Adding more options for signing digest fallback.

Allow configuring digest preferences for the private key. Some
smartcards have limited support for signing digests, notably Windows
CAPI keys and old Estonian smartcards. Chromium used the supports_digest
hook in SSL_PRIVATE_KEY_METHOD to limit such keys to SHA1. However,
detecting those keys was a heuristic, so some SHA256-capable keys
authenticating to SHA256-only servers regressed in the switch to
BoringSSL. Replace this mechanism with an API to configure digest
preference order. This way heuristically-detected SHA1-only keys may be
configured by Chromium as SHA1-preferring rather than SHA1-requiring.

In doing so, clean up the shared_sigalgs machinery somewhat.

BUG=468076

Change-Id: I996a2df213ae4d8b4062f0ab85b15262ca26f3c6
Reviewed-on: https://boringssl-review.googlesource.com/5755
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 039d164..77be9f6 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -191,6 +191,7 @@
 	SRTPProtectionProfile      uint16                // the negotiated DTLS-SRTP protection profile
 	TLSUnique                  []byte                // the tls-unique channel binding
 	SCTList                    []byte                // signed certificate timestamp list
+	ClientCertSignatureHash    uint8                 // TLS id of the hash used by the client to sign the handshake
 }
 
 // ClientAuthType declares the policy the server will follow for
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index ed016e0..39bdfda 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -50,6 +50,11 @@
 	// firstFinished contains the first Finished hash sent during the
 	// handshake. This is the "tls-unique" channel binding value.
 	firstFinished [12]byte
+	// clientCertSignatureHash contains the TLS hash id for the hash that
+	// was used by the client to sign the handshake with a client
+	// certificate. This is only set by a server and is zero if no client
+	// certificates were used.
+	clientCertSignatureHash uint8
 
 	clientRandom, serverRandom [32]byte
 	masterSecret               [48]byte
@@ -1349,6 +1354,7 @@
 		state.SRTPProtectionProfile = c.srtpProtectionProfile
 		state.TLSUnique = c.firstFinished[:]
 		state.SCTList = c.sctList
+		state.ClientCertSignatureHash = c.clientCertSignatureHash
 	}
 
 	return state
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index d23bf71..068dff9 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -685,6 +685,7 @@
 			if !isSupportedSignatureAndHash(signatureAndHash, config.signatureAndHashesForServer()) {
 				return errors.New("tls: unsupported hash function for client certificate")
 			}
+			c.clientCertSignatureHash = signatureAndHash.hash
 		} 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/runner.go b/ssl/test/runner/runner.go
index 819f1ea..269a955 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -159,11 +159,17 @@
 	expectedOCSPResponse []uint8
 	// expectedSCTList, if not nil, is the expected SCT list to be received.
 	expectedSCTList []uint8
+	// expectedClientCertSignatureHash, if not zero, is the TLS id of the
+	// hash function that the client should have used when signing the
+	// handshake with a client certificate.
+	expectedClientCertSignatureHash uint8
 	// messageLen is the length, in bytes, of the test message that will be
 	// sent.
 	messageLen int
 	// messageCount is the number of test messages that will be sent.
 	messageCount int
+	// digestPrefs is the list of digest preferences from the client.
+	digestPrefs string
 	// certFile is the path to the certificate to use for the server.
 	certFile string
 	// keyFile is the path to the private key to use for the server.
@@ -340,6 +346,10 @@
 		return fmt.Errorf("SCT list mismatch")
 	}
 
+	if expected := test.expectedClientCertSignatureHash; expected != 0 && expected != connState.ClientCertSignatureHash {
+		return fmt.Errorf("expected client to sign handshake with hash %d, but got %d", expected, connState.ClientCertSignatureHash)
+	}
+
 	if test.exportKeyingMaterial > 0 {
 		actual := make([]byte, test.exportKeyingMaterial)
 		if _, err := io.ReadFull(tlsConn, actual); err != nil {
@@ -525,6 +535,10 @@
 		panic("expectResumeRejected without resumeSession in " + test.name)
 	}
 
+	if test.testType != clientTest && test.expectedClientCertSignatureHash != 0 {
+		panic("expectedClientCertSignatureHash non-zero with serverTest in " + test.name)
+	}
+
 	listener, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IP{127, 0, 0, 1}})
 	if err != nil {
 		panic(err)
@@ -554,6 +568,11 @@
 		}
 	}
 
+	if test.digestPrefs != "" {
+		flags = append(flags, "-digest-prefs")
+		flags = append(flags, test.digestPrefs)
+	}
+
 	if test.protocol == dtls {
 		flags = append(flags, "-dtls")
 	}
@@ -3903,6 +3922,73 @@
 		shouldFail:    true,
 		expectedError: ":WRONG_SIGNATURE_TYPE:",
 	})
+
+	// Test that the agreed upon digest respects the client preferences and
+	// the server digests.
+	testCases = append(testCases, testCase{
+		name: "Agree-Digest-Fallback",
+		config: Config{
+			ClientAuth: RequireAnyClientCert,
+			SignatureAndHashes: []signatureAndHash{
+				{signatureRSA, hashSHA512},
+				{signatureRSA, hashSHA1},
+			},
+		},
+		flags: []string{
+			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+			"-key-file", path.Join(*resourceDir, rsaKeyFile),
+		},
+		digestPrefs:                     "SHA256",
+		expectedClientCertSignatureHash: hashSHA1,
+	})
+	testCases = append(testCases, testCase{
+		name: "Agree-Digest-SHA256",
+		config: Config{
+			ClientAuth: RequireAnyClientCert,
+			SignatureAndHashes: []signatureAndHash{
+				{signatureRSA, hashSHA1},
+				{signatureRSA, hashSHA256},
+			},
+		},
+		flags: []string{
+			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+			"-key-file", path.Join(*resourceDir, rsaKeyFile),
+		},
+		digestPrefs:                     "SHA256,SHA1",
+		expectedClientCertSignatureHash: hashSHA256,
+	})
+	testCases = append(testCases, testCase{
+		name: "Agree-Digest-SHA1",
+		config: Config{
+			ClientAuth: RequireAnyClientCert,
+			SignatureAndHashes: []signatureAndHash{
+				{signatureRSA, hashSHA1},
+			},
+		},
+		flags: []string{
+			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+			"-key-file", path.Join(*resourceDir, rsaKeyFile),
+		},
+		digestPrefs:                     "SHA512,SHA256,SHA1",
+		expectedClientCertSignatureHash: hashSHA1,
+	})
+	testCases = append(testCases, testCase{
+		name: "Agree-Digest-Default",
+		config: Config{
+			ClientAuth: RequireAnyClientCert,
+			SignatureAndHashes: []signatureAndHash{
+				{signatureRSA, hashSHA256},
+				{signatureECDSA, hashSHA256},
+				{signatureRSA, hashSHA1},
+				{signatureECDSA, hashSHA1},
+			},
+		},
+		flags: []string{
+			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+			"-key-file", path.Join(*resourceDir, rsaKeyFile),
+		},
+		expectedClientCertSignatureHash: hashSHA256,
+	})
 }
 
 // timeouts is the retransmit schedule for BoringSSL. It doubles and