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")
 		}