Skip to content

Commit 3eac369

Browse files
daniel@poradnik-webmastera.comsirzooro
authored andcommitted
Add support for Master Key Indicator
This adds support for Master Key Indicator (MKI). It is used to select one of pre-configured SRTP/SRTCP encryption keys. To use it, Context has to be created with MasterKeyIndicator option, it specifies MKI for master key and salt passed to CreateContext. Additional master keys/salts with their MKIs can be added using AddCipherForMKI. To remove MKIs, use RemoveMKI. All MKIs must have the same length, and use the same length of master key and salt - they use the same crypto profile. SRTP/SRTCP packets by default are encrypted using first key/salt/MKI. To select other key/salt/MKI, use SetSendMKI. key/salt/MKI used for decryption are chosen automatically, using MKI sent in encrypted SRTP/SRTCP packet.
1 parent f8540ec commit 3eac369

File tree

12 files changed

+817
-62
lines changed

12 files changed

+817
-62
lines changed

context.go

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package srtp
55

66
import (
7+
"bytes"
78
"fmt"
89

910
"github.com/pion/transport/v2/replaydetector"
@@ -56,6 +57,11 @@ type Context struct {
5657

5758
newSRTCPReplayDetector func() replaydetector.ReplayDetector
5859
newSRTPReplayDetector func() replaydetector.ReplayDetector
60+
61+
profile ProtectionProfile
62+
63+
sendMKI []byte // Master Key Identifier used for encrypting RTP/RTCP packets. Set to nil if MKI is not enabled.
64+
mkis map[string]srtpCipher // Master Key Identifier to cipher mapping. Used for decrypting packets. Empty if MKI is not enabled.
5965
}
6066

6167
// CreateContext creates a new SRTP Context.
@@ -66,52 +72,108 @@ type Context struct {
6672
//
6773
// decCtx, err := srtp.CreateContext(key, salt, profile, srtp.SRTPReplayProtection(256))
6874
func CreateContext(masterKey, masterSalt []byte, profile ProtectionProfile, opts ...ContextOption) (c *Context, err error) {
69-
keyLen, err := profile.KeyLen()
75+
c = &Context{
76+
srtpSSRCStates: map[uint32]*srtpSSRCState{},
77+
srtcpSSRCStates: map[uint32]*srtcpSSRCState{},
78+
profile: profile,
79+
mkis: map[string]srtpCipher{},
80+
}
81+
82+
for _, o := range append(
83+
[]ContextOption{ // Default options
84+
SRTPNoReplayProtection(),
85+
SRTCPNoReplayProtection(),
86+
},
87+
opts..., // User specified options
88+
) {
89+
if errOpt := o(c); errOpt != nil {
90+
return nil, errOpt
91+
}
92+
}
93+
94+
c.cipher, err = c.createCipher(c.sendMKI, masterKey, masterSalt)
7095
if err != nil {
7196
return nil, err
7297
}
98+
if len(c.sendMKI) != 0 {
99+
c.mkis[string(c.sendMKI)] = c.cipher
100+
}
101+
102+
return c, nil
103+
}
73104

74-
saltLen, err := profile.SaltLen()
105+
// AddCipherForMKI adds new MKI with associated masker key and salt. Context must be created with MasterKeyIndicator option
106+
// to enable MKI support. MKI must be unique and have the same length as the one used for creating Context.
107+
// Operation is not thread-safe, you need to provide synchronization with decrypting packets.
108+
func (c *Context) AddCipherForMKI(mki, masterKey, masterSalt []byte) error {
109+
if len(c.mkis) == 0 {
110+
return errMKIIsNotEnabled
111+
}
112+
if len(mki) == 0 || len(mki) != len(c.sendMKI) {
113+
return errInvalidMKILength
114+
}
115+
if _, ok := c.mkis[string(mki)]; ok {
116+
return errMKIAlreadyInUse
117+
}
118+
119+
cipher, err := c.createCipher(mki, masterKey, masterSalt)
120+
if err != nil {
121+
return err
122+
}
123+
c.mkis[string(mki)] = cipher
124+
return nil
125+
}
126+
127+
func (c *Context) createCipher(mki, masterKey, masterSalt []byte) (srtpCipher, error) {
128+
keyLen, err := c.profile.KeyLen()
75129
if err != nil {
76130
return nil, err
77131
}
78132

79-
if masterKeyLen := len(masterKey); masterKeyLen != keyLen {
80-
return c, fmt.Errorf("%w expected(%d) actual(%d)", errShortSrtpMasterKey, masterKey, keyLen)
81-
} else if masterSaltLen := len(masterSalt); masterSaltLen != saltLen {
82-
return c, fmt.Errorf("%w expected(%d) actual(%d)", errShortSrtpMasterSalt, saltLen, masterSaltLen)
133+
saltLen, err := c.profile.SaltLen()
134+
if err != nil {
135+
return nil, err
83136
}
84137

85-
c = &Context{
86-
srtpSSRCStates: map[uint32]*srtpSSRCState{},
87-
srtcpSSRCStates: map[uint32]*srtcpSSRCState{},
138+
if masterKeyLen := len(masterKey); masterKeyLen != keyLen {
139+
return nil, fmt.Errorf("%w expected(%d) actual(%d)", errShortSrtpMasterKey, masterKey, keyLen)
140+
} else if masterSaltLen := len(masterSalt); masterSaltLen != saltLen {
141+
return nil, fmt.Errorf("%w expected(%d) actual(%d)", errShortSrtpMasterSalt, saltLen, masterSaltLen)
88142
}
89143

90-
switch profile {
144+
switch c.profile {
91145
case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm:
92-
c.cipher, err = newSrtpCipherAeadAesGcm(profile, masterKey, masterSalt)
146+
return newSrtpCipherAeadAesGcm(c.profile, masterKey, masterSalt, mki)
93147
case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80:
94-
c.cipher, err = newSrtpCipherAesCmHmacSha1(profile, masterKey, masterSalt)
148+
return newSrtpCipherAesCmHmacSha1(c.profile, masterKey, masterSalt, mki)
95149
default:
96-
return nil, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, profile)
97-
}
98-
if err != nil {
99-
return nil, err
150+
return nil, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, c.profile)
100151
}
152+
}
101153

102-
for _, o := range append(
103-
[]ContextOption{ // Default options
104-
SRTPNoReplayProtection(),
105-
SRTCPNoReplayProtection(),
106-
},
107-
opts..., // User specified options
108-
) {
109-
if errOpt := o(c); errOpt != nil {
110-
return nil, errOpt
111-
}
154+
// RemoveMKI removes one of MKIs. You cannot remove last MKI and one used for encrypting RTP/RTCP packets.
155+
// Operation is not thread-safe, you need to provide synchronization with decrypting packets.
156+
func (c *Context) RemoveMKI(mki []byte) error {
157+
if _, ok := c.mkis[string(mki)]; !ok {
158+
return ErrMKINotFound
112159
}
160+
if bytes.Equal(mki, c.sendMKI) {
161+
return errMKIAlreadyInUse
162+
}
163+
delete(c.mkis, string(mki))
164+
return nil
165+
}
113166

114-
return c, nil
167+
// SetSendMKI switches MKI and cipher used for encrypting RTP/RTCP packets.
168+
// Operation is not thread-safe, you need to provide synchronization with encrypting packets.
169+
func (c *Context) SetSendMKI(mki []byte) error {
170+
cipher, ok := c.mkis[string(mki)]
171+
if !ok {
172+
return ErrMKINotFound
173+
}
174+
c.sendMKI = mki
175+
c.cipher = cipher
176+
return nil
115177
}
116178

117179
// https://tools.ietf.org/html/rfc3550#appendix-A.1

context_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package srtp
55

66
import (
77
"testing"
8+
9+
"github.com/stretchr/testify/assert"
810
)
911

1012
func TestContextROC(t *testing.T) {
@@ -44,3 +46,123 @@ func TestContextIndex(t *testing.T) {
4446
t.Errorf("Index is set to 100, but returned %d", index)
4547
}
4648
}
49+
50+
func TestContextWithoutMKI(t *testing.T) {
51+
c, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR)
52+
if err != nil {
53+
t.Fatal(err)
54+
}
55+
56+
err = c.AddCipherForMKI(nil, make([]byte, 16), make([]byte, 14))
57+
assert.Error(t, err)
58+
59+
err = c.AddCipherForMKI(make([]byte, 0), make([]byte, 16), make([]byte, 14))
60+
assert.Error(t, err)
61+
62+
err = c.AddCipherForMKI(make([]byte, 4), make([]byte, 16), make([]byte, 14))
63+
assert.Error(t, err)
64+
65+
err = c.SetSendMKI(nil)
66+
assert.Error(t, err)
67+
68+
err = c.SetSendMKI(make([]byte, 0))
69+
assert.Error(t, err)
70+
71+
err = c.RemoveMKI(nil)
72+
assert.Error(t, err)
73+
74+
err = c.RemoveMKI(make([]byte, 0))
75+
assert.Error(t, err)
76+
77+
err = c.RemoveMKI(make([]byte, 2))
78+
assert.Error(t, err)
79+
}
80+
81+
func TestAddMKIToContextWithMKI(t *testing.T) {
82+
mki1 := []byte{1, 2, 3, 4}
83+
mki2 := []byte{2, 3, 4, 5}
84+
85+
c, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR, MasterKeyIndicator(mki1))
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
err = c.AddCipherForMKI(mki2, make([]byte, 16), make([]byte, 14))
90+
if err != nil {
91+
t.Fatal(err)
92+
}
93+
94+
err = c.AddCipherForMKI(nil, make([]byte, 16), make([]byte, 14))
95+
assert.Error(t, err)
96+
97+
err = c.AddCipherForMKI(make([]byte, 0), make([]byte, 16), make([]byte, 14))
98+
assert.Error(t, err)
99+
100+
err = c.AddCipherForMKI(make([]byte, 3), make([]byte, 16), make([]byte, 14))
101+
assert.Error(t, err)
102+
103+
err = c.AddCipherForMKI(mki1, make([]byte, 16), make([]byte, 14))
104+
assert.Error(t, err)
105+
106+
err = c.AddCipherForMKI(mki2, make([]byte, 16), make([]byte, 14))
107+
assert.Error(t, err)
108+
}
109+
110+
func TestContextSetSendMKI(t *testing.T) {
111+
mki1 := []byte{1, 2, 3, 4}
112+
mki2 := []byte{2, 3, 4, 5}
113+
114+
c, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR, MasterKeyIndicator(mki1))
115+
if err != nil {
116+
t.Fatal(err)
117+
}
118+
err = c.AddCipherForMKI(mki2, make([]byte, 16), make([]byte, 14))
119+
if err != nil {
120+
t.Fatal(err)
121+
}
122+
123+
err = c.SetSendMKI(mki1)
124+
assert.NoError(t, err)
125+
126+
err = c.SetSendMKI(mki2)
127+
assert.NoError(t, err)
128+
129+
err = c.SetSendMKI(make([]byte, 4))
130+
assert.Error(t, err)
131+
}
132+
133+
func TestContextRemoveMKI(t *testing.T) {
134+
mki1 := []byte{1, 2, 3, 4}
135+
mki2 := []byte{2, 3, 4, 5}
136+
mki3 := []byte{3, 4, 5, 6}
137+
138+
c, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR, MasterKeyIndicator(mki1))
139+
if err != nil {
140+
t.Fatal(err)
141+
}
142+
err = c.AddCipherForMKI(mki2, make([]byte, 16), make([]byte, 14))
143+
if err != nil {
144+
t.Fatal(err)
145+
}
146+
err = c.AddCipherForMKI(mki3, make([]byte, 16), make([]byte, 14))
147+
if err != nil {
148+
t.Fatal(err)
149+
}
150+
151+
err = c.RemoveMKI(make([]byte, 4))
152+
assert.Error(t, err)
153+
154+
err = c.RemoveMKI(mki1)
155+
assert.Error(t, err)
156+
157+
err = c.SetSendMKI(mki3)
158+
assert.NoError(t, err)
159+
160+
err = c.RemoveMKI(mki1)
161+
assert.NoError(t, err)
162+
163+
err = c.RemoveMKI(mki2)
164+
assert.NoError(t, err)
165+
166+
err = c.RemoveMKI(mki3)
167+
assert.Error(t, err)
168+
}

errors.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import (
99
)
1010

1111
var (
12+
// ErrFailedToVerifyAuthTag is returned when decryption fails due to invalid authentication tag
13+
ErrFailedToVerifyAuthTag = errors.New("failed to verify auth tag")
14+
// ErrMKINotFound is returned when decryption fails due to unknown MKI value in packet
15+
ErrMKINotFound = errors.New("MKI not found")
16+
1217
errDuplicated = errors.New("duplicated packet")
1318
errShortSrtpMasterKey = errors.New("SRTP master key is not long enough")
1419
errShortSrtpMasterSalt = errors.New("SRTP master salt is not long enough")
@@ -17,13 +22,15 @@ var (
1722
errExporterWrongLabel = errors.New("exporter called with wrong label")
1823
errNoConfig = errors.New("no config provided")
1924
errNoConn = errors.New("no conn provided")
20-
errFailedToVerifyAuthTag = errors.New("failed to verify auth tag")
2125
errTooShortRTP = errors.New("packet is too short to be RTP packet")
2226
errTooShortRTCP = errors.New("packet is too short to be RTCP packet")
2327
errPayloadDiffers = errors.New("payload differs")
2428
errStartedChannelUsedIncorrectly = errors.New("started channel used incorrectly, should only be closed")
2529
errBadIVLength = errors.New("bad iv length in xorBytesCTR")
2630
errExceededMaxPackets = errors.New("exceeded the maximum number of packets")
31+
errMKIAlreadyInUse = errors.New("MKI already in use")
32+
errMKIIsNotEnabled = errors.New("MKI is not enabled")
33+
errInvalidMKILength = errors.New("invalid MKI length")
2734

2835
errStreamNotInited = errors.New("stream has not been inited, unable to close")
2936
errStreamAlreadyClosed = errors.New("stream is already closed")

option.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,16 @@ type nopReplayDetector struct{}
7171
func (s *nopReplayDetector) Check(uint64) (func(), bool) {
7272
return func() {}, true
7373
}
74+
75+
// MasterKeyIndicator sets RTP/RTCP MKI for the initial master key. Array passed as an argument will be
76+
// copied as-is to encrypted SRTP/SRTCP packets, so it must be of proper length and in Big Endian format.
77+
// All MKIs added later using Context.AddCipherForMKI must have the same length as the one used here.
78+
func MasterKeyIndicator(mki []byte) ContextOption {
79+
return func(c *Context) error {
80+
if len(mki) > 0 {
81+
c.sendMKI = make([]byte, len(mki))
82+
copy(c.sendMKI, mki)
83+
}
84+
return nil
85+
}
86+
}

srtcp.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ func (c *Context) decryptRTCP(dst, encrypted []byte) ([]byte, error) {
2323
if err != nil {
2424
return nil, err
2525
}
26-
tailOffset := len(encrypted) - (authTagLen + srtcpIndexSize)
26+
mkiLen := len(c.sendMKI)
27+
tailOffset := len(encrypted) - (authTagLen + mkiLen + srtcpIndexSize)
2728

2829
if tailOffset < aeadAuthTagLen {
2930
return nil, fmt.Errorf("%w: %d", errTooShortRTCP, len(encrypted))
@@ -40,7 +41,17 @@ func (c *Context) decryptRTCP(dst, encrypted []byte) ([]byte, error) {
4041
return nil, &duplicatedError{Proto: "srtcp", SSRC: ssrc, Index: index}
4142
}
4243

43-
out, err = c.cipher.decryptRTCP(out, encrypted, index, ssrc)
44+
cipher := c.cipher
45+
if len(c.mkis) > 0 {
46+
// Find cipher for MKI
47+
actualMKI := c.cipher.getMKI(encrypted, false)
48+
cipher, ok = c.mkis[string(actualMKI)]
49+
if !ok {
50+
return nil, ErrMKINotFound
51+
}
52+
}
53+
54+
out, err = cipher.decryptRTCP(out, encrypted, index, ssrc)
4455
if err != nil {
4556
return nil, err
4657
}

0 commit comments

Comments
 (0)