Implement fuzzer mode for ECH server.
Now skipping over HPKE decryption in |ssl_client_hello_decrypt| when
fuzzer mode is enabled. To improve code coverage, this fuzzer-only logic
also also has the ability to simulate a failed decryption.
As a result of mostly skipping the decryption, we now have to exclude
"*-ECH-Server-Decline*" tests from running in fuzzer mode. These tests
rely on the now-broken assumption that decryption will fail when the
client used an ECHConfig unknown to the server.
Bug: 275
Change-Id: I759a79c8596897cdd3d3a37e05f2973d47346ef9
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/47624
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/encrypted_client_hello.cc b/ssl/encrypted_client_hello.cc
index 5aeb1b4..7851667 100644
--- a/ssl/encrypted_client_hello.cc
+++ b/ssl/encrypted_client_hello.cc
@@ -316,6 +316,20 @@
return false;
}
+#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
+ // In fuzzer mode, disable encryption to improve coverage. We reserve a short
+ // input to signal decryption failure, so the fuzzer can explore fallback to
+ // ClientHelloOuter.
+ const uint8_t kBadPayload[] = {0xff};
+ if (payload == kBadPayload) {
+ *out_is_decrypt_error = true;
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
+ return false;
+ }
+ if (!out_encoded_client_hello_inner->CopyFrom(payload)) {
+ return false;
+ }
+#else
// Attempt to decrypt into |out_encoded_client_hello_inner|.
if (!out_encoded_client_hello_inner->Init(payload.size())) {
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
@@ -332,6 +346,7 @@
return false;
}
out_encoded_client_hello_inner->Shrink(encoded_client_hello_inner_len);
+#endif
return true;
}
diff --git a/ssl/test/fuzzer.h b/ssl/test/fuzzer.h
index 8f7a355..839560e 100644
--- a/ssl/test/fuzzer.h
+++ b/ssl/test/fuzzer.h
@@ -230,6 +230,22 @@
0x01, 'a', 0x02, 'a', 'a', 0x03, 'a', 'a', 'a',
};
+const uint8_t kECHServerConfig[] = {
+ 0xfe, 0x0a, 0x00, 0x47, 0x2a, 0x00, 0x20, 0x00, 0x20, 0x6c, 0x55,
+ 0x96, 0x41, 0x3d, 0x12, 0x4e, 0x63, 0x3d, 0x39, 0x7a, 0xe9, 0xbc,
+ 0xec, 0xb2, 0x55, 0xd0, 0xe6, 0xaa, 0xbd, 0xa9, 0x79, 0xb8, 0x86,
+ 0x9a, 0x13, 0x61, 0xc6, 0x69, 0xac, 0xb4, 0x21, 0x00, 0x0c, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03,
+ 0x00, 0x10, 0x00, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e,
+ 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x00, 0x00,
+};
+
+const uint8_t kECHServerConfigPrivateKey[] = {
+ 0x35, 0x6d, 0x45, 0x06, 0xb3, 0x88, 0x89, 0x2e, 0xd6, 0x87, 0x84,
+ 0xd2, 0x2d, 0x6f, 0x83, 0x48, 0xad, 0xf2, 0xfd, 0x08, 0x51, 0x73,
+ 0x10, 0xa0, 0xb8, 0xdd, 0xe9, 0x96, 0x6a, 0xde, 0xbc, 0x82,
+};
+
int ALPNSelectCallback(SSL *ssl, const uint8_t **out, uint8_t *out_len,
const uint8_t *in, unsigned in_len, void *arg) {
static const uint8_t kProtocol[] = {'a', 'a'};
@@ -438,6 +454,23 @@
}
SSL_CTX_set_tls_channel_id_enabled(ctx_.get(), 1);
+ if (role_ == kServer) {
+ bssl::UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> config_list(
+ SSL_ECH_SERVER_CONFIG_LIST_new());
+ if (!config_list) {
+ return false;
+ }
+ if (!SSL_ECH_SERVER_CONFIG_LIST_add(
+ config_list.get(), /*is_retry_config=*/true, kECHServerConfig,
+ sizeof(kECHServerConfig), kECHServerConfigPrivateKey,
+ sizeof(kECHServerConfigPrivateKey))) {
+ return false;
+ }
+ if (!SSL_CTX_set1_ech_server_config_list(ctx_.get(), config_list.get())) {
+ return false;
+ }
+ }
+
return true;
}
diff --git a/ssl/test/fuzzer_tags.h b/ssl/test/fuzzer_tags.h
index 3946df7..b612222 100644
--- a/ssl/test/fuzzer_tags.h
+++ b/ssl/test/fuzzer_tags.h
@@ -23,7 +23,7 @@
// The TLS client and server fuzzers coordinate with bssl_shim on a common
// format to encode configuration parameters in a fuzzer file. To add a new
// configuration, define a tag, update |SetupTest| in fuzzer.h to parse it, and
-// update |WriteSettings| in bssl_shim to serialize it. Finally, record
+// update |SettingsWriter| in bssl_shim to serialize it. Finally, record
// transcripts from a test run, and use the BORINGSSL_FUZZER_DEBUG environment
// variable to confirm the transcripts are compatible.
diff --git a/ssl/test/runner/fuzzer_mode.json b/ssl/test/runner/fuzzer_mode.json
index f2dc3fd..e7c8ad7 100644
--- a/ssl/test/runner/fuzzer_mode.json
+++ b/ssl/test/runner/fuzzer_mode.json
@@ -51,6 +51,8 @@
"Renegotiate-Client-BadExt*": "Fuzzer mode does not check renegotiation_info.",
- "CBCRecordSplitting*": "Fuzzer mode does not implement record-splitting."
+ "CBCRecordSplitting*": "Fuzzer mode does not implement record-splitting.",
+
+ "*-ECH-Server-Decline*": "Encryption with wrong ECHConfig will not fail because fuzzer mode skips HPKE decryption."
}
}
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 7d32c74..3451137 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -23,6 +23,8 @@
"boringssl.googlesource.com/boringssl/ssl/test/runner/hpke"
)
+const echBadPayloadByte = 0xff
+
type clientHandshakeState struct {
c *Conn
serverHello *serverHelloMsg
@@ -826,7 +828,11 @@
return nil, err
}
if c.config.Bugs.CorruptEncryptedClientHello {
- hello.clientECH.payload[0] ^= 1
+ if c.config.Bugs.NullAllCiphers {
+ hello.clientECH.payload = []byte{echBadPayloadByte}
+ } else {
+ hello.clientECH.payload[0] ^= 1
+ }
}
}
@@ -884,6 +890,10 @@
encodedInner := innerHello.marshalForEncodedInner()
payload := hs.echHPKEContext.Seal(encodedInner, aad.finish())
+ if c.config.Bugs.NullAllCiphers {
+ payload = encodedInner
+ }
+
// Place the ECH extension in the outer CH.
hello.clientECH = &clientECH{
hpkeKDF: hs.echHPKEContext.KDF(),
@@ -1534,7 +1544,11 @@
return err
}
if c.config.Bugs.CorruptSecondEncryptedClientHello {
- hello.clientECH.payload[0] ^= 1
+ if c.config.Bugs.NullAllCiphers {
+ hello.clientECH.payload = []byte{echBadPayloadByte}
+ } else {
+ hello.clientECH.payload[0] ^= 1
+ }
}
}
}