Work around a Java client bug when rotating certificates.

The Java client implementation of the 3SHAKE mitigation incorrectly
rejects initial handshakes when all of the following are true:

1. The ClientHello offered a session.
2. The session was successfully resumed previously.
3. The server declines the session.
4. The server sends a certificate with a different SAN list than in the
   previous session.

(Note the 3SHAKE mitigation is to reject certificates changes on
renegotiation, while Java's logic applies to initial handshakes as
well.)

The end result is long-lived Java clients break on some certificate
rotations. Fingerprint Java clients and decline all offered sessions.
This avoids (2) while still introducing new sessions to clear any
existing problematic sessions.

See also b/65323005.

Change-Id: Ib2b84c69b5ecba285ffb8c4d03de5626838d794e
Reviewed-on: https://boringssl-review.googlesource.com/20184
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/ssl/internal.h b/ssl/internal.h
index 2ebb7e9..ddc91c7 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1440,8 +1440,11 @@
 
 // ClientHello functions.
 
-int ssl_client_hello_init(SSL *ssl, SSL_CLIENT_HELLO *out,
-                          const SSLMessage &msg);
+// ssl_client_hello_init parses |msg| as a ClientHello and writes the result to
+// |*out|. It returns one on success and zero on error. This function is
+// exported for unit tests.
+OPENSSL_EXPORT int ssl_client_hello_init(SSL *ssl, SSL_CLIENT_HELLO *out,
+                                         const SSLMessage &msg);
 
 int ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
                                    CBS *out, uint16_t extension_type);
@@ -1449,6 +1452,10 @@
 int ssl_client_cipher_list_contains_cipher(const SSL_CLIENT_HELLO *client_hello,
                                            uint16_t id);
 
+// ssl_is_probably_java returns true if |client_hello| looks like a Java
+// ClientHello and false otherwise. This function is exported for tests.
+OPENSSL_EXPORT bool ssl_is_probably_java(const SSL_CLIENT_HELLO *client_hello);
+
 
 // GREASE.
 
diff --git a/ssl/ssl_session.cc b/ssl/ssl_session.cc
index c21c282..203555c 100644
--- a/ssl/ssl_session.cc
+++ b/ssl/ssl_session.cc
@@ -721,6 +721,73 @@
   return ssl_hs_ok;
 }
 
+bool ssl_is_probably_java(const SSL_CLIENT_HELLO *client_hello) {
+  CBS extension, groups;
+  if (SSL_is_dtls(client_hello->ssl) ||
+      !ssl_client_hello_get_extension(client_hello, &extension,
+                                      TLSEXT_TYPE_supported_groups) ||
+      !CBS_get_u16_length_prefixed(&extension, &groups) ||
+      CBS_len(&extension) != 0) {
+    return false;
+  }
+
+  // Original Java curve list.
+  static const uint8_t kCurveList1[] = {
+      0x00, 0x17, 0x00, 0x01, 0x00, 0x03, 0x00, 0x13, 0x00, 0x15,
+      0x00, 0x06, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x18,
+      0x00, 0x0b, 0x00, 0x0c, 0x00, 0x19, 0x00, 0x0d, 0x00, 0x0e,
+      0x00, 0x0f, 0x00, 0x10, 0x00, 0x11, 0x00, 0x02, 0x00, 0x12,
+      0x00, 0x04, 0x00, 0x05, 0x00, 0x14, 0x00, 0x08, 0x00, 0x16};
+
+  // Newer Java curve list.
+  static const uint8_t kCurveList2[] = {
+      0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0a,
+      0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x16};
+
+  // IcedTea curve list.
+  static const uint8_t kCurveList3[] = {0x00, 0x17, 0x00, 0x18, 0x00, 0x19};
+
+  auto groups_span = MakeConstSpan(CBS_data(&groups), CBS_len(&groups));
+  if (groups_span != kCurveList1 && groups_span != kCurveList2 &&
+      groups_span != kCurveList3) {
+    return false;
+  }
+
+  // Java has a very distinctive curve list, but IcedTea patches it to a more
+  // standard [P-256, P-384, P-521]. Additionally check the extension
+  // order. This is more likely to lead to false positives but false positives
+  // only mean a loss of resumption. Any client new enough to support one of
+  // X25519, extended master secret, session tickets, or TLS 1.3 will be
+  // unaffected.
+  //
+  // Java sends different extensions depending on configuration and version, but
+  // those which are present are always in the same order. Check if the
+  // extensions are an ordered subset of |kJavaExtensions|.
+  static const uint16_t kJavaExtensions[] = {
+      TLSEXT_TYPE_supported_groups,
+      TLSEXT_TYPE_ec_point_formats,
+      TLSEXT_TYPE_signature_algorithms,
+      TLSEXT_TYPE_server_name,
+      17 /* status_request_v2 */,
+      TLSEXT_TYPE_status_request,
+      TLSEXT_TYPE_application_layer_protocol_negotiation,
+      TLSEXT_TYPE_renegotiate,
+  };
+  CBS extensions;
+  CBS_init(&extensions, client_hello->extensions, client_hello->extensions_len);
+  for (uint16_t expected : kJavaExtensions) {
+    CBS extensions_copy = extensions, body;
+    uint16_t type;
+    // Peek at the next extension.
+    if (CBS_get_u16(&extensions_copy, &type) &&
+        CBS_get_u16_length_prefixed(&extensions_copy, &body) &&
+        type == expected) {
+      extensions = extensions_copy;
+    }
+  }
+  return CBS_len(&extensions) == 0;
+}
+
 enum ssl_hs_wait_t ssl_get_prev_session(SSL *ssl,
                                         UniquePtr<SSL_SESSION> *out_session,
                                         bool *out_tickets_supported,
@@ -728,8 +795,6 @@
                                         const SSL_CLIENT_HELLO *client_hello) {
   // This is used only by servers.
   assert(ssl->server);
-  UniquePtr<SSL_SESSION> session;
-  bool renew_ticket = false;
 
   // If tickets are disabled, always behave as if no tickets are present.
   const uint8_t *ticket = NULL;
@@ -739,6 +804,32 @@
       ssl->version > SSL3_VERSION &&
       SSL_early_callback_ctx_extension_get(
           client_hello, TLSEXT_TYPE_session_ticket, &ticket, &ticket_len);
+
+  if (ssl_is_probably_java(client_hello)) {
+    // The Java client implementation of the 3SHAKE mitigation incorrectly
+    // rejects initial handshakes when all of the following are true:
+    //
+    // 1. The ClientHello offered a session.
+    // 2. The session was successfully resumed previously.
+    // 3. The server declines the session.
+    // 4. The server sends a certificate with a different SAN list than in the
+    //    previous session.
+    //
+    // (Note the 3SHAKE mitigation is to reject certificates changes on
+    // renegotiation, while Java's logic applies to initial handshakes as well.)
+    //
+    // The end result is long-lived Java clients break on some certificate
+    // rotations. Fingerprint Java clients and decline all offered
+    // sessions. This avoids (2) while still introducing new sessions to clear
+    // any existing problematic sessions.
+    *out_session = nullptr;
+    *out_tickets_supported = tickets_supported;
+    *out_renew_ticket = false;
+    return ssl_hs_ok;
+  }
+
+  UniquePtr<SSL_SESSION> session;
+  bool renew_ticket = false;
   if (tickets_supported && ticket_len > 0) {
     switch (ssl_process_ticket(ssl, &session, &renew_ticket, ticket, ticket_len,
                                client_hello->session_id,
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 66f0304..f032b25 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -3816,6 +3816,179 @@
   EXPECT_EQ(SSL_R_NO_CIPHERS_AVAILABLE, ERR_GET_REASON(err));
 }
 
+TEST(SSLTest, IsProbablyJava) {
+  bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
+  ASSERT_TRUE(ctx);
+  bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
+  ASSERT_TRUE(ssl);
+
+  const struct {
+    const char *name;
+    std::vector<uint8_t> in;
+    bool is_probably_java;
+  } kTests[] = {
+      {"JDK 6 with IcedTea curve list",
+       {0x03, 0x01, 0x59, 0xb3, 0x10, 0xea, 0x17, 0xfe, 0x9e, 0x69, 0x7e, 0x79,
+        0xc7, 0x33, 0x10, 0x81, 0x73, 0x9e, 0xe7, 0xbf, 0x78, 0x4a, 0x33, 0x76,
+        0x12, 0x1f, 0xc5, 0x6d, 0x28, 0x8d, 0xd7, 0x60, 0xf0, 0x5e, 0x00, 0x00,
+        0x2c, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x35, 0xc0, 0x05, 0xc0, 0x0f, 0x00,
+        0x39, 0x00, 0x38, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x2f, 0xc0, 0x04, 0xc0,
+        0x0e, 0x00, 0x33, 0x00, 0x32, 0xc0, 0x08, 0xc0, 0x12, 0x00, 0x0a, 0xc0,
+        0x03, 0xc0, 0x0d, 0x00, 0x16, 0x00, 0x13, 0x00, 0xff, 0x01, 0x00, 0x00,
+        0x12, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x17, 0x00, 0x18, 0x00,
+        0x19, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00},
+       true},
+      {"JDK 7 with IcedTea curve list",
+       {0x03, 0x03, 0x59, 0xb3, 0x10, 0xc8, 0x03, 0x7d, 0x10, 0x5a, 0x6b, 0x6e,
+        0x84, 0xa5, 0xbe, 0x6e, 0xe2, 0xd0, 0xb4, 0xb5, 0xcf, 0x6d, 0xa1, 0x58,
+        0xb5, 0xc0, 0x05, 0x63, 0xf6, 0x81, 0xda, 0xc2, 0xa0, 0xb0, 0x00, 0x00,
+        0x48, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x3d, 0xc0, 0x26, 0xc0, 0x2a, 0x00,
+        0x6b, 0x00, 0x6a, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x35, 0xc0, 0x05, 0xc0,
+        0x0f, 0x00, 0x39, 0x00, 0x38, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x3c, 0xc0,
+        0x25, 0xc0, 0x29, 0x00, 0x67, 0x00, 0x40, 0xc0, 0x09, 0xc0, 0x13, 0x00,
+        0x2f, 0xc0, 0x04, 0xc0, 0x0e, 0x00, 0x33, 0x00, 0x32, 0xc0, 0x08, 0xc0,
+        0x12, 0x00, 0x0a, 0xc0, 0x03, 0xc0, 0x0d, 0x00, 0x16, 0x00, 0x13, 0x00,
+        0xff, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00,
+        0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00,
+        0x0d, 0x00, 0x18, 0x00, 0x16, 0x06, 0x03, 0x06, 0x01, 0x05, 0x03, 0x05,
+        0x01, 0x04, 0x03, 0x04, 0x01, 0x03, 0x03, 0x03, 0x01, 0x02, 0x03, 0x02,
+        0x01, 0x02, 0x02},
+       true},
+      {"JDK 8",
+       {0x03, 0x03, 0x59, 0xb3, 0x10, 0xfc, 0xc7, 0xcb, 0x36, 0x85, 0x8c, 0x00,
+        0xd6, 0xa7, 0x5b, 0xfb, 0x98, 0xbe, 0xf9, 0xa3, 0xa0, 0x01, 0xff, 0x35,
+        0xb9, 0x1a, 0xc1, 0x72, 0xf1, 0x51, 0x4b, 0x49, 0x96, 0x1a, 0x00, 0x00,
+        0x70, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x3d, 0xc0, 0x26, 0xc0, 0x2a, 0x00,
+        0x6b, 0x00, 0x6a, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x35, 0xc0, 0x05, 0xc0,
+        0x0f, 0x00, 0x39, 0x00, 0x38, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x3c, 0xc0,
+        0x25, 0xc0, 0x29, 0x00, 0x67, 0x00, 0x40, 0xc0, 0x09, 0xc0, 0x13, 0x00,
+        0x2f, 0xc0, 0x04, 0xc0, 0x0e, 0x00, 0x33, 0x00, 0x32, 0xc0, 0x2c, 0xc0,
+        0x2b, 0xc0, 0x30, 0x00, 0x9d, 0xc0, 0x2e, 0xc0, 0x32, 0x00, 0x9f, 0x00,
+        0xa3, 0xc0, 0x2f, 0x00, 0x9c, 0xc0, 0x2d, 0xc0, 0x31, 0x00, 0x9e, 0x00,
+        0xa2, 0xc0, 0x08, 0xc0, 0x12, 0x00, 0x0a, 0xc0, 0x03, 0xc0, 0x0d, 0x00,
+        0x16, 0x00, 0x13, 0xc0, 0x07, 0xc0, 0x11, 0x00, 0x05, 0xc0, 0x02, 0xc0,
+        0x0c, 0x00, 0x04, 0x00, 0xff, 0x01, 0x00, 0x00, 0x5c, 0x00, 0x0a, 0x00,
+        0x34, 0x00, 0x32, 0x00, 0x17, 0x00, 0x01, 0x00, 0x03, 0x00, 0x13, 0x00,
+        0x15, 0x00, 0x06, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x18, 0x00,
+        0x0b, 0x00, 0x0c, 0x00, 0x19, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x0f, 0x00,
+        0x10, 0x00, 0x11, 0x00, 0x02, 0x00, 0x12, 0x00, 0x04, 0x00, 0x05, 0x00,
+        0x14, 0x00, 0x08, 0x00, 0x16, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00,
+        0x0d, 0x00, 0x1a, 0x00, 0x18, 0x06, 0x03, 0x06, 0x01, 0x05, 0x03, 0x05,
+        0x01, 0x04, 0x03, 0x04, 0x01, 0x03, 0x03, 0x03, 0x01, 0x02, 0x03, 0x02,
+        0x01, 0x02, 0x02, 0x01, 0x01},
+       true},
+      {"JDK 9",
+       {0x03, 0x03, 0x0c, 0xe6, 0x06, 0xc6, 0x5d, 0x38, 0xb4, 0x5e, 0x3a, 0xd5,
+        0xb0, 0x5f, 0x5b, 0x84, 0x3b, 0xff, 0x86, 0x4f, 0xb0, 0x3f, 0xc1, 0xfd,
+        0x08, 0xf0, 0x97, 0xf3, 0x56, 0x44, 0x08, 0xe2, 0xdd, 0x2a, 0x00, 0x00,
+        0x64, 0xc0, 0x2c, 0xc0, 0x2b, 0xc0, 0x30, 0x00, 0x9d, 0xc0, 0x2e, 0xc0,
+        0x32, 0x00, 0x9f, 0x00, 0xa3, 0xc0, 0x2f, 0x00, 0x9c, 0xc0, 0x2d, 0xc0,
+        0x31, 0x00, 0x9e, 0x00, 0xa2, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x3d, 0xc0,
+        0x26, 0xc0, 0x2a, 0x00, 0x6b, 0x00, 0x6a, 0xc0, 0x0a, 0xc0, 0x14, 0x00,
+        0x35, 0xc0, 0x05, 0xc0, 0x0f, 0x00, 0x39, 0x00, 0x38, 0xc0, 0x23, 0xc0,
+        0x27, 0x00, 0x3c, 0xc0, 0x25, 0xc0, 0x29, 0x00, 0x67, 0x00, 0x40, 0xc0,
+        0x09, 0xc0, 0x13, 0x00, 0x2f, 0xc0, 0x04, 0xc0, 0x0e, 0x00, 0x33, 0x00,
+        0x32, 0xc0, 0x08, 0xc0, 0x12, 0x00, 0x0a, 0xc0, 0x03, 0xc0, 0x0d, 0x00,
+        0x16, 0x00, 0x13, 0x00, 0xff, 0x01, 0x00, 0x00, 0x5d, 0x00, 0x0a, 0x00,
+        0x16, 0x00, 0x14, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x09, 0x00,
+        0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x16, 0x00,
+        0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0d, 0x00, 0x1c, 0x00, 0x1a, 0x06,
+        0x03, 0x06, 0x01, 0x05, 0x03, 0x05, 0x01, 0x04, 0x03, 0x04, 0x01, 0x04,
+        0x02, 0x03, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x03, 0x02, 0x01, 0x02,
+        0x02, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0e, 0x02, 0x00, 0x04, 0x00, 0x00,
+        0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
+        0x05, 0x01, 0x00, 0x00, 0x00, 0x00},
+       true},
+      {"JDK 9 with SNI and ALPN enabled and no SCSV cipher",
+       {0x03, 0x03, 0x37, 0xa6, 0x4b, 0x58, 0x02, 0xd0, 0x77, 0xe4, 0x48, 0xaf,
+        0x90, 0x50, 0x45, 0xd5, 0x2f, 0xe7, 0x98, 0x5d, 0x54, 0x93, 0x85, 0x3a,
+        0xde, 0xb6, 0xaa, 0x47, 0xef, 0x7f, 0xb5, 0x52, 0xe6, 0xf8, 0x00, 0x00,
+        0x02, 0xc0, 0x24, 0x01, 0x00, 0x00, 0x87, 0x00, 0x0a, 0x00, 0x16, 0x00,
+        0x14, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0a, 0x00,
+        0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x0b, 0x00,
+        0x02, 0x01, 0x00, 0x00, 0x0d, 0x00, 0x1c, 0x00, 0x1a, 0x06, 0x03, 0x06,
+        0x01, 0x05, 0x03, 0x05, 0x01, 0x04, 0x03, 0x04, 0x01, 0x04, 0x02, 0x03,
+        0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x03, 0x02, 0x01, 0x02, 0x02, 0x00,
+        0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d,
+        0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x11, 0x00, 0x10, 0x00,
+        0x0e, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x10, 0x00, 0x0d, 0x00, 0x0b, 0x07, 0x68, 0x74, 0x74, 0x70, 0x31,
+        0x2e, 0x31, 0x02, 0x68, 0x32, 0xff, 0x01, 0x00, 0x01, 0x00},
+       true},
+      {"JDK 9 with EMS extension appended",
+       {0x03, 0x03, 0x0c, 0xe6, 0x06, 0xc6, 0x5d, 0x38, 0xb4, 0x5e, 0x3a, 0xd5,
+        0xb0, 0x5f, 0x5b, 0x84, 0x3b, 0xff, 0x86, 0x4f, 0xb0, 0x3f, 0xc1, 0xfd,
+        0x08, 0xf0, 0x97, 0xf3, 0x56, 0x44, 0x08, 0xe2, 0xdd, 0x2a, 0x00, 0x00,
+        0x64, 0xc0, 0x2c, 0xc0, 0x2b, 0xc0, 0x30, 0x00, 0x9d, 0xc0, 0x2e, 0xc0,
+        0x32, 0x00, 0x9f, 0x00, 0xa3, 0xc0, 0x2f, 0x00, 0x9c, 0xc0, 0x2d, 0xc0,
+        0x31, 0x00, 0x9e, 0x00, 0xa2, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x3d, 0xc0,
+        0x26, 0xc0, 0x2a, 0x00, 0x6b, 0x00, 0x6a, 0xc0, 0x0a, 0xc0, 0x14, 0x00,
+        0x35, 0xc0, 0x05, 0xc0, 0x0f, 0x00, 0x39, 0x00, 0x38, 0xc0, 0x23, 0xc0,
+        0x27, 0x00, 0x3c, 0xc0, 0x25, 0xc0, 0x29, 0x00, 0x67, 0x00, 0x40, 0xc0,
+        0x09, 0xc0, 0x13, 0x00, 0x2f, 0xc0, 0x04, 0xc0, 0x0e, 0x00, 0x33, 0x00,
+        0x32, 0xc0, 0x08, 0xc0, 0x12, 0x00, 0x0a, 0xc0, 0x03, 0xc0, 0x0d, 0x00,
+        0x16, 0x00, 0x13, 0x00, 0xff, 0x01, 0x00, 0x00, 0x61, 0x00, 0x0a, 0x00,
+        0x16, 0x00, 0x14, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x09, 0x00,
+        0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x16, 0x00,
+        0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0d, 0x00, 0x1c, 0x00, 0x1a, 0x06,
+        0x03, 0x06, 0x01, 0x05, 0x03, 0x05, 0x01, 0x04, 0x03, 0x04, 0x01, 0x04,
+        0x02, 0x03, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x03, 0x02, 0x01, 0x02,
+        0x02, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0e, 0x02, 0x00, 0x04, 0x00, 0x00,
+        0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
+        0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00},
+       false},
+      {"Chrome 61",
+       {0x03, 0x03, 0x33, 0x43, 0x8a, 0x6f, 0x76, 0xdd, 0x84, 0x3f, 0x8d, 0xaa,
+        0x43, 0xf1, 0x86, 0xee, 0xdd, 0x97, 0x96, 0x54, 0xf6, 0x17, 0x2c, 0xde,
+        0x69, 0xfe, 0x5e, 0x53, 0xaa, 0x47, 0xee, 0xad, 0xd7, 0x47, 0x00, 0x00,
+        0x1c, 0x3a, 0x3a, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc,
+        0xa9, 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00,
+        0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x77, 0x8a, 0x8a, 0x00,
+        0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0c,
+        0x00, 0x00, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
+        0x00, 0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x14,
+        0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05,
+        0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x05, 0x00, 0x05,
+        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00,
+        0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f,
+        0x31, 0x2e, 0x31, 0x75, 0x50, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01,
+        0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x4a, 0x4a, 0x00, 0x1d, 0x00,
+        0x17, 0x00, 0x18, 0x6a, 0x6a, 0x00, 0x01, 0x00},
+       false},
+      {"Firefox 55",
+       {0x03, 0x03, 0x06, 0x8e, 0xf8, 0xf7, 0x7a, 0xfd, 0xce, 0x45, 0xb0, 0x39,
+        0xbe, 0xa4, 0x55, 0x27, 0xe2, 0x80, 0xc4, 0x0a, 0xbd, 0xce, 0x56, 0x7a,
+        0xbc, 0x1f, 0x26, 0x2f, 0xfa, 0xb9, 0xa1, 0x7e, 0xe6, 0xde, 0x00, 0x00,
+        0x1e, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x2c, 0xc0,
+        0x30, 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x33, 0x00,
+        0x39, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x6a, 0x00,
+        0x00, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x00, 0x09, 0x6c, 0x6f, 0x63, 0x61,
+        0x6c, 0x68, 0x6f, 0x73, 0x74, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00,
+        0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17,
+        0x00, 0x18, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23,
+        0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08,
+        0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05,
+        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x18, 0x00, 0x16, 0x04,
+        0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04,
+        0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01},
+       false},
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(t.name);
+
+    bssl::SSLMessage msg;
+    msg.is_v2_hello = false;
+    msg.type = SSL3_MT_CLIENT_HELLO;
+    CBS_init(&msg.body, t.in.data(), t.in.size());
+    CBS_init(&msg.raw, nullptr, 0);  // unused
+
+    SSL_CLIENT_HELLO client_hello;
+    ASSERT_TRUE(bssl::ssl_client_hello_init(ssl.get(), &client_hello, msg));
+    EXPECT_EQ(t.is_probably_java, bssl::ssl_is_probably_java(&client_hello));
+  }
+}
+
 // TODO(davidben): Convert this file to GTest properly.
 TEST(SSLTest, AllTests) {
   if (!TestSSL_SESSIONEncoding(kOpenSSLSession) ||
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 7eba397..61540bb 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -1442,6 +1442,11 @@
 	// PadClientHello, if non-zero, pads the ClientHello to a multiple of
 	// that many bytes.
 	PadClientHello int
+
+	// SendOnlyECExtensions omits all extensions except supported_groups and
+	// ec_point_formats, in order to trigger the Java ClientHello
+	// fingerprint.
+	SendOnlyECExtensions bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 595fcae..13c3a19 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -97,6 +97,7 @@
 		pskBinderFirst:          c.config.Bugs.PSKBinderFirst,
 		omitExtensions:          c.config.Bugs.OmitExtensions,
 		emptyExtensions:         c.config.Bugs.EmptyExtensions,
+		sendOnlyECExtensions:    c.config.Bugs.SendOnlyECExtensions,
 	}
 
 	if maxVersion >= VersionTLS13 {
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 5dbcab9..2e6d286 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -176,6 +176,7 @@
 	omitExtensions          bool
 	emptyExtensions         bool
 	pad                     int
+	sendOnlyECExtensions    bool
 }
 
 func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -224,7 +225,8 @@
 		m.pskBinderFirst == m1.pskBinderFirst &&
 		m.omitExtensions == m1.omitExtensions &&
 		m.emptyExtensions == m1.emptyExtensions &&
-		m.pad == m1.pad
+		m.pad == m1.pad &&
+		m.sendOnlyECExtensions == m1.sendOnlyECExtensions
 }
 
 func (m *clientHelloMsg) marshal() []byte {
@@ -312,22 +314,6 @@
 		certificateStatusRequest.addU16(0) // ResponderID length
 		certificateStatusRequest.addU16(0) // Extensions length
 	}
-	if len(m.supportedCurves) > 0 {
-		// http://tools.ietf.org/html/rfc4492#section-5.1.1
-		extensions.addU16(extensionSupportedCurves)
-		supportedCurvesList := extensions.addU16LengthPrefixed()
-		supportedCurves := supportedCurvesList.addU16LengthPrefixed()
-		for _, curve := range m.supportedCurves {
-			supportedCurves.addU16(uint16(curve))
-		}
-	}
-	if len(m.supportedPoints) > 0 {
-		// http://tools.ietf.org/html/rfc4492#section-5.1.2
-		extensions.addU16(extensionSupportedPoints)
-		supportedPointsList := extensions.addU16LengthPrefixed()
-		supportedPoints := supportedPointsList.addU8LengthPrefixed()
-		supportedPoints.addBytes(m.supportedPoints)
-	}
 	if m.hasKeyShares {
 		extensions.addU16(extensionKeyShare)
 		keyShareList := extensions.addU16LengthPrefixed()
@@ -440,6 +426,30 @@
 		customExt := extensions.addU16LengthPrefixed()
 		customExt.addBytes([]byte(m.customExtension))
 	}
+
+	// Discard all extensions but the curve-related ones to trigger the Java
+	// fingerprinter.
+	if m.sendOnlyECExtensions {
+		hello.discardChild()
+		extensions = hello.addU16LengthPrefixed()
+	}
+	if len(m.supportedCurves) > 0 {
+		// http://tools.ietf.org/html/rfc4492#section-5.1.1
+		extensions.addU16(extensionSupportedCurves)
+		supportedCurvesList := extensions.addU16LengthPrefixed()
+		supportedCurves := supportedCurvesList.addU16LengthPrefixed()
+		for _, curve := range m.supportedCurves {
+			supportedCurves.addU16(uint16(curve))
+		}
+	}
+	if len(m.supportedPoints) > 0 {
+		// http://tools.ietf.org/html/rfc4492#section-5.1.2
+		extensions.addU16(extensionSupportedPoints)
+		supportedPointsList := extensions.addU16LengthPrefixed()
+		supportedPoints := supportedPointsList.addU8LengthPrefixed()
+		supportedPoints.addBytes(m.supportedPoints)
+	}
+
 	// The PSK extension must be last (draft-ietf-tls-tls13-18 section 4.2.6).
 	if len(m.pskIdentities) > 0 && !m.pskBinderFirst {
 		extensions.addU16(extensionPreSharedKey)
@@ -456,6 +466,8 @@
 		}
 	}
 
+	// This must be swapped with PSK (with some length computation) if we
+	// ever need to support PadClientHello and TLS 1.3.
 	if m.pad != 0 && hello.len()%m.pad != 0 {
 		extensions.addU16(extensionPadding)
 		padding := extensions.addU16LengthPrefixed()
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 093fa2c..0bd24ff 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2790,6 +2790,25 @@
 			shouldFail:         true,
 			expectedLocalError: "local error: record overflow",
 		},
+		{
+			// Test that Java-like ClientHellos are provided session
+			// IDs but resumption is always declined. This is to
+			// workaround a bug that causes connection failures when
+			// certificates rotate.
+			testType: serverTest,
+			name:     "JavaWorkaround",
+			config: Config{
+				MaxVersion:             VersionTLS12,
+				CurvePreferences:       []CurveID{CurveP256, CurveP384, CurveP521},
+				SessionTicketsDisabled: true,
+				Bugs: ProtocolBugs{
+					SendOnlyECExtensions: true,
+				},
+			},
+			flags:                []string{"-expect-session-id"},
+			resumeSession:        true,
+			expectResumeRejected: true,
+		},
 	}
 	testCases = append(testCases, basicTests...)