wireguard_keys/
lib.rs

1//! This crate allows for working with WireGuard keys. WireGuard uses asymmetric x25519 keys,
2//! which are represented by the [Privkey] and [Pubkey] types respectively. Private keys can
3//! be generated randomly, and their corresponding public key can be derived. Additionally,
4//! WireGuard allows using a preshared key as additional security layer, which is just a random
5//! 256-bit value. This is represented by the [Secret] type.
6//!
7//! For security reasons, this crate uses the [Zeroize] trait to mark all types containing
8//! cryyptographically relevant information to be cleared on drop. The [x25519_dalek_fiat]
9//! crate is used for x25519 operations.
10//!
11//! This crate allows for encoding keys in various ways. The crate supports `base64`, which is
12//! typically used by WireGuard, but `hex` and `base32` can be enabled as well. Enabling encodings
13//! also enables parsing from that encoding.
14//!
15//! The [serde] feature, which is enabled by default, adds [serialize][serde::Serialize] and
16//! [deserialize][serde::Deserialize] support for WireGuard types. How these types are serialized
17//! depends on the format: when serializing into human-readable formats, such as
18//! JSON, the keys are serialized as base64-encoded strings. However, when
19//! serializing to binary formats such as Bincode, keys are serialized as raw bytes.
20//!
21//! The optional `schema` feature adds information to the types allowing to generate JSON schema
22//! from them automatically using schemars.
23//!
24//! Enabling the `rocket` feature adds the ability to parse any WireGuard types from a HTTP
25//! request using the [FromParam][rocket::request::FromParam] trait.
26
27#[macro_use]
28mod macros;
29
30use paste::paste;
31use rand_core::{OsRng, RngCore};
32#[cfg(feature = "rocket")]
33use rocket::request::FromParam;
34#[cfg(feature = "schema")]
35use schemars::JsonSchema;
36#[cfg(feature = "serde")]
37use serde::{
38    de::{Error, Visitor},
39    Deserialize, Deserializer, Serialize, Serializer,
40};
41use std::fmt;
42use std::str::FromStr;
43use thiserror::Error;
44use x25519_dalek_fiat::{PublicKey, StaticSecret};
45use zeroize::Zeroize;
46
47/// Possible errors that can be generated when parsing WireGuard keys.
48#[derive(Error, Debug)]
49pub enum ParseError {
50    /// Error decoding base64
51    #[cfg(feature = "base64")]
52    #[error("base64 decoding error")]
53    Base64(#[from] base64::DecodeError),
54    /// Error decoding hex
55    #[cfg(feature = "hex")]
56    #[error("hex decoding errro")]
57    Hex(#[from] hex::FromHexError),
58    /// Error decoding base32
59    #[cfg(feature = "base32")]
60    #[error("base32 decoding error")]
61    Base32Error,
62    /// Illegal length
63    #[error("length mismatch")]
64    Length,
65}
66
67/// Length (in bytes) of a WireGuard public key (ed25519).
68pub const PUBKEY_LEN: usize = 32;
69
70/// Length (in bytes) of a WireGuard private key (ed25519).
71pub const PRIVKEY_LEN: usize = 32;
72
73/// Length (in bytes) of a WireGuard preshared key.
74pub const SECRET_LEN: usize = 32;
75
76/// WireGuard public key.
77#[cfg_attr(feature = "schema", derive(JsonSchema))]
78#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)]
79pub struct Pubkey([u8; PUBKEY_LEN]);
80
81impl_new!(Pubkey, PUBKEY_LEN);
82impl_display!(Pubkey);
83impl_deref!(Pubkey, PUBKEY_LEN);
84#[cfg(feature = "hex")]
85impl_hex!(Pubkey);
86#[cfg(feature = "base64")]
87impl_base64!(Pubkey);
88#[cfg(feature = "base32")]
89impl_base32!(Pubkey);
90impl_parse!(Pubkey);
91#[cfg(feature = "serde")]
92impl_serde!(Pubkey, "WireGuard public key");
93#[cfg(feature = "rocket")]
94impl_rocket!(Pubkey);
95
96impl Pubkey {
97    #[cfg(test)]
98    fn generate() -> Pubkey {
99        Privkey::generate().pubkey()
100    }
101}
102
103#[test]
104fn test_pubkey_from_slice() {
105    let slice = [0; 3];
106    match Pubkey::try_from(&slice[..]) {
107        Err(ParseError::Length) => {}
108        _ => assert!(false),
109    }
110    let slice = [0; PUBKEY_LEN];
111    match Pubkey::try_from(&slice[..]) {
112        Ok(_) => {}
113        _ => assert!(false),
114    }
115}
116
117impl TryFrom<&[u8]> for Pubkey {
118    type Error = ParseError;
119    fn try_from(key: &[u8]) -> Result<Self, Self::Error> {
120        if key.len() != PUBKEY_LEN {
121            Err(ParseError::Length)
122        } else {
123            let mut data = [0; PUBKEY_LEN];
124            data[0..PUBKEY_LEN].copy_from_slice(&key[0..PUBKEY_LEN]);
125            Ok(Pubkey(data))
126        }
127    }
128}
129
130/// WireGuard private key.
131#[cfg_attr(feature = "schema", derive(JsonSchema))]
132#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)]
133pub struct Privkey([u8; PRIVKEY_LEN]);
134
135impl_display!(Privkey);
136impl_new!(Privkey, PRIVKEY_LEN);
137impl_deref!(Privkey, PRIVKEY_LEN);
138#[cfg(feature = "hex")]
139impl_hex!(Privkey);
140#[cfg(feature = "base64")]
141impl_base64!(Privkey);
142#[cfg(feature = "base32")]
143impl_base32!(Privkey);
144impl_parse!(Privkey);
145#[cfg(feature = "serde")]
146impl_serde!(Privkey, "WireGuard private key");
147#[cfg(feature = "rocket")]
148impl_rocket!(Privkey);
149
150impl Privkey {
151    /// Generate new private key using the kernel randomness generator.
152    pub fn generate() -> Self {
153        let private_key = StaticSecret::new(OsRng);
154        Privkey(private_key.to_bytes())
155    }
156
157    /// Attempt to check if this private key is valid.
158    pub fn valid(&self) -> bool {
159        if self.0 == [0; PRIVKEY_LEN] {
160            return false;
161        }
162
163        let private_key = StaticSecret::from(self.0.clone());
164        self.0 == private_key.to_bytes()
165    }
166
167    /// Generate the corresponding public key for this private key.
168    pub fn pubkey(&self) -> Pubkey {
169        let private_key = StaticSecret::from(self.0.clone());
170        let public_key: PublicKey = (&private_key).into();
171        Pubkey(public_key.to_bytes())
172    }
173}
174
175#[test]
176fn test_privkey_from_slice() {
177    let slice = [0; 3];
178    match Privkey::try_from(&slice[..]) {
179        Err(ParseError::Length) => {}
180        _ => assert!(false),
181    }
182    let slice = [0; PRIVKEY_LEN];
183    match Privkey::try_from(&slice[..]) {
184        Ok(_) => {}
185        _ => assert!(false),
186    }
187}
188
189impl TryFrom<&[u8]> for Privkey {
190    type Error = ParseError;
191    fn try_from(key: &[u8]) -> Result<Self, Self::Error> {
192        if key.len() != PUBKEY_LEN {
193            Err(ParseError::Length)
194        } else {
195            let mut data = [0; PUBKEY_LEN];
196            data[0..PUBKEY_LEN].copy_from_slice(&key[0..PUBKEY_LEN]);
197            Ok(Privkey(data))
198        }
199    }
200}
201
202#[test]
203fn test_wireguard_privkey() {
204    let key = Privkey::new([0; PRIVKEY_LEN]);
205    assert_eq!(key.valid(), false);
206    let key = Privkey::new([255; PRIVKEY_LEN]);
207    assert_eq!(key.valid(), false);
208    let key = Privkey::generate();
209    assert_eq!(key.valid(), true);
210    // always generate same pubkey
211    assert_eq!(key.pubkey(), key.pubkey());
212}
213
214/// WireGuard preshared key.
215#[cfg_attr(feature = "schema", derive(JsonSchema))]
216#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)]
217pub struct Secret([u8; SECRET_LEN]);
218
219impl_new!(Secret, SECRET_LEN);
220impl_display!(Secret);
221impl_deref!(Secret, SECRET_LEN);
222#[cfg(feature = "hex")]
223impl_hex!(Secret);
224#[cfg(feature = "base64")]
225impl_base64!(Secret);
226#[cfg(feature = "base32")]
227impl_base32!(Secret);
228impl_parse!(Secret);
229#[cfg(feature = "serde")]
230impl_serde!(Secret, "WireGuard preshared key");
231#[cfg(feature = "rocket")]
232impl_rocket!(Secret);
233
234impl Secret {
235    /// Generate new random preshared key using the system randomness generator.
236    pub fn generate() -> Self {
237        let mut data = [0; SECRET_LEN];
238        OsRng.fill_bytes(&mut data);
239        Secret(data)
240    }
241}
242
243#[test]
244fn test_secret_from_slice() {
245    let slice = [0; 3];
246    match Secret::try_from(&slice[..]) {
247        Err(ParseError::Length) => {}
248        _ => assert!(false),
249    }
250    let slice = [0; PRIVKEY_LEN];
251    match Secret::try_from(&slice[..]) {
252        Ok(_) => {}
253        _ => assert!(false),
254    }
255}
256
257impl TryFrom<&[u8]> for Secret {
258    type Error = ParseError;
259    fn try_from(key: &[u8]) -> Result<Self, Self::Error> {
260        if key.len() != PUBKEY_LEN {
261            Err(ParseError::Length)
262        } else {
263            let mut data = [0; PUBKEY_LEN];
264            data[0..PUBKEY_LEN].copy_from_slice(&key[0..PUBKEY_LEN]);
265            Ok(Secret(data))
266        }
267    }
268}