xref: /webrtc/srtp/src/key_derivation.rs (revision 5d8fe953)
1 use aes::cipher::generic_array::GenericArray;
2 use aes::cipher::NewBlockCipher;
3 use aes::{Aes128, BlockEncrypt};
4 
5 use byteorder::{BigEndian, WriteBytesExt};
6 use std::io::BufWriter;
7 
8 use crate::error::{Error, Result};
9 
10 pub const LABEL_SRTP_ENCRYPTION: u8 = 0x00;
11 pub const LABEL_SRTP_AUTHENTICATION_TAG: u8 = 0x01;
12 pub const LABEL_SRTP_SALT: u8 = 0x02;
13 pub const LABEL_SRTCP_ENCRYPTION: u8 = 0x03;
14 pub const LABEL_SRTCP_AUTHENTICATION_TAG: u8 = 0x04;
15 pub const LABEL_SRTCP_SALT: u8 = 0x05;
16 
17 pub(crate) const SRTCP_INDEX_SIZE: usize = 4;
18 
aes_cm_key_derivation( label: u8, master_key: &[u8], master_salt: &[u8], index_over_kdr: usize, out_len: usize, ) -> Result<Vec<u8>>19 pub(crate) fn aes_cm_key_derivation(
20     label: u8,
21     master_key: &[u8],
22     master_salt: &[u8],
23     index_over_kdr: usize,
24     out_len: usize,
25 ) -> Result<Vec<u8>> {
26     if index_over_kdr != 0 {
27         // 24-bit "index DIV kdr" must be xored to prf input.
28         return Err(Error::UnsupportedIndexOverKdr);
29     }
30 
31     // https://tools.ietf.org/html/rfc3711#appendix-B.3
32     // The input block for AES-CM is generated by exclusive-oring the master salt with the
33     // concatenation of the encryption key label 0x00 with (index DIV kdr),
34     // - index is 'rollover count' and DIV is 'divided by'
35 
36     let n_master_key = master_key.len();
37     let n_master_salt = master_salt.len();
38 
39     let mut prf_in = vec![0u8; n_master_key];
40     prf_in[..n_master_salt].copy_from_slice(master_salt);
41 
42     prf_in[7] ^= label;
43 
44     //The resulting value is then AES encrypted using the master key to get the cipher key.
45     let key = GenericArray::from_slice(master_key);
46     let block = Aes128::new(key);
47 
48     let mut out = vec![0u8; ((out_len + n_master_key) / n_master_key) * n_master_key];
49     for (i, n) in (0..out_len).step_by(n_master_key).enumerate() {
50         //BigEndian.PutUint16(prfIn[nMasterKey-2:], i)
51         prf_in[n_master_key - 2] = ((i >> 8) & 0xFF) as u8;
52         prf_in[n_master_key - 1] = (i & 0xFF) as u8;
53 
54         out[n..n + n_master_key].copy_from_slice(&prf_in);
55         let out_key = GenericArray::from_mut_slice(&mut out[n..n + n_master_key]);
56         block.encrypt_block(out_key);
57     }
58 
59     Ok(out[..out_len].to_vec())
60 }
61 
62 /// Generate IV https://tools.ietf.org/html/rfc3711#section-4.1.1
63 /// where the 128-bit integer value IV SHALL be defined by the SSRC, the
64 /// SRTP packet index i, and the SRTP session salting key k_s, as below.
65 /// ROC = a 32-bit unsigned rollover counter (roc), which records how many
66 /// times the 16-bit RTP sequence number has been reset to zero after
67 /// passing through 65,535
68 /// ```nobuild
69 /// i = 2^16 * roc + SEQ
70 /// IV = (salt*2 ^ 16) | (ssrc*2 ^ 64) | (i*2 ^ 16)
71 /// ```
generate_counter( sequence_number: u16, rollover_counter: u32, ssrc: u32, session_salt: &[u8], ) -> Result<Vec<u8>>72 pub(crate) fn generate_counter(
73     sequence_number: u16,
74     rollover_counter: u32,
75     ssrc: u32,
76     session_salt: &[u8],
77 ) -> Result<Vec<u8>> {
78     assert!(session_salt.len() <= 16);
79 
80     let mut counter: Vec<u8> = vec![0; 16];
81     {
82         let mut writer = BufWriter::<&mut [u8]>::new(counter[4..].as_mut());
83         writer.write_u32::<BigEndian>(ssrc)?;
84         writer.write_u32::<BigEndian>(rollover_counter)?;
85         writer.write_u32::<BigEndian>((sequence_number as u32) << 16)?;
86     }
87 
88     for i in 0..session_salt.len() {
89         counter[i] ^= session_salt[i];
90     }
91 
92     Ok(counter)
93 }
94 
95 #[cfg(test)]
96 mod test {
97     use super::*;
98     use crate::protection_profile::*;
99 
100     #[test]
test_valid_session_keys() -> Result<()>101     fn test_valid_session_keys() -> Result<()> {
102         // Key Derivation Test Vectors from https://tools.ietf.org/html/rfc3711#appendix-B.3
103         let master_key = vec![
104             0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE,
105             0x41, 0x39,
106         ];
107         let master_salt = vec![
108             0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6,
109         ];
110 
111         let expected_session_key = vec![
112             0xC6, 0x1E, 0x7A, 0x93, 0x74, 0x4F, 0x39, 0xEE, 0x10, 0x73, 0x4A, 0xFE, 0x3F, 0xF7,
113             0xA0, 0x87,
114         ];
115         let expected_session_salt = vec![
116             0x30, 0xCB, 0xBC, 0x08, 0x86, 0x3D, 0x8C, 0x85, 0xD4, 0x9D, 0xB3, 0x4A, 0x9A, 0xE1,
117         ];
118         let expected_session_auth_tag = vec![
119             0xCE, 0xBE, 0x32, 0x1F, 0x6F, 0xF7, 0x71, 0x6B, 0x6F, 0xD4, 0xAB, 0x49, 0xAF, 0x25,
120             0x6A, 0x15, 0x6D, 0x38, 0xBA, 0xA4,
121         ];
122 
123         let session_key = aes_cm_key_derivation(
124             LABEL_SRTP_ENCRYPTION,
125             &master_key,
126             &master_salt,
127             0,
128             master_key.len(),
129         )?;
130         assert_eq!(
131             session_key, expected_session_key,
132             "Session Key:\n{session_key:?} \ndoes not match expected:\n{expected_session_key:?}\nMaster Key:\n{master_key:?}\nMaster Salt:\n{master_salt:?}\n",
133         );
134 
135         let session_salt = aes_cm_key_derivation(
136             LABEL_SRTP_SALT,
137             &master_key,
138             &master_salt,
139             0,
140             master_salt.len(),
141         )?;
142         assert_eq!(
143             session_salt, expected_session_salt,
144             "Session Salt {session_salt:?} does not match expected {expected_session_salt:?}"
145         );
146 
147         let auth_key_len = ProtectionProfile::Aes128CmHmacSha1_80.auth_key_len();
148 
149         let session_auth_tag = aes_cm_key_derivation(
150             LABEL_SRTP_AUTHENTICATION_TAG,
151             &master_key,
152             &master_salt,
153             0,
154             auth_key_len,
155         )?;
156         assert_eq!(
157             session_auth_tag, expected_session_auth_tag,
158             "Session Auth Tag {session_auth_tag:?} does not match expected {expected_session_auth_tag:?}",
159         );
160 
161         Ok(())
162     }
163 
164     // This test asserts that calling aesCmKeyDerivation with a non-zero indexOverKdr fails
165     // Currently this isn't supported, but the API makes sure we can add this in the future
166     #[test]
test_index_over_kdr() -> Result<()>167     fn test_index_over_kdr() -> Result<()> {
168         let result = aes_cm_key_derivation(LABEL_SRTP_AUTHENTICATION_TAG, &[], &[], 1, 0);
169         assert!(result.is_err());
170 
171         Ok(())
172     }
173 }
174