fullmac_helpers/
fake_ap.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::recorded_request_stream::RecordedRequestStream;
6use assert_matches::assert_matches;
7use fidl_fuchsia_wlan_mlme::EapolResultCode;
8use fuchsia_sync::Mutex;
9use ieee80211::MacAddr;
10use std::sync::Arc;
11use wlan_common::bss;
12use wlan_common::ie::rsn::cipher::{CIPHER_BIP_CMAC_128, CIPHER_CCMP_128};
13use wlan_common::ie::rsn::rsne;
14use wlan_rsn::Authenticator;
15use wlan_rsn::rsna::{SecAssocUpdate, UpdateSink};
16use zerocopy::IntoBytes;
17use {
18    fidl_fuchsia_wlan_common_security as fidl_wlan_security,
19    fidl_fuchsia_wlan_fullmac as fidl_fullmac, fidl_fuchsia_wlan_mlme as fidl_mlme,
20};
21
22/// Creates a WPA2 authenticator based on the given parameters.
23pub fn create_wpa2_authenticator(
24    client_mac_addr: MacAddr,
25    bss_description: &bss::BssDescription,
26    credentials: fidl_wlan_security::WpaCredentials,
27) -> Authenticator {
28    assert_eq!(bss_description.protection(), bss::Protection::Wpa2Personal);
29
30    // It's assumed that advertised and supplicant protections are the same.
31    let advertised_protection_info = get_protection_info(bss_description);
32    let supplicant_protection_info = get_protection_info(bss_description);
33
34    let nonce_rdr = wlan_rsn::nonce::NonceReader::new(&bss_description.bssid.clone().into())
35        .expect("creating nonce reader");
36    let gtk_provider =
37        wlan_rsn::GtkProvider::new(CIPHER_CCMP_128, 1, 0).expect("creating gtk provider");
38
39    let psk = match credentials {
40        fidl_wlan_security::WpaCredentials::Passphrase(passphrase) => {
41            wlan_rsn::psk::compute(passphrase.as_bytes(), &bss_description.ssid)
42                .expect("Could not compute psk")
43        }
44        fidl_wlan_security::WpaCredentials::Psk(psk) => Box::new(psk),
45        _ => panic!("Unsupported credential type"),
46    };
47
48    Authenticator::new_wpa2psk_ccmp128(
49        nonce_rdr,
50        Arc::new(Mutex::new(gtk_provider)),
51        psk,
52        client_mac_addr,
53        supplicant_protection_info,
54        bss_description.bssid.clone().into(),
55        advertised_protection_info,
56    )
57    .expect("Failed to create authenticator")
58}
59
60/// Creates a WPA3 authenticator based on the given parameters.
61pub fn create_wpa3_authenticator(
62    client_mac_addr: MacAddr,
63    bss_description: &bss::BssDescription,
64    credentials: fidl_wlan_security::WpaCredentials,
65) -> Authenticator {
66    assert_eq!(bss_description.protection(), bss::Protection::Wpa3Personal);
67
68    // It's assumed that advertised and supplicant protections are the same.
69    let advertised_protection_info = get_protection_info(bss_description);
70    let supplicant_protection_info = get_protection_info(bss_description);
71
72    let password =
73        assert_matches!(credentials, fidl_wlan_security::WpaCredentials::Passphrase(p) => p);
74
75    let nonce_rdr = wlan_rsn::nonce::NonceReader::new(&bss_description.bssid.clone().into())
76        .expect("creating nonce reader");
77    let gtk_provider =
78        wlan_rsn::GtkProvider::new(CIPHER_CCMP_128, 1, 0).expect("creating gtk provider");
79    let igtk_provider =
80        wlan_rsn::IgtkProvider::new(CIPHER_BIP_CMAC_128).expect("error creating IgtkProvider");
81
82    Authenticator::new_wpa3(
83        nonce_rdr,
84        Arc::new(Mutex::new(gtk_provider)),
85        Arc::new(Mutex::new(igtk_provider)),
86        bss_description.ssid.clone(),
87        password,
88        client_mac_addr,
89        supplicant_protection_info.clone(),
90        bss_description.bssid.into(),
91        advertised_protection_info,
92    )
93    .expect("Failed to create authenticator")
94}
95
96// Uses |fullmac_req_stream| and |fullmac_ifc_proxy| to perform an SAE exchange as a WPA3
97// authenticator.
98//
99// This assumes that the user has already sent an `SaeHandshakeInd` to the test realm, and the test
100// realm is ready to send the first SAE commit frame to the authenticator.
101//
102// Returns the EAPOL key frame that will be used as the first frame in the EAPOL handshake.
103// When this function returns, the user can expect that:
104//  - |authenticator| is ready to be initiated and used in an EAPOL handshake.
105//  - |fullmac_req_stream| has a pending `SaeHandshakeResp` request.
106//
107// Panics if the handshake fails for any reason.
108pub async fn handle_sae_exchange(
109    authenticator: &mut Authenticator,
110    fullmac_req_stream: &mut RecordedRequestStream,
111    fullmac_ifc_proxy: &fidl_fullmac::WlanFullmacImplIfcProxy,
112) -> UpdateSink {
113    let mut update_sink = UpdateSink::new();
114
115    // Handle supplicant confirm
116    let supplicant_commit_frame = get_sae_frame_from_test_realm(fullmac_req_stream).await;
117    authenticator
118        .on_sae_frame_rx(&mut update_sink, supplicant_commit_frame)
119        .expect("Failed to send SAE commit frame to authenticator");
120
121    // Authenticator produces the commit and confirm frame at the same time
122    // after receiving the supplicant commit.
123    let authenticator_commit_frame = assert_matches!(
124        &update_sink[0], SecAssocUpdate::TxSaeFrame(frame) => frame.clone());
125    let authenticator_confirm_frame = assert_matches!(
126        &update_sink[1], SecAssocUpdate::TxSaeFrame(frame) => frame.clone());
127    update_sink.clear();
128
129    // Send the authenticator commit frame
130    send_sae_frame_to_test_realm(authenticator_commit_frame, fullmac_ifc_proxy).await;
131
132    // Handle supplicant confirm frame
133    let supplicant_confirm_frame = get_sae_frame_from_test_realm(fullmac_req_stream).await;
134    authenticator
135        .on_sae_frame_rx(&mut update_sink, supplicant_confirm_frame)
136        .expect("Failed to send SAE confirm frame to authenticator");
137
138    // Send the authenticator confirm frame
139    send_sae_frame_to_test_realm(authenticator_confirm_frame, fullmac_ifc_proxy).await;
140
141    update_sink
142}
143
144/// Uses |fullmac_req_stream| and |fullmac_ifc_proxy| to perform an EAPOL handshake as an
145/// authenticator.
146/// This assumes that |authenticator| is already initiated.
147/// |frame_to_client| is the first EAPOL frame that the authenticator sends.
148///
149/// Returns the UpdateSink of |authenticator|. By the end of a successful EAPOL handshake, the
150/// UpdateSink should include all the keys for a successful EAPOL handshake.
151/// Panics if the handshake fails for any reason.
152pub async fn handle_fourway_eapol_handshake(
153    authenticator: &mut Authenticator,
154    frame_to_client: eapol::KeyFrameBuf,
155    bssid: [u8; 6],
156    client_sta_addr: [u8; 6],
157    fullmac_req_stream: &mut RecordedRequestStream,
158    fullmac_ifc_proxy: &fidl_fullmac::WlanFullmacImplIfcProxy,
159) -> UpdateSink {
160    let mut update_sink = UpdateSink::new();
161    let mic_size = authenticator.get_negotiated_protection().mic_size;
162
163    send_eapol_frame_to_test_realm(
164        authenticator,
165        frame_to_client,
166        bssid.clone(),
167        client_sta_addr.clone(),
168        fullmac_ifc_proxy,
169    )
170    .await;
171    let frame_to_auth_data =
172        get_eapol_frame_from_test_realm(bssid.clone(), fullmac_req_stream, fullmac_ifc_proxy).await;
173    let frame_to_auth = eapol::KeyFrameRx::parse(mic_size as usize, &frame_to_auth_data[..])
174        .expect("Could not parse EAPOL key frame");
175    authenticator
176        .on_eapol_frame(&mut update_sink, eapol::Frame::Key(frame_to_auth))
177        .expect("Could not send EAPOL frame to authenticator");
178
179    let frame_to_client = assert_matches!(update_sink.remove(0), SecAssocUpdate::TxEapolKeyFrame { frame, .. } => frame);
180    send_eapol_frame_to_test_realm(
181        authenticator,
182        frame_to_client,
183        bssid.clone(),
184        client_sta_addr.clone(),
185        fullmac_ifc_proxy,
186    )
187    .await;
188    let frame_to_auth_data =
189        get_eapol_frame_from_test_realm(bssid.clone(), fullmac_req_stream, fullmac_ifc_proxy).await;
190    let frame_to_auth = eapol::KeyFrameRx::parse(mic_size as usize, &frame_to_auth_data[..])
191        .expect("Could not parse EAPOL key frame");
192    authenticator
193        .on_eapol_frame(&mut update_sink, eapol::Frame::Key(frame_to_auth))
194        .expect("Could not send EAPOL frame to authenticator");
195
196    update_sink
197}
198
199/// Sends an EAPOL |frame| to the test realm via |fullmac_ifc_proxy|.
200async fn send_eapol_frame_to_test_realm(
201    authenticator: &mut Authenticator,
202    frame: eapol::KeyFrameBuf,
203    authenticator_addr: [u8; 6],
204    client_addr: [u8; 6],
205    fullmac_ifc_proxy: &fidl_fullmac::WlanFullmacImplIfcProxy,
206) {
207    fullmac_ifc_proxy
208        .eapol_ind(&fidl_fullmac::WlanFullmacImplIfcEapolIndRequest {
209            src_addr: Some(authenticator_addr),
210            dst_addr: Some(client_addr),
211            data: Some(frame.into()),
212            ..Default::default()
213        })
214        .await
215        .expect("Could not send EAPOL ind");
216    let mut update_sink = UpdateSink::new();
217    authenticator
218        .on_eapol_conf(&mut update_sink, EapolResultCode::Success)
219        .expect("Could not send EAPOL conf to authenticator");
220    assert_eq!(update_sink.len(), 0);
221}
222
223/// Returns the buffer containing EAPOL frame data from the test realm through |fullmac_req_stream|.
224async fn get_eapol_frame_from_test_realm(
225    authenticator_addr: [u8; 6],
226    fullmac_req_stream: &mut RecordedRequestStream,
227    fullmac_ifc_proxy: &fidl_fullmac::WlanFullmacImplIfcProxy,
228) -> Vec<u8> {
229    let frame_data = assert_matches!(fullmac_req_stream.next().await,
230        fidl_fullmac::WlanFullmacImpl_Request::EapolTx { payload, responder } => {
231            responder
232                .send()
233                .expect("Failed to respond to EapolTx");
234            payload.data.unwrap()
235    });
236
237    fullmac_ifc_proxy
238        .eapol_conf(&fidl_fullmac::WlanFullmacImplIfcEapolConfRequest {
239            result_code: Some(fidl_fullmac::EapolTxResult::Success),
240            dst_addr: Some(authenticator_addr),
241            ..Default::default()
242        })
243        .await
244        .expect("Could not send EAPOL conf");
245
246    frame_data
247}
248
249fn get_protection_info(bss_description: &bss::BssDescription) -> wlan_rsn::ProtectionInfo {
250    let (_, rsne) = rsne::from_bytes(bss_description.rsne().unwrap()).expect("Could not get RSNE");
251    wlan_rsn::ProtectionInfo::Rsne(rsne)
252}
253
254async fn get_sae_frame_from_test_realm(
255    fullmac_req_stream: &mut RecordedRequestStream,
256) -> fidl_mlme::SaeFrame {
257    let fullmac_sae_frame = assert_matches!(fullmac_req_stream.next().await,
258        fidl_fullmac::WlanFullmacImpl_Request::SaeFrameTx { frame, responder } => {
259            responder
260                .send()
261                .expect("Failed to respond to SaeFrameTx");
262            frame
263    });
264
265    fidl_mlme::SaeFrame {
266        peer_sta_address: fullmac_sae_frame.peer_sta_address.unwrap(),
267        status_code: fullmac_sae_frame.status_code.unwrap(),
268        seq_num: fullmac_sae_frame.seq_num.unwrap(),
269        sae_fields: fullmac_sae_frame.sae_fields.unwrap(),
270    }
271}
272
273async fn send_sae_frame_to_test_realm(
274    frame: fidl_mlme::SaeFrame,
275    fullmac_ifc_proxy: &fidl_fullmac::WlanFullmacImplIfcProxy,
276) {
277    fullmac_ifc_proxy
278        .sae_frame_rx(&fidl_fullmac::SaeFrame {
279            peer_sta_address: Some(frame.peer_sta_address),
280            status_code: Some(frame.status_code),
281            seq_num: Some(frame.seq_num),
282            sae_fields: Some(frame.sae_fields.clone()),
283            ..Default::default()
284        })
285        .await
286        .expect("Could not send authenticator SAE commit frame");
287}