wlan_fcg_crypto/
owe.rs

1// Copyright 2025 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 anyhow::{Error, bail};
6use mundane::hash::Sha256;
7use num::FromPrimitive;
8use wlan_common::ie::rsn::akm::{self, Akm};
9
10use crate::boringssl::{Bignum, EcGroupId, EcPoint};
11use crate::ecc;
12use crate::fcg::FiniteCyclicGroup;
13use crate::hmac_utils::{HmacUtils, HmacUtilsImpl};
14
15/// We store an FcgConstructor rather than a FiniteCyclicGroup so that our handshake
16/// can impl `Send`. FCGs are not generally `Send`, so we construct them on the fly.
17type FcgConstructor<E> =
18    Box<dyn Fn() -> Result<Box<dyn FiniteCyclicGroup<Element = E>>, Error> + Send + 'static>;
19
20#[derive(Debug)]
21pub enum OweUpdate {
22    TxPublicKey { group_id: u16, key: Vec<u8> },
23    Success { key: Vec<u8> },
24}
25
26pub type OweUpdateSink = Vec<OweUpdate>;
27
28/// IEEE 8021.11-2024, 12.14: Opportunistic Wireless Encryption
29///
30/// An OWE handshake occurs during a association between a client and an AP. The client
31/// initiates the handshake by generating a public/private key pair and sending its public key
32/// to the AP. The AP then also generates a own public/private key pair and responds with its
33/// own public key. Both parties then compute a shared secret and derive the Pairwise Master Key
34/// (PMK) from it.
35pub trait ClientOweHandshake: Send {
36    /// Initiate OWE by generating a public/private key pair and putting the public key
37    /// in the update sink.
38    fn initiate_owe(&mut self, sink: &mut OweUpdateSink) -> Result<(), Error>;
39
40    /// Handle the received public key from the AP and compute the shared secret.
41    /// A PMK is derived and put in the update sink.
42    fn handle_public_key(
43        &mut self,
44        sink: &mut OweUpdateSink,
45        group: u16,
46        peer_public_key: Vec<u8>,
47    ) -> Result<(), Error>;
48}
49
50/// IEEE 8021.11-2024, 12.14: Opportunistic Wireless Encryption
51pub trait ApOweHandshake: Send {
52    /// Handle the received public key from the client by generating a public/private key pair,
53    /// computing the shared secret, and deriving the PMK. The AP's public key and the PMK
54    /// will be put in the update sink.
55    fn handle_public_key(
56        &mut self,
57        sink: &mut OweUpdateSink,
58        group: u16,
59        peer_public_key: Vec<u8>,
60    ) -> Result<(), Error>;
61}
62
63pub enum OweHandshakeEntity {
64    Client,
65    Ap,
66}
67
68/// Creates a client OWE handshake for the given group ID and authentication parameters.
69pub fn new_client_owe_handshake(
70    group_id: u16,
71    akm: Akm,
72) -> Result<Box<dyn ClientOweHandshake>, Error> {
73    new_owe_handshake(group_id, akm, OweHandshakeEntity::Client)
74        .map(|h| Box::new(h) as Box<dyn ClientOweHandshake>)
75}
76
77/// Creates a AP OWE handshake for the given group ID and authentication parameters.
78pub fn new_ap_owe_handshake(group_id: u16, akm: Akm) -> Result<Box<dyn ApOweHandshake>, Error> {
79    new_owe_handshake(group_id, akm, OweHandshakeEntity::Ap)
80        .map(|h| Box::new(h) as Box<dyn ApOweHandshake>)
81}
82
83fn new_owe_handshake(
84    group_id: u16,
85    akm: Akm,
86    entity: OweHandshakeEntity,
87) -> Result<OweHandshakeImpl<EcPoint>, Error> {
88    match akm.suite_type {
89        akm::OWE => (),
90        _ => bail!("Cannot construct OWE handshake with AKM {:?}", akm),
91    };
92    let (hmac, group_constructor) = match EcGroupId::from_u16(group_id) {
93        Some(EcGroupId::P256) => {
94            // IEEE 802.11-2024, 12.4.2
95            // Group 19 has a 256-bit prime length, thus we use SHA256.
96            let hmac = Box::new(HmacUtilsImpl::<Sha256>::new());
97            let group_constructor = Box::new(|| {
98                ecc::Group::new(EcGroupId::P256).map(|group| {
99                    Box::new(group)
100                        as Box<
101                            dyn FiniteCyclicGroup<
102                                Element = <ecc::Group as FiniteCyclicGroup>::Element,
103                            >,
104                        >
105                })
106            });
107            (hmac, group_constructor)
108        }
109        _ => bail!("Unsupported OWE group id: {}", group_id),
110    };
111
112    let params = OweParameters { hmac, group_id, entity };
113    Ok(OweHandshakeImpl::new(group_constructor, params))
114}
115
116#[derive(Debug, Clone)]
117struct AsymmetricKeyPair {
118    private_key: Vec<u8>,
119    public_key: Vec<u8>,
120}
121
122struct OweHandshakeImpl<E> {
123    fcg: FcgConstructor<E>,
124    key_pair: Option<AsymmetricKeyPair>,
125    params: OweParameters,
126}
127
128struct OweParameters {
129    pub hmac: Box<dyn HmacUtils + Send>,
130    pub group_id: u16,
131    entity: OweHandshakeEntity,
132}
133
134impl<E> OweHandshakeImpl<E> {
135    pub fn new(fcg_constructor: FcgConstructor<E>, params: OweParameters) -> Self {
136        Self { fcg: fcg_constructor, key_pair: None, params }
137    }
138
139    fn generate_keys(&mut self, sink: &mut OweUpdateSink) -> Result<(), Error> {
140        // IEEE 802.11-2024, 12.14.2
141        // 1 < private_key < order
142        self.internal_generate_keys(sink, |order| Bignum::rand_range_ex(2, &order))
143    }
144
145    fn internal_generate_keys(
146        &mut self,
147        sink: &mut OweUpdateSink,
148        generate_private_key: impl FnOnce(Bignum) -> Result<Bignum, Error>,
149    ) -> Result<(), Error> {
150        let fcg = (self.fcg)()?;
151        let order = fcg.order()?;
152        let private_key = generate_private_key(order)?;
153        // IEEE 802.11-2024, 12.14.2
154        // M = scalar-op(m, G) -- where M and m are the public/private key and G is the generator
155        let generator: E = fcg.generator()?;
156        let public_key_element = fcg.scalar_op(&private_key, &generator)?;
157        // RFC 8110, Section 4.3 - If the public key is from a curve defined in [RFC6090],
158        // compact representation SHALL be used.
159        let public_key = fcg.element_to_octets_compact(&public_key_element)?;
160        self.key_pair = Some(AsymmetricKeyPair {
161            private_key: private_key.to_be_vec(0),
162            public_key: public_key.clone(),
163        });
164        sink.push(OweUpdate::TxPublicKey { group_id: self.params.group_id, key: public_key });
165        Ok(())
166    }
167
168    // Internal method to handle the received public key from the peer and compute the shared
169    // secret.
170    //
171    // The behavior is slightly different depending on whether the entity is a client or an AP.
172    fn internal_handle_public_key(
173        &mut self,
174        sink: &mut OweUpdateSink,
175        group: u16,
176        peer_public_key: Vec<u8>,
177    ) -> Result<(), Error> {
178        if group != EcGroupId::P256.id() {
179            bail!("Received unsupported OWE group {}", group);
180        }
181        if let OweHandshakeEntity::Ap = self.params.entity {
182            if self.key_pair.is_none() {
183                self.generate_keys(sink)?;
184            }
185        }
186        let (private_key, self_public_key) = match &self.key_pair {
187            Some(key_pair) => {
188                (Bignum::new_from_slice(&key_pair.private_key)?, key_pair.public_key.clone())
189            }
190            _ => bail!("Received public key from peer but private key has not been generated"),
191        };
192        let fcg = (self.fcg)()?;
193        // RFC 8110, Section 4.3 - If the public key is from a curve defined in [RFC6090],
194        // compact representation SHALL be used.
195        let peer_public_key_element = match fcg.element_from_octets_compact(&peer_public_key)? {
196            Some(element) => element,
197            None => bail!("Failed to convert peer public key octets to FCG element"),
198        };
199        // IEEE 802.11-2024, 12.14.2
200        // S = scalar-op(m, N) -- where m is the private key and N is the peer's public key
201        let element_s = fcg.scalar_op(&private_key, &peer_public_key_element)?;
202        // s = F(S) -- where F() is the element-to-scalar mapping function
203        let s = match fcg.map_to_secret_value(&element_s)? {
204            Some(s) => s,
205            None => bail!("Failed to map shared secret element to scalar"),
206        };
207        // prk = HKDF-Extract(C || A || group, s) -- where C and A are the client and AP public keys
208        let (client_public_key, ap_public_key) = match self.params.entity {
209            OweHandshakeEntity::Client => (&self_public_key, &peer_public_key),
210            OweHandshakeEntity::Ap => (&peer_public_key, &self_public_key),
211        };
212        let salt =
213            concat_public_keys_and_group(client_public_key, ap_public_key, self.params.group_id);
214        let prk = self.params.hmac.hkdf_extract(&salt, &s);
215        // pmk = HKDF-Expand(prk, "OWE Key Generation", key_length)
216        let pmk =
217            self.params.hmac.hkdf_expand(&prk, "OWE Key Generation", self.params.hmac.bits() / 8);
218        sink.push(OweUpdate::Success { key: pmk });
219        Ok(())
220    }
221}
222
223impl<E> ClientOweHandshake for OweHandshakeImpl<E> {
224    fn initiate_owe(&mut self, sink: &mut OweUpdateSink) -> Result<(), Error> {
225        self.generate_keys(sink)
226    }
227
228    fn handle_public_key(
229        &mut self,
230        sink: &mut OweUpdateSink,
231        group: u16,
232        ap_public_key: Vec<u8>,
233    ) -> Result<(), Error> {
234        self.internal_handle_public_key(sink, group, ap_public_key)
235    }
236}
237
238impl<E> ApOweHandshake for OweHandshakeImpl<E> {
239    fn handle_public_key(
240        &mut self,
241        sink: &mut OweUpdateSink,
242        group: u16,
243        client_public_key: Vec<u8>,
244    ) -> Result<(), Error> {
245        self.generate_keys(sink)?;
246        self.internal_handle_public_key(sink, group, client_public_key)
247    }
248}
249
250fn concat_public_keys_and_group(
251    client_public_key: &[u8],
252    ap_public_key: &[u8],
253    group_id: u16,
254) -> Vec<u8> {
255    let mut result: Vec<u8> = Vec::with_capacity(client_public_key.len() + ap_public_key.len() + 2);
256    result.extend_from_slice(&client_public_key);
257    result.extend_from_slice(&ap_public_key);
258    result.extend_from_slice(&group_id.to_le_bytes());
259    result
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use assert_matches::assert_matches;
266    use wlan_common::ie::rsn::akm::AKM_OWE;
267
268    struct TestHandshake {
269        client: Box<dyn ClientOweHandshake>,
270        ap: Box<dyn ApOweHandshake>,
271    }
272
273    struct TxPublicKey {
274        group_id: u16,
275        key: Vec<u8>,
276    }
277
278    impl TestHandshake {
279        fn new(group_id: u16) -> Self {
280            let client = new_client_owe_handshake(group_id, AKM_OWE).unwrap();
281            let ap = new_ap_owe_handshake(group_id, AKM_OWE).unwrap();
282            Self { client, ap }
283        }
284
285        fn client_initiate_owe(&mut self) -> TxPublicKey {
286            let mut sink = OweUpdateSink::new();
287            self.client.initiate_owe(&mut sink).unwrap();
288            assert_eq!(sink.len(), 1);
289            expect_tx_public_key(&mut sink)
290        }
291
292        fn ap_handle_public_key(
293            &mut self,
294            client_public_key: TxPublicKey,
295        ) -> (TxPublicKey, Vec<u8>) {
296            let mut sink = OweUpdateSink::new();
297            self.ap
298                .handle_public_key(&mut sink, client_public_key.group_id, client_public_key.key)
299                .unwrap();
300            assert_eq!(sink.len(), 2);
301            let ap_public_key = expect_tx_public_key(&mut sink);
302            let pmk = assert_matches!(sink.remove(0), OweUpdate::Success { key } => key);
303            (ap_public_key, pmk)
304        }
305
306        fn client_handle_public_key(&mut self, ap_public_key: TxPublicKey) -> Vec<u8> {
307            let mut sink = OweUpdateSink::new();
308            self.client
309                .handle_public_key(&mut sink, ap_public_key.group_id, ap_public_key.key)
310                .unwrap();
311            assert_eq!(sink.len(), 1);
312            assert_matches!(sink.remove(0), OweUpdate::Success { key } => key)
313        }
314    }
315
316    fn expect_tx_public_key(sink: &mut OweUpdateSink) -> TxPublicKey {
317        let (group_id, key) = assert_matches!(sink.remove(0), OweUpdate::TxPublicKey { group_id, key } => (group_id, key));
318        TxPublicKey { group_id, key }
319    }
320
321    #[test]
322    fn test_owe_handshake() {
323        for i in 0..10 {
324            let mut handshake = TestHandshake::new(EcGroupId::P256.id());
325            let client_public_key = handshake.client_initiate_owe();
326            let (ap_public_key, ap_pmk) = handshake.ap_handle_public_key(client_public_key);
327            let client_pmk = handshake.client_handle_public_key(ap_public_key);
328            if client_pmk != ap_pmk {
329                panic!(
330                    "Iteration {}: PMKs do not match:\n client: {:x?}\n ap: {:x?}",
331                    i, client_pmk, ap_pmk
332                );
333            }
334        }
335    }
336
337    #[test]
338    fn test_new_handshake_unsupported_group() {
339        for group_id in [20, 21] {
340            let result = new_owe_handshake(group_id, AKM_OWE, OweHandshakeEntity::Client);
341            assert!(result.is_err());
342        }
343    }
344
345    #[test]
346    fn test_handshake_handle_public_key_unsupported_group() {
347        let mut handshake = TestHandshake::new(EcGroupId::P256.id());
348        let client_public_key = handshake.client_initiate_owe();
349        for group in [20, 21] {
350            let result = handshake.ap.handle_public_key(
351                &mut Vec::new(),
352                group,
353                client_public_key.key.clone(),
354            );
355            assert!(result.is_err());
356        }
357    }
358
359    // There isn't an official OWE test vector that we know of.
360    // The below test vector is generated from running our own implementation and is intended
361    // to catch future regressions.
362    const SELF_PRIVATE_KEY: &str =
363        "5d6edb96f46993e0fb7621f93e5d3450813eb8c63ece17ecbf45376c574debb2";
364    const EXPECTED_SELF_PUBLIC_KEY: &str =
365        "4abf7c206407893a1c8b27a7b9b2c1dc6e3d7038fed9ad7ae2250d03dc18e080";
366    const PEER_PUBLIC_KEY: &str =
367        "40ac0ba500964f5872fb969e673d0c83869dd8976c688cec46fe4ba4b140e7ef";
368    const EXPECTED_PMK: &str = "afac9db29ab652d7f76054c9c51204a3bb59339e302523d9c114b269a46f5734";
369
370    #[test]
371    fn test_handshake_test_vector() {
372        let mut handshake =
373            new_owe_handshake(EcGroupId::P256.id(), AKM_OWE, OweHandshakeEntity::Client).unwrap();
374        let mut sink = OweUpdateSink::new();
375        handshake
376            .internal_generate_keys(&mut sink, |_| {
377                let private_key = hex::decode(SELF_PRIVATE_KEY).unwrap();
378                Ok(Bignum::new_from_slice(&private_key).unwrap())
379            })
380            .unwrap();
381        let tx_public_key = expect_tx_public_key(&mut sink);
382        let expected_self_public_key = hex::decode(EXPECTED_SELF_PUBLIC_KEY).unwrap();
383        assert_eq!(tx_public_key.key, expected_self_public_key);
384
385        let peer_public_key = hex::decode(PEER_PUBLIC_KEY).unwrap();
386        handshake
387            .internal_handle_public_key(&mut sink, EcGroupId::P256.id(), peer_public_key)
388            .unwrap();
389        let pmk = assert_matches!(sink.remove(0), OweUpdate::Success { key } => key);
390        let expected_pmk = hex::decode(EXPECTED_PMK).unwrap();
391        assert_eq!(pmk, expected_pmk);
392    }
393}