1pub mod psk;
6
7use crate::Error;
8use crate::key::exchange::Key;
9use crate::rsna::{
10 AuthRejectedReason, AuthStatus, Dot11VerifiedKeyFrame, SecAssocUpdate, UpdateSink,
11};
12use fidl_fuchsia_wlan_mlme::SaeFrame;
13use ieee80211::{MacAddr, MacAddrBytes, Ssid};
14use log::warn;
15use wlan_common::ie::rsn::akm::{AKM_OWE, AKM_SAE};
16use wlan_fcg_crypto::{owe, sae};
17use zerocopy::SplitByteSlice;
18
19const DEFAULT_GROUP_ID: u16 = 19;
23
24#[derive(Error, Debug)]
25pub enum AuthError {
26 #[error("Failed to construct auth method from the given configuration: {:?}", _0)]
27 FailedConstruction(anyhow::Error),
28 #[error("Non-SAE auth method received an SAE event")]
29 UnexpectedSaeEvent,
30 #[error("Non-OWE auth method received an OWE event")]
31 UnexpectedOweEvent,
32 #[error("Failed to initiate OWE: {:?}", _0)]
33 FailedInitiateOwe(anyhow::Error),
34 #[error("Failed to handle OWE public key: {:?}", _0)]
35 FailedHandleOwePublicKey(anyhow::Error),
36}
37
38pub struct SaeData {
39 peer: MacAddr,
40 pub pmk: Option<sae::Key>,
41 handshake: Box<dyn sae::SaeHandshake>,
42 retransmit_timeout_id: u64,
45}
46
47pub struct OweData {
48 pub pmk: Option<Vec<u8>>,
49 handshake: Box<dyn owe::ClientOweHandshake>,
50}
51
52#[derive(Debug, PartialEq, Clone)]
53pub enum Config {
54 ComputedPsk(psk::Psk),
55 Sae {
56 ssid: Ssid,
57 password: Vec<u8>,
58 mac: MacAddr,
59 peer_mac: MacAddr,
60 pwe_method: sae::PweMethod,
61 },
62 DriverSae {
63 password: Vec<u8>,
64 },
65 Owe,
66}
67
68impl Config {
69 pub fn method_name(&self) -> MethodName {
70 match self {
71 Config::ComputedPsk(_) => MethodName::Psk,
72 Config::Sae { .. } | Config::DriverSae { .. } => MethodName::Sae,
73 Config::Owe => MethodName::Owe,
74 }
75 }
76}
77
78pub enum Method {
79 Psk(psk::Psk),
80 Sae(SaeData),
81 DriverSae(Option<sae::Key>),
83 Owe(OweData),
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub enum MethodName {
88 Psk,
89 Sae,
90 Owe,
91}
92
93impl std::fmt::Debug for Method {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
95 match self {
96 Self::Psk(psk) => write!(f, "Method::Psk({:?})", psk),
97 Self::Sae(sae_data) => write!(
98 f,
99 "Method::Sae {{ peer: {:?}, pmk: {}, .. }}",
100 sae_data.peer,
101 match sae_data.pmk {
102 Some(_) => "Some(_)",
103 None => "None",
104 }
105 ),
106 Self::DriverSae(key) => write!(f, "Method::DriverSae({:?})", key),
107 Self::Owe(owe_data) => write!(
108 f,
109 "Method::Owe {{ pmk: {}, .. }}",
110 match owe_data.pmk {
111 Some(_) => "Some(_)",
112 None => "None",
113 }
114 ),
115 }
116 }
117}
118
119impl Method {
120 pub fn from_config(cfg: Config) -> Result<Method, AuthError> {
121 match cfg {
122 Config::ComputedPsk(psk) => Ok(Method::Psk(psk)),
123 Config::Sae { ssid, password, mac, peer_mac, pwe_method } => {
124 let handshake = sae::new_sae_handshake(
126 DEFAULT_GROUP_ID,
127 AKM_SAE,
128 pwe_method,
129 ssid,
130 password,
131 None, mac,
133 peer_mac.clone(),
134 )
135 .map_err(AuthError::FailedConstruction)?;
136 Ok(Method::Sae(SaeData {
137 peer: peer_mac,
138 pmk: None,
139 handshake,
140 retransmit_timeout_id: 0,
141 }))
142 }
143 Config::DriverSae { .. } => Ok(Method::DriverSae(None)),
144 Config::Owe => {
145 let handshake = owe::new_client_owe_handshake(DEFAULT_GROUP_ID, AKM_OWE)
146 .map_err(AuthError::FailedConstruction)?;
147 Ok(Method::Owe(OweData { pmk: None, handshake }))
148 }
149 }
150 }
151
152 pub fn on_eapol_key_frame<B: SplitByteSlice>(
154 &self,
155 _update_sink: &mut UpdateSink,
156 _frame: Dot11VerifiedKeyFrame<B>,
157 ) -> Result<(), AuthError> {
158 Ok(())
159 }
160
161 pub fn on_pmk_available(
164 &mut self,
165 pmk: &[u8],
166 pmkid: &[u8],
167 assoc_update_sink: &mut UpdateSink,
168 ) -> Result<(), AuthError> {
169 match self {
170 Method::DriverSae(key) => {
171 key.replace(sae::Key { pmk: pmk.to_vec(), pmkid: pmkid.to_vec() });
172 assoc_update_sink.push(SecAssocUpdate::Key(Key::Pmk(pmk.to_vec())));
173 Ok(())
174 }
175 _ => Err(AuthError::UnexpectedSaeEvent),
176 }
177 }
178
179 pub fn on_sae_handshake_ind(
180 &mut self,
181 assoc_update_sink: &mut UpdateSink,
182 ) -> Result<(), AuthError> {
183 match self {
184 Method::Sae(sae_data) => {
185 let mut sae_update_sink = sae::SaeUpdateSink::default();
186 sae_data.handshake.initiate_sae(&mut sae_update_sink);
187 process_sae_updates(sae_data, assoc_update_sink, sae_update_sink);
188 Ok(())
189 }
190 _ => Err(AuthError::UnexpectedSaeEvent),
191 }
192 }
193
194 pub fn on_sae_frame_rx(
195 &mut self,
196 assoc_update_sink: &mut UpdateSink,
197 frame: SaeFrame,
198 ) -> Result<(), AuthError> {
199 match self {
200 Method::Sae(sae_data) => {
201 let mut sae_update_sink = sae::SaeUpdateSink::default();
202 let frame_rx = sae::AuthFrameRx {
203 seq: frame.seq_num,
204 status_code: frame.status_code,
205 body: &frame.sae_fields[..],
206 };
207 sae_data.handshake.handle_frame(&mut sae_update_sink, &frame_rx);
208 process_sae_updates(sae_data, assoc_update_sink, sae_update_sink);
209 Ok(())
210 }
211 _ => Err(AuthError::UnexpectedSaeEvent),
212 }
213 }
214
215 pub fn on_sae_timeout(
216 &mut self,
217 assoc_update_sink: &mut UpdateSink,
218 event_id: u64,
219 ) -> Result<(), AuthError> {
220 match self {
221 Method::Sae(sae_data) => {
222 if sae_data.retransmit_timeout_id == event_id {
223 sae_data.retransmit_timeout_id += 1;
224 let mut sae_update_sink = sae::SaeUpdateSink::default();
225 sae_data
226 .handshake
227 .handle_timeout(&mut sae_update_sink, sae::Timeout::Retransmission);
228 process_sae_updates(sae_data, assoc_update_sink, sae_update_sink);
229 }
230 Ok(())
231 }
232 _ => Err(AuthError::UnexpectedSaeEvent),
233 }
234 }
235
236 pub fn initiate_owe(&mut self, assoc_update_sink: &mut UpdateSink) -> Result<(), AuthError> {
237 match self {
238 Method::Owe(owe_data) => {
239 let mut owe_update_sink = owe::OweUpdateSink::default();
240 owe_data
241 .handshake
242 .initiate_owe(&mut owe_update_sink)
243 .map_err(AuthError::FailedInitiateOwe)?;
244 process_owe_updates(owe_data, assoc_update_sink, owe_update_sink);
245 Ok(())
246 }
247 _ => Err(AuthError::UnexpectedOweEvent),
248 }
249 }
250
251 pub fn on_owe_public_key_rx(
252 &mut self,
253 assoc_update_sink: &mut UpdateSink,
254 group: u16,
255 public_key: Vec<u8>,
256 ) -> Result<(), AuthError> {
257 match self {
258 Method::Owe(owe_data) => {
259 let mut owe_update_sink = owe::OweUpdateSink::default();
260 owe_data
261 .handshake
262 .handle_public_key(&mut owe_update_sink, group, public_key)
263 .map_err(AuthError::FailedHandleOwePublicKey)?;
264 process_owe_updates(owe_data, assoc_update_sink, owe_update_sink);
265 Ok(())
266 }
267 _ => Err(AuthError::UnexpectedOweEvent),
268 }
269 }
270}
271
272fn process_sae_updates(
273 sae_data: &mut SaeData,
274 assoc_update_sink: &mut UpdateSink,
275 sae_update_sink: sae::SaeUpdateSink,
276) {
277 for sae_update in sae_update_sink {
278 match sae_update {
279 sae::SaeUpdate::SendFrame(frame) => {
280 let sae_frame = SaeFrame {
281 peer_sta_address: sae_data.peer.clone().to_array(),
282 status_code: frame.status_code,
283 seq_num: frame.seq,
284 sae_fields: frame.body,
285 };
286 assoc_update_sink.push(SecAssocUpdate::TxSaeFrame(sae_frame));
287 }
288 sae::SaeUpdate::Success(key) => {
289 sae_data.pmk.replace(key.clone());
290 assoc_update_sink.push(SecAssocUpdate::Key(Key::Pmk(key.pmk)));
291 assoc_update_sink.push(SecAssocUpdate::SaeAuthStatus(AuthStatus::Success));
292 }
293 sae::SaeUpdate::Reject(reason) => {
294 warn!("SAE handshake rejected: {:?}", reason);
295 let status = match reason {
296 sae::RejectReason::AuthFailed => {
297 AuthStatus::Rejected(AuthRejectedReason::AuthFailed)
298 }
299 sae::RejectReason::KeyExpiration => {
300 AuthStatus::Rejected(AuthRejectedReason::PmksaExpired)
301 }
302 sae::RejectReason::TooManyRetries => {
303 AuthStatus::Rejected(AuthRejectedReason::TooManyRetries)
304 }
305 sae::RejectReason::InternalError(_) => AuthStatus::InternalError,
306 };
307 assoc_update_sink.push(SecAssocUpdate::SaeAuthStatus(status));
308 }
309 sae::SaeUpdate::ResetTimeout(timer) => {
310 match timer {
311 sae::Timeout::KeyExpiration => (), sae::Timeout::Retransmission => {
313 sae_data.retransmit_timeout_id += 1;
314 assoc_update_sink.push(SecAssocUpdate::ScheduleSaeTimeout(
315 sae_data.retransmit_timeout_id,
316 ));
317 }
318 };
319 }
320 sae::SaeUpdate::CancelTimeout(timer) => {
321 match timer {
322 sae::Timeout::KeyExpiration => (),
323 sae::Timeout::Retransmission => {
324 sae_data.retransmit_timeout_id += 1;
325 }
326 };
327 }
328 }
329 }
330}
331
332fn process_owe_updates(
333 owe_data: &mut OweData,
334 assoc_update_sink: &mut UpdateSink,
335 owe_update_sink: owe::OweUpdateSink,
336) {
337 for owe_update in owe_update_sink {
338 match owe_update {
339 owe::OweUpdate::TxPublicKey { group_id, key } => {
340 assoc_update_sink.push(SecAssocUpdate::TxOwePublicKey { group_id, key });
341 }
342 owe::OweUpdate::Success { key } => {
343 owe_data.pmk.replace(key.clone());
344 assoc_update_sink.push(SecAssocUpdate::Key(Key::Pmk(key)));
345 }
346 }
347 }
348}
349
350#[cfg(test)]
351mod test {
352 use super::*;
353 use assert_matches::assert_matches;
354 use fuchsia_sync::Mutex;
355 use std::sync::Arc;
356
357 #[test]
358 fn psk_rejects_sae() {
359 let mut auth = Method::from_config(Config::ComputedPsk(Box::new([0x8; 16])))
360 .expect("Failed to construct PSK auth method");
361 let mut sink = UpdateSink::default();
362 auth.on_sae_handshake_ind(&mut sink).expect_err("PSK auth method accepted SAE ind");
363 let frame = SaeFrame {
364 peer_sta_address: [0xaa; 6],
365 status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
366 seq_num: 1,
367 sae_fields: vec![0u8; 10],
368 };
369 auth.on_sae_frame_rx(&mut sink, frame).expect_err("PSK auth method accepted SAE frame");
370 assert!(sink.is_empty());
372 }
373
374 #[derive(Default)]
375 struct SaeCounter {
376 initiated: bool,
377 handled_commits: u32,
378 handled_confirms: u32,
379 handled_timeouts: u32,
380 }
381
382 struct DummySae(Arc<Mutex<SaeCounter>>);
383
384 impl sae::SaeHandshake for DummySae {
386 fn initiate_sae(&mut self, sink: &mut sae::SaeUpdateSink) {
387 self.0.lock().initiated = true;
388 sink.push(sae::SaeUpdate::SendFrame(sae::AuthFrameTx {
389 seq: 1,
390 status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
391 body: vec![],
392 }));
393 }
394 fn handle_commit(
395 &mut self,
396 _sink: &mut sae::SaeUpdateSink,
397 _commit_msg: &sae::CommitMsg<'_>,
398 ) {
399 assert!(self.0.lock().initiated);
400 self.0.lock().handled_commits += 1;
401 }
402 fn handle_confirm(
403 &mut self,
404 sink: &mut sae::SaeUpdateSink,
405 _confirm_msg: &sae::ConfirmMsg<'_>,
406 ) {
407 assert!(self.0.lock().initiated);
408 self.0.lock().handled_confirms += 1;
409 sink.push(sae::SaeUpdate::SendFrame(sae::AuthFrameTx {
410 seq: 2,
411 status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
412 body: vec![],
413 }));
414 sink.push(sae::SaeUpdate::Success(sae::Key { pmk: vec![0xaa], pmkid: vec![0xbb] }))
415 }
416 fn handle_anti_clogging_token(
417 &mut self,
418 _sink: &mut sae::SaeUpdateSink,
419 _msg: &sae::AntiCloggingTokenMsg<'_>,
420 ) {
421 panic!("The SAE initiator should never receive an anti-clogging token.");
422 }
423 fn handle_timeout(&mut self, _sink: &mut sae::SaeUpdateSink, _timeout: sae::Timeout) {
424 self.0.lock().handled_timeouts += 1;
425 }
426 }
427
428 const COMMIT: [u8; 98] = [
431 0x13, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
432 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
433 0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
434 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
435 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
436 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
437 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
438 ];
439 const CONFIRM: [u8; 34] = [
440 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
441 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
442 0xbb, 0xbb, 0xbb, 0xbb,
443 ];
444
445 #[test]
446 fn sae_executes_handshake() {
447 let sae_counter = Arc::new(Mutex::new(SaeCounter::default()));
448 let mut auth = Method::Sae(SaeData {
449 peer: MacAddr::from([0xaa; 6]),
450 pmk: None,
451 handshake: Box::new(DummySae(sae_counter.clone())),
452 retransmit_timeout_id: 0,
453 });
454 let mut sink = UpdateSink::default();
455
456 auth.on_sae_handshake_ind(&mut sink).expect("SAE handshake should accept SAE ind");
457 assert!(sae_counter.lock().initiated);
458 assert_matches!(sink.pop(), Some(SecAssocUpdate::TxSaeFrame(_)));
459
460 let commit_frame = SaeFrame {
461 peer_sta_address: [0xaa; 6],
462 status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
463 seq_num: 1,
464 sae_fields: COMMIT.to_vec(),
465 };
466 auth.on_sae_frame_rx(&mut sink, commit_frame).expect("SAE handshake should accept commit");
467 assert_eq!(sae_counter.lock().handled_commits, 1);
468 assert!(sink.is_empty());
469
470 let confirm_frame = SaeFrame {
471 peer_sta_address: [0xaa; 6],
472 status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
473 seq_num: 2,
474 sae_fields: CONFIRM.to_vec(),
475 };
476 auth.on_sae_frame_rx(&mut sink, confirm_frame)
477 .expect("SAE handshake should accept confirm");
478 assert_eq!(sae_counter.lock().handled_confirms, 1);
479 assert_eq!(sink.len(), 3);
480 assert_matches!(sink.remove(0), SecAssocUpdate::TxSaeFrame(_));
481 assert_matches!(sink.remove(0), SecAssocUpdate::Key(_));
482 assert_matches!(sink.remove(0), SecAssocUpdate::SaeAuthStatus(AuthStatus::Success));
483 match auth {
484 Method::Sae(sae_data) => assert!(sae_data.pmk.is_some()),
485 _ => unreachable!(),
486 };
487 }
488
489 #[test]
490 fn sae_handles_current_timeouts() {
491 let sae_counter = Arc::new(Mutex::new(SaeCounter::default()));
492 let mut sae = Method::Sae(SaeData {
493 peer: MacAddr::from([0xaa; 6]),
494 pmk: None,
495 handshake: Box::new(DummySae(sae_counter.clone())),
496 retransmit_timeout_id: 0,
497 });
498 let mut sink = UpdateSink::default();
499
500 if let Method::Sae(data) = &mut sae {
501 process_sae_updates(
502 data,
503 &mut sink,
504 vec![sae::SaeUpdate::ResetTimeout(sae::Timeout::Retransmission)],
505 );
506 };
507 let event_id = assert_matches!(sink.pop(),
508 Some(SecAssocUpdate::ScheduleSaeTimeout(id)) => id
509 );
510 sae.on_sae_timeout(&mut sink, event_id).expect("SAE handshake should accept timeout");
511 assert_eq!(sae_counter.lock().handled_timeouts, 1);
512 sae.on_sae_timeout(&mut sink, event_id).expect("SAE handshake should accept timeout");
514 assert_eq!(sae_counter.lock().handled_timeouts, 1); if let Method::Sae(data) = &mut sae {
518 process_sae_updates(
519 data,
520 &mut sink,
521 vec![
522 sae::SaeUpdate::ResetTimeout(sae::Timeout::Retransmission),
523 sae::SaeUpdate::CancelTimeout(sae::Timeout::Retransmission),
524 ],
525 );
526 };
527 let event_id = assert_matches!(sink.pop(),
528 Some(SecAssocUpdate::ScheduleSaeTimeout(id)) => id
529 );
530 sae.on_sae_timeout(&mut sink, event_id).expect("SAE handshake should accept timeout");
531 assert_eq!(sae_counter.lock().handled_timeouts, 1); }
533
534 #[test]
535 fn sae_key_expiration_no_op() {
536 let sae_counter = Arc::new(Mutex::new(SaeCounter::default()));
537 let mut data = SaeData {
538 peer: MacAddr::from([0xaa; 6]),
539 pmk: None,
540 handshake: Box::new(DummySae(sae_counter.clone())),
541 retransmit_timeout_id: 0,
542 };
543 let mut sink = UpdateSink::new();
544 process_sae_updates(
545 &mut data,
546 &mut sink,
547 vec![
548 sae::SaeUpdate::ResetTimeout(sae::Timeout::KeyExpiration),
549 sae::SaeUpdate::CancelTimeout(sae::Timeout::KeyExpiration),
550 ],
551 );
552 assert!(sink.is_empty(), "KeyExpiration should not produce updates.");
553 }
554
555 #[test]
556 fn driver_sae_handles_pmk() {
557 let mut auth = Method::from_config(Config::DriverSae { password: vec![0xbb; 8] })
558 .expect("Failed to construct PSK auth method");
559 let mut sink = UpdateSink::default();
560 auth.on_pmk_available(&[0xcc; 8][..], &[0xdd; 8][..], &mut sink)
561 .expect("Driver SAE should handle on_pmk_available");
562 assert_eq!(sink.len(), 1);
563 let pmk = assert_matches!(sink.get(0), Some(SecAssocUpdate::Key(Key::Pmk(pmk))) => pmk);
564 assert_eq!(*pmk, vec![0xcc; 8]);
565 }
566
567 #[test]
568 fn driver_sae_rejects_sme_sae_calls() {
569 let mut auth = Method::from_config(Config::DriverSae { password: vec![0xbb; 8] })
570 .expect("Failed to construct PSK auth method");
571 let mut sink = UpdateSink::default();
572 auth.on_sae_handshake_ind(&mut sink).expect_err("Driver SAE shouldn't handle SAE ind");
573 let frame = SaeFrame {
574 peer_sta_address: [0xaa; 6],
575 status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
576 seq_num: 1,
577 sae_fields: COMMIT.to_vec(),
578 };
579 auth.on_sae_frame_rx(&mut sink, frame).expect_err("Driver SAE shouldn't handle frames");
580 auth.on_sae_timeout(&mut sink, 0).expect_err("Driver SAE shouldn't handle SAE timeouts");
581 assert!(sink.is_empty());
582 }
583
584 #[test]
585 fn owe_initiates_and_handles_public_key() {
586 let mut auth =
587 Method::from_config(Config::Owe).expect("Failed to construct OWE auth method");
588 let mut sink = UpdateSink::default();
589
590 auth.initiate_owe(&mut sink).expect("OWE handshake should initiate");
591 assert_eq!(sink.len(), 1);
592 let (group_id, key) = assert_matches!(sink.remove(0),
593 SecAssocUpdate::TxOwePublicKey { group_id, key } => (group_id, key)
594 );
595 assert_eq!(group_id, 19);
596 assert!(!key.is_empty());
597
598 const AP_PUBLIC_KEY: [u8; 32] = [
599 0xa9, 0x8c, 0x47, 0xc5, 0xbd, 0xcf, 0x1d, 0x5e, 0x2c, 0x3c, 0x95, 0x8e, 0x10, 0xf3,
600 0x71, 0x61, 0xc4, 0x61, 0x02, 0x13, 0x22, 0xb2, 0x95, 0xf6, 0xc7, 0x81, 0x1e, 0xf8,
601 0x14, 0xc6, 0x03, 0x17,
602 ];
603 auth.on_owe_public_key_rx(&mut sink, group_id, AP_PUBLIC_KEY.to_vec())
604 .expect("OWE handshake should handle public key");
605 assert_eq!(sink.len(), 1);
606 let pmk = assert_matches!(sink.remove(0), SecAssocUpdate::Key(Key::Pmk(pmk)) => pmk);
607 assert!(!pmk.is_empty());
608 }
609}