go.crypto/ssh: Add support for ECDSA keys and certs.
R=agl, dave
CC=golang-dev
https://golang.org/cl/6873060
diff --git a/ssh/certs.go b/ssh/certs.go
index 40cf706..eeaef31 100644
--- a/ssh/certs.go
+++ b/ssh/certs.go
@@ -9,17 +9,18 @@
import (
"crypto/dsa"
+ "crypto/ecdsa"
"crypto/rsa"
"time"
)
// String constants in [PROTOCOL.certkeys] for certificate algorithm names.
const (
- hostAlgoRSACertV01 = "ssh-rsa-cert-v01@openssh.com"
- hostAlgoDSACertV01 = "ssh-dss-cert-v01@openssh.com"
- hostAlgoECDSA256CertV01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
- hostAlgoECDSA384CertV01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
- hostAlgoECDSA521CertV01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
+ certAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
+ certAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
+ certAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
+ certAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
+ certAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
)
// Certificate types are used to specify whether a certificate is for identification
@@ -41,10 +42,12 @@
// An OpenSSHCertV01 represents an OpenSSH certificate as defined in
// [PROTOCOL.certkeys] rev 1.8. Supported formats include
-// ssh-rsa-cert-v01@openssh.com and ssh-dss-cert-v01@openssh.com.
+// ssh-rsa-cert-v01@openssh.com, ssh-dss-cert-v01@openssh.com,
+// ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com,
+// and ecdsa-sha2-nistp521-cert-v01@openssh.com.
type OpenSSHCertV01 struct {
Nonce []byte
- Key interface{} // rsa or dsa *PublicKey
+ Key interface{} // rsa, dsa, or ecdsa *PublicKey
Serial uint64
Type uint32
KeyId string
@@ -65,18 +68,24 @@
}
switch algo {
- case hostAlgoRSACertV01:
+ case certAlgoRSAv01:
var rsaPubKey *rsa.PublicKey
if rsaPubKey, in, ok = parseRSA(in); !ok {
return
}
cert.Key = rsaPubKey
- case hostAlgoDSACertV01:
+ case certAlgoDSAv01:
var dsaPubKey *dsa.PublicKey
if dsaPubKey, in, ok = parseDSA(in); !ok {
return
}
cert.Key = dsaPubKey
+ case certAlgoECDSA256v01, certAlgoECDSA384v01, certAlgoECDSA521v01:
+ var ecdsaPubKey *ecdsa.PublicKey
+ if ecdsaPubKey, in, ok = parseECDSA(in); !ok {
+ return
+ }
+ cert.Key = ecdsaPubKey
default:
ok = false
return
@@ -149,6 +158,9 @@
case *dsa.PublicKey:
k := cert.Key.(*dsa.PublicKey)
pubKey = marshalPubDSA(k)
+ case *ecdsa.PublicKey:
+ k := cert.Key.(*ecdsa.PublicKey)
+ pubKey = marshalPubECDSA(k)
default:
panic("ssh: unknown public key type in cert")
}
diff --git a/ssh/common.go b/ssh/common.go
index 8709d2a..82b2f25 100644
--- a/ssh/common.go
+++ b/ssh/common.go
@@ -6,6 +6,7 @@
import (
"crypto/dsa"
+ "crypto/ecdsa"
"crypto/rsa"
"errors"
"fmt"
@@ -191,11 +192,17 @@
switch algoname {
// The corresponding private key to a public certificate is always a normal
// private key. For signature serialization purposes, ensure we use the
- // proper ssh-rsa or ssh-dss algo name in case the public cert algo name is passed.
- case hostAlgoRSACertV01:
+ // proper key algorithm name in case the public cert algorithm name is passed.
+ case certAlgoRSAv01:
algoname = "ssh-rsa"
- case hostAlgoDSACertV01:
+ case certAlgoDSAv01:
algoname = "ssh-dss"
+ case certAlgoECDSA256v01:
+ algoname = "ecdsa-sha2-nistp256"
+ case certAlgoECDSA384v01:
+ algoname = "ecdsa-sha2-nistp384"
+ case certAlgoECDSA521v01:
+ algoname = "ecdsa-sha2-nistp521"
}
length := stringLength(len(algoname))
length += stringLength(len(sig))
@@ -216,6 +223,8 @@
pubKeyBytes = marshalPubRSA(key)
case *dsa.PublicKey:
pubKeyBytes = marshalPubDSA(key)
+ case *ecdsa.PublicKey:
+ pubKeyBytes = marshalPubECDSA(key)
case *OpenSSHCertV01:
pubKeyBytes = marshalOpenSSHCertV01(key)
default:
@@ -236,6 +245,15 @@
return "ssh-rsa"
case *dsa.PublicKey:
return "ssh-dss"
+ case *ecdsa.PublicKey:
+ switch key.(*ecdsa.PublicKey).Params().BitSize {
+ case 256:
+ return "ecdsa-sha2-nistp256"
+ case 384:
+ return "ecdsa-sha2-nistp384"
+ case 521:
+ return "ecdsa-sha2-nistp521"
+ }
case *OpenSSHCertV01:
return algoName(key.(*OpenSSHCertV01).Key) + "-cert-v01@openssh.com"
}
diff --git a/ssh/keys.go b/ssh/keys.go
index 7225ecf..bc3e2cb 100644
--- a/ssh/keys.go
+++ b/ssh/keys.go
@@ -7,6 +7,8 @@
import (
"bytes"
"crypto/dsa"
+ "crypto/ecdsa"
+ "crypto/elliptic"
"crypto/rsa"
"encoding/base64"
"math/big"
@@ -29,11 +31,13 @@
}
switch string(algo) {
- case hostAlgoRSA:
+ case keyAlgoRSA:
return parseRSA(in)
- case hostAlgoDSA:
+ case keyAlgoDSA:
return parseDSA(in)
- case hostAlgoRSACertV01, hostAlgoDSACertV01:
+ case keyAlgoECDSA256, keyAlgoECDSA384, keyAlgoECDSA521:
+ return parseECDSA(in)
+ case certAlgoRSAv01, certAlgoDSAv01, certAlgoECDSA256v01, certAlgoECDSA384v01, certAlgoECDSA521v01:
return parseOpenSSHCertV01(in, string(algo))
}
panic("ssh: unknown public key type")
@@ -86,15 +90,49 @@
return key, in, ok
}
+// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
+func parseECDSA(in []byte) (out *ecdsa.PublicKey, rest []byte, ok bool) {
+ var identifier []byte
+ if identifier, in, ok = parseString(in); !ok {
+ return
+ }
+
+ key := new(ecdsa.PublicKey)
+
+ switch string(identifier) {
+ case "nistp256":
+ key.Curve = elliptic.P256()
+ case "nistp384":
+ key.Curve = elliptic.P384()
+ case "nistp521":
+ key.Curve = elliptic.P521()
+ default:
+ ok = false
+ return
+ }
+
+ var keyBytes []byte
+ if keyBytes, in, ok = parseString(in); !ok {
+ return
+ }
+
+ key.X, key.Y = elliptic.Unmarshal(key.Curve, keyBytes)
+ if key.X == nil || key.Y == nil {
+ ok = false
+ return
+ }
+ return key, in, ok
+}
+
// marshalPrivRSA serializes an RSA private key according to RFC 4253, section 6.6.
func marshalPrivRSA(priv *rsa.PrivateKey) []byte {
e := new(big.Int).SetInt64(int64(priv.E))
- length := stringLength(len(hostAlgoRSA))
+ length := stringLength(len(keyAlgoRSA))
length += intLength(e)
length += intLength(priv.N)
ret := make([]byte, length)
- r := marshalString(ret, []byte(hostAlgoRSA))
+ r := marshalString(ret, []byte(keyAlgoRSA))
r = marshalInt(r, e)
r = marshalInt(r, priv.N)
@@ -125,11 +163,35 @@
r := marshalInt(ret, key.P)
r = marshalInt(r, key.Q)
r = marshalInt(r, key.G)
- marshalInt(r, key.Y)
+ r = marshalInt(r, key.Y)
return ret
}
+// marshalPubECDSA serializes an ECDSA public key according to RFC 5656, section 3.1.
+func marshalPubECDSA(key *ecdsa.PublicKey) []byte {
+ var identifier []byte
+ switch key.Params().BitSize {
+ case 256:
+ identifier = []byte("nistp256")
+ case 384:
+ identifier = []byte("nistp384")
+ case 521:
+ identifier = []byte("nistp521")
+ default:
+ panic("ssh: unsupported ecdsa key size")
+ }
+ keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
+
+ length := stringLength(len(identifier))
+ length += stringLength(len(keyBytes))
+
+ ret := make([]byte, length)
+ r := marshalString(ret, identifier)
+ r = marshalString(r, keyBytes)
+ return ret
+}
+
// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
// (see sshd(8) manual page) once the options and key type fields have been
// removed.
@@ -196,8 +258,8 @@
// We don't support these keys.
in = rest
continue
- case hostAlgoRSACertV01, hostAlgoDSACertV01,
- hostAlgoECDSA256CertV01, hostAlgoECDSA384CertV01, hostAlgoECDSA521CertV01:
+ case certAlgoRSAv01, certAlgoDSAv01,
+ certAlgoECDSA256v01, certAlgoECDSA384v01, certAlgoECDSA521v01:
// We don't support these certificates.
in = rest
continue
@@ -270,15 +332,37 @@
b := &bytes.Buffer{}
switch keyType := key.(type) {
case *rsa.PublicKey:
- b.WriteString(hostAlgoRSA)
+ b.WriteString(keyAlgoRSA)
case *dsa.PublicKey:
- b.WriteString(hostAlgoDSA)
+ b.WriteString(keyAlgoDSA)
+ case *ecdsa.PublicKey:
+ switch keyType.Params().BitSize {
+ case 256:
+ b.WriteString(keyAlgoECDSA256)
+ case 384:
+ b.WriteString(keyAlgoECDSA384)
+ case 521:
+ b.WriteString(keyAlgoECDSA521)
+ default:
+ panic("unexpected key type")
+ }
case *OpenSSHCertV01:
switch keyType.Key.(type) {
case *rsa.PublicKey:
- b.WriteString(hostAlgoRSACertV01)
+ b.WriteString(certAlgoRSAv01)
case *dsa.PublicKey:
- b.WriteString(hostAlgoDSACertV01)
+ b.WriteString(certAlgoDSAv01)
+ case *ecdsa.PublicKey:
+ switch keyType.Key.(*ecdsa.PublicKey).Params().BitSize {
+ case 256:
+ b.WriteString(certAlgoECDSA256v01)
+ case 384:
+ b.WriteString(certAlgoECDSA384v01)
+ case 521:
+ b.WriteString(certAlgoECDSA521v01)
+ default:
+ panic("unexpected key type")
+ }
default:
panic("unexpected key type")
}