├── .gitignore ├── README.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | ssh-agent-tool 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssh-agent OAUTH 2.0 bridge 2 | 3 | Nearly everyone who does server infrastructure has an ssh key. This is a strong 4 | public-key based identity. Not only that but it is often the only identity that 5 | is available when people remote into a host via ssh thanks to "ssh-agent 6 | forwarding". 7 | 8 | This tool lets you play with the ssh-agent and shows how it can be used to sign 9 | and verify challenge-responses. Eventually I want to use this code as an 10 | identity exchange from say SSH public key to OAUTH 2.0 JWT. 11 | 12 | ## Manual 13 | 14 | ### List Public Keys 15 | 16 | ``` 17 | $ ssh-agent-tool list-keys 18 | 00: ssh-rsa AAAAB3NzaC1yc2EAAAA...y+1EoNzsPkY2aw== /Users/philips/.ssh/id_rsa 19 | 01: ssh-rsa AAAAB3NzaC1yc2EAAAA...oYVfmKAMjmKkV /Users/philips/.ssh/id_rsa_old 20 | ``` 21 | 22 | ### Sign 23 | 24 | ``` 25 | $ ssh-agent-tool sign $(echo foobar | base64) 26 | 00: ssh-rsa fStlJeCQW8...Glu5ZHNuc= 27 | 01: ssh-rsa gtLeSrdor...rgQb9Qp/SDw== 28 | ``` 29 | 30 | ### Challenge Verify 31 | 32 | ``` 33 | $ ./ssh-agent-tool verify $(echo foobar | base64) ssh-rsa fStlJeCQW8...Glu5ZHNuc= 34 | 00: verified: true 35 | 01: verified: false 36 | $ ./ssh-agent-tool verify $(echo foobar | base64) ssh-rsa gtLeSrdor...rgQb9Qp/SDw== 37 | 00: verified: false 38 | 01: verified: true 39 | ``` 40 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "golang.org/x/crypto/ssh" 6 | "golang.org/x/crypto/ssh/agent" 7 | 8 | "encoding/base64" 9 | "errors" 10 | "fmt" 11 | "net" 12 | "os" 13 | "text/tabwriter" 14 | ) 15 | 16 | // agentFromAuthSock returns an Agent that talks to the local ssh-agent on SSH_AUTH_SOCK 17 | func agentFromAuthSock() (agent.Agent, error) { 18 | sock := os.Getenv("SSH_AUTH_SOCK") 19 | if sock == "" { 20 | return nil, errors.New("SSH_AUTH_SOCK environment variable is not set. Verify ssh-agent is running. See https://github.com/coreos/fleet/blob/master/Documentation/using-the-client.md for help.") 21 | } 22 | 23 | a, err := net.Dial("unix", sock) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return agent.NewClient(a), nil 29 | } 30 | 31 | func listKeys(a agent.Agent) { 32 | keys, err := a.List() 33 | if err != nil { 34 | fmt.Println(err) 35 | os.Exit(1) 36 | } 37 | 38 | w := new(tabwriter.Writer) 39 | // Format in tab-separated columns with a tab stop of 8. 40 | w.Init(os.Stdout, 0, 8, 0, '\t', 0) 41 | 42 | for i, k := range keys { 43 | fmt.Fprintf(w, "%02d: %s\t%s\t%s\n", i, k.Format, base64.StdEncoding.EncodeToString(k.Blob), k.Comment) 44 | w.Flush() 45 | } 46 | } 47 | 48 | func sign(a agent.Agent, b string) { 49 | keys, err := a.List() 50 | if err != nil { 51 | fmt.Println(err) 52 | os.Exit(1) 53 | } 54 | 55 | data, err := base64.StdEncoding.DecodeString(b) 56 | if err != nil { 57 | fmt.Println(err) 58 | os.Exit(1) 59 | } 60 | 61 | w := new(tabwriter.Writer) 62 | // Format in tab-separated columns with a tab stop of 8. 63 | w.Init(os.Stdout, 0, 8, 0, '\t', 0) 64 | 65 | for i, k := range keys { 66 | sig, err := a.Sign(k, data) 67 | if err != nil { 68 | fmt.Println(err) 69 | os.Exit(1) 70 | } 71 | fmt.Fprintf(w, "%02d: %s %s\n", i, sig.Format, base64.StdEncoding.EncodeToString(sig.Blob)) 72 | w.Flush() 73 | } 74 | } 75 | 76 | func verify(a agent.Agent, b string, sFormat string, s string) { 77 | keys, err := a.List() 78 | if err != nil { 79 | fmt.Println(err) 80 | os.Exit(1) 81 | } 82 | 83 | data, err := base64.StdEncoding.DecodeString(b) 84 | if err != nil { 85 | fmt.Println(err) 86 | os.Exit(1) 87 | } 88 | 89 | sigData, err := base64.StdEncoding.DecodeString(s) 90 | if err != nil { 91 | fmt.Println(err) 92 | os.Exit(1) 93 | } 94 | 95 | sig := &ssh.Signature{sFormat, sigData} 96 | 97 | w := new(tabwriter.Writer) 98 | // Format in tab-separated columns with a tab stop of 8. 99 | w.Init(os.Stdout, 0, 8, 0, '\t', 0) 100 | 101 | for i, k := range keys { 102 | mKey := k.Marshal() 103 | verifyKey, err := ssh.ParsePublicKey(mKey) 104 | if err != nil { 105 | fmt.Println(err.Error()) 106 | os.Exit(1) 107 | } 108 | ok := true 109 | err = verifyKey.Verify(data, sig) 110 | if err != nil { 111 | ok = false 112 | } 113 | fmt.Fprintf(w, "%02d: verified: %t\n", i, ok) 114 | w.Flush() 115 | } 116 | } 117 | 118 | func main() { 119 | a, err := agentFromAuthSock() 120 | if err != nil { 121 | fmt.Println(err) 122 | os.Exit(1) 123 | } 124 | 125 | var toolCmd = &cobra.Command{ 126 | Use: "ssh-agent-tool", 127 | Short: "ssh-agent-tool is a swiss army knife for an ssh-agent", 128 | Long: `ssh clients have an agent for protecting and forwarding access to challenge-response 129 | of public keys. This tool exercises the agent and exposes these 130 | features for use by other tools besides ssh clients.`, 131 | } 132 | 133 | var listKeysCmd = &cobra.Command{ 134 | Use: "list-keys", 135 | Short: "List all public keys added in the agent", 136 | Run: func(cmd *cobra.Command, args []string) { 137 | listKeys(a) 138 | }, 139 | } 140 | 141 | var signCmd = &cobra.Command{ 142 | Use: "sign", 143 | Short: "Sign a base64 encoded string", 144 | Run: func(cmd *cobra.Command, args []string) { 145 | if len(args) != 1 { 146 | fmt.Println("sign takes a single base64 encoded string to sign") 147 | os.Exit(1) 148 | } 149 | sign(a, args[0]) 150 | }, 151 | } 152 | 153 | var verifyCmd = &cobra.Command{ 154 | Use: "verify", 155 | Short: "Verify a base64 encoded string and signature", 156 | Run: func(cmd *cobra.Command, args []string) { 157 | if len(args) != 3 { 158 | fmt.Println("verify takes a single base64 encoded string verify, a signature type, and a base64 encoded signature") 159 | os.Exit(1) 160 | } 161 | verify(a, args[0], args[1], args[2]) 162 | }, 163 | } 164 | 165 | toolCmd.AddCommand(listKeysCmd) 166 | toolCmd.AddCommand(signCmd) 167 | toolCmd.AddCommand(verifyCmd) 168 | toolCmd.Execute() 169 | } 170 | --------------------------------------------------------------------------------