Skip to main content

sipx_media/dtls/
mod.rs

1//! DTLS-SRTP: keying SRTP on the media path (RFC 5764).
2//!
3//! SDES ([`sipx_sdp::crypto`], RFC 4568) puts the master key in the SDP. That works and it means
4//! every element that reads the signalling — every proxy, every session border controller that
5//! terminates the TLS — has held the key. DTLS-SRTP does not: the two endpoints handshake **on the
6//! media path**, derive the SRTP keys from the DTLS master secret, and the signalling carries only
7//! a hash of the certificate that will appear ([`sipx_sdp::fingerprint`], RFC 8122).
8//!
9//! This module is the parts of RFC 5764 that are sipx's own: telling a DTLS record from an RTP
10//! packet on one port (§5.1.2), the protection profiles and their key sizes (§4.1.2), and turning
11//! the exported keying material into the two SRTP contexts a session needs (§4.2). The handshake
12//! itself is a DTLS implementation's job and is reached through [`Handshake`].
13//!
14//! **Supported**: `sipx-call` now selects this protocol, key-derivation and handshake surface for
15//! explicit DTLS-SRTP call policy (`M-28`), so an upper-layer caller has constrained its shape. The
16//! optional `dtls::openssl` implementation remains experimental; enabling that feature only
17//! makes the explicit selection available and never changes a call's default.
18//!
19
20#[cfg(feature = "dtls")]
21pub mod openssl;
22
23use sipx_rtp::srtp;
24
25/// The TLS exporter label RFC 5764 §4.2 fixes for this use.
26pub const EXPORTER_LABEL: &str = "EXTRACTOR-dtls_srtp";
27
28/// What a datagram arriving on a media port is (RFC 5764 §5.1.2).
29///
30/// One port carries three protocols at once, and §5.1.2 disambiguates them by the first byte alone.
31/// The ranges do not overlap because RTP's version-2 header puts `10` in the top two bits, DTLS
32/// content types are 20–63, and STUN's first two bits are zero.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Arriving {
35    /// A STUN message — first byte 0 or 1. Relevant once ICE exists; classified here because
36    /// §5.1.2 classifies it, and silently treating one as RTP would corrupt the sequence state.
37    Stun,
38    /// A DTLS record — first byte 20 to 63.
39    Dtls,
40    /// RTP or RTCP — first byte 128 to 191.
41    Rtp,
42    /// None of the three. §5.1.2 gives no meaning to these, so they are dropped by name rather
43    /// than fed to whichever parser happens to be first.
44    Unknown,
45}
46
47/// Classify a datagram by its first byte (RFC 5764 §5.1.2).
48#[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/// An SRTP protection profile (RFC 5764 §4.1.2).
59///
60/// Only the one sipx can perform. §4.1.2 defines four; the two `NULL` profiles encrypt nothing, and
61/// offering `AES128_CM_HMAC_SHA1_32` would mean an SRTP transform with a 32-bit tag that
62/// [`sipx_rtp::srtp`] does not implement. A profile list is a promise, so the list is short.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum Profile {
65    /// AES-128 counter mode, 80-bit HMAC-SHA1 tag — the same transform SDES negotiates.
66    Aes128CmHmacSha1_80,
67}
68
69impl Profile {
70    /// The name as the IANA registry and every DTLS API spell it.
71    #[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    /// The two-byte value carried in the `use_srtp` extension (RFC 5764 §4.1.2).
79    #[must_use]
80    pub fn id(self) -> u16 {
81        match self {
82            Self::Aes128CmHmacSha1_80 => 0x0001,
83        }
84    }
85
86    /// Master key and master salt lengths, in octets.
87    ///
88    /// §4.1.2 states these in bits: a 128-bit key and a 112-bit salt. Fourteen octets of salt, not
89    /// sixteen — the value that is easy to get wrong, and getting it wrong produces a key schedule
90    /// that decrypts nothing with no error to say why.
91    #[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    /// How many octets to export from the handshake: `2 * (key + salt)` (RFC 5764 §4.2).
99    #[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/// Which end of the DTLS connection this endpoint is.
107///
108/// It decides nothing about the handshake here and everything about the keys: §4.2's exported block
109/// holds a client write key and a server write key, and each side protects with its own and
110/// unprotects with the other's. A stack that picks the wrong one produces authentication failures
111/// on every packet, in both directions, with no clue as to the cause.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum Role {
114    /// The endpoint that sent the `ClientHello` — SDP `a=setup:active`.
115    Client,
116    /// The endpoint that answered it — SDP `a=setup:passive`.
117    Server,
118}
119
120/// The two SRTP contexts a session needs: one to protect with, one to unprotect with.
121#[derive(Debug)]
122pub struct Keys {
123    /// Protects what this endpoint sends.
124    pub outbound: srtp::Context,
125    /// Unprotects what it receives.
126    pub inbound: srtp::Context,
127    material: crate::SrtpKeys,
128}
129
130/// DTLS-SRTP keys whose peer certificate and protection profile were verified.
131///
132/// Unlike [`Keys`], this value cannot be constructed from raw exporter bytes. The browser-audio
133/// component accepts this type at its key-installation boundary so a caller cannot accidentally
134/// advance media after key derivation while skipping RFC 8122's fingerprint check.
135pub 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    /// Move the same directional master key and salt pairs into a live media session.
151    ///
152    /// The contexts above exist for users that apply SRTP themselves. A [`crate::MediaSession`]
153    /// constructs separate RTP and RTCP contexts, so it needs the master material instead; keeping
154    /// it here closes that boundary without trying to recover secrets from an opaque context.
155    #[must_use]
156    pub fn into_srtp_keys(self) -> crate::SrtpKeys {
157        self.material
158    }
159}
160
161/// Why keying material could not be turned into SRTP contexts.
162#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
163#[non_exhaustive]
164pub enum KeyError {
165    /// The handshake exported fewer octets than the profile needs.
166    #[error("the handshake exported {got} octets; profile {profile} needs {needed}")]
167    Short {
168        /// The profile in force.
169        profile: &'static str,
170        /// How many octets are needed.
171        needed: usize,
172        /// How many arrived.
173        got: usize,
174    },
175    /// The SRTP layer refused the key or salt.
176    #[error("srtp: {0}")]
177    Srtp(#[from] srtp::SrtpError),
178}
179
180/// Split exported keying material into the two SRTP contexts (RFC 5764 §4.2).
181///
182/// §4.2 fixes both the length and the order: `2 * (key + salt)` octets, assigned as **client write
183/// key, server write key, client write salt, server write salt**. Keys first and salts after, not
184/// key-and-salt per side — which is the natural way to read it and produces a context that
185/// authenticates nothing.
186///
187/// `role` selects which pair protects and which unprotects. Both sides derive the same block; the
188/// only thing that differs is which half each one sends with.
189pub 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
220/// A DTLS handshake on the media path, as much of one as RFC 5764 needs.
221///
222/// sipx does not implement DTLS. This is the seam: everything above it — the fingerprint check, the
223/// profile, the key split, the demultiplexing — is sipx's, and an implementor of this trait supplies
224/// the record layer and the handshake. Keeping it a trait rather than a hard dependency is what lets
225/// the fingerprint verification be tested exhaustively without a certificate authority in the loop,
226/// and what stops the choice of DTLS library from reaching into the media session.
227pub trait Handshake {
228    /// Why the handshake failed.
229    type Error: std::error::Error;
230
231    /// Run the handshake to completion.
232    ///
233    /// `role` comes from the negotiated `a=setup` and must not be guessed: a UA that starts a
234    /// handshake it agreed to wait for meets one coming the other way.
235    fn run(&mut self, role: Role) -> Result<(), Self::Error>;
236
237    /// The peer's certificate, DER-encoded, once the handshake has produced one.
238    ///
239    /// This is what the SDP fingerprint is checked against, and why the trait exposes it rather than
240    /// leaving verification to the implementation: RFC 8122 §6.2's check is against a value that
241    /// arrived in the *signalling*, which a DTLS library has no way to see.
242    fn peer_certificate(&self) -> Option<Vec<u8>>;
243
244    /// The profile both ends agreed on, from the `use_srtp` extension (RFC 5764 §4.1).
245    fn profile(&self) -> Option<Profile>;
246
247    /// Export `len` octets under [`EXPORTER_LABEL`] (RFC 5764 §4.2).
248    fn export(&self, len: usize) -> Result<Vec<u8>, Self::Error>;
249}
250
251/// Why a keyed media path could not be established.
252#[derive(Debug, thiserror::Error)]
253#[non_exhaustive]
254pub enum Error {
255    /// The peer offered no fingerprint, so there is nothing to check its certificate against.
256    ///
257    /// Refused rather than accepted unverified. RFC 8122's guarantee is the fingerprint; without
258    /// one, a DTLS handshake with a self-signed certificate authenticates nobody, and proceeding
259    /// would produce encrypted media with no idea who is at the other end.
260    #[error("the peer's SDP carried no fingerprint, so its certificate cannot be verified")]
261    NoFingerprint,
262    /// The handshake completed and presented no certificate.
263    #[error("the peer presented no certificate")]
264    NoCertificate,
265    /// The certificate presented is not the one the SDP named (RFC 8122 §6.2).
266    #[error("the peer's certificate does not match the fingerprint its SDP carried")]
267    FingerprintMismatch,
268    /// No SRTP profile was agreed.
269    #[error("the handshake agreed no SRTP protection profile")]
270    NoProfile,
271    /// The keying material could not be used.
272    #[error("keying: {0}")]
273    Keying(#[from] KeyError),
274    /// The handshake itself failed.
275    #[error("dtls: {0}")]
276    Dtls(String),
277}
278
279/// Handshake, verify the peer against the fingerprint from its SDP, and derive the SRTP keys.
280///
281/// The order is the point. RFC 8122 §6.2 requires an endpoint whose peer's certificate does not
282/// match the fingerprint to "terminate the media connection with a `bad_certificate` error" — so the
283/// check happens **before** any keys are handed back, and a mismatch returns an error rather than a
284/// pair of contexts a caller might use anyway.
285pub fn establish<H: Handshake>(
286    handshake: &mut H,
287    role: Role,
288    peer_fingerprint: Option<&sipx_sdp::fingerprint::Fingerprint>,
289) -> Result<Keys, Error> {
290    // Before the handshake, not after: a peer that sent no fingerprint cannot be authenticated at
291    // all, and finding that out after exchanging keys means having done the work to establish a
292    // channel to an unknown party.
293    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
311/// Handshake, verify and return key material carrying proof that verification ran.
312///
313/// This has the same protocol behavior and errors as [`establish`]. Its distinct return type is
314/// for composition boundaries that must make skipping fingerprint verification unrepresentable.
315pub 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    /// A handshake that succeeds, presenting whatever certificate the test names.
335    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                // A recognisable block: 16 key, 16 key, 14 salt, 14 salt.
353                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    /// RFC 5764 §5.1.2's ranges, at every boundary.
382    #[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    /// A real RTP packet and a real DTLS record, classified as themselves. The ranges above are
406    /// only useful if actual traffic lands in them.
407    #[test]
408    fn a_real_rtp_packet_and_a_real_dtls_record_land_where_they_should() {
409        // RTP: version 2 in the top two bits, payload type 0 (PCMU).
410        let rtp = [0x80, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0, 0, 0];
411        assert_eq!(classify(&rtp), Arriving::Rtp);
412        // RTCP: version 2, packet type 200 (sender report).
413        let sender_report = [0x80, 0xc8, 0x00, 0x06];
414        assert_eq!(classify(&sender_report), Arriving::Rtp);
415        // DTLS 1.2 handshake record: content type 22, version 254.253.
416        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    /// §4.1.2 states 128 bits of key and **112** bits of salt. Fourteen octets, not sixteen.
421    #[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    /// §4.2's order: client key, server key, client salt, server salt. Keys first, then salts.
431    ///
432    /// Asserted by *position in the exported block* rather than by round-tripping through SRTP,
433    /// because the failure this guards against — reading key-and-salt per side — produces contexts
434    /// that are structurally valid and decrypt nothing.
435    #[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        // Both roles derive from the same block, so deriving both and comparing is enough to pin
440        // which bytes went where without reaching inside `srtp::Context`.
441        let client = keys_from_exported(&exported, profile, Role::Client).expect("keys");
442        let server = keys_from_exported(&exported, profile, Role::Server).expect("keys");
443        // The client's outbound context must equal the server's inbound one: same key, same salt.
444        // `srtp::Context` does not expose its key, so this is asserted through behaviour — what one
445        // protects, the other unprotects.
446        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    /// And the other direction, which a one-sided split would leave working by accident.
465    #[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    /// The two roles must not derive the *same* sending key — that is what a split ignoring the
484    /// role would produce, and it would work perfectly in a loopback test.
485    #[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    /// The story's failing-first test, at this layer: RFC 8122 §6.2's mandatory check.
523    #[test]
524    fn a_mismatched_fingerprint_yields_no_keys() {
525        let mut handshake = Stub::good();
526        // A fingerprint of some *other* certificate — what a substituting intermediary produces.
527        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    /// A peer that sent no fingerprint is refused *before* the handshake runs. An unverified DTLS
548    /// handshake with a self-signed certificate authenticates nobody, and finding that out
549    /// afterwards means having established a channel to an unknown party.
550    #[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        // §4.2. Not a detail sipx may choose: a different label derives different keys, and the
597        // failure is silent on both sides.
598        assert_eq!(EXPORTER_LABEL, "EXTRACTOR-dtls_srtp");
599    }
600}