use std::sync::LazyLock;
use dimpl::crypto::{ActiveKeyExchange, Buf, NamedGroup, SupportedKxGroup};
use windows::Win32::Security::Cryptography::BCRYPT_ALG_HANDLE;
use windows::Win32::Security::Cryptography::BCRYPT_ECCPUBLIC_BLOB;
use windows::Win32::Security::Cryptography::BCRYPT_ECDH_ALGORITHM;
use windows::Win32::Security::Cryptography::BCRYPT_HANDLE;
use windows::Win32::Security::Cryptography::BCRYPT_KEY_HANDLE;
use windows::Win32::Security::Cryptography::BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS;
use windows::Win32::Security::Cryptography::BCRYPT_SECRET_HANDLE;
use windows::Win32::Security::Cryptography::BCryptExportKey;
use windows::Win32::Security::Cryptography::BCryptFinalizeKeyPair;
use windows::Win32::Security::Cryptography::BCryptGenerateKeyPair;
use windows::Win32::Security::Cryptography::BCryptImportKeyPair;
use windows::Win32::Security::Cryptography::BCryptOpenAlgorithmProvider;
use windows::Win32::Security::Cryptography::BCryptSecretAgreement;
use windows::Win32::Security::Cryptography::BCryptSetProperty;
use windows::core::Owned;
use crate::WinCryptoError;
use super::kx_group::derive_raw_secret;
struct X25519Alg(BCRYPT_ALG_HANDLE);
unsafe impl Send for X25519Alg {}
unsafe impl Sync for X25519Alg {}
static X25519_PROVIDER: LazyLock<X25519Alg> = LazyLock::new(|| {
unsafe {
let mut handle = BCRYPT_ALG_HANDLE::default();
WinCryptoError::from_ntstatus(BCryptOpenAlgorithmProvider(
&mut handle,
BCRYPT_ECDH_ALGORITHM,
None,
BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS(0),
))
.expect("BCryptOpenAlgorithmProvider ECDH for X25519");
let curve_name: Vec<u16> = "curve25519\0".encode_utf16().collect();
let curve_bytes =
std::slice::from_raw_parts(curve_name.as_ptr() as *const u8, curve_name.len() * 2);
WinCryptoError::from_ntstatus(BCryptSetProperty(
BCRYPT_HANDLE(handle.0),
windows::core::w!("ECCCurveName"),
curve_bytes,
0,
))
.expect("BCryptSetProperty curve25519");
X25519Alg(handle)
}
});
struct X25519KeyExchange {
key_handle: Owned<BCRYPT_KEY_HANDLE>,
public_key_bytes: Buf,
}
unsafe impl Send for X25519KeyExchange {}
unsafe impl Sync for X25519KeyExchange {}
impl std::fmt::Debug for X25519KeyExchange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("X25519KeyExchange").finish_non_exhaustive()
}
}
impl X25519KeyExchange {
fn new(mut buf: Buf) -> Result<Self, String> {
let alg_handle = X25519_PROVIDER.0;
let key_handle = unsafe {
let mut key_handle = Owned::new(BCRYPT_KEY_HANDLE::default());
WinCryptoError::from_ntstatus(BCryptGenerateKeyPair(
alg_handle,
&mut *key_handle,
255,
0,
))
.map_err(|e| format!("BCryptGenerateKeyPair X25519 failed: {e}"))?;
WinCryptoError::from_ntstatus(BCryptFinalizeKeyPair(*key_handle, 0))
.map_err(|e| format!("BCryptFinalizeKeyPair X25519 failed: {e}"))?;
key_handle
};
let pub_key = export_x25519_public_key(*key_handle)?;
buf.clear();
buf.extend_from_slice(&pub_key);
Ok(Self {
key_handle,
public_key_bytes: buf,
})
}
}
impl ActiveKeyExchange for X25519KeyExchange {
fn pub_key(&self) -> &[u8] {
&self.public_key_bytes
}
fn complete(self: Box<Self>, peer_pub: &[u8], out: &mut Buf) -> Result<(), String> {
if peer_pub.len() != 32 {
return Err(format!(
"Invalid X25519 public key length: {} (expected 32)",
peer_pub.len(),
));
}
let alg_handle = X25519_PROVIDER.0;
let peer_key_handle = import_x25519_public_key(alg_handle, peer_pub)?;
let shared_secret = unsafe {
let mut secret_handle = Owned::new(BCRYPT_SECRET_HANDLE::default());
WinCryptoError::from_ntstatus(BCryptSecretAgreement(
*self.key_handle,
*peer_key_handle,
&mut *secret_handle,
0,
))
.map_err(|e| format!("BCryptSecretAgreement X25519 failed: {e}"))?;
derive_raw_secret(*secret_handle)?
};
if shared_secret.iter().all(|&b| b == 0) {
return Err("X25519 shared secret is zero (non-contributory)".into());
}
let mut secret = shared_secret;
secret.reverse();
out.clear();
out.extend_from_slice(&secret);
Ok(())
}
fn group(&self) -> NamedGroup {
NamedGroup::X25519
}
}
fn export_x25519_public_key(key_handle: BCRYPT_KEY_HANDLE) -> Result<Vec<u8>, String> {
unsafe {
let mut blob_size = 0u32;
WinCryptoError::from_ntstatus(BCryptExportKey(
key_handle,
None,
BCRYPT_ECCPUBLIC_BLOB,
None,
&mut blob_size,
0,
))
.map_err(|e| format!("BCryptExportKey X25519 size query failed: {e}"))?;
let mut blob = vec![0u8; blob_size as usize];
WinCryptoError::from_ntstatus(BCryptExportKey(
key_handle,
None,
BCRYPT_ECCPUBLIC_BLOB,
Some(&mut blob),
&mut blob_size,
0,
))
.map_err(|e| format!("BCryptExportKey X25519 failed: {e}"))?;
let header_size = 8;
let cb_key = u32::from_le_bytes(blob[4..8].try_into().unwrap()) as usize;
if blob.len() < header_size + cb_key {
return Err(format!("X25519 public key blob too small: {}", blob.len()));
}
Ok(blob[header_size..header_size + cb_key].to_vec())
}
}
fn import_x25519_public_key(
alg_handle: BCRYPT_ALG_HANDLE,
pub_key: &[u8],
) -> Result<Owned<BCRYPT_KEY_HANDLE>, String> {
let magic: u32 = 0x504B4345;
let cb_key: u32 = 32;
let mut blob = Vec::with_capacity(8 + 64);
blob.extend_from_slice(&magic.to_le_bytes());
blob.extend_from_slice(&cb_key.to_le_bytes());
blob.extend_from_slice(pub_key); blob.extend_from_slice(&[0u8; 32]);
unsafe {
let mut key_handle = Owned::new(BCRYPT_KEY_HANDLE::default());
WinCryptoError::from_ntstatus(BCryptImportKeyPair(
alg_handle,
None,
BCRYPT_ECCPUBLIC_BLOB,
&mut *key_handle,
&blob,
0,
))
.map_err(|e| format!("BCryptImportKeyPair X25519 failed: {e}"))?;
Ok(key_handle)
}
}
#[derive(Debug)]
pub(super) struct X25519Kx;
impl SupportedKxGroup for X25519Kx {
fn name(&self) -> NamedGroup {
NamedGroup::X25519
}
fn start_exchange(&self, buf: Buf) -> Result<Box<dyn ActiveKeyExchange>, String> {
Ok(Box::new(X25519KeyExchange::new(buf)?))
}
}
pub(super) static KX_GROUP_X25519: X25519Kx = X25519Kx;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_symmetric() {
let alice = X25519Kx.start_exchange(Buf::new()).unwrap();
let bob = X25519Kx.start_exchange(Buf::new()).unwrap();
let alice_pub = alice.pub_key().to_vec();
let bob_pub = bob.pub_key().to_vec();
let mut alice_secret = Buf::new();
let mut bob_secret = Buf::new();
alice.complete(&bob_pub, &mut alice_secret).unwrap();
bob.complete(&alice_pub, &mut bob_secret).unwrap();
assert_eq!(
&alice_secret[..],
&bob_secret[..],
"X25519 shared secrets must be identical regardless of which side completes"
);
assert_eq!(alice_secret.len(), 32);
}
#[test]
fn interop_with_dalek() {
use rand_core::OsRng;
use x25519_dalek::{EphemeralSecret, PublicKey};
let dalek_secret = EphemeralSecret::random_from_rng(&mut OsRng);
let dalek_pub = PublicKey::from(&dalek_secret);
let win_kx = X25519Kx.start_exchange(Buf::new()).unwrap();
let win_pub = win_kx.pub_key().to_vec();
let mut win_shared = Buf::new();
win_kx
.complete(dalek_pub.as_bytes(), &mut win_shared)
.unwrap();
let win_pub_bytes: [u8; 32] = win_pub.try_into().unwrap();
let dalek_peer = PublicKey::from(win_pub_bytes);
let dalek_shared = dalek_secret.diffie_hellman(&dalek_peer);
assert_eq!(
&win_shared[..],
dalek_shared.as_bytes(),
"wincrypto and x25519-dalek must produce the same shared secret"
);
}
}