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