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>
diff --git a/ssh/keys.go b/ssh/keys.go
index 25be3c5..d6167e7 100644
--- a/ssh/keys.go
+++ b/ssh/keys.go
@@ -20,6 +20,8 @@
"io"
"math/big"
"strings"
+
+ "golang.org/x/crypto/ed25519"
)
// These constants represent the algorithm names for key types supported by this
@@ -30,6 +32,7 @@
KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
+ KeyAlgoED25519 = "ssh-ed25519"
)
// parsePubKey parses a public key of the given algorithm.
@@ -42,14 +45,16 @@
return parseDSA(in)
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
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))
if err != nil {
return nil, nil, err
}
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
@@ -459,6 +464,51 @@
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 {
return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
}
@@ -597,13 +647,19 @@
hashFunc = crypto.SHA1
case *ecdsaPublicKey:
hashFunc = ecHash(key.Curve)
+ case ed25519PublicKey:
default:
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)
if err != nil {
@@ -643,9 +699,9 @@
}, 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) {
switch key := key.(type) {
case *rsa.PublicKey:
@@ -657,6 +713,8 @@
return (*ecdsaPublicKey)(key), nil
case *dsa.PublicKey:
return (*dsaPublicKey)(key), nil
+ case ed25519.PublicKey:
+ return (ed25519PublicKey)(key), nil
default:
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
}
@@ -688,6 +746,8 @@
return x509.ParseECPrivateKey(block.Bytes)
case "DSA PRIVATE KEY":
return ParseDSAPrivateKey(block.Bytes)
+ case "OPENSSH PRIVATE KEY":
+ return parseOpenSSHPrivateKey(block.Bytes)
default:
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
}
@@ -724,3 +784,63 @@
X: k.Pub,
}, 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
+}