/
context.go
296 lines (247 loc) · 10.1 KB
/
context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
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
}