wlan_common/security/wpa/
credential.rs

1// Copyright 2021 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 std::fmt::Debug;
6use std::str;
7use thiserror::Error;
8
9pub const PSK_SIZE_BYTES: usize = 32;
10pub const PASSPHRASE_MIN_SIZE_BYTES: usize = 8;
11pub const PASSPHRASE_MAX_SIZE_BYTES: usize = 63;
12
13#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
14#[non_exhaustive]
15pub enum PskError {
16    #[error("invalid PSK size: {0} bytes")]
17    Size(usize),
18    #[error("invalid PSK encoding")]
19    Encoding,
20}
21
22#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
23#[non_exhaustive]
24pub enum PassphraseError {
25    #[error("invalid WPA passphrase size: {0} bytes")]
26    Size(usize),
27    #[error("invalid WPA passphrase encoding")]
28    Encoding,
29}
30
31/// WPA pre-shared key (PSK).
32#[derive(Clone, Debug, Eq, PartialEq)]
33#[repr(transparent)]
34pub struct Psk(pub [u8; PSK_SIZE_BYTES]);
35
36impl Psk {
37    /// Parses a PSK from a byte sequence.
38    ///
39    /// This function parses both unencoded and ASCII hexadecimal encoded PSKs.
40    ///
41    /// Note that `Psk` does not provide a mechanism to restore the original byte sequence parsed
42    /// by this function, so the exact encoding of ASCII hexadecimal encoded PSKs may be lost.
43    ///
44    /// # Errors
45    ///
46    /// Returns an error if the size or encoding of the byte sequence is incompatible.
47    pub fn parse(bytes: impl AsRef<[u8]>) -> Result<Self, PskError> {
48        let bytes = bytes.as_ref();
49        if bytes.len() == PSK_SIZE_BYTES * 2 {
50            let bytes = hex::decode(bytes).map_err(|_| PskError::Encoding)?;
51            Ok(Psk(bytes.try_into().unwrap()))
52        } else {
53            Psk::try_from(bytes)
54        }
55    }
56}
57
58impl AsRef<[u8]> for Psk {
59    fn as_ref(&self) -> &[u8] {
60        &self.0
61    }
62}
63
64impl From<[u8; PSK_SIZE_BYTES]> for Psk {
65    fn from(bytes: [u8; 32]) -> Self {
66        Psk(bytes)
67    }
68}
69
70impl From<Psk> for [u8; PSK_SIZE_BYTES] {
71    fn from(psk: Psk) -> Self {
72        psk.0
73    }
74}
75
76impl From<Psk> for Box<[u8]> {
77    fn from(psk: Psk) -> Self {
78        Vec::from(psk).into_boxed_slice()
79    }
80}
81
82impl From<Psk> for Vec<u8> {
83    fn from(psk: Psk) -> Self {
84        psk.0.into()
85    }
86}
87
88/// Converts unencoded bytes into a PSK.
89///
90/// This conversion is not a parse and does **not** accept ASCII hexadecimal encoded PSKs; the
91/// bytes are copied as is. Use `Psk::parse` for hexadecimal keys.
92impl<'a> TryFrom<&'a [u8]> for Psk {
93    type Error = PskError;
94
95    fn try_from(bytes: &'a [u8]) -> Result<Self, PskError> {
96        let n = bytes.len();
97        let psk = Psk(bytes.try_into().map_err(|_| PskError::Size(n))?);
98        Ok(psk)
99    }
100}
101
102/// WPA passphrase.
103///
104/// Passphrases are UTF-8 encoded and the underlying representation is `String`.
105#[derive(Clone, Debug, Eq, PartialEq)]
106#[repr(transparent)]
107pub struct Passphrase {
108    text: String,
109}
110
111impl Passphrase {
112    /// Consumes the `Passphrase` and performs a fallible write.
113    ///
114    /// The function `f` is used to mutate the `String` representation of the passphrase.
115    ///
116    /// # Errors
117    ///
118    /// Returns an error if the mutated `String` is not a valid WPA passphrase. Namely, the
119    /// `String` must consist of between `PASSPHRASE_MIN_SIZE_BYTES` and
120    /// `PASSPHRASE_MAX_SIZE_BYTES` bytes (**not** characters or graphemes).
121    ///
122    /// Note that if an error is returned, then the `Passphrase` is consumed. Use `clone` to
123    /// recover the original `Passphrase`.
124    pub fn try_write_with<F>(mut self, mut f: F) -> Result<Self, PassphraseError>
125    where
126        F: FnMut(&mut String),
127    {
128        f(&mut self.text);
129        Passphrase::check(&self.text)?;
130        Ok(self)
131    }
132
133    fn check(text: &str) -> Result<(), PassphraseError> {
134        let n = text.as_bytes().len();
135        if n < PASSPHRASE_MIN_SIZE_BYTES || n > PASSPHRASE_MAX_SIZE_BYTES {
136            return Err(PassphraseError::Size(n));
137        }
138        Ok(())
139    }
140}
141
142impl AsRef<[u8]> for Passphrase {
143    fn as_ref(&self) -> &[u8] {
144        &self.text.as_bytes()
145    }
146}
147
148impl AsRef<str> for Passphrase {
149    fn as_ref(&self) -> &str {
150        &self.text
151    }
152}
153
154impl From<Passphrase> for Vec<u8> {
155    fn from(passphrase: Passphrase) -> Self {
156        passphrase.text.into_bytes()
157    }
158}
159
160impl From<Passphrase> for String {
161    fn from(passphrase: Passphrase) -> Self {
162        passphrase.text
163    }
164}
165
166impl<'a> TryFrom<&'a [u8]> for Passphrase {
167    type Error = PassphraseError;
168
169    fn try_from(bytes: &'a [u8]) -> Result<Self, PassphraseError> {
170        let text = str::from_utf8(bytes).map_err(|_| PassphraseError::Encoding)?;
171        Passphrase::check(text.as_ref())?;
172        Ok(Passphrase { text: text.to_owned() })
173    }
174}
175
176impl<'a> TryFrom<&'a str> for Passphrase {
177    type Error = PassphraseError;
178
179    fn try_from(text: &'a str) -> Result<Self, PassphraseError> {
180        Passphrase::check(text)?;
181        Ok(Passphrase { text: text.to_owned() })
182    }
183}
184
185impl TryFrom<String> for Passphrase {
186    type Error = PassphraseError;
187
188    fn try_from(text: String) -> Result<Self, PassphraseError> {
189        Passphrase::check(text.as_ref())?;
190        Ok(Passphrase { text })
191    }
192}
193
194impl TryFrom<Vec<u8>> for Passphrase {
195    type Error = PassphraseError;
196
197    fn try_from(bytes: Vec<u8>) -> Result<Self, PassphraseError> {
198        let bytes: &[u8] = bytes.as_ref();
199        Passphrase::try_from(bytes)
200    }
201}
202
203#[cfg(test)]
204mod tests {
205
206    use crate::security::wpa::credential::{
207        Passphrase, PassphraseError, Psk, PskError, PSK_SIZE_BYTES,
208    };
209
210    #[test]
211    fn convert_passphrase_bad_encoding() {
212        assert!(matches!(
213            Passphrase::try_from([0xFFu8, 0xFF, 0xFF, 0xFF, 0xFF].as_ref()),
214            Err(PassphraseError::Encoding)
215        ));
216    }
217
218    #[test]
219    fn passphrase_bad_size() {
220        assert!(matches!(Passphrase::try_from("tiny"), Err(PassphraseError::Size(4))));
221        assert!(matches!(
222            Passphrase::try_from(
223                "huuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuge"
224            ),
225            Err(PassphraseError::Size(65))
226        ));
227
228        let passphrase = Passphrase::try_from("itsasecret").unwrap();
229        assert!(matches!(
230            passphrase.try_write_with(|text| {
231                *text = "tiny".to_string();
232            }),
233            Err(PassphraseError::Size(4))
234        ));
235    }
236
237    #[test]
238    fn parse_psk() {
239        // Parse binary PSK.
240        assert_eq!(
241            Psk::parse("therearethirtytwobytesineverypsk").unwrap(),
242            Psk(*b"therearethirtytwobytesineverypsk")
243        );
244        // Parse hexadecimal ASCII encoded PSK.
245        assert_eq!(
246            Psk::parse("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap(),
247            Psk::from([0xFF; PSK_SIZE_BYTES])
248        );
249    }
250
251    #[test]
252    fn parse_psk_bad_size() {
253        assert!(matches!(Psk::parse(b"lolwut"), Err(PskError::Size(6))));
254    }
255
256    #[test]
257    fn parse_psk_bad_encoding() {
258        assert!(matches!(
259            Psk::parse("ZZFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
260            Err(PskError::Encoding)
261        ));
262    }
263}