Add tests to ensure our ClientHello does not change.
We'll need to update it on occasion, but we should not update our
default ClientHello without noticing.
Change-Id: I19ca52fdbe10e3ac14413fecd16be2e58af5a1f6
Reviewed-on: https://boringssl-review.googlesource.com/9075
Reviewed-by: Steven Valdez <svaldez@google.com>
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_test.cc b/ssl/ssl_test.cc
index 86e7942..0dc0884 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -796,6 +796,29 @@
return session;
}
+static bool GetClientHello(SSL *ssl, std::vector<uint8_t> *out) {
+ ScopedBIO bio(BIO_new(BIO_s_mem()));
+ if (!bio) {
+ return false;
+ }
+ // Do not configure a reading BIO, but record what's written to a memory BIO.
+ SSL_set_bio(ssl, nullptr /* rbio */, BIO_up_ref(bio.get()));
+ int ret = SSL_connect(ssl);
+ if (ret > 0) {
+ // SSL_connect should fail without a BIO to write to.
+ return false;
+ }
+ ERR_clear_error();
+
+ const uint8_t *client_hello;
+ size_t client_hello_len;
+ if (!BIO_mem_contents(bio.get(), &client_hello, &client_hello_len)) {
+ return false;
+ }
+ *out = std::vector<uint8_t>(client_hello, client_hello + client_hello_len);
+ return true;
+}
+
// GetClientHelloLen creates a client SSL connection with a ticket of length
// |ticket_len| and records the ClientHello. It returns the length of the
// ClientHello, not including the record header, on success and zero on error.
@@ -806,26 +829,15 @@
return 0;
}
ScopedSSL ssl(SSL_new(ctx.get()));
- ScopedBIO bio(BIO_new(BIO_s_mem()));
- if (!ssl || !bio || !SSL_set_session(ssl.get(), session.get())) {
+ if (!ssl || !SSL_set_session(ssl.get(), session.get())) {
return 0;
}
- // Do not configure a reading BIO, but record what's written to a memory BIO.
- SSL_set_bio(ssl.get(), nullptr /* rbio */, BIO_up_ref(bio.get()));
- int ret = SSL_connect(ssl.get());
- if (ret > 0) {
- // SSL_connect should fail without a BIO to write to.
+ std::vector<uint8_t> client_hello;
+ if (!GetClientHello(ssl.get(), &client_hello) ||
+ client_hello.size() <= SSL3_RT_HEADER_LENGTH) {
return 0;
}
- ERR_clear_error();
-
- const uint8_t *unused;
- size_t client_hello_len;
- if (!BIO_mem_contents(bio.get(), &unused, &client_hello_len) ||
- client_hello_len <= SSL3_RT_HEADER_LENGTH) {
- return 0;
- }
- return client_hello_len - SSL3_RT_HEADER_LENGTH;
+ return client_hello.size() - SSL3_RT_HEADER_LENGTH;
}
struct PaddingTest {
@@ -1575,6 +1587,131 @@
return true;
}
+static bool ClientHelloMatches(uint16_t version, const uint8_t *expected,
+ size_t expected_len) {
+ ScopedSSL_CTX ctx(SSL_CTX_new(TLS_method()));
+ if (!ctx) {
+ return false;
+ }
+ SSL_CTX_set_max_version(ctx.get(), version);
+ // Our default cipher list varies by CPU capabilities, so manually place the
+ // ChaCha20 ciphers in front.
+ if (!SSL_CTX_set_cipher_list(ctx.get(), "CHACHA20:ALL")) {
+ return false;
+ }
+ ScopedSSL ssl(SSL_new(ctx.get()));
+ if (!ssl) {
+ return false;
+ }
+ std::vector<uint8_t> client_hello;
+ if (!GetClientHello(ssl.get(), &client_hello)) {
+ return false;
+ }
+
+ // Zero the client_random.
+ constexpr size_t kRandomOffset = 1 + 2 + 2 + // record header
+ 1 + 3 + // handshake message header
+ 2; // client_version
+ if (client_hello.size() < kRandomOffset + SSL3_RANDOM_SIZE) {
+ fprintf(stderr, "ClientHello for version %04x too short.\n", version);
+ return false;
+ }
+ memset(client_hello.data() + kRandomOffset, 0, SSL3_RANDOM_SIZE);
+
+ if (client_hello.size() != expected_len ||
+ memcmp(client_hello.data(), expected, expected_len) != 0) {
+ fprintf(stderr, "ClientHello for version %04x did not match:\n", version);
+ fprintf(stderr, "Got:\n\t");
+ for (size_t i = 0; i < client_hello.size(); i++) {
+ fprintf(stderr, "0x%02x, ", client_hello[i]);
+ }
+ fprintf(stderr, "\nWanted:\n\t");
+ for (size_t i = 0; i < expected_len; i++) {
+ fprintf(stderr, "0x%02x, ", expected[i]);
+ }
+ fprintf(stderr, "\n");
+ return false;
+ }
+
+ return true;
+}
+
+// Tests that our ClientHellos do not change unexpectedly.
+static bool TestClientHello() {
+ static const uint8_t kSSL3ClientHello[] = {
+ 0x16, 0x03, 0x00, 0x00, 0x47, 0x01, 0x00, 0x00, 0x43, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x1c, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0xc0, 0x0a, 0xc0,
+ 0x14, 0x00, 0x39, 0xc0, 0x07, 0xc0, 0x11, 0x00, 0x2f, 0x00, 0x35,
+ 0x00, 0x0a, 0x00, 0x05, 0x00, 0x04, 0x00, 0xff, 0x01, 0x00,
+ };
+ if (!ClientHelloMatches(SSL3_VERSION, kSSL3ClientHello,
+ sizeof(kSSL3ClientHello))) {
+ return false;
+ }
+
+ static const uint8_t kTLS1ClientHello[] = {
+ 0x16, 0x03, 0x01, 0x00, 0x66, 0x01, 0x00, 0x00, 0x62, 0x03, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xc0, 0x09,
+ 0xc0, 0x13, 0x00, 0x33, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x07,
+ 0xc0, 0x11, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x00, 0x05, 0x00, 0x04,
+ 0x01, 0x00, 0x00, 0x1f, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x17, 0x00,
+ 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00,
+ 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
+ };
+ if (!ClientHelloMatches(TLS1_VERSION, kTLS1ClientHello,
+ sizeof(kTLS1ClientHello))) {
+ return false;
+ }
+
+ static const uint8_t kTLS11ClientHello[] = {
+ 0x16, 0x03, 0x01, 0x00, 0x66, 0x01, 0x00, 0x00, 0x62, 0x03, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xc0, 0x09,
+ 0xc0, 0x13, 0x00, 0x33, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x07,
+ 0xc0, 0x11, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x00, 0x05, 0x00, 0x04,
+ 0x01, 0x00, 0x00, 0x1f, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x17, 0x00,
+ 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00,
+ 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
+ };
+ if (!ClientHelloMatches(TLS1_1_VERSION, kTLS11ClientHello,
+ sizeof(kTLS11ClientHello))) {
+ return false;
+ }
+
+ static const uint8_t kTLS12ClientHello[] = {
+ 0x16, 0x03, 0x01, 0x00, 0xa4, 0x01, 0x00, 0x00, 0xa0, 0x03, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0xcc, 0xa9,
+ 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e,
+ 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xc0, 0x09, 0xc0, 0x23, 0xc0, 0x13,
+ 0xc0, 0x27, 0x00, 0x33, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x24, 0xc0, 0x14,
+ 0xc0, 0x28, 0x00, 0x39, 0x00, 0x6b, 0xc0, 0x07, 0xc0, 0x11, 0x00, 0x9c,
+ 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x3d, 0x00, 0x0a,
+ 0x00, 0x05, 0x00, 0x04, 0x01, 0x00, 0x00, 0x35, 0xff, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
+ 0x12, 0x00, 0x10, 0x06, 0x01, 0x06, 0x03, 0x05, 0x01, 0x05, 0x03, 0x04,
+ 0x01, 0x04, 0x03, 0x02, 0x01, 0x02, 0x03, 0x00, 0x0b, 0x00, 0x02, 0x01,
+ 0x00, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00,
+ 0x18,
+ };
+ if (!ClientHelloMatches(TLS1_2_VERSION, kTLS12ClientHello,
+ sizeof(kTLS12ClientHello))) {
+ return false;
+ }
+
+ // TODO(davidben): Add a change detector for TLS 1.3 once the spec and our
+ // implementation has settled enough that it won't change.
+
+ return true;
+}
+
int main() {
CRYPTO_library_init();
@@ -1605,7 +1742,8 @@
!TestSetFD() ||
!TestSetBIO() ||
!TestGetPeerCertificate() ||
- !TestRetainOnlySHA256OfCerts()) {
+ !TestRetainOnlySHA256OfCerts() ||
+ !TestClientHello()) {
ERR_print_errors_fp(stderr);
return 1;
}