comments inline
On 1/16/26 4:33 PM, Christoph Heiss wrote:
[snip]
> +impl PrivateKey {
> + /// Length of the raw private key data in bytes.
> + pub const RAW_LENGTH: usize = ed25519_dalek::SECRET_KEY_LENGTH;
> +
> + /// Generates a new private key suitable for use with WireGuard.
> + pub fn generate() -> Result<Self, Error> {
> + generate_key().map(Self)
> + }
> +
> + /// Calculates the public key from the private key.
> + pub fn public_key(&self) -> PublicKey {
> + PublicKey(
> + ed25519_dalek::SigningKey::from_bytes(&self.0)
> + .verifying_key()
> + .to_bytes(),
> + )
> + }
> +
> + /// Returns the raw private key material.
> + #[must_use]
> + pub fn raw(&self) -> &ed25519_dalek::SecretKey {
> + &self.0
> + }
might be better to implement AsRef instead for ergonomics?
> + /// Builds a new [`PrivateKey`] from raw key material.
> + #[must_use]
> + pub fn from_raw(data: ed25519_dalek::SecretKey) -> Self {
> + // [`SigningKey`] takes care of correct key clamping.
> + Self(SigningKey::from(&data).to_bytes())
> + }
> +}
> +
> +impl From<ed25519_dalek::SecretKey> for PrivateKey {
> + fn from(value: ed25519_dalek::SecretKey) -> Self {
> + Self(value)
> + }
> +}
> +
> +/// Preshared key between two WireGuard peers.
> +#[derive(Clone, Deserialize, Serialize)]
> +#[serde(transparent)]
> +pub struct PresharedKey(
> + #[serde(with = "proxmox_serde::byte_array_as_base64")]
> ed25519_dalek::SecretKey,
> +);
> +
> +impl fmt::Debug for PresharedKey {
> + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
> + write!(f, "<preshared-key>")
> + }
> +}
> +
> +impl PresharedKey {
> + /// Length of the raw private key data in bytes.
> + pub const RAW_LENGTH: usize = ed25519_dalek::SECRET_KEY_LENGTH;
> +
> + /// Generates a new preshared key suitable for use with WireGuard.
> + pub fn generate() -> Result<Self, Error> {
> + generate_key().map(Self)
> + }
> +
> + /// Returns the raw private key material.
> + #[must_use]
> + pub fn raw(&self) -> &ed25519_dalek::SecretKey {
> + &self.0
> + }
> +
here too: might be better to implement AsRef instead for ergonomics?
> + /// Builds a new [`PrivateKey`] from raw key material.
> + #[must_use]
> + pub fn from_raw(data: ed25519_dalek::SecretKey) -> Self {
> + // [`SigningKey`] takes care of correct key clamping.
> + Self(SigningKey::from(&data).to_bytes())
> + }
> +}
> +
> +/// A single WireGuard peer.
> +#[derive(Serialize, Debug)]
> +#[serde(rename_all = "PascalCase")]
> +pub struct WireGuardPeer {
> + /// Public key, matching the private key of of the remote peer.
> + pub public_key: PublicKey,
> + /// Additional key preshared between two peers. Adds an additional layer
> of symmetric-key
> + /// cryptography to be mixed into the already existing public-key
> cryptography, for
> + /// post-quantum resistance.
> + pub preshared_key: PresharedKey,
> + /// List of IPv4/v6 CIDRs from which incoming traffic for this peer is
> allowed and to which
> + /// outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0
> may be specified for
> + /// matching all IPv4 addresses, and ::/0 may be specified for matching
> all IPv6 addresses.
> + #[serde(rename = "AllowedIPs")]
> + pub allowed_ips: Vec<Cidr>,
potentially missing skip_serializing_if = "Vec::is_empty"?
[snip]
> +#[cfg(test)]
> +mod tests {
> + use std::net::Ipv4Addr;
> +
> + use proxmox_network_types::ip_address::Cidr;
> +
> + use crate::{PresharedKey, PrivateKey, WireGuardConfig,
> WireGuardInterface, WireGuardPeer};
> +
> + fn mock_private_key(v: u8) -> PrivateKey {
> + let base = v * 32;
> + PrivateKey((base..base +
> 32).collect::<Vec<u8>>().try_into().unwrap())
> + }
> +
> + fn mock_preshared_key(v: u8) -> PresharedKey {
> + let base = v * 32;
> + PresharedKey((base..base +
> 32).collect::<Vec<u8>>().try_into().unwrap())
> + }
> +
> + #[test]
> + fn single_peer() {
> + let config = WireGuardConfig {
> + interface: WireGuardInterface {
> + private_key: mock_private_key(0),
> + listen_port: Some(51820),
> + fw_mark: Some(127),
> + },
> + peers: vec![WireGuardPeer {
> + public_key: mock_private_key(1).public_key(),
> + preshared_key: mock_preshared_key(1),
> + allowed_ips: vec![Cidr::new_v4(Ipv4Addr::new(192, 168, 0,
> 0), 24).unwrap()],
> + endpoint: Some("foo.example.com:51820".parse().unwrap()),
> + persistent_keepalive: Some(25),
> + }],
> + };
> +
> + pretty_assertions::assert_eq!(
> + config.to_raw_config().unwrap(),
> + "[Interface]
> +PrivateKey = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=
> +ListenPort = 51820
> +FwMark = 127
> +
> +[Peer]
> +PublicKey = Kay64UG8yvCyLhqU000LxzYeUm0L/hLIl5S8kyKWbdc=
> +PresharedKey = ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=
> +AllowedIPs = 192.168.0.0/24
> +Endpoint = foo.example.com:51820
> +PersistentKeepalive = 25
> +"
> + );
> + }
> +
> + #[test]
> + fn multiple_peers() {
> + let config = WireGuardConfig {
> + interface: WireGuardInterface {
> + private_key: mock_private_key(0),
> + listen_port: Some(51820),
> + fw_mark: None,
> + },
> + peers: vec![
> + WireGuardPeer {
> + public_key: mock_private_key(1).public_key(),
> + preshared_key: mock_preshared_key(1),
> + allowed_ips: vec![Cidr::new_v4(Ipv4Addr::new(192, 168,
> 0, 0), 24).unwrap()],
> + endpoint: Some("foo.example.com:51820".parse().unwrap()),
> + persistent_keepalive: None,
> + },
> + WireGuardPeer {
> + public_key: mock_private_key(2).public_key(),
> + preshared_key: mock_preshared_key(2),
> + allowed_ips: vec![Cidr::new_v4(Ipv4Addr::new(192, 168,
> 1, 0), 24).unwrap()],
> + endpoint: None,
> + persistent_keepalive: Some(25),
> + },
> + WireGuardPeer {
> + public_key: mock_private_key(3).public_key(),
> + preshared_key: mock_preshared_key(3),
> + allowed_ips: vec![Cidr::new_v4(Ipv4Addr::new(192, 168,
> 2, 0), 24).unwrap()],
> + endpoint: None,
> + persistent_keepalive: None,
> + },
> + ],
> + };
> +
> + pretty_assertions::assert_eq!(
> + config.to_raw_config().unwrap(),
> + "[Interface]
> +PrivateKey = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=
> +ListenPort = 51820
> +
> +[Peer]
> +PublicKey = Kay64UG8yvCyLhqU000LxzYeUm0L/hLIl5S8kyKWbdc=
> +PresharedKey = ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=
> +AllowedIPs = 192.168.0.0/24
> +Endpoint = foo.example.com:51820
> +
> +[Peer]
> +PublicKey = JUO5L/EJVRFHatyDadtt3JM2ZaEZeN2hQE7hBmypVZ0=
> +PresharedKey = QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl8=
> +AllowedIPs = 192.168.1.0/24
> +PersistentKeepalive = 25
> +
> +[Peer]
> +PublicKey = F0VTtFbd38aQjsqxwQH+arIeK6oGF3lbfUOmNIKZP9U=
> +PresharedKey = YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8=
> +AllowedIPs = 192.168.2.0/24
> +"
> + );
> + }
> +}
maybe add some test cases for missing properties as well (or adapt the
existing ones)?