mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-23 10:07:30 +00:00
296 lines
10 KiB
Go
296 lines
10 KiB
Go
package srtp
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/hmac"
|
|
"crypto/sha1" // #nosec
|
|
"encoding/binary"
|
|
"hash"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// ProtectionProfile specifies Cipher and AuthTag details, similar to TLS cipher suite
|
|
type ProtectionProfile uint16
|
|
|
|
// Supported protection profiles
|
|
const (
|
|
ProtectionProfileAes128CmHmacSha1_80 ProtectionProfile = 0x0001
|
|
)
|
|
|
|
const (
|
|
labelSRTPEncryption = 0x00
|
|
labelSRTPAuthenticationTag = 0x01
|
|
labelSRTPSalt = 0x02
|
|
|
|
labelSRTCPEncryption = 0x03
|
|
labelSRTCPAuthenticationTag = 0x04
|
|
labelSRTCPSalt = 0x05
|
|
|
|
keyLen = 16
|
|
saltLen = 14
|
|
|
|
maxROCDisorder = 100
|
|
maxSequenceNumber = 65535
|
|
|
|
authTagSize = 10
|
|
srtcpIndexSize = 4
|
|
)
|
|
|
|
// Encode/Decode state for a single SSRC
|
|
type ssrcState struct {
|
|
ssrc uint32
|
|
rolloverCounter uint32
|
|
rolloverHasProcessed bool
|
|
lastSequenceNumber uint16
|
|
}
|
|
|
|
// Context represents a SRTP cryptographic context
|
|
// Context can only be used for one-way operations
|
|
// it must either used ONLY for encryption or ONLY for decryption
|
|
type Context struct {
|
|
masterKey []byte
|
|
masterSalt []byte
|
|
|
|
ssrcStates map[uint32]*ssrcState
|
|
srtpSessionKey []byte
|
|
srtpSessionSalt []byte
|
|
srtpSessionAuth hash.Hash
|
|
srtpSessionAuthTag []byte
|
|
srtpBlock cipher.Block
|
|
|
|
srtcpSessionKey []byte
|
|
srtcpSessionSalt []byte
|
|
srtcpSessionAuth hash.Hash
|
|
srtcpSessionAuthTag []byte
|
|
srtcpIndex uint32
|
|
srtcpBlock cipher.Block
|
|
}
|
|
|
|
// CreateContext creates a new SRTP Context
|
|
func CreateContext(masterKey, masterSalt []byte, profile ProtectionProfile) (c *Context, err error) {
|
|
if masterKeyLen := len(masterKey); masterKeyLen != keyLen {
|
|
return c, errors.Errorf("SRTP Master Key must be len %d, got %d", masterKey, keyLen)
|
|
} else if masterSaltLen := len(masterSalt); masterSaltLen != saltLen {
|
|
return c, errors.Errorf("SRTP Salt must be len %d, got %d", saltLen, masterSaltLen)
|
|
}
|
|
|
|
c = &Context{
|
|
masterKey: masterKey,
|
|
masterSalt: masterSalt,
|
|
ssrcStates: map[uint32]*ssrcState{},
|
|
}
|
|
|
|
if c.srtpSessionKey, err = c.generateSessionKey(labelSRTPEncryption); err != nil {
|
|
return nil, err
|
|
} else if c.srtpSessionSalt, err = c.generateSessionSalt(labelSRTPSalt); err != nil {
|
|
return nil, err
|
|
} else if c.srtpSessionAuthTag, err = c.generateSessionAuthTag(labelSRTPAuthenticationTag); err != nil {
|
|
return nil, err
|
|
} else if c.srtpBlock, err = aes.NewCipher(c.srtpSessionKey); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.srtpSessionAuth = hmac.New(sha1.New, c.srtpSessionAuthTag)
|
|
|
|
if c.srtcpSessionKey, err = c.generateSessionKey(labelSRTCPEncryption); err != nil {
|
|
return nil, err
|
|
} else if c.srtcpSessionSalt, err = c.generateSessionSalt(labelSRTCPSalt); err != nil {
|
|
return nil, err
|
|
} else if c.srtcpSessionAuthTag, err = c.generateSessionAuthTag(labelSRTCPAuthenticationTag); err != nil {
|
|
return nil, err
|
|
} else if c.srtcpBlock, err = aes.NewCipher(c.srtcpSessionKey); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.srtcpSessionAuth = hmac.New(sha1.New, c.srtcpSessionAuthTag)
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func (c *Context) generateSessionKey(label byte) ([]byte, error) {
|
|
// https://tools.ietf.org/html/rfc3711#appendix-B.3
|
|
// The input block for AES-CM is generated by exclusive-oring the master salt with the
|
|
// concatenation of the encryption key label 0x00 with (index DIV kdr),
|
|
// - index is 'rollover count' and DIV is 'divided by'
|
|
sessionKey := make([]byte, len(c.masterSalt))
|
|
copy(sessionKey, c.masterSalt)
|
|
|
|
labelAndIndexOverKdr := []byte{label, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
|
for i, j := len(labelAndIndexOverKdr)-1, len(sessionKey)-1; i >= 0; i, j = i-1, j-1 {
|
|
sessionKey[j] = sessionKey[j] ^ labelAndIndexOverKdr[i]
|
|
}
|
|
|
|
// then padding on the right with two null octets (which implements the multiply-by-2^16 operation, see Section 4.3.3).
|
|
sessionKey = append(sessionKey, []byte{0x00, 0x00}...)
|
|
|
|
//The resulting value is then AES-CM- encrypted using the master key to get the cipher key.
|
|
block, err := aes.NewCipher(c.masterKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block.Encrypt(sessionKey, sessionKey)
|
|
return sessionKey, nil
|
|
}
|
|
|
|
func (c *Context) generateSessionSalt(label byte) ([]byte, error) {
|
|
// https://tools.ietf.org/html/rfc3711#appendix-B.3
|
|
// The input block for AES-CM is generated by exclusive-oring the master salt with
|
|
// the concatenation of the encryption salt label
|
|
sessionSalt := make([]byte, len(c.masterSalt))
|
|
copy(sessionSalt, c.masterSalt)
|
|
|
|
labelAndIndexOverKdr := []byte{label, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
|
for i, j := len(labelAndIndexOverKdr)-1, len(sessionSalt)-1; i >= 0; i, j = i-1, j-1 {
|
|
sessionSalt[j] = sessionSalt[j] ^ labelAndIndexOverKdr[i]
|
|
}
|
|
|
|
// That value is padded and encrypted as above.
|
|
sessionSalt = append(sessionSalt, []byte{0x00, 0x00}...)
|
|
block, err := aes.NewCipher(c.masterKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block.Encrypt(sessionSalt, sessionSalt)
|
|
return sessionSalt[0:saltLen], nil
|
|
}
|
|
|
|
func (c *Context) generateSessionAuthTag(label byte) ([]byte, error) {
|
|
// https://tools.ietf.org/html/rfc3711#appendix-B.3
|
|
// We now show how the auth key is generated. The input block for AES-
|
|
// CM is generated as above, but using the authentication key label.
|
|
sessionAuthTag := make([]byte, len(c.masterSalt))
|
|
copy(sessionAuthTag, c.masterSalt)
|
|
|
|
labelAndIndexOverKdr := []byte{label, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
|
for i, j := len(labelAndIndexOverKdr)-1, len(sessionAuthTag)-1; i >= 0; i, j = i-1, j-1 {
|
|
sessionAuthTag[j] = sessionAuthTag[j] ^ labelAndIndexOverKdr[i]
|
|
}
|
|
|
|
// That value is padded and encrypted as above.
|
|
// - We need to do multiple runs at key size (20) is larger then source
|
|
firstRun := append(sessionAuthTag, []byte{0x00, 0x00}...)
|
|
secondRun := append(sessionAuthTag, []byte{0x00, 0x01}...)
|
|
block, err := aes.NewCipher(c.masterKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block.Encrypt(firstRun, firstRun)
|
|
block.Encrypt(secondRun, secondRun)
|
|
return append(firstRun, secondRun[:4]...), nil
|
|
}
|
|
|
|
// Generate IV https://tools.ietf.org/html/rfc3711#section-4.1.1
|
|
// where the 128-bit integer value IV SHALL be defined by the SSRC, the
|
|
// SRTP packet index i, and the SRTP session salting key k_s, as below.
|
|
// - ROC = a 32-bit unsigned rollover counter (ROC), which records how many
|
|
// - times the 16-bit RTP sequence number has been reset to zero after
|
|
// - passing through 65,535
|
|
// i = 2^16 * ROC + SEQ
|
|
// IV = (salt*2 ^ 16) | (ssrc*2 ^ 64) | (i*2 ^ 16)
|
|
func (c *Context) generateCounter(sequenceNumber uint16, rolloverCounter uint32, ssrc uint32, sessionSalt []byte) []byte {
|
|
counter := make([]byte, 16)
|
|
|
|
binary.BigEndian.PutUint32(counter[4:], ssrc)
|
|
binary.BigEndian.PutUint32(counter[8:], rolloverCounter)
|
|
binary.BigEndian.PutUint32(counter[12:], uint32(sequenceNumber)<<16)
|
|
|
|
for i := range sessionSalt {
|
|
counter[i] = counter[i] ^ sessionSalt[i]
|
|
}
|
|
|
|
return counter
|
|
}
|
|
|
|
func (c *Context) generateSrtpAuthTag(buf []byte, roc uint32) ([]byte, error) {
|
|
// https://tools.ietf.org/html/rfc3711#section-4.2
|
|
// In the case of SRTP, M SHALL consist of the Authenticated
|
|
// Portion of the packet (as specified in Figure 1) concatenated with
|
|
// the ROC, M = Authenticated Portion || ROC;
|
|
//
|
|
// The pre-defined authentication transform for SRTP is HMAC-SHA1
|
|
// [RFC2104]. With HMAC-SHA1, the SRTP_PREFIX_LENGTH (Figure 3) SHALL
|
|
// be 0. For SRTP (respectively SRTCP), the HMAC SHALL be applied to
|
|
// the session authentication key and M as specified above, i.e.,
|
|
// HMAC(k_a, M). The HMAC output SHALL then be truncated to the n_tag
|
|
// left-most bits.
|
|
// - Authenticated portion of the packet is everything BEFORE MKI
|
|
// - k_a is the session message authentication key
|
|
// - n_tag is the bit-length of the output authentication tag
|
|
c.srtpSessionAuth.Reset()
|
|
|
|
if _, err := c.srtpSessionAuth.Write(buf); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// For SRTP only, we need to hash the rollover counter as well.
|
|
rocRaw := [4]byte{}
|
|
binary.BigEndian.PutUint32(rocRaw[:], roc)
|
|
|
|
_, err := c.srtpSessionAuth.Write(rocRaw[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Truncate the hash to the first 10 bytes.
|
|
return c.srtpSessionAuth.Sum(nil)[0:10], nil
|
|
}
|
|
|
|
func (c *Context) generateSrtcpAuthTag(buf []byte) ([]byte, error) {
|
|
// https://tools.ietf.org/html/rfc3711#section-4.2
|
|
//
|
|
// The pre-defined authentication transform for SRTP is HMAC-SHA1
|
|
// [RFC2104]. With HMAC-SHA1, the SRTP_PREFIX_LENGTH (Figure 3) SHALL
|
|
// be 0. For SRTP (respectively SRTCP), the HMAC SHALL be applied to
|
|
// the session authentication key and M as specified above, i.e.,
|
|
// HMAC(k_a, M). The HMAC output SHALL then be truncated to the n_tag
|
|
// left-most bits.
|
|
// - Authenticated portion of the packet is everything BEFORE MKI
|
|
// - k_a is the session message authentication key
|
|
// - n_tag is the bit-length of the output authentication tag
|
|
c.srtcpSessionAuth.Reset()
|
|
|
|
if _, err := c.srtcpSessionAuth.Write(buf); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return c.srtcpSessionAuth.Sum(nil)[0:10], nil
|
|
}
|
|
|
|
// https://tools.ietf.org/html/rfc3550#appendix-A.1
|
|
func (c *Context) updateRolloverCount(sequenceNumber uint16, s *ssrcState) {
|
|
if !s.rolloverHasProcessed {
|
|
s.rolloverHasProcessed = true
|
|
} else if sequenceNumber == 0 { // We exactly hit the rollover count
|
|
|
|
// Only update rolloverCounter if lastSequenceNumber is greater then maxROCDisorder
|
|
// otherwise we already incremented for disorder
|
|
if s.lastSequenceNumber > maxROCDisorder {
|
|
s.rolloverCounter++
|
|
}
|
|
} else if s.lastSequenceNumber < maxROCDisorder && sequenceNumber > (maxSequenceNumber-maxROCDisorder) {
|
|
// Our last sequence number incremented because we crossed 0, but then our current number was within maxROCDisorder of the max
|
|
// So we fell behind, drop to account for jitter
|
|
s.rolloverCounter--
|
|
} else if sequenceNumber < maxROCDisorder && s.lastSequenceNumber > (maxSequenceNumber-maxROCDisorder) {
|
|
// our current is within a maxROCDisorder of 0
|
|
// and our last sequence number was a high sequence number, increment to account for jitter
|
|
s.rolloverCounter++
|
|
}
|
|
s.lastSequenceNumber = sequenceNumber
|
|
}
|
|
|
|
func (c *Context) getSSRCState(ssrc uint32) *ssrcState {
|
|
s, ok := c.ssrcStates[ssrc]
|
|
if ok {
|
|
return s
|
|
}
|
|
|
|
s = &ssrcState{ssrc: ssrc}
|
|
c.ssrcStates[ssrc] = s
|
|
return s
|
|
}
|