From 67456e2aa3d9fe424b5d171393f8bb42346ad5bf Mon Sep 17 00:00:00 2001 From: Jonathan Pittman Date: Fri, 2 Mar 2012 14:16:57 -0500 Subject: [PATCH] go.crypto/ssh: Initial checkin of ssh agent support. R=agl, dave, djm CC=golang-dev https://golang.org/cl/5695081 --- ssh/agent.go | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 ssh/agent.go diff --git a/ssh/agent.go b/ssh/agent.go new file mode 100644 index 0000000..f63ce6d --- /dev/null +++ b/ssh/agent.go @@ -0,0 +1,244 @@ +// 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 +}