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