diff --git a/context.go b/context.go index 2f7afe5..cb2ed56 100644 --- a/context.go +++ b/context.go @@ -62,6 +62,9 @@ type Context struct { sendMKI []byte // Master Key Identifier used for encrypting RTP/RTCP packets. Set to nil if MKI is not enabled. mkis map[string]srtpCipher // Master Key Identifier to cipher mapping. Used for decrypting packets. Empty if MKI is not enabled. + + encryptSRTP bool + encryptSRTCP bool } // CreateContext creates a new SRTP Context. @@ -83,6 +86,8 @@ func CreateContext(masterKey, masterSalt []byte, profile ProtectionProfile, opts []ContextOption{ // Default options SRTPNoReplayProtection(), SRTCPNoReplayProtection(), + SRTPEncryption(), + SRTCPEncryption(), }, opts..., // User specified options ) { @@ -91,7 +96,7 @@ func CreateContext(masterKey, masterSalt []byte, profile ProtectionProfile, opts } } - c.cipher, err = c.createCipher(c.sendMKI, masterKey, masterSalt) + c.cipher, err = c.createCipher(c.sendMKI, masterKey, masterSalt, c.encryptSRTP, c.encryptSRTCP) if err != nil { return nil, err } @@ -116,7 +121,7 @@ func (c *Context) AddCipherForMKI(mki, masterKey, masterSalt []byte) error { return errMKIAlreadyInUse } - cipher, err := c.createCipher(mki, masterKey, masterSalt) + cipher, err := c.createCipher(mki, masterKey, masterSalt, c.encryptSRTP, c.encryptSRTCP) if err != nil { return err } @@ -124,7 +129,7 @@ func (c *Context) AddCipherForMKI(mki, masterKey, masterSalt []byte) error { return nil } -func (c *Context) createCipher(mki, masterKey, masterSalt []byte) (srtpCipher, error) { +func (c *Context) createCipher(mki, masterKey, masterSalt []byte, encryptSRTP, encryptSRTCP bool) (srtpCipher, error) { keyLen, err := c.profile.KeyLen() if err != nil { return nil, err @@ -143,9 +148,11 @@ func (c *Context) createCipher(mki, masterKey, masterSalt []byte) (srtpCipher, e switch c.profile { case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: - return newSrtpCipherAeadAesGcm(c.profile, masterKey, masterSalt, mki) + return newSrtpCipherAeadAesGcm(c.profile, masterKey, masterSalt, mki, encryptSRTP, encryptSRTCP) case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: - return newSrtpCipherAesCmHmacSha1(c.profile, masterKey, masterSalt, mki) + return newSrtpCipherAesCmHmacSha1(c.profile, masterKey, masterSalt, mki, encryptSRTP, encryptSRTCP) + case ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: + return newSrtpCipherAesCmHmacSha1(c.profile, masterKey, masterSalt, mki, false, false) default: return nil, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, c.profile) } diff --git a/option.go b/option.go index 4d30cd5..dac0bcf 100644 --- a/option.go +++ b/option.go @@ -84,3 +84,37 @@ func MasterKeyIndicator(mki []byte) ContextOption { return nil } } + +// SRTPEncryption enables SRTP encryption. +func SRTPEncryption() ContextOption { // nolint:revive + return func(c *Context) error { + c.encryptSRTP = true + return nil + } +} + +// SRTPNoEncryption disables SRTP encryption. This option is useful when you want to use NullCipher for SRTP and keep authentication only. +// It simplifies debugging and testing, but it is not recommended for production use. +func SRTPNoEncryption() ContextOption { // nolint:revive + return func(c *Context) error { + c.encryptSRTP = false + return nil + } +} + +// SRTCPEncryption enables SRTCP encryption. +func SRTCPEncryption() ContextOption { + return func(c *Context) error { + c.encryptSRTCP = true + return nil + } +} + +// SRTCPNoEncryption disables SRTCP encryption. This option is useful when you want to use NullCipher for SRTCP and keep authentication only. +// It simplifies debugging and testing, but it is not recommended for production use. +func SRTCPNoEncryption() ContextOption { + return func(c *Context) error { + c.encryptSRTCP = false + return nil + } +} diff --git a/protection_profile.go b/protection_profile.go index ab9a9c9..9384bf8 100644 --- a/protection_profile.go +++ b/protection_profile.go @@ -15,19 +15,24 @@ type ProtectionProfile uint16 // 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 +// +// Null profiles disable encryption, they are used for debugging and testing. They are not recommended for production use. +// Use of them is equivalent to using ProtectionProfileAes128CmHmacSha1_NN profile with SRTPNoEncryption and SRTCPNoEncryption options. const ( ProtectionProfileAes128CmHmacSha1_80 ProtectionProfile = 0x0001 ProtectionProfileAes128CmHmacSha1_32 ProtectionProfile = 0x0002 ProtectionProfileAes256CmHmacSha1_80 ProtectionProfile = 0x0003 ProtectionProfileAes256CmHmacSha1_32 ProtectionProfile = 0x0004 + ProtectionProfileNullHmacSha1_80 ProtectionProfile = 0x0005 + ProtectionProfileNullHmacSha1_32 ProtectionProfile = 0x0006 ProtectionProfileAeadAes128Gcm ProtectionProfile = 0x0007 ProtectionProfileAeadAes256Gcm ProtectionProfile = 0x0008 ) -// KeyLen returns length of encryption key in bytes. +// KeyLen returns length of encryption key in bytes. For all profiles except NullHmacSha1_32 and NullHmacSha1_80 is is also the length of the session key. func (p ProtectionProfile) KeyLen() (int, error) { switch p { - case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAeadAes128Gcm: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAeadAes128Gcm, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 16, nil case ProtectionProfileAeadAes256Gcm, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: return 32, nil @@ -36,10 +41,10 @@ func (p ProtectionProfile) KeyLen() (int, error) { } } -// SaltLen returns length of salt key in bytes. +// SaltLen returns length of salt key in bytes. For all profiles except NullHmacSha1_32 and NullHmacSha1_80 is is also the length of the session salt. func (p ProtectionProfile) SaltLen() (int, error) { switch p { - case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 14, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 12, nil @@ -51,9 +56,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, ProtectionProfileAes256CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_80: return 10, nil - case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_32: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileNullHmacSha1_32: return 4, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 0, nil @@ -65,7 +70,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, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 10, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 0, nil @@ -77,7 +82,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, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 0, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 16, nil @@ -89,7 +94,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, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: + case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 20, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 0, nil @@ -113,6 +118,10 @@ func (p ProtectionProfile) String() string { return "SRTP_AEAD_AES_128_GCM" case ProtectionProfileAeadAes256Gcm: return "SRTP_AEAD_AES_256_GCM" + case ProtectionProfileNullHmacSha1_80: + return "SRTP_NULL_HMAC_SHA1_80" + case ProtectionProfileNullHmacSha1_32: + return "SRTP_NULL_HMAC_SHA1_32" default: return fmt.Sprintf("Unknown SRTP profile: %#v", p) } diff --git a/srtcp.go b/srtcp.go index 1359fcd..6d1a1c1 100644 --- a/srtcp.go +++ b/srtcp.go @@ -12,9 +12,9 @@ import ( const maxSRTCPIndex = 0x7FFFFFFF -func (c *Context) decryptRTCP(dst, encrypted []byte) ([]byte, error) { - out := allocateIfMismatch(dst, encrypted) +const srtcpHeaderSize = 8 +func (c *Context) decryptRTCP(dst, encrypted []byte) ([]byte, error) { authTagLen, err := c.cipher.AuthTagRTCPLen() if err != nil { return nil, err @@ -24,12 +24,10 @@ func (c *Context) decryptRTCP(dst, encrypted []byte) ([]byte, error) { return nil, err } mkiLen := len(c.sendMKI) - tailOffset := len(encrypted) - (authTagLen + mkiLen + srtcpIndexSize) - if tailOffset < aeadAuthTagLen { + // Verify that encrypted packet is long enough + if len(encrypted) < (srtcpHeaderSize + aeadAuthTagLen + srtcpIndexSize + mkiLen + authTagLen) { return nil, fmt.Errorf("%w: %d", errTooShortRTCP, len(encrypted)) - } else if isEncrypted := encrypted[tailOffset] >> 7; isEncrypted == 0 { - return out, nil } index := c.cipher.getRTCPIndex(encrypted) @@ -51,6 +49,8 @@ func (c *Context) decryptRTCP(dst, encrypted []byte) ([]byte, error) { } } + out := allocateIfMismatch(dst, encrypted) + out, err = cipher.decryptRTCP(out, encrypted, index, ssrc) if err != nil { return nil, err @@ -74,7 +74,7 @@ func (c *Context) DecryptRTCP(dst, encrypted []byte, header *rtcp.Header) ([]byt } func (c *Context) encryptRTCP(dst, decrypted []byte) ([]byte, error) { - if len(decrypted) < 8 { + if len(decrypted) < srtcpHeaderSize { return nil, fmt.Errorf("%w: %d", errTooShortRTCP, len(decrypted)) } diff --git a/srtp.go b/srtp.go index ca7c3fc..56828bc 100644 --- a/srtp.go +++ b/srtp.go @@ -5,6 +5,8 @@ package srtp import ( + "fmt" + "github.com/pion/rtp" ) @@ -13,9 +15,15 @@ func (c *Context) decryptRTP(dst, ciphertext []byte, header *rtp.Header, headerL if err != nil { return nil, err } + aeadAuthTagLen, err := c.cipher.AEADAuthTagLen() + if err != nil { + return nil, err + } + mkiLen := len(c.sendMKI) - if len(ciphertext) < headerLen+len(c.sendMKI)+authTagLen { - return nil, errTooShortRTP + // Verify that encrypted packet is long enough + if len(ciphertext) < (headerLen + aeadAuthTagLen + mkiLen + authTagLen) { + return nil, fmt.Errorf("%w: %d", errTooShortRTP, len(ciphertext)) } s := c.getSRTPSSRCState(header.SSRC) diff --git a/srtp_cipher_aead_aes_gcm.go b/srtp_cipher_aead_aes_gcm.go index e59c141..64f890f 100644 --- a/srtp_cipher_aead_aes_gcm.go +++ b/srtp_cipher_aead_aes_gcm.go @@ -7,6 +7,7 @@ import ( "crypto/aes" "crypto/cipher" "encoding/binary" + "fmt" "github.com/pion/rtp" ) @@ -23,10 +24,16 @@ type srtpCipherAeadAesGcm struct { srtpSessionSalt, srtcpSessionSalt []byte mki []byte + + srtpEncrypted, srtcpEncrypted bool } -func newSrtpCipherAeadAesGcm(profile ProtectionProfile, masterKey, masterSalt, mki []byte) (*srtpCipherAeadAesGcm, error) { - s := &srtpCipherAeadAesGcm{ProtectionProfile: profile} +func newSrtpCipherAeadAesGcm(profile ProtectionProfile, masterKey, masterSalt, mki []byte, encryptSRTP, encryptSRTCP bool) (*srtpCipherAeadAesGcm, error) { + s := &srtpCipherAeadAesGcm{ + ProtectionProfile: profile, + srtpEncrypted: encryptSRTP, + srtcpEncrypted: encryptSRTCP, + } srtpSessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey)) if err != nil { @@ -87,7 +94,13 @@ func (s *srtpCipherAeadAesGcm) encryptRTP(dst []byte, header *rtp.Header, payloa } iv := s.rtpInitializationVector(header, roc) - s.srtpCipher.Seal(dst[n:n], iv[:], payload, dst[:n]) + if s.srtpEncrypted { + s.srtpCipher.Seal(dst[n:n], iv[:], payload, dst[:n]) + } else { + clearLen := n + len(payload) + copy(dst[n:], payload) + s.srtpCipher.Seal(dst[clearLen:clearLen], iv[:], nil, dst[:clearLen]) + } // Add MKI after the encrypted payload if len(s.mki) > 0 { @@ -113,10 +126,20 @@ func (s *srtpCipherAeadAesGcm) decryptRTP(dst, ciphertext []byte, header *rtp.He iv := s.rtpInitializationVector(header, roc) nEnd := len(ciphertext) - len(s.mki) - if _, err := s.srtpCipher.Open( - dst[headerLen:headerLen], iv[:], ciphertext[headerLen:nEnd], ciphertext[:headerLen], - ); err != nil { - return nil, err + if s.srtpEncrypted { + if _, err := s.srtpCipher.Open( + dst[headerLen:headerLen], iv[:], ciphertext[headerLen:nEnd], ciphertext[:headerLen], + ); err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedToVerifyAuthTag, err) + } + } else { + nDataEnd := nEnd - authTagLen + if _, err := s.srtpCipher.Open( + nil, iv[:], ciphertext[nDataEnd:nEnd], ciphertext[:nDataEnd], + ); err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedToVerifyAuthTag, err) + } + copy(dst[headerLen:], ciphertext[headerLen:nDataEnd]) } copy(dst[:headerLen], ciphertext[:headerLen]) @@ -133,12 +156,25 @@ func (s *srtpCipherAeadAesGcm) encryptRTCP(dst, decrypted []byte, srtcpIndex uin dst = growBufferSize(dst, aadPos+srtcpIndexSize+len(s.mki)) iv := s.rtcpInitializationVector(srtcpIndex, ssrc) - aad := s.rtcpAdditionalAuthenticatedData(decrypted, srtcpIndex) - - s.srtcpCipher.Seal(dst[8:8], iv[:], decrypted[8:], aad[:]) + if s.srtcpEncrypted { + aad := s.rtcpAdditionalAuthenticatedData(decrypted, srtcpIndex) + copy(dst[:8], decrypted[:8]) + copy(dst[aadPos:aadPos+4], aad[8:12]) + s.srtcpCipher.Seal(dst[8:8], iv[:], decrypted[8:], aad[:]) + } else { + // Copy the packet unencrypted. + copy(dst, decrypted) + // Append the SRTCP index to the end of the packet - this will form the AAD. + binary.BigEndian.PutUint32(dst[len(decrypted):], srtcpIndex) + // Generate the authentication tag. + tag := make([]byte, authTagLen) + s.srtcpCipher.Seal(tag[0:0], iv[:], nil, dst[:len(decrypted)+4]) + // Copy index to the proper place. + copy(dst[aadPos:], dst[len(decrypted):len(decrypted)+4]) + // Copy the auth tag after RTCP payload. + copy(dst[len(decrypted):], tag) + } - copy(dst[:8], decrypted[:8]) - copy(dst[aadPos:aadPos+4], aad[8:12]) copy(dst[aadPos+4:], s.mki) return dst, nil } @@ -157,11 +193,25 @@ func (s *srtpCipherAeadAesGcm) decryptRTCP(dst, encrypted []byte, srtcpIndex, ss } dst = growBufferSize(dst, nDst) + isEncrypted := encrypted[aadPos]>>7 != 0 iv := s.rtcpInitializationVector(srtcpIndex, ssrc) - aad := s.rtcpAdditionalAuthenticatedData(encrypted, srtcpIndex) - - if _, err := s.srtcpCipher.Open(dst[8:8], iv[:], encrypted[8:aadPos], aad[:]); err != nil { - return nil, err + if isEncrypted { + aad := s.rtcpAdditionalAuthenticatedData(encrypted, srtcpIndex) + if _, err := s.srtcpCipher.Open(dst[8:8], iv[:], encrypted[8:aadPos], aad[:]); err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedToVerifyAuthTag, err) + } + } else { + // Prepare AAD for received packet. + dataEnd := aadPos - authTagLen + aad := make([]byte, dataEnd+4) + copy(aad, encrypted[:dataEnd]) + copy(aad[dataEnd:], encrypted[aadPos:aadPos+4]) + // Verify the auth tag. + if _, err := s.srtcpCipher.Open(nil, iv[:], encrypted[dataEnd:aadPos], aad); err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedToVerifyAuthTag, err) + } + // Copy the unencrypted payload. + copy(dst[8:], encrypted[8:dataEnd]) } copy(dst[:8], encrypted[:8]) diff --git a/srtp_cipher_aead_aes_gcm_rfc_test.go b/srtp_cipher_aead_aes_gcm_rfc_test.go new file mode 100644 index 0000000..9e50bde --- /dev/null +++ b/srtp_cipher_aead_aes_gcm_rfc_test.go @@ -0,0 +1,227 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package srtp + +import ( + "encoding/hex" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func fromHex(s string) []byte { + s = strings.ReplaceAll(s, " ", "") + s = strings.ReplaceAll(s, "\n", "") + s = strings.ReplaceAll(s, "\t", "") + s = strings.ReplaceAll(s, "\r", "") + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +type testRfcAeadCipher struct { + profile ProtectionProfile // Protection profile + keys derivedSessionKeys // Derived session keys + + decryptedRTPPacket []byte + encryptedRTPPacket []byte + authenticatedRTPPacket []byte + + decryptedRTCPPacket []byte + encryptedRTCPPacket []byte + authenticatedRTCPPacket []byte +} + +// createRfcAeadTestCiphers returns a list of test ciphers for the RFC test vectors +func createRfcAeadTestCiphers() []testRfcAeadCipher { + tests := []testRfcAeadCipher{} + + // AES-128-GCM, RFC 7714, Sections 16 and 17 + aes128Gcm := testRfcAeadCipher{ + profile: ProtectionProfileAeadAes128Gcm, + keys: derivedSessionKeys{ + srtpSessionKey: fromHex(`00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f`), + srtpSessionSalt: fromHex(`51 75 69 64 20 70 72 6f 20 71 75 6f`), + }, + decryptedRTPPacket: fromHex(`8040f17b 8041f8d3 5501a0b2 47616c6c + 69612065 7374206f 6d6e6973 20646976 + 69736120 696e2070 61727465 73207472 + 6573`), + encryptedRTPPacket: fromHex(`8040f17b 8041f8d3 5501a0b2 f24de3a3 + fb34de6c acba861c 9d7e4bca be633bd5 + 0d294e6f 42a5f47a 51c7d19b 36de3adf + 8833899d 7f27beb1 6a9152cf 765ee439 + 0cce`), + authenticatedRTPPacket: fromHex(`8040f17b 8041f8d3 5501a0b2 47616c6c + 69612065 7374206f 6d6e6973 20646976 + 69736120 696e2070 61727465 73207472 + 65732249 3f82d2bc e397e9d7 9e3b19aa + 4216`), + decryptedRTCPPacket: fromHex(`81c8000d 4d617273 4e545031 4e545032 + 52545020 0000042a 0000e930 4c756e61 + deadbeef deadbeef deadbeef deadbeef + deadbeef`), + encryptedRTCPPacket: fromHex(`81c8000d 4d617273 63e94885 dcdab67c + a727d766 2f6b7e99 7ff5c0f7 6c06f32d + c676a5f1 730d6fda 4ce09b46 86303ded + 0bb9275b c84aa458 96cf4d2f c5abf872 + 45d9eade 800005d4`), + authenticatedRTCPPacket: fromHex(`81c8000d 4d617273 4e545031 4e545032 + 52545020 0000042a 0000e930 4c756e61 + deadbeef deadbeef deadbeef deadbeef + deadbeef 841dd968 3dd78ec9 2ae58790 + 125f62b3 000005d4`), + } + aes128Gcm.keys.srtcpSessionKey = aes128Gcm.keys.srtpSessionKey + aes128Gcm.keys.srtcpSessionSalt = aes128Gcm.keys.srtpSessionSalt + tests = append(tests, aes128Gcm) + + // AES-256-GCM, RFC 7714, Sections 16 and 17 + aes256Gcm := testRfcAeadCipher{ + profile: ProtectionProfileAeadAes256Gcm, + keys: derivedSessionKeys{ + srtpSessionKey: fromHex(`00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f + 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f`), + srtpSessionSalt: fromHex(`51 75 69 64 20 70 72 6f 20 71 75 6f`), + }, + decryptedRTPPacket: fromHex(`8040f17b 8041f8d3 5501a0b2 47616c6c + 69612065 7374206f 6d6e6973 20646976 + 69736120 696e2070 61727465 73207472 + 6573`), + encryptedRTPPacket: fromHex(`8040f17b 8041f8d3 5501a0b2 32b1de78 + a822fe12 ef9f78fa 332e33aa b1801238 + 9a58e2f3 b50b2a02 76ffae0f 1ba63799 + b87b7aa3 db36dfff d6b0f9bb 7878d7a7 + 6c13`), + authenticatedRTPPacket: fromHex(`8040f17b 8041f8d3 5501a0b2 47616c6c + 69612065 7374206f 6d6e6973 20646976 + 69736120 696e2070 61727465 73207472 + 6573a866 d5910f88 7463067c eefec452 + 15d4`), + decryptedRTCPPacket: fromHex(`81c8000d 4d617273 4e545031 4e545032 + 52545020 0000042a 0000e930 4c756e61 + deadbeef deadbeef deadbeef deadbeef + deadbeef`), + encryptedRTCPPacket: fromHex(`81c8000d 4d617273 d50ae4d1 f5ce5d30 + 4ba297e4 7d470c28 2c3ece5d bffe0a50 + a2eaa5c1 110555be 8415f658 c61de047 + 6f1b6fad 1d1eb30c 4446839f 57ff6f6c + b26ac3be 800005d4`), + authenticatedRTCPPacket: fromHex(`81c8000d 4d617273 4e545031 4e545032 + 52545020 0000042a 0000e930 4c756e61 + deadbeef deadbeef deadbeef deadbeef + deadbeef 91db4afb feee5a97 8fab4393 + ed2615fe 000005d4`), + } + aes256Gcm.keys.srtcpSessionKey = aes256Gcm.keys.srtpSessionKey + aes256Gcm.keys.srtcpSessionSalt = aes256Gcm.keys.srtpSessionSalt + tests = append(tests, aes256Gcm) + + return tests +} + +func TestAeadCiphersWithRfcTestVectors(t *testing.T) { + for _, c := range createRfcAeadTestCiphers() { + t.Run(c.profile.String(), func(t *testing.T) { + t.Run("Encrypt RTP", func(t *testing.T) { + cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(c.profile, c.keys, true, true) + assert.NoError(t, err) + ctx, err := createContextWithCipher(c.profile, cipher) + assert.NoError(t, err) + ctx.SetIndex(0x4d617273, 0x000005d3) + + actualEncrypted, err := ctx.EncryptRTP(nil, c.decryptedRTPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.encryptedRTPPacket, actualEncrypted) + }) + + t.Run("Decrypt RTP", func(t *testing.T) { + cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(c.profile, c.keys, true, true) + assert.NoError(t, err) + ctx, err := createContextWithCipher(c.profile, cipher) + assert.NoError(t, err) + ctx.SetIndex(0x4d617273, 0x000005d3) + + actualDecrypted, err := ctx.DecryptRTP(nil, c.encryptedRTPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTPPacket, actualDecrypted) + }) + + t.Run("Encrypt RTCP", func(t *testing.T) { + cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(c.profile, c.keys, true, true) + assert.NoError(t, err) + ctx, err := createContextWithCipher(c.profile, cipher) + assert.NoError(t, err) + ctx.SetIndex(0x4d617273, 0x000005d3) + + actualEncrypted, err := ctx.EncryptRTCP(nil, c.decryptedRTCPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.encryptedRTCPPacket, actualEncrypted) + }) + + t.Run("Decrypt RTCP", func(t *testing.T) { + cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(c.profile, c.keys, true, true) + assert.NoError(t, err) + ctx, err := createContextWithCipher(c.profile, cipher) + assert.NoError(t, err) + ctx.SetIndex(0x4d617273, 0x000005d3) + + actualDecrypted, err := ctx.DecryptRTCP(nil, c.encryptedRTCPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTCPPacket, actualDecrypted) + }) + + t.Run("Encrypt RTP with NULL cipher", func(t *testing.T) { + cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(c.profile, c.keys, false, false) + assert.NoError(t, err) + ctx, err := createContextWithCipher(c.profile, cipher) + assert.NoError(t, err) + ctx.SetIndex(0x4d617273, 0x000005d3) + + actualEncrypted, err := ctx.EncryptRTP(nil, c.decryptedRTPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.authenticatedRTPPacket, actualEncrypted) + }) + + t.Run("Decrypt RTP with NULL cipher", func(t *testing.T) { + cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(c.profile, c.keys, false, false) + assert.NoError(t, err) + ctx, err := createContextWithCipher(c.profile, cipher) + assert.NoError(t, err) + ctx.SetIndex(0x4d617273, 0x000005d3) + + actualDecrypted, err := ctx.DecryptRTP(nil, c.authenticatedRTPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTPPacket, actualDecrypted) + }) + + t.Run("Encrypt RTCP with NULL cipher", func(t *testing.T) { + cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(c.profile, c.keys, false, false) + assert.NoError(t, err) + ctx, err := createContextWithCipher(c.profile, cipher) + assert.NoError(t, err) + ctx.SetIndex(0x4d617273, 0x000005d3) + + actualEncrypted, err := ctx.EncryptRTCP(nil, c.decryptedRTCPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.authenticatedRTCPPacket, actualEncrypted) + }) + + t.Run("Decrypt RTCP with NULL cipher", func(t *testing.T) { + cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(c.profile, c.keys, false, false) + assert.NoError(t, err) + ctx, err := createContextWithCipher(c.profile, cipher) + assert.NoError(t, err) + ctx.SetIndex(0x4d617273, 0x000005d3) + + actualDecrypted, err := ctx.DecryptRTCP(nil, c.authenticatedRTCPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTCPPacket, actualDecrypted) + }) + }) + } +} diff --git a/srtp_cipher_aes_cm_hmac_sha1.go b/srtp_cipher_aes_cm_hmac_sha1.go index 0ffe51a..aa673fa 100644 --- a/srtp_cipher_aes_cm_hmac_sha1.go +++ b/srtp_cipher_aes_cm_hmac_sha1.go @@ -21,16 +21,28 @@ type srtpCipherAesCmHmacSha1 struct { srtpSessionSalt []byte srtpSessionAuth hash.Hash srtpBlock cipher.Block + srtpEncrypted bool srtcpSessionSalt []byte srtcpSessionAuth hash.Hash srtcpBlock cipher.Block + srtcpEncrypted bool mki []byte } -func newSrtpCipherAesCmHmacSha1(profile ProtectionProfile, masterKey, masterSalt, mki []byte) (*srtpCipherAesCmHmacSha1, error) { - s := &srtpCipherAesCmHmacSha1{ProtectionProfile: profile} +func newSrtpCipherAesCmHmacSha1(profile ProtectionProfile, masterKey, masterSalt, mki []byte, encryptSRTP, encryptSRTCP bool) (*srtpCipherAesCmHmacSha1, error) { + if profile == ProtectionProfileNullHmacSha1_80 || profile == ProtectionProfileNullHmacSha1_32 { + encryptSRTP = false + encryptSRTCP = false + } + + s := &srtpCipherAesCmHmacSha1{ + ProtectionProfile: profile, + srtpEncrypted: encryptSRTP, + srtcpEncrypted: encryptSRTCP, + } + srtpSessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey)) if err != nil { return nil, err @@ -93,9 +105,13 @@ func (s *srtpCipherAesCmHmacSha1) encryptRTP(dst []byte, header *rtp.Header, pay } // Encrypt the payload - counter := generateCounter(header.SequenceNumber, roc, header.SSRC, s.srtpSessionSalt) - if err = xorBytesCTR(s.srtpBlock, counter[:], dst[n:], payload); err != nil { - return nil, err + if s.srtpEncrypted { + counter := generateCounter(header.SequenceNumber, roc, header.SSRC, s.srtpSessionSalt) + if err = xorBytesCTR(s.srtpBlock, counter[:], dst[n:], payload); err != nil { + return nil, err + } + } else { + copy(dst[n:], payload) } n += len(payload) @@ -144,26 +160,42 @@ func (s *srtpCipherAesCmHmacSha1) decryptRTP(dst, ciphertext []byte, header *rtp copy(dst, ciphertext[:headerLen]) // Decrypt the ciphertext for the payload. - counter := generateCounter(header.SequenceNumber, roc, header.SSRC, s.srtpSessionSalt) - err = xorBytesCTR( - s.srtpBlock, counter[:], dst[headerLen:], ciphertext[headerLen:], - ) - return dst, err + if s.srtpEncrypted { + counter := generateCounter(header.SequenceNumber, roc, header.SSRC, s.srtpSessionSalt) + err = xorBytesCTR( + s.srtpBlock, counter[:], dst[headerLen:], ciphertext[headerLen:], + ) + if err != nil { + return nil, err + } + } else { + copy(dst[headerLen:], ciphertext[headerLen:]) + } + return dst, nil } func (s *srtpCipherAesCmHmacSha1) encryptRTCP(dst, decrypted []byte, srtcpIndex uint32, ssrc uint32) ([]byte, error) { dst = allocateIfMismatch(dst, decrypted) // Encrypt everything after header - counter := generateCounter(uint16(srtcpIndex&0xffff), srtcpIndex>>16, ssrc, s.srtcpSessionSalt) - if err := xorBytesCTR(s.srtcpBlock, counter[:], dst[8:], dst[8:]); err != nil { - return nil, err - } + if s.srtcpEncrypted { + counter := generateCounter(uint16(srtcpIndex&0xffff), srtcpIndex>>16, ssrc, s.srtcpSessionSalt) + if err := xorBytesCTR(s.srtcpBlock, counter[:], dst[8:], dst[8:]); err != nil { + return nil, err + } + + // Add SRTCP Index and set Encryption bit + dst = append(dst, make([]byte, 4)...) + binary.BigEndian.PutUint32(dst[len(dst)-4:], srtcpIndex) + dst[len(dst)-4] |= 0x80 + } else { + // Copy the decrypted payload as is + copy(dst[8:], decrypted[8:]) - // Add SRTCP Index and set Encryption bit - dst = append(dst, make([]byte, 4)...) - binary.BigEndian.PutUint32(dst[len(dst)-4:], srtcpIndex) - dst[len(dst)-4] |= 0x80 + // Add SRTCP Index with Encryption bit cleared + dst = append(dst, make([]byte, 4)...) + binary.BigEndian.PutUint32(dst[len(dst)-4:], srtcpIndex) + } // Generate the authentication tag authTag, err := s.generateSrtcpAuthTag(dst) @@ -201,8 +233,13 @@ func (s *srtpCipherAesCmHmacSha1) decryptRTCP(out, encrypted []byte, index, ssrc return nil, ErrFailedToVerifyAuthTag } - counter := generateCounter(uint16(index&0xffff), index>>16, ssrc, s.srtcpSessionSalt) - err = xorBytesCTR(s.srtcpBlock, counter[:], out[8:], out[8:]) + isEncrypted := encrypted[tailOffset]>>7 != 0 + if isEncrypted { + counter := generateCounter(uint16(index&0xffff), index>>16, ssrc, s.srtcpSessionSalt) + err = xorBytesCTR(s.srtcpBlock, counter[:], out[8:], out[8:]) + } else { + copy(out[8:], encrypted[8:]) + } return out, err } diff --git a/srtp_cipher_aes_cm_hmac_sha1_rfc_test.go b/srtp_cipher_aes_cm_hmac_sha1_rfc_test.go new file mode 100644 index 0000000..f784d6c --- /dev/null +++ b/srtp_cipher_aes_cm_hmac_sha1_rfc_test.go @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package srtp + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type testRfcAesCipher struct { + profile ProtectionProfile // Protection profile + keys derivedSessionKeys // Derived session keys + keystream []byte +} + +// createRfcAesTestCiphers returns a list of test ciphers for the RFC test vectors +func createRfcAesTestCiphers() []testRfcAesCipher { + tests := []testRfcAesCipher{} + + // AES-128-CM, RFC 3711, Appendix B.2 + aes128Cm := testRfcAesCipher{ + profile: ProtectionProfileAes128CmHmacSha1_80, + keys: derivedSessionKeys{ + srtpSessionKey: fromHex(`2B7E151628AED2A6ABF7158809CF4F3C`), + srtpSessionSalt: fromHex(`F0F1F2F3F4F5F6F7F8F9FAFBFCFD0000`), + }, + keystream: fromHex(`E03EAD0935C95E80E166B16DD92B4EB4 + D23513162B02D0F72A43A2FE4A5F97AB + 41E95B3BB0A2E8DD477901E4FCA894C0`), + } + aes128Cm.keys.srtcpSessionKey = aes128Cm.keys.srtpSessionKey + aes128Cm.keys.srtcpSessionSalt = aes128Cm.keys.srtpSessionSalt + tests = append(tests, aes128Cm) + + // AES-256-CM, RFC 6188, Section 7.1 + aes256Cm := testRfcAesCipher{ + profile: ProtectionProfileAes256CmHmacSha1_80, + keys: derivedSessionKeys{ + srtpSessionKey: fromHex(`57f82fe3613fd170a85ec93c40b1f092 + 2ec4cb0dc025b58272147cc438944a98`), + srtpSessionSalt: fromHex(`f0f1f2f3f4f5f6f7f8f9fafbfcfd0000`), + }, + keystream: fromHex(`92bdd28a93c3f52511c677d08b5515a4 + 9da71b2378a854f67050756ded165bac + 63c4868b7096d88421b563b8c94c9a31`), + } + aes256Cm.keys.srtcpSessionKey = aes256Cm.keys.srtpSessionKey + aes256Cm.keys.srtcpSessionSalt = aes256Cm.keys.srtpSessionSalt + tests = append(tests, aes256Cm) + + return tests +} + +func TestAesCiphersWithRfcTestVectors(t *testing.T) { + for _, c := range createRfcAesTestCiphers() { + t.Run(c.profile.String(), func(t *testing.T) { + // Use zero SSRC and sequence number as specified in RFC + rtpHeader := []byte{ + 0x80, 0x0f, 0x00, 0x00, 0xde, 0xca, 0xfb, 0xad, + 0x00, 0x00, 0x00, 0x00, + } + + t.Run("Keystream generation", func(t *testing.T) { + cipher, err := newSrtpCipherAesCmHmacSha1WithDerivedKeys(c.profile, c.keys, true, true) + assert.NoError(t, err) + ctx, err := createContextWithCipher(c.profile, cipher) + assert.NoError(t, err) + + // Generated AES keystream will be XOR'ed with zeroes in RTP packet payload, so SRTP payload will be equal to keystream + decryptedRTPPacket := make([]byte, len(rtpHeader)+len(c.keystream)) + copy(decryptedRTPPacket, rtpHeader) + + actualEncrypted, err := ctx.EncryptRTP(nil, decryptedRTPPacket, nil) + assert.NoError(t, err) + + assert.Equal(t, rtpHeader, actualEncrypted[:len(rtpHeader)]) + assert.Equal(t, c.keystream, actualEncrypted[len(rtpHeader):len(rtpHeader)+len(c.keystream)]) + }) + }) + } +} diff --git a/srtp_cipher_test.go b/srtp_cipher_test.go index 262b89c..e5806dd 100644 --- a/srtp_cipher_test.go +++ b/srtp_cipher_test.go @@ -15,13 +15,17 @@ type testCipher struct { masterSalt []byte // Master salt mki []byte // Master key identifier - decryptedRTPPacket []byte - encryptedRTPPacket []byte - encryptedRTPPacketWithMKI []byte + decryptedRTPPacket []byte + encryptedRTPPacket []byte + encryptedRTPPacketWithMKI []byte + authenticatedRTPPacket []byte + authenticatedRTPPacketWithMKI []byte - decryptedRTCPPacket []byte - encryptedRTCPPacket []byte - encryptedRTCPPacketWithMKI []byte + decryptedRTCPPacket []byte + encryptedRTCPPacket []byte + encryptedRTCPPacketWithMKI []byte + authenticatedRTCPPacket []byte + authenticatedRTCPPacketWithMKI []byte } // create array of testCiphers for each supported profile @@ -57,6 +61,34 @@ func createTestCiphers() []testCipher { 0x3d, 0xb7, 0xa1, 0x98, 0x37, 0xff, 0x64, 0xe5, 0xcb, 0xd2, }, + authenticatedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, + }, + authenticatedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, + 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, + }, + authenticatedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, + 0xda, 0x9a, 0x3c, 0xa1, + }, + authenticatedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, + 0x0a, 0xc9, + }, }, { //nolint:dupl profile: ProtectionProfileAes128CmHmacSha1_80, @@ -90,6 +122,36 @@ func createTestCiphers() []testCipher { 0x3d, 0xb7, 0xa1, 0x98, 0x37, 0xff, 0x64, 0xe5, 0xcb, 0xd2, }, + authenticatedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, + 0xba, 0x8e, 0xfd, 0xd7, 0x07, 0xdc, + }, + authenticatedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, + 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, + }, + authenticatedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, + 0xda, 0x9a, 0x3c, 0xa1, 0xba, 0x8e, 0xfd, 0xd7, + 0x07, 0xdc, + }, + authenticatedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, + 0x0a, 0xc9, + }, }, { //nolint:dupl profile: ProtectionProfileAes256CmHmacSha1_32, @@ -121,6 +183,34 @@ func createTestCiphers() []testCipher { 0xbf, 0x18, 0x18, 0x2d, 0xd1, 0x18, 0x81, 0x28, 0x78, 0xb1, }, + authenticatedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x3d, 0x03, 0x2a, 0x52, + }, + authenticatedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0xf6, 0xd9, 0xd0, 0xc1, + 0x44, 0xf6, 0x6a, 0xb5, 0x25, 0x43, + }, + authenticatedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, + 0x3d, 0x03, 0x2a, 0x52, + }, + authenticatedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + 0xf6, 0xd9, 0xd0, 0xc1, 0x44, 0xf6, 0x6a, 0xb5, + 0x25, 0x43, + }, }, { //nolint:dupl profile: ProtectionProfileAes256CmHmacSha1_80, @@ -154,6 +244,36 @@ func createTestCiphers() []testCipher { 0xbf, 0x18, 0x18, 0x2d, 0xd1, 0x18, 0x81, 0x28, 0x78, 0xb1, }, + authenticatedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x3d, 0x03, 0x2a, 0x52, + 0x72, 0x97, 0x99, 0x48, 0x5c, 0x39, + }, + authenticatedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0xf6, 0xd9, 0xd0, 0xc1, + 0x44, 0xf6, 0x6a, 0xb5, 0x25, 0x43, + }, + authenticatedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, + 0x3d, 0x03, 0x2a, 0x52, 0x72, 0x97, 0x99, 0x48, + 0x5c, 0x39, + }, + authenticatedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + 0xf6, 0xd9, 0xd0, 0xc1, 0x44, 0xf6, 0x6a, 0xb5, + 0x25, 0x43, + }, }, { //nolint:dupl profile: ProtectionProfileAeadAes128Gcm, @@ -189,6 +309,38 @@ func createTestCiphers() []testCipher { 0xea, 0x31, 0x12, 0x3b, 0xa8, 0x8c, 0xe6, 0x1e, 0x80, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, }, + authenticatedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x8b, 0xdd, 0xb6, 0x20, + 0xb1, 0x0d, 0x2f, 0xe2, 0x76, 0xf7, 0xbd, 0xcf, + 0xc5, 0xc3, 0x8a, 0xe5, + }, + authenticatedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x0c, 0xf6, 0x35, 0x16, 0x8f, 0x82, 0x42, 0xa2, + 0x1b, 0x12, 0xd6, 0x64, 0xec, 0xd8, 0x62, 0xe8, + 0x00, 0x00, 0x00, 0x01, + }, + authenticatedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x8b, 0xdd, 0xb6, 0x20, + 0xb1, 0x0d, 0x2f, 0xe2, 0x76, 0xf7, 0xbd, 0xcf, + 0xc5, 0xc3, 0x8a, 0xe5, 0x01, 0x02, 0x00, 0x04, + }, + authenticatedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x0c, 0xf6, 0x35, 0x16, 0x8f, 0x82, 0x42, 0xa2, + 0x1b, 0x12, 0xd6, 0x64, 0xec, 0xd8, 0x62, 0xe8, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + }, }, { //nolint:dupl profile: ProtectionProfileAeadAes256Gcm, @@ -224,6 +376,160 @@ func createTestCiphers() []testCipher { 0xb9, 0x51, 0xb6, 0x66, 0x84, 0x24, 0xd4, 0xe2, 0x80, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, }, + authenticatedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x9c, 0x27, 0x45, 0xcc, + 0xde, 0x31, 0xda, 0x1f, 0x03, 0xa5, 0x4c, 0xfd, + 0xfa, 0xa2, 0x62, 0x8d, + }, + authenticatedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x22, 0x55, 0xdc, 0xaf, 0x86, 0x9a, 0xbb, 0x1c, + 0xd0, 0x1a, 0xe8, 0x35, 0x4c, 0x94, 0x11, 0xee, + 0x00, 0x00, 0x00, 0x01, + }, + authenticatedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x9c, 0x27, 0x45, 0xcc, + 0xde, 0x31, 0xda, 0x1f, 0x03, 0xa5, 0x4c, 0xfd, + 0xfa, 0xa2, 0x62, 0x8d, 0x01, 0x02, 0x00, 0x04, + }, + authenticatedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x22, 0x55, 0xdc, 0xaf, 0x86, 0x9a, 0xbb, 0x1c, + 0xd0, 0x1a, 0xe8, 0x35, 0x4c, 0x94, 0x11, 0xee, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + }, + }, + { //nolint:dupl + profile: ProtectionProfileNullHmacSha1_32, + encryptedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, + }, + encryptedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, + 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, + }, + encryptedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, + 0xda, 0x9a, 0x3c, 0xa1, + }, + encryptedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, + 0x0a, 0xc9, + }, + authenticatedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, + }, + authenticatedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, + 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, + }, + authenticatedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, + 0xda, 0x9a, 0x3c, 0xa1, + }, + authenticatedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, + 0x0a, 0xc9, + }, + }, + { //nolint:dupl + profile: ProtectionProfileNullHmacSha1_80, + encryptedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, + 0xba, 0x8e, 0xfd, 0xd7, 0x07, 0xdc, + }, + encryptedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, + 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, + }, + encryptedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, + 0xda, 0x9a, 0x3c, 0xa1, 0xba, 0x8e, 0xfd, 0xd7, + 0x07, 0xdc, + }, + encryptedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, + 0x0a, 0xc9, + }, + authenticatedRTPPacket: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, + 0xba, 0x8e, 0xfd, 0xd7, 0x07, 0xdc, + }, + authenticatedRTCPPacket: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, + 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, + }, + authenticatedRTPPacketWithMKI: []byte{ + 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, + 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, + 0xda, 0x9a, 0x3c, 0xa1, 0xba, 0x8e, 0xfd, 0xd7, + 0x07, 0xdc, + }, + authenticatedRTCPPacketWithMKI: []byte{ + 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, + 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, + 0x0a, 0xc9, + }, }, } @@ -272,6 +578,9 @@ func createTestCiphers() []testCipher { func TestSrtpCipher(t *testing.T) { for _, c := range createTestCiphers() { t.Run(c.profile.String(), func(t *testing.T) { + assert.Equal(t, c.decryptedRTPPacket, c.authenticatedRTPPacket[:len(c.decryptedRTPPacket)]) + assert.Equal(t, c.decryptedRTCPPacket, c.authenticatedRTCPPacket[:len(c.decryptedRTCPPacket)]) + t.Run("Encrypt RTP", func(t *testing.T) { ctx, err := CreateContext(c.masterKey, c.masterSalt, c.profile) assert.NoError(t, err) @@ -359,6 +668,98 @@ func TestSrtpCipher(t *testing.T) { assert.Equal(t, c.decryptedRTCPPacket, actualDecrypted) }) }) + + t.Run("Encrypt RTP with NULL cipher", func(t *testing.T) { + ctx, err := CreateContext(c.masterKey, c.masterSalt, c.profile, SRTPNoEncryption(), SRTCPNoEncryption()) + assert.NoError(t, err) + + t.Run("New Allocation", func(t *testing.T) { + actualEncrypted, err := ctx.EncryptRTP(nil, c.decryptedRTPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTPPacket, actualEncrypted[:len(c.decryptedRTPPacket)]) + assert.Equal(t, c.authenticatedRTPPacket, actualEncrypted) + }) + }) + + t.Run("Decrypt RTP with NULL cipher", func(t *testing.T) { + ctx, err := CreateContext(c.masterKey, c.masterSalt, c.profile, SRTPNoEncryption(), SRTCPNoEncryption()) + assert.NoError(t, err) + + t.Run("New Allocation", func(t *testing.T) { + actualDecrypted, err := ctx.DecryptRTP(nil, c.authenticatedRTPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTPPacket, actualDecrypted) + }) + }) + + t.Run("Encrypt RTCP with NULL cipher", func(t *testing.T) { + ctx, err := CreateContext(c.masterKey, c.masterSalt, c.profile, SRTPNoEncryption(), SRTCPNoEncryption()) + assert.NoError(t, err) + + t.Run("New Allocation", func(t *testing.T) { + actualEncrypted, err := ctx.EncryptRTCP(nil, c.decryptedRTCPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTCPPacket, actualEncrypted[:len(c.decryptedRTCPPacket)]) + assert.Equal(t, c.authenticatedRTCPPacket, actualEncrypted) + }) + }) + + t.Run("Decrypt RTCP with NULL cipher", func(t *testing.T) { + ctx, err := CreateContext(c.masterKey, c.masterSalt, c.profile, SRTPNoEncryption(), SRTCPNoEncryption()) + assert.NoError(t, err) + + t.Run("New Allocation", func(t *testing.T) { + actualDecrypted, err := ctx.DecryptRTCP(nil, c.authenticatedRTCPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTCPPacket, actualDecrypted) + }) + }) + + t.Run("Encrypt RTP with NULL cipher and MKI", func(t *testing.T) { + ctx, err := CreateContext(c.masterKey, c.masterSalt, c.profile, SRTPNoEncryption(), SRTCPNoEncryption(), MasterKeyIndicator(c.mki)) + assert.NoError(t, err) + + t.Run("New Allocation", func(t *testing.T) { + actualEncrypted, err := ctx.EncryptRTP(nil, c.decryptedRTPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTPPacket, actualEncrypted[:len(c.decryptedRTPPacket)]) + assert.Equal(t, c.authenticatedRTPPacketWithMKI, actualEncrypted) + }) + }) + + t.Run("Decrypt RTP with NULL cipher and MKI", func(t *testing.T) { + ctx, err := CreateContext(c.masterKey, c.masterSalt, c.profile, SRTPNoEncryption(), SRTCPNoEncryption(), MasterKeyIndicator(c.mki)) + assert.NoError(t, err) + + t.Run("New Allocation", func(t *testing.T) { + actualDecrypted, err := ctx.DecryptRTP(nil, c.authenticatedRTPPacketWithMKI, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTPPacket, actualDecrypted) + }) + }) + + t.Run("Encrypt RTCP with NULL cipher and MKI", func(t *testing.T) { + ctx, err := CreateContext(c.masterKey, c.masterSalt, c.profile, SRTPNoEncryption(), SRTCPNoEncryption(), MasterKeyIndicator(c.mki)) + assert.NoError(t, err) + + t.Run("New Allocation", func(t *testing.T) { + actualEncrypted, err := ctx.EncryptRTCP(nil, c.decryptedRTCPPacket, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTCPPacket, actualEncrypted[:len(c.decryptedRTCPPacket)]) + assert.Equal(t, c.authenticatedRTCPPacketWithMKI, actualEncrypted) + }) + }) + + t.Run("Decrypt RTCP with NULL cipher and MKI", func(t *testing.T) { + ctx, err := CreateContext(c.masterKey, c.masterSalt, c.profile, SRTPNoEncryption(), SRTCPNoEncryption(), MasterKeyIndicator(c.mki)) + assert.NoError(t, err) + + t.Run("New Allocation", func(t *testing.T) { + actualDecrypted, err := ctx.DecryptRTCP(nil, c.authenticatedRTCPPacketWithMKI, nil) + assert.NoError(t, err) + assert.Equal(t, c.decryptedRTCPPacket, actualDecrypted) + }) + }) }) } } diff --git a/srtp_cipher_utils_test.go b/srtp_cipher_utils_test.go new file mode 100644 index 0000000..3b28ca0 --- /dev/null +++ b/srtp_cipher_utils_test.go @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package srtp + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/sha1" // nolint:gosec +) + +// deriveSessionKeys should be used in tests only. RFCs test vectors specifes derived keys to use, this struct is used to inject them into the cipher in tests. +type derivedSessionKeys struct { + srtpSessionKey []byte + srtpSessionSalt []byte + srtpSessionAuthTag []byte + srtcpSessionKey []byte + srtcpSessionSalt []byte + srtcpSessionAuthTag []byte +} + +func newSrtpCipherAesCmHmacSha1WithDerivedKeys(profile ProtectionProfile, keys derivedSessionKeys, encryptSRTP, encryptSRTCP bool) (*srtpCipherAesCmHmacSha1, error) { + if profile == ProtectionProfileNullHmacSha1_80 || profile == ProtectionProfileNullHmacSha1_32 { + encryptSRTP = false + encryptSRTCP = false + } + + s := &srtpCipherAesCmHmacSha1{ + ProtectionProfile: profile, + srtpEncrypted: encryptSRTP, + srtcpEncrypted: encryptSRTCP, + } + + var err error + if s.srtpBlock, err = aes.NewCipher(keys.srtpSessionKey); err != nil { + return nil, err + } + + if s.srtcpBlock, err = aes.NewCipher(keys.srtcpSessionKey); err != nil { + return nil, err + } + + s.srtpSessionSalt = keys.srtpSessionSalt + s.srtcpSessionSalt = keys.srtcpSessionSalt + + s.srtcpSessionAuth = hmac.New(sha1.New, keys.srtcpSessionAuthTag) + s.srtpSessionAuth = hmac.New(sha1.New, keys.srtpSessionAuthTag) + + return s, nil +} + +func newSrtpCipherAeadAesGcmWithDerivedKeys(profile ProtectionProfile, keys derivedSessionKeys, encryptSRTP, encryptSRTCP bool) (*srtpCipherAeadAesGcm, error) { + s := &srtpCipherAeadAesGcm{ProtectionProfile: profile, srtpEncrypted: encryptSRTP, srtcpEncrypted: encryptSRTCP} + + srtpBlock, err := aes.NewCipher(keys.srtpSessionKey) + if err != nil { + return nil, err + } + + s.srtpCipher, err = cipher.NewGCM(srtpBlock) + if err != nil { + return nil, err + } + + srtcpBlock, err := aes.NewCipher(keys.srtcpSessionKey) + if err != nil { + return nil, err + } + + s.srtcpCipher, err = cipher.NewGCM(srtcpBlock) + if err != nil { + return nil, err + } + + s.srtpSessionSalt = keys.srtpSessionSalt + s.srtcpSessionSalt = keys.srtcpSessionSalt + + return s, nil +} + +// createContextWithCipher creates a new SRTP Context with a pre-created cipher. This is used for testing purposes only. +func createContextWithCipher(profile ProtectionProfile, cipher srtpCipher) (*Context, error) { + c := &Context{ + srtpSSRCStates: map[uint32]*srtpSSRCState{}, + srtcpSSRCStates: map[uint32]*srtcpSSRCState{}, + profile: profile, + mkis: map[string]srtpCipher{}, + cipher: cipher, + } + err := SRTPNoReplayProtection()(c) + if err != nil { + return nil, err + } + err = SRTCPNoReplayProtection()(c) + if err != nil { + return nil, err + } + return c, nil +}