diff --git a/context.go b/context.go index 229527c..9c8c9a1 100644 --- a/context.go +++ b/context.go @@ -90,7 +90,7 @@ func CreateContext(masterKey, masterSalt []byte, profile ProtectionProfile, opts switch profile { case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: c.cipher, err = newSrtpCipherAeadAesGcm(profile, masterKey, masterSalt) - case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: c.cipher, err = newSrtpCipherAesCmHmacSha1(profile, masterKey, masterSalt) default: return nil, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, profile) diff --git a/key_derivation.go b/key_derivation.go index 05f0f29..f192faf 100644 --- a/key_derivation.go +++ b/key_derivation.go @@ -19,7 +19,6 @@ func aesCmKeyDerivation(label byte, masterKey, masterSalt []byte, indexOverKdr i // concatenation of the encryption key label 0x00 with (index DIV kdr), // - index is 'rollover count' and DIV is 'divided by' - nMasterKey := len(masterKey) nMasterSalt := len(masterSalt) prfIn := make([]byte, 16) @@ -33,11 +32,12 @@ func aesCmKeyDerivation(label byte, masterKey, masterSalt []byte, indexOverKdr i return nil, err } - out := make([]byte, ((outLen+nMasterKey)/nMasterKey)*nMasterKey) + nBlockSize := block.BlockSize() + out := make([]byte, ((outLen+nBlockSize-1)/nBlockSize)*nBlockSize) var i uint16 - for n := 0; n < outLen; n += block.BlockSize() { + for n := 0; n < outLen; n += nBlockSize { binary.BigEndian.PutUint16(prfIn[len(prfIn)-2:], i) - block.Encrypt(out[n:n+nMasterKey], prfIn) + block.Encrypt(out[n:n+nBlockSize], prfIn) i++ } return out[:outLen], nil diff --git a/key_derivation_test.go b/key_derivation_test.go index 0b7cf18..f0eeb95 100644 --- a/key_derivation_test.go +++ b/key_derivation_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidSessionKeys(t *testing.T) { +func TestValidSessionKeys_AesCm128(t *testing.T) { masterKey := []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39} masterSalt := []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6} @@ -43,6 +43,45 @@ func TestValidSessionKeys(t *testing.T) { } } +func TestValidSessionKeys_AesCm256(t *testing.T) { + masterKey := []byte{ + 0xf0, 0xf0, 0x49, 0x14, 0xb5, 0x13, 0xf2, 0x76, 0x3a, 0x1b, 0x1f, 0xa1, 0x30, 0xf1, 0x0e, 0x29, + 0x98, 0xf6, 0xf6, 0xe4, 0x3e, 0x43, 0x09, 0xd1, 0xe6, 0x22, 0xa0, 0xe3, 0x32, 0xb9, 0xf1, 0xb6, + } + masterSalt := []byte{0x3b, 0x04, 0x80, 0x3d, 0xe5, 0x1e, 0xe7, 0xc9, 0x64, 0x23, 0xab, 0x5b, 0x78, 0xd2} + + expectedSessionKey := []byte{ + 0x5b, 0xa1, 0x06, 0x4e, 0x30, 0xec, 0x51, 0x61, 0x3c, 0xad, 0x92, 0x6c, 0x5a, 0x28, 0xef, 0x73, + 0x1e, 0xc7, 0xfb, 0x39, 0x7f, 0x70, 0xa9, 0x60, 0x65, 0x3c, 0xaf, 0x06, 0x55, 0x4c, 0xd8, 0xc4, + } + expectedSessionSalt := []byte{0xfa, 0x31, 0x79, 0x16, 0x85, 0xca, 0x44, 0x4a, 0x9e, 0x07, 0xc6, 0xc6, 0x4e, 0x93} + expectedSessionAuthTag := []byte{0xfd, 0x9c, 0x32, 0xd3, 0x9e, 0xd5, 0xfb, 0xb5, 0xa9, 0xdc, 0x96, 0xb3, 0x08, 0x18, 0x45, 0x4d, 0x13, 0x13, 0xdc, 0x05} + + sessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey)) + if err != nil { + t.Errorf("generateSessionKey failed: %v", err) + } else if !bytes.Equal(sessionKey, expectedSessionKey) { + t.Errorf("Session Key % 02x does not match expected % 02x", sessionKey, expectedSessionKey) + } + + sessionSalt, err := aesCmKeyDerivation(labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt)) + if err != nil { + t.Errorf("generateSessionSalt failed: %v", err) + } else if !bytes.Equal(sessionSalt, expectedSessionSalt) { + t.Errorf("Session Salt % 02x does not match expected % 02x", sessionSalt, expectedSessionSalt) + } + + authKeyLen, err := ProtectionProfileAes256CmHmacSha1_80.AuthKeyLen() + assert.NoError(t, err) + + sessionAuthTag, err := aesCmKeyDerivation(labelSRTPAuthenticationTag, masterKey, masterSalt, 0, authKeyLen) + if err != nil { + t.Errorf("generateSessionAuthTag failed: %v", err) + } else if !bytes.Equal(sessionAuthTag, expectedSessionAuthTag) { + t.Errorf("Session Auth Tag % 02x does not match expected % 02x", sessionAuthTag, expectedSessionAuthTag) + } +} + // This test asserts that calling aesCmKeyDerivation with a non-zero indexOverKdr fails // Currently this isn't supported, but the API makes sure we can add this in the future func TestIndexOverKDR(t *testing.T) { diff --git a/protection_profile.go b/protection_profile.go index 6271407..ab9a9c9 100644 --- a/protection_profile.go +++ b/protection_profile.go @@ -10,9 +10,16 @@ type ProtectionProfile uint16 // Supported protection profiles // See https://www.iana.org/assignments/srtp-protection/srtp-protection.xhtml +// +// AES128_CM_HMAC_SHA1_80 and AES128_CM_HMAC_SHA1_32 are valid SRTP profiles, but they do not have an DTLS-SRTP Protection Profiles ID assigned +// in RFC 5764. They were in earlier draft of this RFC: https://datatracker.ietf.org/doc/html/draft-ietf-avt-dtls-srtp-03#section-4.1.2 +// Their IDs are now marked as reserved in the IANA registry. Despite this Chrome supports them: +// https://chromium.googlesource.com/chromium/deps/libsrtp/+/84122798bb16927b1e676bd4f938a6e48e5bf2fe/srtp/include/srtp.h#694 const ( ProtectionProfileAes128CmHmacSha1_80 ProtectionProfile = 0x0001 ProtectionProfileAes128CmHmacSha1_32 ProtectionProfile = 0x0002 + ProtectionProfileAes256CmHmacSha1_80 ProtectionProfile = 0x0003 + ProtectionProfileAes256CmHmacSha1_32 ProtectionProfile = 0x0004 ProtectionProfileAeadAes128Gcm ProtectionProfile = 0x0007 ProtectionProfileAeadAes256Gcm ProtectionProfile = 0x0008 ) @@ -22,7 +29,7 @@ func (p ProtectionProfile) KeyLen() (int, error) { switch p { case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAeadAes128Gcm: return 16, nil - case ProtectionProfileAeadAes256Gcm: + case ProtectionProfileAeadAes256Gcm, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: return 32, nil default: return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) @@ -32,7 +39,7 @@ func (p ProtectionProfile) KeyLen() (int, error) { // SaltLen returns length of salt key in bytes. func (p ProtectionProfile) SaltLen() (int, error) { switch p { - case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: return 14, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 12, nil @@ -44,9 +51,9 @@ func (p ProtectionProfile) SaltLen() (int, error) { // AuthTagRTPLen returns length of RTP authentication tag in bytes for AES protection profiles. For AEAD ones it returns zero. func (p ProtectionProfile) AuthTagRTPLen() (int, error) { switch p { - case ProtectionProfileAes128CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_80: return 10, nil - case ProtectionProfileAes128CmHmacSha1_32: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_32: return 4, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 0, nil @@ -58,7 +65,7 @@ func (p ProtectionProfile) AuthTagRTPLen() (int, error) { // AuthTagRTCPLen returns length of RTCP authentication tag in bytes for AES protection profiles. For AEAD ones it returns zero. func (p ProtectionProfile) AuthTagRTCPLen() (int, error) { switch p { - case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: return 10, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 0, nil @@ -70,7 +77,7 @@ func (p ProtectionProfile) AuthTagRTCPLen() (int, error) { // AEADAuthTagLen returns length of authentication tag in bytes for AEAD protection profiles. For AES ones it returns zero. func (p ProtectionProfile) AEADAuthTagLen() (int, error) { switch p { - case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: return 0, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 16, nil @@ -82,7 +89,7 @@ func (p ProtectionProfile) AEADAuthTagLen() (int, error) { // AuthKeyLen returns length of authentication key in bytes for AES protection profiles. For AEAD ones it returns zero. func (p ProtectionProfile) AuthKeyLen() (int, error) { switch p { - case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: return 20, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 0, nil @@ -98,6 +105,10 @@ func (p ProtectionProfile) String() string { return "SRTP_AES128_CM_HMAC_SHA1_80" case ProtectionProfileAes128CmHmacSha1_32: return "SRTP_AES128_CM_HMAC_SHA1_32" + case ProtectionProfileAes256CmHmacSha1_80: + return "SRTP_AES256_CM_HMAC_SHA1_80" + case ProtectionProfileAes256CmHmacSha1_32: + return "SRTP_AES256_CM_HMAC_SHA1_32" case ProtectionProfileAeadAes128Gcm: return "SRTP_AEAD_AES_128_GCM" case ProtectionProfileAeadAes256Gcm: diff --git a/srtp_cipher_test.go b/srtp_cipher_test.go index 2066c94..fa99ab7 100644 --- a/srtp_cipher_test.go +++ b/srtp_cipher_test.go @@ -56,6 +56,39 @@ func createTestCiphers() []testCipher { 0x37, 0xff, 0x64, 0xe5, 0xcb, 0xd2, }, }, + { + profile: ProtectionProfileAes256CmHmacSha1_32, + encryptedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xac, 0x3b, 0xca, 0x88, + 0x14, 0x37, 0x57, 0x83, 0x35, 0xc6, 0xd4, 0x57, + 0xf1, 0xc3, 0x6b, 0xa7, 0x3d, 0x71, 0x48, 0x63, + }, + encryptedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0x97, 0x04, 0x31, 0xdc, 0x4a, 0xe6, 0xd2, 0xaf, + 0xd6, 0x54, 0xbf, 0x90, 0xf4, 0x35, 0x44, 0x9e, + 0x80, 0x00, 0x00, 0x01, 0xbf, 0x18, 0x18, 0x2d, + 0xd1, 0x18, 0x81, 0x28, 0x78, 0xb1, + }, + }, + { + profile: ProtectionProfileAes256CmHmacSha1_80, + encryptedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xac, 0x3b, 0xca, 0x88, + 0x14, 0x37, 0x57, 0x83, 0x35, 0xc6, 0xd4, 0x57, + 0xf1, 0xc3, 0x6b, 0xa7, 0x3d, 0x71, 0x48, 0x63, + 0x90, 0x9b, 0xbf, 0x15, 0xac, 0xec, + }, + encryptedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0x97, 0x04, 0x31, 0xdc, 0x4a, 0xe6, 0xd2, 0xaf, + 0xd6, 0x54, 0xbf, 0x90, 0xf4, 0x35, 0x44, 0x9e, + 0x80, 0x00, 0x00, 0x01, 0xbf, 0x18, 0x18, 0x2d, + 0xd1, 0x18, 0x81, 0x28, 0x78, 0xb1, + }, + }, { profile: ProtectionProfileAeadAes128Gcm, encryptedRTPPacket: []byte{ @@ -104,7 +137,7 @@ func createTestCiphers() []testCipher { } masterSalt := []byte{ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, - 0xa8, 0xa9, 0xaa, 0xab, 0x0ac, 0xad, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, } decryptedRTPPacket := []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,