| // Copyright 2012 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package agent implements the ssh-agent protocol, and provides both |
| // a client and a server. The client can talk to a standard ssh-agent |
| // that uses UNIX sockets, and one could implement an alternative |
| // ssh-agent process using the sample server. |
| // |
| // References: |
| // [PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD |
| package agent // import "golang.org/x/crypto/ssh/agent" |
| |
| import ( |
| "bytes" |
| "crypto/dsa" |
| "crypto/ecdsa" |
| "crypto/elliptic" |
| "crypto/rsa" |
| "encoding/base64" |
| "encoding/binary" |
| "errors" |
| "fmt" |
| "io" |
| "math/big" |
| "sync" |
| |
| "golang.org/x/crypto/ssh" |
| ) |
| |
| // Agent represents the capabilities of an ssh-agent. |
| type Agent interface { |
| // List returns the identities known to the agent. |
| List() ([]*Key, error) |
| |
| // Sign has the agent sign the data using a protocol 2 key as defined |
| // in [PROTOCOL.agent] section 2.6.2. |
| Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) |
| |
| // Add adds a private key to the agent. |
| Add(key AddedKey) error |
| |
| // Remove removes all identities with the given public key. |
| Remove(key ssh.PublicKey) error |
| |
| // RemoveAll removes all identities. |
| RemoveAll() error |
| |
| // Lock locks the agent. Sign and Remove will fail, and List will empty an empty list. |
| Lock(passphrase []byte) error |
| |
| // Unlock undoes the effect of Lock |
| Unlock(passphrase []byte) error |
| |
| // Signers returns signers for all the known keys. |
| Signers() ([]ssh.Signer, error) |
| } |
| |
| // AddedKey describes an SSH key to be added to an Agent. |
| type AddedKey struct { |
| // PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or |
| // *ecdsa.PrivateKey, which will be inserted into the agent. |
| PrivateKey interface{} |
| // Certificate, if not nil, is communicated to the agent and will be |
| // stored with the key. |
| Certificate *ssh.Certificate |
| // Comment is an optional, free-form string. |
| Comment string |
| // LifetimeSecs, if not zero, is the number of seconds that the |
| // agent will store the key for. |
| LifetimeSecs uint32 |
| // ConfirmBeforeUse, if true, requests that the agent confirm with the |
| // user before each use of this key. |
| ConfirmBeforeUse bool |
| } |
| |
| // See [PROTOCOL.agent], section 3. |
| const ( |
| agentRequestV1Identities = 1 |
| |
| // 3.2 Requests from client to agent for protocol 2 key operations |
| agentAddIdentity = 17 |
| agentRemoveIdentity = 18 |
| agentRemoveAllIdentities = 19 |
| agentAddIdConstrained = 25 |
| |
| // 3.3 Key-type independent requests from client to agent |
| agentAddSmartcardKey = 20 |
| agentRemoveSmartcardKey = 21 |
| agentLock = 22 |
| agentUnlock = 23 |
| agentAddSmartcardKeyConstrained = 26 |
| |
| // 3.7 Key constraint identifiers |
| agentConstrainLifetime = 1 |
| agentConstrainConfirm = 2 |
| ) |
| |
| // maxAgentResponseBytes is the maximum agent reply size that is accepted. This |
| // is a sanity check, not a limit in the spec. |
| const maxAgentResponseBytes = 16 << 20 |
| |
| // Agent messages: |
| // These structures mirror the wire format of the corresponding ssh agent |
| // messages found in [PROTOCOL.agent]. |
| |
| // 3.4 Generic replies from agent to client |
| const agentFailure = 5 |
| |
| type failureAgentMsg struct{} |
| |
| const agentSuccess = 6 |
| |
| type successAgentMsg struct{} |
| |
| // See [PROTOCOL.agent], section 2.5.2. |
| const agentRequestIdentities = 11 |
| |
| type requestIdentitiesAgentMsg struct{} |
| |
| // See [PROTOCOL.agent], section 2.5.2. |
| const agentIdentitiesAnswer = 12 |
| |
| type identitiesAnswerAgentMsg struct { |
| NumKeys uint32 `sshtype:"12"` |
| Keys []byte `ssh:"rest"` |
| } |
| |
| // See [PROTOCOL.agent], section 2.6.2. |
| const agentSignRequest = 13 |
| |
| type signRequestAgentMsg struct { |
| KeyBlob []byte `sshtype:"13"` |
| Data []byte |
| Flags uint32 |
| } |
| |
| // See [PROTOCOL.agent], section 2.6.2. |
| |
| // 3.6 Replies from agent to client for protocol 2 key operations |
| const agentSignResponse = 14 |
| |
| type signResponseAgentMsg struct { |
| SigBlob []byte `sshtype:"14"` |
| } |
| |
| type publicKey struct { |
| Format string |
| Rest []byte `ssh:"rest"` |
| } |
| |
| // Key represents a protocol 2 public key as defined in |
| // [PROTOCOL.agent], section 2.5.2. |
| type Key struct { |
| Format string |
| Blob []byte |
| Comment string |
| } |
| |
| func clientErr(err error) error { |
| return fmt.Errorf("agent: client error: %v", err) |
| } |
| |
| // String returns the storage form of an agent key with the format, base64 |
| // encoded serialized key, and the comment if it is not empty. |
| func (k *Key) String() string { |
| s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob) |
| |
| if k.Comment != "" { |
| s += " " + k.Comment |
| } |
| |
| return s |
| } |
| |
| // Type returns the public key type. |
| func (k *Key) Type() string { |
| return k.Format |
| } |
| |
| // Marshal returns key blob to satisfy the ssh.PublicKey interface. |
| func (k *Key) Marshal() []byte { |
| return k.Blob |
| } |
| |
| // Verify satisfies the ssh.PublicKey interface, but is not |
| // implemented for agent keys. |
| func (k *Key) Verify(data []byte, sig *ssh.Signature) error { |
| return errors.New("agent: agent key does not know how to verify") |
| } |
| |
| type wireKey struct { |
| Format string |
| Rest []byte `ssh:"rest"` |
| } |
| |
| func parseKey(in []byte) (out *Key, rest []byte, err error) { |
| var record struct { |
| Blob []byte |
| Comment string |
| Rest []byte `ssh:"rest"` |
| } |
| |
| if err := ssh.Unmarshal(in, &record); err != nil { |
| return nil, nil, err |
| } |
| |
| var wk wireKey |
| if err := ssh.Unmarshal(record.Blob, &wk); err != nil { |
| return nil, nil, err |
| } |
| |
| return &Key{ |
| Format: wk.Format, |
| Blob: record.Blob, |
| Comment: record.Comment, |
| }, record.Rest, nil |
| } |
| |
| // client is a client for an ssh-agent process. |
| type client struct { |
| // conn is typically a *net.UnixConn |
| conn io.ReadWriter |
| // mu is used to prevent concurrent access to the agent |
| mu sync.Mutex |
| } |
| |
| // NewClient returns an Agent that talks to an ssh-agent process over |
| // the given connection. |
| func NewClient(rw io.ReadWriter) Agent { |
| return &client{conn: rw} |
| } |
| |
| // call sends an RPC to the agent. On success, the reply is |
| // unmarshaled into reply and replyType is set to the first byte of |
| // the reply, which contains the type of the message. |
| func (c *client) call(req []byte) (reply interface{}, err error) { |
| c.mu.Lock() |
| defer c.mu.Unlock() |
| |
| msg := make([]byte, 4+len(req)) |
| binary.BigEndian.PutUint32(msg, uint32(len(req))) |
| copy(msg[4:], req) |
| if _, err = c.conn.Write(msg); err != nil { |
| return nil, clientErr(err) |
| } |
| |
| var respSizeBuf [4]byte |
| if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil { |
| return nil, clientErr(err) |
| } |
| respSize := binary.BigEndian.Uint32(respSizeBuf[:]) |
| if respSize > maxAgentResponseBytes { |
| return nil, clientErr(err) |
| } |
| |
| buf := make([]byte, respSize) |
| if _, err = io.ReadFull(c.conn, buf); err != nil { |
| return nil, clientErr(err) |
| } |
| reply, err = unmarshal(buf) |
| if err != nil { |
| return nil, clientErr(err) |
| } |
| return reply, err |
| } |
| |
| func (c *client) simpleCall(req []byte) error { |
| resp, err := c.call(req) |
| if err != nil { |
| return err |
| } |
| if _, ok := resp.(*successAgentMsg); ok { |
| return nil |
| } |
| return errors.New("agent: failure") |
| } |
| |
| func (c *client) RemoveAll() error { |
| return c.simpleCall([]byte{agentRemoveAllIdentities}) |
| } |
| |
| func (c *client) Remove(key ssh.PublicKey) error { |
| req := ssh.Marshal(&agentRemoveIdentityMsg{ |
| KeyBlob: key.Marshal(), |
| }) |
| return c.simpleCall(req) |
| } |
| |
| func (c *client) Lock(passphrase []byte) error { |
| req := ssh.Marshal(&agentLockMsg{ |
| Passphrase: passphrase, |
| }) |
| return c.simpleCall(req) |
| } |
| |
| func (c *client) Unlock(passphrase []byte) error { |
| req := ssh.Marshal(&agentUnlockMsg{ |
| Passphrase: passphrase, |
| }) |
| return c.simpleCall(req) |
| } |
| |
| // List returns the identities known to the agent. |
| func (c *client) List() ([]*Key, error) { |
| // see [PROTOCOL.agent] section 2.5.2. |
| req := []byte{agentRequestIdentities} |
| |
| msg, err := c.call(req) |
| if err != nil { |
| return nil, err |
| } |
| |
| switch msg := msg.(type) { |
| case *identitiesAnswerAgentMsg: |
| if msg.NumKeys > maxAgentResponseBytes/8 { |
| return nil, errors.New("agent: too many keys in agent reply") |
| } |
| keys := make([]*Key, msg.NumKeys) |
| data := msg.Keys |
| for i := uint32(0); i < msg.NumKeys; i++ { |
| var key *Key |
| var err error |
| if key, data, err = parseKey(data); err != nil { |
| return nil, err |
| } |
| keys[i] = key |
| } |
| return keys, nil |
| case *failureAgentMsg: |
| return nil, errors.New("agent: failed to list keys") |
| } |
| panic("unreachable") |
| } |
| |
| // Sign has the agent sign the data using a protocol 2 key as defined |
| // in [PROTOCOL.agent] section 2.6.2. |
| func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { |
| req := ssh.Marshal(signRequestAgentMsg{ |
| KeyBlob: key.Marshal(), |
| Data: data, |
| }) |
| |
| msg, err := c.call(req) |
| if err != nil { |
| return nil, err |
| } |
| |
| switch msg := msg.(type) { |
| case *signResponseAgentMsg: |
| var sig ssh.Signature |
| if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil { |
| return nil, err |
| } |
| |
| return &sig, nil |
| case *failureAgentMsg: |
| return nil, errors.New("agent: failed to sign challenge") |
| } |
| panic("unreachable") |
| } |
| |
| // unmarshal parses an agent message in packet, returning the parsed |
| // form and the message type of packet. |
| func unmarshal(packet []byte) (interface{}, error) { |
| if len(packet) < 1 { |
| return nil, errors.New("agent: empty packet") |
| } |
| var msg interface{} |
| switch packet[0] { |
| case agentFailure: |
| return new(failureAgentMsg), nil |
| case agentSuccess: |
| return new(successAgentMsg), nil |
| case agentIdentitiesAnswer: |
| msg = new(identitiesAnswerAgentMsg) |
| case agentSignResponse: |
| msg = new(signResponseAgentMsg) |
| default: |
| return nil, fmt.Errorf("agent: unknown type tag %d", packet[0]) |
| } |
| if err := ssh.Unmarshal(packet, msg); err != nil { |
| return nil, err |
| } |
| return msg, nil |
| } |
| |
| type rsaKeyMsg struct { |
| Type string `sshtype:"17"` |
| N *big.Int |
| E *big.Int |
| D *big.Int |
| Iqmp *big.Int // IQMP = Inverse Q Mod P |
| P *big.Int |
| Q *big.Int |
| Comments string |
| Constraints []byte `ssh:"rest"` |
| } |
| |
| type dsaKeyMsg struct { |
| Type string `sshtype:"17"` |
| P *big.Int |
| Q *big.Int |
| G *big.Int |
| Y *big.Int |
| X *big.Int |
| Comments string |
| Constraints []byte `ssh:"rest"` |
| } |
| |
| type ecdsaKeyMsg struct { |
| Type string `sshtype:"17"` |
| Curve string |
| KeyBytes []byte |
| D *big.Int |
| Comments string |
| Constraints []byte `ssh:"rest"` |
| } |
| |
| // Insert adds a private key to the agent. |
| func (c *client) insertKey(s interface{}, comment string, constraints []byte) error { |
| var req []byte |
| switch k := s.(type) { |
| case *rsa.PrivateKey: |
| if len(k.Primes) != 2 { |
| return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) |
| } |
| k.Precompute() |
| req = ssh.Marshal(rsaKeyMsg{ |
| Type: ssh.KeyAlgoRSA, |
| N: k.N, |
| E: big.NewInt(int64(k.E)), |
| D: k.D, |
| Iqmp: k.Precomputed.Qinv, |
| P: k.Primes[0], |
| Q: k.Primes[1], |
| Comments: comment, |
| Constraints: constraints, |
| }) |
| case *dsa.PrivateKey: |
| req = ssh.Marshal(dsaKeyMsg{ |
| Type: ssh.KeyAlgoDSA, |
| P: k.P, |
| Q: k.Q, |
| G: k.G, |
| Y: k.Y, |
| X: k.X, |
| Comments: comment, |
| Constraints: constraints, |
| }) |
| case *ecdsa.PrivateKey: |
| nistID := fmt.Sprintf("nistp%d", k.Params().BitSize) |
| req = ssh.Marshal(ecdsaKeyMsg{ |
| Type: "ecdsa-sha2-" + nistID, |
| Curve: nistID, |
| KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y), |
| D: k.D, |
| Comments: comment, |
| Constraints: constraints, |
| }) |
| default: |
| return fmt.Errorf("agent: unsupported key type %T", s) |
| } |
| |
| // if constraints are present then the message type needs to be changed. |
| if len(constraints) != 0 { |
| req[0] = agentAddIdConstrained |
| } |
| |
| resp, err := c.call(req) |
| if err != nil { |
| return err |
| } |
| if _, ok := resp.(*successAgentMsg); ok { |
| return nil |
| } |
| return errors.New("agent: failure") |
| } |
| |
| type rsaCertMsg struct { |
| Type string `sshtype:"17"` |
| CertBytes []byte |
| D *big.Int |
| Iqmp *big.Int // IQMP = Inverse Q Mod P |
| P *big.Int |
| Q *big.Int |
| Comments string |
| Constraints []byte `ssh:"rest"` |
| } |
| |
| type dsaCertMsg struct { |
| Type string `sshtype:"17"` |
| CertBytes []byte |
| X *big.Int |
| Comments string |
| Constraints []byte `ssh:"rest"` |
| } |
| |
| type ecdsaCertMsg struct { |
| Type string `sshtype:"17"` |
| CertBytes []byte |
| D *big.Int |
| Comments string |
| Constraints []byte `ssh:"rest"` |
| } |
| |
| // Insert adds a private key to the agent. If a certificate is given, |
| // that certificate is added instead as public key. |
| func (c *client) Add(key AddedKey) error { |
| var constraints []byte |
| |
| if secs := key.LifetimeSecs; secs != 0 { |
| constraints = append(constraints, agentConstrainLifetime) |
| |
| var secsBytes [4]byte |
| binary.BigEndian.PutUint32(secsBytes[:], secs) |
| constraints = append(constraints, secsBytes[:]...) |
| } |
| |
| if key.ConfirmBeforeUse { |
| constraints = append(constraints, agentConstrainConfirm) |
| } |
| |
| if cert := key.Certificate; cert == nil { |
| return c.insertKey(key.PrivateKey, key.Comment, constraints) |
| } else { |
| return c.insertCert(key.PrivateKey, cert, key.Comment, constraints) |
| } |
| } |
| |
| func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error { |
| var req []byte |
| switch k := s.(type) { |
| case *rsa.PrivateKey: |
| if len(k.Primes) != 2 { |
| return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) |
| } |
| k.Precompute() |
| req = ssh.Marshal(rsaCertMsg{ |
| Type: cert.Type(), |
| CertBytes: cert.Marshal(), |
| D: k.D, |
| Iqmp: k.Precomputed.Qinv, |
| P: k.Primes[0], |
| Q: k.Primes[1], |
| Comments: comment, |
| Constraints: constraints, |
| }) |
| case *dsa.PrivateKey: |
| req = ssh.Marshal(dsaCertMsg{ |
| Type: cert.Type(), |
| CertBytes: cert.Marshal(), |
| X: k.X, |
| Comments: comment, |
| }) |
| case *ecdsa.PrivateKey: |
| req = ssh.Marshal(ecdsaCertMsg{ |
| Type: cert.Type(), |
| CertBytes: cert.Marshal(), |
| D: k.D, |
| Comments: comment, |
| }) |
| default: |
| return fmt.Errorf("agent: unsupported key type %T", s) |
| } |
| |
| // if constraints are present then the message type needs to be changed. |
| if len(constraints) != 0 { |
| req[0] = agentAddIdConstrained |
| } |
| |
| signer, err := ssh.NewSignerFromKey(s) |
| if err != nil { |
| return err |
| } |
| if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 { |
| return errors.New("agent: signer and cert have different public key") |
| } |
| |
| resp, err := c.call(req) |
| if err != nil { |
| return err |
| } |
| if _, ok := resp.(*successAgentMsg); ok { |
| return nil |
| } |
| return errors.New("agent: failure") |
| } |
| |
| // Signers provides a callback for client authentication. |
| func (c *client) Signers() ([]ssh.Signer, error) { |
| keys, err := c.List() |
| if err != nil { |
| return nil, err |
| } |
| |
| var result []ssh.Signer |
| for _, k := range keys { |
| result = append(result, &agentKeyringSigner{c, k}) |
| } |
| return result, nil |
| } |
| |
| type agentKeyringSigner struct { |
| agent *client |
| pub ssh.PublicKey |
| } |
| |
| func (s *agentKeyringSigner) PublicKey() ssh.PublicKey { |
| return s.pub |
| } |
| |
| func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { |
| // The agent has its own entropy source, so the rand argument is ignored. |
| return s.agent.Sign(s.pub, data) |
| } |