1#[cfg(feature = "dtls")]
21pub mod openssl;
22
23use sipx_rtp::srtp;
24
25pub const EXPORTER_LABEL: &str = "EXTRACTOR-dtls_srtp";
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Arriving {
35 Stun,
38 Dtls,
40 Rtp,
42 Unknown,
45}
46
47#[must_use]
49pub fn classify(datagram: &[u8]) -> Arriving {
50 match datagram.first() {
51 Some(0 | 1) => Arriving::Stun,
52 Some(20..=63) => Arriving::Dtls,
53 Some(128..=191) => Arriving::Rtp,
54 _ => Arriving::Unknown,
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum Profile {
65 Aes128CmHmacSha1_80,
67}
68
69impl Profile {
70 #[must_use]
72 pub fn as_str(self) -> &'static str {
73 match self {
74 Self::Aes128CmHmacSha1_80 => "SRTP_AES128_CM_SHA1_80",
75 }
76 }
77
78 #[must_use]
80 pub fn id(self) -> u16 {
81 match self {
82 Self::Aes128CmHmacSha1_80 => 0x0001,
83 }
84 }
85
86 #[must_use]
92 pub fn key_and_salt_len(self) -> (usize, usize) {
93 match self {
94 Self::Aes128CmHmacSha1_80 => (16, 14),
95 }
96 }
97
98 #[must_use]
100 pub fn exported_len(self) -> usize {
101 let (key, salt) = self.key_and_salt_len();
102 2 * (key + salt)
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum Role {
114 Client,
116 Server,
118}
119
120#[derive(Debug)]
122pub struct Keys {
123 pub outbound: srtp::Context,
125 pub inbound: srtp::Context,
127 material: crate::SrtpKeys,
128}
129
130pub struct VerifiedKeys(Keys);
136
137impl std::fmt::Debug for VerifiedKeys {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 f.write_str("VerifiedKeys { .. }")
140 }
141}
142
143impl VerifiedKeys {
144 pub(crate) fn into_srtp_keys(self) -> crate::SrtpKeys {
145 self.0.into_srtp_keys()
146 }
147}
148
149impl Keys {
150 #[must_use]
156 pub fn into_srtp_keys(self) -> crate::SrtpKeys {
157 self.material
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
163#[non_exhaustive]
164pub enum KeyError {
165 #[error("the handshake exported {got} octets; profile {profile} needs {needed}")]
167 Short {
168 profile: &'static str,
170 needed: usize,
172 got: usize,
174 },
175 #[error("srtp: {0}")]
177 Srtp(#[from] srtp::SrtpError),
178}
179
180pub fn keys_from_exported(exported: &[u8], profile: Profile, role: Role) -> Result<Keys, KeyError> {
190 let (key_len, salt_len) = profile.key_and_salt_len();
191 let needed = profile.exported_len();
192 if exported.len() < needed {
193 return Err(KeyError::Short {
194 profile: profile.as_str(),
195 needed,
196 got: exported.len(),
197 });
198 }
199 let take = |from: usize, len: usize| exported.get(from..from + len).unwrap_or_default();
200 let client_key = take(0, key_len);
201 let server_key = take(key_len, key_len);
202 let client_salt = take(2 * key_len, salt_len);
203 let server_salt = take(2 * key_len + salt_len, salt_len);
204
205 let (own_key, own_salt, peer_key, peer_salt) = match role {
206 Role::Client => (client_key, client_salt, server_key, server_salt),
207 Role::Server => (server_key, server_salt, client_key, client_salt),
208 };
209 let material = crate::SrtpKeys {
210 local: (own_key.to_vec(), own_salt.to_vec()),
211 remote: (peer_key.to_vec(), peer_salt.to_vec()),
212 };
213 Ok(Keys {
214 outbound: srtp::Context::new(own_key, own_salt)?,
215 inbound: srtp::Context::new(peer_key, peer_salt)?,
216 material,
217 })
218}
219
220pub trait Handshake {
228 type Error: std::error::Error;
230
231 fn run(&mut self, role: Role) -> Result<(), Self::Error>;
236
237 fn peer_certificate(&self) -> Option<Vec<u8>>;
243
244 fn profile(&self) -> Option<Profile>;
246
247 fn export(&self, len: usize) -> Result<Vec<u8>, Self::Error>;
249}
250
251#[derive(Debug, thiserror::Error)]
253#[non_exhaustive]
254pub enum Error {
255 #[error("the peer's SDP carried no fingerprint, so its certificate cannot be verified")]
261 NoFingerprint,
262 #[error("the peer presented no certificate")]
264 NoCertificate,
265 #[error("the peer's certificate does not match the fingerprint its SDP carried")]
267 FingerprintMismatch,
268 #[error("the handshake agreed no SRTP protection profile")]
270 NoProfile,
271 #[error("keying: {0}")]
273 Keying(#[from] KeyError),
274 #[error("dtls: {0}")]
276 Dtls(String),
277}
278
279pub fn establish<H: Handshake>(
286 handshake: &mut H,
287 role: Role,
288 peer_fingerprint: Option<&sipx_sdp::fingerprint::Fingerprint>,
289) -> Result<Keys, Error> {
290 let fingerprint = peer_fingerprint.ok_or(Error::NoFingerprint)?;
294
295 handshake
296 .run(role)
297 .map_err(|error| Error::Dtls(error.to_string()))?;
298
299 let certificate = handshake.peer_certificate().ok_or(Error::NoCertificate)?;
300 if !fingerprint.matches(&certificate) {
301 return Err(Error::FingerprintMismatch);
302 }
303
304 let profile = handshake.profile().ok_or(Error::NoProfile)?;
305 let exported = handshake
306 .export(profile.exported_len())
307 .map_err(|error| Error::Dtls(error.to_string()))?;
308 Ok(keys_from_exported(&exported, profile, role)?)
309}
310
311pub fn establish_verified<H: Handshake>(
316 handshake: &mut H,
317 role: Role,
318 peer_fingerprint: Option<&sipx_sdp::fingerprint::Fingerprint>,
319) -> Result<VerifiedKeys, Error> {
320 establish(handshake, role, peer_fingerprint).map(VerifiedKeys)
321}
322
323#[cfg(test)]
324#[allow(
325 clippy::unwrap_used,
326 clippy::expect_used,
327 clippy::panic,
328 clippy::indexing_slicing
329)]
330mod tests {
331 use super::*;
332 use sipx_sdp::fingerprint::{Fingerprint, HashFunc};
333
334 struct Stub {
336 certificate: Option<Vec<u8>>,
337 profile: Option<Profile>,
338 exported: Vec<u8>,
339 fail: bool,
340 ran_as: Option<Role>,
341 }
342
343 #[derive(Debug, thiserror::Error)]
344 #[error("the stub was told to fail")]
345 struct StubError;
346
347 impl Stub {
348 fn good() -> Self {
349 Self {
350 certificate: Some(b"the peer's certificate".to_vec()),
351 profile: Some(Profile::Aes128CmHmacSha1_80),
352 exported: (0u8..60).collect(),
354 fail: false,
355 ran_as: None,
356 }
357 }
358 }
359
360 impl Handshake for Stub {
361 type Error = StubError;
362
363 fn run(&mut self, role: Role) -> Result<(), Self::Error> {
364 self.ran_as = Some(role);
365 if self.fail { Err(StubError) } else { Ok(()) }
366 }
367
368 fn peer_certificate(&self) -> Option<Vec<u8>> {
369 self.certificate.clone()
370 }
371
372 fn profile(&self) -> Option<Profile> {
373 self.profile
374 }
375
376 fn export(&self, len: usize) -> Result<Vec<u8>, Self::Error> {
377 Ok(self.exported.iter().copied().take(len).collect())
378 }
379 }
380
381 #[test]
383 fn one_port_tells_stun_dtls_and_rtp_apart_by_the_first_byte() {
384 assert_eq!(classify(&[0]), Arriving::Stun);
385 assert_eq!(classify(&[1]), Arriving::Stun);
386 assert_eq!(classify(&[2]), Arriving::Unknown);
387 assert_eq!(classify(&[19]), Arriving::Unknown);
388 assert_eq!(classify(&[20]), Arriving::Dtls, "DTLS ChangeCipherSpec");
389 assert_eq!(classify(&[22]), Arriving::Dtls, "DTLS Handshake");
390 assert_eq!(classify(&[23]), Arriving::Dtls, "DTLS ApplicationData");
391 assert_eq!(classify(&[63]), Arriving::Dtls);
392 assert_eq!(classify(&[64]), Arriving::Unknown);
393 assert_eq!(classify(&[127]), Arriving::Unknown);
394 assert_eq!(classify(&[128]), Arriving::Rtp, "RTP version 2, no padding");
395 assert_eq!(classify(&[0x80]), Arriving::Rtp);
396 assert_eq!(classify(&[0xbf]), Arriving::Rtp);
397 assert_eq!(classify(&[192]), Arriving::Unknown);
398 assert_eq!(
399 classify(&[]),
400 Arriving::Unknown,
401 "an empty datagram is not RTP"
402 );
403 }
404
405 #[test]
408 fn a_real_rtp_packet_and_a_real_dtls_record_land_where_they_should() {
409 let rtp = [0x80, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0, 0, 0];
411 assert_eq!(classify(&rtp), Arriving::Rtp);
412 let sender_report = [0x80, 0xc8, 0x00, 0x06];
414 assert_eq!(classify(&sender_report), Arriving::Rtp);
415 let dtls = [0x16, 0xfe, 0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
417 assert_eq!(classify(&dtls), Arriving::Dtls);
418 }
419
420 #[test]
422 fn the_profile_asks_for_the_key_and_salt_sizes_the_rfc_states() {
423 let profile = Profile::Aes128CmHmacSha1_80;
424 assert_eq!(profile.key_and_salt_len(), (16, 14));
425 assert_eq!(profile.exported_len(), 60, "2 * (16 + 14)");
426 assert_eq!(profile.id(), 0x0001);
427 assert_eq!(profile.as_str(), "SRTP_AES128_CM_SHA1_80");
428 }
429
430 #[test]
436 fn the_exported_block_splits_keys_before_salts() {
437 let exported: Vec<u8> = (0u8..60).collect();
438 let profile = Profile::Aes128CmHmacSha1_80;
439 let client = keys_from_exported(&exported, profile, Role::Client).expect("keys");
442 let server = keys_from_exported(&exported, profile, Role::Server).expect("keys");
443 let mut protecting = client.outbound;
447 let mut unprotecting = server.inbound;
448 let packet = [
449 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa0, 0xde, 0xad, 0xbe, 0xef, 0x11, 0x22,
450 ];
451 let protected = protecting.protect(&packet).expect("protects");
452 assert_ne!(
453 protected.get(12..14),
454 packet.get(12..14),
455 "the payload should not be in the clear"
456 );
457 let recovered = unprotecting.unprotect(&protected).expect(
458 "the client's outbound key must be the server's inbound key; if this fails the block \
459 was split key-and-salt per side rather than keys-then-salts",
460 );
461 assert_eq!(recovered, packet);
462 }
463
464 #[test]
466 fn the_server_protects_with_what_the_client_unprotects_with() {
467 let exported: Vec<u8> = (0u8..60).collect();
468 let profile = Profile::Aes128CmHmacSha1_80;
469 let client = keys_from_exported(&exported, profile, Role::Client).expect("keys");
470 let server = keys_from_exported(&exported, profile, Role::Server).expect("keys");
471 let mut protecting = server.outbound;
472 let mut unprotecting = client.inbound;
473 let packet = [
474 0x80, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x20, 0xca, 0xfe, 0xba, 0xbe, 0x33, 0x44,
475 ];
476 let protected = protecting.protect(&packet).expect("protects");
477 assert_eq!(
478 unprotecting.unprotect(&protected).expect("unprotects"),
479 packet
480 );
481 }
482
483 #[test]
486 fn the_two_roles_do_not_send_with_the_same_key() {
487 let exported: Vec<u8> = (0u8..60).collect();
488 let profile = Profile::Aes128CmHmacSha1_80;
489 let mut client = keys_from_exported(&exported, profile, Role::Client)
490 .expect("keys")
491 .outbound;
492 let mut server = keys_from_exported(&exported, profile, Role::Server)
493 .expect("keys")
494 .outbound;
495 let packet = [
496 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa0, 0xde, 0xad, 0xbe, 0xef, 0x55, 0x66,
497 ];
498 assert_ne!(
499 client.protect(&packet).expect("protects"),
500 server.protect(&packet).expect("protects"),
501 "both roles derived the same sending key, so the role was ignored"
502 );
503 }
504
505 #[test]
506 fn keying_material_shorter_than_the_profile_needs_is_refused() {
507 let short: Vec<u8> = (0u8..59).collect();
508 let outcome = keys_from_exported(&short, Profile::Aes128CmHmacSha1_80, Role::Client);
509 assert!(
510 matches!(
511 outcome,
512 Err(KeyError::Short {
513 needed: 60,
514 got: 59,
515 ..
516 })
517 ),
518 "{outcome:?}"
519 );
520 }
521
522 #[test]
524 fn a_mismatched_fingerprint_yields_no_keys() {
525 let mut handshake = Stub::good();
526 let wrong = Fingerprint::of(b"a certificate the peer does not have", HashFunc::Sha256);
528 let outcome = establish(&mut handshake, Role::Client, Some(&wrong));
529 assert!(
530 matches!(outcome, Err(Error::FingerprintMismatch)),
531 "a certificate that does not match the SDP must yield an error, not keys: {outcome:?}"
532 );
533 }
534
535 #[test]
536 fn a_matching_fingerprint_yields_keys() {
537 let mut handshake = Stub::good();
538 let right = Fingerprint::of(b"the peer's certificate", HashFunc::Sha256);
539 assert!(establish(&mut handshake, Role::Client, Some(&right)).is_ok());
540 assert_eq!(
541 handshake.ran_as,
542 Some(Role::Client),
543 "the negotiated role must reach the handshake, not be guessed there"
544 );
545 }
546
547 #[test]
551 fn a_peer_with_no_fingerprint_is_refused_before_the_handshake_runs() {
552 let mut handshake = Stub::good();
553 let outcome = establish(&mut handshake, Role::Client, None);
554 assert!(matches!(outcome, Err(Error::NoFingerprint)), "{outcome:?}");
555 assert_eq!(
556 handshake.ran_as, None,
557 "the handshake must not run for a peer that cannot be verified"
558 );
559 }
560
561 #[test]
562 fn a_handshake_that_agrees_no_profile_yields_no_keys() {
563 let mut handshake = Stub::good();
564 handshake.profile = None;
565 let right = Fingerprint::of(b"the peer's certificate", HashFunc::Sha256);
566 assert!(matches!(
567 establish(&mut handshake, Role::Client, Some(&right)),
568 Err(Error::NoProfile)
569 ));
570 }
571
572 #[test]
573 fn a_handshake_that_presents_no_certificate_yields_no_keys() {
574 let mut handshake = Stub::good();
575 handshake.certificate = None;
576 let right = Fingerprint::of(b"the peer's certificate", HashFunc::Sha256);
577 assert!(matches!(
578 establish(&mut handshake, Role::Client, Some(&right)),
579 Err(Error::NoCertificate)
580 ));
581 }
582
583 #[test]
584 fn a_failed_handshake_is_reported_rather_than_keyed_around() {
585 let mut handshake = Stub::good();
586 handshake.fail = true;
587 let right = Fingerprint::of(b"the peer's certificate", HashFunc::Sha256);
588 assert!(matches!(
589 establish(&mut handshake, Role::Client, Some(&right)),
590 Err(Error::Dtls(_))
591 ));
592 }
593
594 #[test]
595 fn the_exporter_label_is_the_one_the_rfc_fixes() {
596 assert_eq!(EXPORTER_LABEL, "EXTRACTOR-dtls_srtp");
599 }
600}