[wlan][mlme] Adds validation for SAE auth frames

Maintains the old c-binding interface for compatibility with the legacy C++
MLME.

Bug: 40006
Test: Update unit tests
Change-Id: I595406401906676807850fefeb35729edb102232
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/339304
Commit-Queue: Dylan Swiggett <swiggett@google.com>
Reviewed-by: Eric Wang <eyw@google.com>
Testability-Review: Eric Wang <eyw@google.com>
diff --git a/src/connectivity/wlan/lib/mlme/rust/c-binding/bindings.h b/src/connectivity/wlan/lib/mlme/rust/c-binding/bindings.h
index afbeae4..ea6ae6db 100644
--- a/src/connectivity/wlan/lib/mlme/rust/c-binding/bindings.h
+++ b/src/connectivity/wlan/lib/mlme/rust/c-binding/bindings.h
@@ -241,8 +241,6 @@
 
 extern "C" int32_t client_mlme_handle_eth_frame(wlan_client_mlme_t *mlme, wlan_span_t frame);
 
-extern "C" int32_t mlme_is_valid_open_auth_resp(wlan_span_t auth_resp);
-
 extern "C" void mlme_sequence_manager_delete(mlme_sequence_manager_t *mgr);
 
 extern "C" mlme_sequence_manager_t *mlme_sequence_manager_new(void);
diff --git a/src/connectivity/wlan/lib/mlme/rust/c-binding/src/auth.rs b/src/connectivity/wlan/lib/mlme/rust/c-binding/src/auth.rs
deleted file mode 100644
index 09f1bb7e..0000000
--- a/src/connectivity/wlan/lib/mlme/rust/c-binding/src/auth.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use {
-    fuchsia_zircon::sys as zx, log::error, wlan_common::mac, wlan_mlme::auth,
-    zerocopy::LayoutVerified,
-};
-
-#[no_mangle]
-pub extern "C" fn mlme_is_valid_open_auth_resp(auth_resp: wlan_span::CSpan<'_>) -> i32 {
-    // `slice` does not outlive `auth_resp`.
-    let slice: &[u8] = auth_resp.into();
-    match LayoutVerified::<_, mac::AuthHdr>::new_unaligned_from_prefix(slice) {
-        Some((auth_hdr, _)) => {
-            unwrap_or_bail!(auth::is_valid_open_ap_resp(&auth_hdr), zx::ZX_ERR_IO_REFUSED);
-            zx::ZX_OK
-        }
-        None => zx::ZX_ERR_IO_REFUSED,
-    }
-}
diff --git a/src/connectivity/wlan/lib/mlme/rust/c-binding/src/lib.rs b/src/connectivity/wlan/lib/mlme/rust/c-binding/src/lib.rs
index 5c79839..356ecf03 100644
--- a/src/connectivity/wlan/lib/mlme/rust/c-binding/src/lib.rs
+++ b/src/connectivity/wlan/lib/mlme/rust/c-binding/src/lib.rs
@@ -6,10 +6,6 @@
 
 // Explicitly declare usage for cbindgen.
 
-#[macro_use]
-pub mod utils;
-
 pub mod ap;
-pub mod auth;
 pub mod client;
 pub mod sequence;
diff --git a/src/connectivity/wlan/lib/mlme/rust/c-binding/src/utils.rs b/src/connectivity/wlan/lib/mlme/rust/c-binding/src/utils.rs
deleted file mode 100644
index fa70df1..0000000
--- a/src/connectivity/wlan/lib/mlme/rust/c-binding/src/utils.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/// Unwraps a given `Result` yielding content of `Ok`.
-/// If Result carries an `Err` its content will be logged and the function will early return with
-/// the raw value of the given `zx::Status`.
-/// This macro is comparable to Rust's try macro.
-macro_rules! unwrap_or_bail {
-    ($result:expr, $e:expr) => {
-        match $result {
-            Ok(x) => x,
-            Err(e) => {
-                error!("error: {}", e);
-                return $e.into();
-            }
-        }
-    };
-}
diff --git a/src/connectivity/wlan/lib/mlme/rust/src/auth.rs b/src/connectivity/wlan/lib/mlme/rust/src/auth.rs
index 4edfd38..82c3aff 100644
--- a/src/connectivity/wlan/lib/mlme/rust/src/auth.rs
+++ b/src/connectivity/wlan/lib/mlme/rust/src/auth.rs
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 use {
-    anyhow::{ensure, Error},
+    anyhow::{bail, ensure, Error},
     wlan_common::mac,
 };
 
@@ -15,52 +15,91 @@
     }
 }
 
-/// Validates whether a given authentication header is a valid response to an open authentication
+#[derive(Debug)]
+pub enum ValidFrame {
+    Open,
+    SaeCommit,
+    SaeConfirm,
+}
+
+/// Validates whether a given authentication header is a valid response to an authentication
 /// request.
-pub fn is_valid_open_ap_resp(auth: &mac::AuthHdr) -> Result<(), Error> {
-    ensure!(
-        { auth.auth_alg_num } == mac::AuthAlgorithmNumber::OPEN,
-        "invalid auth_alg_num: {}",
-        { auth.auth_alg_num }.0
-    );
-    ensure!(auth.auth_txn_seq_num == 2, "invalid auth_txn_seq_num: {}", { auth.auth_txn_seq_num });
+pub fn validate_ap_resp(auth: &mac::AuthHdr) -> Result<ValidFrame, Error> {
     ensure!(
         { auth.status_code } == mac::StatusCode::SUCCESS,
         "invalid status_code: {}",
         { auth.status_code }.0
     );
-    Ok(())
+    match auth.auth_alg_num {
+        mac::AuthAlgorithmNumber::OPEN => {
+            ensure!(auth.auth_txn_seq_num == 2, "invalid auth_txn_seq_num: {}", {
+                auth.auth_txn_seq_num
+            });
+            Ok(ValidFrame::Open)
+        }
+        mac::AuthAlgorithmNumber::SAE => match auth.auth_txn_seq_num {
+            1 => Ok(ValidFrame::SaeCommit),
+            2 => Ok(ValidFrame::SaeConfirm),
+            _ => bail!("invalid auth_txn_seq_num: {}", { auth.auth_txn_seq_num }),
+        },
+        _ => bail!("invalid auth_alg_num: {}", { auth.auth_alg_num }.0),
+    }
 }
 
 #[cfg(test)]
 mod tests {
-    use super::*;
+    use {super::*, wlan_common::assert_variant};
 
-    fn make_valid_auth_resp() -> mac::AuthHdr {
+    fn make_valid_auth_resp(frame_type: ValidFrame) -> mac::AuthHdr {
         mac::AuthHdr {
-            auth_alg_num: mac::AuthAlgorithmNumber::OPEN,
-            auth_txn_seq_num: 2,
+            auth_alg_num: match frame_type {
+                ValidFrame::Open => mac::AuthAlgorithmNumber::OPEN,
+                ValidFrame::SaeCommit | ValidFrame::SaeConfirm => mac::AuthAlgorithmNumber::SAE,
+            },
+            auth_txn_seq_num: match frame_type {
+                ValidFrame::SaeCommit => 1,
+                ValidFrame::Open | ValidFrame::SaeConfirm => 2,
+            },
             status_code: mac::StatusCode::SUCCESS,
         }
     }
 
     #[test]
-    fn valid_open_auth_resp() {
-        assert!(is_valid_open_ap_resp(&make_valid_auth_resp()).is_ok());
+    fn valid_auth_resp() {
+        assert_variant!(
+            validate_ap_resp(&make_valid_auth_resp(ValidFrame::Open)),
+            Ok(ValidFrame::Open)
+        );
+        assert_variant!(
+            validate_ap_resp(&make_valid_auth_resp(ValidFrame::SaeCommit)),
+            Ok(ValidFrame::SaeCommit)
+        );
+        assert_variant!(
+            validate_ap_resp(&make_valid_auth_resp(ValidFrame::SaeConfirm)),
+            Ok(ValidFrame::SaeConfirm)
+        );
     }
 
     #[test]
-    fn invalid_open_auth_resp() {
-        let mut auth_hdr = make_valid_auth_resp();
+    fn invalid_auth_resp() {
+        let mut auth_hdr = make_valid_auth_resp(ValidFrame::Open);
         auth_hdr.auth_alg_num = mac::AuthAlgorithmNumber::FAST_BSS_TRANSITION;
-        assert!(is_valid_open_ap_resp(&auth_hdr).is_err());
+        assert_variant!(validate_ap_resp(&auth_hdr), Err(_));
 
-        let mut auth_hdr = make_valid_auth_resp();
+        let mut auth_hdr = make_valid_auth_resp(ValidFrame::Open);
         auth_hdr.auth_txn_seq_num = 1;
-        assert!(is_valid_open_ap_resp(&auth_hdr).is_err());
+        assert_variant!(validate_ap_resp(&auth_hdr), Err(_));
 
-        let mut auth_hdr = make_valid_auth_resp();
+        let mut auth_hdr = make_valid_auth_resp(ValidFrame::Open);
         auth_hdr.status_code = mac::StatusCode::REFUSED;
-        assert!(is_valid_open_ap_resp(&auth_hdr).is_err());
+        assert_variant!(validate_ap_resp(&auth_hdr), Err(_));
+
+        let mut auth_hdr = make_valid_auth_resp(ValidFrame::SaeCommit);
+        auth_hdr.auth_txn_seq_num = 4;
+        assert_variant!(validate_ap_resp(&auth_hdr), Err(_));
+
+        let mut auth_hdr = make_valid_auth_resp(ValidFrame::SaeCommit);
+        auth_hdr.status_code = mac::StatusCode::REFUSED;
+        assert_variant!(validate_ap_resp(&auth_hdr), Err(_));
     }
 }
diff --git a/src/connectivity/wlan/lib/mlme/rust/src/client/state.rs b/src/connectivity/wlan/lib/mlme/rust/src/client/state.rs
index 01533f5..5fdc490 100644
--- a/src/connectivity/wlan/lib/mlme/rust/src/client/state.rs
+++ b/src/connectivity/wlan/lib/mlme/rust/src/client/state.rs
@@ -97,13 +97,17 @@
     fn on_auth_frame(&self, sta: &mut BoundClient<'_>, auth_hdr: &mac::AuthHdr) -> Result<(), ()> {
         sta.ctx.timer.cancel_event(self.timeout);
 
-        match auth::is_valid_open_ap_resp(auth_hdr) {
-            Ok(()) => {
+        let frame_type = auth::validate_ap_resp(auth_hdr).map_err(|e| {
+            error!("authentication with BSS failed: {}", e);
+            sta.send_authenticate_conf(fidl_mlme::AuthenticateResultCodes::AuthenticationRejected);
+        })?;
+        match frame_type {
+            auth::ValidFrame::Open => {
                 sta.send_authenticate_conf(fidl_mlme::AuthenticateResultCodes::Success);
                 Ok(())
             }
-            Err(e) => {
-                error!("authentication with BSS failed: {}", e);
+            _ => {
+                error!("authentication with BSS failed: unhandled auth type {:?}", frame_type);
                 sta.send_authenticate_conf(
                     fidl_mlme::AuthenticateResultCodes::AuthenticationRejected,
                 );
@@ -746,6 +750,9 @@
     Authenticated => Associating,
     Associating => Associated,
 
+    // Multi-step authentication (SAE):
+    Authenticating => Authenticating,
+
     // Timeout:
     Authenticating => Joined,
     Associating => Authenticated,