|
@@ -25,10 +25,22 @@ import (
|
|
|
"math/big"
|
|
"math/big"
|
|
|
"sync"
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
+ "crypto"
|
|
|
"golang.org/x/crypto/ed25519"
|
|
"golang.org/x/crypto/ed25519"
|
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/crypto/ssh"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+// SignatureFlags represent additional flags that can be passed to the signature
|
|
|
|
|
+// requests an defined in [PROTOCOL.agent] section 4.5.1.
|
|
|
|
|
+type SignatureFlags uint32
|
|
|
|
|
+
|
|
|
|
|
+// SignatureFlag values as defined in [PROTOCOL.agent] section 5.3.
|
|
|
|
|
+const (
|
|
|
|
|
+ SignatureFlagReserved SignatureFlags = 1 << iota
|
|
|
|
|
+ SignatureFlagRsaSha256
|
|
|
|
|
+ SignatureFlagRsaSha512
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
// Agent represents the capabilities of an ssh-agent.
|
|
// Agent represents the capabilities of an ssh-agent.
|
|
|
type Agent interface {
|
|
type Agent interface {
|
|
|
// List returns the identities known to the agent.
|
|
// List returns the identities known to the agent.
|
|
@@ -57,6 +69,26 @@ type Agent interface {
|
|
|
Signers() ([]ssh.Signer, error)
|
|
Signers() ([]ssh.Signer, error)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+type ExtendedAgent interface {
|
|
|
|
|
+ Agent
|
|
|
|
|
+
|
|
|
|
|
+ // SignWithFlags signs like Sign, but allows for additional flags to be sent/received
|
|
|
|
|
+ SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error)
|
|
|
|
|
+
|
|
|
|
|
+ // Extension processes a custom extension request. Standard-compliant agents are not
|
|
|
|
|
+ // required to support any extensions, but this method allows agents to implement
|
|
|
|
|
+ // vendor-specific methods or add experimental features. See [PROTOCOL.agent] section 4.7.
|
|
|
|
|
+ // If agent extensions are unsupported entirely this method MUST return an
|
|
|
|
|
+ // ErrExtensionUnsupported error. Similarly, if just the specific extensionType in
|
|
|
|
|
+ // the request is unsupported by the agent then ErrExtensionUnsupported MUST be
|
|
|
|
|
+ // returned.
|
|
|
|
|
+ //
|
|
|
|
|
+ // In the case of success, since [PROTOCOL.agent] section 4.7 specifies that the contents
|
|
|
|
|
+ // of the response are unspecified (including the type of the message), the complete
|
|
|
|
|
+ // response will be returned as a []byte slice, including the "type" byte of the message.
|
|
|
|
|
+ Extension(extensionType string, contents []byte) ([]byte, error)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// ConstraintExtension describes an optional constraint defined by users.
|
|
// ConstraintExtension describes an optional constraint defined by users.
|
|
|
type ConstraintExtension struct {
|
|
type ConstraintExtension struct {
|
|
|
// ExtensionName consist of a UTF-8 string suffixed by the
|
|
// ExtensionName consist of a UTF-8 string suffixed by the
|
|
@@ -179,6 +211,23 @@ type constrainExtensionAgentMsg struct {
|
|
|
Rest []byte `ssh:"rest"`
|
|
Rest []byte `ssh:"rest"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// See [PROTOCOL.agent], section 4.7
|
|
|
|
|
+const agentExtension = 27
|
|
|
|
|
+const agentExtensionFailure = 28
|
|
|
|
|
+
|
|
|
|
|
+// ErrExtensionUnsupported indicates that an extension defined in
|
|
|
|
|
+// [PROTOCOL.agent] section 4.7 is unsupported by the agent. Specifically this
|
|
|
|
|
+// error indicates that the agent returned a standard SSH_AGENT_FAILURE message
|
|
|
|
|
+// as the result of a SSH_AGENTC_EXTENSION request. Note that the protocol
|
|
|
|
|
+// specification (and therefore this error) does not distinguish between a
|
|
|
|
|
+// specific extension being unsupported and extensions being unsupported entirely.
|
|
|
|
|
+var ErrExtensionUnsupported = errors.New("agent: extension unsupported")
|
|
|
|
|
+
|
|
|
|
|
+type extensionAgentMsg struct {
|
|
|
|
|
+ ExtensionType string `sshtype:"27"`
|
|
|
|
|
+ Contents []byte
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// Key represents a protocol 2 public key as defined in
|
|
// Key represents a protocol 2 public key as defined in
|
|
|
// [PROTOCOL.agent], section 2.5.2.
|
|
// [PROTOCOL.agent], section 2.5.2.
|
|
|
type Key struct {
|
|
type Key struct {
|
|
@@ -260,7 +309,7 @@ type client struct {
|
|
|
|
|
|
|
|
// NewClient returns an Agent that talks to an ssh-agent process over
|
|
// NewClient returns an Agent that talks to an ssh-agent process over
|
|
|
// the given connection.
|
|
// the given connection.
|
|
|
-func NewClient(rw io.ReadWriter) Agent {
|
|
|
|
|
|
|
+func NewClient(rw io.ReadWriter) ExtendedAgent {
|
|
|
return &client{conn: rw}
|
|
return &client{conn: rw}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -268,6 +317,21 @@ func NewClient(rw io.ReadWriter) Agent {
|
|
|
// unmarshaled into reply and replyType is set to the first byte of
|
|
// unmarshaled into reply and replyType is set to the first byte of
|
|
|
// the reply, which contains the type of the message.
|
|
// the reply, which contains the type of the message.
|
|
|
func (c *client) call(req []byte) (reply interface{}, err error) {
|
|
func (c *client) call(req []byte) (reply interface{}, err error) {
|
|
|
|
|
+ buf, err := c.callRaw(req)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ reply, err = unmarshal(buf)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, clientErr(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ return reply, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// callRaw sends an RPC to the agent. On success, the raw
|
|
|
|
|
+// bytes of the response are returned; no unmarshalling is
|
|
|
|
|
+// performed on the response.
|
|
|
|
|
+func (c *client) callRaw(req []byte) (reply []byte, err error) {
|
|
|
c.mu.Lock()
|
|
c.mu.Lock()
|
|
|
defer c.mu.Unlock()
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
@@ -291,11 +355,7 @@ func (c *client) call(req []byte) (reply interface{}, err error) {
|
|
|
if _, err = io.ReadFull(c.conn, buf); err != nil {
|
|
if _, err = io.ReadFull(c.conn, buf); err != nil {
|
|
|
return nil, clientErr(err)
|
|
return nil, clientErr(err)
|
|
|
}
|
|
}
|
|
|
- reply, err = unmarshal(buf)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, clientErr(err)
|
|
|
|
|
- }
|
|
|
|
|
- return reply, err
|
|
|
|
|
|
|
+ return buf, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (c *client) simpleCall(req []byte) error {
|
|
func (c *client) simpleCall(req []byte) error {
|
|
@@ -369,9 +429,14 @@ func (c *client) List() ([]*Key, error) {
|
|
|
// Sign has the agent sign the data using a protocol 2 key as defined
|
|
// Sign has the agent sign the data using a protocol 2 key as defined
|
|
|
// in [PROTOCOL.agent] section 2.6.2.
|
|
// in [PROTOCOL.agent] section 2.6.2.
|
|
|
func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
|
func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
|
|
|
|
+ return c.SignWithFlags(key, data, 0)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (c *client) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error) {
|
|
|
req := ssh.Marshal(signRequestAgentMsg{
|
|
req := ssh.Marshal(signRequestAgentMsg{
|
|
|
KeyBlob: key.Marshal(),
|
|
KeyBlob: key.Marshal(),
|
|
|
Data: data,
|
|
Data: data,
|
|
|
|
|
+ Flags: uint32(flags),
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
msg, err := c.call(req)
|
|
msg, err := c.call(req)
|
|
@@ -681,3 +746,44 @@ func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature,
|
|
|
// The agent has its own entropy source, so the rand argument is ignored.
|
|
// The agent has its own entropy source, so the rand argument is ignored.
|
|
|
return s.agent.Sign(s.pub, data)
|
|
return s.agent.Sign(s.pub, data)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+func (s *agentKeyringSigner) SignWithOpts(rand io.Reader, data []byte, opts crypto.SignerOpts) (*ssh.Signature, error) {
|
|
|
|
|
+ var flags SignatureFlags
|
|
|
|
|
+ if opts != nil {
|
|
|
|
|
+ switch opts.HashFunc() {
|
|
|
|
|
+ case crypto.SHA256:
|
|
|
|
|
+ flags = SignatureFlagRsaSha256
|
|
|
|
|
+ case crypto.SHA512:
|
|
|
|
|
+ flags = SignatureFlagRsaSha512
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return s.agent.SignWithFlags(s.pub, data, flags)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Calls an extension method. It is up to the agent implementation as to whether or not
|
|
|
|
|
+// any particular extension is supported and may always return an error. Because the
|
|
|
|
|
+// type of the response is up to the implementation, this returns the bytes of the
|
|
|
|
|
+// response and does not attempt any type of unmarshalling.
|
|
|
|
|
+func (c *client) Extension(extensionType string, contents []byte) ([]byte, error) {
|
|
|
|
|
+ req := ssh.Marshal(extensionAgentMsg{
|
|
|
|
|
+ ExtensionType: extensionType,
|
|
|
|
|
+ Contents: contents,
|
|
|
|
|
+ })
|
|
|
|
|
+ buf, err := c.callRaw(req)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ if len(buf) == 0 {
|
|
|
|
|
+ return nil, errors.New("agent: failure; empty response")
|
|
|
|
|
+ }
|
|
|
|
|
+ // [PROTOCOL.agent] section 4.7 indicates that an SSH_AGENT_FAILURE message
|
|
|
|
|
+ // represents an agent that does not support the extension
|
|
|
|
|
+ if buf[0] == agentFailure {
|
|
|
|
|
+ return nil, ErrExtensionUnsupported
|
|
|
|
|
+ }
|
|
|
|
|
+ if buf[0] == agentExtensionFailure {
|
|
|
|
|
+ return nil, errors.New("agent: generic extension failure")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return buf, nil
|
|
|
|
|
+}
|