From 590d1cf4aa3be1210a20c75560e9e4589a2b829f Mon Sep 17 00:00:00 2001 From: sirzooro Date: Wed, 17 Jul 2024 08:13:29 +0200 Subject: [PATCH] Added support for NULL ciphers Added support for NULL ciphers. When they are used, created SRTP and SRTCP packets are authenticated only (no encryption). Received SRTP/SRTCP packets are checked if their authentication tag is valid, and extra SRTP protocol fields are removed before returning then to application. Fixed processing of SRTCP packets with E (encryption) bit cleared, previously duplicate check and tag valiation was not performed, and whole packet was returned as-is (with extra fields) from decryptRTCP. Use of NULL ciphers can be enabled independently for SRTP and SRTCP using SRTPNoEncryption and SRTCPNoEncryption options. They can be used with key exchange protocols which allows to configure them separately. Added support for SRTP_NULL_HMAC_SHA1_80 and SRTP_NULL_HMAC_SHA1_32 cipher suites. They use key and salt of the same length as AES_CM_128 ones. Added new tests to verify test vectors from RFCs. --- context.go | 17 +- option.go | 34 ++ protection_profile.go | 27 +- srtcp.go | 14 +- srtp.go | 12 +- srtp_cipher_aead_aes_gcm.go | 82 ++++- srtp_cipher_aead_aes_gcm_rfc_test.go | 227 +++++++++++++ srtp_cipher_aes_cm_hmac_sha1.go | 77 +++-- srtp_cipher_aes_cm_hmac_sha1_rfc_test.go | 83 +++++ srtp_cipher_test.go | 413 ++++++++++++++++++++++- srtp_cipher_utils_test.go | 100 ++++++ 11 files changed, 1021 insertions(+), 65 deletions(-) create mode 100644 srtp_cipher_aead_aes_gcm_rfc_test.go create mode 100644 srtp_cipher_aes_cm_hmac_sha1_rfc_test.go create mode 100644 srtp_cipher_utils_test.go 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 +}