Browse Source

go.crypto/ssh: Begin adding server side support for more than RSA for client key auth

R=agl, dave, hanwen
CC=ekg, golang-dev
https://golang.org/cl/13528044
Jonathan Pittman 12 years ago
parent
commit
6a743c56c7
5 changed files with 131 additions and 79 deletions
  1. 3 27
      ssh/client.go
  2. 1 1
      ssh/client_auth.go
  3. 2 1
      ssh/client_auth_test.go
  4. 96 12
      ssh/common.go
  5. 29 38
      ssh/server.go

+ 3 - 27
ssh/client.go

@@ -9,7 +9,6 @@ import (
 	"crypto/ecdsa"
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/elliptic"
 	"crypto/rand"
 	"crypto/rand"
-	"crypto/rsa"
 	"encoding/binary"
 	"encoding/binary"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
@@ -248,21 +247,10 @@ func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte
 	}
 	}
 
 
 	// Select hash function to match the hostkey algorithm, as per
 	// 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).
 	// 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)
 		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)
 	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.
 // kexResult captures the outcome of a key exchange.
 type kexResult struct {
 type kexResult struct {
 	// Session hash. See also RFC 4253, section 8.
 	// Session hash. See also RFC 4253, section 8.

+ 1 - 1
ssh/client_auth.go

@@ -340,7 +340,7 @@ func handleAuthResponse(t *transport) (bool, []string, error) {
 	panic("unreachable")
 	panic("unreachable")
 }
 }
 
 
-// ClientAuthKeyring returns a ClientAuth using public key authentication via
+// ClientAuthAgent returns a ClientAuth using public key authentication via
 // an agent.
 // an agent.
 func ClientAuthAgent(agent *AgentClient) ClientAuth {
 func ClientAuthAgent(agent *AgentClient) ClientAuth {
 	return ClientAuthKeyring(&agentKeyring{agent: agent})
 	return ClientAuthKeyring(&agentKeyring{agent: agent})

+ 2 - 1
ssh/client_auth_test.go

@@ -9,7 +9,6 @@ import (
 	"crypto"
 	"crypto"
 	"crypto/dsa"
 	"crypto/dsa"
 	"crypto/rsa"
 	"crypto/rsa"
-	_ "crypto/sha1"
 	"crypto/x509"
 	"crypto/x509"
 	"encoding/pem"
 	"encoding/pem"
 	"errors"
 	"errors"
@@ -18,6 +17,8 @@ import (
 	"math/big"
 	"math/big"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
+
+	_ "crypto/sha1"
 )
 )
 
 
 // private key for mock server
 // private key for mock server

+ 96 - 12
ssh/common.go

@@ -14,6 +14,10 @@ import (
 	"fmt"
 	"fmt"
 	"math/big"
 	"math/big"
 	"sync"
 	"sync"
+
+	_ "crypto/sha1"
+	_ "crypto/sha256"
+	_ "crypto/sha512"
 )
 )
 
 
 // These are string constants in the SSH protocol.
 // These are string constants in the SSH protocol.
@@ -38,6 +42,21 @@ var supportedKexAlgos = []string{
 var supportedHostKeyAlgos = []string{hostAlgoRSA}
 var supportedHostKeyAlgos = []string{hostAlgoRSA}
 var supportedCompressions = []string{compressionNone}
 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.
 // dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
 type dhGroup struct {
 type dhGroup struct {
 	g, p *big.Int
 	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.
 // serialize a signed slice according to RFC 4254 6.6.
 func serializeSignature(algoname string, sig []byte) []byte {
 func serializeSignature(algoname string, sig []byte) []byte {
-	switch algoname {
 	// The corresponding private key to a public certificate is always a normal
 	// The corresponding private key to a public certificate is always a normal
 	// private key.  For signature serialization purposes, ensure we use the
 	// private key.  For signature serialization purposes, ensure we use the
 	// proper key algorithm name in case the public cert algorithm name is passed.
 	// 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(algoname))
 	length += stringLength(len(sig))
 	length += stringLength(len(sig))
 
 
@@ -247,6 +256,60 @@ func serializeSignature(algoname string, sig []byte) []byte {
 	return ret
 	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.
 // serialize a *rsa.PublicKey or *dsa.PublicKey according to RFC 4253 6.6.
 func serializePublicKey(key interface{}) []byte {
 func serializePublicKey(key interface{}) []byte {
 	var pubKeyBytes []byte
 	var pubKeyBytes []byte
@@ -307,6 +370,27 @@ func algoName(key interface{}) string {
 	panic(fmt.Sprintf("unexpected key type %T", key))
 	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
 // buildDataSignedForAuth returns the data that is signed in order to prove
 // posession of a private key. See RFC 4252, section 7.
 // posession of a private key. See RFC 4252, section 7.
 func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
 func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {

+ 29 - 38
ssh/server.go

@@ -20,12 +20,14 @@ import (
 	"net"
 	"net"
 	"sync"
 	"sync"
 
 
-	_ "crypto/sha256"
-	_ "crypto/sha512"
+	_ "crypto/sha1"
 )
 )
 
 
 type ServerConfig struct {
 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
 	rsaSerialized []byte
 
 
 	// Rand provides the source of entropy for key exchange. If Rand is
 	// 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
 	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
 // cachedPubKey contains the results of querying whether a public key is
 // acceptable for a user. The cache only applies to a single ServerConn.
 // acceptable for a user. The cache only applies to a single ServerConn.
 type cachedPubKey struct {
 type cachedPubKey struct {
@@ -490,7 +480,12 @@ func (s *ServerConn) clientInitHandshake(clientKexInit *kexInitMsg, clientKexIni
 }
 }
 
 
 func isAcceptableAlgo(algo string) bool {
 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.
 // testPubKey returns true if the given public key is acceptable for the user.
@@ -607,38 +602,34 @@ userAuthLoop:
 					continue userAuthLoop
 					continue userAuthLoop
 				}
 				}
 			} else {
 			} else {
-				sig, payload, ok := parseString(payload)
+				sig, payload, ok := parseSignature(payload)
 				if !ok || len(payload) > 0 {
 				if !ok || len(payload) > 0 {
 					return ParseError{msgUserAuthRequest}
 					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
 					break
 				}
 				}
-				rsaSig, ok := parseRSASig(sig)
+				signedData := buildDataSignedForAuth(H, userAuthReq, algoBytes, pubKey)
+				key, _, ok := parsePubKey(pubKey)
 				if !ok {
 				if !ok {
 					return ParseError{msgUserAuthRequest}
 					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")
 					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
 				s.User = userAuthReq.User
 				if s.testPubKey(userAuthReq.User, algo, pubKey) {
 				if s.testPubKey(userAuthReq.User, algo, pubKey) {
 					break userAuthLoop
 					break userAuthLoop