Add tests for signature algorithm negotiation.

Change-Id: I5a263734560997b774014b5742877aa4b2940664
Reviewed-on: https://boringssl-review.googlesource.com/2289
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index a4bdef8..01b7581 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -129,8 +129,12 @@
 
 // Hash functions for TLS 1.2 (See RFC 5246, section A.4.1)
 const (
+	hashMD5    uint8 = 1
 	hashSHA1   uint8 = 2
+	hashSHA224 uint8 = 3
 	hashSHA256 uint8 = 4
+	hashSHA384 uint8 = 5
+	hashSHA512 uint8 = 6
 )
 
 // Signature algorithms for TLS 1.2 (See RFC 5246, section A.4.1)
@@ -346,6 +350,11 @@
 	// protection profiles to offer in DTLS-SRTP.
 	SRTPProtectionProfiles []uint16
 
+	// SignatureAndHashes, if not nil, overrides the default set of
+	// supported signature and hash algorithms to advertise in
+	// CertificateRequest.
+	SignatureAndHashes []signatureAndHash
+
 	// Bugs specifies optional misbehaviour to be used for testing other
 	// implementations.
 	Bugs ProtocolBugs
@@ -541,6 +550,14 @@
 	// SendSRTPProtectionProfile, if non-zero, is the SRTP profile that the
 	// server sends in the ServerHello instead of the negotiated one.
 	SendSRTPProtectionProfile uint16
+
+	// NoSignatureAndHashes, if true, causes the client to omit the
+	// signature and hashes extension.
+	//
+	// For a server, it will cause an empty list to be sent in the
+	// CertificateRequest message. None the less, the configured set will
+	// still be enforced.
+	NoSignatureAndHashes bool
 }
 
 func (c *Config) serverInit() {
@@ -655,6 +672,20 @@
 	return &c.Certificates[0]
 }
 
+func (c *Config) signatureAndHashesForServer() []signatureAndHash {
+	if c != nil && c.SignatureAndHashes != nil {
+		return c.SignatureAndHashes
+	}
+	return supportedClientCertSignatureAlgorithms
+}
+
+func (c *Config) signatureAndHashesForClient() []signatureAndHash {
+	if c != nil && c.SignatureAndHashes != nil {
+		return c.SignatureAndHashes
+	}
+	return supportedSKXSignatureAlgorithms
+}
+
 // BuildNameToCertificate parses c.Certificates and builds c.NameToCertificate
 // from the CommonName and SubjectAlternateName fields of each of the leaf
 // certificates.
@@ -806,3 +837,12 @@
 func unexpectedMessageError(wanted, got interface{}) error {
 	return fmt.Errorf("tls: received unexpected handshake message of type %T when waiting for %T", got, wanted)
 }
+
+func isSupportedSignatureAndHash(sigHash signatureAndHash, sigHashes []signatureAndHash) bool {
+	for _, s := range sigHashes {
+		if s == sigHash {
+			return true
+		}
+	}
+	return false
+}
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 71712a9..c4dff89 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -129,8 +129,8 @@
 		return errors.New("tls: short read from Rand: " + err.Error())
 	}
 
-	if hello.vers >= VersionTLS12 {
-		hello.signatureAndHashes = supportedSKXSignatureAlgorithms
+	if hello.vers >= VersionTLS12 && !c.config.Bugs.NoSignatureAndHashes {
+		hello.signatureAndHashes = c.config.signatureAndHashesForClient()
 	}
 
 	var session *ClientSessionState
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index bd6f702..32814d3 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -467,7 +467,9 @@
 		}
 		if c.vers >= VersionTLS12 {
 			certReq.hasSignatureAndHash = true
-			certReq.signatureAndHashes = supportedClientCertSignatureAlgorithms
+			if !config.Bugs.NoSignatureAndHashes {
+				certReq.signatureAndHashes = config.signatureAndHashesForServer()
+			}
 		}
 
 		// An empty list of certificateAuthorities signals to
@@ -567,6 +569,9 @@
 		var signatureAndHash signatureAndHash
 		if certVerify.hasSignatureAndHash {
 			signatureAndHash = certVerify.signatureAndHash
+			if !isSupportedSignatureAndHash(signatureAndHash, config.signatureAndHashesForServer()) {
+				return errors.New("tls: unsupported hash function for client certificate")
+			}
 		} else {
 			// Before TLS 1.2 the signature algorithm was implicit
 			// from the key type, and only one hash per signature
diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go
index 47f34cb..d59168f 100644
--- a/ssl/test/runner/key_agreement.go
+++ b/ssl/test/runner/key_agreement.go
@@ -12,7 +12,6 @@
 	"crypto/rand"
 	"crypto/rsa"
 	"crypto/sha1"
-	"crypto/sha256"
 	"crypto/x509"
 	"encoding/asn1"
 	"errors"
@@ -125,28 +124,20 @@
 	return md5sha1
 }
 
-// sha256Hash implements TLS 1.2's hash function.
-func sha256Hash(slices [][]byte) []byte {
-	h := sha256.New()
-	for _, slice := range slices {
-		h.Write(slice)
-	}
-	return h.Sum(nil)
-}
-
 // hashForServerKeyExchange hashes the given slices and returns their digest
 // and the identifier of the hash function used. The hashFunc argument is only
 // used for >= TLS 1.2 and precisely identifies the hash function to use.
 func hashForServerKeyExchange(sigType, hashFunc uint8, version uint16, slices ...[]byte) ([]byte, crypto.Hash, error) {
 	if version >= VersionTLS12 {
-		switch hashFunc {
-		case hashSHA256:
-			return sha256Hash(slices), crypto.SHA256, nil
-		case hashSHA1:
-			return sha1Hash(slices), crypto.SHA1, nil
-		default:
-			return nil, crypto.Hash(0), errors.New("tls: unknown hash function used by peer")
+		hash, err := lookupTLSHash(hashFunc)
+		if err != nil {
+			return nil, 0, err
 		}
+		h := hash.New()
+		for _, slice := range slices {
+			h.Write(slice)
+		}
+		return h.Sum(nil), hash, nil
 	}
 	if sigType == signatureECDSA {
 		return sha1Hash(slices), crypto.SHA1, nil
@@ -307,6 +298,10 @@
 		if len(sig) < 2 {
 			return errServerKeyExchange
 		}
+
+		if !isSupportedSignatureAndHash(signatureAndHash{ka.sigType, tls12HashId}, config.signatureAndHashesForClient()) {
+			return errors.New("tls: unsupported hash function for ServerKeyExchange")
+		}
 	}
 	sigLen := int(sig[0])<<8 | int(sig[1])
 	if sigLen+2 != len(sig) {
diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go
index d45c080..75a8933 100644
--- a/ssl/test/runner/prf.go
+++ b/ssl/test/runner/prf.go
@@ -185,6 +185,27 @@
 	return
 }
 
+// lookupTLSHash looks up the corresponding crypto.Hash for a given
+// TLS hash identifier.
+func lookupTLSHash(hash uint8) (crypto.Hash, error) {
+	switch hash {
+	case hashMD5:
+		return crypto.MD5, nil
+	case hashSHA1:
+		return crypto.SHA1, nil
+	case hashSHA224:
+		return crypto.SHA224, nil
+	case hashSHA256:
+		return crypto.SHA256, nil
+	case hashSHA384:
+		return crypto.SHA384, nil
+	case hashSHA512:
+		return crypto.SHA512, nil
+	default:
+		return 0, errors.New("tls: unsupported hash algorithm")
+	}
+}
+
 func newFinishedHash(version uint16, cipherSuite *cipherSuite) finishedHash {
 	if version >= VersionTLS12 {
 		newHash := sha256.New
@@ -331,11 +352,13 @@
 		return finishedSum30(md5Hash, sha1Hash, masterSecret, nil), crypto.MD5SHA1, nil
 	}
 	if h.version >= VersionTLS12 {
-		if signatureAndHash.hash != hashSHA256 {
-			return nil, 0, errors.New("tls: unsupported hash function for client certificate")
+		hashAlg, err := lookupTLSHash(signatureAndHash.hash)
+		if err != nil {
+			return nil, 0, err
 		}
-		digest := sha256.Sum256(h.buffer)
-		return digest[:], crypto.SHA256, nil
+		hash := hashAlg.New()
+		hash.Write(h.buffer)
+		return hash.Sum(nil), hashAlg, nil
 	}
 	if signatureAndHash.signature == signatureECDSA {
 		return h.server.Sum(nil), crypto.SHA1, nil
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 8c661a6..dca0479 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2030,6 +2030,114 @@
 	})
 }
 
+var testHashes = []struct {
+	name string
+	id   uint8
+}{
+	{"SHA1", hashSHA1},
+	{"SHA224", hashSHA224},
+	{"SHA256", hashSHA256},
+	{"SHA384", hashSHA384},
+	{"SHA512", hashSHA512},
+}
+
+func addSigningHashTests() {
+	// Make sure each hash works. Include some fake hashes in the list and
+	// ensure they're ignored.
+	for _, hash := range testHashes {
+		testCases = append(testCases, testCase{
+			name: "SigningHash-ClientAuth-" + hash.name,
+			config: Config{
+				ClientAuth: RequireAnyClientCert,
+				SignatureAndHashes: []signatureAndHash{
+					{signatureRSA, 42},
+					{signatureRSA, hash.id},
+					{signatureRSA, 255},
+				},
+			},
+			flags: []string{
+				"-cert-file", rsaCertificateFile,
+				"-key-file", rsaKeyFile,
+			},
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SigningHash-ServerKeyExchange-Sign-" + hash.name,
+			config: Config{
+				CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+				SignatureAndHashes: []signatureAndHash{
+					{signatureRSA, 42},
+					{signatureRSA, hash.id},
+					{signatureRSA, 255},
+				},
+			},
+		})
+	}
+
+	// Test that hash resolution takes the signature type into account.
+	testCases = append(testCases, testCase{
+		name: "SigningHash-ClientAuth-SignatureType",
+		config: Config{
+			ClientAuth: RequireAnyClientCert,
+			SignatureAndHashes: []signatureAndHash{
+				{signatureECDSA, hashSHA512},
+				{signatureRSA, hashSHA384},
+				{signatureECDSA, hashSHA1},
+			},
+		},
+		flags: []string{
+			"-cert-file", rsaCertificateFile,
+			"-key-file", rsaKeyFile,
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "SigningHash-ServerKeyExchange-SignatureType",
+		config: Config{
+			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			SignatureAndHashes: []signatureAndHash{
+				{signatureECDSA, hashSHA512},
+				{signatureRSA, hashSHA384},
+				{signatureECDSA, hashSHA1},
+			},
+		},
+	})
+
+	// Test that, if the list is missing, the peer falls back to SHA-1.
+	testCases = append(testCases, testCase{
+		name: "SigningHash-ClientAuth-Fallback",
+		config: Config{
+			ClientAuth: RequireAnyClientCert,
+			SignatureAndHashes: []signatureAndHash{
+				{signatureRSA, hashSHA1},
+			},
+			Bugs: ProtocolBugs{
+				NoSignatureAndHashes: true,
+			},
+		},
+		flags: []string{
+			"-cert-file", rsaCertificateFile,
+			"-key-file", rsaKeyFile,
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "SigningHash-ServerKeyExchange-Fallback",
+		config: Config{
+			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			SignatureAndHashes: []signatureAndHash{
+				{signatureRSA, hashSHA1},
+			},
+			Bugs: ProtocolBugs{
+				NoSignatureAndHashes: true,
+			},
+		},
+	})
+}
+
 func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -2088,6 +2196,7 @@
 	addExtendedMasterSecretTests()
 	addRenegotiationTests()
 	addDTLSReplayTests()
+	addSigningHashTests()
 	for _, async := range []bool{false, true} {
 		for _, splitHandshake := range []bool{false, true} {
 			for _, protocol := range []protocol{tls, dtls} {