| // 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" |
| "fmt" |
| "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 |
| ) |
| |
| // 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 "malformed key" |
| } |
| |
| algoName := string(algo) |
| b64EncKey := base64.StdEncoding.EncodeToString(ak.blob) |
| comment := "" |
| |
| if ak.Comment != "" { |
| comment = " " + ak.Comment |
| } |
| |
| return fmt.Sprintf("%s %s%s", algoName, b64EncKey, comment) |
| } |
| |
| // 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 |
| } |
| |
| func (ac *AgentClient) sendRequest(req []byte) error { |
| msg := make([]byte, stringLength(req)) |
| marshalString(msg, req) |
| if _, err := ac.Write(msg); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func (ac *AgentClient) readResponse() ([]byte, error) { |
| var respSizeBuf [4]byte |
| if _, err := io.ReadFull(ac, respSizeBuf[:]); err != nil { |
| return nil, err |
| } |
| |
| respSize, _, ok := parseUint32(respSizeBuf[:]) |
| if !ok { |
| return nil, errors.New("ssh: failure to parse response size") |
| } |
| |
| buf := make([]byte, respSize) |
| if _, err := io.ReadFull(ac, buf); err != nil { |
| return nil, err |
| } |
| return buf, nil |
| } |
| |
| // 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{}) |
| if err := ac.sendRequest(req); err != nil { |
| return nil, err |
| } |
| |
| resp, err := ac.readResponse() |
| if err != nil { |
| return nil, err |
| } |
| |
| switch msg := decodeAgentMsg(resp).(type) { |
| case *identitiesAnswerAgentMsg: |
| 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.") |
| case ParseError, UnexpectedMessageError: |
| return nil, msg.(error) |
| } |
| return nil, UnexpectedMessageError{agentIdentitiesAnswer, resp[0]} |
| } |
| |
| // 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, |
| }) |
| if err := ac.sendRequest(req); err != nil { |
| return nil, err |
| } |
| |
| resp, err := ac.readResponse() |
| if err != nil { |
| return nil, err |
| } |
| |
| switch msg := decodeAgentMsg(resp).(type) { |
| case *signResponseAgentMsg: |
| return msg.SigBlob, nil |
| case *failureAgentMsg: |
| return nil, errors.New("ssh: failed to sign challenge") |
| case ParseError, UnexpectedMessageError: |
| return nil, msg.(error) |
| } |
| return nil, UnexpectedMessageError{agentSignResponse, resp[0]} |
| } |
| |
| func decodeAgentMsg(packet []byte) interface{} { |
| if len(packet) < 1 { |
| return 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 UnexpectedMessageError{0, packet[0]} |
| } |
| if err := unmarshal(msg, packet, packet[0]); err != nil { |
| return err |
| } |
| return msg |
| } |