|
@@ -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
|
|
|
|
|
+}
|