1#![cfg_attr(feature = "benchmarks", feature(test))]
6
7use thiserror::Error;
8
9mod aes;
12pub mod auth;
13mod integrity;
14pub mod key;
15mod key_data;
16mod keywrap;
17pub mod nonce;
18mod prf;
19pub mod rsna;
20
21use crate::aes::AesError;
22use crate::key::exchange::handshake::{HandshakeMessageNumber, fourway, group_key};
23use crate::key::exchange::{self};
24use crate::rsna::esssa::EssSa;
25use crate::rsna::{Role, UpdateSink};
26use fidl_fuchsia_wlan_mlme::{EapolResultCode, SaeFrame};
27use fuchsia_sync::Mutex;
28use ieee80211::{MacAddr, Ssid};
29use log::warn;
30use std::sync::Arc;
31use wlan_common::ie::rsn::cipher::Cipher;
32use wlan_common::ie::rsn::rsne::{self, Rsne};
33use wlan_common::ie::wpa::WpaIe;
34use zerocopy::SplitByteSlice;
35
36pub use crate::auth::psk;
37pub use crate::key::gtk::{self, GtkProvider};
38pub use crate::key::igtk::{self, IgtkProvider};
39pub use crate::rsna::NegotiatedProtection;
40
41#[derive(Debug)]
42pub struct Supplicant {
43 auth_method: auth::Method,
44 esssa: EssSa,
45 pub auth_cfg: auth::Config,
46}
47
48#[derive(Debug, Clone, PartialEq)]
50pub enum ProtectionInfo {
51 Rsne(Rsne),
52 LegacyWpa(WpaIe),
53}
54
55fn extract_sae_key_helper(update_sink: &UpdateSink) -> Option<Vec<u8>> {
56 for update in &update_sink[..] {
57 if let rsna::SecAssocUpdate::Key(key::exchange::Key::Pmk(pmk)) = update {
58 return Some(pmk.clone());
59 }
60 }
61 None
62}
63
64impl Supplicant {
65 pub fn new_wpa_personal(
67 nonce_rdr: Arc<nonce::NonceReader>,
68 auth_cfg: auth::Config,
69 s_addr: MacAddr,
70 s_protection: ProtectionInfo,
71 a_addr: MacAddr,
72 a_protection: ProtectionInfo,
73 ) -> Result<Supplicant, anyhow::Error> {
74 let negotiated_protection = NegotiatedProtection::from_protection(&s_protection)?;
75 let gtk_exch_cfg = Some(exchange::Config::GroupKeyHandshake(group_key::Config {
76 role: Role::Supplicant,
77 protection: negotiated_protection.clone(),
78 }));
79
80 let auth_method = auth::Method::from_config(auth_cfg.clone())?;
81 let pmk = match auth_cfg.clone() {
82 auth::Config::ComputedPsk(psk) => Some(psk.to_vec()),
83 _ => None,
84 };
85 let esssa = EssSa::new(
86 Role::Supplicant,
87 pmk,
88 negotiated_protection,
89 exchange::Config::FourWayHandshake(fourway::Config::new(
90 Role::Supplicant,
91 s_addr,
92 s_protection,
93 a_addr,
94 a_protection,
95 nonce_rdr,
96 None,
97 None,
98 )?),
99 gtk_exch_cfg,
100 )?;
101
102 Ok(Supplicant { auth_method, esssa, auth_cfg })
103 }
104
105 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
106 pub fn start(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
107 self.esssa.initiate(update_sink)
108 }
109
110 pub fn reset(&mut self) {
111 self.esssa.reset_replay_counter();
113 self.esssa.reset_security_associations();
114 }
115
116 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
117 pub fn on_eapol_frame<B: SplitByteSlice>(
118 &mut self,
119 update_sink: &mut UpdateSink,
120 frame: eapol::Frame<B>,
121 ) -> Result<(), Error> {
122 self.esssa.on_eapol_frame(update_sink, frame)
123 }
124
125 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
126 pub fn on_eapol_conf(
127 &mut self,
128 update_sink: &mut UpdateSink,
129 result: EapolResultCode,
130 ) -> Result<(), Error> {
131 self.esssa.on_eapol_conf(update_sink, result)
132 }
133
134 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
135 pub fn on_rsna_retransmission_timeout(
136 &mut self,
137 update_sink: &mut UpdateSink,
138 ) -> Result<(), Error> {
139 self.esssa.on_rsna_retransmission_timeout(update_sink)
140 }
141
142 pub fn incomplete_reason(&self) -> Error {
147 self.esssa.incomplete_reason()
148 }
149
150 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
151 fn extract_sae_key(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
152 if let Some(pmk) = extract_sae_key_helper(&update_sink) {
153 self.esssa.on_pmk_available(update_sink, pmk)?;
154 }
155 Ok(())
156 }
157
158 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
159 pub fn on_pmk_available(
160 &mut self,
161 update_sink: &mut UpdateSink,
162 pmk: &[u8],
163 pmkid: &[u8],
164 ) -> Result<(), Error> {
165 let mut updates = UpdateSink::new();
166 self.auth_method.on_pmk_available(pmk, pmkid, &mut updates)?;
167 self.extract_sae_key(update_sink)
168 }
169
170 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
171 pub fn on_sae_handshake_ind(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
172 self.auth_method.on_sae_handshake_ind(update_sink).map_err(Error::AuthError)
173 }
174
175 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
176 pub fn on_sae_frame_rx(
177 &mut self,
178 update_sink: &mut UpdateSink,
179 frame: SaeFrame,
180 ) -> Result<(), Error> {
181 self.auth_method.on_sae_frame_rx(update_sink, frame).map_err(Error::AuthError)?;
182 self.extract_sae_key(update_sink)
183 }
184
185 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
186 pub fn on_sae_timeout(
187 &mut self,
188 update_sink: &mut UpdateSink,
189 event_id: u64,
190 ) -> Result<(), Error> {
191 self.auth_method.on_sae_timeout(update_sink, event_id).map_err(Error::AuthError)
192 }
193}
194
195#[derive(Debug)]
196pub struct Authenticator {
197 auth_method: auth::Method,
198 esssa: EssSa,
199 pub auth_cfg: auth::Config,
200}
201
202impl Authenticator {
203 pub fn new_wpa2psk_ccmp128(
206 nonce_rdr: Arc<nonce::NonceReader>,
207 gtk_provider: Arc<Mutex<gtk::GtkProvider>>,
208 psk: psk::Psk,
209 s_addr: MacAddr,
210 s_protection: ProtectionInfo,
211 a_addr: MacAddr,
212 a_protection: ProtectionInfo,
213 ) -> Result<Authenticator, anyhow::Error> {
214 let negotiated_protection = NegotiatedProtection::from_protection(&s_protection)?;
215 let auth_cfg = auth::Config::ComputedPsk(psk.clone());
216 let auth_method = auth::Method::from_config(auth_cfg.clone())?;
217 let esssa = EssSa::new(
218 Role::Authenticator,
219 Some(psk.to_vec()),
220 negotiated_protection,
221 exchange::Config::FourWayHandshake(fourway::Config::new(
222 Role::Authenticator,
223 s_addr,
224 s_protection,
225 a_addr,
226 a_protection,
227 nonce_rdr,
228 Some(gtk_provider),
229 None,
230 )?),
231 None,
233 )?;
234
235 Ok(Authenticator { auth_method, esssa, auth_cfg })
236 }
237
238 pub fn new_wpa3(
241 nonce_rdr: Arc<nonce::NonceReader>,
242 gtk_provider: Arc<Mutex<gtk::GtkProvider>>,
243 igtk_provider: Arc<Mutex<igtk::IgtkProvider>>,
244 ssid: Ssid,
245 password: Vec<u8>,
246 s_addr: MacAddr,
247 s_protection: ProtectionInfo,
248 a_addr: MacAddr,
249 a_protection: ProtectionInfo,
250 ) -> Result<Authenticator, anyhow::Error> {
251 let negotiated_protection = NegotiatedProtection::from_protection(&s_protection)?;
252 let auth_cfg =
253 auth::Config::Sae { ssid, password, mac: a_addr.clone(), peer_mac: s_addr.clone() };
254 let auth_method = auth::Method::from_config(auth_cfg.clone())?;
255
256 let esssa = EssSa::new(
257 Role::Authenticator,
258 None,
259 negotiated_protection,
260 exchange::Config::FourWayHandshake(fourway::Config::new(
261 Role::Authenticator,
262 s_addr,
263 s_protection,
264 a_addr,
265 a_protection,
266 nonce_rdr,
267 Some(gtk_provider),
268 Some(igtk_provider),
269 )?),
270 None,
272 )?;
273
274 Ok(Authenticator { auth_cfg, esssa, auth_method })
275 }
276
277 pub fn get_negotiated_protection(&self) -> &NegotiatedProtection {
278 &self.esssa.negotiated_protection
279 }
280
281 pub fn reset(&mut self) {
285 self.esssa.reset_replay_counter();
286 self.esssa.reset_security_associations();
287
288 match auth::Method::from_config(self.auth_cfg.clone()) {
290 Ok(auth_method) => self.auth_method = auth_method,
291 Err(e) => warn!("Unable to recreate auth::Method: {}", e),
292 }
293 }
294
295 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
296 pub fn initiate(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
304 self.esssa.initiate(update_sink)
305 }
306
307 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
308 pub fn on_eapol_frame<B: SplitByteSlice>(
313 &mut self,
314 update_sink: &mut UpdateSink,
315 frame: eapol::Frame<B>,
316 ) -> Result<(), Error> {
317 self.esssa.on_eapol_frame(update_sink, frame)
318 }
319
320 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
321 pub fn on_eapol_conf(
322 &mut self,
323 update_sink: &mut UpdateSink,
324 result: EapolResultCode,
325 ) -> Result<(), Error> {
326 self.esssa.on_eapol_conf(update_sink, result)
327 }
328
329 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
330 fn extract_sae_key(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
331 if let Some(pmk) = extract_sae_key_helper(&update_sink) {
332 self.esssa.on_pmk_available(update_sink, pmk)?;
333 }
334 Ok(())
335 }
336
337 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
338 pub fn on_sae_handshake_ind(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
339 self.auth_method.on_sae_handshake_ind(update_sink).map_err(Error::AuthError)
340 }
341
342 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
343 pub fn on_sae_frame_rx(
344 &mut self,
345 update_sink: &mut UpdateSink,
346 frame: SaeFrame,
347 ) -> Result<(), Error> {
348 self.auth_method.on_sae_frame_rx(update_sink, frame).map_err(Error::AuthError)?;
349 self.extract_sae_key(update_sink)
350 }
351}
352
353#[derive(Debug, Error)]
354pub enum Error {
355 #[error("invalid OUI length; expected 3 bytes but received {}", _0)]
356 InvalidOuiLength(usize),
357 #[error("invalid PMKID length; expected 16 bytes but received {}", _0)]
358 InvalidPmkidLength(usize),
359 #[error("invalid passphrase length: {}", _0)]
360 InvalidPassphraseLen(usize),
361 #[error("passphrase is not valid UTF-8; failed to parse after byte at index: {:x}", _0)]
362 InvalidPassphraseEncoding(usize),
363 #[error("the config `{:?}` is incompatible with the auth method `{:?}`", _0, _1)]
364 IncompatibleConfig(auth::Config, String),
365 #[error("invalid bit size; must be a multiple of 8 but was {}", _0)]
366 InvalidBitSize(usize),
367 #[error("nonce could not be generated")]
368 NonceError,
369 #[error("error deriving PTK; invalid PMK")]
370 PtkHierarchyInvalidPmkError,
371 #[error("error deriving PTK; unsupported AKM suite")]
372 PtkHierarchyUnsupportedAkmError,
373 #[error("error deriving PTK; unsupported cipher suite")]
374 PtkHierarchyUnsupportedCipherError,
375 #[error("error deriving GTK; unsupported cipher suite")]
376 GtkHierarchyUnsupportedCipherError,
377 #[error("error deriving IGTK; unsupported cipher suite")]
378 IgtkHierarchyUnsupportedCipherError,
379 #[error("no GtkProvider for Authenticator")]
380 MissingGtkProvider,
381 #[error("no IgtkProvider for Authenticator with Management Frame Protection support")]
382 MissingIgtkProvider,
383 #[error("invalid supplicant protection: {}", _0)]
384 InvalidSupplicantProtection(String),
385 #[error("required group mgmt cipher does not match IgtkProvider cipher: {:?} != {:?}", _0, _1)]
386 WrongIgtkProviderCipher(Cipher, Cipher),
387 #[error("error determining group mgmt cipher: {:?} != {:?}", _0, _1)]
388 GroupMgmtCipherMismatch(Cipher, Cipher),
389 #[error("client requires management frame protection and ap is not capable")]
390 MgmtFrameProtectionRequiredByClient,
391 #[error("ap requires management frame protection and client is not capable")]
392 MgmtFrameProtectionRequiredByAp,
393 #[error("client set MFP required bit without setting MFP capability bit")]
394 InvalidClientMgmtFrameProtectionCapabilityBit,
395 #[error("ap set MFP required bit without setting MFP capability bit")]
396 InvalidApMgmtFrameProtectionCapabilityBit,
397 #[error("AES operation failed: {}", _0)]
398 Aes(AesError),
399 #[error("invalid key data length; must be at least 16 bytes and a multiple of 8: {}", _0)]
400 InvaidKeyDataLength(usize),
401 #[error("invalid key data; error code: {:?}", _0)]
402 InvalidKeyData(nom::error::ErrorKind),
403 #[error("unknown authentication method")]
404 UnknownAuthenticationMethod,
405 #[error("no AKM negotiated")]
406 InvalidNegotiatedAkm,
407 #[error("unknown key exchange method")]
408 UnknownKeyExchange,
409 #[error("cannot initiate Fourway Handshake as Supplicant")]
410 UnexpectedInitiationRequest,
411 #[error("cannot initiate Supplicant in current EssSa state")]
412 UnexpectedEsssaInitiation,
413 #[error("key frame transmission failed")]
414 KeyFrameTransmissionFailed,
415 #[error("no key frame transmission confirm received; dropped {} pending updates", _0)]
416 NoKeyFrameTransmissionConfirm(usize),
417 #[error("eapol handshake not started")]
418 EapolHandshakeNotStarted,
419 #[error("likely wrong credential")]
420 LikelyWrongCredential,
421 #[error("eapol handshake incomplete: {}", _0)]
422 EapolHandshakeIncomplete(String),
423 #[error("unsupported Key Descriptor Type: {:?}", _0)]
424 UnsupportedKeyDescriptor(eapol::KeyDescriptor),
425 #[error("unexpected Key Descriptor Type {:?}; expected {:?}", _0, _1)]
426 InvalidKeyDescriptor(eapol::KeyDescriptor, eapol::KeyDescriptor),
427 #[error("unsupported Key Descriptor Version: {:?}", _0)]
428 UnsupportedKeyDescriptorVersion(u16),
429 #[error("only PTK and GTK derivation is supported")]
430 UnsupportedKeyDerivation,
431 #[error("unexpected message: {:?}", _0)]
432 UnexpectedHandshakeMessage(HandshakeMessageNumber),
433 #[error("invalid install bit value; message: {:?}", _0)]
434 InvalidInstallBitValue(HandshakeMessageNumber),
435 #[error("error, install bit set for Group-/SMK-Handshake")]
436 InvalidInstallBitGroupSmkHandshake,
437 #[error("invalid key_ack bit value; message: {:?}", _0)]
438 InvalidKeyAckBitValue(HandshakeMessageNumber),
439 #[error("invalid key_mic bit value; message: {:?}", _0)]
440 InvalidKeyMicBitValue(HandshakeMessageNumber),
441 #[error("invalid secure bit value; message: {:?}", _0)]
442 InvalidSecureBitValue(HandshakeMessageNumber),
443 #[error("error, secure bit set by Authenticator before PTK is known")]
444 SecureBitWithUnknownPtk,
445 #[error("error, secure bit set must be set by Supplicant once PTK and GTK are known")]
446 SecureBitNotSetWithKnownPtkGtk,
447 #[error("invalid error bit value; message: {:?}", _0)]
448 InvalidErrorBitValue(HandshakeMessageNumber),
449 #[error("invalid request bit value; message: {:?}", _0)]
450 InvalidRequestBitValue(HandshakeMessageNumber),
451 #[error("error, Authenticator set request bit")]
452 InvalidRequestBitAuthenticator,
453 #[error("error, Authenticator set error bit")]
454 InvalidErrorBitAuthenticator,
455 #[error("error, Supplicant set key_ack bit")]
456 InvalidKeyAckBitSupplicant,
457 #[error("invalid encrypted_key_data bit value")]
458 InvalidEncryptedKeyDataBitValue(HandshakeMessageNumber),
459 #[error("encrypted_key_data bit requires MIC bit to be set")]
460 InvalidMicBitForEncryptedKeyData,
461 #[error("invalid key length {:?}; expected {:?}", _0, _1)]
462 InvalidKeyLength(usize, usize),
463 #[error("unsupported cipher suite")]
464 UnsupportedCipherSuite,
465 #[error("unsupported AKM suite")]
466 UnsupportedAkmSuite,
467 #[error("cannot compute MIC for key frames which haven't set their MIC bit")]
468 ComputingMicForUnprotectedFrame,
469 #[error("cannot compute MIC; error while encrypting")]
470 ComputingMicEncryptionError,
471 #[error("the key frame's MIC size ({}) differes from the expected size: {}", _0, _1)]
472 MicSizesDiffer(usize, usize),
473 #[error("invalid MIC size")]
474 InvalidMicSize,
475 #[error("invalid Nonce; expected to be non-zero")]
476 InvalidNonce(HandshakeMessageNumber),
477 #[error("invalid RSC; expected to be zero")]
478 InvalidRsc(HandshakeMessageNumber),
479 #[error("invalid key data; must not be zero")]
480 EmptyKeyData(HandshakeMessageNumber),
481 #[error("invalid key data")]
482 InvalidKeyDataContent,
483 #[error("invalid key data length; doesn't match with key data")]
484 InvalidKeyDataLength,
485 #[error("cannot validate MIC; PTK not yet derived")]
486 UnexpectedMic,
487 #[error("invalid MIC")]
488 InvalidMic,
489 #[error("cannot decrypt key data; PTK not yet derived")]
490 UnexpectedEncryptedKeyData,
491 #[error("invalid key replay counter {:?}; expected counter to be > {:?}", _0, _1)]
492 InvalidKeyReplayCounter(u64, u64),
493 #[error("invalid nonce; nonce must match nonce from 1st message")]
494 ErrorNonceDoesntMatch,
495 #[error("invalid IV; EAPOL protocol version: {:?}; message: {:?}", _0, _1)]
496 InvalidIv(eapol::ProtocolVersion, HandshakeMessageNumber),
497 #[error("PMKSA was not yet established")]
498 PmksaNotEstablished,
499 #[error("invalid nonce size; expected 32 bytes, found: {:?}", _0)]
500 InvalidNonceSize(usize),
501 #[error("invalid key data; expected negotiated protection")]
502 InvalidKeyDataProtection,
503 #[error("buffer too small; required: {}, available: {}", _0, _1)]
504 BufferTooSmall(usize, usize),
505 #[error("error, SMK-Handshake is not supported")]
506 SmkHandshakeNotSupported,
507 #[error("error, negotiated protection is invalid")]
508 InvalidNegotiatedProtection,
509 #[error("unknown integrity algorithm for negotiated protection")]
510 UnknownIntegrityAlgorithm,
511 #[error("unknown keywrap algorithm for negotiated protection")]
512 UnknownKeywrapAlgorithm,
513 #[error("eapol error, {}", _0)]
514 EapolError(eapol::Error),
515 #[error("auth error, {}", _0)]
516 AuthError(auth::AuthError),
517 #[error("rsne error, {}", _0)]
518 RsneError(rsne::Error),
519 #[error("rsne invalid subset, supplicant: {:?}, authenticator: {:?}", _0, _1)]
520 RsneInvalidSubset(rsne::Rsne, rsne::Rsne),
521 #[error("error, {}", _0)]
522 GenericError(String),
523}
524
525impl PartialEq for Error {
526 fn eq(&self, other: &Self) -> bool {
527 format!("{:?}", self) == format!("{:?}", other)
528 }
529}
530impl Eq for Error {}
531
532impl From<AesError> for Error {
533 fn from(error: AesError) -> Self {
534 Error::Aes(error)
535 }
536}
537
538#[macro_export]
539macro_rules! rsn_ensure {
540 ($cond:expr, $err:literal) => {
541 if !$cond {
542 return std::result::Result::Err(Error::GenericError($err.to_string()));
543 }
544 };
545 ($cond:expr, $err:expr $(,)?) => {
546 if !$cond {
547 return std::result::Result::Err($err);
548 }
549 };
550}
551
552#[macro_export]
553macro_rules! format_rsn_err {
554 ($msg:literal $(,)?) => {
555 Error::GenericError($msg.to_string())
558 };
559 ($err:expr $(,)?) => ({
560 Error::GenericError($err)
561 });
562 ($fmt:expr, $($arg:tt)*) => {
563 Error::GenericError(format!($fmt, $($arg)*))
564 };
565}
566
567impl From<eapol::Error> for Error {
568 fn from(e: eapol::Error) -> Self {
569 Error::EapolError(e)
570 }
571}
572
573impl From<auth::AuthError> for Error {
574 fn from(e: auth::AuthError) -> Self {
575 Error::AuthError(e)
576 }
577}
578
579impl From<rsne::Error> for Error {
580 fn from(e: rsne::Error) -> Self {
581 Error::RsneError(e)
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use crate::key::exchange::Key;
588 use crate::rsna::{SecAssocStatus, SecAssocUpdate, test_util};
589 use assert_matches::assert_matches;
590
591 #[test]
592 fn supplicant_extract_sae_key() {
593 let mut supplicant = test_util::get_wpa3_supplicant();
594 let mut dummy_update_sink = vec![
595 SecAssocUpdate::ScheduleSaeTimeout(123),
596 SecAssocUpdate::Key(Key::Pmk(vec![1, 2, 3, 4, 5, 6, 7, 8])),
597 ];
598 supplicant.extract_sae_key(&mut dummy_update_sink).expect("Failed to extract key");
599 assert_eq!(
601 dummy_update_sink,
602 vec![
603 SecAssocUpdate::ScheduleSaeTimeout(123),
604 SecAssocUpdate::Key(Key::Pmk(vec![1, 2, 3, 4, 5, 6, 7, 8])),
605 SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished),
606 ]
607 );
608 }
609
610 #[test]
611 fn supplicant_extract_sae_key_no_key() {
612 let mut supplicant = test_util::get_wpa3_supplicant();
613 let mut dummy_update_sink = vec![SecAssocUpdate::ScheduleSaeTimeout(123)];
614 supplicant.extract_sae_key(&mut dummy_update_sink).expect("Failed to extract key");
615 assert_eq!(dummy_update_sink, vec![SecAssocUpdate::ScheduleSaeTimeout(123)]);
617 }
618
619 #[test]
620 fn authenticator_extract_sae_key() {
621 let mut authenticator = test_util::get_wpa3_authenticator();
622 let mut dummy_update_sink = vec![
623 SecAssocUpdate::ScheduleSaeTimeout(123),
624 SecAssocUpdate::Key(Key::Pmk(vec![1, 2, 3, 4, 5, 6, 7, 8])),
625 ];
626 authenticator.extract_sae_key(&mut dummy_update_sink).expect("Failed to extract key");
627 assert_eq!(
629 &dummy_update_sink[0..3],
630 vec![
631 SecAssocUpdate::ScheduleSaeTimeout(123),
632 SecAssocUpdate::Key(Key::Pmk(vec![1, 2, 3, 4, 5, 6, 7, 8])),
633 SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished),
634 ]
635 .as_slice(),
636 );
637
638 assert_matches!(&dummy_update_sink[3], &SecAssocUpdate::TxEapolKeyFrame { .. });
640 }
641
642 #[test]
643 fn authenticator_extract_sae_key_no_key() {
644 let mut authenticator = test_util::get_wpa3_authenticator();
645 let mut dummy_update_sink = vec![SecAssocUpdate::ScheduleSaeTimeout(123)];
646 authenticator.extract_sae_key(&mut dummy_update_sink).expect("Failed to extract key");
647 assert_eq!(dummy_update_sink, vec![SecAssocUpdate::ScheduleSaeTimeout(123)]);
649 }
650}