From 94c9f92c921cdca7aa54dee7375fc491462194d6 Mon Sep 17 00:00:00 2001 From: Jonathan Pittman Date: Fri, 14 Dec 2012 05:52:19 +1100 Subject: [PATCH] go.crypto/ssh: Add support for ECDSA keys and certs. R=agl, dave CC=golang-dev https://golang.org/cl/6873060 --- ssh/certs.go | 30 +++++++++----- ssh/common.go | 24 +++++++++-- ssh/keys.go | 108 ++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 138 insertions(+), 24 deletions(-) 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 @@ package ssh 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 @@ type tuple struct { // 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 @@ func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []by } 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 @@ func marshalOpenSSHCertV01(cert *OpenSSHCertV01) []byte { 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 @@ package ssh import ( "crypto/dsa" + "crypto/ecdsa" "crypto/rsa" "errors" "fmt" @@ -191,11 +192,17 @@ func serializeSignature(algoname string, sig []byte) []byte { 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 @@ func serializePublickey(key interface{}) []byte { 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 @@ func algoName(key interface{}) string { 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 @@ package ssh import ( "bytes" "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rsa" "encoding/base64" "math/big" @@ -29,11 +31,13 @@ func parsePubKey(in []byte) (out interface{}, rest []byte, ok bool) { } 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 @@ func parseDSA(in []byte) (out *dsa.PublicKey, rest []byte, ok bool) { 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 @@ func marshalPubDSA(key *dsa.PublicKey) []byte { 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 @@ func ParseAuthorizedKey(in []byte) (out interface{}, comment string, options []s // 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 @@ func MarshalAuthorizedKey(key interface{}) []byte { 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") }