From ca6c82643ae885f94acff27ddd93bfb73fda3af5 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Sat, 15 Nov 2014 19:06:08 -0500 Subject: [PATCH] Add DTLS-SRTP tests. Just the negotiation portion as everything else is external. This feature is used in WebRTC. Change-Id: Iccc3983ea99e7d054b59010182f9a56a8099e116 Reviewed-on: https://boringssl-review.googlesource.com/2310 Reviewed-by: Adam Langley --- ssl/test/bssl_shim.cc | 6 + ssl/test/runner/common.go | 21 ++++ ssl/test/runner/conn.go | 3 + ssl/test/runner/handshake_client.go | 51 +++++--- ssl/test/runner/handshake_messages.go | 168 ++++++++++++++++++++------ ssl/test/runner/handshake_server.go | 17 +++ ssl/test/runner/runner.go | 85 ++++++++++++- ssl/test/test_config.cc | 1 + ssl/test/test_config.h | 1 + 9 files changed, 297 insertions(+), 56 deletions(-) diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index 7adf0824..cdd62ff6 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc @@ -425,6 +425,12 @@ static int do_exchange(SSL_SESSION **out_session, return 1; } } + if (!config->srtp_profiles.empty()) { + if (!SSL_set_srtp_profiles(ssl, config->srtp_profiles.c_str())) { + BIO_print_errors_fp(stdout); + return 1; + } + } BIO *bio = BIO_new_fd(fd, 1 /* take ownership */); if (bio == NULL) { diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index 9f79778f..a4bdef80 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -77,6 +77,7 @@ const ( extensionSupportedCurves uint16 = 10 extensionSupportedPoints uint16 = 11 extensionSignatureAlgorithms uint16 = 13 + extensionUseSRTP uint16 = 14 extensionALPN uint16 = 16 extensionExtendedMasterSecret uint16 = 23 extensionSessionTicket uint16 = 35 @@ -161,6 +162,12 @@ var supportedClientCertSignatureAlgorithms = []signatureAndHash{ {signatureECDSA, hashSHA256}, } +// SRTP protection profiles (See RFC 5764, section 4.1.2) +const ( + SRTP_AES128_CM_HMAC_SHA1_80 uint16 = 0x0001 + SRTP_AES128_CM_HMAC_SHA1_32 = 0x0002 +) + // ConnectionState records basic TLS details about the connection. type ConnectionState struct { Version uint16 // TLS version used by the connection (e.g. VersionTLS12) @@ -174,6 +181,7 @@ type ConnectionState struct { PeerCertificates []*x509.Certificate // certificate chain presented by remote peer VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates ChannelID *ecdsa.PublicKey // the channel ID for this connection + SRTPProtectionProfile uint16 // the negotiated DTLS-SRTP protection profile } // ClientAuthType declares the policy the server will follow for @@ -334,6 +342,10 @@ type Config struct { // with the PSK cipher suites. PreSharedKeyIdentity string + // SRTPProtectionProfiles, if not nil, is the list of SRTP + // protection profiles to offer in DTLS-SRTP. + SRTPProtectionProfiles []uint16 + // Bugs specifies optional misbehaviour to be used for testing other // implementations. Bugs ProtocolBugs @@ -520,6 +532,15 @@ type ProtocolBugs struct { // RSAServerKeyExchange, if true, causes the server to send a // ServerKeyExchange message in the plain RSA key exchange. RSAServerKeyExchange bool + + // SRTPMasterKeyIdentifer, if not empty, is the SRTP MKI value that the + // client offers when negotiating SRTP. MKI support is still missing so + // the peer must still send none. + SRTPMasterKeyIdentifer string + + // SendSRTPProtectionProfile, if non-zero, is the SRTP profile that the + // server sends in the ServerHello instead of the negotiated one. + SendSRTPProtectionProfile uint16 } func (c *Config) serverInit() { diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go index f4b4c361..94d7434d 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go @@ -56,6 +56,8 @@ type Conn struct { channelID *ecdsa.PublicKey + srtpProtectionProfile uint16 + // input/output in, out halfConn // in.Mutex < out.Mutex rawInput *block // raw input, right off the wire @@ -1184,6 +1186,7 @@ func (c *Conn) ConnectionState() ConnectionState { state.VerifiedChains = c.verifiedChains state.ServerName = c.serverName state.ChannelID = c.channelID + state.SRTPProtectionProfile = c.srtpProtectionProfile } return state diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index 702797b2..71712a90 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go @@ -56,21 +56,23 @@ func (c *Conn) clientHandshake() error { } hello := &clientHelloMsg{ - isDTLS: c.isDTLS, - vers: c.config.maxVersion(), - compressionMethods: []uint8{compressionNone}, - random: make([]byte, 32), - ocspStapling: true, - serverName: c.config.ServerName, - supportedCurves: c.config.curvePreferences(), - supportedPoints: []uint8{pointFormatUncompressed}, - nextProtoNeg: len(c.config.NextProtos) > 0, - secureRenegotiation: []byte{}, - alpnProtocols: c.config.NextProtos, - duplicateExtension: c.config.Bugs.DuplicateExtension, - channelIDSupported: c.config.ChannelID != nil, - npnLast: c.config.Bugs.SwapNPNAndALPN, - extendedMasterSecret: c.config.maxVersion() >= VersionTLS10, + isDTLS: c.isDTLS, + vers: c.config.maxVersion(), + compressionMethods: []uint8{compressionNone}, + random: make([]byte, 32), + ocspStapling: true, + serverName: c.config.ServerName, + supportedCurves: c.config.curvePreferences(), + supportedPoints: []uint8{pointFormatUncompressed}, + nextProtoNeg: len(c.config.NextProtos) > 0, + secureRenegotiation: []byte{}, + alpnProtocols: c.config.NextProtos, + duplicateExtension: c.config.Bugs.DuplicateExtension, + channelIDSupported: c.config.ChannelID != nil, + npnLast: c.config.Bugs.SwapNPNAndALPN, + extendedMasterSecret: c.config.maxVersion() >= VersionTLS10, + srtpProtectionProfiles: c.config.SRTPProtectionProfiles, + srtpMasterKeyIdentifier: c.config.Bugs.SRTPMasterKeyIdentifer, } if c.config.Bugs.SendClientVersion != 0 { @@ -666,6 +668,25 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { return false, errors.New("server advertised unrequested Channel ID extension") } + if hs.serverHello.srtpProtectionProfile != 0 { + if hs.serverHello.srtpMasterKeyIdentifier != "" { + return false, errors.New("tls: server selected SRTP MKI value") + } + + found := false + for _, p := range c.config.SRTPProtectionProfiles { + if p == hs.serverHello.srtpProtectionProfile { + found = true + break + } + } + if !found { + return false, errors.New("tls: server advertised unsupported SRTP profile") + } + + c.srtpProtectionProfile = hs.serverHello.srtpProtectionProfile + } + if hs.serverResumedSession() { // Restore masterSecret and peerCerts from previous state hs.masterSecret = hs.session.masterSecret diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go index 12a9f3d5..cb3b5c42 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go @@ -7,28 +7,30 @@ package main import "bytes" type clientHelloMsg struct { - raw []byte - isDTLS bool - vers uint16 - random []byte - sessionId []byte - cookie []byte - cipherSuites []uint16 - compressionMethods []uint8 - nextProtoNeg bool - serverName string - ocspStapling bool - supportedCurves []CurveID - supportedPoints []uint8 - ticketSupported bool - sessionTicket []uint8 - signatureAndHashes []signatureAndHash - secureRenegotiation []byte - alpnProtocols []string - duplicateExtension bool - channelIDSupported bool - npnLast bool - extendedMasterSecret bool + raw []byte + isDTLS bool + vers uint16 + random []byte + sessionId []byte + cookie []byte + cipherSuites []uint16 + compressionMethods []uint8 + nextProtoNeg bool + serverName string + ocspStapling bool + supportedCurves []CurveID + supportedPoints []uint8 + ticketSupported bool + sessionTicket []uint8 + signatureAndHashes []signatureAndHash + secureRenegotiation []byte + alpnProtocols []string + duplicateExtension bool + channelIDSupported bool + npnLast bool + extendedMasterSecret bool + srtpProtectionProfiles []uint16 + srtpMasterKeyIdentifier string } func (m *clientHelloMsg) equal(i interface{}) bool { @@ -59,7 +61,9 @@ func (m *clientHelloMsg) equal(i interface{}) bool { m.duplicateExtension == m1.duplicateExtension && m.channelIDSupported == m1.channelIDSupported && m.npnLast == m1.npnLast && - m.extendedMasterSecret == m1.extendedMasterSecret + m.extendedMasterSecret == m1.extendedMasterSecret && + eqUint16s(m.srtpProtectionProfiles, m1.srtpProtectionProfiles) && + m.srtpMasterKeyIdentifier == m1.srtpMasterKeyIdentifier } func (m *clientHelloMsg) marshal() []byte { @@ -124,6 +128,11 @@ func (m *clientHelloMsg) marshal() []byte { if m.extendedMasterSecret { numExtensions++ } + if len(m.srtpProtectionProfiles) > 0 { + extensionsLength += 2 + 2*len(m.srtpProtectionProfiles) + extensionsLength += 1 + len(m.srtpMasterKeyIdentifier) + numExtensions++ + } if numExtensions > 0 { extensionsLength += 4 * numExtensions length += 2 + extensionsLength @@ -334,6 +343,29 @@ func (m *clientHelloMsg) marshal() []byte { z[1] = byte(extensionExtendedMasterSecret & 0xff) z = z[4:] } + if len(m.srtpProtectionProfiles) > 0 { + z[0] = byte(extensionUseSRTP >> 8) + z[1] = byte(extensionUseSRTP & 0xff) + + profilesLen := 2 * len(m.srtpProtectionProfiles) + mkiLen := len(m.srtpMasterKeyIdentifier) + l := 2 + profilesLen + 1 + mkiLen + z[2] = byte(l >> 8) + z[3] = byte(l & 0xff) + + z[4] = byte(profilesLen >> 8) + z[5] = byte(profilesLen & 0xff) + z = z[6:] + for _, p := range m.srtpProtectionProfiles { + z[0] = byte(p >> 8) + z[1] = byte(p & 0xff) + z = z[2:] + } + + z[0] = byte(mkiLen) + copy(z[1:], []byte(m.srtpMasterKeyIdentifier)) + z = z[1+mkiLen:] + } m.raw = x @@ -538,6 +570,25 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { return false } m.extendedMasterSecret = true + case extensionUseSRTP: + if length < 2 { + return false + } + l := int(data[0])<<8 | int(data[1]) + if l > length-2 || l%2 != 0 { + return false + } + n := l / 2 + m.srtpProtectionProfiles = make([]uint16, n) + d := data[2:length] + for i := 0; i < n; i++ { + m.srtpProtectionProfiles[i] = uint16(d[0])<<8 | uint16(d[1]) + d = d[2:] + } + if len(d) < 1 || int(d[0]) != len(d)-1 { + return false + } + m.srtpMasterKeyIdentifier = string(d[1:]) } data = data[length:] } @@ -546,22 +597,24 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { } type serverHelloMsg struct { - raw []byte - isDTLS bool - vers uint16 - random []byte - sessionId []byte - cipherSuite uint16 - compressionMethod uint8 - nextProtoNeg bool - nextProtos []string - ocspStapling bool - ticketSupported bool - secureRenegotiation []byte - alpnProtocol string - duplicateExtension bool - channelIDRequested bool - extendedMasterSecret bool + raw []byte + isDTLS bool + vers uint16 + random []byte + sessionId []byte + cipherSuite uint16 + compressionMethod uint8 + nextProtoNeg bool + nextProtos []string + ocspStapling bool + ticketSupported bool + secureRenegotiation []byte + alpnProtocol string + duplicateExtension bool + channelIDRequested bool + extendedMasterSecret bool + srtpProtectionProfile uint16 + srtpMasterKeyIdentifier string } func (m *serverHelloMsg) equal(i interface{}) bool { @@ -586,7 +639,9 @@ func (m *serverHelloMsg) equal(i interface{}) bool { m.alpnProtocol == m1.alpnProtocol && m.duplicateExtension == m1.duplicateExtension && m.channelIDRequested == m1.channelIDRequested && - m.extendedMasterSecret == m1.extendedMasterSecret + m.extendedMasterSecret == m1.extendedMasterSecret && + m.srtpProtectionProfile == m1.srtpProtectionProfile && + m.srtpMasterKeyIdentifier == m1.srtpMasterKeyIdentifier } func (m *serverHelloMsg) marshal() []byte { @@ -633,6 +688,10 @@ func (m *serverHelloMsg) marshal() []byte { if m.extendedMasterSecret { numExtensions++ } + if m.srtpProtectionProfile != 0 { + extensionsLength += 2 + 2 + 1 + len(m.srtpMasterKeyIdentifier) + numExtensions++ + } if numExtensions > 0 { extensionsLength += 4 * numExtensions @@ -734,6 +793,21 @@ func (m *serverHelloMsg) marshal() []byte { z[1] = byte(extensionExtendedMasterSecret & 0xff) z = z[4:] } + if m.srtpProtectionProfile != 0 { + z[0] = byte(extensionUseSRTP >> 8) + z[1] = byte(extensionUseSRTP & 0xff) + l := 2 + 2 + 1 + len(m.srtpMasterKeyIdentifier) + z[2] = byte(l >> 8) + z[3] = byte(l & 0xff) + z[4] = 0 + z[5] = 2 + z[6] = byte(m.srtpProtectionProfile >> 8) + z[7] = byte(m.srtpProtectionProfile & 0xff) + l = len(m.srtpMasterKeyIdentifier) + z[8] = byte(l) + copy(z[9:], []byte(m.srtpMasterKeyIdentifier)) + z = z[9+l:] + } m.raw = x @@ -846,6 +920,20 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool { return false } m.extendedMasterSecret = true + case extensionUseSRTP: + if length < 2+2+1 { + return false + } + if data[0] != 0 || data[1] != 2 { + return false + } + m.srtpProtectionProfile = uint16(data[2])<<8 | uint16(data[3]) + d := data[4:length] + l := int(d[0]) + if l != len(d)-1 { + return false + } + m.srtpMasterKeyIdentifier = string(d[1:]) } data = data[length:] } diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index 89c7b8d6..bd6f7022 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go @@ -270,6 +270,23 @@ Curves: hs.hello.channelIDRequested = true } + if hs.clientHello.srtpProtectionProfiles != nil { + SRTPLoop: + for _, p1 := range c.config.SRTPProtectionProfiles { + for _, p2 := range hs.clientHello.srtpProtectionProfiles { + if p1 == p2 { + hs.hello.srtpProtectionProfile = p1 + c.srtpProtectionProfile = p1 + break SRTPLoop + } + } + } + } + + if c.config.Bugs.SendSRTPProtectionProfile != 0 { + hs.hello.srtpProtectionProfile = c.config.Bugs.SendSRTPProtectionProfile + } + _, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey) if hs.checkForResumption() { diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index a3026877..8c661a62 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -129,6 +129,9 @@ type testCase struct { // expectedNextProtoType, if non-zero, is the expected next // protocol negotiation mechanism. expectedNextProtoType int + // expectedSRTPProtectionProfile is the DTLS-SRTP profile that + // should be negotiated. If zero, none should be negotiated. + expectedSRTPProtectionProfile uint16 // messageLen is the length, in bytes, of the test message that will be // sent. messageLen int @@ -357,7 +360,7 @@ var testCases = []testCase{ name: "FragmentAlert", config: Config{ Bugs: ProtocolBugs{ - FragmentAlert: true, + FragmentAlert: true, SendSpuriousAlert: true, }, }, @@ -589,6 +592,10 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, i } } + if p := tlsConn.ConnectionState().SRTPProtectionProfile; p != test.expectedSRTPProtectionProfile { + return fmt.Errorf("SRTP profile mismatch: got %d, wanted %d", p, test.expectedSRTPProtectionProfile) + } + if test.shimWritesFirst { var buf [5]byte _, err := io.ReadFull(tlsConn, buf[:]) @@ -1741,6 +1748,82 @@ func addExtensionTests() { shouldFail: true, expectedError: ":DECODE_ERROR:", }) + // Basic DTLS-SRTP tests. Include fake profiles to ensure they + // are ignored. + testCases = append(testCases, testCase{ + protocol: dtls, + name: "SRTP-Client", + config: Config{ + SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42}, + }, + flags: []string{ + "-srtp-profiles", + "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", + }, + expectedSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80, + }) + testCases = append(testCases, testCase{ + protocol: dtls, + testType: serverTest, + name: "SRTP-Server", + config: Config{ + SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42}, + }, + flags: []string{ + "-srtp-profiles", + "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", + }, + expectedSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80, + }) + // Test that the MKI is ignored. + testCases = append(testCases, testCase{ + protocol: dtls, + testType: serverTest, + name: "SRTP-Server-IgnoreMKI", + config: Config{ + SRTPProtectionProfiles: []uint16{SRTP_AES128_CM_HMAC_SHA1_80}, + Bugs: ProtocolBugs{ + SRTPMasterKeyIdentifer: "bogus", + }, + }, + flags: []string{ + "-srtp-profiles", + "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", + }, + expectedSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80, + }) + // Test that SRTP isn't negotiated on the server if there were + // no matching profiles. + testCases = append(testCases, testCase{ + protocol: dtls, + testType: serverTest, + name: "SRTP-Server-NoMatch", + config: Config{ + SRTPProtectionProfiles: []uint16{100, 101, 102}, + }, + flags: []string{ + "-srtp-profiles", + "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", + }, + expectedSRTPProtectionProfile: 0, + }) + // Test that the server returning an invalid SRTP profile is + // flagged as an error by the client. + testCases = append(testCases, testCase{ + protocol: dtls, + name: "SRTP-Client-NoMatch", + config: Config{ + Bugs: ProtocolBugs{ + SendSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_32, + }, + }, + flags: []string{ + "-srtp-profiles", + "SRTP_AES128_CM_SHA1_80", + }, + shouldFail: true, + expectedError: ":BAD_SRTP_PROTECTION_PROFILE_LIST:", + }) } func addResumptionVersionTests() { diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index ebd5e4ee..5d0119d5 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -81,6 +81,7 @@ const StringFlag kStringFlags[] = { { "-select-alpn", &TestConfig::select_alpn }, { "-psk", &TestConfig::psk }, { "-psk-identity", &TestConfig::psk_identity }, + { "-srtp-profiles", &TestConfig::srtp_profiles }, }; const size_t kNumStringFlags = sizeof(kStringFlags) / sizeof(kStringFlags[0]); diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 7de8b3fd..07f0897a 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h @@ -58,6 +58,7 @@ struct TestConfig { std::string psk_identity; bool renegotiate; bool allow_unsafe_legacy_renegotiation; + std::string srtp_profiles; }; bool ParseConfig(int argc, char **argv, TestConfig *out_config);