From e62b2aead43494d8abe8c8be4cf9993beb379779 Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Fri, 13 Sep 2013 14:25:14 -0400 Subject: [PATCH] go.crypto/ssh: introduce PublicKey interface type. Public functions affected: -AgentKey.Key -AgentClient.SignRequest -ClientKeyring.Key -MarshalPublicKey -ParsePublicKey R=agl, jpsugar, jmpittman CC=golang-dev https://golang.org/cl/13642043 --- ssh/agent.go | 6 +- ssh/certs.go | 81 ++++--- ssh/client.go | 17 +- ssh/client_auth.go | 29 ++- ssh/client_auth_test.go | 13 +- ssh/common.go | 134 ++---------- ssh/common_test.go | 51 ----- ssh/keys.go | 428 ++++++++++++++++++++++++------------- ssh/keys_test.go | 60 ++++++ ssh/server.go | 54 ++--- ssh/test/keys_test.go | 6 +- ssh/test/test_unix_test.go | 8 +- 12 files changed, 448 insertions(+), 439 deletions(-) create mode 100644 ssh/keys_test.go diff --git a/ssh/agent.go b/ssh/agent.go index 5d2175d..924a58a 100644 --- a/ssh/agent.go +++ b/ssh/agent.go @@ -99,7 +99,7 @@ func (ak *AgentKey) String() string { } // Key returns an agent's public key as one of the supported key or certificate types. -func (ak *AgentKey) Key() (interface{}, error) { +func (ak *AgentKey) Key() (PublicKey, error) { if key, _, ok := parsePubKey(ak.blob); ok { return key, nil } @@ -204,9 +204,9 @@ func (ac *AgentClient) RequestIdentities() ([]*AgentKey, error) { // SignRequest requests the signing of data by the agent using a protocol 2 key // as defined in [PROTOCOL.agent] section 2.6.2. -func (ac *AgentClient) SignRequest(key interface{}, data []byte) ([]byte, error) { +func (ac *AgentClient) SignRequest(key PublicKey, data []byte) ([]byte, error) { req := marshal(agentSignRequest, signRequestAgentMsg{ - KeyBlob: serializePublicKey(key), + KeyBlob: MarshalPublicKey(key), Data: data, }) diff --git a/ssh/certs.go b/ssh/certs.go index 0094b3d..ddece44 100644 --- a/ssh/certs.go +++ b/ssh/certs.go @@ -5,9 +5,6 @@ package ssh import ( - "crypto/dsa" - "crypto/ecdsa" - "crypto/rsa" "time" ) @@ -42,7 +39,7 @@ type tuple struct { // [PROTOCOL.certkeys]?rev=1.8. type OpenSSHCertV01 struct { Nonce []byte - Key interface{} // rsa, dsa, or ecdsa *PublicKey + Key PublicKey Serial uint64 Type uint32 KeyId string @@ -51,10 +48,38 @@ type OpenSSHCertV01 struct { CriticalOptions []tuple Extensions []tuple Reserved []byte - SignatureKey interface{} // rsa, dsa, or ecdsa *PublicKey + SignatureKey PublicKey Signature *signature } +var certAlgoNames = map[string]string{ + KeyAlgoRSA: CertAlgoRSAv01, + KeyAlgoDSA: CertAlgoDSAv01, + KeyAlgoECDSA256: CertAlgoECDSA256v01, + KeyAlgoECDSA384: CertAlgoECDSA384v01, + KeyAlgoECDSA521: CertAlgoECDSA521v01, +} + +func (c *OpenSSHCertV01) PublicKeyAlgo() string { + algo, ok := certAlgoNames[c.Key.PublicKeyAlgo()] + if !ok { + panic("unknown cert key type") + } + return algo +} + +func (c *OpenSSHCertV01) RawKey() interface{} { + return c.Key.RawKey() +} + +func (c *OpenSSHCertV01) PrivateKeyAlgo() string { + return c.Key.PrivateKeyAlgo() +} + +func (c *OpenSSHCertV01) Verify(data []byte, sig []byte) bool { + return c.Key.Verify(data, sig) +} + func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []byte, ok bool) { cert := new(OpenSSHCertV01) @@ -62,26 +87,12 @@ func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []by return } - switch algo { - case CertAlgoRSAv01: - var rsaPubKey *rsa.PublicKey - if rsaPubKey, in, ok = parseRSA(in); !ok { - return - } - cert.Key = rsaPubKey - 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: + cert.Key, in, ok = ParsePublicKey(in) + if !ok { + return + } + + if cert.Key.PrivateKeyAlgo() != algo { ok = false return } @@ -144,23 +155,10 @@ func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []by return cert, in, ok } -func marshalOpenSSHCertV01(cert *OpenSSHCertV01) []byte { - var pubKey []byte - switch cert.Key.(type) { - case *rsa.PublicKey: - k := cert.Key.(*rsa.PublicKey) - pubKey = marshalPubRSA(k) - 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") - } +func (cert *OpenSSHCertV01) Marshal() []byte { + pubKey := MarshalPublicKey(cert.Key) - sigKey := serializePublicKey(cert.SignatureKey) + sigKey := MarshalPublicKey(cert.SignatureKey) length := stringLength(len(cert.Nonce)) length += len(pubKey) @@ -314,5 +312,6 @@ func parseSignature(in []byte) (out *signature, rest []byte, ok bool) { return } + // TODO(hanwen): this is a bug; 'rest' gets swallowed. return parseSignatureBody(sigBytes) } diff --git a/ssh/client.go b/ssh/client.go index f79fd02..6a5ec24 100644 --- a/ssh/client.go +++ b/ssh/client.go @@ -246,18 +246,6 @@ func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte return errors.New("ssh: could not parse hostkey") } - // Select hash function to match the hostkey algorithm, as per - // RFC 4253, section 6.6 (for RSA/DSS) and RFC 5656, section - // 6.2.1 (for ECDSA). - hashFunc, ok := hashFuncs[hostKeyAlgo] - if !ok { - return errors.New("ssh: unknown key algorithm: " + hostKeyAlgo) - } - - signed := hashFunc.New() - signed.Write(data) - digest := signed.Sum(nil) - sig, rest, ok := parseSignatureBody(signature) if len(rest) > 0 || !ok { return errors.New("ssh: signature parse error") @@ -266,7 +254,10 @@ func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte return fmt.Errorf("ssh: unexpected signature type %q", sig.Format) } - return verifySignature(digest, sig, hostKey) + if !hostKey.Verify(data, sig.Blob) { + return errors.New("ssh: host key signature error") + } + return nil } // kexResult captures the outcome of a key exchange. diff --git a/ssh/client_auth.go b/ssh/client_auth.go index 7946843..31a5e09 100644 --- a/ssh/client_auth.go +++ b/ssh/client_auth.go @@ -155,9 +155,8 @@ func ClientAuthPassword(impl ClientPassword) ClientAuth { // ClientKeyring implements access to a client key ring. type ClientKeyring interface { - // Key returns the i'th *rsa.Publickey or *dsa.Publickey, or nil if - // no key exists at i. - Key(i int) (key interface{}, err error) + // Key returns the i'th Publickey, or nil if no key exists at i. + Key(i int) (key PublicKey, err error) // Sign returns a signature of the given data using the i'th key // and the supplied random source. @@ -190,7 +189,7 @@ func (p *publickeyAuth) auth(session []byte, user string, t *transport, rand io. var index int // a map of public keys to their index in the keyring - validKeys := make(map[int]interface{}) + validKeys := make(map[int]PublicKey) for { key, err := p.Key(index) if err != nil { @@ -214,8 +213,8 @@ func (p *publickeyAuth) auth(session []byte, user string, t *transport, rand io. // methods that may continue if this auth is not successful. var methods []string for i, key := range validKeys { - pubkey := serializePublicKey(key) - algoname := algoName(key) + pubkey := MarshalPublicKey(key) + algoname := key.PublicKeyAlgo() sign, err := p.Sign(i, rand, buildDataSignedForAuth(session, userAuthRequestMsg{ User: user, Service: serviceSSH, @@ -225,7 +224,7 @@ func (p *publickeyAuth) auth(session []byte, user string, t *transport, rand io. return false, nil, err } // manually wrap the serialized signature in a string - s := serializeSignature(algoname, sign) + s := serializeSignature(key.PublicKeyAlgo(), sign) sig := make([]byte, stringLength(len(s))) marshalString(sig, s) msg := publickeyAuthMsg{ @@ -253,9 +252,9 @@ func (p *publickeyAuth) auth(session []byte, user string, t *transport, rand io. } // validateKey validates the key provided it is acceptable to the server. -func (p *publickeyAuth) validateKey(key interface{}, user string, t *transport) (bool, error) { - pubkey := serializePublicKey(key) - algoname := algoName(key) +func (p *publickeyAuth) validateKey(key PublicKey, user string, t *transport) (bool, error) { + pubkey := MarshalPublicKey(key) + algoname := key.PublicKeyAlgo() msg := publickeyAuthMsg{ User: user, Service: serviceSSH, @@ -271,9 +270,9 @@ func (p *publickeyAuth) validateKey(key interface{}, user string, t *transport) return p.confirmKeyAck(key, t) } -func (p *publickeyAuth) confirmKeyAck(key interface{}, t *transport) (bool, error) { - pubkey := serializePublicKey(key) - algoname := algoName(key) +func (p *publickeyAuth) confirmKeyAck(key PublicKey, t *transport) (bool, error) { + pubkey := MarshalPublicKey(key) + algoname := key.PublicKeyAlgo() for { packet, err := t.readPacket() @@ -352,7 +351,7 @@ type agentKeyring struct { keys []*AgentKey } -func (kr *agentKeyring) Key(i int) (key interface{}, err error) { +func (kr *agentKeyring) Key(i int) (key PublicKey, err error) { if kr.keys == nil { if kr.keys, err = kr.agent.RequestIdentities(); err != nil { return @@ -365,7 +364,7 @@ func (kr *agentKeyring) Key(i int) (key interface{}, err error) { } func (kr *agentKeyring) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) { - var key interface{} + var key PublicKey if key, err = kr.Key(i); err != nil { return } diff --git a/ssh/client_auth_test.go b/ssh/client_auth_test.go index df6dbd6..4460b41 100644 --- a/ssh/client_auth_test.go +++ b/ssh/client_auth_test.go @@ -65,15 +65,15 @@ type keychain struct { keys []interface{} } -func (k *keychain) Key(i int) (interface{}, error) { +func (k *keychain) Key(i int) (PublicKey, error) { if i < 0 || i >= len(k.keys) { return nil, nil } switch key := k.keys[i].(type) { case *rsa.PrivateKey: - return &key.PublicKey, nil + return NewRSAPublicKey(&key.PublicKey), nil case *dsa.PrivateKey: - return &key.PublicKey, nil + return NewDSAPublicKey(&key.PublicKey), nil } panic("unknown key type") } @@ -135,9 +135,10 @@ var ( return user == "testuser" && pass == string(clientPassword) }, PublicKeyCallback: func(conn *ServerConn, user, algo string, pubkey []byte) bool { - key := &clientKeychain.keys[0].(*rsa.PrivateKey).PublicKey - expected := []byte(serializePublicKey(key)) - algoname := algoName(key) + rsaKey := &clientKeychain.keys[0].(*rsa.PrivateKey).PublicKey + key := NewRSAPublicKey(rsaKey) + expected := MarshalPublicKey(key) + algoname := key.PublicKeyAlgo() return user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected) }, KeyboardInteractiveCallback: func(conn *ServerConn, user string, client ClientKeyboardInteractive) bool { diff --git a/ssh/common.go b/ssh/common.go index dab10d5..e536c1a 100644 --- a/ssh/common.go +++ b/ssh/common.go @@ -6,10 +6,7 @@ package ssh import ( "crypto" - "crypto/dsa" - "crypto/ecdsa" "crypto/elliptic" - "crypto/rsa" "errors" "fmt" "math/big" @@ -239,137 +236,36 @@ func ecHash(curve elliptic.Curve) crypto.Hash { return crypto.SHA512 } -// serialize a signed slice according to RFC 4254 6.6. -func serializeSignature(algoname string, sig []byte) []byte { - // The corresponding private key to a public certificate is always a normal - // private key. For signature serialization purposes, ensure we use the - // proper key algorithm name in case the public cert algorithm name is passed. - algoname = pubAlgoToPrivAlgo(algoname) - - length := stringLength(len(algoname)) +// serialize a signed slice according to RFC 4254 6.6. The name should +// be a key type name, rather than a cert type name. +func serializeSignature(name string, sig []byte) []byte { + length := stringLength(len(name)) length += stringLength(len(sig)) ret := make([]byte, length) - r := marshalString(ret, []byte(algoname)) + r := marshalString(ret, []byte(name)) r = marshalString(r, sig) return ret } -func verifySignature(hash []byte, sig *signature, key interface{}) error { - switch pubKey := key.(type) { - case *rsa.PublicKey: - return verifyRSASignature(hash, sig, pubKey) - case *dsa.PublicKey: - return verifyDSASignature(hash, sig, pubKey) - case *ecdsa.PublicKey: - return verifyECDSASignature(hash, sig, pubKey) - case *OpenSSHCertV01: - return verifySignature(hash, sig, pubKey.Key) - } - return fmt.Errorf("ssh: unknown key type %T", key) -} - -func verifyRSASignature(hash []byte, sig *signature, key *rsa.PublicKey) error { - return rsa.VerifyPKCS1v15(key, crypto.SHA1, hash, sig.Blob) -} - -func verifyDSASignature(hash []byte, sig *signature, key *dsa.PublicKey) error { - // Per RFC 4253, section 6.6, - // The value for 'dss_signature_blob' is encoded as a string containing - // r, followed by s (which are 160-bit integers, without lengths or - // padding, unsigned, and in network byte order). - // For DSS purposes, sig.Blob should be exactly 40 bytes in length. - if len(sig.Blob) != 40 { - return fmt.Errorf("ssh: improper dss signature length of %d", len(sig.Blob)) - } - r := new(big.Int).SetBytes(sig.Blob[:20]) - s := new(big.Int).SetBytes(sig.Blob[20:]) - if !dsa.Verify(key, hash, r, s) { - return errors.New("ssh: unable to verify dsa signature") - } - return nil -} - -func verifyECDSASignature(hash []byte, sig *signature, key *ecdsa.PublicKey) error { - // Per RFC 5656, section 3.1.2, - // The ecdsa_signature_blob value has the following specific encoding: - // mpint r - // mpint s - r, rest, ok := parseInt(sig.Blob) - if !ok { - return errors.New("ssh: ecdsa signature blob parse failed") - } - s, rest, ok := parseInt(rest) - if !ok || len(rest) > 0 { - return errors.New("ssh: ecdsa signature blob parse failed") - } - if !ecdsa.Verify(key, hash, r, s) { - return errors.New("ssh: unable to verify ecdsa signature") - } - return nil -} - -// serialize a *rsa.PublicKey or *dsa.PublicKey according to RFC 4253 6.6. -func serializePublicKey(key interface{}) []byte { - var pubKeyBytes []byte - algoname := algoName(key) - switch key := key.(type) { - case *rsa.PublicKey: - pubKeyBytes = marshalPubRSA(key) - case *dsa.PublicKey: - pubKeyBytes = marshalPubDSA(key) - case *ecdsa.PublicKey: - pubKeyBytes = marshalPubECDSA(key) - case *OpenSSHCertV01: - pubKeyBytes = marshalOpenSSHCertV01(key) - default: - panic("unexpected key type") - } +// MarshalPublicKey serializes a supported key or certificate for use +// by the SSH wire protocol. It can be used for comparison with the +// pubkey argument of ServerConfig's PublicKeyCallback as well as for +// generating an authorized_keys or host_keys file. +func MarshalPublicKey(key PublicKey) []byte { + // See also RFC 4253 6.6. + algoname := key.PrivateKeyAlgo() + blob := key.Marshal() length := stringLength(len(algoname)) - length += len(pubKeyBytes) + length += len(blob) ret := make([]byte, length) r := marshalString(ret, []byte(algoname)) - copy(r, pubKeyBytes) + copy(r, blob) return ret } -func algoName(key interface{}) string { - switch key.(type) { - case *rsa.PublicKey: - return KeyAlgoRSA - case *dsa.PublicKey: - return KeyAlgoDSA - case *ecdsa.PublicKey: - switch key.(*ecdsa.PublicKey).Params().BitSize { - case 256: - return KeyAlgoECDSA256 - case 384: - return KeyAlgoECDSA384 - case 521: - return KeyAlgoECDSA521 - } - case *OpenSSHCertV01: - switch key.(*OpenSSHCertV01).Key.(type) { - case *rsa.PublicKey: - return CertAlgoRSAv01 - case *dsa.PublicKey: - return CertAlgoDSAv01 - case *ecdsa.PublicKey: - switch key.(*OpenSSHCertV01).Key.(*ecdsa.PublicKey).Params().BitSize { - case 256: - return CertAlgoECDSA256v01 - case 384: - return CertAlgoECDSA384v01 - case 521: - return CertAlgoECDSA521v01 - } - } - } - panic(fmt.Sprintf("unexpected key type %T", key)) -} - // pubAlgoToPrivAlgo returns the private key algorithm format name that // corresponds to a given public key algorithm format name. For most // public keys, the private key algorithm name is the same. For some diff --git a/ssh/common_test.go b/ssh/common_test.go index 0d8f939..058fb04 100644 --- a/ssh/common_test.go +++ b/ssh/common_test.go @@ -5,11 +5,6 @@ package ssh import ( - "crypto/dsa" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rsa" - "errors" "testing" ) @@ -29,49 +24,3 @@ func TestSafeString(t *testing.T) { } } } - -func TestAlgoNameSupported(t *testing.T) { - supported := map[string]interface{}{ - KeyAlgoRSA: new(rsa.PublicKey), - KeyAlgoDSA: new(dsa.PublicKey), - KeyAlgoECDSA256: &ecdsa.PublicKey{Curve: elliptic.P256()}, - KeyAlgoECDSA384: &ecdsa.PublicKey{Curve: elliptic.P384()}, - KeyAlgoECDSA521: &ecdsa.PublicKey{Curve: elliptic.P521()}, - CertAlgoRSAv01: &OpenSSHCertV01{Key: new(rsa.PublicKey)}, - CertAlgoDSAv01: &OpenSSHCertV01{Key: new(dsa.PublicKey)}, - CertAlgoECDSA256v01: &OpenSSHCertV01{Key: &ecdsa.PublicKey{Curve: elliptic.P256()}}, - CertAlgoECDSA384v01: &OpenSSHCertV01{Key: &ecdsa.PublicKey{Curve: elliptic.P384()}}, - CertAlgoECDSA521v01: &OpenSSHCertV01{Key: &ecdsa.PublicKey{Curve: elliptic.P521()}}, - } - - for expected, key := range supported { - actual := algoName(key) - if expected != actual { - t.Errorf("expected: %s, actual: %s", expected, actual) - } - } - -} - -func TestAlgoNameNotSupported(t *testing.T) { - notSupported := []interface{}{ - &ecdsa.PublicKey{Curve: elliptic.P224()}, - &OpenSSHCertV01{Key: &ecdsa.PublicKey{Curve: elliptic.P224()}}, - } - - panicTest := func(key interface{}) (algo string, err error) { - defer func() { - if r := recover(); r != nil { - err = errors.New(r.(string)) - } - }() - algo = algoName(key) - return - } - - for _, unsupportedKey := range notSupported { - if algo, err := panicTest(unsupportedKey); err == nil { - t.Errorf("Expected a panic, Got: %s (for type %T)", algo, unsupportedKey) - } - } -} diff --git a/ssh/keys.go b/ssh/keys.go index 1dd6856..c135d3a 100644 --- a/ssh/keys.go +++ b/ssh/keys.go @@ -6,6 +6,7 @@ package ssh import ( "bytes" + "crypto" "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" @@ -25,7 +26,7 @@ const ( ) // parsePubKey parses a public key according to RFC 4253, section 6.6. -func parsePubKey(in []byte) (out interface{}, rest []byte, ok bool) { +func parsePubKey(in []byte) (pubKey PublicKey, rest []byte, ok bool) { algo, in, ok := parseString(in) if !ok { return @@ -41,141 +42,7 @@ func parsePubKey(in []byte) (out interface{}, rest []byte, ok bool) { case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: return parseOpenSSHCertV01(in, string(algo)) } - panic("ssh: unknown public key type") -} - -// parseRSA parses an RSA key according to RFC 4253, section 6.6. -func parseRSA(in []byte) (out *rsa.PublicKey, rest []byte, ok bool) { - key := new(rsa.PublicKey) - - bigE, in, ok := parseInt(in) - if !ok || bigE.BitLen() > 24 { - return - } - e := bigE.Int64() - if e < 3 || e&1 == 0 { - ok = false - return - } - key.E = int(e) - - if key.N, in, ok = parseInt(in); !ok { - return - } - - ok = true - return key, in, ok -} - -// parseDSA parses an DSA key according to RFC 4253, section 6.6. -func parseDSA(in []byte) (out *dsa.PublicKey, rest []byte, ok bool) { - key := new(dsa.PublicKey) - - if key.P, in, ok = parseInt(in); !ok { - return - } - - if key.Q, in, ok = parseInt(in); !ok { - return - } - - if key.G, in, ok = parseInt(in); !ok { - return - } - - if key.Y, in, ok = parseInt(in); !ok { - return - } - - ok = true - 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 -} - -// marshalPubRSA serializes an RSA public key according to RFC 4253, section 6.6. -func marshalPubRSA(key *rsa.PublicKey) []byte { - e := new(big.Int).SetInt64(int64(key.E)) - length := intLength(e) - length += intLength(key.N) - - ret := make([]byte, length) - r := marshalInt(ret, e) - r = marshalInt(r, key.N) - - return ret -} - -// marshalPubDSA serializes an DSA public key according to RFC 4253, section 6.6. -func marshalPubDSA(key *dsa.PublicKey) []byte { - length := intLength(key.P) - length += intLength(key.Q) - length += intLength(key.G) - length += intLength(key.Y) - - ret := make([]byte, length) - r := marshalInt(ret, key.P) - r = marshalInt(r, key.Q) - r = marshalInt(r, key.G) - 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 + return nil, nil, false } // parseAuthorizedKey parses a public key in OpenSSH authorized_keys format @@ -307,28 +174,297 @@ func ParseAuthorizedKey(in []byte) (out interface{}, comment string, options []s // ParsePublicKey parses an SSH public key formatted for use in // the SSH wire protocol. -func ParsePublicKey(in []byte) (out interface{}, rest []byte, ok bool) { +func ParsePublicKey(in []byte) (out PublicKey, rest []byte, ok bool) { return parsePubKey(in) } // MarshalAuthorizedKey returns a byte stream suitable for inclusion // in an OpenSSH authorized_keys file following the format specified // in the sshd(8) manual page. -func MarshalAuthorizedKey(key interface{}) []byte { +func MarshalAuthorizedKey(key PublicKey) []byte { b := &bytes.Buffer{} - b.WriteString(algoName(key)) + b.WriteString(key.PublicKeyAlgo()) b.WriteByte(' ') e := base64.NewEncoder(base64.StdEncoding, b) - e.Write(serializePublicKey(key)) + e.Write(MarshalPublicKey(key)) e.Close() b.WriteByte('\n') return b.Bytes() } -// MarshalPublicKey serializes a supported key or certificate for use by the -// SSH wire protocol. It can be used for comparison with the pubkey argument -// of ServerConfig's PublicKeyCallback as well as for generating an -// authorized_keys or host_keys file. -func MarshalPublicKey(key interface{}) []byte { - return serializePublicKey(key) +// PublicKey is an abstraction of different types of public keys. +type PublicKey interface { + // PrivateKeyAlgo returns the name of the encryption system. + PrivateKeyAlgo() string + + // PublicKeyAlgo returns the algorithm for the public key, + // which may be different from PrivateKeyAlgo for certificates. + PublicKeyAlgo() string + + // Marshal returns the serialized key data in SSH wire format, + // without the name prefix. Callers should typically use + // MarshalPublicKey(). + Marshal() []byte + + // Verify that sig is a signature on the given data using this + // key. This function will hash the data appropriately first. + Verify(data []byte, sigBlob []byte) bool + + // RawKey returns the underlying object, eg. *rsa.PublicKey. + RawKey() interface{} +} + +// TODO(hanwen): define PrivateKey too. + +type rsaPublicKey rsa.PublicKey + +func (r *rsaPublicKey) PrivateKeyAlgo() string { + return "ssh-rsa" +} + +func (r *rsaPublicKey) PublicKeyAlgo() string { + return "ssh-rsa" +} + +func (r *rsaPublicKey) RawKey() interface{} { + return (*rsa.PublicKey)(r) +} + +// parseRSA parses an RSA key according to RFC 4253, section 6.6. +func parseRSA(in []byte) (out PublicKey, rest []byte, ok bool) { + key := new(rsa.PublicKey) + + bigE, in, ok := parseInt(in) + if !ok || bigE.BitLen() > 24 { + return + } + e := bigE.Int64() + if e < 3 || e&1 == 0 { + ok = false + return + } + key.E = int(e) + + if key.N, in, ok = parseInt(in); !ok { + return + } + + ok = true + return NewRSAPublicKey(key), in, ok +} + +func (r *rsaPublicKey) Marshal() []byte { + // See RFC 4253, section 6.6. + e := new(big.Int).SetInt64(int64(r.E)) + length := intLength(e) + length += intLength(r.N) + + ret := make([]byte, length) + rest := marshalInt(ret, e) + marshalInt(rest, r.N) + + return ret +} + +func (r *rsaPublicKey) Verify(data []byte, sig []byte) bool { + h := crypto.SHA1.New() + h.Write(data) + digest := h.Sum(nil) + return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig) == nil +} + +func NewRSAPublicKey(k *rsa.PublicKey) PublicKey { + return (*rsaPublicKey)(k) +} + +type dsaPublicKey dsa.PublicKey + +func (r *dsaPublicKey) PrivateKeyAlgo() string { + return "ssh-dss" +} +func (r *dsaPublicKey) PublicKeyAlgo() string { + return "ssh-dss" +} +func (r *dsaPublicKey) RawKey() interface{} { + return (*dsa.PublicKey)(r) +} + +// parseDSA parses an DSA key according to RFC 4253, section 6.6. +func parseDSA(in []byte) (out PublicKey, rest []byte, ok bool) { + key := new(dsa.PublicKey) + + if key.P, in, ok = parseInt(in); !ok { + return + } + + if key.Q, in, ok = parseInt(in); !ok { + return + } + + if key.G, in, ok = parseInt(in); !ok { + return + } + + if key.Y, in, ok = parseInt(in); !ok { + return + } + + ok = true + return NewDSAPublicKey(key), in, ok +} + +func (r *dsaPublicKey) Marshal() []byte { + // See RFC 4253, section 6.6. + length := intLength(r.P) + length += intLength(r.Q) + length += intLength(r.G) + length += intLength(r.Y) + + ret := make([]byte, length) + rest := marshalInt(ret, r.P) + rest = marshalInt(rest, r.Q) + rest = marshalInt(rest, r.G) + marshalInt(rest, r.Y) + + return ret +} + +func (k *dsaPublicKey) Verify(data []byte, sigBlob []byte) bool { + h := crypto.SHA1.New() + h.Write(data) + digest := h.Sum(nil) + + // Per RFC 4253, section 6.6, + // The value for 'dss_signature_blob' is encoded as a string containing + // r, followed by s (which are 160-bit integers, without lengths or + // padding, unsigned, and in network byte order). + // For DSS purposes, sig.Blob should be exactly 40 bytes in length. + if len(sigBlob) != 40 { + return false + } + r := new(big.Int).SetBytes(sigBlob[:20]) + s := new(big.Int).SetBytes(sigBlob[20:]) + return dsa.Verify((*dsa.PublicKey)(k), digest, r, s) +} + +func NewDSAPublicKey(k *dsa.PublicKey) PublicKey { + return (*dsaPublicKey)(k) +} + +type ecdsaPublicKey ecdsa.PublicKey + +func NewECDSAPublicKey(k *ecdsa.PublicKey) PublicKey { + return (*ecdsaPublicKey)(k) +} +func (r *ecdsaPublicKey) RawKey() interface{} { + return (*ecdsa.PublicKey)(r) +} + +func (key *ecdsaPublicKey) PrivateKeyAlgo() string { + return "ecdh-sha2-" + key.nistID() +} + +func (key *ecdsaPublicKey) nistID() string { + switch key.Params().BitSize { + case 256: + return "nistp256" + case 384: + return "nistp384" + case 521: + return "nistp521" + } + panic("ssh: unsupported ecdsa key size") +} + +// RFC 5656, section 6.2.1 (for ECDSA). +func (key *ecdsaPublicKey) hash() crypto.Hash { + switch key.Params().BitSize { + case 256: + return crypto.SHA256 + case 384: + return crypto.SHA384 + case 521: + return crypto.SHA512 + } + panic("ssh: unsupported ecdsa key size") +} + +func (key *ecdsaPublicKey) PublicKeyAlgo() string { + switch key.Params().BitSize { + case 256: + return KeyAlgoECDSA256 + case 384: + return KeyAlgoECDSA384 + case 521: + return KeyAlgoECDSA521 + } + panic("ssh: unsupported ecdsa key size") +} + +// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1. +func parseECDSA(in []byte) (out 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 NewECDSAPublicKey(key), in, ok +} + +func (key *ecdsaPublicKey) Marshal() []byte { + // See RFC 5656, section 3.1. + keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y) + + ID := key.nistID() + length := stringLength(len(ID)) + length += stringLength(len(keyBytes)) + + ret := make([]byte, length) + r := marshalString(ret, []byte(ID)) + r = marshalString(r, keyBytes) + return ret +} + +func (key *ecdsaPublicKey) Verify(data []byte, sigBlob []byte) bool { + h := key.hash().New() + h.Write(data) + digest := h.Sum(nil) + + // Per RFC 5656, section 3.1.2, + // The ecdsa_signature_blob value has the following specific encoding: + // mpint r + // mpint s + r, rest, ok := parseInt(sigBlob) + if !ok { + return false + } + s, rest, ok := parseInt(rest) + if !ok || len(rest) > 0 { + return false + } + return ecdsa.Verify((*ecdsa.PublicKey)(key), digest, r, s) } diff --git a/ssh/keys_test.go b/ssh/keys_test.go new file mode 100644 index 0000000..b77cacb --- /dev/null +++ b/ssh/keys_test.go @@ -0,0 +1,60 @@ +package ssh + +import ( + "crypto" + "crypto/dsa" + "crypto/rand" + "crypto/rsa" + "reflect" + "testing" +) + +func TestRSAMarshal(t *testing.T) { + k0 := &rsakey.PublicKey + k1 := NewRSAPublicKey(k0) + k2, rest, ok := ParsePublicKey(MarshalPublicKey(k1)) + if !ok { + t.Errorf("could not parse back Blob output") + } + if len(rest) > 0 { + t.Errorf("trailing junk in RSA Blob() output") + } + if !reflect.DeepEqual(k0, k2.RawKey().(*rsa.PublicKey)) { + t.Errorf("got %#v in roundtrip, want %#v", k2.RawKey(), k0) + } +} + +func TestRSAKeyVerify(t *testing.T) { + pub := NewRSAPublicKey(&rsakey.PublicKey) + + data := []byte("sign me") + h := crypto.SHA1.New() + h.Write(data) + digest := h.Sum(nil) + + sig, err := rsa.SignPKCS1v15(rand.Reader, rsakey, crypto.SHA1, digest) + if err != nil { + t.Fatalf("SignPKCS1v15: %v", err) + } + + if !pub.Verify(data, sig) { + t.Errorf("publicKey.Verify failed") + } +} + +func TestDSAMarshal(t *testing.T) { + k0 := &dsakey.PublicKey + k1 := NewDSAPublicKey(k0) + k2, rest, ok := ParsePublicKey(MarshalPublicKey(k1)) + if !ok { + t.Errorf("could not parse back Blob output") + } + if len(rest) > 0 { + t.Errorf("trailing junk in DSA Blob() output") + } + if !reflect.DeepEqual(k0, k2.RawKey().(*dsa.PublicKey)) { + t.Errorf("got %#v in roundtrip, want %#v", k2.RawKey(), k0) + } +} + +// TODO(hanwen): test for ECDSA marshal. diff --git a/ssh/server.go b/ssh/server.go index 7e8e537..dc5cd9f 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -78,13 +78,12 @@ func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) error { if block == nil { return errors.New("ssh: no key found") } - var err error - s.rsa, err = x509.ParsePKCS1PrivateKey(block.Bytes) + rsa, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return err } - - s.rsaSerialized = serializePublicKey(&s.rsa.PublicKey) + s.rsa = rsa + s.rsaSerialized = MarshalPublicKey(NewRSAPublicKey(&rsa.PublicKey)) return nil } @@ -170,10 +169,7 @@ func (s *ServerConn) kexECDH(curve elliptic.Curve, magics *handshakeMagics, host return nil, err } - hostKey, err := s.serializedHostKey(hostKeyAlgo) - if err != nil { - return nil, err - } + hostKeyBytes := s.config.rsaSerialized serializedEphKey := elliptic.Marshal(curve, ephKey.PublicKey.X, ephKey.PublicKey.Y) @@ -186,7 +182,7 @@ func (s *ServerConn) kexECDH(curve elliptic.Curve, magics *handshakeMagics, host writeString(h, magics.serverVersion) writeString(h, magics.clientKexInit) writeString(h, magics.serverKexInit) - writeString(h, hostKey) + writeString(h, hostKeyBytes) writeString(h, kexECDHInit.ClientPubKey) writeString(h, serializedEphKey) @@ -196,15 +192,15 @@ func (s *ServerConn) kexECDH(curve elliptic.Curve, magics *handshakeMagics, host H := h.Sum(nil) - serializedSig, err := s.serializedHostKeySignature(hostKeyAlgo, H) + sig, err := s.serializedHostKeySignature(hostKeyAlgo, H) if err != nil { return nil, err } reply := kexECDHReplyMsg{ EphemeralPubKey: serializedEphKey, - HostKey: hostKey, - Signature: serializedSig, + HostKey: hostKeyBytes, + Signature: sig, } serialized := marshal(msgKexECDHReply, reply) @@ -220,14 +216,6 @@ func (s *ServerConn) kexECDH(curve elliptic.Curve, magics *handshakeMagics, host }, nil } -func (s *ServerConn) serializedHostKey(hostKeyAlgo string) ([]byte, error) { - switch hostKeyAlgo { - case hostAlgoRSA: - return s.config.rsaSerialized, nil - } - return nil, errors.New("ssh: internal error") -} - // validateECPublicKey checks that the point is a valid public key for // the given curve. See [SEC1], 3.2.2 func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool { @@ -280,17 +268,14 @@ func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha return nil, err } - hostKey, err := s.serializedHostKey(hostKeyAlgo) - if err != nil { - return nil, err - } + hostKeyBytes := s.config.rsaSerialized h := hashFunc.New() writeString(h, magics.clientVersion) writeString(h, magics.serverVersion) writeString(h, magics.clientKexInit) writeString(h, magics.serverKexInit) - writeString(h, hostKey) + writeString(h, hostKeyBytes) writeInt(h, kexDHInit.X) writeInt(h, Y) @@ -300,15 +285,15 @@ func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha H := h.Sum(nil) - serializedSig, err := s.serializedHostKeySignature(hostKeyAlgo, H) + sig, err := s.serializedHostKeySignature(hostKeyAlgo, H) if err != nil { return nil, err } kexDHReply := kexDHReplyMsg{ - HostKey: hostKey, + HostKey: hostKeyBytes, Y: Y, - Signature: serializedSig, + Signature: sig, } packet = marshal(msgKexDHReply, kexDHReply) @@ -316,7 +301,7 @@ func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha return &kexResult{ H: H, K: K, - HostKey: hostKey, + HostKey: hostKeyBytes, Hash: hashFunc, }, nil } @@ -417,7 +402,6 @@ func (s *ServerConn) clientInitHandshake(clientKexInit *kexInitMsg, clientKexIni if !ok { return errors.New("ssh: no common algorithms") } - if clientKexInit.FirstKexFollows && kexAlgo != clientKexInit.KexAlgos[0] { // The client sent a Kex message for the wrong algorithm, // which we have to ignore. @@ -619,14 +603,8 @@ userAuthLoop: if !ok { return ParseError{msgUserAuthRequest} } - hashFunc, ok := hashFuncs[algo] - if !ok { - return errors.New("ssh: isAcceptableAlgo incorrect") - } - h := hashFunc.New() - h.Write(signedData) - digest := h.Sum(nil) - if verifySignature(digest, sig, key) != nil { + + if !key.Verify(signedData, sig.Blob) { return ParseError{msgUserAuthRequest} } // TODO(jmpittman): Implement full validation for certificates. diff --git a/ssh/test/keys_test.go b/ssh/test/keys_test.go index c9c1ec8..363872c 100644 --- a/ssh/test/keys_test.go +++ b/ssh/test/keys_test.go @@ -1,7 +1,6 @@ package test import ( - "crypto/rsa" "crypto/x509" "encoding/pem" "reflect" @@ -124,6 +123,7 @@ AwEHoUQDQgAEi9Hdw6KvZcWxfg2IDhA7UkpDtzzt6ZqJXSsFdLd+Kx4S3Sx4cVO+ func TestMarshalParsePublicKey(t *testing.T) { pub := getTestPublicKey(t) + authKeys := ssh.MarshalAuthorizedKey(pub) actualFields := strings.Fields(string(authKeys)) if len(actualFields) == 0 { @@ -170,7 +170,7 @@ func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) { } -func getTestPublicKey(t *testing.T) *rsa.PublicKey { +func getTestPublicKey(t *testing.T) ssh.PublicKey { block, _ := pem.Decode([]byte(testClientPrivateKey)) if block == nil { t.Fatalf("pem.Decode: %v", testClientPrivateKey) @@ -180,7 +180,7 @@ func getTestPublicKey(t *testing.T) *rsa.PublicKey { t.Fatalf("x509.ParsePKCS1PrivateKey: %v", err) } - return &priv.PublicKey + return ssh.NewRSAPublicKey(&priv.PublicKey) } func TestAuth(t *testing.T) { diff --git a/ssh/test/test_unix_test.go b/ssh/test/test_unix_test.go index 0c74b33..7ab5e22 100644 --- a/ssh/test/test_unix_test.go +++ b/ssh/test/test_unix_test.go @@ -71,7 +71,7 @@ func init() { if err != nil { panic("ParsePKCS1PrivateKey: " + err.Error()) } - serializedHostKey = ssh.MarshalPublicKey(&priv.PublicKey) + serializedHostKey = ssh.MarshalPublicKey(ssh.NewRSAPublicKey(&priv.PublicKey)) } type server struct { @@ -266,15 +266,15 @@ type keychain struct { keys []interface{} } -func (k *keychain) Key(i int) (interface{}, error) { +func (k *keychain) Key(i int) (ssh.PublicKey, error) { if i < 0 || i >= len(k.keys) { return nil, nil } switch key := k.keys[i].(type) { case *rsa.PrivateKey: - return &key.PublicKey, nil + return ssh.NewRSAPublicKey(&key.PublicKey), nil case *dsa.PrivateKey: - return &key.PublicKey, nil + return ssh.NewDSAPublicKey(&key.PublicKey), nil } panic("unknown key type") }