wlan_mlme/
akm_algorithm.rs

1// Copyright 2018 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::auth;
6use anyhow::{bail, Error};
7use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
8use log::error;
9use wlan_common::mac;
10use zerocopy::SplitByteSlice;
11
12/// AkmState indicates the current status of authentication after each event is handled by an
13/// AkmAlgorithm.
14#[derive(Debug)]
15pub enum AkmState {
16    /// Authentication is proceeding as expected.
17    InProgress,
18    /// Authentication has been cancelled, rejected, or ended.
19    Failed,
20    /// Authentication is complete and we should proceed to association.
21    AuthComplete,
22}
23
24/// AkmAction allows an AkmAlgorithm to interact with the rest of MLME without tying the
25/// implementation to a particular type of STA.
26pub trait AkmAction {
27    /// Transmit an auth frame to the peer in this auth exchange.
28    fn send_auth_frame(
29        &mut self,
30        auth_type: mac::AuthAlgorithmNumber,
31        seq_num: u16,
32        status_code: mac::StatusCode,
33        auth_content: &[u8],
34    ) -> Result<(), Error>;
35    /// Transmit information for an SME-managed SAE handshaek
36    fn forward_sme_sae_rx(
37        &mut self,
38        seq_num: u16,
39        status_code: fidl_ieee80211::StatusCode,
40        sae_fields: Vec<u8>,
41    );
42    fn forward_sae_handshake_ind(&mut self);
43}
44
45/// An algorithm used to perform authentication and optionally generate a PMK.
46pub enum AkmAlgorithm {
47    _OpenAp,
48    OpenSupplicant,
49    Sae,
50}
51
52impl std::fmt::Debug for AkmAlgorithm {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
54        f.write_str(match self {
55            AkmAlgorithm::_OpenAp { .. } => "Open authentication AP",
56            AkmAlgorithm::OpenSupplicant { .. } => "Open authentication SAE",
57            AkmAlgorithm::Sae { .. } => "SAE authentication",
58        })
59    }
60}
61
62impl AkmAlgorithm {
63    pub fn initiate<A: AkmAction>(&mut self, actions: &mut A) -> Result<AkmState, Error> {
64        match self {
65            AkmAlgorithm::_OpenAp => {
66                error!("OpenAp AKM does not support initiating an auth exchange.");
67                Ok(AkmState::Failed)
68            }
69            AkmAlgorithm::OpenSupplicant => {
70                actions.send_auth_frame(
71                    mac::AuthAlgorithmNumber::OPEN,
72                    1,
73                    fidl_ieee80211::StatusCode::Success.into(),
74                    &[],
75                )?;
76                Ok(AkmState::InProgress)
77            }
78            AkmAlgorithm::Sae => {
79                actions.forward_sae_handshake_ind();
80                Ok(AkmState::InProgress)
81            }
82        }
83    }
84
85    pub fn handle_auth_frame<A: AkmAction, B: SplitByteSlice>(
86        &mut self,
87        actions: &mut A,
88        auth_frame: mac::AuthFrame<B>,
89    ) -> Result<AkmState, Error> {
90        let (auth_hdr, auth_body) = auth_frame.into_auth_body();
91        match self {
92            AkmAlgorithm::_OpenAp => bail!("OpenAp akm not yet implemented"),
93            AkmAlgorithm::OpenSupplicant { .. } => match auth::validate_ap_resp(&auth_hdr) {
94                Ok(auth::ValidFrame::Open) => Ok(AkmState::AuthComplete),
95                Ok(frame_type) => {
96                    error!("Received unhandled auth frame type {:?}", frame_type);
97                    Ok(AkmState::Failed)
98                }
99                Err(e) => {
100                    error!("Received invalid auth frame: {}", e);
101                    Ok(AkmState::Failed)
102                }
103            },
104            AkmAlgorithm::Sae { .. } => {
105                let sae_fields = auth_body.to_vec();
106                actions.forward_sme_sae_rx(
107                    auth_hdr.auth_txn_seq_num,
108                    // TODO(https://fxbug.dev/42172907): All reserved values mapped to REFUSED_REASON_UNSPECIFIED.
109                    Option::<fidl_ieee80211::StatusCode>::from(auth_hdr.status_code)
110                        .unwrap_or(fidl_ieee80211::StatusCode::RefusedReasonUnspecified),
111                    sae_fields,
112                );
113                Ok(AkmState::InProgress)
114            }
115        }
116    }
117
118    pub fn handle_sae_resp<A: AkmAction>(
119        &mut self,
120        _actions: &mut A,
121        status_code: fidl_ieee80211::StatusCode,
122    ) -> Result<AkmState, Error> {
123        match self {
124            AkmAlgorithm::_OpenAp => bail!("OpenAp akm not yet implemented"),
125            AkmAlgorithm::OpenSupplicant { .. } => {
126                bail!("Open supplicant doesn't expect an SaeResp")
127            }
128            AkmAlgorithm::Sae { .. } => match status_code {
129                fidl_ieee80211::StatusCode::Success => Ok(AkmState::AuthComplete),
130                _ => Ok(AkmState::Failed),
131            },
132        }
133    }
134
135    pub fn handle_sme_sae_tx<A: AkmAction>(
136        &mut self,
137        actions: &mut A,
138        seq_num: u16,
139        status_code: fidl_ieee80211::StatusCode,
140        sae_fields: &[u8],
141    ) -> Result<AkmState, Error> {
142        match self {
143            AkmAlgorithm::_OpenAp => bail!("OpenAp akm not yet implemented"),
144            AkmAlgorithm::OpenSupplicant { .. } => {
145                bail!("Open supplicant cannot transmit SAE frames")
146            }
147            AkmAlgorithm::Sae { .. } => {
148                actions.send_auth_frame(
149                    mac::AuthAlgorithmNumber::SAE,
150                    seq_num,
151                    status_code.into(),
152                    sae_fields,
153                )?;
154                // The handshake may be complete at this point, but we wait for an SaeResp.
155                Ok(AkmState::InProgress)
156            }
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use fidl_fuchsia_wlan_mlme as fidl_mlme;
165    use wlan_common::assert_variant;
166    use wlan_common::mac::IntoBytesExt;
167
168    struct MockAkmAction {
169        sent_frames: Vec<(mac::AuthAlgorithmNumber, u16, mac::StatusCode, Vec<u8>)>,
170        sent_sae_rx: Vec<(u16, fidl_ieee80211::StatusCode, Vec<u8>)>,
171        accept_frames: bool,
172        published_pmks: Vec<fidl_mlme::PmkInfo>,
173        sae_ind_sent: u16,
174    }
175
176    impl MockAkmAction {
177        fn new() -> Self {
178            MockAkmAction {
179                sent_frames: vec![],
180                sent_sae_rx: vec![],
181                accept_frames: true,
182                published_pmks: vec![],
183                sae_ind_sent: 0,
184            }
185        }
186    }
187
188    impl AkmAction for MockAkmAction {
189        fn send_auth_frame(
190            &mut self,
191            auth_type: mac::AuthAlgorithmNumber,
192            seq_num: u16,
193            status_code: mac::StatusCode,
194            auth_content: &[u8],
195        ) -> Result<(), Error> {
196            if self.accept_frames {
197                self.sent_frames.push((auth_type, seq_num, status_code, auth_content.to_vec()));
198                Ok(())
199            } else {
200                bail!("send_auth_frames disabled by test");
201            }
202        }
203
204        fn forward_sme_sae_rx(
205            &mut self,
206            seq_num: u16,
207            status_code: fidl_ieee80211::StatusCode,
208            sae_fields: Vec<u8>,
209        ) {
210            self.sent_sae_rx.push((seq_num, status_code, sae_fields))
211        }
212
213        fn forward_sae_handshake_ind(&mut self) {
214            self.sae_ind_sent += 1
215        }
216    }
217
218    #[test]
219    fn open_supplicant_success() {
220        let mut actions = MockAkmAction::new();
221        let mut supplicant = AkmAlgorithm::OpenSupplicant;
222
223        // Initiate sends
224        assert_variant!(supplicant.initiate(&mut actions), Ok(AkmState::InProgress));
225        assert_eq!(actions.sent_frames.len(), 1);
226        assert_eq!(
227            actions.sent_frames.remove(0),
228            (mac::AuthAlgorithmNumber::OPEN, 1, fidl_ieee80211::StatusCode::Success.into(), vec![])
229        );
230
231        assert_variant!(
232            supplicant.handle_auth_frame(
233                &mut actions,
234                // A valid response completes auth.
235                mac::AuthFrame {
236                    auth_hdr: mac::AuthHdr {
237                        auth_alg_num: mac::AuthAlgorithmNumber::OPEN,
238                        auth_txn_seq_num: 2,
239                        status_code: fidl_ieee80211::StatusCode::Success.into(),
240                    }
241                    .as_bytes_ref(),
242                    elements: &[][..],
243                },
244            ),
245            Ok(AkmState::AuthComplete)
246        );
247
248        // Everything is cleaned up.
249        assert_eq!(actions.sent_frames.len(), 0);
250        assert_eq!(actions.published_pmks.len(), 0);
251    }
252
253    #[test]
254    fn open_supplicant_reject() {
255        let mut actions = MockAkmAction::new();
256        let mut supplicant = AkmAlgorithm::OpenSupplicant;
257
258        // Initiate sends
259        assert_variant!(supplicant.initiate(&mut actions), Ok(AkmState::InProgress));
260        assert_eq!(actions.sent_frames.len(), 1);
261        actions.sent_frames.clear();
262
263        assert_variant!(
264            supplicant.handle_auth_frame(
265                &mut actions,
266                // A rejected response ends auth.
267                mac::AuthFrame {
268                    auth_hdr: mac::AuthHdr {
269                        auth_alg_num: mac::AuthAlgorithmNumber::OPEN,
270                        auth_txn_seq_num: 2,
271                        status_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified.into(),
272                    }
273                    .as_bytes_ref(),
274                    elements: &[][..],
275                },
276            ),
277            Ok(AkmState::Failed)
278        );
279
280        // Everything is cleaned up.
281        assert_eq!(actions.sent_frames.len(), 0);
282        assert_eq!(actions.published_pmks.len(), 0);
283    }
284
285    #[test]
286    fn sae_supplicant_success() {
287        let mut actions = MockAkmAction::new();
288        let mut supplicant = AkmAlgorithm::Sae;
289
290        assert_variant!(supplicant.initiate(&mut actions), Ok(AkmState::InProgress));
291        assert_eq!(actions.sae_ind_sent, 1);
292        assert_eq!(actions.sent_frames.len(), 0);
293
294        // We only test sending one frame each way, since there's no functional difference in the
295        // second exchange.
296
297        assert_variant!(
298            supplicant.handle_sme_sae_tx(
299                &mut actions,
300                1,
301                fidl_ieee80211::StatusCode::Success,
302                &[0x12, 0x34][..],
303            ),
304            Ok(AkmState::InProgress)
305        );
306        assert_eq!(actions.sent_frames.len(), 1);
307        assert_eq!(
308            actions.sent_frames[0],
309            (
310                mac::AuthAlgorithmNumber::SAE,
311                1,
312                fidl_ieee80211::StatusCode::Success.into(),
313                vec![0x12, 0x34]
314            )
315        );
316        actions.sent_frames.clear();
317
318        assert_variant!(
319            supplicant.handle_auth_frame(
320                &mut actions,
321                mac::AuthFrame {
322                    auth_hdr: mac::AuthHdr {
323                        auth_alg_num: mac::AuthAlgorithmNumber::SAE,
324                        auth_txn_seq_num: 1,
325                        status_code: fidl_ieee80211::StatusCode::Success.into(),
326                    }
327                    .as_bytes_ref(),
328                    elements: &[0x56, 0x78][..],
329                },
330            ),
331            Ok(AkmState::InProgress)
332        );
333        assert_eq!(actions.sent_sae_rx.len(), 1);
334        assert_eq!(
335            actions.sent_sae_rx[0],
336            (1, fidl_ieee80211::StatusCode::Success, vec![0x56, 0x78])
337        );
338        actions.sent_sae_rx.clear();
339
340        assert_variant!(
341            supplicant.handle_sae_resp(&mut actions, fidl_ieee80211::StatusCode::Success),
342            Ok(AkmState::AuthComplete)
343        );
344    }
345
346    #[test]
347    fn sae_supplicant_rejected() {
348        let mut actions = MockAkmAction::new();
349        let mut supplicant = AkmAlgorithm::Sae;
350
351        assert_variant!(supplicant.initiate(&mut actions), Ok(AkmState::InProgress));
352        assert_variant!(
353            supplicant.handle_sae_resp(
354                &mut actions,
355                fidl_ieee80211::StatusCode::RefusedReasonUnspecified
356            ),
357            Ok(AkmState::Failed)
358        );
359    }
360}