srt_protocol/options/encryption.rs
1use std::{
2 convert::TryFrom,
3 fmt::{self, Debug, Display, Formatter},
4};
5
6use super::*;
7
8// https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-6
9// https://github.com/Haivision/srt/blob/master/docs/features/encryption.md
10#[derive(Clone, Default, Debug, Eq, PartialEq)]
11pub struct Encryption {
12 // TODO: support unspecified key length
13 // also check to ensure we implement key negotiation algorithm correctly
14 /// SRTO_PBKEYLEN
15 ///
16 /// Encryption key length.
17 ///
18 /// Possible values:
19 ///
20 /// 0 = PBKEYLEN (default value)
21 /// 16 = AES-128 (effective value)
22 /// 24 = AES-192
23 /// 32 = AES-256
24 ///
25 /// The use is slightly different in 1.2.0 (HSv4), and since 1.3.0 (HSv5):
26 ///
27 /// HSv4: This is set on the sender and enables encryption, if not 0. The receiver shall not set
28 /// it and will agree on the length as defined by the sender.
29 ///
30 /// HSv5: The "default value" for PBKEYLEN is 0, which means that the PBKEYLEN won't be
31 /// advertised. The "effective value" for PBKEYLEN is 16, but this applies only when neither
32 /// party has set the value explicitly (i.e. when both are initially at the default value of 0).
33 /// If any party has set an explicit value (16, 24, 32) it will be advertised in the handshake.
34 /// If the other party remains at the default 0, it will accept the peer's value. The situation
35 /// where both parties set a value should be treated carefully. Actually there are three
36 /// intended methods of defining it, and all other uses are considered undefined behavior:
37 ///
38 /// Unidirectional: the sender shall set PBKEYLEN and the receiver shall not alter the default
39 /// value 0. The effective PBKEYLEN will be the one set on the sender. The receiver need not
40 /// know the sender's PBKEYLEN, just the passphrase, PBKEYLEN will be correctly passed.
41 ///
42 /// Bidirectional in Caller-Listener arrangement: it is recommended to use a rule whereby you
43 /// will be setting the PBKEYLEN exclusively either on the Listener or on the Caller. The value
44 /// set on the Listener will win, if set on both parties.
45 ///
46 /// Bidirectional in Rendezvous arrangement: you have to know the passphrases for both parties,
47 /// as well as PBKEYLEN. Set PBKEYLEN to the same value on both parties (or leave the default
48 /// value on both parties, which will result in 16)
49 ///
50 /// Unwanted behavior cases: if both parties set PBKEYLEN and the value on both sides is
51 /// different, the effective PBKEYLEN will be the one that is set on the Responder party, which
52 /// may also override the PBKEYLEN 32 set by the sender to value 16 if such value was used by
53 /// the receiver. The Responder party is the Listener in a Caller-Listener arrangement. In
54 /// Rendezvous it's a matter of luck which party becomes the Responder.
55 pub key_size: KeySize,
56
57 /// SRTO_PASSPHRASE
58 /// Sets the passphrase for encryption. This enables encryption on this party (or disables it, if
59 /// an empty passphrase is passed). The password must be minimum 10 and maximum 79 characters
60 /// long.
61 ///
62 /// The passphrase is the shared secret between the sender and the receiver. It is used to
63 /// generate the Key Encrypting Key using PBKDF2 (Password-Based Key Derivation Function 2).
64 ///
65 /// When a socket with configured passphrase is being connected, the peer must have the same
66 /// password set, or the connection is rejected. This behavior can be changed by
67 /// SRTO_ENFORCEDENCRYPTION.
68 ///
69 /// Note that since the introduction of bidirectional support, there's only one initial
70 /// encryption key to encrypt the stream (new keys after refreshing will be updated
71 /// independently), and there's no distinction between "service party that defines the password"
72 /// and "client party that is required to set matching password" - both parties are equivalent,
73 /// and in order to have a working encrypted connection, they have to simply set the same
74 /// passphrase.
75 pub passphrase: Option<Passphrase>,
76
77 pub km_refresh: KeyMaterialRefresh,
78}
79
80#[derive(Clone, Debug, Eq, PartialEq)]
81pub struct KeyMaterialRefresh {
82 /// SRTO_KMREFRESHRATE
83 /// KM Refresh Period specifies the number of packets to be sent
84 /// before switching to the new SEK
85 ///
86 /// The recommended KM Refresh Period is after 2^25 packets encrypted
87 /// with the same SEK are sent.
88 ///
89 /// The number of packets to be transmitted after which the Stream Encryption Key (SEK), used to
90 /// encrypt packets, will be switched to the new one. Note that the old and new keys live in
91 /// parallel for a certain period of time (see SRTO_KMPREANNOUNCE) before and after the
92 /// switchover.
93 ///
94 /// Having a preannounce period before switchover ensures the new SEK is installed at the
95 /// receiver before the first packet encrypted with the new SEK is received. The old key remains
96 /// active after switchover in order to decrypt packets that might still be in flight, or
97 /// packets that have to be retransmitted.
98 ///
99 /// Default value: 0 - corresponds to 16777216 packets (2^24 or 0x1000000).
100 pub period: PacketCount,
101
102 /// SRTO_KMPREANNOUNCE
103 /// KM Pre-Announcement Period specifies when a new key is announced
104 /// in a number of packets before key switchover. The same value is
105 /// used to determine when to decommission the old key after
106 /// switchover.
107 ///
108 /// The recommended KM Pre-Announcement Period is 4000 packets (i.e.
109 /// a new key is generated, wrapped, and sent at 2^25 minus 4000
110 /// packets; the old key is decommissioned at 2^25 plus 4000
111 /// packets).
112 ///
113 /// The interval (defined in packets) between when a new Stream Encrypting Key (SEK) is sent and
114 /// when switchover occurs. This value also applies to the subsequent interval between when
115 /// switchover occurs and when the old SEK is decommissioned.
116 ///
117 /// At SRTO_KMPREANNOUNCE packets before switchover the new key is sent (repeatedly, if
118 /// necessary, until it is confirmed by the receiver).
119 ///
120 /// At the switchover point (see SRTO_KMREFRESHRATE), the sender starts encrypting and sending
121 /// packets using the new key. The old key persists in case it is needed to decrypt packets that
122 /// were in the flight window, or retransmitted packets.
123 ///
124 /// The old key is decommissioned at SRTO_KMPREANNOUNCE packets after switchover.
125 ///
126 /// The allowed range for this value is between 1 and half of the current value of
127 /// SRTO_KMREFRESHRATE. The minimum value should never be less than the flight window SRTO_FC
128 /// (i.e. the number of packets that have already left the sender but have not yet arrived at
129 /// the receiver).
130 ///
131 /// The value of SRTO_KMPREANNOUNCE must not exceed (SRTO_KMREFRESHRATE - 1) / 2`.
132 ///
133 /// Default value: 2^12
134 pub pre_announcement_period: PacketCount,
135}
136
137impl Default for KeyMaterialRefresh {
138 fn default() -> Self {
139 Self {
140 period: PacketCount(1u64 << 24), // 2^25
141 pre_announcement_period: PacketCount(1u64 << 12), // 2^12,
142 }
143 }
144}
145
146impl Validation for Encryption {
147 type Error = OptionsError;
148
149 fn is_valid(&self) -> Result<(), Self::Error> {
150 let period: u64 = self.km_refresh.period.into();
151 let pre_announcement_period: u64 = self.km_refresh.pre_announcement_period.into();
152
153 if period == 0 || pre_announcement_period > period.saturating_sub(1) / 2 {
154 Err(OptionsError::KeyMaterialRefresh(
155 PacketCount(period),
156 PacketCount(pre_announcement_period),
157 ))
158 } else {
159 Ok(())
160 }
161 }
162}
163
164// https://github.com/Haivision/srt/blob/master/docs/API/API-socket-options.md#srto_passphrase
165#[derive(Clone, Eq, PartialEq)]
166pub struct Passphrase(String);
167
168impl<'a> From<&'a str> for Passphrase {
169 fn from(value: &'a str) -> Self {
170 Self::try_from(value.to_string()).unwrap()
171 }
172}
173
174impl TryFrom<String> for Passphrase {
175 type Error = OptionsError;
176
177 fn try_from(value: String) -> Result<Self, Self::Error> {
178 if !(10..=79).contains(&value.len()) {
179 return Err(OptionsError::PassphraseLength(value.len()));
180 }
181 Ok(Passphrase(value))
182 }
183}
184
185impl Passphrase {
186 pub fn as_bytes(&self) -> &[u8] {
187 self.0.as_bytes()
188 }
189}
190
191impl Display for Passphrase {
192 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
193 Debug::fmt(self, f)
194 }
195}
196
197impl Debug for Passphrase {
198 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
199 f.debug_struct("Passphrase").finish()
200 }
201}
202
203// https://github.com/Haivision/srt/blob/master/docs/API/API-socket-options.md#srto_pbkeylen
204#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
205pub enum KeySize {
206 #[default]
207 Unspecified,
208 AES128,
209 AES192,
210 AES256,
211}
212
213impl KeySize {
214 pub fn as_raw(self) -> u8 {
215 use KeySize::*;
216 match self {
217 Unspecified => 0,
218 AES128 => 16,
219 AES192 => 24,
220 AES256 => 32,
221 }
222 }
223
224 pub fn as_usize(self) -> usize {
225 use KeySize::*;
226 match self {
227 Unspecified => 16,
228 AES128 => 16,
229 AES192 => 24,
230 AES256 => 32,
231 }
232 }
233}
234
235impl TryFrom<u16> for KeySize {
236 type Error = OptionsError;
237
238 fn try_from(value: u16) -> Result<Self, OptionsError> {
239 use KeySize::*;
240 match value {
241 0 => Ok(Unspecified),
242 16 => Ok(AES128),
243 24 => Ok(AES192),
244 32 => Ok(AES256),
245 value => Err(OptionsError::InvalidKeySize(value)),
246 }
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn formatting() {
256 assert_eq!(
257 format!("{:?}", Passphrase::from("1234567890")),
258 "Passphrase"
259 );
260 }
261
262 #[test]
263 fn try_from() {
264 use OptionsError::*;
265 assert_eq!(
266 Passphrase::try_from("123456789".to_string()),
267 Err(PassphraseLength(9))
268 );
269 assert_eq!(
270 Passphrase::try_from(String::from_utf8_lossy(&[b'X'; 80]).to_string()),
271 Err(PassphraseLength(80))
272 );
273 }
274}