Implement custom extensions. This change mirrors upstream's custom extension API because we have some internal users that depend on it. Change-Id: I408e442de0a55df7b05c872c953ff048cd406513 Reviewed-on: https://boringssl-review.googlesource.com/5471 Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index add45ae..571680b 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc
@@ -103,7 +103,7 @@ }; static void TestStateExFree(void *parent, void *ptr, CRYPTO_EX_DATA *ad, - int index, long argl, void *argp) { + int index, long argl, void *argp) { delete ((TestState *)ptr); } @@ -470,6 +470,63 @@ return 1; } +// kCustomExtensionValue is the extension value that the custom extension +// callbacks will add. +constexpr uint16_t kCustomExtensionValue = 1234; +static void *const kCustomExtensionAddArg = + reinterpret_cast<void *>(kCustomExtensionValue); +static void *const kCustomExtensionParseArg = + reinterpret_cast<void *>(kCustomExtensionValue + 1); +static const char kCustomExtensionContents[] = "custom extension"; + +static int CustomExtensionAddCallback(SSL *ssl, unsigned extension_value, + const uint8_t **out, size_t *out_len, + int *out_alert_value, void *add_arg) { + if (extension_value != kCustomExtensionValue || + add_arg != kCustomExtensionAddArg) { + abort(); + } + + if (GetConfigPtr(ssl)->custom_extension_skip) { + return 0; + } + if (GetConfigPtr(ssl)->custom_extension_fail_add) { + return -1; + } + + *out = reinterpret_cast<const uint8_t*>(kCustomExtensionContents); + *out_len = sizeof(kCustomExtensionContents) - 1; + + return 1; +} + +static void CustomExtensionFreeCallback(SSL *ssl, unsigned extension_value, + const uint8_t *out, void *add_arg) { + if (extension_value != kCustomExtensionValue || + add_arg != kCustomExtensionAddArg || + out != reinterpret_cast<const uint8_t *>(kCustomExtensionContents)) { + abort(); + } +} + +static int CustomExtensionParseCallback(SSL *ssl, unsigned extension_value, + const uint8_t *contents, + size_t contents_len, + int *out_alert_value, void *parse_arg) { + if (extension_value != kCustomExtensionValue || + parse_arg != kCustomExtensionParseArg) { + abort(); + } + + if (contents_len != sizeof(kCustomExtensionContents) - 1 || + memcmp(contents, kCustomExtensionContents, contents_len) != 0) { + *out_alert_value = SSL_AD_DECODE_ERROR; + return 0; + } + + return 1; +} + // Connect returns a new socket connected to localhost on |port| or -1 on // error. static int Connect(uint16_t port) { @@ -579,6 +636,22 @@ SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx.get(), TicketKeyCallback); } + if (config->enable_client_custom_extension && + !SSL_CTX_add_client_custom_ext( + ssl_ctx.get(), kCustomExtensionValue, CustomExtensionAddCallback, + CustomExtensionFreeCallback, kCustomExtensionAddArg, + CustomExtensionParseCallback, kCustomExtensionParseArg)) { + return nullptr; + } + + if (config->enable_server_custom_extension && + !SSL_CTX_add_server_custom_ext( + ssl_ctx.get(), kCustomExtensionValue, CustomExtensionAddCallback, + CustomExtensionFreeCallback, kCustomExtensionAddArg, + CustomExtensionParseCallback, kCustomExtensionParseArg)) { + return nullptr; + } + return ssl_ctx; }
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index fb78ef1..7b7a35b 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go
@@ -82,6 +82,7 @@ extensionSignedCertificateTimestamp uint16 = 18 extensionExtendedMasterSecret uint16 = 23 extensionSessionTicket uint16 = 35 + extensionCustom uint16 = 1234 // not IANA assigned extensionNextProtoNeg uint16 = 13172 // not IANA assigned extensionRenegotiationInfo uint16 = 0xff01 extensionChannelID uint16 = 30032 // not IANA assigned @@ -736,6 +737,14 @@ // RequireClientHelloSize, if not zero, is the required length in bytes // of the ClientHello /record/. This is checked by the server. RequireClientHelloSize int + + // CustomExtension, if not empty, contains the contents of an extension + // that will be added to client/server hellos. + CustomExtension string + + // ExpectedCustomExtension, if not nil, contains the expected contents + // of a custom extension. + ExpectedCustomExtension *string } func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index bc10fe7..a96cd9c 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go
@@ -73,6 +73,7 @@ extendedMasterSecret: c.config.maxVersion() >= VersionTLS10, srtpProtectionProfiles: c.config.SRTPProtectionProfiles, srtpMasterKeyIdentifier: c.config.Bugs.SRTPMasterKeyIdentifer, + customExtension: c.config.Bugs.CustomExtension, } if c.config.Bugs.SendClientVersion != 0 { @@ -290,6 +291,12 @@ } } + if expected := c.config.Bugs.ExpectedCustomExtension; expected != nil { + if serverHello.customExtension != *expected { + return fmt.Errorf("tls: bad custom extension contents %q", serverHello.customExtension) + } + } + hs := &clientHandshakeState{ c: c, serverHello: serverHello,
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go index 46ff2fd..92f603a 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go
@@ -32,6 +32,7 @@ srtpProtectionProfiles []uint16 srtpMasterKeyIdentifier string sctListSupported bool + customExtension string } func (m *clientHelloMsg) equal(i interface{}) bool { @@ -65,7 +66,8 @@ m.extendedMasterSecret == m1.extendedMasterSecret && eqUint16s(m.srtpProtectionProfiles, m1.srtpProtectionProfiles) && m.srtpMasterKeyIdentifier == m1.srtpMasterKeyIdentifier && - m.sctListSupported == m1.sctListSupported + m.sctListSupported == m1.sctListSupported && + m.customExtension == m1.customExtension } func (m *clientHelloMsg) marshal() []byte { @@ -138,6 +140,10 @@ if m.sctListSupported { numExtensions++ } + if l := len(m.customExtension); l > 0 { + extensionsLength += l + numExtensions++ + } if numExtensions > 0 { extensionsLength += 4 * numExtensions length += 2 + extensionsLength @@ -376,6 +382,14 @@ z[1] = byte(extensionSignedCertificateTimestamp & 0xff) z = z[4:] } + if l := len(m.customExtension); l > 0 { + z[0] = byte(extensionCustom >> 8) + z[1] = byte(extensionCustom & 0xff) + z[2] = byte(l >> 8) + z[3] = byte(l & 0xff) + copy(z[4:], []byte(m.customExtension)) + z = z[4 + l:] + } m.raw = x @@ -443,6 +457,7 @@ m.signatureAndHashes = nil m.alpnProtocols = nil m.extendedMasterSecret = false + m.customExtension = "" if len(data) == 0 { // ClientHello is optionally followed by extension data @@ -604,6 +619,8 @@ return false } m.sctListSupported = true + case extensionCustom: + m.customExtension = string(data[:length]) } data = data[length:] } @@ -632,6 +649,7 @@ srtpProtectionProfile uint16 srtpMasterKeyIdentifier string sctList []byte + customExtension string } func (m *serverHelloMsg) marshal() []byte { @@ -686,6 +704,10 @@ extensionsLength += len(m.sctList) numExtensions++ } + if l := len(m.customExtension); l > 0 { + extensionsLength += l + numExtensions++ + } if numExtensions > 0 { extensionsLength += 4 * numExtensions @@ -811,6 +833,14 @@ copy(z[4:], m.sctList) z = z[4+l:] } + if l := len(m.customExtension); l > 0 { + z[0] = byte(extensionCustom >> 8) + z[1] = byte(extensionCustom & 0xff) + z[2] = byte(l >> 8) + z[3] = byte(l & 0xff) + copy(z[4:], []byte(m.customExtension)) + z = z[4 + l:] + } m.raw = x @@ -844,6 +874,7 @@ m.alpnProtocol = "" m.alpnProtocolEmpty = false m.extendedMasterSecret = false + m.customExtension = "" if len(data) == 0 { // ServerHello is optionally followed by extension data @@ -948,6 +979,8 @@ return false } m.sctList = data[2:length] + case extensionCustom: + m.customExtension = string(data[:length]) } data = data[length:] }
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index 5d37674..7686402 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go
@@ -210,8 +210,10 @@ } c.haveVers = true - hs.hello = new(serverHelloMsg) - hs.hello.isDTLS = c.isDTLS + hs.hello = &serverHelloMsg { + isDTLS: c.isDTLS, + customExtension: config.Bugs.CustomExtension, + } supportedCurve := false preferredCurves := config.curvePreferences() @@ -340,6 +342,12 @@ hs.hello.srtpProtectionProfile = c.config.Bugs.SendSRTPProtectionProfile } + if expected := c.config.Bugs.ExpectedCustomExtension; expected != nil { + if hs.clientHello.customExtension != *expected { + return false, fmt.Errorf("tls: bad custom extension contents %q", hs.clientHello.customExtension) + } + } + _, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey) // For test purposes, check that the peer never offers a session when
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index ff10c05..d66dc74 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go
@@ -3627,6 +3627,97 @@ } } +func addCustomExtensionTests() { + expectedContents := "custom extension" + emptyString := "" + + for _, isClient := range []bool{false, true} { + suffix := "Server" + flag := "-enable-server-custom-extension" + testType := serverTest + if isClient { + suffix = "Client" + flag = "-enable-client-custom-extension" + testType = clientTest + } + + testCases = append(testCases, testCase{ + testType: testType, + name: "CustomExtensions-" + suffix, + config: Config{ + Bugs: ProtocolBugs { + CustomExtension: expectedContents, + ExpectedCustomExtension: &expectedContents, + }, + }, + flags: []string{flag}, + }) + + // If the parse callback fails, the handshake should also fail. + testCases = append(testCases, testCase{ + testType: testType, + name: "CustomExtensions-ParseError-" + suffix, + config: Config{ + Bugs: ProtocolBugs { + CustomExtension: expectedContents + "foo", + ExpectedCustomExtension: &expectedContents, + }, + }, + flags: []string{flag}, + shouldFail: true, + expectedError: ":CUSTOM_EXTENSION_ERROR:", + }) + + // If the add callback fails, the handshake should also fail. + testCases = append(testCases, testCase{ + testType: testType, + name: "CustomExtensions-FailAdd-" + suffix, + config: Config{ + Bugs: ProtocolBugs { + CustomExtension: expectedContents, + ExpectedCustomExtension: &expectedContents, + }, + }, + flags: []string{flag, "-custom-extension-fail-add"}, + shouldFail: true, + expectedError: ":CUSTOM_EXTENSION_ERROR:", + }) + + // If the add callback returns zero, no extension should be + // added. + skipCustomExtension := expectedContents + if isClient { + // For the case where the client skips sending the + // custom extension, the server must not “echo” it. + skipCustomExtension = "" + } + testCases = append(testCases, testCase{ + testType: testType, + name: "CustomExtensions-Skip-" + suffix, + config: Config{ + Bugs: ProtocolBugs { + CustomExtension: skipCustomExtension, + ExpectedCustomExtension: &emptyString, + }, + }, + flags: []string{flag, "-custom-extension-skip"}, + }) + } + + // The custom extension add callback should not be called if the client + // doesn't send the extension. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "CustomExtensions-NotCalled-Server", + config: Config{ + Bugs: ProtocolBugs { + ExpectedCustomExtension: &emptyString, + }, + }, + flags: []string{"-enable-server-custom-extension", "-custom-extension-fail-add"}, + }) +} + func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) { defer wg.Done() @@ -3723,6 +3814,7 @@ addDTLSRetransmitTests() addExportKeyingMaterialTests() addTLSUniqueTests() + addCustomExtensionTests() for _, async := range []bool{false, true} { for _, splitHandshake := range []bool{false, true} { for _, protocol := range []protocol{tls, dtls} {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index fef51ed..d873d8e 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc
@@ -84,6 +84,12 @@ { "-expect-no-session", &TestConfig::expect_no_session }, { "-use-ticket-callback", &TestConfig::use_ticket_callback }, { "-renew-ticket", &TestConfig::renew_ticket }, + { "-enable-client-custom-extension", + &TestConfig::enable_client_custom_extension }, + { "-enable-server-custom-extension", + &TestConfig::enable_server_custom_extension }, + { "-custom-extension-skip", &TestConfig::custom_extension_skip }, + { "-custom-extension-fail-add", &TestConfig::custom_extension_fail_add }, }; const Flag<std::string> kStringFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 67655f4..29a1c77 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h
@@ -82,6 +82,10 @@ bool expect_no_session = false; bool use_ticket_callback = false; bool renew_ticket = false; + bool enable_client_custom_extension = false; + bool enable_server_custom_extension = false; + bool custom_extension_skip = false; + bool custom_extension_fail_add = false; }; bool ParseConfig(int argc, char **argv, TestConfig *out_config);