Просмотр исходного кода

go.crypto/ssh: Initial checkin of ssh agent support.

R=agl, dave, djm
CC=golang-dev
https://golang.org/cl/5695081
Jonathan Pittman 13 лет назад
Родитель
Сommit
67456e2aa3
1 измененных файлов с 244 добавлено и 0 удалено
  1. 244 0
      ssh/agent.go

+ 244 - 0
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
+}