Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for AES CM 256 crypto suites #282

Merged
merged 1 commit into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions key_derivation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
41 changes: 40 additions & 1 deletion key_derivation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down Expand Up @@ -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) {
Expand Down
25 changes: 18 additions & 7 deletions protection_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:
Expand Down
35 changes: 34 additions & 1 deletion srtp_cipher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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,
Expand Down
Loading