Simplify MarshalAuthorizedKey by using the algoName func. Make the algoName func be very specific about supported key types in openssh certs. Generalize some of the commentary that previously mentioned specific key types. R=agl, dave CC=golang-dev https://golang.org/cl/6938067
251 lines
6.6 KiB
Go
251 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() (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.
|
|
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 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
|
|
}
|