diff --git a/ssh/client.go b/ssh/client.go index 14ebb5e..f79fd02 100644 --- a/ssh/client.go +++ b/ssh/client.go @@ -9,7 +9,6 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" - "crypto/rsa" "encoding/binary" "errors" "fmt" @@ -248,21 +247,10 @@ func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte } // Select hash function to match the hostkey algorithm, as per - // RFC 4253, section 6.1 (for RSA/DSS) and RFC 5656, section + // RFC 4253, section 6.6 (for RSA/DSS) and RFC 5656, section // 6.2.1 (for ECDSA). - var hashFunc crypto.Hash - switch hostKeyAlgo { - case KeyAlgoRSA: - hashFunc = crypto.SHA1 - case KeyAlgoDSA: - hashFunc = crypto.SHA1 - case KeyAlgoECDSA256: - hashFunc = crypto.SHA256 - case KeyAlgoECDSA384: - hashFunc = crypto.SHA384 - case KeyAlgoECDSA521: - hashFunc = crypto.SHA512 - default: + hashFunc, ok := hashFuncs[hostKeyAlgo] + if !ok { return errors.New("ssh: unknown key algorithm: " + hostKeyAlgo) } @@ -281,18 +269,6 @@ func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte return verifySignature(digest, sig, hostKey) } -func verifySignature(hash []byte, sig *signature, key interface{}) error { - switch pubKey := key.(type) { - case *rsa.PublicKey: - return verifyRSASignature(hash, sig, pubKey) - } - 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) -} - // kexResult captures the outcome of a key exchange. type kexResult struct { // Session hash. See also RFC 4253, section 8. diff --git a/ssh/client_auth.go b/ssh/client_auth.go index abdb24a..7946843 100644 --- a/ssh/client_auth.go +++ b/ssh/client_auth.go @@ -340,7 +340,7 @@ func handleAuthResponse(t *transport) (bool, []string, error) { panic("unreachable") } -// ClientAuthKeyring returns a ClientAuth using public key authentication via +// ClientAuthAgent returns a ClientAuth using public key authentication via // an agent. func ClientAuthAgent(agent *AgentClient) ClientAuth { return ClientAuthKeyring(&agentKeyring{agent: agent}) diff --git a/ssh/client_auth_test.go b/ssh/client_auth_test.go index ba11949..df6dbd6 100644 --- a/ssh/client_auth_test.go +++ b/ssh/client_auth_test.go @@ -9,7 +9,6 @@ import ( "crypto" "crypto/dsa" "crypto/rsa" - _ "crypto/sha1" "crypto/x509" "encoding/pem" "errors" @@ -18,6 +17,8 @@ import ( "math/big" "strings" "testing" + + _ "crypto/sha1" ) // private key for mock server diff --git a/ssh/common.go b/ssh/common.go index 3df556d..dab10d5 100644 --- a/ssh/common.go +++ b/ssh/common.go @@ -14,6 +14,10 @@ import ( "fmt" "math/big" "sync" + + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" ) // These are string constants in the SSH protocol. @@ -38,6 +42,21 @@ var supportedKexAlgos = []string{ var supportedHostKeyAlgos = []string{hostAlgoRSA} var supportedCompressions = []string{compressionNone} +// hashFuncs keeps the mapping of supported algorithms to their respective +// hashes needed for signature verification. +var hashFuncs = map[string]crypto.Hash{ + KeyAlgoRSA: crypto.SHA1, + KeyAlgoDSA: crypto.SHA1, + KeyAlgoECDSA256: crypto.SHA256, + KeyAlgoECDSA384: crypto.SHA384, + KeyAlgoECDSA521: crypto.SHA512, + CertAlgoRSAv01: crypto.SHA1, + CertAlgoDSAv01: crypto.SHA1, + CertAlgoECDSA256v01: crypto.SHA256, + CertAlgoECDSA384v01: crypto.SHA384, + CertAlgoECDSA521v01: crypto.SHA512, +} + // dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement. type dhGroup struct { g, p *big.Int @@ -222,21 +241,11 @@ func ecHash(curve elliptic.Curve) crypto.Hash { // serialize a signed slice according to RFC 4254 6.6. 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 key algorithm name in case the public cert algorithm name is passed. - case CertAlgoRSAv01: - algoname = KeyAlgoRSA - case CertAlgoDSAv01: - algoname = KeyAlgoDSA - case CertAlgoECDSA256v01: - algoname = KeyAlgoECDSA256 - case CertAlgoECDSA384v01: - algoname = KeyAlgoECDSA384 - case CertAlgoECDSA521v01: - algoname = KeyAlgoECDSA521 - } + algoname = pubAlgoToPrivAlgo(algoname) + length := stringLength(len(algoname)) length += stringLength(len(sig)) @@ -247,6 +256,60 @@ func serializeSignature(algoname string, sig []byte) []byte { 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 @@ -307,6 +370,27 @@ func algoName(key interface{}) string { 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 +// situations, such as openssh certificates, the private key algorithm and +// public key algorithm names differ. This accounts for those situations. +func pubAlgoToPrivAlgo(pubAlgo string) string { + switch pubAlgo { + case CertAlgoRSAv01: + return KeyAlgoRSA + case CertAlgoDSAv01: + return KeyAlgoDSA + case CertAlgoECDSA256v01: + return KeyAlgoECDSA256 + case CertAlgoECDSA384v01: + return KeyAlgoECDSA384 + case CertAlgoECDSA521v01: + return KeyAlgoECDSA521 + } + return pubAlgo +} + // buildDataSignedForAuth returns the data that is signed in order to prove // posession of a private key. See RFC 4252, section 7. func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte { diff --git a/ssh/server.go b/ssh/server.go index c172a09..7e8e537 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -20,12 +20,14 @@ import ( "net" "sync" - _ "crypto/sha256" - _ "crypto/sha512" + _ "crypto/sha1" ) type ServerConfig struct { - rsa *rsa.PrivateKey + rsa *rsa.PrivateKey + + // rsaSerialized is the serialized form of the public key that + // corresponds to the private key held in the rsa field. rsaSerialized []byte // Rand provides the source of entropy for key exchange. If Rand is @@ -86,18 +88,6 @@ func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) error { return nil } -func parseRSASig(in []byte) (sig []byte, ok bool) { - algo, in, ok := parseString(in) - if !ok || string(algo) != hostAlgoRSA { - return nil, false - } - sig, in, ok = parseString(in) - if len(in) > 0 { - ok = false - } - return -} - // cachedPubKey contains the results of querying whether a public key is // acceptable for a user. The cache only applies to a single ServerConn. type cachedPubKey struct { @@ -490,7 +480,12 @@ func (s *ServerConn) clientInitHandshake(clientKexInit *kexInitMsg, clientKexIni } func isAcceptableAlgo(algo string) bool { - return algo == hostAlgoRSA + switch algo { + case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, + CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: + return true + } + return false } // testPubKey returns true if the given public key is acceptable for the user. @@ -607,38 +602,34 @@ userAuthLoop: continue userAuthLoop } } else { - sig, payload, ok := parseString(payload) + sig, payload, ok := parseSignature(payload) if !ok || len(payload) > 0 { return ParseError{msgUserAuthRequest} } - if !isAcceptableAlgo(algo) { + // Ensure the public key algo and signature algo + // are supported. Compare the private key + // algorithm name that corresponds to algo with + // sig.Format. This is usually the same, but + // for certs, the names differ. + if !isAcceptableAlgo(algo) || !isAcceptableAlgo(sig.Format) || pubAlgoToPrivAlgo(algo) != sig.Format { break } - rsaSig, ok := parseRSASig(sig) + signedData := buildDataSignedForAuth(H, userAuthReq, algoBytes, pubKey) + key, _, ok := parsePubKey(pubKey) if !ok { return ParseError{msgUserAuthRequest} } - signedData := buildDataSignedForAuth(H, userAuthReq, algoBytes, pubKey) - switch algo { - case hostAlgoRSA: - hashFunc := crypto.SHA1 - h := hashFunc.New() - h.Write(signedData) - digest := h.Sum(nil) - key, _, ok := parsePubKey(pubKey) - if !ok { - return ParseError{msgUserAuthRequest} - } - rsaKey, ok := key.(*rsa.PublicKey) - if !ok { - return ParseError{msgUserAuthRequest} - } - if rsa.VerifyPKCS1v15(rsaKey, hashFunc, digest, rsaSig) != nil { - return ParseError{msgUserAuthRequest} - } - default: + 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 { + return ParseError{msgUserAuthRequest} + } + // TODO(jmpittman): Implement full validation for certificates. s.User = userAuthReq.User if s.testPubKey(userAuthReq.User, algo, pubKey) { break userAuthLoop