Skip to content

Commit

Permalink
x/crypto/ssh: support more keytypes in the agent.
Browse files Browse the repository at this point in the history
This allows the golang ssh-agent to support the full suite of keys
the library accepts.

Currently constraints are ignored.

Change-Id: I7d48c78e9a355582eb54788571a483a736c3d3ef
Reviewed-on: https://go-review.googlesource.com/21536
Reviewed-by: Han-Wen Nienhuys <[email protected]>
Run-TryBot: Han-Wen Nienhuys <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
  • Loading branch information
Peter Moody authored and hanwen committed Apr 25, 2016
1 parent f98fd1f commit e84a34b
Show file tree
Hide file tree
Showing 6 changed files with 390 additions and 48 deletions.
21 changes: 12 additions & 9 deletions ssh/agent/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,13 @@ func (k *Key) Marshal() []byte {
return k.Blob
}

// Verify satisfies the ssh.PublicKey interface, but is not
// implemented for agent keys.
// Verify satisfies the ssh.PublicKey interface.
func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
return errors.New("agent: agent key does not know how to verify")
pubKey, err := ssh.ParsePublicKey(k.Blob)
if err != nil {
return fmt.Errorf("agent: bad public key")
}
return pubKey.Verify(data, sig)
}

type wireKey struct {
Expand Down Expand Up @@ -389,7 +392,7 @@ func unmarshal(packet []byte) (interface{}, error) {
}

type rsaKeyMsg struct {
Type string `sshtype:"17"`
Type string `sshtype:"17|25"`
N *big.Int
E *big.Int
D *big.Int
Expand All @@ -401,7 +404,7 @@ type rsaKeyMsg struct {
}

type dsaKeyMsg struct {
Type string `sshtype:"17"`
Type string `sshtype:"17|25"`
P *big.Int
Q *big.Int
G *big.Int
Expand All @@ -412,7 +415,7 @@ type dsaKeyMsg struct {
}

type ecdsaKeyMsg struct {
Type string `sshtype:"17"`
Type string `sshtype:"17|25"`
Curve string
KeyBytes []byte
D *big.Int
Expand Down Expand Up @@ -481,7 +484,7 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
}

type rsaCertMsg struct {
Type string `sshtype:"17"`
Type string `sshtype:"17|25"`
CertBytes []byte
D *big.Int
Iqmp *big.Int // IQMP = Inverse Q Mod P
Expand All @@ -492,15 +495,15 @@ type rsaCertMsg struct {
}

type dsaCertMsg struct {
Type string `sshtype:"17"`
Type string `sshtype:"17|25"`
CertBytes []byte
X *big.Int
Comments string
Constraints []byte `ssh:"rest"`
}

type ecdsaCertMsg struct {
Type string `sshtype:"17"`
Type string `sshtype:"17|25"`
CertBytes []byte
D *big.Int
Comments string
Expand Down
240 changes: 222 additions & 18 deletions ssh/agent/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
package agent

import (
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
Expand Down Expand Up @@ -128,6 +132,7 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
return nil, err
}
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil

case agentRequestIdentities:
keys, err := s.agent.List()
if err != nil {
Expand All @@ -141,42 +146,241 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
rep.Keys = append(rep.Keys, marshalKey(k)...)
}
return rep, nil
case agentAddIdentity:

case agentAddIdConstrained, agentAddIdentity:
return nil, s.insertIdentity(data)
}

return nil, fmt.Errorf("unknown opcode %d", data[0])
}

func parseRSAKey(req []byte) (*AddedKey, error) {
var k rsaKeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
if k.E.BitLen() > 30 {
return nil, errors.New("agent: RSA public exponent too large")
}
priv := &rsa.PrivateKey{
PublicKey: rsa.PublicKey{
E: int(k.E.Int64()),
N: k.N,
},
D: k.D,
Primes: []*big.Int{k.P, k.Q},
}
priv.Precompute()

return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
}

func parseDSAKey(req []byte) (*AddedKey, error) {
var k dsaKeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
priv := &dsa.PrivateKey{
PublicKey: dsa.PublicKey{
Parameters: dsa.Parameters{
P: k.P,
Q: k.Q,
G: k.G,
},
Y: k.Y,
},
X: k.X,
}

return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
}

func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) {
priv = &ecdsa.PrivateKey{
D: privScalar,
}

switch curveName {
case "nistp256":
priv.Curve = elliptic.P256()
case "nistp384":
priv.Curve = elliptic.P384()
case "nistp521":
priv.Curve = elliptic.P521()
default:
return nil, fmt.Errorf("agent: unknown curve %q", curveName)
}

priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes)
if priv.X == nil || priv.Y == nil {
return nil, errors.New("agent: point not on curve")
}

return priv, nil
}

func parseECDSAKey(req []byte) (*AddedKey, error) {
var k ecdsaKeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}

priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D)
if err != nil {
return nil, err
}

return &AddedKey{PrivateKey: &priv, Comment: k.Comments}, nil
}

func parseRSACert(req []byte) (*AddedKey, error) {
var k rsaCertMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}

pubKey, err := ssh.ParsePublicKey(k.CertBytes)
if err != nil {
return nil, err
}

cert, ok := pubKey.(*ssh.Certificate)
if !ok {
return nil, errors.New("agent: bad RSA certificate")
}

// An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go
var rsaPub struct {
Name string
E *big.Int
N *big.Int
}
if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil {
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
}

if rsaPub.E.BitLen() > 30 {
return nil, errors.New("agent: RSA public exponent too large")
}

priv := rsa.PrivateKey{
PublicKey: rsa.PublicKey{
E: int(rsaPub.E.Int64()),
N: rsaPub.N,
},
D: k.D,
Primes: []*big.Int{k.Q, k.P},
}
priv.Precompute()

return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, nil
}

func parseDSACert(req []byte) (*AddedKey, error) {
var k dsaCertMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
if err != nil {
return nil, err
}
cert, ok := pubKey.(*ssh.Certificate)
if !ok {
return nil, errors.New("agent: bad DSA certificate")
}

// A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go
var w struct {
Name string
P, Q, G, Y *big.Int
}
if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil {
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
}

priv := &dsa.PrivateKey{
PublicKey: dsa.PublicKey{
Parameters: dsa.Parameters{
P: w.P,
Q: w.Q,
G: w.G,
},
Y: w.Y,
},
X: k.X,
}

return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil
}

func parseECDSACert(req []byte) (*AddedKey, error) {
var k ecdsaCertMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}

pubKey, err := ssh.ParsePublicKey(k.CertBytes)
if err != nil {
return nil, err
}
cert, ok := pubKey.(*ssh.Certificate)
if !ok {
return nil, errors.New("agent: bad ECDSA certificate")
}

// An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go
var ecdsaPub struct {
Name string
ID string
Key []byte
}
if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil {
return nil, err
}

priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D)
if err != nil {
return nil, err
}

return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil
}

func (s *server) insertIdentity(req []byte) error {
var record struct {
Type string `sshtype:"17"`
Type string `sshtype:"17|25"`
Rest []byte `ssh:"rest"`
}

if err := ssh.Unmarshal(req, &record); err != nil {
return err
}

var addedKey *AddedKey
var err error

switch record.Type {
case ssh.KeyAlgoRSA:
var k rsaKeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return err
}

priv := rsa.PrivateKey{
PublicKey: rsa.PublicKey{
E: int(k.E.Int64()),
N: k.N,
},
D: k.D,
Primes: []*big.Int{k.P, k.Q},
}
priv.Precompute()
addedKey, err = parseRSAKey(req)
case ssh.KeyAlgoDSA:
addedKey, err = parseDSAKey(req)
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
addedKey, err = parseECDSACert(req)
case ssh.CertAlgoRSAv01:
addedKey, err = parseRSACert(req)
case ssh.CertAlgoDSAv01:
addedKey, err = parseDSACert(req)
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
addedKey, err = parseECDSACert(req)
default:
return fmt.Errorf("agent: not implemented: %q", record.Type)
}

return s.agent.Add(AddedKey{PrivateKey: &priv, Comment: k.Comments})
if err != nil {
return err
}
return fmt.Errorf("not implemented: %s", record.Type)
return s.agent.Add(*addedKey)
}

// ServeAgent serves the agent protocol on the given connection. It
Expand Down
Loading

0 comments on commit e84a34b

Please sign in to comment.