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.
45use crate::Error;
6use anyhow::ensure;
7use core::num::NonZeroU32;
8use ieee80211::Ssid;
9use std::str;
10use wlan_common::security::wpa::credential::Psk as CommonPsk;
11use wlan_common::security::wpa::{self};
1213// PBKDF2-HMAC-SHA1 is considered insecure but required for PSK computation.
14#[allow(deprecated)]
15use mundane::insecure::insecure_pbkdf2_hmac_sha1;
1617/// Conversion of WPA credentials to a PSK.
18pub trait ToPsk {
19fn to_psk(&self, ssid: &Ssid) -> CommonPsk;
20}
2122/// Conversion of WPA1 credentials to a PSK.
23///
24/// RSN specifies that PSKs are used as-is and passphrases are transformed into a PSK. This
25/// implementation performs this transformation when necessary for WPA1 credentials.
26impl ToPsk for wpa::Wpa1Credentials {
27fn to_psk(&self, ssid: &Ssid) -> CommonPsk {
28match self {
29 wpa::Wpa1Credentials::Psk(ref psk) => psk.clone(),
30 wpa::Wpa1Credentials::Passphrase(ref passphrase) => {
31// TODO(https://fxbug.dev/42177995): Unify the representation of PSKs. There can only be
32 // one...!
33CommonPsk(
34 compute(passphrase.as_ref(), ssid)
35 .expect("invalid WPA passphrase data")
36 .as_ref()
37 .try_into()
38 .expect("invalid derived PSK data"),
39 )
40 }
41 }
42 }
43}
4445/// Conversion of WPA2 Personal credentials to a PSK.
46///
47/// RSN specifies that PSKs are used as-is and passphrases are transformed into a PSK. This
48/// implementation performs this transformation when necessary for WPA2 Personal credentials.
49impl ToPsk for wpa::Wpa2PersonalCredentials {
50fn to_psk(&self, ssid: &Ssid) -> CommonPsk {
51match self {
52 wpa::Wpa2PersonalCredentials::Psk(ref psk) => psk.clone(),
53 wpa::Wpa2PersonalCredentials::Passphrase(ref passphrase) => {
54// TODO(https://fxbug.dev/42177995): Unify the representation of PSKs. There can only be
55 // one...!
56CommonPsk(
57 compute(passphrase.as_ref(), ssid)
58 .expect("invalid WPA passphrase data")
59 .as_ref()
60 .try_into()
61 .expect("invalid derived PSK data"),
62 )
63 }
64 }
65 }
66}
6768/// Keys derived from a passphrase provide comparably low levels of security.
69/// Passphrases should have a minimum length of 20 characters since shorter passphrases
70/// are unlikely to prevent attacks.
71pub type Psk = Box<[u8]>;
7273pub fn compute(passphrase: &[u8], ssid: &Ssid) -> Result<Psk, anyhow::Error> {
74// IEEE Std 802.11-2016, J.4.1 provides a reference implementation that describes the
75 // passphrase as:
76 //
77 // ... sequence of between 8 and 63 ASCII-encoded characters ... Each character in the
78 // pass-phrase has an encoding in the range 32 to 126 (decimal).
79 //
80 // However, the standard does not seem to specify this encoding or otherwise state that it is a
81 // requirement. In practice, devices accept UTF-8 encoded passphrases, which is far less
82 // restrictive than a subset of ASCII. This code attempts to parse the passphrase as UTF-8 and
83 // emits an error if this is not possible. Note that this also accepts the ASCII encoding
84 // suggested by J.4.1.
85let _utf8 = str::from_utf8(passphrase)
86 .map_err(|error| Error::InvalidPassphraseEncoding(error.valid_up_to()))?;
8788// IEEE Std 802.11-2016, J.4.1 suggests a passphrase length of [8, 64). However, J.4.1 also
89 // suggests ASCII encoding and this code expects UTF-8 encoded passphrases. This implicitly
90 // supports the ASCII encodings described in J.4.1. However, the length of the byte sequence no
91 // longer represents the number of encoded characters, so non-ASCII passphrases may appear to
92 // have arbitrary character limits. Note that the character count can be obtained via
93 // `_utf8.chars().count()`.
94ensure!(
95 passphrase.len() >= 8 && passphrase.len() <= 63,
96 Error::InvalidPassphraseLen(passphrase.len())
97 );
9899// Compute PSK: IEEE Std 802.11-2016, J.4.1
100let size = 256 / 8;
101let mut psk = vec![0_u8; size];
102const ITERS: NonZeroU32 = NonZeroU32::new(4096).unwrap();
103// PBKDF2-HMAC-SHA1 is considered insecure but required for PSK computation.
104#[allow(deprecated)]
105insecure_pbkdf2_hmac_sha1(&passphrase[..], &ssid[..], ITERS, &mut psk[..]);
106Ok(psk.into_boxed_slice())
107}
108109#[cfg(test)]
110mod tests {
111use super::*;
112use hex::FromHex;
113use wlan_common::security::wpa::credential::Passphrase;
114115fn assert_psk(password: &str, ssid: &str, expected: &str) {
116let psk = compute(password.as_bytes(), &Ssid::try_from(ssid).unwrap())
117 .expect("computing PSK failed");
118let expected = Vec::from_hex(expected).unwrap();
119assert_eq!(&psk[..], &expected[..]);
120 }
121122// IEEE Std 802.11-2016, J.4.2, Test case 1
123#[test]
124fn test_psk_test_case_1() {
125 assert_psk(
126"password",
127"IEEE",
128"f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e",
129 );
130 }
131132// IEEE Std 802.11-2016, J.4.2, Test case 2
133#[test]
134fn test_psk_test_case_2() {
135 assert_psk(
136"ThisIsAPassword",
137"ThisIsASSID",
138"0dc0d6eb90555ed6419756b9a15ec3e3209b63df707dd508d14581f8982721af",
139 );
140 }
141142// IEEE Std 802.11-2016, J.4.2, Test case 3
143#[test]
144fn test_psk_test_case_3() {
145 assert_psk(
146"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
147"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ",
148"becb93866bb8c3832cb777c2f559807c8c59afcb6eae734885001300a981cc62",
149 );
150 }
151152#[test]
153fn test_psk_too_short_password() {
154let result = compute("short".as_bytes(), &Ssid::try_from("Some SSID").unwrap());
155assert!(result.is_err());
156 }
157158#[test]
159fn test_psk_too_long_password() {
160let result = compute(
161"1234567890123456789012345678901234567890123456789012345678901234".as_bytes(),
162&Ssid::try_from("Some SSID").unwrap(),
163 );
164assert!(result.is_err());
165 }
166167#[test]
168fn test_psk_ascii_bounds_password() {
169let result =
170 compute("\x20ASCII Bound Test \x7E".as_bytes(), &Ssid::try_from("Some SSID").unwrap());
171assert!(result.is_ok());
172 }
173174#[test]
175fn test_psk_non_ascii_password() {
176assert!(compute("パスワード".as_bytes(), &Ssid::try_from("Some SSID").unwrap()).is_ok());
177 }
178179#[test]
180fn test_psk_invalid_encoding_password() {
181assert!(compute(&[0xFFu8; 32], &Ssid::try_from("Some SSID").unwrap()).is_err());
182 }
183184#[test]
185fn wpa1_credentials_to_psk() {
186let ssid = Ssid::try_from("IEEE").unwrap();
187188let credentials = wpa::Wpa1Credentials::Psk(CommonPsk::from([0u8; 32]));
189assert_eq!(CommonPsk::from([0u8; 32]), credentials.to_psk(&ssid));
190191let credentials =
192 wpa::Wpa1Credentials::Passphrase(Passphrase::try_from("password").unwrap());
193assert_eq!(
194 CommonPsk::parse("f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e")
195 .unwrap(),
196 credentials.to_psk(&ssid),
197 );
198 }
199200#[test]
201fn wpa2_personal_credentials_to_psk() {
202let ssid = Ssid::try_from("IEEE").unwrap();
203204let credentials = wpa::Wpa2PersonalCredentials::Psk(CommonPsk::from([0u8; 32]));
205assert_eq!(CommonPsk::from([0u8; 32]), credentials.to_psk(&ssid));
206207let credentials =
208 wpa::Wpa2PersonalCredentials::Passphrase(Passphrase::try_from("password").unwrap());
209assert_eq!(
210 CommonPsk::parse("f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e")
211 .unwrap(),
212 credentials.to_psk(&ssid),
213 );
214 }
215}