summaryrefslogtreecommitdiff
path: root/wpa-psk
diff options
context:
space:
mode:
Diffstat (limited to 'wpa-psk')
-rw-r--r--wpa-psk/CHANGELOG.md70
-rw-r--r--wpa-psk/Cargo.toml17
-rw-r--r--wpa-psk/LICENSE-APACHE-2.012
-rw-r--r--wpa-psk/LICENSE-MIT19
-rw-r--r--wpa-psk/README.md22
-rw-r--r--wpa-psk/src/lib.rs181
6 files changed, 321 insertions, 0 deletions
diff --git a/wpa-psk/CHANGELOG.md b/wpa-psk/CHANGELOG.md
new file mode 100644
index 0000000..1d80d14
--- /dev/null
+++ b/wpa-psk/CHANGELOG.md
@@ -0,0 +1,70 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep A Changelog][] and this project adheres to
+[Semantic Versioning][].
+
+[Keep A Changelog]: https://keepachangelog.com/en/1.0.0/
+[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
+
+## Unreleased
+
+### Added
+
+- Added this changelog.
+
+### Changed
+
+- Changed `TryFrom` implementations to return typed validation errors.
+- Changed Git repository URL.
+
+### Removed
+
+- Extracted the command-line utility `wpa-psk` into a dedicated crate `wpa-psk-cli`.
+
+## [0.1.5] - 2022-05-23
+
+### Added
+
+- Described command-line interface in [README.md](README.md).
+
+### Fixed
+
+- Fixed crate version in [Cargo.toml](Cargo.toml).
+
+## [0.1.4] - 2022-05-22
+
+### Changed
+
+- Changed Git repository URL.
+- Upgraded dependencies to their respective latest versions.
+
+## [0.1.3] - 2022-05-21
+
+### Changed
+
+- Updated dependencies.
+- Changed Git repository URL.
+
+### Fixed
+
+- Replaced deprecated function call `clap::IntoApp::into_app`.
+
+## [0.1.2] - 2022-03-30
+
+### Changed
+
+- Relaxed pbkdf2 version to include 0.11.
+
+## [0.1.1] - 2022-02-01
+
+### Changed
+
+- Updated dependencies.
+
+## [0.1.0] - 2022-01-03
+
+### Added
+
+- Add initial implementation.
diff --git a/wpa-psk/Cargo.toml b/wpa-psk/Cargo.toml
new file mode 100644
index 0000000..ca173f4
--- /dev/null
+++ b/wpa-psk/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "wpa-psk"
+version = "0.2.0"
+authors = ["Stefan Kreutz <mail@skreutz.com>"]
+edition = "2021"
+description = "Compute the WPA-PSK of a Wi-FI SSID and passphrase"
+readme = "README.md"
+repository = "https://www.skreutz.com/scm/git/wpa-psk.git"
+license = "MIT OR Apache-2.0"
+keywords = ["wifi", "wpa", "password", "hash"]
+categories = ["algorithms"]
+publish = true
+
+[dependencies]
+pbkdf2 = { version = "0.11.0", default-features = false }
+hmac = { version = "0.12.1", default-features = false }
+sha-1 = { version = "0.10.0", default-features = false }
diff --git a/wpa-psk/LICENSE-APACHE-2.0 b/wpa-psk/LICENSE-APACHE-2.0
new file mode 100644
index 0000000..7eefa9d
--- /dev/null
+++ b/wpa-psk/LICENSE-APACHE-2.0
@@ -0,0 +1,12 @@
+Copyright 2021 Stefan Kreutz <mail@skreutz.com>
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed
+under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
diff --git a/wpa-psk/LICENSE-MIT b/wpa-psk/LICENSE-MIT
new file mode 100644
index 0000000..4012e61
--- /dev/null
+++ b/wpa-psk/LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright 2021 Stefan Kreutz <mail@skreutz.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/wpa-psk/README.md b/wpa-psk/README.md
new file mode 100644
index 0000000..c2e7136
--- /dev/null
+++ b/wpa-psk/README.md
@@ -0,0 +1,22 @@
+# wpa-psk
+
+This is a Rust library to compute the WPA pre-shared key of a Wi-Fi SSID and
+passphrase. See also the corresponding command-line interface `wpa-psk-cli`.
+
+See the [crate documentation][docs] more information.
+
+[docs]: https://docs.rs/wpa-psk/latest/wpa_psk/
+
+## License
+
+This work is distributed under the terms of both, the [MIT License][MIT] and
+the [Apache License, Version 2.0][Apache-2.0].
+
+[MIT]: LICENSE-MIT
+[Apache-2.0]: LICENSE-APACHE-2.0
+
+## Contribution
+
+Contributions are welcome! Please [contact][] me via email.
+
+[contact]: https://www.skreutz.com/contact/
diff --git a/wpa-psk/src/lib.rs b/wpa-psk/src/lib.rs
new file mode 100644
index 0000000..e99418c
--- /dev/null
+++ b/wpa-psk/src/lib.rs
@@ -0,0 +1,181 @@
+//! Compute the WPA-PSK of a Wi-Fi SSID and passphrase.
+//!
+//! # Example
+//!
+//! Compute and print the WPA-PSK of a valid SSID and passphrase:
+//!
+//! ```
+//! # use wpa_psk::{Ssid, Passphrase, wpa_psk, bytes_to_hex};
+//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
+//! let ssid = Ssid::try_from("home")?;
+//! let passphrase = Passphrase::try_from("0123-4567-89")?;
+//! let psk = wpa_psk(&ssid, &passphrase);
+//! assert_eq!(bytes_to_hex(&psk), "150c047b6fad724512a17fa431687048ee503d14c1ea87681d4f241beb04f5ee");
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! Compute the WPA-PSK of possibly invalid raw bytes:
+//!
+//! ```
+//! # use wpa_psk::{wpa_psk_unchecked, bytes_to_hex};
+//! let ssid = "bar".as_bytes();
+//! let passphrase = "2short".as_bytes();
+//! let psk = wpa_psk_unchecked(&ssid, &passphrase);
+//! assert_eq!(bytes_to_hex(&psk), "cb5de4e4d23b2ab0bf5b9ba0fe8132c1e2af3bb52298ec801af8ad520cea3437");
+//! ```
+
+use std::{error::Error, fmt::Display};
+
+use hmac::Hmac;
+use pbkdf2::pbkdf2;
+use sha1::Sha1;
+
+/// An SSID consisting of 1 up to 32 arbitrary bytes.
+#[derive(Debug)]
+pub struct Ssid<'a>(&'a [u8]);
+
+impl<'a> TryFrom<&'a [u8]> for Ssid<'a> {
+ type Error = ValidateSsidError;
+
+ fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
+ if value.is_empty() {
+ Err(ValidateSsidError::TooShort)
+ } else if value.len() > 32 {
+ Err(ValidateSsidError::TooLong)
+ } else {
+ Ok(Ssid(value))
+ }
+ }
+}
+
+impl<'a> TryFrom<&'a str> for Ssid<'a> {
+ type Error = ValidateSsidError;
+
+ fn try_from(value: &'a str) -> Result<Self, Self::Error> {
+ Self::try_from(value.as_bytes())
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ValidateSsidError {
+ TooShort,
+ TooLong,
+}
+
+impl Error for ValidateSsidError {}
+
+impl Display for ValidateSsidError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let msg = match self {
+ ValidateSsidError::TooShort => "SSID must have at least one byte",
+ ValidateSsidError::TooLong => "SSID must have at most 32 bytes",
+ };
+ write!(f, "{msg}")
+ }
+}
+
+/// A passphrase consisting of 8 up to 63 printable ASCII characters.
+#[derive(Debug)]
+pub struct Passphrase<'a>(&'a [u8]);
+
+impl<'a> TryFrom<&'a [u8]> for Passphrase<'a> {
+ type Error = ValidatePassphraseError;
+
+ fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
+ if value.len() < 8 {
+ Err(ValidatePassphraseError::TooShort)
+ } else if value.len() > 63 {
+ Err(ValidatePassphraseError::TooLong)
+ } else if value.iter().any(|i| !matches!(i, 32u8..=126)) {
+ Err(ValidatePassphraseError::InvalidByte)
+ } else {
+ Ok(Passphrase(value))
+ }
+ }
+}
+
+impl<'a> TryFrom<&'a str> for Passphrase<'a> {
+ type Error = ValidatePassphraseError;
+
+ fn try_from(value: &'a str) -> Result<Self, Self::Error> {
+ Self::try_from(value.as_bytes())
+ }
+}
+
+impl Display for Passphrase<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", std::str::from_utf8(self.0).unwrap())
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ValidatePassphraseError {
+ TooShort,
+ TooLong,
+ InvalidByte,
+}
+
+impl Error for ValidatePassphraseError {}
+
+impl Display for ValidatePassphraseError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let msg = match self {
+ ValidatePassphraseError::TooShort => "passphrase must have at least 8 bytes",
+ ValidatePassphraseError::TooLong => "passphrase must have at most 63 bytes",
+ ValidatePassphraseError::InvalidByte => {
+ "passphrase must consist of printable ASCII characters"
+ }
+ };
+ write!(f, "{msg}")
+ }
+}
+
+/// Returns the WPA-PSK of the given SSID and passphrase.
+pub fn wpa_psk(ssid: &Ssid, passphrase: &Passphrase) -> [u8; 32] {
+ wpa_psk_unchecked(ssid.0, passphrase.0)
+}
+
+/// Unchecked WPA-PSK.
+/// See [`wpa_psk`].
+pub fn wpa_psk_unchecked(ssid: &[u8], passphrase: &[u8]) -> [u8; 32] {
+ let mut buf = [0u8; 32];
+ pbkdf2::<Hmac<Sha1>>(passphrase, ssid, 4096, &mut buf);
+ buf
+}
+
+/// Returns the hexdecimal representation of the given bytes.
+pub fn bytes_to_hex(bytes: &[u8]) -> String {
+ bytes.iter().map(|b| format!("{:02x}", b)).collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn special_characters() {
+ let ssid = Ssid::try_from("123abcABC.,-").unwrap();
+ let passphrase = Passphrase::try_from("456defDEF *<:D").unwrap();
+ assert_eq!(
+ bytes_to_hex(&wpa_psk(&ssid, &passphrase)),
+ "8a366e5bc51cd5d8fbbeffacc5f1af23fac30e3ac93cdcc368fafbbf63a1085c"
+ );
+ }
+
+ #[test]
+ fn passphrase_too_short() {
+ assert_eq!(
+ Passphrase::try_from("foobar").unwrap_err(),
+ ValidatePassphraseError::TooShort
+ );
+ }
+
+ #[test]
+ fn display_passphrase() {
+ assert_eq!(
+ format!("{}", Passphrase::try_from("foobarbuzz").unwrap()),
+ "foobarbuzz"
+ );
+ }
+}
Generated by cgit. See skreutz.com for my tech blog and contact information.