use std::str;
use dimpl::crypto::Buf;
use dimpl::crypto::{HashAlgorithm, KeyProvider};
use dimpl::crypto::{SignatureAlgorithm, SignatureVerifier, SigningKey as SigningKeyTrait};
use windows::Win32::Security::Cryptography::BCRYPT_ECCPRIVATE_BLOB;
use windows::Win32::Security::Cryptography::BCRYPT_ECCPUBLIC_BLOB;
use windows::Win32::Security::Cryptography::BCRYPT_ECDSA_P256_ALG_HANDLE;
use windows::Win32::Security::Cryptography::BCRYPT_ECDSA_P384_ALG_HANDLE;
use windows::Win32::Security::Cryptography::BCRYPT_FLAGS;
use windows::Win32::Security::Cryptography::BCRYPT_KEY_HANDLE;
use windows::Win32::Security::Cryptography::BCRYPT_SHA256_ALG_HANDLE;
use windows::Win32::Security::Cryptography::BCRYPT_SHA384_ALG_HANDLE;
use windows::Win32::Security::Cryptography::BCryptHash;
use windows::Win32::Security::Cryptography::BCryptImportKeyPair;
use windows::Win32::Security::Cryptography::BCryptSignHash;
use windows::Win32::Security::Cryptography::BCryptVerifySignature;
use windows::core::Owned;
use crate::WinCryptoError;
const OID_EC_PUBLIC_KEY: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01];
const OID_PRIME256V1: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];
const OID_SECP384R1: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x22];
#[derive(Clone, Copy, Debug)]
enum EcCurve {
P256,
P384,
}
struct EcdsaSigningKey {
key_handle: Owned<BCRYPT_KEY_HANDLE>,
curve: EcCurve,
}
unsafe impl Send for EcdsaSigningKey {}
unsafe impl Sync for EcdsaSigningKey {}
impl std::fmt::Debug for EcdsaSigningKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EcdsaSigningKey")
.field("curve", &self.curve)
.finish_non_exhaustive()
}
}
impl SigningKeyTrait for EcdsaSigningKey {
fn sign(&mut self, data: &[u8], hash_alg: HashAlgorithm, out: &mut Buf) -> Result<(), String> {
let key_hash = self.hash_algorithm();
if hash_alg != key_hash {
return Err(format!(
"wincrypto ECDSA key is locked to {:?} but {:?} was requested",
key_hash, hash_alg
));
}
let (bcrypt_hash_alg, hash_len) = match self.curve {
EcCurve::P256 => (BCRYPT_SHA256_ALG_HANDLE, 32usize),
EcCurve::P384 => (BCRYPT_SHA384_ALG_HANDLE, 48usize),
};
let mut hash = vec![0u8; hash_len];
unsafe {
WinCryptoError::from_ntstatus(BCryptHash(bcrypt_hash_alg, None, data, &mut hash))
.map_err(|e| format!("Hash failed: {e}"))?;
}
let sig_size = match self.curve {
EcCurve::P256 => 64usize, EcCurve::P384 => 96usize, };
let mut raw_sig = vec![0u8; sig_size];
let mut sig_len = 0u32;
unsafe {
WinCryptoError::from_ntstatus(BCryptSignHash(
*self.key_handle,
None,
&hash,
Some(&mut raw_sig),
&mut sig_len,
BCRYPT_FLAGS(0),
))
.map_err(|e| format!("BCryptSignHash failed: {e}"))?;
}
raw_sig.truncate(sig_len as usize);
let der_sig = raw_rs_to_der(&raw_sig)?;
out.clear();
out.extend_from_slice(&der_sig);
Ok(())
}
fn algorithm(&self) -> SignatureAlgorithm {
SignatureAlgorithm::ECDSA
}
fn hash_algorithm(&self) -> HashAlgorithm {
match self.curve {
EcCurve::P256 => HashAlgorithm::SHA256,
EcCurve::P384 => HashAlgorithm::SHA384,
}
}
fn supported_hash_algorithms(&self) -> &[HashAlgorithm] {
match self.curve {
EcCurve::P256 => &[HashAlgorithm::SHA256],
EcCurve::P384 => &[HashAlgorithm::SHA384],
}
}
}
#[derive(Debug)]
pub(super) struct WinCngKeyProvider;
impl KeyProvider for WinCngKeyProvider {
fn load_private_key(&self, key_der: &[u8]) -> Result<Box<dyn SigningKeyTrait>, String> {
if let Ok(pem_str) = str::from_utf8(key_der) {
if pem_str.contains("-----BEGIN") {
if let Ok(decoded) = decode_pem(pem_str) {
return self.load_private_key(&decoded);
}
}
}
let sec1_der = try_unwrap_pkcs8(key_der).unwrap_or_else(|| key_der.to_vec());
let (curve_hint, private_key_bytes, public_key_opt) = parse_ec_private_key(&sec1_der)?;
let curve = if let Some(c) = curve_hint {
c
} else if private_key_bytes.len() == 32 {
EcCurve::P256
} else if private_key_bytes.len() == 48 {
EcCurve::P384
} else {
return Err(format!(
"Could not determine EC curve from key length: {}",
private_key_bytes.len()
));
};
let public_key_bytes = public_key_opt
.ok_or_else(|| "EC private key missing public key component".to_string())?;
let coord_size = match curve {
EcCurve::P256 => 32usize,
EcCurve::P384 => 48usize,
};
let expected_pub_len = 1 + 2 * coord_size;
if public_key_bytes.len() != expected_pub_len || public_key_bytes[0] != 0x04 {
return Err(format!(
"Unexpected public key length: {} (expected {})",
public_key_bytes.len(),
expected_pub_len
));
}
let x = &public_key_bytes[1..1 + coord_size];
let y = &public_key_bytes[1 + coord_size..];
let d = &private_key_bytes;
let (alg_handle, magic) = match curve {
EcCurve::P256 => (
BCRYPT_ECDSA_P256_ALG_HANDLE,
0x32534345u32, ),
EcCurve::P384 => (
BCRYPT_ECDSA_P384_ALG_HANDLE,
0x34534345u32, ),
};
let header_size = 8;
let mut blob = Vec::with_capacity(header_size + 3 * coord_size);
blob.extend_from_slice(&magic.to_le_bytes());
blob.extend_from_slice(&(coord_size as u32).to_le_bytes());
pad_to(&mut blob, x, coord_size);
pad_to(&mut blob, y, coord_size);
pad_to(&mut blob, d, coord_size);
let key_handle = unsafe {
let mut key_handle = Owned::new(BCRYPT_KEY_HANDLE::default());
WinCryptoError::from_ntstatus(BCryptImportKeyPair(
alg_handle,
None,
BCRYPT_ECCPRIVATE_BLOB,
&mut *key_handle,
&blob,
0,
))
.map_err(|e| format!("BCryptImportKeyPair failed: {e}"))?;
key_handle
};
Ok(Box::new(EcdsaSigningKey { key_handle, curve }))
}
}
fn pad_to(dst: &mut Vec<u8>, src: &[u8], size: usize) {
if src.len() >= size {
dst.extend_from_slice(&src[src.len() - size..]);
} else {
let pad = size - src.len();
dst.extend(std::iter::repeat_n(0u8, pad));
dst.extend_from_slice(src);
}
}
#[derive(Debug)]
pub(super) struct WinCngSignatureVerifier;
impl SignatureVerifier for WinCngSignatureVerifier {
fn verify_signature(
&self,
cert_der: &[u8],
data: &[u8],
signature: &[u8],
hash_alg: HashAlgorithm,
sig_alg: SignatureAlgorithm,
) -> Result<(), String> {
if sig_alg != SignatureAlgorithm::ECDSA {
return Err(format!("Unsupported signature algorithm: {sig_alg:?}"));
}
let (alg_oid, pubkey_bytes) = extract_spki_from_cert(cert_der)?;
if alg_oid != OID_EC_PUBLIC_KEY {
return Err("Unsupported public key algorithm OID".into());
}
if pubkey_bytes.is_empty() || pubkey_bytes[0] != 0x04 {
return Err("EC public key is not in uncompressed format (0x04)".into());
}
let (alg_handle, coord_size, magic) = if pubkey_bytes.len() == 65 {
(
BCRYPT_ECDSA_P256_ALG_HANDLE,
32usize,
0x31534345u32, )
} else if pubkey_bytes.len() == 97 {
(
BCRYPT_ECDSA_P384_ALG_HANDLE,
48usize,
0x33534345u32, )
} else {
return Err(format!(
"Unsupported EC public key size: {} bytes",
pubkey_bytes.len()
));
};
let (hash_bcrypt_alg, hash_len) = match hash_alg {
HashAlgorithm::SHA256 => (BCRYPT_SHA256_ALG_HANDLE, 32usize),
HashAlgorithm::SHA384 => (BCRYPT_SHA384_ALG_HANDLE, 48usize),
_ => {
return Err(format!(
"Unsupported hash algorithm for ECDSA: {hash_alg:?}"
));
}
};
let mut hash = vec![0u8; hash_len];
unsafe {
WinCryptoError::from_ntstatus(BCryptHash(hash_bcrypt_alg, None, data, &mut hash))
.map_err(|e| format!("Hash failed: {e}"))?;
}
let header_size = 8;
let mut blob = Vec::with_capacity(header_size + 2 * coord_size);
blob.extend_from_slice(&magic.to_le_bytes());
blob.extend_from_slice(&(coord_size as u32).to_le_bytes());
blob.extend_from_slice(&pubkey_bytes[1..]);
let key_handle = 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!("Import public key failed: {e}"))?;
key_handle
};
let raw_sig = der_to_raw_rs(signature, coord_size)?;
unsafe {
WinCryptoError::from_ntstatus(BCryptVerifySignature(
*key_handle,
None,
&hash,
&raw_sig,
BCRYPT_FLAGS(0),
))
.map_err(|e| format!("ECDSA signature verification failed: {e}"))?;
}
Ok(())
}
}
pub(crate) fn raw_rs_to_der(raw: &[u8]) -> Result<Vec<u8>, String> {
if raw.len() % 2 != 0 {
return Err("Raw signature length must be even".into());
}
let half = raw.len() / 2;
let r = &raw[..half];
let s = &raw[half..];
let r_der = encode_der_integer(r);
let s_der = encode_der_integer(s);
let mut content = Vec::new();
content.extend_from_slice(&r_der);
content.extend_from_slice(&s_der);
let mut result = vec![0x30]; encode_der_length(content.len(), &mut result);
result.extend_from_slice(&content);
Ok(result)
}
fn der_to_raw_rs(der: &[u8], coord_size: usize) -> Result<Vec<u8>, String> {
let (tag, seq_content, _) = parse_tlv(der)?;
if tag != 0x30 {
return Err("Invalid DER signature: not a SEQUENCE".into());
}
let (tag, r_bytes, rest) = parse_tlv(seq_content)?;
if tag != 0x02 {
return Err("Invalid DER signature: r not INTEGER".into());
}
let (tag, s_bytes, _) = parse_tlv(rest)?;
if tag != 0x02 {
return Err("Invalid DER signature: s not INTEGER".into());
}
let mut raw = vec![0u8; coord_size * 2];
copy_integer_to_fixed(&mut raw[..coord_size], r_bytes)?;
copy_integer_to_fixed(&mut raw[coord_size..], s_bytes)?;
Ok(raw)
}
fn copy_integer_to_fixed(dst: &mut [u8], src: &[u8]) -> Result<(), String> {
let mut start = 0;
while start < src.len() && src[start] == 0 && (src.len() - start) > dst.len() {
start += 1;
}
let meaningful = &src[start..];
if meaningful.len() <= dst.len() {
let offset = dst.len() - meaningful.len();
dst[offset..].copy_from_slice(meaningful);
Ok(())
} else {
Err(format!(
"DER integer too large: {} bytes for {}-byte field",
meaningful.len(),
dst.len()
))
}
}
fn encode_der_integer(value: &[u8]) -> Vec<u8> {
if value.is_empty() {
return vec![0x02, 0x01, 0x00];
}
let mut start = 0;
while start < value.len() - 1 && value[start] == 0 {
start += 1;
}
let trimmed = &value[start..];
let mut result = vec![0x02]; if trimmed[0] & 0x80 != 0 {
encode_der_length(trimmed.len() + 1, &mut result);
result.push(0x00);
} else {
encode_der_length(trimmed.len(), &mut result);
}
result.extend_from_slice(trimmed);
result
}
fn encode_der_length(len: usize, out: &mut Vec<u8>) {
if len < 128 {
out.push(len as u8);
} else if len < 256 {
out.push(0x81);
out.push(len as u8);
} else {
out.push(0x82);
out.push((len >> 8) as u8);
out.push(len as u8);
}
}
fn parse_der_length(data: &[u8]) -> Result<(usize, &[u8]), String> {
if data.is_empty() {
return Err("DER: unexpected end of data".into());
}
if data[0] < 128 {
Ok((data[0] as usize, &data[1..]))
} else if data[0] == 0x81 {
if data.len() < 2 {
return Err("DER: truncated length".into());
}
Ok((data[1] as usize, &data[2..]))
} else if data[0] == 0x82 {
if data.len() < 3 {
return Err("DER: truncated length".into());
}
Ok((((data[1] as usize) << 8) | (data[2] as usize), &data[3..]))
} else {
Err("DER: unsupported length encoding".into())
}
}
fn parse_tlv(data: &[u8]) -> Result<(u8, &[u8], &[u8]), String> {
if data.is_empty() {
return Err("DER: unexpected end of data".into());
}
let tag = data[0];
let (len, after_len) = parse_der_length(&data[1..])?;
if after_len.len() < len {
return Err(format!(
"DER: truncated (need {len}, have {})",
after_len.len()
));
}
Ok((tag, &after_len[..len], &after_len[len..]))
}
fn decode_pem(pem: &str) -> Result<Vec<u8>, String> {
use windows::Win32::Security::Cryptography::{CRYPT_STRING_BASE64HEADER, CryptStringToBinaryA};
let pem_bytes = pem.as_bytes();
let mut der_len = 0u32;
unsafe {
CryptStringToBinaryA(
pem_bytes,
CRYPT_STRING_BASE64HEADER,
None,
&mut der_len,
None,
None,
)
.map_err(|e| format!("PEM decode (size query): {e}"))?;
}
let mut der = vec![0u8; der_len as usize];
unsafe {
CryptStringToBinaryA(
pem_bytes,
CRYPT_STRING_BASE64HEADER,
Some(der.as_mut_ptr()),
&mut der_len,
None,
None,
)
.map_err(|e| format!("PEM decode: {e}"))?;
}
der.truncate(der_len as usize);
Ok(der)
}
fn try_unwrap_pkcs8(der: &[u8]) -> Option<Vec<u8>> {
let (tag, seq_content, _) = parse_tlv(der).ok()?;
if tag != 0x30 {
return None;
}
let (tag, _version, rest) = parse_tlv(seq_content).ok()?;
if tag != 0x02 {
return None;
}
let (tag, alg_content, rest) = parse_tlv(rest).ok()?;
if tag != 0x30 {
return None;
}
let (tag, alg_oid, _) = parse_tlv(alg_content).ok()?;
if tag != 0x06 || alg_oid != OID_EC_PUBLIC_KEY {
return None;
}
let (tag, private_key, _) = parse_tlv(rest).ok()?;
if tag != 0x04 {
return None;
}
Some(private_key.to_vec())
}
#[allow(clippy::type_complexity)]
fn parse_ec_private_key(der: &[u8]) -> Result<(Option<EcCurve>, Vec<u8>, Option<Vec<u8>>), String> {
let (tag, seq_content, _) = parse_tlv(der)?;
if tag != 0x30 {
return Err("ECPrivateKey: not a SEQUENCE".into());
}
let (tag, _version, rest) = parse_tlv(seq_content)?;
if tag != 0x02 {
return Err("ECPrivateKey: version not INTEGER".into());
}
let (tag, private_key, mut rest) = parse_tlv(rest)?;
if tag != 0x04 {
return Err("ECPrivateKey: privateKey not OCTET STRING".into());
}
let mut curve = None;
let mut public_key = None;
while !rest.is_empty() {
let (tag, content, remaining) = parse_tlv(rest)?;
match tag {
0xA0 => {
let (tag, oid_bytes, _) = parse_tlv(content)?;
if tag == 0x06 {
curve = oid_to_curve(oid_bytes);
}
}
0xA1 => {
let (tag, bs_content, _) = parse_tlv(content)?;
if tag == 0x03 && bs_content.len() > 1 {
if bs_content[0] != 0 {
return Err("ECPrivateKey: BIT STRING has non-zero unused bits".into());
}
public_key = Some(bs_content[1..].to_vec());
}
}
_ => {} }
rest = remaining;
}
Ok((curve, private_key.to_vec(), public_key))
}
fn oid_to_curve(oid_bytes: &[u8]) -> Option<EcCurve> {
if oid_bytes == OID_PRIME256V1 {
Some(EcCurve::P256)
} else if oid_bytes == OID_SECP384R1 {
Some(EcCurve::P384)
} else {
None
}
}
fn extract_spki_from_cert(cert_der: &[u8]) -> Result<(&[u8], &[u8]), String> {
let (tag, cert_content, _) = parse_tlv(cert_der)?;
if tag != 0x30 {
return Err("Certificate: not a SEQUENCE".into());
}
let (tag, tbs_content, _) = parse_tlv(cert_content)?;
if tag != 0x30 {
return Err("TBSCertificate: not a SEQUENCE".into());
}
let mut pos = tbs_content;
if !pos.is_empty() && pos[0] == 0xA0 {
let (_, _, rest) = parse_tlv(pos)?;
pos = rest;
}
let (tag, _, rest) = parse_tlv(pos)?;
if tag != 0x02 {
return Err("TBS: serialNumber not INTEGER".into());
}
pos = rest;
let (tag, _, rest) = parse_tlv(pos)?;
if tag != 0x30 {
return Err("TBS: signature not SEQUENCE".into());
}
pos = rest;
let (tag, _, rest) = parse_tlv(pos)?;
if tag != 0x30 {
return Err("TBS: issuer not SEQUENCE".into());
}
pos = rest;
let (tag, _, rest) = parse_tlv(pos)?;
if tag != 0x30 {
return Err("TBS: validity not SEQUENCE".into());
}
pos = rest;
let (tag, _, rest) = parse_tlv(pos)?;
if tag != 0x30 {
return Err("TBS: subject not SEQUENCE".into());
}
pos = rest;
let (tag, spki_content, _) = parse_tlv(pos)?;
if tag != 0x30 {
return Err("SPKI: not a SEQUENCE".into());
}
let (tag, alg_content, rest) = parse_tlv(spki_content)?;
if tag != 0x30 {
return Err("SPKI algorithm: not a SEQUENCE".into());
}
let (tag, oid_bytes, _) = parse_tlv(alg_content)?;
if tag != 0x06 {
return Err("SPKI: algorithm OID not found".into());
}
let (tag, bs_content, _) = parse_tlv(rest)?;
if tag != 0x03 {
return Err("SPKI: publicKey not BIT STRING".into());
}
if bs_content.len() < 2 {
return Err("SPKI: BIT STRING too short".into());
}
if bs_content[0] != 0 {
return Err("SPKI: BIT STRING has non-zero unused bits".into());
}
Ok((oid_bytes, &bs_content[1..]))
}
pub(super) static KEY_PROVIDER: WinCngKeyProvider = WinCngKeyProvider;
pub(super) static SIGNATURE_VERIFIER: WinCngSignatureVerifier = WinCngSignatureVerifier;