webrtc_srtp/
key_derivation.rs

1use aes::cipher::BlockEncrypt;
2use aes::Aes256;
3use aes::{cipher::generic_array::GenericArray, Aes128};
4use aes_gcm::KeyInit;
5
6use crate::error::{Error, Result};
7
8pub const LABEL_SRTP_ENCRYPTION: u8 = 0x00;
9pub const LABEL_SRTP_AUTHENTICATION_TAG: u8 = 0x01;
10pub const LABEL_SRTP_SALT: u8 = 0x02;
11pub const LABEL_SRTCP_ENCRYPTION: u8 = 0x03;
12pub const LABEL_SRTCP_AUTHENTICATION_TAG: u8 = 0x04;
13pub const LABEL_SRTCP_SALT: u8 = 0x05;
14
15pub(crate) const SRTCP_INDEX_SIZE: usize = 4;
16
17pub(crate) fn aes_cm_key_derivation(
18    label: u8,
19    master_key: &[u8],
20    master_salt: &[u8],
21    index_over_kdr: usize,
22    out_len: usize,
23) -> Result<Vec<u8>> {
24    if index_over_kdr != 0 {
25        // 24-bit "index DIV kdr" must be xored to prf input.
26        return Err(Error::UnsupportedIndexOverKdr);
27    }
28
29    // https://tools.ietf.org/html/rfc3711#appendix-B.3
30    // The input block for AES-CM is generated by exclusive-oring the master salt with the
31    // concatenation of the encryption key label 0x00 with (index DIV kdr),
32    // - index is 'rollover count' and DIV is 'divided by'
33
34    let n_master_key = master_key.len();
35    let n_master_salt = master_salt.len();
36
37    let mut prf_in = vec![0u8; n_master_key];
38    prf_in[..n_master_salt].copy_from_slice(master_salt);
39
40    prf_in[7] ^= label;
41
42    //The resulting value is then AES encrypted using the master key to get the cipher key.
43    let key = GenericArray::from_slice(master_key);
44    let block = Aes128::new(key);
45
46    let mut out = vec![0u8; ((out_len + n_master_key) / n_master_key) * n_master_key];
47    for (i, n) in (0..out_len).step_by(n_master_key).enumerate() {
48        //BigEndian.PutUint16(prfIn[nMasterKey-2:], i)
49        prf_in[n_master_key - 2] = ((i >> 8) & 0xFF) as u8;
50        prf_in[n_master_key - 1] = (i & 0xFF) as u8;
51
52        out[n..n + n_master_key].copy_from_slice(&prf_in);
53        let out_key = GenericArray::from_mut_slice(&mut out[n..n + 16]);
54        block.encrypt_block(out_key);
55    }
56
57    Ok(out[..out_len].to_vec())
58}
59
60// As per https://datatracker.ietf.org/doc/html/rfc6188
61// The key derivation rate is zero as per https://datatracker.ietf.org/doc/html/rfc5764 hence index_over-kdr is 0
62const AES_256_BS: usize = 16;
63pub(crate) fn aes_256_cm_key_derivation(
64    label: u8,
65    master_key: &[u8],
66    master_salt: &[u8],
67    index_over_kdr: usize,
68    out_len: usize,
69) -> Result<Vec<u8>> {
70    if index_over_kdr != 0 {
71        // 24-bit "index DIV kdr" must be xored to prf input.
72        return Err(Error::UnsupportedIndexOverKdr);
73    }
74
75    if master_key.len() != 32 {
76        return Err(Error::InvalidMasterKeyLength);
77    }
78
79    if master_salt.len() > 14 {
80        return Err(Error::InvalidMasterSaltLength);
81    }
82
83    if out_len > 32 {
84        return Err(Error::UnsupportedOutLength);
85    }
86
87    let mut prf_in = [0; AES_256_BS];
88    prf_in[7] = label;
89    prf_in[8..12].copy_from_slice((index_over_kdr as u32).to_be_bytes().as_slice());
90
91    for (i, x) in prf_in.iter_mut().enumerate() {
92        *x ^= master_salt.get(i).unwrap_or(&0);
93    }
94
95    //The resulting value is then AES encrypted using the master key to get the cipher key.
96    let key = GenericArray::from_slice(master_key);
97    let block = Aes256::new(key);
98
99    let mut out = vec![0u8; ((out_len + AES_256_BS) / AES_256_BS) * AES_256_BS];
100    for (i, n) in (0..out_len).step_by(AES_256_BS).enumerate() {
101        prf_in[AES_256_BS - 2..].copy_from_slice(&((i as u16).to_be_bytes()));
102
103        out[n..n + AES_256_BS].copy_from_slice(&prf_in);
104        let out_key = GenericArray::from_mut_slice(&mut out[n..n + 16]);
105        block.encrypt_block(out_key);
106    }
107
108    Ok(out[..out_len].to_vec())
109}
110
111/// Generate IV https://tools.ietf.org/html/rfc3711#section-4.1.1
112/// where the 128-bit integer value IV SHALL be defined by the SSRC, the
113/// SRTP packet index i, and the SRTP session salting key k_s, as below.
114/// ROC = a 32-bit unsigned rollover counter (roc), which records how many
115/// times the 16-bit RTP sequence number has been reset to zero after
116/// passing through 65,535
117/// ```nobuild
118/// i = 2^16 * roc + SEQ
119/// IV = (salt*2 ^ 16) | (ssrc*2 ^ 64) | (i*2 ^ 16)
120/// ```
121pub(crate) fn generate_counter(
122    sequence_number: u16,
123    rollover_counter: u32,
124    ssrc: u32,
125    session_salt: &[u8],
126) -> [u8; 16] {
127    assert!(session_salt.len() <= 16);
128
129    let mut counter = [0; 16];
130
131    let ssrc_be = ssrc.to_be_bytes();
132    let rollover_be = rollover_counter.to_be_bytes();
133    let seq_be = ((sequence_number as u32) << 16).to_be_bytes();
134
135    counter[4..8].copy_from_slice(&ssrc_be);
136    counter[8..12].copy_from_slice(&rollover_be);
137    counter[12..16].copy_from_slice(&seq_be);
138
139    for i in 0..session_salt.len() {
140        counter[i] ^= session_salt[i];
141    }
142
143    counter
144}
145
146#[cfg(test)]
147mod test {
148    use super::*;
149    use crate::protection_profile::*;
150
151    #[test]
152    fn test_valid_session_keys() -> Result<()> {
153        // Key Derivation Test Vectors from https://tools.ietf.org/html/rfc3711#appendix-B.3
154        let master_key = vec![
155            0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE,
156            0x41, 0x39,
157        ];
158        let master_salt = vec![
159            0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6,
160        ];
161
162        let expected_session_key = vec![
163            0xC6, 0x1E, 0x7A, 0x93, 0x74, 0x4F, 0x39, 0xEE, 0x10, 0x73, 0x4A, 0xFE, 0x3F, 0xF7,
164            0xA0, 0x87,
165        ];
166        let expected_session_salt = vec![
167            0x30, 0xCB, 0xBC, 0x08, 0x86, 0x3D, 0x8C, 0x85, 0xD4, 0x9D, 0xB3, 0x4A, 0x9A, 0xE1,
168        ];
169        let expected_session_auth_tag = vec![
170            0xCE, 0xBE, 0x32, 0x1F, 0x6F, 0xF7, 0x71, 0x6B, 0x6F, 0xD4, 0xAB, 0x49, 0xAF, 0x25,
171            0x6A, 0x15, 0x6D, 0x38, 0xBA, 0xA4,
172        ];
173
174        let session_key = aes_cm_key_derivation(
175            LABEL_SRTP_ENCRYPTION,
176            &master_key,
177            &master_salt,
178            0,
179            master_key.len(),
180        )?;
181        assert_eq!(
182            session_key, expected_session_key,
183            "Session Key:\n{session_key:?} \ndoes not match expected:\n{expected_session_key:?}\nMaster Key:\n{master_key:?}\nMaster Salt:\n{master_salt:?}\n",
184        );
185
186        let session_salt = aes_cm_key_derivation(
187            LABEL_SRTP_SALT,
188            &master_key,
189            &master_salt,
190            0,
191            master_salt.len(),
192        )?;
193        assert_eq!(
194            session_salt, expected_session_salt,
195            "Session Salt {session_salt:?} does not match expected {expected_session_salt:?}"
196        );
197
198        let auth_key_len = ProtectionProfile::Aes128CmHmacSha1_80.auth_key_len();
199
200        let session_auth_tag = aes_cm_key_derivation(
201            LABEL_SRTP_AUTHENTICATION_TAG,
202            &master_key,
203            &master_salt,
204            0,
205            auth_key_len,
206        )?;
207        assert_eq!(
208            session_auth_tag, expected_session_auth_tag,
209            "Session Auth Tag {session_auth_tag:?} does not match expected {expected_session_auth_tag:?}",
210        );
211
212        Ok(())
213    }
214
215    // This test asserts that calling aesCmKeyDerivation with a non-zero indexOverKdr fails
216    // Currently this isn't supported, but the API makes sure we can add this in the future
217    #[test]
218    fn test_index_over_kdr() -> Result<()> {
219        let result = aes_cm_key_derivation(LABEL_SRTP_AUTHENTICATION_TAG, &[], &[], 1, 0);
220        assert!(result.is_err());
221
222        Ok(())
223    }
224
225    #[test]
226    fn test_aes_256_cm_key_derivation() -> Result<()> {
227        // Key Derivation Test Vectors from https://datatracker.ietf.org/doc/html/rfc6188#section-7.2
228        let master_key = vec![
229            0xF0, 0xF0, 0x49, 0x14, 0xB5, 0x13, 0xF2, 0x76, 0x3A, 0x1B, 0x1F, 0xA1, 0x30, 0xF1,
230            0x0E, 0x29, 0x98, 0xF6, 0xF6, 0xE4, 0x3E, 0x43, 0x09, 0xD1, 0xE6, 0x22, 0xA0, 0xE3,
231            0x32, 0xB9, 0xF1, 0xB6,
232        ];
233        let master_salt = vec![
234            0x3B, 0x04, 0x80, 0x3D, 0xE5, 0x1E, 0xE7, 0xC9, 0x64, 0x23, 0xAB, 0x5B, 0x78, 0xD2,
235        ];
236
237        let expected_session_key = vec![
238            0x5B, 0xA1, 0x06, 0x4E, 0x30, 0xEC, 0x51, 0x61, 0x3C, 0xAD, 0x92, 0x6C, 0x5A, 0x28,
239            0xEF, 0x73, 0x1E, 0xC7, 0xFB, 0x39, 0x7F, 0x70, 0xA9, 0x60, 0x65, 0x3C, 0xAF, 0x06,
240            0x55, 0x4C, 0xD8, 0xC4,
241        ];
242        let expected_session_salt = vec![
243            0xFA, 0x31, 0x79, 0x16, 0x85, 0xCA, 0x44, 0x4A, 0x9E, 0x07, 0xC6, 0xC6, 0x4E, 0x93,
244        ];
245        let expected_session_auth_tag = vec![
246            0xFD, 0x9C, 0x32, 0xD3, 0x9E, 0xD5, 0xFB, 0xB5, 0xA9, 0xDC, 0x96, 0xB3, 0x08, 0x18,
247            0x45, 0x4D, 0x13, 0x13, 0xDC, 0x05,
248        ];
249
250        let session_key = aes_256_cm_key_derivation(
251            LABEL_SRTP_ENCRYPTION,
252            &master_key,
253            &master_salt,
254            0,
255            master_key.len(),
256        )?;
257        assert_eq!(
258            session_key, expected_session_key,
259            "Session Key:\n{session_key:?} \ndoes not match expected:\n{expected_session_key:?}\nMaster Key:\n{master_key:?}\nMaster Salt:\n{master_salt:?}\n",
260        );
261
262        let session_salt = aes_256_cm_key_derivation(
263            LABEL_SRTP_SALT,
264            &master_key,
265            &master_salt,
266            0,
267            master_salt.len(),
268        )?;
269        assert_eq!(
270            session_salt, expected_session_salt,
271            "Session Salt {session_salt:?} does not match expected {expected_session_salt:?}"
272        );
273
274        let auth_key_len = ProtectionProfile::Aes128CmHmacSha1_80.auth_key_len();
275
276        let session_auth_tag = aes_256_cm_key_derivation(
277            LABEL_SRTP_AUTHENTICATION_TAG,
278            &master_key,
279            &master_salt,
280            0,
281            auth_key_len,
282        )?;
283        assert_eq!(
284            session_auth_tag, expected_session_auth_tag,
285            "Session Auth Tag {session_auth_tag:?} does not match expected {expected_session_auth_tag:?}",
286        );
287
288        Ok(())
289    }
290}