Add support for dummy PQ padding.

This extension will be used to measure the latency impact of potentially
sending a post-quantum key share by default. At this time it's purely
measuring the impact of the client sending the key share, not the server
replying with a ciphertext.

We could use the existing padding extension for this but that extension
doesn't allow the server to echo it, so we would need a different
extension in the future anyway. Thus we just create one now.

We can assume that modern clients will be using TLS 1.3 by the time that
PQ key-exchange is established and thus the key share will be sent in
all ClientHello messages. However, since TLS 1.3 isn't quite here yet,
this extension is also sent for TLS 1.0–1.2 ClientHellos. The latency
impact should be the same either way.

Change-Id: Ie4a17551f6589b28505797e8c54cddbe3338dfe5
Reviewed-on: https://boringssl-review.googlesource.com/24585
Commit-Queue: Adam Langley <agl@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 92a502d..b868d3f 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2909,6 +2909,21 @@
 OPENSSL_EXPORT const char *SSL_get_psk_identity(const SSL *ssl);
 
 
+// Dummy post-quantum padding.
+//
+// Dummy post-quantum padding invovles the client (and later server) sending
+// useless, random-looking bytes in an extension in their ClientHello or
+// ServerHello. These extensions are sized to simulate a post-quantum
+// key-exchange and so enable measurement of the latency impact of the
+// additional bandwidth.
+
+// SSL_set_dummy_pq_padding_size enables the sending of a dummy PQ padding
+// extension and configures its size. This is only effective for a client: a
+// server will echo an extension with one of equal length when we get to that
+// phase of the experiment. It returns one for success and zero otherwise.
+OPENSSL_EXPORT int SSL_set_dummy_pq_padding_size(SSL *ssl, size_t num_bytes);
+
+
 // Early data.
 //
 // WARNING: 0-RTT support in BoringSSL is currently experimental and not fully
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index 2238043..4b5d226 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -229,6 +229,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_dummy_pq_padding 54537
+
 // status request value from RFC 3546
 #define TLSEXT_STATUSTYPE_ocsp 1
 
diff --git a/ssl/internal.h b/ssl/internal.h
index bbefd48..91ca1f7 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -2581,6 +2581,7 @@
   uint32_t options;  // protocol behaviour
   uint32_t mode;     // API behaviour
   uint32_t max_cert_list;
+  uint16_t dummy_pq_padding_len;
   char *tlsext_hostname;
   size_t supported_group_list_len;
   uint16_t *supported_group_list;  // our list
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 5e420e8..8f53dcd 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -2385,6 +2385,15 @@
   ctx->psk_server_callback = cb;
 }
 
+int SSL_set_dummy_pq_padding_size(SSL *ssl, size_t num_bytes) {
+  if (num_bytes > 0xffff) {
+    return 0;
+  }
+
+  ssl->dummy_pq_padding_len = num_bytes;
+  return 1;
+}
+
 void SSL_CTX_set_msg_callback(SSL_CTX *ctx,
                               void (*cb)(int write_p, int version,
                                          int content_type, const void *buf,
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index 4b3de43..a8833e0 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -116,6 +116,7 @@
 #include <utility>
 
 #include <openssl/bytestring.h>
+#include <openssl/chacha.h>
 #include <openssl/digest.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -559,6 +560,11 @@
   return true;
 }
 
+static bool ignore_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
+                                     CBS *contents) {
+  return true;
+}
+
 static bool dont_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
   return true;
 }
@@ -2318,6 +2324,42 @@
 }
 
 
+// Dummy PQ Padding extension
+//
+// Dummy post-quantum padding invovles the client (and later server) sending
+// useless, random-looking bytes in an extension in their ClientHello or
+// ServerHello. These extensions are sized to simulate a post-quantum
+// key-exchange and so enable measurement of the latency impact of the
+// additional bandwidth.
+
+static bool ext_dummy_pq_padding_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
+  const size_t len = hs->ssl->dummy_pq_padding_len;
+  if (len == 0) {
+    return true;
+  }
+
+  CBB contents;
+  uint8_t *buffer;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_dummy_pq_padding) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      !CBB_add_space(&contents, &buffer, len)) {
+    return false;
+  }
+
+  // The length is used as the nonce so that different length extensions have
+  // different contents. There's no reason this has to be the case, it just
+  // makes things a little more obvious in a packet dump.
+  uint8_t nonce[12] = {0};
+  memcpy(nonce, &len, sizeof(len));
+
+  memset(buffer, 0, len);
+  static const uint8_t kZeroKey[32] = {0};
+  CRYPTO_chacha_20(buffer, buffer, len, kZeroKey, nonce, 0);
+
+  return CBB_flush(out);
+}
+
+
 // Negotiated Groups
 //
 // https://tools.ietf.org/html/rfc4492#section-5.1.2
@@ -2547,6 +2589,14 @@
     ignore_parse_clienthello,
     dont_add_serverhello,
   },
+  {
+    TLSEXT_TYPE_dummy_pq_padding,
+    NULL,
+    ext_dummy_pq_padding_add_clienthello,
+    ignore_parse_serverhello,
+    ignore_parse_clienthello,
+    dont_add_serverhello,
+  },
   // The final extension must be non-empty. WebSphere Application Server 7.0 is
   // intolerant to the last extension being zero-length. See
   // https://crbug.com/363583.
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 38e4077..0123df7 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -2054,6 +2054,10 @@
   if (config->max_send_fragment > 0) {
     SSL_set_max_send_fragment(ssl.get(), config->max_send_fragment);
   }
+  if (config->dummy_pq_padding_len > 0 &&
+      !SSL_set_dummy_pq_padding_size(ssl.get(), config->dummy_pq_padding_len)) {
+    return false;
+  }
 
   int sock = Connect(config->port);
   if (sock == -1) {
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index cd04acf..15e42ff 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -136,6 +136,7 @@
 	extensionNextProtoNeg               uint16 = 13172 // not IANA assigned
 	extensionRenegotiationInfo          uint16 = 0xff01
 	extensionChannelID                  uint16 = 30032 // not IANA assigned
+	extensionDummyPQPadding             uint16 = 54537 // not IANA assigned
 )
 
 // TLS signaling cipher suite values
@@ -1525,6 +1526,11 @@
 	// ExpectDraftTLS13DowngradeRandom, if true, causes the client to
 	// require the server send the draft TLS 1.3 anti-downgrade signal.
 	ExpectDraftTLS13DowngradeRandom bool
+
+	// ExpectDummyPQPaddingLength, if not zero, causes the server to
+	// require that the client sent a dummy PQ padding extension of this
+	// length.
+	ExpectDummyPQPaddingLength int
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index b650393..16d858f 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -292,6 +292,7 @@
 	omitExtensions          bool
 	emptyExtensions         bool
 	pad                     int
+	dummyPQPaddingLen       int
 }
 
 func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -340,7 +341,8 @@
 		m.pskBinderFirst == m1.pskBinderFirst &&
 		m.omitExtensions == m1.omitExtensions &&
 		m.emptyExtensions == m1.emptyExtensions &&
-		m.pad == m1.pad
+		m.pad == m1.pad &&
+		m.dummyPQPaddingLen == m1.dummyPQPaddingLen
 }
 
 func (m *clientHelloMsg) marshal() []byte {
@@ -853,6 +855,11 @@
 			m.sctListSupported = true
 		case extensionCustom:
 			m.customExtension = string(body)
+		case extensionDummyPQPadding:
+			if len(body) == 0 {
+				return false
+			}
+			m.dummyPQPaddingLen = len(body)
 		}
 
 		if isGREASEValue(extension) {
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index abfab91..d317d01 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -345,6 +345,10 @@
 		}
 	}
 
+	if expected := hs.clientHello.dummyPQPaddingLen; expected != config.Bugs.ExpectDummyPQPaddingLength {
+		return fmt.Errorf("tls: expected dummy PQ padding extension of length %d, but got one of length %d", expected, config.Bugs.ExpectDummyPQPaddingLength)
+	}
+
 	applyBugsToClientHello(hs.clientHello, config)
 
 	return nil
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 74e060c..5f5e8c7 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -6750,6 +6750,32 @@
 		shouldFail:    true,
 		expectedError: ":INVALID_SCT_LIST:",
 	})
+
+	for _, version := range allVersions(tls) {
+		if version.version < VersionTLS12 {
+			continue
+		}
+
+		for _, paddingLen := range []int{1, 9700} {
+			flags := []string{
+				"-max-version", version.shimFlag(tls),
+				"-dummy-pq-padding-len", strconv.Itoa(paddingLen),
+			}
+
+			testCases = append(testCases, testCase{
+				name:         fmt.Sprintf("DummyPQPadding-%d-%s", paddingLen, version.name),
+				testType:     clientTest,
+				tls13Variant: version.tls13Variant,
+				config: Config{
+					MaxVersion: version.version,
+					Bugs: ProtocolBugs{
+						ExpectDummyPQPaddingLength: paddingLen,
+					},
+				},
+				flags: flags,
+			})
+		}
+	}
 }
 
 func addResumptionVersionTests() {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 6744a00..0d11f90 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -194,6 +194,7 @@
   { "-read-size", &TestConfig::read_size },
   { "-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew },
   { "-tls13-variant", &TestConfig::tls13_variant },
+  { "-dummy-pq-padding-len", &TestConfig::dummy_pq_padding_len },
 };
 
 const Flag<std::vector<int>> kIntVectorFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index a459ae5..fe107bc 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -147,6 +147,7 @@
   std::string expect_msg_callback;
   bool allow_false_start_without_alpn = false;
   bool expect_draft_downgrade = false;
+  int dummy_pq_padding_len = 0;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_initial,