From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id AA2841FF13F for ; Thu, 07 May 2026 14:44:49 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id F3B7B1AB9F; Thu, 7 May 2026 14:42:58 +0200 (CEST) Message-ID: Date: Thu, 7 May 2026 14:40:51 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH proxmox v4 02/31] wireguard: utilize x25519 for public key generation To: pve-devel@lists.proxmox.com, Christian Ebner References: <20260507124008.417223-1-s.hanreich@proxmox.com> <20260507124008.417223-3-s.hanreich@proxmox.com> Content-Language: en-US From: Stefan Hanreich In-Reply-To: <20260507124008.417223-3-s.hanreich@proxmox.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.636 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record WEIRD_PORT 0.001 Uses non-standard port number for HTTP Message-ID-Hash: JKNMVCPFPQCWNOWG764PGI6L5RIKYZYM X-Message-ID-Hash: JKNMVCPFPQCWNOWG764PGI6L5RIKYZYM X-MailFrom: s.hanreich@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: @Christoph could you please double-check this in particular? On 5/7/26 2:38 PM, Stefan Hanreich wrote: > Previously, proxmox-wireguard used ed25519 for generating the public > keys, which is the wrong algorithm for deriving suitable public keys > for WireGuard - since ed25519 is a digital signature algorithm. x25519 > is for conducting DH key exchanges, which is what is utilized in the > WireGuard protocol. > > The generated public keys from the tests have been checked against the > output from wg pubkey - to make sure that generated keys are exactly > the same as the ones generated by the userspace wg(8) tool. > > Signed-off-by: Stefan Hanreich > --- > proxmox-wireguard/Cargo.toml | 1 + > proxmox-wireguard/src/lib.rs | 56 +++++++++++++----------------------- > 2 files changed, 21 insertions(+), 36 deletions(-) > > diff --git a/proxmox-wireguard/Cargo.toml b/proxmox-wireguard/Cargo.toml > index b1abae3d..ae3236a8 100644 > --- a/proxmox-wireguard/Cargo.toml > +++ b/proxmox-wireguard/Cargo.toml > @@ -11,6 +11,7 @@ rust-version.workspace = true > > [dependencies] > ed25519-dalek = "2.1" > +x25519-dalek = { version = "2.0.1", features = ["getrandom", "static_secrets"] } > serde = { workspace = true, features = [ "derive" ] } > thiserror.workspace = true > proxmox-schema = { workspace = true, optional = true, features = ["api-types"] } > diff --git a/proxmox-wireguard/src/lib.rs b/proxmox-wireguard/src/lib.rs > index 08579775..bf6ea8ad 100644 > --- a/proxmox-wireguard/src/lib.rs > +++ b/proxmox-wireguard/src/lib.rs > @@ -12,9 +12,11 @@ > > #![forbid(unsafe_code, missing_docs)] > > +use std::fmt; > + > use ed25519_dalek::SigningKey; > use serde::{Deserialize, Serialize}; > -use std::fmt; > +use x25519_dalek::StaticSecret; > > use proxmox_network_types::{endpoint::ServiceEndpoint, ip_address::Cidr}; > #[cfg(feature = "api-types")] > @@ -42,9 +44,7 @@ impl From for Error { > /// Public key of a WireGuard peer. > #[derive(Clone, Copy, Deserialize, Serialize, Hash, Debug)] > #[serde(transparent)] > -pub struct PublicKey( > - #[serde(with = "proxmox_serde::byte_array_as_base64")] [u8; ed25519_dalek::PUBLIC_KEY_LENGTH], > -); > +pub struct PublicKey(#[serde(with = "proxmox_serde::byte_array_as_base64")] [u8; 32]); > > #[cfg(feature = "api-types")] > impl ApiType for PublicKey { > @@ -62,9 +62,7 @@ impl UpdaterType for PublicKey { > /// Private key of a WireGuard peer. > #[derive(Serialize)] > #[serde(transparent)] > -pub struct PrivateKey( > - #[serde(with = "proxmox_serde::byte_array_as_base64")] ed25519_dalek::SecretKey, > -); > +pub struct PrivateKey(#[serde(with = "proxmox_serde::byte_array_as_base64")] [u8; 32]); > > impl fmt::Debug for PrivateKey { > fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { > @@ -73,42 +71,27 @@ impl fmt::Debug for PrivateKey { > } > > 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. > #[cfg(feature = "key-generation")] > pub fn generate() -> Result { > - generate_key().map(Self) > + Ok(Self(StaticSecret::random().to_bytes())) > } > > /// 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(), > - ) > - } > - > - /// 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()) > + PublicKey(x25519_dalek::PublicKey::from(&StaticSecret::from(self.0)).to_bytes()) > } > } > > -impl From for PrivateKey { > - fn from(value: ed25519_dalek::SecretKey) -> Self { > +impl From<[u8; 32]> for PrivateKey { > + fn from(value: [u8; 32]) -> Self { > Self(value) > } > } > > -impl AsRef for PrivateKey { > - /// Returns the raw private key material. > - fn as_ref(&self) -> &ed25519_dalek::SecretKey { > - &self.0 > +impl From for PrivateKey { > + fn from(value: x25519_dalek::StaticSecret) -> Self { > + Self(value.to_bytes()) > } > } > > @@ -239,7 +222,8 @@ mod tests { > > fn mock_private_key(v: u8) -> PrivateKey { > let base = v * 32; > - PrivateKey((base..base + 32).collect::>().try_into().unwrap()) > + let key: [u8; 32] = (base..base + 32).collect::>().try_into().unwrap(); > + PrivateKey(key.into()) > } > > fn mock_preshared_key(v: u8) -> PresharedKey { > @@ -272,7 +256,7 @@ ListenPort = 51820 > FwMark = 127 > > [Peer] > -PublicKey = Kay64UG8yvCyLhqU000LxzYeUm0L/hLIl5S8kyKWbdc= > +PublicKey = NYBy1jZYgNGu6jKa35EhODhR7SGijjt16WXQ0s0WYlQ= > PresharedKey = ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8= > AllowedIPs = 192.168.0.0/24 > Endpoint = foo.example.com:51820 > @@ -328,24 +312,24 @@ PrivateKey = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8= > ListenPort = 51820 > > [Peer] > -PublicKey = Kay64UG8yvCyLhqU000LxzYeUm0L/hLIl5S8kyKWbdc= > +PublicKey = NYBy1jZYgNGu6jKa35EhODhR7SGijjt16WXQ0s0WYlQ= > PresharedKey = ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8= > AllowedIPs = 192.168.0.0/24 > Endpoint = foo.example.com:51820 > > [Peer] > -PublicKey = JUO5L/EJVRFHatyDadtt3JM2ZaEZeN2hQE7hBmypVZ0= > +PublicKey = eaYx7t4b+cmPEgMs3q3Q56B5OY/HhriMyEbsia+FpRo= > PresharedKey = QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl8= > AllowedIPs = 192.168.1.0/24 > PersistentKeepalive = 25 > > [Peer] > -PublicKey = F0VTtFbd38aQjsqxwQH+arIeK6oGF3lbfUOmNIKZP9U= > +PublicKey = Z13VdO13iTELPS52gfN5C0ZsdzsVIf7PNld5WDcepS8= > PresharedKey = YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8= > AllowedIPs = 192.168.2.0/24 > > [Peer] > -PublicKey = zRSzf5VulTGU/3+3Oz2B3MVh1hp1OAlLfD4aZD7l86o= > +PublicKey = ST6C/HRGSlkmiBdiPSBTxeuOLMSpiLT+4XnsawENUx0= > PresharedKey = gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp8= > Endpoint = 10.0.0.1:51820 > PersistentKeepalive = 25 > @@ -376,7 +360,7 @@ PersistentKeepalive = 25 > PrivateKey = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8= > > [Peer] > -PublicKey = Kay64UG8yvCyLhqU000LxzYeUm0L/hLIl5S8kyKWbdc= > +PublicKey = NYBy1jZYgNGu6jKa35EhODhR7SGijjt16WXQ0s0WYlQ= > PresharedKey = ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8= > AllowedIPs = 192.168.0.0/24 > Endpoint = 10.0.0.1:51820