These are the cosmetic cleanups from the bits of code that I rereviewed. 1) stringLength now takes a int; the length of the string. Too many callers were allocating with stringLength([]byte(s)) and stringLength only needs to call len(). 2) agent.go now has sendAndReceive to remove logic that was duplicated. 3) We now reject negative DH values 4) We now reject empty packets rather than crashing. R=dave, jonathan.mark.pittman CC=golang-dev https://golang.org/cl/6061052
242 lines
6.3 KiB
Go
242 lines
6.3 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
|
|
|
|
// 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
|
|
}
|