Skip to content

Commit

Permalink
Generalize ProtectionProfiles
Browse files Browse the repository at this point in the history
This removes a lot of profile specific constants and insteads
put them behind the profile. Key, Salt and TagLen need to be
determined by the profile.

Relates to #85
  • Loading branch information
Sean-Der committed Jul 16, 2020
1 parent add2176 commit d852a31
Show file tree
Hide file tree
Showing 13 changed files with 495 additions and 341 deletions.
230 changes: 19 additions & 211 deletions context.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
package srtp

import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha1" // #nosec
"encoding/binary"
"fmt"
"hash"

"github.com/pion/transport/replaydetector"
)

// 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
Expand All @@ -29,13 +15,9 @@ const (
labelSRTCPAuthenticationTag = 0x04
labelSRTCPSalt = 0x05

keyLen = 16
saltLen = 14

maxROCDisorder = 100
maxSequenceNumber = 65535

authTagSize = 10
srtcpIndexSize = 4
)

Expand All @@ -59,22 +41,10 @@ type srtcpSSRCState struct {
// 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

srtpSSRCStates map[uint32]*srtpSSRCState
srtpSessionKey []byte
srtpSessionSalt []byte
srtpSessionAuth hash.Hash
srtpSessionAuthTag []byte
srtpBlock cipher.Block
cipher srtpCipher

srtcpSSRCStates map[uint32]*srtcpSSRCState
srtcpSessionKey []byte
srtcpSessionSalt []byte
srtcpSessionAuth hash.Hash
srtcpSessionAuthTag []byte
srtcpBlock cipher.Block
srtpSSRCStates map[uint32]*srtpSSRCState
srtcpSSRCStates map[uint32]*srtcpSSRCState

newSRTCPReplayDetector func() replaydetector.ReplayDetector
newSRTPReplayDetector func() replaydetector.ReplayDetector
Expand All @@ -89,15 +59,29 @@ type Context struct {
// decCtx, err := srtp.CreateContext(key, salt, profile, srtp.SRTPReplayProtection(256))
//
func CreateContext(masterKey, masterSalt []byte, profile ProtectionProfile, opts ...ContextOption) (c *Context, err error) {
keyLen, err := profile.keyLen()
if err != nil {
return nil, err
}

saltLen, err := profile.saltLen()
if err != nil {
return nil, err
}

if masterKeyLen := len(masterKey); masterKeyLen != keyLen {
return c, fmt.Errorf("SRTP Master Key must be len %d, got %d", masterKey, keyLen)
} else if masterSaltLen := len(masterSalt); masterSaltLen != saltLen {
return c, fmt.Errorf("SRTP Salt must be len %d, got %d", saltLen, masterSaltLen)
}

sCipher, err := newSrtpCipherAesCmHmacSha1(masterKey, masterSalt)
if err != nil {
return nil, err
}

c = &Context{
masterKey: masterKey,
masterSalt: masterSalt,
cipher: sCipher,
srtpSSRCStates: map[uint32]*srtpSSRCState{},
srtcpSSRCStates: map[uint32]*srtcpSSRCState{},
}
Expand All @@ -113,185 +97,9 @@ func CreateContext(masterKey, masterSalt []byte, profile ProtectionProfile, opts
}
}

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 (s *srtpSSRCState) nextRolloverCount(sequenceNumber uint16) (uint32, func()) {
roc := s.rolloverCounter
Expand Down
106 changes: 106 additions & 0 deletions key_derivation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package srtp

import (
"crypto/aes"
"encoding/binary"
)

// All of these key derivation functions are AES-CM specific
// in the future we have multiple implementations of each of these functions

func generateSessionKey(label byte, masterKey, masterSalt []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(masterSalt))
copy(sessionKey, 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(masterKey)
if err != nil {
return nil, err
}

block.Encrypt(sessionKey, sessionKey)
return sessionKey, nil
}

func generateSessionSalt(label byte, saltLen int, masterKey, masterSalt []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(masterSalt))
copy(sessionSalt, 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(masterKey)
if err != nil {
return nil, err
}

block.Encrypt(sessionSalt, sessionSalt)
return sessionSalt[0:saltLen], nil
}

func generateSessionAuthTag(label byte, masterKey, masterSalt []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(masterSalt))
copy(sessionAuthTag, 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(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 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
}
Loading

0 comments on commit d852a31

Please sign in to comment.