Add post-quantum experiment signal extension. When testing HRSS-SXY and SIKE, we also want a control group. However, how are clients to indicate that they're part of the 1/3 of the experiment population that's not advertising CECPQ? And how are servers to indicate that they would have negotiated CECPQ2 / 2b if only the client had asked? This change adds a temporary signaling extension to solve these issues. Change-Id: Ic087a09149ef10141568b734396981ae97950a9b Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/36725 Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 47fb851..c0247ca 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h
@@ -3034,6 +3034,19 @@ OPENSSL_EXPORT const char *SSL_get_psk_identity(const SSL *ssl); +// Post-quantum experiment signaling extension. +// +// *** EXPERIMENTAL *** +// +// In order to define a control group in an experiment of post-quantum key +// agreements, clients and servers may send a non-IANA defined extension as a +// signaling bit. These functions should not be used without explicit permission +// from BoringSSL-team. + +OPENSSL_EXPORT int SSL_enable_pq_experiment_signal(SSL *ssl); +OPENSSL_EXPORT int SSL_pq_experiment_signal_seen(const SSL *ssl); + + // QUIC transport parameters. // // draft-ietf-quic-tls defines a new TLS extension quic_transport_parameters
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h index 384d102..f7766f2 100644 --- a/include/openssl/tls1.h +++ b/include/openssl/tls1.h
@@ -244,6 +244,9 @@ // This is not an IANA defined extension number #define TLSEXT_TYPE_channel_id 30032 +// This is not an IANA defined extension number +#define TLSEXT_TYPE_pq_experiment_signal 54538 + // status request value from RFC 3546 #define TLSEXT_STATUSTYPE_nothing (-1) #define TLSEXT_STATUSTYPE_ocsp 1
diff --git a/ssl/internal.h b/ssl/internal.h index bc17af9..2598058 100644 --- a/ssl/internal.h +++ b/ssl/internal.h
@@ -2254,6 +2254,10 @@ // token_binding_negotiated is set if Token Binding was negotiated. bool token_binding_negotiated : 1; + // pq_experimental_signal_seen is true if the peer was observed + // sending/echoing the post-quantum experiment signal. + bool pq_experiment_signal_seen : 1; + // hs_buf is the buffer of handshake data to process. UniquePtr<BUF_MEM> hs_buf; @@ -2584,6 +2588,11 @@ // jdk11_workaround is whether to disable TLS 1.3 for JDK 11 clients, as a // workaround for https://bugs.openjdk.java.net/browse/JDK-8211806. bool jdk11_workaround : 1; + + // pq_experiment_signal indicates that an empty extension should be sent + // (for clients) or echoed (for servers) to indicate participation in an + // experiment of post-quantum key exchanges. + bool pq_experiment_signal : 1; }; // From RFC 8446, used in determining PSK modes.
diff --git a/ssl/s3_lib.cc b/ssl/s3_lib.cc index b6d905d..75e1f68 100644 --- a/ssl/s3_lib.cc +++ b/ssl/s3_lib.cc
@@ -179,7 +179,8 @@ wpend_pending(false), early_data_accepted(false), tls13_downgrade(false), - token_binding_negotiated(false) {} + token_binding_negotiated(false), + pq_experiment_signal_seen(false) {} SSL3_STATE::~SSL3_STATE() {}
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc index f27f2b8..45ed62f 100644 --- a/ssl/ssl_lib.cc +++ b/ssl/ssl_lib.cc
@@ -734,7 +734,8 @@ handoff(false), shed_handshake_config(false), ignore_tls13_downgrade(false), - jdk11_workaround(false) { + jdk11_workaround(false), + pq_experiment_signal(false) { assert(ssl); } @@ -1245,6 +1246,18 @@ return ssl_send_alert_impl(ssl, SSL3_AL_FATAL, alert); } +int SSL_enable_pq_experiment_signal(SSL *ssl) { + if (!ssl->config) { + return 0; + } + ssl->config->pq_experiment_signal = true; + return 1; +} + +int SSL_pq_experiment_signal_seen(const SSL *ssl) { + return ssl->s3->pq_experiment_signal_seen; +} + int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, size_t params_len) { return ssl->config && ssl->config->quic_transport_params.CopyFrom(
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc index 3388556..c05e2c6 100644 --- a/ssl/t1_lib.cc +++ b/ssl/t1_lib.cc
@@ -2886,6 +2886,67 @@ return true; } + +// Post-quantum experiment signal +// +// This extension may be used in order to identify a control group for +// experimenting with post-quantum key exchange algorithms. + +static bool ext_pq_experiment_signal_add_clienthello(SSL_HANDSHAKE *hs, + CBB *out) { + if (hs->config->pq_experiment_signal && + (!CBB_add_u16(out, TLSEXT_TYPE_pq_experiment_signal) || + !CBB_add_u16(out, 0))) { + return false; + } + + return true; +} + +static bool ext_pq_experiment_signal_parse_serverhello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, + CBS *contents) { + if (contents == nullptr) { + return true; + } + + if (!hs->config->pq_experiment_signal || CBS_len(contents) != 0) { + return false; + } + + hs->ssl->s3->pq_experiment_signal_seen = true; + return true; +} + +static bool ext_pq_experiment_signal_parse_clienthello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, + CBS *contents) { + if (contents == nullptr) { + return true; + } + + if (CBS_len(contents) != 0) { + return false; + } + + if (hs->ssl->config->pq_experiment_signal) { + hs->ssl->s3->pq_experiment_signal_seen = true; + } + + return true; +} + +static bool ext_pq_experiment_signal_add_serverhello(SSL_HANDSHAKE *hs, + CBB *out) { + if (hs->ssl->s3->pq_experiment_signal_seen && + (!CBB_add_u16(out, TLSEXT_TYPE_pq_experiment_signal) || + !CBB_add_u16(out, 0))) { + return false; + } + + return true; +} + // kExtensions contains all the supported extensions. static const struct tls_extension kExtensions[] = { { @@ -3074,6 +3135,14 @@ ext_delegated_credential_parse_clienthello, dont_add_serverhello, }, + { + TLSEXT_TYPE_pq_experiment_signal, + NULL, + ext_pq_experiment_signal_add_clienthello, + ext_pq_experiment_signal_parse_serverhello, + ext_pq_experiment_signal_parse_clienthello, + ext_pq_experiment_signal_add_serverhello, + }, }; #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index 98da2ec..dfcb2c7 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc
@@ -672,6 +672,14 @@ SSL_delegated_credential_used(ssl) ? "" : "no"); return false; } + + if (config->expect_pq_experiment_signal != + !!SSL_pq_experiment_signal_seen(ssl)) { + fprintf(stderr, "Got %sPQ experiment signal, but wanted opposite. \n", + SSL_pq_experiment_signal_seen(ssl) ? "" : "no "); + return false; + } + return true; }
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index 1729289..9930bd3 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go
@@ -126,6 +126,7 @@ extensionQUICTransportParams uint16 = 0xffa5 // draft-ietf-quic-tls-13 extensionChannelID uint16 = 30032 // not IANA assigned extensionDelegatedCredentials uint16 = 0xff02 // not IANA assigned + extensionPQExperimentSignal uint16 = 54538 ) // TLS signaling cipher suite values @@ -500,6 +501,11 @@ CertCompressionAlgs map[uint16]CertCompressionAlg + // PQExperimentSignal instructs a client to send a non-IANA defined extension + // that signals participation in an experiment of post-quantum key exchange + // methods. + PQExperimentSignal bool + // Bugs specifies optional misbehaviour to be used for testing other // implementations. Bugs ProtocolBugs @@ -1650,6 +1656,10 @@ // DisableDelegatedCredentials, if true, disables client support for delegated // credentials. DisableDelegatedCredentials bool + + // ExpectPQExperimentSignal specifies whether or not the post-quantum + // experiment signal should be received by a client or server. + ExpectPQExperimentSignal bool } func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index 45dc75d..2574ec3 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go
@@ -129,6 +129,7 @@ omitExtensions: c.config.Bugs.OmitExtensions, emptyExtensions: c.config.Bugs.EmptyExtensions, delegatedCredentials: !c.config.Bugs.DisableDelegatedCredentials, + pqExperimentSignal: c.config.PQExperimentSignal, } if maxVersion >= VersionTLS13 { @@ -1666,6 +1667,10 @@ c.quicTransportParams = serverExtensions.quicTransportParams } + if c.config.Bugs.ExpectPQExperimentSignal != serverExtensions.pqExperimentSignal { + return fmt.Errorf("tls: PQ experiment signal presence (%t) was not what was expected", serverExtensions.pqExperimentSignal) + } + return nil }
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go index f12ca1a..ac52eed 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go
@@ -298,6 +298,7 @@ pad int compressedCertAlgs []uint16 delegatedCredentials bool + pqExperimentSignal bool } func (m *clientHelloMsg) equal(i interface{}) bool { @@ -352,7 +353,8 @@ m.emptyExtensions == m1.emptyExtensions && m.pad == m1.pad && eqUint16s(m.compressedCertAlgs, m1.compressedCertAlgs) && - m.delegatedCredentials == m1.delegatedCredentials + m.delegatedCredentials == m1.delegatedCredentials && + m.pqExperimentSignal == m1.pqExperimentSignal } func (m *clientHelloMsg) marshalKeyShares(bb *byteBuilder) { @@ -598,6 +600,11 @@ extensions.addU16(extensionDelegatedCredentials) extensions.addU16(0) // Length is always 0 } + if m.pqExperimentSignal { + extensions.addU16(extensionPQExperimentSignal) + extensions.addU16(0) // Length is always 0 + } + // The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11 if len(m.pskIdentities) > 0 && !m.pskBinderFirst { extensions.addU16(extensionPreSharedKey) @@ -724,6 +731,7 @@ m.extendedMasterSecret = false m.customExtension = "" m.delegatedCredentials = false + m.pqExperimentSignal = false if len(reader) == 0 { // ClientHello is optionally followed by extension data @@ -959,6 +967,11 @@ return false } m.delegatedCredentials = true + case extensionPQExperimentSignal: + if len(body) != 0 { + return false + } + m.pqExperimentSignal = true } if isGREASEValue(extension) { @@ -1226,6 +1239,7 @@ supportedCurves []CurveID quicTransportParams []byte serverNameAck bool + pqExperimentSignal bool } func (m *serverExtensions) marshal(extensions *byteBuilder) { @@ -1360,6 +1374,10 @@ extensions.addU16(extensionServerName) extensions.addU16(0) // zero length } + if m.pqExperimentSignal { + extensions.addU16(extensionPQExperimentSignal) + extensions.addU16(0) // zero length + } } func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool { @@ -1468,6 +1486,11 @@ return false } m.hasEarlyData = true + case extensionPQExperimentSignal: + if len(body) != 0 { + return false + } + m.pqExperimentSignal = true default: // Unknown extensions are illegal from the server. return false
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index 2dc7032..1eb92bf 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go
@@ -228,6 +228,10 @@ } } + if c.config.Bugs.ExpectPQExperimentSignal != hs.clientHello.pqExperimentSignal { + return fmt.Errorf("tls: PQ experiment signal presence (%t) was not what was expected", hs.clientHello.pqExperimentSignal) + } + c.clientVersion = hs.clientHello.vers // Use the versions extension if supplied, otherwise use the legacy ClientHello version. @@ -1456,6 +1460,7 @@ } serverExtensions.serverNameAck = c.config.Bugs.SendServerNameAck + serverExtensions.pqExperimentSignal = hs.clientHello.pqExperimentSignal return nil }
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index ee09faf..316373a 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go
@@ -15531,6 +15531,67 @@ }) } +func addPQExperimentSignalTests() { + testCases = append(testCases, testCase{ + testType: serverTest, + name: "PQExperimentSignal-Server-NoEchoIfNotConfigured", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectPQExperimentSignal: false, + }, + PQExperimentSignal: true, + }, + }) + + testCases = append(testCases, testCase{ + testType: serverTest, + name: "PQExperimentSignal-Server-Echo", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectPQExperimentSignal: true, + }, + PQExperimentSignal: true, + }, + flags: []string{ + "-enable-pq-experiment-signal", + "-expect-pq-experiment-signal", + }, + }) + + testCases = append(testCases, testCase{ + testType: clientTest, + name: "PQExperimentSignal-Client-NotDefault", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectPQExperimentSignal: false, + }, + PQExperimentSignal: true, + }, + }) + + testCases = append(testCases, testCase{ + testType: clientTest, + name: "PQExperimentSignal-Client", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectPQExperimentSignal: true, + }, + }, + flags: []string{ + "-enable-pq-experiment-signal", + "-expect-pq-experiment-signal", + }, + }) +} + func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) { defer wg.Done() @@ -15668,6 +15729,7 @@ addCertCompressionTests() addJDK11WorkaroundTests() addDelegatedCredentialTests() + addPQExperimentSignalTests() testCases = append(testCases, convertToSplitHandshakeTests(testCases)...)
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index bad295e..8de81f5 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc
@@ -150,6 +150,8 @@ {"-key-update", &TestConfig::key_update}, {"-expect-delegated-credential-used", &TestConfig::expect_delegated_credential_used}, + {"-enable-pq-experiment-signal", &TestConfig::enable_pq_experiment_signal}, + {"-expect-pq-experiment-signal", &TestConfig::expect_pq_experiment_signal}, }; const Flag<std::string> kStringFlags[] = { @@ -1714,5 +1716,11 @@ } } + if (enable_pq_experiment_signal && + !SSL_enable_pq_experiment_signal(ssl.get())) { + fprintf(stderr, "SSL_enable_pq_experiment_signal failed.\n"); + return nullptr; + } + return ssl; }
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index fc2dded..57bf66a 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h
@@ -176,6 +176,8 @@ bool expect_delegated_credential_used = false; std::string delegated_credential; std::string expect_early_data_reason; + bool enable_pq_experiment_signal = false; + bool expect_pq_experiment_signal = false; int argc; char **argv;