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 <agl@google.com>kris/onging/CECPQ3_patch15
@@ -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) { | |||
@@ -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() { | |||
@@ -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 | |||
@@ -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 | |||
@@ -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:] | |||
} | |||
@@ -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() { | |||
@@ -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() { | |||
@@ -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]); | |||
@@ -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); | |||