فهرست منبع

x/crypto/ssh: add support for ed25519 keys

Added support for parsing the "new" openssh private key format.
(ed25519 keys only in this format for now)

Signing and verifying functions now work with ed25519 keys.

ed25519 can now be accepted by the server to authenticate a client.

ed25519 can now be accepted by a client as a server host key.

Related documentation used:
https://www.ietf.org/archive/id/draft-bjh21-ssh-ed25519-02.txt

Change-Id: I84385f24d666fea08de21f980f78623f7bff8007
Reviewed-on: https://go-review.googlesource.com/22512
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
Martin Garton 9 سال پیش
والد
کامیت
1e61df8d9e
9فایلهای تغییر یافته به همراه178 افزوده شده و 12 حذف شده
  1. 1 1
      ssh/agent/client.go
  2. 3 1
      ssh/certs.go
  3. 2 0
      ssh/common.go
  4. 128 8
      ssh/keys.go
  5. 3 0
      ssh/keys_test.go
  6. 1 1
      ssh/server.go
  7. 25 0
      ssh/test/session_test.go
  8. 7 1
      ssh/test/test_unix_test.go
  9. 8 0
      ssh/testdata/keys.go

+ 1 - 1
ssh/agent/client.go

@@ -188,7 +188,7 @@ func (k *Key) Marshal() []byte {
 func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
 func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
 	pubKey, err := ssh.ParsePublicKey(k.Blob)
 	pubKey, err := ssh.ParsePublicKey(k.Blob)
 	if err != nil {
 	if err != nil {
-		return fmt.Errorf("agent: bad public key")
+		return fmt.Errorf("agent: bad public key: %v", err)
 	}
 	}
 	return pubKey.Verify(data, sig)
 	return pubKey.Verify(data, sig)
 }
 }

+ 3 - 1
ssh/certs.go

@@ -22,6 +22,7 @@ const (
 	CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
 	CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
 	CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
 	CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
 	CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
 	CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
+	CertAlgoED25519v01  = "ssh-ed25519-cert-v01@openssh.com"
 )
 )
 
 
 // Certificate types distinguish between host and user
 // Certificate types distinguish between host and user
@@ -401,6 +402,7 @@ var certAlgoNames = map[string]string{
 	KeyAlgoECDSA256: CertAlgoECDSA256v01,
 	KeyAlgoECDSA256: CertAlgoECDSA256v01,
 	KeyAlgoECDSA384: CertAlgoECDSA384v01,
 	KeyAlgoECDSA384: CertAlgoECDSA384v01,
 	KeyAlgoECDSA521: CertAlgoECDSA521v01,
 	KeyAlgoECDSA521: CertAlgoECDSA521v01,
+	KeyAlgoED25519:  CertAlgoED25519v01,
 }
 }
 
 
 // certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
 // certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
@@ -459,7 +461,7 @@ func (c *Certificate) Marshal() []byte {
 func (c *Certificate) Type() string {
 func (c *Certificate) Type() string {
 	algo, ok := certAlgoNames[c.Key.Type()]
 	algo, ok := certAlgoNames[c.Key.Type()]
 	if !ok {
 	if !ok {
-		panic("unknown cert key type")
+		panic("unknown cert key type " + c.Key.Type())
 	}
 	}
 	return algo
 	return algo
 }
 }

+ 2 - 0
ssh/common.go

@@ -48,6 +48,8 @@ var supportedHostKeyAlgos = []string{
 
 
 	KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
 	KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
 	KeyAlgoRSA, KeyAlgoDSA,
 	KeyAlgoRSA, KeyAlgoDSA,
+
+	KeyAlgoED25519,
 }
 }
 
 
 // supportedMACs specifies a default set of MAC algorithms in preference order.
 // supportedMACs specifies a default set of MAC algorithms in preference order.

+ 128 - 8
ssh/keys.go

@@ -20,6 +20,8 @@ import (
 	"io"
 	"io"
 	"math/big"
 	"math/big"
 	"strings"
 	"strings"
+
+	"golang.org/x/crypto/ed25519"
 )
 )
 
 
 // These constants represent the algorithm names for key types supported by this
 // These constants represent the algorithm names for key types supported by this
@@ -30,6 +32,7 @@ const (
 	KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
 	KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
 	KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
 	KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
 	KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
 	KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
+	KeyAlgoED25519  = "ssh-ed25519"
 )
 )
 
 
 // parsePubKey parses a public key of the given algorithm.
 // parsePubKey parses a public key of the given algorithm.
@@ -42,14 +45,16 @@ func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err err
 		return parseDSA(in)
 		return parseDSA(in)
 	case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
 	case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
 		return parseECDSA(in)
 		return parseECDSA(in)
-	case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
+	case KeyAlgoED25519:
+		return parseED25519(in)
+	case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01:
 		cert, err := parseCert(in, certToPrivAlgo(algo))
 		cert, err := parseCert(in, certToPrivAlgo(algo))
 		if err != nil {
 		if err != nil {
 			return nil, nil, err
 			return nil, nil, err
 		}
 		}
 		return cert, nil, nil
 		return cert, nil, nil
 	}
 	}
-	return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", err)
+	return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo)
 }
 }
 
 
 // parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
 // parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
@@ -459,6 +464,51 @@ func (key *ecdsaPublicKey) nistID() string {
 	panic("ssh: unsupported ecdsa key size")
 	panic("ssh: unsupported ecdsa key size")
 }
 }
 
 
+type ed25519PublicKey ed25519.PublicKey
+
+func (key ed25519PublicKey) Type() string {
+	return KeyAlgoED25519
+}
+
+func parseED25519(in []byte) (out PublicKey, rest []byte, err error) {
+	var w struct {
+		KeyBytes []byte
+		Rest     []byte `ssh:"rest"`
+	}
+
+	if err := Unmarshal(in, &w); err != nil {
+		return nil, nil, err
+	}
+
+	key := ed25519.PublicKey(w.KeyBytes)
+
+	return (ed25519PublicKey)(key), w.Rest, nil
+}
+
+func (key ed25519PublicKey) Marshal() []byte {
+	w := struct {
+		Name     string
+		KeyBytes []byte
+	}{
+		KeyAlgoED25519,
+		[]byte(key),
+	}
+	return Marshal(&w)
+}
+
+func (key ed25519PublicKey) Verify(b []byte, sig *Signature) error {
+	if sig.Format != key.Type() {
+		return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
+	}
+
+	edKey := (ed25519.PublicKey)(key)
+	if ok := ed25519.Verify(edKey, b, sig.Blob); !ok {
+		return errors.New("ssh: signature did not verify")
+	}
+
+	return nil
+}
+
 func supportedEllipticCurve(curve elliptic.Curve) bool {
 func supportedEllipticCurve(curve elliptic.Curve) bool {
 	return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
 	return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
 }
 }
@@ -597,13 +647,19 @@ func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
 		hashFunc = crypto.SHA1
 		hashFunc = crypto.SHA1
 	case *ecdsaPublicKey:
 	case *ecdsaPublicKey:
 		hashFunc = ecHash(key.Curve)
 		hashFunc = ecHash(key.Curve)
+	case ed25519PublicKey:
 	default:
 	default:
 		return nil, fmt.Errorf("ssh: unsupported key type %T", key)
 		return nil, fmt.Errorf("ssh: unsupported key type %T", key)
 	}
 	}
 
 
-	h := hashFunc.New()
-	h.Write(data)
-	digest := h.Sum(nil)
+	var digest []byte
+	if hashFunc != 0 {
+		h := hashFunc.New()
+		h.Write(data)
+		digest = h.Sum(nil)
+	} else {
+		digest = data
+	}
 
 
 	signature, err := s.signer.Sign(rand, digest, hashFunc)
 	signature, err := s.signer.Sign(rand, digest, hashFunc)
 	if err != nil {
 	if err != nil {
@@ -643,9 +699,9 @@ func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
 	}, nil
 	}, nil
 }
 }
 
 
-// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or
-// any other crypto.Signer and returns a corresponding Signer instance. ECDSA
-// keys must use P-256, P-384 or P-521.
+// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey,
+// ed25519.PublicKey, or any other crypto.Signer and returns a corresponding
+// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
 func NewPublicKey(key interface{}) (PublicKey, error) {
 func NewPublicKey(key interface{}) (PublicKey, error) {
 	switch key := key.(type) {
 	switch key := key.(type) {
 	case *rsa.PublicKey:
 	case *rsa.PublicKey:
@@ -657,6 +713,8 @@ func NewPublicKey(key interface{}) (PublicKey, error) {
 		return (*ecdsaPublicKey)(key), nil
 		return (*ecdsaPublicKey)(key), nil
 	case *dsa.PublicKey:
 	case *dsa.PublicKey:
 		return (*dsaPublicKey)(key), nil
 		return (*dsaPublicKey)(key), nil
+	case ed25519.PublicKey:
+		return (ed25519PublicKey)(key), nil
 	default:
 	default:
 		return nil, fmt.Errorf("ssh: unsupported key type %T", key)
 		return nil, fmt.Errorf("ssh: unsupported key type %T", key)
 	}
 	}
@@ -688,6 +746,8 @@ func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
 		return x509.ParseECPrivateKey(block.Bytes)
 		return x509.ParseECPrivateKey(block.Bytes)
 	case "DSA PRIVATE KEY":
 	case "DSA PRIVATE KEY":
 		return ParseDSAPrivateKey(block.Bytes)
 		return ParseDSAPrivateKey(block.Bytes)
+	case "OPENSSH PRIVATE KEY":
+		return parseOpenSSHPrivateKey(block.Bytes)
 	default:
 	default:
 		return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
 		return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
 	}
 	}
@@ -724,3 +784,63 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
 		X: k.Pub,
 		X: k.Pub,
 	}, nil
 	}, nil
 }
 }
+
+// Implemented based on the documentation at
+// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
+func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
+	magic := append([]byte("openssh-key-v1"), 0)
+	if !bytes.Equal(magic, key[0:len(magic)]) {
+		return nil, errors.New("ssh: invalid openssh private key format")
+	}
+	remaining := key[len(magic):]
+
+	var w struct {
+		CipherName   string
+		KdfName      string
+		KdfOpts      string
+		NumKeys      uint32
+		PubKey       []byte
+		PrivKeyBlock []byte
+	}
+
+	if err := Unmarshal(remaining, &w); err != nil {
+		return nil, err
+	}
+
+	pk1 := struct {
+		Check1  uint32
+		Check2  uint32
+		Keytype string
+		Pub     []byte
+		Priv    []byte
+		Comment string
+		Pad     []byte `ssh:"rest"`
+	}{}
+
+	if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil {
+		return nil, err
+	}
+
+	if pk1.Check1 != pk1.Check2 {
+		return nil, errors.New("ssh: checkint mismatch")
+	}
+
+	// we only handle ed25519 keys currently
+	if pk1.Keytype != KeyAlgoED25519 {
+		return nil, errors.New("ssh: unhandled key type")
+	}
+
+	for i, b := range pk1.Pad {
+		if int(b) != i+1 {
+			return nil, errors.New("ssh: padding not as expected")
+		}
+	}
+
+	if len(pk1.Priv) != ed25519.PrivateKeySize {
+		return nil, errors.New("ssh: private key unexpected length")
+	}
+
+	pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
+	copy(pk, pk1.Priv)
+	return &pk, nil
+}

+ 3 - 0
ssh/keys_test.go

@@ -17,6 +17,7 @@ import (
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 
 
+	"golang.org/x/crypto/ed25519"
 	"golang.org/x/crypto/ssh/testdata"
 	"golang.org/x/crypto/ssh/testdata"
 )
 )
 
 
@@ -28,6 +29,8 @@ func rawKey(pub PublicKey) interface{} {
 		return (*dsa.PublicKey)(k)
 		return (*dsa.PublicKey)(k)
 	case *ecdsaPublicKey:
 	case *ecdsaPublicKey:
 		return (*ecdsa.PublicKey)(k)
 		return (*ecdsa.PublicKey)(k)
+	case ed25519PublicKey:
+		return (ed25519.PublicKey)(k)
 	case *Certificate:
 	case *Certificate:
 		return k
 		return k
 	}
 	}

+ 1 - 1
ssh/server.go

@@ -224,7 +224,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
 
 
 func isAcceptableAlgo(algo string) bool {
 func isAcceptableAlgo(algo string) bool {
 	switch algo {
 	switch algo {
-	case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
+	case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519,
 		CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
 		CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
 		return true
 		return true
 	}
 	}

+ 25 - 0
ssh/test/session_test.go

@@ -338,3 +338,28 @@ func TestKeyExchanges(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestClientAuthAlgorithms(t *testing.T) {
+	for _, key := range []string{
+		"rsa",
+		"dsa",
+		"ecdsa",
+		"ed25519",
+	} {
+		server := newServer(t)
+		conf := clientConfig()
+		conf.SetDefaults()
+		conf.Auth = []ssh.AuthMethod{
+			ssh.PublicKeys(testSigners[key]),
+		}
+
+		conn, err := server.TryDial(conf)
+		if err == nil {
+			conn.Close()
+		} else {
+			t.Errorf("failed for key %q", key)
+		}
+
+		server.Shutdown()
+	}
+}

+ 7 - 1
ssh/test/test_unix_test.go

@@ -41,7 +41,7 @@ PermitRootLogin no
 StrictModes no
 StrictModes no
 RSAAuthentication yes
 RSAAuthentication yes
 PubkeyAuthentication yes
 PubkeyAuthentication yes
-AuthorizedKeysFile	{{.Dir}}/id_user.pub
+AuthorizedKeysFile	{{.Dir}}/authorized_keys
 TrustedUserCAKeys {{.Dir}}/id_ecdsa.pub
 TrustedUserCAKeys {{.Dir}}/id_ecdsa.pub
 IgnoreRhosts yes
 IgnoreRhosts yes
 RhostsRSAAuthentication no
 RhostsRSAAuthentication no
@@ -250,6 +250,12 @@ func newServer(t *testing.T) *server {
 		writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k]))
 		writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k]))
 	}
 	}
 
 
+	var authkeys bytes.Buffer
+	for k, _ := range testdata.PEMBytes {
+		authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
+	}
+	writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes())
+
 	return &server{
 	return &server{
 		t:          t,
 		t:          t,
 		configfile: f.Name(),
 		configfile: f.Name(),

+ 8 - 0
ssh/testdata/keys.go

@@ -39,6 +39,14 @@ NDvRS0rjwt6lJGv7zPZoqDc65VfrK2aNyHx2PgFyzwrEOtuF57bu7pnvEIxpLTeM
 z26i6XVMeYXAWZMTloMCQBbpGgEERQpeUknLBqUHhg/wXF6+lFA+vEGnkY+Dwab2
 z26i6XVMeYXAWZMTloMCQBbpGgEERQpeUknLBqUHhg/wXF6+lFA+vEGnkY+Dwab2
 KCXFGd+SQ5GdUcEMe9isUH6DYj/6/yCDoFrXXmpQb+M=
 KCXFGd+SQ5GdUcEMe9isUH6DYj/6/yCDoFrXXmpQb+M=
 -----END RSA PRIVATE KEY-----
 -----END RSA PRIVATE KEY-----
+`),
+	"ed25519": []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACA+3f7hS7g5UWwXOGVTrMfhmxyrjqz7Sxxbx7I1j8DvvwAAAJhAFfkOQBX5
+DgAAAAtzc2gtZWQyNTUxOQAAACA+3f7hS7g5UWwXOGVTrMfhmxyrjqz7Sxxbx7I1j8Dvvw
+AAAEAaYmXltfW6nhRo3iWGglRB48lYq0z0Q3I3KyrdutEr6j7d/uFLuDlRbBc4ZVOsx+Gb
+HKuOrPtLHFvHsjWPwO+/AAAAE2dhcnRvbm1AZ2FydG9ubS14cHMBAg==
+-----END OPENSSH PRIVATE KEY-----
 `),
 `),
 	"user": []byte(`-----BEGIN EC PRIVATE KEY-----
 	"user": []byte(`-----BEGIN EC PRIVATE KEY-----
 MHcCAQEEILYCAeq8f7V4vSSypRw7pxy8yz3V5W4qg8kSC3zJhqpQoAoGCCqGSM49
 MHcCAQEEILYCAeq8f7V4vSSypRw7pxy8yz3V5W4qg8kSC3zJhqpQoAoGCCqGSM49