| // 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 ssh |
| |
| // References |
| // PROTOCOL.agent: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent |
| |
| import ( |
| "encoding/base64" |
| "errors" |
| "io" |
| ) |
| |
| // See PROTOCOL.agent, section 3. |
| const ( |
| // 3.2 Requests from client to agent for protocol 2 key operations |
| agentRequestIdentities = 11 |
| agentSignRequest = 13 |
| 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.4 Generic replies from agent to client |
| agentFailure = 5 |
| agentSuccess = 6 |
| |
| // 3.6 Replies from agent to client for protocol 2 key operations |
| agentIdentitiesAnswer = 12 |
| agentSignResponse = 14 |
| |
| // 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. |
| |
| type failureAgentMsg struct{} |
| |
| type successAgentMsg struct{} |
| |
| // See PROTOCOL.agent, section 2.5.2. |
| type requestIdentitiesAgentMsg struct{} |
| |
| // See PROTOCOL.agent, section 2.5.2. |
| type identitiesAnswerAgentMsg struct { |
| NumKeys uint32 |
| Keys []byte `ssh:"rest"` |
| } |
| |
| // See PROTOCOL.agent, section 2.6.2. |
| type signRequestAgentMsg struct { |
| KeyBlob []byte |
| Data []byte |
| Flags uint32 |
| } |
| |
| // See PROTOCOL.agent, section 2.6.2. |
| type signResponseAgentMsg struct { |
| SigBlob []byte |
| } |
| |
| // AgentKey represents a protocol 2 key as defined in PROTOCOL.agent, |
| // section 2.5.2. |
| type AgentKey struct { |
| blob []byte |
| Comment string |
| } |
| |
| // 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 (ak *AgentKey) String() string { |
| algo, _, ok := parseString(ak.blob) |
| if !ok { |
| return "ssh: malformed key" |
| } |
| |
| s := string(algo) + " " + base64.StdEncoding.EncodeToString(ak.blob) |
| |
| if ak.Comment != "" { |
| s += " " + ak.Comment |
| } |
| |
| return s |
| } |
| |
| // Key returns an agent's public key as a *rsa.PublicKey, *dsa.PublicKey, or |
| // *OpenSSHCertV01. |
| func (ak *AgentKey) Key() (interface{}, error) { |
| if key, _, ok := parsePubKey(ak.blob); ok { |
| return key, nil |
| } |
| return nil, errors.New("ssh: failed to parse key blob") |
| } |
| |
| func parseAgentKey(in []byte) (out *AgentKey, rest []byte, ok bool) { |
| ak := new(AgentKey) |
| |
| if ak.blob, in, ok = parseString(in); !ok { |
| return |
| } |
| |
| comment, in, ok := parseString(in) |
| if !ok { |
| return |
| } |
| ak.Comment = string(comment) |
| |
| return ak, in, true |
| } |
| |
| // AgentClient provides a means to communicate with an ssh agent process based |
| // on the protocol described in PROTOCOL.agent?rev=1.6. It contains an |
| // embedded io.ReadWriter that is typically represented by using a *net.UnixConn. |
| type AgentClient struct { |
| io.ReadWriter |
| } |
| |
| // sendAndReceive sends req to the agent and waits for a reply. 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 (ac *AgentClient) sendAndReceive(req []byte) (reply interface{}, replyType uint8, err error) { |
| msg := make([]byte, stringLength(len(req))) |
| marshalString(msg, req) |
| if _, err = ac.Write(msg); err != nil { |
| return |
| } |
| |
| var respSizeBuf [4]byte |
| if _, err = io.ReadFull(ac, respSizeBuf[:]); err != nil { |
| return |
| } |
| respSize, _, _ := parseUint32(respSizeBuf[:]) |
| |
| if respSize > maxAgentResponseBytes { |
| err = errors.New("ssh: agent reply too large") |
| return |
| } |
| |
| buf := make([]byte, respSize) |
| if _, err = io.ReadFull(ac, buf); err != nil { |
| return |
| } |
| return unmarshalAgentMsg(buf) |
| } |
| |
| // RequestIdentities queries the agent for protocol 2 keys as defined in |
| // PROTOCOL.agent section 2.5.2. |
| func (ac *AgentClient) RequestIdentities() ([]*AgentKey, error) { |
| req := marshal(agentRequestIdentities, requestIdentitiesAgentMsg{}) |
| |
| msg, msgType, err := ac.sendAndReceive(req) |
| if err != nil { |
| return nil, err |
| } |
| |
| switch msg := msg.(type) { |
| case *identitiesAnswerAgentMsg: |
| if msg.NumKeys > maxAgentResponseBytes/8 { |
| return nil, errors.New("ssh: too many keys in agent reply") |
| } |
| keys := make([]*AgentKey, msg.NumKeys) |
| data := msg.Keys |
| for i := uint32(0); i < msg.NumKeys; i++ { |
| var key *AgentKey |
| var ok bool |
| if key, data, ok = parseAgentKey(data); !ok { |
| return nil, ParseError{agentIdentitiesAnswer} |
| } |
| keys[i] = key |
| } |
| return keys, nil |
| case *failureAgentMsg: |
| return nil, errors.New("ssh: failed to list keys") |
| } |
| return nil, UnexpectedMessageError{agentIdentitiesAnswer, msgType} |
| } |
| |
| // SignRequest requests the signing of data by the agent using a protocol 2 key |
| // as defined in PROTOCOL.agent section 2.6.2. Supported key types include |
| // *rsa.PublicKey, *dsa.PublicKey, *OpenSSHCertV01. |
| func (ac *AgentClient) SignRequest(key interface{}, data []byte) ([]byte, error) { |
| req := marshal(agentSignRequest, signRequestAgentMsg{ |
| KeyBlob: serializePublickey(key), |
| Data: data, |
| }) |
| |
| msg, msgType, err := ac.sendAndReceive(req) |
| if err != nil { |
| return nil, err |
| } |
| |
| switch msg := msg.(type) { |
| case *signResponseAgentMsg: |
| return msg.SigBlob, nil |
| case *failureAgentMsg: |
| return nil, errors.New("ssh: failed to sign challenge") |
| } |
| return nil, UnexpectedMessageError{agentSignResponse, msgType} |
| } |
| |
| // unmarshalAgentMsg parses an agent message in packet, returning the parsed |
| // form and the message type of packet. |
| func unmarshalAgentMsg(packet []byte) (interface{}, uint8, error) { |
| if len(packet) < 1 { |
| return nil, 0, ParseError{0} |
| } |
| var msg interface{} |
| switch packet[0] { |
| case agentFailure: |
| msg = new(failureAgentMsg) |
| case agentSuccess: |
| msg = new(successAgentMsg) |
| case agentIdentitiesAnswer: |
| msg = new(identitiesAnswerAgentMsg) |
| case agentSignResponse: |
| msg = new(signResponseAgentMsg) |
| default: |
| return nil, 0, UnexpectedMessageError{0, packet[0]} |
| } |
| if err := unmarshal(msg, packet, packet[0]); err != nil { |
| return nil, 0, err |
| } |
| return msg, packet[0], nil |
| } |