Support handshake hints for TLS 1.2 full handshakes.

Follow-up work will add support for TLS 1.2 ticket decryption.

Bug: 504
Change-Id: Ieaee37d94562040f1d51227216359bd63db15198
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/53525
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 3a2a861..633a15a 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3977,8 +3977,9 @@
 // those cases, BoringSSL will not predict a signature as there is no benefit.
 // Callers must allow for handshakes to complete without a predicted signature.
 //
-// For now, only TLS 1.3 is hinted. TLS 1.2 will work, but the hints will be
-// empty.
+// Handshake hints are supported for TLS 1.3 and partially supported for
+// TLS 1.2. TLS 1.2 resumption handshakes are not yet fully hinted. They will
+// still work, but may not be as efficient.
 
 // SSL_serialize_capabilities writes an opaque byte string to |out| describing
 // some of |ssl|'s capabilities. It returns one on success and zero on error.
diff --git a/ssl/handoff.cc b/ssl/handoff.cc
index 883f832..e414705 100644
--- a/ssl/handoff.cc
+++ b/ssl/handoff.cc
@@ -769,7 +769,7 @@
 // implicit tagging to make it a little more compact.
 //
 // HandshakeHints ::= SEQUENCE {
-//     serverRandom            [0] IMPLICIT OCTET STRING OPTIONAL,
+//     serverRandomTLS13       [0] IMPLICIT OCTET STRING OPTIONAL,
 //     keyShareHint            [1] IMPLICIT KeyShareHint OPTIONAL,
 //     signatureHint           [2] IMPLICIT SignatureHint OPTIONAL,
 //     -- At most one of decryptedPSKHint or ignorePSKHint may be present. It
@@ -779,6 +779,12 @@
 //     decryptedPSKHint        [3] IMPLICIT OCTET STRING OPTIONAL,
 //     ignorePSKHint           [4] IMPLICIT NULL OPTIONAL,
 //     compressCertificateHint [5] IMPLICIT CompressCertificateHint OPTIONAL,
+//     -- TLS 1.2 and 1.3 use different server random hints because one contains
+//     -- a timestamp while the other doesn't. If the hint was generated
+//     -- assuming TLS 1.3 but we actually negotiate TLS 1.2, mixing the two
+//     -- will break this.
+//     serverRandomTLS12       [7] IMPLICIT OCTET STRING OPTIONAL,
+//     ecdheHint               [6] IMPLICIT ECDHEHint OPTIONAL
 // }
 //
 // KeyShareHint ::= SEQUENCE {
@@ -799,9 +805,15 @@
 //     input                   OCTET STRING,
 //     compressed              OCTET STRING,
 // }
+//
+// ECDHEHint ::= SEQUENCE {
+//     groupId                 INTEGER,
+//     publicKey               OCTET STRING,
+//     privateKey              OCTET STRING,
+// }
 
 // HandshakeHints tags.
-static const unsigned kServerRandomTag = CBS_ASN1_CONTEXT_SPECIFIC | 0;
+static const unsigned kServerRandomTLS13Tag = CBS_ASN1_CONTEXT_SPECIFIC | 0;
 static const unsigned kKeyShareHintTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1;
 static const unsigned kSignatureHintTag =
@@ -809,6 +821,8 @@
 static const unsigned kDecryptedPSKTag = CBS_ASN1_CONTEXT_SPECIFIC | 3;
 static const unsigned kIgnorePSKTag = CBS_ASN1_CONTEXT_SPECIFIC | 4;
 static const unsigned kCompressCertificateTag = CBS_ASN1_CONTEXT_SPECIFIC | 5;
+static const unsigned kServerRandomTLS12Tag = CBS_ASN1_CONTEXT_SPECIFIC | 6;
+static const unsigned kECDHEHintTag = CBS_ASN1_CONSTRUCTED | 7;
 
 int SSL_serialize_handshake_hints(const SSL *ssl, CBB *out) {
   const SSL_HANDSHAKE *hs = ssl->s3->hs.get();
@@ -823,10 +837,10 @@
     return 0;
   }
 
-  if (!hints->server_random.empty()) {
-    if (!CBB_add_asn1(&seq, &child, kServerRandomTag) ||
-        !CBB_add_bytes(&child, hints->server_random.data(),
-                       hints->server_random.size())) {
+  if (!hints->server_random_tls13.empty()) {
+    if (!CBB_add_asn1(&seq, &child, kServerRandomTLS13Tag) ||
+        !CBB_add_bytes(&child, hints->server_random_tls13.data(),
+                       hints->server_random_tls13.size())) {
       return 0;
     }
   }
@@ -884,6 +898,26 @@
     }
   }
 
+  if (!hints->server_random_tls12.empty()) {
+    if (!CBB_add_asn1(&seq, &child, kServerRandomTLS12Tag) ||
+        !CBB_add_bytes(&child, hints->server_random_tls12.data(),
+                       hints->server_random_tls12.size())) {
+      return 0;
+    }
+  }
+
+  if (hints->ecdhe_group_id != 0 && !hints->ecdhe_public_key.empty() &&
+      !hints->ecdhe_private_key.empty()) {
+    if (!CBB_add_asn1(&seq, &child, kECDHEHintTag) ||
+        !CBB_add_asn1_uint64(&child, hints->ecdhe_group_id) ||
+        !CBB_add_asn1_octet_string(&child, hints->ecdhe_public_key.data(),
+                                   hints->ecdhe_public_key.size()) ||
+        !CBB_add_asn1_octet_string(&child, hints->ecdhe_private_key.data(),
+                                   hints->ecdhe_private_key.size())) {
+      return 0;
+    }
+  }
+
   return CBB_flush(out);
 }
 
@@ -898,14 +932,14 @@
     return 0;
   }
 
-  CBS cbs, seq, server_random, key_share, signature_hint, ticket, ignore_psk,
-      cert_compression;
-  int has_server_random, has_key_share, has_signature_hint, has_ticket,
-      has_ignore_psk, has_cert_compression;
+  CBS cbs, seq, server_random_tls13, key_share, signature_hint, ticket,
+      ignore_psk, cert_compression, server_random_tls12, ecdhe;
+  int has_server_random_tls13, has_key_share, has_signature_hint, has_ticket,
+      has_ignore_psk, has_cert_compression, has_server_random_tls12, has_ecdhe;
   CBS_init(&cbs, hints, hints_len);
   if (!CBS_get_asn1(&cbs, &seq, CBS_ASN1_SEQUENCE) ||
-      !CBS_get_optional_asn1(&seq, &server_random, &has_server_random,
-                             kServerRandomTag) ||
+      !CBS_get_optional_asn1(&seq, &server_random_tls13,
+                             &has_server_random_tls13, kServerRandomTLS13Tag) ||
       !CBS_get_optional_asn1(&seq, &key_share, &has_key_share,
                              kKeyShareHintTag) ||
       !CBS_get_optional_asn1(&seq, &signature_hint, &has_signature_hint,
@@ -914,12 +948,16 @@
       !CBS_get_optional_asn1(&seq, &ignore_psk, &has_ignore_psk,
                              kIgnorePSKTag) ||
       !CBS_get_optional_asn1(&seq, &cert_compression, &has_cert_compression,
-                             kCompressCertificateTag)) {
+                             kCompressCertificateTag) ||
+      !CBS_get_optional_asn1(&seq, &server_random_tls12,
+                             &has_server_random_tls12, kServerRandomTLS12Tag) ||
+      !CBS_get_optional_asn1(&seq, &ecdhe, &has_ecdhe, kECDHEHintTag)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_COULD_NOT_PARSE_HINTS);
     return 0;
   }
 
-  if (has_server_random && !hints_obj->server_random.CopyFrom(server_random)) {
+  if (has_server_random_tls13 &&
+      !hints_obj->server_random_tls13.CopyFrom(server_random_tls13)) {
     return 0;
   }
 
@@ -981,6 +1019,26 @@
     hints_obj->cert_compression_alg_id = static_cast<uint16_t>(alg);
   }
 
+  if (has_server_random_tls12 &&
+      !hints_obj->server_random_tls12.CopyFrom(server_random_tls12)) {
+    return 0;
+  }
+
+  if (has_ecdhe) {
+    uint64_t group_id;
+    CBS public_key, private_key;
+    if (!CBS_get_asn1_uint64(&ecdhe, &group_id) ||  //
+        group_id == 0 || group_id > 0xffff ||
+        !CBS_get_asn1(&ecdhe, &public_key, CBS_ASN1_OCTETSTRING) ||
+        !hints_obj->ecdhe_public_key.CopyFrom(public_key) ||
+        !CBS_get_asn1(&ecdhe, &private_key, CBS_ASN1_OCTETSTRING) ||
+        !hints_obj->ecdhe_private_key.CopyFrom(private_key)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_COULD_NOT_PARSE_HINTS);
+      return 0;
+    }
+    hints_obj->ecdhe_group_id = static_cast<uint16_t>(group_id);
+  }
+
   ssl->s3->hs->hints = std::move(hints_obj);
   return 1;
 }
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index 4a2ada0..fa6e9d6 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -801,12 +801,6 @@
   // or below.
   assert(ssl->s3->ech_status != ssl_ech_accepted);
 
-  // TODO(davidben): Also compute hints for TLS 1.2. When doing so, update the
-  // check in bssl_shim.cc to test this.
-  if (hs->hints_requested) {
-    return ssl_hs_hints_ready;
-  }
-
   ssl->s3->early_data_reason = ssl_early_data_protocol_version;
 
   SSLMessage msg_unused;
@@ -988,14 +982,23 @@
     hs->channel_id_negotiated = false;
   }
 
-  struct OPENSSL_timeval now;
-  ssl_get_current_time(ssl, &now);
-  ssl->s3->server_random[0] = now.tv_sec >> 24;
-  ssl->s3->server_random[1] = now.tv_sec >> 16;
-  ssl->s3->server_random[2] = now.tv_sec >> 8;
-  ssl->s3->server_random[3] = now.tv_sec;
-  if (!RAND_bytes(ssl->s3->server_random + 4, SSL3_RANDOM_SIZE - 4)) {
-    return ssl_hs_error;
+  SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
+  if (hints && !hs->hints_requested &&
+      hints->server_random_tls12.size() == SSL3_RANDOM_SIZE) {
+    OPENSSL_memcpy(ssl->s3->server_random, hints->server_random_tls12.data(),
+                   SSL3_RANDOM_SIZE);
+  } else {
+    struct OPENSSL_timeval now;
+    ssl_get_current_time(ssl, &now);
+    CRYPTO_store_u32_be(ssl->s3->server_random,
+                        static_cast<uint32_t>(now.tv_sec));
+    if (!RAND_bytes(ssl->s3->server_random + 4, SSL3_RANDOM_SIZE - 4)) {
+      return ssl_hs_error;
+    }
+    if (hints && hs->hints_requested &&
+        !hints->server_random_tls12.CopyFrom(ssl->s3->server_random)) {
+      return ssl_hs_error;
+    }
   }
 
   // Implement the TLS 1.3 anti-downgrade feature.
@@ -1040,7 +1043,11 @@
     return ssl_hs_error;
   }
 
-  if (ssl->session != NULL) {
+  if (ssl->session != nullptr) {
+    // No additional hints to generate in resumption.
+    if (hs->hints_requested) {
+      return ssl_hs_hints_ready;
+    }
     hs->state = state12_send_server_finished;
   } else {
     hs->state = state12_send_server_certificate;
@@ -1113,18 +1120,51 @@
         OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
         ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
         return ssl_hs_error;
-       }
+      }
       hs->new_session->group_id = group_id;
 
-      // Set up ECDH, generate a key, and emit the public half.
       hs->key_shares[0] = SSLKeyShare::Create(group_id);
       if (!hs->key_shares[0] ||
           !CBB_add_u8(cbb.get(), NAMED_CURVE_TYPE) ||
           !CBB_add_u16(cbb.get(), group_id) ||
-          !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
-          !hs->key_shares[0]->Offer(&child)) {
+          !CBB_add_u8_length_prefixed(cbb.get(), &child)) {
         return ssl_hs_error;
       }
+
+      SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
+      bool hint_ok = false;
+      if (hints && !hs->hints_requested &&
+          hints->ecdhe_group_id == group_id &&
+          !hints->ecdhe_public_key.empty() &&
+          !hints->ecdhe_private_key.empty()) {
+        CBS cbs = MakeConstSpan(hints->ecdhe_private_key);
+        hint_ok = hs->key_shares[0]->DeserializePrivateKey(&cbs);
+      }
+      if (hint_ok) {
+        // Reuse the ECDH key from handshake hints.
+        if (!CBB_add_bytes(&child, hints->ecdhe_public_key.data(),
+                           hints->ecdhe_public_key.size())) {
+          return ssl_hs_error;
+        }
+      } else {
+        // Generate a key, and emit the public half.
+        if (!hs->key_shares[0]->Offer(&child)) {
+          return ssl_hs_error;
+        }
+        // If generating hints, save the ECDHE key.
+        if (hints && hs->hints_requested) {
+          bssl::ScopedCBB private_key_cbb;
+          if (!hints->ecdhe_public_key.CopyFrom(
+                  MakeConstSpan(CBB_data(&child), CBB_len(&child))) ||
+              !CBB_init(private_key_cbb.get(), 32) ||
+              !hs->key_shares[0]->SerializePrivateKey(private_key_cbb.get()) ||
+              !CBBFinishArray(private_key_cbb.get(),
+                              &hints->ecdhe_private_key)) {
+            return ssl_hs_error;
+          }
+          hints->ecdhe_group_id = group_id;
+        }
+      }
     } else {
       assert(alg_k & SSL_kPSK);
     }
@@ -1214,6 +1254,9 @@
 
 static enum ssl_hs_wait_t do_send_server_hello_done(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+  if (hs->hints_requested) {
+    return ssl_hs_hints_ready;
+  }
 
   ScopedCBB cbb;
   CBB body;
diff --git a/ssl/internal.h b/ssl/internal.h
index 41630f5..1a78f63 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1697,7 +1697,8 @@
 struct SSL_HANDSHAKE_HINTS {
   static constexpr bool kAllowUniquePtr = true;
 
-  Array<uint8_t> server_random;
+  Array<uint8_t> server_random_tls12;
+  Array<uint8_t> server_random_tls13;
 
   uint16_t key_share_group_id = 0;
   Array<uint8_t> key_share_public_key;
@@ -1714,6 +1715,10 @@
   uint16_t cert_compression_alg_id = 0;
   Array<uint8_t> cert_compression_input;
   Array<uint8_t> cert_compression_output;
+
+  uint16_t ecdhe_group_id = 0;
+  Array<uint8_t> ecdhe_public_key;
+  Array<uint8_t> ecdhe_private_key;
 };
 
 struct SSL_HANDSHAKE {
diff --git a/ssl/ssl_privkey.cc b/ssl/ssl_privkey.cc
index 41a13a2..0843e0b 100644
--- a/ssl/ssl_privkey.cc
+++ b/ssl/ssl_privkey.cc
@@ -201,6 +201,31 @@
     SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, size_t max_out,
     uint16_t sigalg, Span<const uint8_t> in) {
   SSL *const ssl = hs->ssl;
+  SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
+  Array<uint8_t> spki;
+  if (hints) {
+    ScopedCBB spki_cbb;
+    if (!CBB_init(spki_cbb.get(), 64) ||
+        !EVP_marshal_public_key(spki_cbb.get(), hs->local_pubkey.get()) ||
+        !CBBFinishArray(spki_cbb.get(), &spki)) {
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+      return ssl_private_key_failure;
+    }
+  }
+
+  // Replay the signature from handshake hints if available.
+  if (hints && !hs->hints_requested &&         //
+      sigalg == hints->signature_algorithm &&  //
+      in == hints->signature_input &&
+      MakeConstSpan(spki) == hints->signature_spki &&
+      !hints->signature.empty() &&  //
+      hints->signature.size() <= max_out) {
+    // Signature algorithm and input both match. Reuse the signature from hints.
+    *out_len = hints->signature.size();
+    OPENSSL_memcpy(out, hints->signature.data(), hints->signature.size());
+    return ssl_private_key_success;
+  }
+
   const SSL_PRIVATE_KEY_METHOD *key_method = hs->config->cert->key_method;
   EVP_PKEY *privatekey = hs->config->cert->privatekey.get();
   assert(!hs->can_release_private_key);
@@ -214,21 +239,33 @@
     if (hs->pending_private_key_op) {
       ret = key_method->complete(ssl, out, out_len, max_out);
     } else {
-      ret = key_method->sign(ssl, out, out_len, max_out,
-                             sigalg, in.data(), in.size());
+      ret = key_method->sign(ssl, out, out_len, max_out, sigalg, in.data(),
+                             in.size());
     }
     if (ret == ssl_private_key_failure) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PRIVATE_KEY_OPERATION_FAILED);
     }
     hs->pending_private_key_op = ret == ssl_private_key_retry;
-    return ret;
+    if (ret != ssl_private_key_success) {
+      return ret;
+    }
+  } else {
+    *out_len = max_out;
+    ScopedEVP_MD_CTX ctx;
+    if (!setup_ctx(ssl, ctx.get(), privatekey, sigalg, false /* sign */) ||
+        !EVP_DigestSign(ctx.get(), out, out_len, in.data(), in.size())) {
+      return ssl_private_key_failure;
+    }
   }
 
-  *out_len = max_out;
-  ScopedEVP_MD_CTX ctx;
-  if (!setup_ctx(ssl, ctx.get(), privatekey, sigalg, false /* sign */) ||
-      !EVP_DigestSign(ctx.get(), out, out_len, in.data(), in.size())) {
-    return ssl_private_key_failure;
+  // Save the hint if applicable.
+  if (hints && hs->hints_requested) {
+    hints->signature_algorithm = sigalg;
+    hints->signature_spki = std::move(spki);
+    if (!hints->signature_input.CopyFrom(in) ||
+        !hints->signature.CopyFrom(MakeConstSpan(out, *out_len))) {
+      return ssl_private_key_failure;
+    }
   }
   return ssl_private_key_success;
 }
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 510328b..35086f8 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -667,24 +667,34 @@
   }
 
   // Test that handshake hints correctly skipped the expected operations.
-  //
-  // TODO(davidben): Add support for TLS 1.2 hints and remove the version check.
-  // Also add a check for the session cache lookup.
-  if (config->handshake_hints && !config->allow_hint_mismatch &&
-      SSL_version(ssl) == TLS1_3_VERSION) {
+  if (config->handshake_hints && !config->allow_hint_mismatch) {
     const TestState *state = GetTestState(ssl);
-    if (!SSL_used_hello_retry_request(ssl) && state->used_private_key) {
+    // If the private key operation is performed in the first roundtrip, a hint
+    // match should have skipped it. This is ECDHE-based cipher suites in TLS
+    // 1.2 and non-HRR handshakes in TLS 1.3.
+    bool private_key_allowed;
+    if (SSL_version(ssl) == TLS1_3_VERSION) {
+      private_key_allowed = SSL_used_hello_retry_request(ssl);
+    } else {
+      private_key_allowed =
+          SSL_CIPHER_get_kx_nid(SSL_get_current_cipher(ssl)) == NID_kx_rsa;
+    }
+    if (!private_key_allowed && state->used_private_key) {
       fprintf(
           stderr,
           "Performed private key operation, but hint should have skipped it\n");
       return false;
     }
 
-    if (state->ticket_decrypt_done) {
+    // TODO(davidben): Make handshake hints skip TLS 1.2 ticket decryption.
+    if (SSL_version(ssl) == TLS1_3_VERSION && state->ticket_decrypt_done) {
       fprintf(stderr,
               "Performed ticket decryption, but hint should have skipped it\n");
       return false;
     }
+
+    // TODO(davidben): Decide what we want to do with TLS 1.2 stateful
+    // resumption.
   }
   return true;
 }
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 65ed278..9291605 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -18770,6 +18770,27 @@
 				curveID: CurveX25519,
 			},
 		})
+		if protocol != quic {
+			testCases = append(testCases, testCase{
+				name:               protocol.String() + "-HintMismatch-ECDHE-Group",
+				testType:           serverTest,
+				protocol:           protocol,
+				skipSplitHandshake: true,
+				config: Config{
+					MinVersion:    VersionTLS12,
+					MaxVersion:    VersionTLS12,
+					DefaultCurves: []CurveID{CurveX25519, CurveP256},
+				},
+				flags: []string{
+					"-allow-hint-mismatch",
+					"-on-shim-curves", strconv.Itoa(int(CurveX25519)),
+					"-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
+				},
+				expectations: connectionExpectations{
+					curveID: CurveX25519,
+				},
+			})
+		}
 
 		// If the handshaker does HelloRetryRequest, it will omit most hints.
 		// The shim should still work.
@@ -18818,7 +18839,7 @@
 		// The shim and handshaker may have different signature algorithm
 		// preferences.
 		testCases = append(testCases, testCase{
-			name:               protocol.String() + "-HintMismatch-SignatureAlgorithm",
+			name:               protocol.String() + "-HintMismatch-SignatureAlgorithm-TLS13",
 			testType:           serverTest,
 			protocol:           protocol,
 			skipSplitHandshake: true,
@@ -18841,6 +18862,32 @@
 				peerSignatureAlgorithm: signatureRSAPSSWithSHA256,
 			},
 		})
+		if protocol != quic {
+			testCases = append(testCases, testCase{
+				name:               protocol.String() + "-HintMismatch-SignatureAlgorithm-TLS12",
+				testType:           serverTest,
+				protocol:           protocol,
+				skipSplitHandshake: true,
+				config: Config{
+					MinVersion: VersionTLS12,
+					MaxVersion: VersionTLS12,
+					VerifySignatureAlgorithms: []signatureAlgorithm{
+						signatureRSAPSSWithSHA256,
+						signatureRSAPSSWithSHA384,
+					},
+				},
+				flags: []string{
+					"-allow-hint-mismatch",
+					"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+					"-key-file", path.Join(*resourceDir, rsaKeyFile),
+					"-on-shim-signing-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA256)),
+					"-on-handshaker-signing-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
+				},
+				expectations: connectionExpectations{
+					peerSignatureAlgorithm: signatureRSAPSSWithSHA256,
+				},
+			})
+		}
 
 		// The shim and handshaker may disagree on whether resumption is allowed.
 		// We run the first connection with tickets enabled, so the client is
@@ -19029,6 +19076,51 @@
 				ocspResponse: testOCSPResponse,
 			},
 		})
+
+		// The shim and handshaker may disagree on cipher suite, to the point
+		// that one selects RSA key exchange (no applicable hint) and the other
+		// selects ECDHE_RSA (hints are useful).
+		if protocol != quic {
+			testCases = append(testCases, testCase{
+				testType:           serverTest,
+				name:               protocol.String() + "-HintMismatch-CipherMismatch1",
+				protocol:           protocol,
+				skipSplitHandshake: true,
+				config: Config{
+					MinVersion: VersionTLS12,
+					MaxVersion: VersionTLS12,
+				},
+				flags: []string{
+					"-allow-hint-mismatch",
+					"-on-shim-cipher", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+					"-on-handshaker-cipher", "TLS_RSA_WITH_AES_128_GCM_SHA256",
+				},
+				expectations: connectionExpectations{
+					cipher: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+				},
+			})
+			testCases = append(testCases, testCase{
+				testType:           serverTest,
+				name:               protocol.String() + "-HintMismatch-CipherMismatch2",
+				protocol:           protocol,
+				skipSplitHandshake: true,
+				config: Config{
+					MinVersion: VersionTLS12,
+					MaxVersion: VersionTLS12,
+				},
+				flags: []string{
+					// There is no need to pass -allow-hint-mismatch. The
+					// handshaker will unnecessarily generate a signature hints.
+					// This is not reported as a mismatch because hints would
+					// not have helped the shim anyway.
+					"-on-shim-cipher", "TLS_RSA_WITH_AES_128_GCM_SHA256",
+					"-on-handshaker-cipher", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+				},
+				expectations: connectionExpectations{
+					cipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
+				},
+			})
+		}
 	}
 }
 
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index 208c6ff..d7997c7 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -576,7 +576,6 @@
     return ssl_private_key_failure;
   }
 
-  // Sign the digest.
   CBB child;
   const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey.get());
   uint8_t *sig;
@@ -595,40 +594,10 @@
     return ssl_private_key_failure;
   }
 
-  SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
-  Array<uint8_t> spki;
-  if (hints) {
-    ScopedCBB spki_cbb;
-    if (!CBB_init(spki_cbb.get(), 64) ||
-        !EVP_marshal_public_key(spki_cbb.get(), hs->local_pubkey.get()) ||
-        !CBBFinishArray(spki_cbb.get(), &spki)) {
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return ssl_private_key_failure;
-    }
-  }
-
-  if (hints && !hs->hints_requested &&
-      signature_algorithm == hints->signature_algorithm &&
-      MakeConstSpan(msg) == hints->signature_input &&
-      MakeConstSpan(spki) == hints->signature_spki &&
-      !hints->signature.empty() && hints->signature.size() <= max_sig_len) {
-    // Signature algorithm and input both match. Reuse the signature from hints.
-    sig_len = hints->signature.size();
-    OPENSSL_memcpy(sig, hints->signature.data(), sig_len);
-  } else {
-    enum ssl_private_key_result_t sign_result = ssl_private_key_sign(
-        hs, sig, &sig_len, max_sig_len, signature_algorithm, msg);
-    if (sign_result != ssl_private_key_success) {
-      return sign_result;
-    }
-    if (hints && hs->hints_requested) {
-      hints->signature_algorithm = signature_algorithm;
-      hints->signature_input = std::move(msg);
-      hints->signature_spki = std::move(spki);
-      if (!hints->signature.CopyFrom(MakeSpan(sig, sig_len))) {
-        return ssl_private_key_failure;
-      }
-    }
+  enum ssl_private_key_result_t sign_result = ssl_private_key_sign(
+      hs, sig, &sig_len, max_sig_len, signature_algorithm, msg);
+  if (sign_result != ssl_private_key_success) {
+    return sign_result;
   }
 
   if (!CBB_did_write(&child, sig_len) ||
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index eca63f7..ca43624 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -738,12 +738,13 @@
 
   SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
   if (hints && !hs->hints_requested &&
-      hints->server_random.size() == random.size()) {
-    OPENSSL_memcpy(random.data(), hints->server_random.data(), random.size());
+      hints->server_random_tls13.size() == random.size()) {
+    OPENSSL_memcpy(random.data(), hints->server_random_tls13.data(),
+                   random.size());
   } else {
     RAND_bytes(random.data(), random.size());
     if (hints && hs->hints_requested &&
-        !hints->server_random.CopyFrom(random)) {
+        !hints->server_random_tls13.CopyFrom(random)) {
       return ssl_hs_error;
     }
   }