go.crypto/ssh: Initial checkin of ssh agent support.
R=agl, dave, djm CC=golang-dev https://golang.org/cl/5695081
This commit is contained in:
committed by
Adam Langley
parent
6de97b525f
commit
67456e2aa3
+244
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user