From 95c37a33d4df8aef1a62bd64ac57432ed9cf8d1b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 16 Aug 2026 10:52:52 +0300 Subject: [PATCH 1/5] refactor: refactor keys and error into separate modules --- src/error.rs | 33 +++++ src/keys/mod.rs | 7 + src/keys/preshared.rs | 146 ++++++++++++++++++++ src/keys/private.rs | 150 ++++++++++++++++++++ src/keys/public.rs | 144 +++++++++++++++++++ src/lib.rs | 6 +- src/prelude.rs | 3 +- src/utils/keys.rs | 311 ------------------------------------------ src/utils/mod.rs | 39 ------ src/utils/serde.rs | 97 ------------- 10 files changed, 486 insertions(+), 450 deletions(-) create mode 100644 src/error.rs create mode 100644 src/keys/mod.rs create mode 100644 src/keys/preshared.rs create mode 100644 src/keys/private.rs create mode 100644 src/keys/public.rs delete mode 100644 src/utils/serde.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..baef0ac --- /dev/null +++ b/src/error.rs @@ -0,0 +1,33 @@ +use thiserror::Error; + +/// `wireguard-conf` error. +#[derive(Error, Debug, PartialEq)] +pub enum WireguardError { + /// Error, when private key is invalid. + #[error("invalid private key")] + InvalidPrivateKey, + + /// Error, when public key is invalid. + #[error("invalid public key")] + InvalidPublicKey, + + /// Error, when preshared key is invalid. + #[error("invalid preshared key")] + InvalidPresharedKey, + + /// Error, when private key isn't provided. + #[error("no private key provided")] + NoPrivateKeyProvided, + + /// Error, when no IP assigned. + #[error("no assigned ip")] + NoAssignedIP, + + /// Error, when some amnezia setting is invalid + #[cfg(feature = "amneziawg")] + #[error("invalid amnezia setting: {0}")] + InvalidAmneziaSetting(String), +} + +/// Result alias. +pub type WireguardResult = Result; diff --git a/src/keys/mod.rs b/src/keys/mod.rs new file mode 100644 index 0000000..e2eae69 --- /dev/null +++ b/src/keys/mod.rs @@ -0,0 +1,7 @@ +mod preshared; +mod private; +mod public; + +pub use preshared::PresharedKey; +pub use private::PrivateKey; +pub use public::PublicKey; diff --git a/src/keys/preshared.rs b/src/keys/preshared.rs new file mode 100644 index 0000000..bbbf960 --- /dev/null +++ b/src/keys/preshared.rs @@ -0,0 +1,146 @@ +use core::fmt; + +use base64::prelude::*; +use rand::Rng; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use crate::WireguardError; + +/// Preshared key. +/// +/// Wrapper around `[u8; 32]`. +/// +/// # Implements +/// +/// - Implements [`Zeroize`] and [`ZeroizeOnDrop`] for clearing secrets from memory. +/// - Implements [`TryFrom<&str>`] or [`TryFrom`] for importing key from Base64 format. +/// - Implements [`fmt::Display`] for exporting key in Wireguard's format. +/// - Implements [`fmt::Debug`]. +/// +/// # Examples +/// +/// ``` +/// # use wireguard_conf::prelude::*; +/// # fn main() -> WireguardResult<()> { +/// // generate new preshared key: +/// let preshared_key = PresharedKey::random(); +/// +/// // import key: +/// let imported_key = PresharedKey::try_from("MA4zR0tvQpZ4CQ7gs/KcAVMNMGIFBtDcfpjBr+0GwHY=")?; +/// +/// // export key via `fmt::Display` trait: +/// let exported_key = imported_key.to_string(); +/// +/// assert_eq!(exported_key, "MA4zR0tvQpZ4CQ7gs/KcAVMNMGIFBtDcfpjBr+0GwHY=".to_string()); +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone, PartialEq, Zeroize, ZeroizeOnDrop)] +pub struct PresharedKey([u8; 32]); + +impl PresharedKey { + /// Generate random [`PresharedKey`]. + #[must_use] + pub fn random() -> Self { + let mut key = [0u8; 32]; + rand::rng().fill_bytes(&mut key); + Self(key) + } +} + +impl PresharedKey { + /// Convert [`PresharedKey`] into byte array. + #[inline] + #[must_use] + pub fn to_bytes(&self) -> [u8; 32] { + self.0 + } + + /// Get [`PresharedKey`] as reference to bytes. + #[inline] + #[must_use] + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +impl fmt::Debug for PresharedKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PresharedKey") + .field(&self.to_string()) + .finish() + } +} + +/// Export [`PresharedKey`] as base64 format for Wireguard. +impl fmt::Display for PresharedKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.as_bytes())) + } +} + +impl From<[u8; 32]> for PresharedKey { + fn from(value: [u8; 32]) -> Self { + Self(value) + } +} + +impl TryFrom<&str> for PresharedKey { + type Error = WireguardError; + + fn try_from(value: &str) -> Result { + let bytes: [u8; 32] = BASE64_STANDARD + .decode(value) + .map_err(|_| WireguardError::InvalidPresharedKey)? + .try_into() + .map_err(|_| WireguardError::InvalidPresharedKey)?; + + Ok(Self(bytes)) + } +} + +impl TryFrom for PresharedKey { + type Error = WireguardError; + + fn try_from(value: String) -> Result { + Self::try_from(value.as_str()) + } +} + +#[cfg(feature = "serde")] +mod serde_impl { + use super::PresharedKey; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; + + impl Serialize for PresharedKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(&self.to_string()) + } else { + serializer.serialize_bytes(self.as_bytes()) + } + } + } + + impl<'de> Deserialize<'de> for PresharedKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + let data = String::deserialize(deserializer)?; + + PresharedKey::try_from(data.as_str()).map_err(|_| { + de::Error::invalid_value(de::Unexpected::Str(&data), &"a preshared key") + }) + } else { + let bytes = <[u8; 32]>::deserialize(deserializer)?; + + Ok(PresharedKey::from(bytes)) + } + } + } +} diff --git a/src/keys/private.rs b/src/keys/private.rs new file mode 100644 index 0000000..79ab41e --- /dev/null +++ b/src/keys/private.rs @@ -0,0 +1,150 @@ +use core::fmt; + +use base64::prelude::*; +use x25519_dalek::StaticSecret; +use zeroize::ZeroizeOnDrop; + +use crate::WireguardError; + +/// Private key +/// +/// Wrapper around [`x25519_dalek::StaticSecret`] with some traits. +/// +/// # Implements +/// +/// - Implements [`Zeroize`] and [`ZeroizeOnDrop`] for clearing secrets from memory. +/// - Implements [`TryFrom<&str>`] or [`TryFrom`] for importing key from Base64 format. +/// - Implements [`fmt::Display`] for exporting key in Wireguard's format. +/// - Implements [`fmt::Debug`]. +/// +/// # Examples +/// +/// ``` +/// # use wireguard_conf::prelude::*; +/// # fn main() -> WireguardResult<()> { +/// // generate new random key: +/// let key = PrivateKey::random(); +/// +/// // import key: +/// let imported_key = PrivateKey::try_from("sJkP2oorqrq49P6Ln25MWo3X04PxhB8k+RnJJnZ4gEo=")?; +/// +/// // export key via `fmt::Display` trait: +/// let exported_key = imported_key.to_string(); +/// +/// assert_eq!(exported_key, "sJkP2oorqrq49P6Ln25MWo3X04PxhB8k+RnJJnZ4gEo=".to_string()); +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone, ZeroizeOnDrop)] +pub struct PrivateKey(pub(crate) StaticSecret); + +impl PrivateKey { + /// Generate new a random [`PrivateKey`] + #[must_use] + pub fn random() -> PrivateKey { + Self(StaticSecret::random()) + } +} + +impl PrivateKey { + /// View private key as byte array. + #[inline] + #[must_use] + pub fn as_bytes(&self) -> &[u8; 32] { + self.0.as_bytes() + } + + /// Convert private key to a byte array. + #[inline] + #[must_use] + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } +} + +impl fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("PrivateKey") + .field(&self.to_string()) + .finish() + } +} + +/// Export key as base64 for Wireguard. +impl fmt::Display for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.as_bytes())) + } +} + +impl PartialEq for PrivateKey { + fn eq(&self, other: &Self) -> bool { + self.as_bytes() == other.as_bytes() + } +} + +impl TryFrom<&str> for PrivateKey { + type Error = WireguardError; + + fn try_from(value: &str) -> Result { + let bytes: [u8; 32] = BASE64_STANDARD + .decode(value) + .map_err(|_| WireguardError::InvalidPrivateKey)? + .try_into() + .map_err(|_| WireguardError::InvalidPrivateKey)?; + + Ok(Self(StaticSecret::from(bytes))) + } +} + +impl TryFrom for PrivateKey { + type Error = WireguardError; + + fn try_from(value: String) -> Result { + Self::try_from(value.as_str()) + } +} + +impl From<[u8; 32]> for PrivateKey { + fn from(value: [u8; 32]) -> Self { + Self(StaticSecret::from(value)) + } +} + +#[cfg(feature = "serde")] +mod serde_impl { + use super::PrivateKey; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; + + impl Serialize for PrivateKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(&self.to_string()) + } else { + serializer.serialize_bytes(self.as_bytes()) + } + } + } + + impl<'de> Deserialize<'de> for PrivateKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + let data = String::deserialize(deserializer)?; + + PrivateKey::try_from(data.as_str()).map_err(|_| { + de::Error::invalid_value(de::Unexpected::Str(&data), &"a private key") + }) + } else { + let bytes = <[u8; 32]>::deserialize(deserializer)?; + + Ok(PrivateKey::from(bytes)) + } + } + } +} diff --git a/src/keys/public.rs b/src/keys/public.rs new file mode 100644 index 0000000..790271f --- /dev/null +++ b/src/keys/public.rs @@ -0,0 +1,144 @@ +use core::fmt; + +use base64::prelude::*; +use x25519_dalek::PublicKey as XPublicKey; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use crate::WireguardError; + +use super::PrivateKey; + +/// Public key. +/// +/// Wrapper around [`x25519_dalek::PublicKey`] with some traits. +/// +/// # Implements +/// +/// - Implements [`Zeroize`] and [`ZeroizeOnDrop`] for clearing secrets from memory. +/// - Implements [`TryFrom<&str>`] or [`TryFrom`] for importing key from Base64 format. +/// - Implements [`From<&PrivateKey>`] for converting [`PrivateKey`] to [`PublicKey`]. +/// - Implements [`fmt::Display`] for exporting key in Wireguard's format. +/// - Implements [`fmt::Debug`]. +/// +/// # Examples +/// +/// ``` +/// # use wireguard_conf::prelude::*; +/// # fn main() -> WireguardResult<()> { +/// // generate new random key: +/// let private_key = PrivateKey::random(); // 1. generate private key +/// let public_key = PublicKey::from(&private_key); // 2. get public key via `From<&PrivateKey>` +/// +/// // import key: +/// let imported_key = PublicKey::try_from("ijxpP+2xo+s77bfbm4QZzl6OyYP7sIOTutqngQSlZBs=")?; +/// +/// // export key via `fmt::Display` trait: +/// let exported_key = imported_key.to_string(); +/// +/// assert_eq!(exported_key, "ijxpP+2xo+s77bfbm4QZzl6OyYP7sIOTutqngQSlZBs=".to_string()); +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone, PartialEq, Zeroize, ZeroizeOnDrop)] +pub struct PublicKey(XPublicKey); + +impl PublicKey { + /// Convert this public key to a byte array. + #[inline] + #[must_use] + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + + /// View this public key as a byte array. + #[inline] + #[must_use] + pub fn as_bytes(&self) -> &[u8; 32] { + self.0.as_bytes() + } +} + +impl fmt::Debug for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PublicKey").field(&self.to_string()).finish() + } +} + +/// Export key in base64 format for Wireguard. +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.as_bytes())) + } +} + +impl TryFrom<&str> for PublicKey { + type Error = WireguardError; + + fn try_from(value: &str) -> Result { + let bytes: [u8; 32] = BASE64_STANDARD + .decode(value) + .map_err(|_| WireguardError::InvalidPublicKey)? + .try_into() + .map_err(|_| WireguardError::InvalidPublicKey)?; + + Ok(Self(XPublicKey::from(bytes))) + } +} + +impl TryFrom for PublicKey { + type Error = WireguardError; + + fn try_from(value: String) -> Result { + Self::try_from(value.as_str()) + } +} + +impl From<[u8; 32]> for PublicKey { + fn from(value: [u8; 32]) -> Self { + Self(XPublicKey::from(value)) + } +} + +impl From<&PrivateKey> for PublicKey { + fn from(value: &PrivateKey) -> Self { + Self(XPublicKey::from(&value.0)) + } +} + +#[cfg(feature = "serde")] +mod serde_impl { + use super::PublicKey; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; + + impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(&self.to_string()) + } else { + serializer.serialize_bytes(self.as_bytes()) + } + } + } + + impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + let data = String::deserialize(deserializer)?; + + PublicKey::try_from(data.as_str()).map_err(|_| { + de::Error::invalid_value(de::Unexpected::Str(&data), &"a public key") + }) + } else { + let bytes = <[u8; 32]>::deserialize(deserializer)?; + + Ok(PublicKey::from(bytes)) + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 51aa9fb..f53bdf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,9 +46,10 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, doc(auto_cfg))] +mod error; +mod keys; mod macros; mod models; -mod utils; #[cfg(feature = "amneziawg")] mod amneziawg; @@ -59,5 +60,6 @@ pub mod prelude; pub use ipnet; +pub use error::*; +pub use keys::*; pub use models::*; -pub use utils::*; diff --git a/src/prelude.rs b/src/prelude.rs index f0b909b..a12151b 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,5 +2,6 @@ #[cfg(feature = "amneziawg")] pub use crate::amneziawg::*; +pub use crate::error::*; +pub use crate::keys::*; pub use crate::models::*; -pub use crate::utils::*; diff --git a/src/utils/keys.rs b/src/utils/keys.rs index 15cb8e6..e69de29 100644 --- a/src/utils/keys.rs +++ b/src/utils/keys.rs @@ -1,311 +0,0 @@ -use core::fmt; - -use base64::prelude::*; -use rand::Rng; -use x25519_dalek::{PublicKey as XPublicKey, StaticSecret}; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -use crate::WireguardError; - -/// Private key -/// -/// Wrapper around [`x25519_dalek::StaticSecret`] with some traits. -/// -/// # Implements -/// -/// - Implements [`Zeroize`] and [`ZeroizeOnDrop`] for clearing secrets from memory. -/// - Implements [`TryFrom<&str>`] or [`TryFrom`] for importing key from Base64 format. -/// - Implements [`fmt::Display`] for exporting key in Wireguard's format. -/// - Implements [`fmt::Debug`]. -/// -/// # Examples -/// -/// ``` -/// # use wireguard_conf::prelude::*; -/// # fn main() -> WireguardResult<()> { -/// // generate new random key: -/// let key = PrivateKey::random(); -/// -/// // import key: -/// let imported_key = PrivateKey::try_from("sJkP2oorqrq49P6Ln25MWo3X04PxhB8k+RnJJnZ4gEo=")?; -/// -/// // export key via `fmt::Display` trait: -/// let exported_key = imported_key.to_string(); -/// -/// assert_eq!(exported_key, "sJkP2oorqrq49P6Ln25MWo3X04PxhB8k+RnJJnZ4gEo=".to_string()); -/// # Ok(()) -/// # } -/// ``` -#[derive(Clone, ZeroizeOnDrop)] -pub struct PrivateKey(StaticSecret); - -impl PrivateKey { - /// Generate new a random [`PrivateKey`] - #[must_use] - pub fn random() -> PrivateKey { - Self(StaticSecret::random()) - } -} - -impl PrivateKey { - /// View private key as byte array. - #[inline] - #[must_use] - pub fn as_bytes(&self) -> &[u8; 32] { - self.0.as_bytes() - } - - /// Convert private key to a byte array. - #[inline] - #[must_use] - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() - } -} - -impl fmt::Debug for PrivateKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("PrivateKey") - .field(&self.to_string()) - .finish() - } -} - -/// Export key as base64 for Wireguard. -impl fmt::Display for PrivateKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.as_bytes())) - } -} - -impl PartialEq for PrivateKey { - fn eq(&self, other: &Self) -> bool { - self.as_bytes() == other.as_bytes() - } -} - -impl TryFrom<&str> for PrivateKey { - type Error = WireguardError; - - fn try_from(value: &str) -> Result { - let bytes: [u8; 32] = BASE64_STANDARD - .decode(value) - .map_err(|_| WireguardError::InvalidPrivateKey)? - .try_into() - .map_err(|_| WireguardError::InvalidPrivateKey)?; - - Ok(Self(StaticSecret::from(bytes))) - } -} - -impl TryFrom for PrivateKey { - type Error = WireguardError; - - fn try_from(value: String) -> Result { - Self::try_from(value.as_str()) - } -} - -impl From<[u8; 32]> for PrivateKey { - fn from(value: [u8; 32]) -> Self { - Self(StaticSecret::from(value)) - } -} - -/// Public key. -/// -/// Wrapper around [`x25519_dalek::PublicKey`] with some traits. -/// -/// # Implements -/// -/// - Implements [`Zeroize`] and [`ZeroizeOnDrop`] for clearing secrets from memory. -/// - Implements [`TryFrom<&str>`] or [`TryFrom`] for importing key from Base64 format. -/// - Implements [`From<&PrivateKey>`] for converting [`PrivateKey`] to [`PublicKey`]. -/// - Implements [`fmt::Display`] for exporting key in Wireguard's format. -/// - Implements [`fmt::Debug`]. -/// -/// # Examples -/// -/// ``` -/// # use wireguard_conf::prelude::*; -/// # fn main() -> WireguardResult<()> { -/// // generate new random key: -/// let private_key = PrivateKey::random(); // 1. generate private key -/// let public_key = PublicKey::from(&private_key); // 2. get public key via `From<&PrivateKey>` -/// -/// // import key: -/// let imported_key = PublicKey::try_from("ijxpP+2xo+s77bfbm4QZzl6OyYP7sIOTutqngQSlZBs=")?; -/// -/// // export key via `fmt::Display` trait: -/// let exported_key = imported_key.to_string(); -/// -/// assert_eq!(exported_key, "ijxpP+2xo+s77bfbm4QZzl6OyYP7sIOTutqngQSlZBs=".to_string()); -/// # Ok(()) -/// # } -/// ``` -#[derive(Clone, PartialEq, Zeroize, ZeroizeOnDrop)] -pub struct PublicKey(XPublicKey); - -impl PublicKey { - /// Convert this public key to a byte array. - #[inline] - #[must_use] - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() - } - - /// View this public key as a byte array. - #[inline] - #[must_use] - pub fn as_bytes(&self) -> &[u8; 32] { - self.0.as_bytes() - } -} - -impl fmt::Debug for PublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PublicKey").field(&self.to_string()).finish() - } -} - -/// Export key in base64 format for Wireguard. -impl fmt::Display for PublicKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.as_bytes())) - } -} - -impl TryFrom<&str> for PublicKey { - type Error = WireguardError; - - fn try_from(value: &str) -> Result { - let bytes: [u8; 32] = BASE64_STANDARD - .decode(value) - .map_err(|_| WireguardError::InvalidPublicKey)? - .try_into() - .map_err(|_| WireguardError::InvalidPublicKey)?; - - Ok(Self(XPublicKey::from(bytes))) - } -} - -impl TryFrom for PublicKey { - type Error = WireguardError; - - fn try_from(value: String) -> Result { - Self::try_from(value.as_str()) - } -} - -impl From<[u8; 32]> for PublicKey { - fn from(value: [u8; 32]) -> Self { - Self(XPublicKey::from(value)) - } -} - -impl From<&PrivateKey> for PublicKey { - fn from(value: &PrivateKey) -> Self { - Self(XPublicKey::from(&value.0)) - } -} - -/// Preshared key. -/// -/// Wrapper around `[u8; 32]`. -/// -/// # Implements -/// -/// - Implements [`Zeroize`] and [`ZeroizeOnDrop`] for clearing secrets from memory. -/// - Implements [`TryFrom<&str>`] or [`TryFrom`] for importing key from Base64 format. -/// - Implements [`fmt::Display`] for exporting key in Wireguard's format. -/// - Implements [`fmt::Debug`]. -/// -/// # Examples -/// -/// ``` -/// # use wireguard_conf::prelude::*; -/// # fn main() -> WireguardResult<()> { -/// // generate new preshared key: -/// let preshared_key = PresharedKey::random(); -/// -/// // import key: -/// let imported_key = PresharedKey::try_from("MA4zR0tvQpZ4CQ7gs/KcAVMNMGIFBtDcfpjBr+0GwHY=")?; -/// -/// // export key via `fmt::Display` trait: -/// let exported_key = imported_key.to_string(); -/// -/// assert_eq!(exported_key, "MA4zR0tvQpZ4CQ7gs/KcAVMNMGIFBtDcfpjBr+0GwHY=".to_string()); -/// # Ok(()) -/// # } -/// ``` -#[derive(Clone, PartialEq, Zeroize, ZeroizeOnDrop)] -pub struct PresharedKey([u8; 32]); - -impl PresharedKey { - /// Generate random [`PresharedKey`]. - #[must_use] - pub fn random() -> Self { - let mut key = [0u8; 32]; - rand::rng().fill_bytes(&mut key); - Self(key) - } -} - -impl PresharedKey { - /// Convert [`PresharedKey`] into byte array. - #[inline] - #[must_use] - pub fn to_bytes(&self) -> [u8; 32] { - self.0 - } - - /// Get [`PresharedKey`] as reference to bytes. - #[inline] - #[must_use] - pub fn as_bytes(&self) -> &[u8; 32] { - &self.0 - } -} - -impl fmt::Debug for PresharedKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PresharedKey") - .field(&self.to_string()) - .finish() - } -} - -/// Export [`PresharedKey`] as base64 format for Wireguard. -impl fmt::Display for PresharedKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.as_bytes())) - } -} - -impl From<[u8; 32]> for PresharedKey { - fn from(value: [u8; 32]) -> Self { - Self(value) - } -} - -impl TryFrom<&str> for PresharedKey { - type Error = WireguardError; - - fn try_from(value: &str) -> Result { - let bytes: [u8; 32] = BASE64_STANDARD - .decode(value) - .map_err(|_| WireguardError::InvalidPresharedKey)? - .try_into() - .map_err(|_| WireguardError::InvalidPresharedKey)?; - - Ok(Self(bytes)) - } -} - -impl TryFrom for PresharedKey { - type Error = WireguardError; - - fn try_from(value: String) -> Result { - Self::try_from(value.as_str()) - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 7687ae6..e69de29 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,39 +0,0 @@ -#[cfg(feature = "serde")] -mod serde; - -mod keys; -pub use keys::*; - -use thiserror::Error; - -/// `wireguard-conf` error. -#[derive(Error, Debug, PartialEq)] -pub enum WireguardError { - /// Error, when private key is invalid. - #[error("invalid private key")] - InvalidPrivateKey, - - /// Error, when public key is invalid. - #[error("invalid public key")] - InvalidPublicKey, - - /// Error, when preshared key is invalid. - #[error("invalid preshared key")] - InvalidPresharedKey, - - /// Error, when private key isn't provided. - #[error("no private key provided")] - NoPrivateKeyProvided, - - /// Error, when no IP assigned. - #[error("no assigned ip")] - NoAssignedIP, - - /// Error, when some amnezia setting is invalid - #[cfg(feature = "amneziawg")] - #[error("invalid amnezia setting: {0}")] - InvalidAmneziaSetting(String), -} - -/// Result alias. -pub type WireguardResult = Result; diff --git a/src/utils/serde.rs b/src/utils/serde.rs deleted file mode 100644 index 8ba4a4f..0000000 --- a/src/utils/serde.rs +++ /dev/null @@ -1,97 +0,0 @@ -use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; - -use super::{PresharedKey, PrivateKey, PublicKey}; - -impl Serialize for PrivateKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) - } else { - serializer.serialize_bytes(self.as_bytes()) - } - } -} - -impl<'de> Deserialize<'de> for PrivateKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - if deserializer.is_human_readable() { - let data = String::deserialize(deserializer)?; - - PrivateKey::try_from(data.as_str()) - .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(&data), &"a private key")) - } else { - let bytes = <[u8; 32]>::deserialize(deserializer)?; - - Ok(PrivateKey::from(bytes)) - } - } -} - -impl Serialize for PublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) - } else { - serializer.serialize_bytes(self.as_bytes()) - } - } -} - -impl<'de> Deserialize<'de> for PublicKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - if deserializer.is_human_readable() { - let data = String::deserialize(deserializer)?; - - PublicKey::try_from(data.as_str()) - .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(&data), &"a public key")) - } else { - let bytes = <[u8; 32]>::deserialize(deserializer)?; - - Ok(PublicKey::from(bytes)) - } - } -} - -impl Serialize for PresharedKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) - } else { - serializer.serialize_bytes(self.as_bytes()) - } - } -} - -impl<'de> Deserialize<'de> for PresharedKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - if deserializer.is_human_readable() { - let data = String::deserialize(deserializer)?; - - PresharedKey::try_from(data.as_str()).map_err(|_| { - de::Error::invalid_value(de::Unexpected::Str(&data), &"a preshared key") - }) - } else { - let bytes = <[u8; 32]>::deserialize(deserializer)?; - - Ok(PresharedKey::from(bytes)) - } - } -} From bfe11457ebbff9822b91fcde96b046fd7bff7ae1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 16 Aug 2026 10:58:16 +0300 Subject: [PATCH 2/5] docs: move documentation out of root --- CHANGELOG.md | 2 +- README.md | 19 ++++++++----------- MIGRATION.md => docs/MIGRATION.md | 0 QUICKSTART.md => docs/QUICKSTART.md | 0 4 files changed, 9 insertions(+), 12 deletions(-) rename MIGRATION.md => docs/MIGRATION.md (100%) rename QUICKSTART.md => docs/QUICKSTART.md (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0111b76..63ba31c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Breaking changes -- [Rewritten how AmneziaWG values are handled](./MIGRATION.md). +- [Rewritten how AmneziaWG values are handled](./docs/MIGRATION.md). Now `amneziawg` feature has both versions of AmneziaWG. diff --git a/README.md b/README.md index bb71819..50c6298 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Wireguard Conf +# wireguard-conf [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/idkncc/wireguard-conf/build.yml)](https://github.com/idkncc/wireguard-conf/actions) [![docs.rs](https://img.shields.io/docsrs/wireguard-conf)](https://docs.rs/wireguard-conf) [![Crates.io Version](https://img.shields.io/crates/v/wireguard-conf)](https://crates.io/crates/wireguard-conf) [![Crates.io Total Downloads](https://img.shields.io/crates/d/wireguard-conf)](https://crates.io/crates/wireguard-conf) [![Crates.io MSRV](https://img.shields.io/crates/msrv/wireguard-conf)](https://crates.io/crates/wireguard-conf) -[![Quickstart Tutorial](https://img.shields.io/badge/Quickstart_Tutorial-blue)](./QUICKSTART.md) +[![Quickstart Tutorial](https://img.shields.io/badge/Quickstart_Tutorial-blue)](./docs/QUICKSTART.md) Easy to use library for creating wireguard configs. @@ -19,7 +19,7 @@ cargo add wireguard-conf ### Usage -The best way to understand library: [Quickstart](./QUICKSTART.md)! +The best way to understand library: [Quickstart](./docs/QUICKSTART.md)! More usage examples you can find in [tests/tutorial.rs](tests/tutorial.rs), [tests folder](tests/) and documentation examples on [docs.rs](https://docs.rs/wireguard-conf). @@ -47,19 +47,16 @@ println!("{}", peer.to_interface(&interface, ToInterfaceOptions::new()).unwrap() ### Features -- `amneziawg-*`: AmneziaWG is a fork of wireguard, that less recognizable by DPI systems by - randomizing packet headers with packet sizes. +- `amneziawg`: adds support for generating and manually building amnezia settings via + [`AmneziaWG`](https://docs.rs/wireguard-conf/latest/wireguard-conf/enum.AmneziaWG.html) enum. - These feature flags add support for generating and manually building amnezia settings: - - - `amneziawg`: _alias for `amneziawg-1`, this will update in next major release_ - - `amneziawg-1`: adds [AmneziaWG 1.0](https://docs.amnezia.org/documentation/amnezia-wg/) support. - - `amneziawg-2`: adds [AmneziaWG 2.0](https://docs.amnezia.org/documentation/amnezia-wg/) support. **(recommended over `amneziawg-1`)** - - ~~`amneziawg-3`~~: **WIP** (waiting for official release) + > **AmneziaWG** is a fork of wireguard, that less recognizable by DPI systems by + > randomizing packet headers with packet sizes. - `serde`: adds implementions of `serde::Serialize` and `serde::Deserialize` for all structs. ## Misc - [Contributing](./CONTRIBUTING.md) +- [Major releases migration guides](./docs/MIGRATION.md) - Licensed under either [MIT](./LICENSE-MIT) or [Apache 2.0](./LICENSE-APACHE) diff --git a/MIGRATION.md b/docs/MIGRATION.md similarity index 100% rename from MIGRATION.md rename to docs/MIGRATION.md diff --git a/QUICKSTART.md b/docs/QUICKSTART.md similarity index 100% rename from QUICKSTART.md rename to docs/QUICKSTART.md From fbfc723d8b05c9dc2cf2d80bdaf2f3310890db6a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 16 Aug 2026 11:08:15 +0300 Subject: [PATCH 3/5] feat(serde): mark `AmneziaWG` as untagged --- src/amneziawg/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/amneziawg/mod.rs b/src/amneziawg/mod.rs index 6e4b692..3422c5e 100644 --- a/src/amneziawg/mod.rs +++ b/src/amneziawg/mod.rs @@ -28,6 +28,7 @@ use crate::WireguardResult; /// ``` #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] pub enum AmneziaWG { /// AmneziaWG 1.0 V1(AmneziaWG1), From 32ff246c9efefe7710bfeb7f3a1cdf855e16a1c7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 16 Aug 2026 11:20:40 +0300 Subject: [PATCH 4/5] feat: update justfile, add details to `AmneziaWG1` and `AmneziaWG2` --- justfile | 11 ++++++----- src/amneziawg/ver1.rs | 12 ++++++++++++ src/amneziawg/ver2.rs | 17 +++++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/justfile b/justfile index f598017..c8d7688 100644 --- a/justfile +++ b/justfile @@ -13,13 +13,14 @@ build: alias doc := docs docs: - cargo doc {{CFLAGS}} -docs-open: - cargo doc {{CFLAGS}} --open + cargo doc --all-features {{CFLAGS}} + +docs-rs: + RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --all-features {{CFLAGS}} fmt: cargo fmt {{CFLAGS}} lint: - cargo clippy --all-features {{CFLAGS}} + cargo clippy --all-targets --all-features {{CFLAGS}} lint-fix: - cargo clippy --all-features --fix --allow-dirty {{CFLAGS}} + cargo clippy --all-targets --all-features --fix --allow-dirty {{CFLAGS}} diff --git a/src/amneziawg/ver1.rs b/src/amneziawg/ver1.rs index cddc4b7..d2a918d 100644 --- a/src/amneziawg/ver1.rs +++ b/src/amneziawg/ver1.rs @@ -21,6 +21,18 @@ macro_rules! assert_return { /// **AmneziaWG 1.0** obfuscation values. /// +/// If you need to generate, build, validate or use these settings in interface, **use [`AmneziaWG`] +/// enum instead**. +/// +/// ``` +/// # use wireguard_conf::prelude::*; +/// let settings = AmneziaWG::random_v1(); +/// +/// _ = Interface::builder() +/// .amnezia_settings(settings) +/// .build(); +/// ``` +/// /// - [Documentation](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module?tab=readme-ov-file#configuration) #[must_use] #[derive(Clone, Debug, PartialEq)] diff --git a/src/amneziawg/ver2.rs b/src/amneziawg/ver2.rs index 63b5966..5d8f108 100644 --- a/src/amneziawg/ver2.rs +++ b/src/amneziawg/ver2.rs @@ -45,6 +45,23 @@ impl fmt::Display for HRange { /// **AmneziaWG 2.0** obfuscation values. /// +/// If you need to generate, build, validate or use these settings in interface, **use [`AmneziaWG`] +/// enum instead**. +/// +/// ``` +/// # use wireguard_conf::prelude::*; +/// let settings = AmneziaWG::random_v2(); +/// let settings = AmneziaWG::builder_v2() +/// .jc(9) +/// // +/// .build() +/// .expect("invalid settings"); +/// +/// _ = Interface::builder() +/// .amnezia_settings(settings) +/// .build(); +/// ``` +/// /// - [Documentation](https://docs.amnezia.org/documentation/amnezia-wg) #[must_use] #[derive(Clone, Debug, PartialEq, Default, Builder)] From 877cda2a8b744253f1d7c446654951726b040ead Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 16 Aug 2026 11:24:28 +0300 Subject: [PATCH 5/5] docs: fix warning in docs-rs --- src/keys/private.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keys/private.rs b/src/keys/private.rs index 79ab41e..791e0a6 100644 --- a/src/keys/private.rs +++ b/src/keys/private.rs @@ -12,7 +12,7 @@ use crate::WireguardError; /// /// # Implements /// -/// - Implements [`Zeroize`] and [`ZeroizeOnDrop`] for clearing secrets from memory. +/// - Implements [`ZeroizeOnDrop`] for clearing secrets from memory. /// - Implements [`TryFrom<&str>`] or [`TryFrom`] for importing key from Base64 format. /// - Implements [`fmt::Display`] for exporting key in Wireguard's format. /// - Implements [`fmt::Debug`].