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/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,