Skip to main content

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