package srtp
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha1"
"crypto/subtle"
"encoding/binary"
"hash"
"github.com/pion/rtp"
)
type srtpCipherAesCmHmacSha1 struct {
protectionProfileWithArgs
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 protectionProfileWithArgs ,
masterKey , masterSalt , mki []byte ,
encryptSRTP , encryptSRTCP bool ,
) (*srtpCipherAesCmHmacSha1 , error ) {
switch profile .ProtectionProfile {
case ProtectionProfileNullHmacSha1_80 , ProtectionProfileNullHmacSha1_32 :
encryptSRTP = false
encryptSRTCP = false
default :
}
srtpCipher := &srtpCipherAesCmHmacSha1 {
protectionProfileWithArgs : profile ,
srtpEncrypted : encryptSRTP ,
srtcpEncrypted : encryptSRTCP ,
}
srtpSessionKey , err := aesCmKeyDerivation (labelSRTPEncryption , masterKey , masterSalt , 0 , len (masterKey ))
if err != nil {
return nil , err
} else if srtpCipher .srtpBlock , err = aes .NewCipher (srtpSessionKey ); err != nil {
return nil , err
}
srtcpSessionKey , err := aesCmKeyDerivation (labelSRTCPEncryption , masterKey , masterSalt , 0 , len (masterKey ))
if err != nil {
return nil , err
} else if srtpCipher .srtcpBlock , err = aes .NewCipher (srtcpSessionKey ); err != nil {
return nil , err
}
if srtpCipher .srtpSessionSalt , err = aesCmKeyDerivation (
labelSRTPSalt , masterKey , masterSalt , 0 , len (masterSalt ),
); err != nil {
return nil , err
} else if srtpCipher .srtcpSessionSalt , err = aesCmKeyDerivation (
labelSRTCPSalt , masterKey , masterSalt , 0 , len (masterSalt ),
); err != nil {
return nil , err
}
authKeyLen , err := profile .AuthKeyLen ()
if err != nil {
return nil , err
}
srtpSessionAuthTag , err := aesCmKeyDerivation (labelSRTPAuthenticationTag , masterKey , masterSalt , 0 , authKeyLen )
if err != nil {
return nil , err
}
srtcpSessionAuthTag , err := aesCmKeyDerivation (labelSRTCPAuthenticationTag , masterKey , masterSalt , 0 , authKeyLen )
if err != nil {
return nil , err
}
srtpCipher .srtcpSessionAuth = hmac .New (sha1 .New , srtcpSessionAuthTag )
srtpCipher .srtpSessionAuth = hmac .New (sha1 .New , srtpSessionAuthTag )
mkiLen := len (mki )
if mkiLen > 0 {
srtpCipher .mki = make ([]byte , mkiLen )
copy (srtpCipher .mki , mki )
}
return srtpCipher , nil
}
func (s *srtpCipherAesCmHmacSha1 ) encryptRTP (
dst []byte ,
header *rtp .Header ,
headerLen int ,
plaintext []byte ,
roc uint32 ,
rocInAuthTag bool ,
) (ciphertext []byte , err error ) {
payload := plaintext [headerLen :]
payloadLen := len (payload )
authTagLen , err := s .AuthTagRTPLen ()
if err != nil {
return nil , err
}
dst = growBufferSize (dst , headerLen +payloadLen +len (s .mki )+authTagLen )
sameBuffer := isSameBuffer (dst , plaintext )
if !sameBuffer {
copy (dst , plaintext [:headerLen ])
}
if s .srtpEncrypted {
counter := generateCounter (header .SequenceNumber , roc , header .SSRC , s .srtpSessionSalt )
if err = xorBytesCTR (s .srtpBlock , counter [:], dst [headerLen :], payload ); err != nil {
return nil , err
}
} else if !sameBuffer {
copy (dst [headerLen :], payload )
}
n := headerLen + payloadLen
authTag , err := s .generateSrtpAuthTag (dst [:n ], roc , rocInAuthTag )
if err != nil {
return nil , err
}
if len (s .mki ) > 0 {
copy (dst [n :], s .mki )
n += len (s .mki )
}
copy (dst [n :], authTag )
return dst , nil
}
func (s *srtpCipherAesCmHmacSha1 ) decryptRTP (
dst , ciphertext []byte ,
header *rtp .Header ,
headerLen int ,
roc uint32 ,
rocInAuthTag bool ,
) ([]byte , error ) {
authTagLen , err := s .AuthTagRTPLen ()
if err != nil {
return nil , err
}
actualTag := ciphertext [len (ciphertext )-authTagLen :]
ciphertext = ciphertext [:len (ciphertext )-len (s .mki )-authTagLen ]
expectedTag , err := s .generateSrtpAuthTag (ciphertext , roc , rocInAuthTag )
if err != nil {
return nil , err
}
if subtle .ConstantTimeCompare (actualTag , expectedTag ) != 1 {
return nil , ErrFailedToVerifyAuthTag
}
sameBuffer := isSameBuffer (dst , ciphertext )
if !sameBuffer {
copy (dst , ciphertext [:headerLen ])
}
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 if !sameBuffer {
copy (dst [headerLen :], ciphertext [headerLen :])
}
return dst , nil
}
func (s *srtpCipherAesCmHmacSha1 ) encryptRTCP (dst , decrypted []byte , srtcpIndex uint32 , ssrc uint32 ) ([]byte , error ) {
authTagLen , err := s .AuthTagRTCPLen ()
if err != nil {
return nil , err
}
mkiLen := len (s .mki )
decryptedLen := len (decrypted )
encryptedLen := decryptedLen + authTagLen + mkiLen + srtcpIndexSize
dst = growBufferSize (dst , encryptedLen )
sameBuffer := isSameBuffer (dst , decrypted )
if !sameBuffer {
copy (dst , decrypted [:srtcpHeaderSize ])
}
if s .srtcpEncrypted {
counter := generateCounter (uint16 (srtcpIndex &0xffff ), srtcpIndex >>16 , ssrc , s .srtcpSessionSalt )
if err = xorBytesCTR (s .srtcpBlock , counter [:], dst [srtcpHeaderSize :], decrypted [srtcpHeaderSize :]); err != nil {
return nil , err
}
binary .BigEndian .PutUint32 (dst [decryptedLen :], srtcpIndex )
dst [decryptedLen ] |= srtcpEncryptionFlag
} else {
if !sameBuffer {
copy (dst [srtcpHeaderSize :], decrypted [srtcpHeaderSize :])
}
binary .BigEndian .PutUint32 (dst [decryptedLen :], srtcpIndex )
}
n := decryptedLen + srtcpIndexSize
authTag , err := s .generateSrtcpAuthTag (dst [:n ])
if err != nil {
return nil , err
}
if len (s .mki ) > 0 {
copy (dst [n :], s .mki )
n += mkiLen
}
copy (dst [n :], authTag )
return dst , nil
}
func (s *srtpCipherAesCmHmacSha1 ) decryptRTCP (dst , encrypted []byte , index , ssrc uint32 ) ([]byte , error ) {
authTagLen , err := s .AuthTagRTCPLen ()
if err != nil {
return nil , err
}
mkiLen := len (s .mki )
encryptedLen := len (encrypted )
decryptedLen := encryptedLen - (authTagLen + mkiLen + srtcpIndexSize )
if decryptedLen < 8 {
return nil , errTooShortRTCP
}
expectedTag , err := s .generateSrtcpAuthTag (encrypted [:encryptedLen -mkiLen -authTagLen ])
if err != nil {
return nil , err
}
actualTag := encrypted [encryptedLen -authTagLen :]
if subtle .ConstantTimeCompare (actualTag , expectedTag ) != 1 {
return nil , ErrFailedToVerifyAuthTag
}
dst = growBufferSize (dst , decryptedLen )
sameBuffer := isSameBuffer (dst , encrypted )
if !sameBuffer {
copy (dst , encrypted [:srtcpHeaderSize ])
}
isEncrypted := encrypted [decryptedLen ]&srtcpEncryptionFlag != 0
if isEncrypted {
counter := generateCounter (uint16 (index &0xffff ), index >>16 , ssrc , s .srtcpSessionSalt )
err = xorBytesCTR (s .srtcpBlock , counter [:], dst [srtcpHeaderSize :], encrypted [srtcpHeaderSize :decryptedLen ])
} else if !sameBuffer {
copy (dst [srtcpHeaderSize :], encrypted [srtcpHeaderSize :])
}
return dst , err
}
func (s *srtpCipherAesCmHmacSha1 ) generateSrtpAuthTag (buf []byte , roc uint32 , rocInAuthTag bool ) ([]byte , error ) {
s .srtpSessionAuth .Reset ()
if _ , err := s .srtpSessionAuth .Write (buf ); err != nil {
return nil , err
}
rocRaw := [4 ]byte {}
binary .BigEndian .PutUint32 (rocRaw [:], roc )
_ , err := s .srtpSessionAuth .Write (rocRaw [:])
if err != nil {
return nil , err
}
authTagLen , err := s .AuthTagRTPLen ()
if err != nil {
return nil , err
}
var authTag []byte
if rocInAuthTag {
authTag = append (authTag , rocRaw [:]...)
}
return s .srtpSessionAuth .Sum (authTag )[0 :authTagLen ], nil
}
func (s *srtpCipherAesCmHmacSha1 ) generateSrtcpAuthTag (buf []byte ) ([]byte , error ) {
s .srtcpSessionAuth .Reset ()
if _ , err := s .srtcpSessionAuth .Write (buf ); err != nil {
return nil , err
}
authTagLen , err := s .AuthTagRTCPLen ()
if err != nil {
return nil , err
}
return s .srtcpSessionAuth .Sum (nil )[0 :authTagLen ], nil
}
func (s *srtpCipherAesCmHmacSha1 ) getRTCPIndex (in []byte ) uint32 {
authTagLen , _ := s .AuthTagRTCPLen ()
tailOffset := len (in ) - (authTagLen + srtcpIndexSize + len (s .mki ))
srtcpIndexBuffer := in [tailOffset : tailOffset +srtcpIndexSize ]
return binary .BigEndian .Uint32 (srtcpIndexBuffer ) &^ (1 << 31 )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .