woodpecker/vendor/code.google.com/p/go.crypto/ssh/agent.go
2015-09-29 18:21:17 -07:00

250 lines
6.6 KiB
Go

// 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
import (
"encoding/base64"
"errors"
"io"
"sync"
)
// 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 one of the supported key or certificate types.
func (ak *AgentKey) Key() (PublicKey, error) {
if key, _, ok := ParsePublicKey(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.
type AgentClient struct {
// conn is typically represented by using a *net.UnixConn
conn io.ReadWriter
// mu is used to prevent concurrent access to the agent
mu sync.Mutex
}
// NewAgentClient creates and returns a new *AgentClient using the
// passed in io.ReadWriter as a connection to a ssh agent.
func NewAgentClient(rw io.ReadWriter) *AgentClient {
return &AgentClient{conn: rw}
}
// 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) {
// ac.mu prevents multiple, concurrent requests. Since the agent is typically
// on the same machine, we don't attempt to pipeline the requests.
ac.mu.Lock()
defer ac.mu.Unlock()
msg := make([]byte, stringLength(len(req)))
marshalString(msg, req)
if _, err = ac.conn.Write(msg); err != nil {
return
}
var respSizeBuf [4]byte
if _, err = io.ReadFull(ac.conn, 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.conn, 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.
func (ac *AgentClient) SignRequest(key PublicKey, data []byte) ([]byte, error) {
req := marshal(agentSignRequest, signRequestAgentMsg{
KeyBlob: MarshalPublicKey(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
}