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/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} {