Skip to content

Commit f871f43

Browse files
committed
Implement AEAD_AES_128_GCM
Resolves #85
1 parent 2f1e8b4 commit f871f43

11 files changed

+356
-91
lines changed

context.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,23 @@ func CreateContext(masterKey, masterSalt []byte, profile ProtectionProfile, opts
7575
return c, fmt.Errorf("SRTP Salt must be len %d, got %d", saltLen, masterSaltLen)
7676
}
7777

78-
sCipher, err := newSrtpCipherAesCmHmacSha1(masterKey, masterSalt)
79-
if err != nil {
80-
return nil, err
81-
}
82-
8378
c = &Context{
84-
cipher: sCipher,
8579
srtpSSRCStates: map[uint32]*srtpSSRCState{},
8680
srtcpSSRCStates: map[uint32]*srtcpSSRCState{},
8781
}
82+
83+
switch profile {
84+
case ProtectionProfileAeadAes128Gcm:
85+
c.cipher, err = newSrtpCipherAeadAesGcm(masterKey, masterSalt)
86+
case ProtectionProfileAes128CmHmacSha1_80:
87+
c.cipher, err = newSrtpCipherAesCmHmacSha1(masterKey, masterSalt)
88+
default:
89+
return nil, fmt.Errorf("no such SRTP Profile %#v", profile)
90+
}
91+
if err != nil {
92+
return nil, err
93+
}
94+
8895
for _, o := range append(
8996
[]ContextOption{ // Default options
9097
SRTPNoReplayProtection(),

key_derivation.go

Lines changed: 19 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,84 +3,42 @@ package srtp
33
import (
44
"crypto/aes"
55
"encoding/binary"
6+
"errors"
67
)
78

8-
// All of these key derivation functions are AES-CM specific
9-
// in the future we have multiple implementations of each of these functions
9+
func aesCmKeyDerivation(label byte, masterKey, masterSalt []byte, indexOverKdr int, outLen int) ([]byte, error) {
10+
if indexOverKdr != 0 {
11+
// 24-bit "index DIV kdr" must be xored to prf input.
12+
return nil, errors.New("indexOverKdr > 0 is not supported yet")
13+
}
1014

11-
func generateSessionKey(label byte, masterKey, masterSalt []byte) ([]byte, error) {
1215
// https://tools.ietf.org/html/rfc3711#appendix-B.3
1316
// The input block for AES-CM is generated by exclusive-oring the master salt with the
1417
// concatenation of the encryption key label 0x00 with (index DIV kdr),
1518
// - index is 'rollover count' and DIV is 'divided by'
16-
sessionKey := make([]byte, len(masterSalt))
17-
copy(sessionKey, masterSalt)
18-
19-
labelAndIndexOverKdr := []byte{label, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
20-
for i, j := len(labelAndIndexOverKdr)-1, len(sessionKey)-1; i >= 0; i, j = i-1, j-1 {
21-
sessionKey[j] = sessionKey[j] ^ labelAndIndexOverKdr[i]
22-
}
2319

24-
// then padding on the right with two null octets (which implements the multiply-by-2^16 operation, see Section 4.3.3).
25-
sessionKey = append(sessionKey, []byte{0x00, 0x00}...)
20+
nMasterKey := len(masterKey)
21+
nMasterSalt := len(masterSalt)
2622

27-
//The resulting value is then AES-CM- encrypted using the master key to get the cipher key.
28-
block, err := aes.NewCipher(masterKey)
29-
if err != nil {
30-
return nil, err
31-
}
32-
33-
block.Encrypt(sessionKey, sessionKey)
34-
return sessionKey, nil
35-
}
36-
37-
func generateSessionSalt(label byte, masterKey, masterSalt []byte) ([]byte, error) {
38-
// https://tools.ietf.org/html/rfc3711#appendix-B.3
39-
// The input block for AES-CM is generated by exclusive-oring the master salt with
40-
// the concatenation of the encryption salt label
41-
sessionSalt := make([]byte, len(masterSalt))
42-
copy(sessionSalt, masterSalt)
23+
prfIn := make([]byte, nMasterKey)
24+
copy(prfIn[:nMasterSalt], masterSalt)
4325

44-
labelAndIndexOverKdr := []byte{label, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
45-
for i, j := len(labelAndIndexOverKdr)-1, len(sessionSalt)-1; i >= 0; i, j = i-1, j-1 {
46-
sessionSalt[j] = sessionSalt[j] ^ labelAndIndexOverKdr[i]
47-
}
26+
prfIn[7] ^= label
4827

49-
// That value is padded and encrypted as above.
50-
sessionSalt = append(sessionSalt, []byte{0x00, 0x00}...)
28+
//The resulting value is then AES encrypted using the master key to get the cipher key.
5129
block, err := aes.NewCipher(masterKey)
5230
if err != nil {
5331
return nil, err
5432
}
5533

56-
block.Encrypt(sessionSalt, sessionSalt)
57-
return sessionSalt[0:len(masterSalt)], nil
58-
}
59-
60-
func generateSessionAuthTag(label byte, masterKey, masterSalt []byte) ([]byte, error) {
61-
// https://tools.ietf.org/html/rfc3711#appendix-B.3
62-
// We now show how the auth key is generated. The input block for AES-
63-
// CM is generated as above, but using the authentication key label.
64-
sessionAuthTag := make([]byte, len(masterSalt))
65-
copy(sessionAuthTag, masterSalt)
66-
67-
labelAndIndexOverKdr := []byte{label, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
68-
for i, j := len(labelAndIndexOverKdr)-1, len(sessionAuthTag)-1; i >= 0; i, j = i-1, j-1 {
69-
sessionAuthTag[j] = sessionAuthTag[j] ^ labelAndIndexOverKdr[i]
34+
out := make([]byte, ((outLen+nMasterKey)/nMasterKey)*nMasterKey)
35+
var i uint16
36+
for n := 0; n < outLen; n += nMasterKey {
37+
binary.BigEndian.PutUint16(prfIn[nMasterKey-2:], i)
38+
block.Encrypt(out[n:n+nMasterKey], prfIn)
39+
i++
7040
}
71-
72-
// That value is padded and encrypted as above.
73-
// - We need to do multiple runs at key size (20) is larger then source
74-
firstRun := append(sessionAuthTag, []byte{0x00, 0x00}...)
75-
secondRun := append(sessionAuthTag, []byte{0x00, 0x01}...)
76-
block, err := aes.NewCipher(masterKey)
77-
if err != nil {
78-
return nil, err
79-
}
80-
81-
block.Encrypt(firstRun, firstRun)
82-
block.Encrypt(secondRun, secondRun)
83-
return append(firstRun, secondRun[:4]...), nil
41+
return out[:outLen], nil
8442
}
8543

8644
// Generate IV https://tools.ietf.org/html/rfc3711#section-4.1.1

key_derivation_test.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package srtp
33
import (
44
"bytes"
55
"testing"
6+
7+
"github.com/stretchr/testify/assert"
68
)
79

810
func TestValidSessionKeys(t *testing.T) {
@@ -13,24 +15,34 @@ func TestValidSessionKeys(t *testing.T) {
1315
expectedSessionSalt := []byte{0x30, 0xCB, 0xBC, 0x08, 0x86, 0x3D, 0x8C, 0x85, 0xD4, 0x9D, 0xB3, 0x4A, 0x9A, 0xE1}
1416
expectedSessionAuthTag := []byte{0xCE, 0xBE, 0x32, 0x1F, 0x6F, 0xF7, 0x71, 0x6B, 0x6F, 0xD4, 0xAB, 0x49, 0xAF, 0x25, 0x6A, 0x15, 0x6D, 0x38, 0xBA, 0xA4}
1517

16-
sessionKey, err := generateSessionKey(labelSRTPEncryption, masterKey, masterSalt)
18+
sessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey))
1719
if err != nil {
1820
t.Errorf("generateSessionKey failed: %v", err)
1921
} else if !bytes.Equal(sessionKey, expectedSessionKey) {
2022
t.Errorf("Session Key % 02x does not match expected % 02x", sessionKey, expectedSessionKey)
2123
}
2224

23-
sessionSalt, err := generateSessionSalt(labelSRTPSalt, masterKey, masterSalt)
25+
sessionSalt, err := aesCmKeyDerivation(labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt))
2426
if err != nil {
2527
t.Errorf("generateSessionSalt failed: %v", err)
2628
} else if !bytes.Equal(sessionSalt, expectedSessionSalt) {
2729
t.Errorf("Session Salt % 02x does not match expected % 02x", sessionSalt, expectedSessionSalt)
2830
}
2931

30-
sessionAuthTag, err := generateSessionAuthTag(labelSRTPAuthenticationTag, masterKey, masterSalt)
32+
authKeyLen, err := ProtectionProfileAes128CmHmacSha1_80.authKeyLen()
33+
assert.NoError(t, err)
34+
35+
sessionAuthTag, err := aesCmKeyDerivation(labelSRTPAuthenticationTag, masterKey, masterSalt, 0, authKeyLen)
3136
if err != nil {
3237
t.Errorf("generateSessionAuthTag failed: %v", err)
3338
} else if !bytes.Equal(sessionAuthTag, expectedSessionAuthTag) {
3439
t.Errorf("Session Auth Tag % 02x does not match expected % 02x", sessionAuthTag, expectedSessionAuthTag)
3540
}
3641
}
42+
43+
// This test asserts that calling aesCmKeyDerivation with a non-zero indexOverKdr fails
44+
// Currently this isn't supported, but the API makes sure we can add this in the future
45+
func TestIndexOverKDR(t *testing.T) {
46+
_, err := aesCmKeyDerivation(labelSRTPAuthenticationTag, []byte{}, []byte{}, 1, 0)
47+
assert.Error(t, err)
48+
}

protection_profile.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ type ProtectionProfile uint16
88
// Supported protection profiles
99
const (
1010
ProtectionProfileAes128CmHmacSha1_80 ProtectionProfile = 0x0001
11+
ProtectionProfileAeadAes128Gcm ProtectionProfile = 0x0007
1112
)
1213

1314
func (p ProtectionProfile) keyLen() (int, error) {
1415
switch p {
1516
case ProtectionProfileAes128CmHmacSha1_80:
17+
fallthrough
18+
case ProtectionProfileAeadAes128Gcm:
1619
return 16, nil
1720
default:
1821
return 0, fmt.Errorf("no such ProtectionProfile %#v", p)
@@ -23,6 +26,8 @@ func (p ProtectionProfile) saltLen() (int, error) {
2326
switch p {
2427
case ProtectionProfileAes128CmHmacSha1_80:
2528
return 14, nil
29+
case ProtectionProfileAeadAes128Gcm:
30+
return 12, nil
2631
default:
2732
return 0, fmt.Errorf("no such ProtectionProfile %#v", p)
2833
}
@@ -31,7 +36,20 @@ func (p ProtectionProfile) saltLen() (int, error) {
3136
func (p ProtectionProfile) authTagLen() (int, error) {
3237
switch p {
3338
case ProtectionProfileAes128CmHmacSha1_80:
34-
return 10, nil
39+
return (&srtpCipherAesCmHmacSha1{}).authTagLen(), nil
40+
case ProtectionProfileAeadAes128Gcm:
41+
return (&srtpCipherAeadAesGcm{}).authTagLen(), nil
42+
default:
43+
return 0, fmt.Errorf("no such ProtectionProfile %#v", p)
44+
}
45+
}
46+
47+
func (p ProtectionProfile) authKeyLen() (int, error) {
48+
switch p {
49+
case ProtectionProfileAes128CmHmacSha1_80:
50+
return 20, nil
51+
case ProtectionProfileAeadAes128Gcm:
52+
return 0, nil
3553
default:
3654
return 0, fmt.Errorf("no such ProtectionProfile %#v", p)
3755
}

srtcp.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package srtp
22

33
import (
44
"encoding/binary"
5+
"fmt"
56

67
"github.com/pion/rtcp"
78
)
@@ -10,14 +11,15 @@ const maxSRTCPIndex = 0x7FFFFFFF
1011

1112
func (c *Context) decryptRTCP(dst, encrypted []byte) ([]byte, error) {
1213
out := allocateIfMismatch(dst, encrypted)
13-
1414
tailOffset := len(encrypted) - (c.cipher.authTagLen() + srtcpIndexSize)
15-
if isEncrypted := encrypted[tailOffset] >> 7; isEncrypted == 0 {
15+
16+
if tailOffset < 0 {
17+
return nil, fmt.Errorf("%d is too short to be a valid RTCP packet", len(encrypted))
18+
} else if isEncrypted := encrypted[tailOffset] >> 7; isEncrypted == 0 {
1619
return out, nil
1720
}
1821

19-
srtcpIndexBuffer := encrypted[tailOffset : tailOffset+srtcpIndexSize]
20-
index := binary.BigEndian.Uint32(srtcpIndexBuffer) &^ (1 << 31)
22+
index := c.cipher.getRTCPIndex(encrypted)
2123
ssrc := binary.BigEndian.Uint32(encrypted[4:])
2224

2325
s := c.getSRTCPSSRCState(ssrc)
@@ -49,8 +51,7 @@ func (c *Context) DecryptRTCP(dst, encrypted []byte, header *rtcp.Header) ([]byt
4951
}
5052

5153
func (c *Context) encryptRTCP(dst, decrypted []byte) ([]byte, error) {
52-
out := allocateIfMismatch(dst, decrypted)
53-
ssrc := binary.BigEndian.Uint32(out[4:])
54+
ssrc := binary.BigEndian.Uint32(decrypted[4:])
5455
s := c.getSRTCPSSRCState(ssrc)
5556

5657
// We roll over early because MSB is used for marking as encrypted
@@ -59,7 +60,7 @@ func (c *Context) encryptRTCP(dst, decrypted []byte) ([]byte, error) {
5960
s.srtcpIndex = 0
6061
}
6162

62-
return c.cipher.encryptRTCP(out, decrypted, s.srtcpIndex, ssrc)
63+
return c.cipher.encryptRTCP(dst, decrypted, s.srtcpIndex, ssrc)
6364
}
6465

6566
// EncryptRTCP Encrypts a RTCP packet

srtp.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,17 @@ func (c *Context) EncryptRTP(dst []byte, plaintext []byte, header *rtp.Header) (
4747
header = &rtp.Header{}
4848
}
4949

50-
err := header.Unmarshal(plaintext)
51-
if err != nil {
50+
if err := header.Unmarshal(plaintext); err != nil {
5251
return nil, err
5352
}
5453

5554
return c.encryptRTP(dst, header, plaintext[header.PayloadOffset:])
5655
}
5756

5857
// encryptRTP marshals and encrypts an RTP packet, writing to the dst buffer provided.
59-
// If the dst buffer does not have the capacity to hold `len(plaintext) + 10` bytes, a new one will be allocated and returned.
58+
// If the dst buffer does not have the capacity, a new one will be allocated and returned.
6059
// Similar to above but faster because it can avoid unmarshaling the header and marshaling the payload.
6160
func (c *Context) encryptRTP(dst []byte, header *rtp.Header, payload []byte) (ciphertext []byte, err error) {
62-
// Grow the given buffer to fit the output.
63-
dst = growBufferSize(dst, header.MarshalSize()+len(payload)+c.cipher.authTagLen())
64-
6561
s := c.getSRTPSSRCState(header.SSRC)
6662
roc, updateROC := s.nextRolloverCount(header.SequenceNumber)
6763
updateROC()

srtp_cipher.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "github.com/pion/rtp"
66
// of the SRTP Specific ciphers
77
type srtpCipher interface {
88
authTagLen() int
9+
getRTCPIndex([]byte) uint32
910

1011
encryptRTP([]byte, *rtp.Header, []byte, uint32) ([]byte, error)
1112
encryptRTCP([]byte, []byte, uint32, uint32) ([]byte, error)

0 commit comments

Comments
 (0)