diff --git a/Cargo.lock b/Cargo.lock index 78903f8..dbc7f0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,27 +17,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "asn1" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9795210620c0cb3f9a7ce4f882808c38e1ef7b347c90591dceae0886e031fb1" -dependencies = [ - "asn1_derive", - "itoa", -] - -[[package]] -name = "asn1_derive" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909e307f1cc32bb8bccbd98f446e6d1bf03fa30f7b53a4337da7181ad30fa11a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "aws-lc-rs" version = "1.17.0" @@ -62,9 +41,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "brotli" @@ -169,12 +148,6 @@ dependencies = [ "wasip2", ] -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - [[package]] name = "jobserver" version = "0.1.34" @@ -193,9 +166,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "log" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "num-conv" @@ -221,24 +194,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - [[package]] name = "r-efi" version = "5.3.0" @@ -248,7 +203,7 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rustls" version = "0.24.0-dev.0" -source = "git+https://github.com/rustls/rustls.git#22a546cfa1f24130514411be7c99295c093ec575" +source = "git+https://github.com/rustls/rustls.git#97f4f106922facf64a555209411b7045cc9c3a49" dependencies = [ "brotli", "brotli-decompressor", @@ -264,7 +219,7 @@ dependencies = [ [[package]] name = "rustls-aws-lc-rs" version = "0.1.0-dev.0" -source = "git+https://github.com/rustls/rustls.git#22a546cfa1f24130514411be7c99295c093ec575" +source = "git+https://github.com/rustls/rustls.git#97f4f106922facf64a555209411b7045cc9c3a49" dependencies = [ "aws-lc-rs", "rustls", @@ -277,7 +232,6 @@ dependencies = [ name = "rustls-cng" version = "0.8.0-dev" dependencies = [ - "asn1", "bitflags", "rustls", "rustls-aws-lc-rs", @@ -311,7 +265,7 @@ dependencies = [ [[package]] name = "rustls-util" version = "0.1.0" -source = "git+https://github.com/rustls/rustls.git#22a546cfa1f24130514411be7c99295c093ec575" +source = "git+https://github.com/rustls/rustls.git#97f4f106922facf64a555209411b7045cc9c3a49" dependencies = [ "log", "rustls", @@ -371,17 +325,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "time" version = "0.3.47" @@ -400,12 +343,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 282e1bd..cdb63ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Securit rustls-aws-lc-rs = { git = "https://github.com/rustls/rustls.git" } rustls-util = { git = "https://github.com/rustls/rustls.git" } rustls-native-certs = "0.8" -asn1 = "0.24" [features] default = ["rustls-log", "rustls-webpki"] diff --git a/src/signer.rs b/src/signer.rs index 3ae9759..7cb9163 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -8,73 +8,75 @@ use rustls::{ pki_types::SubjectPublicKeyInfoDer, }; use windows_sys::Win32::Security::Cryptography::{ - BCRYPT_SHA256_ALG_HANDLE, BCRYPT_SHA384_ALG_HANDLE, BCRYPT_SHA512_ALG_HANDLE, BCryptHash, + BCRYPT_SHA256_ALG_HANDLE, BCRYPT_SHA384_ALG_HANDLE, BCRYPT_SHA512_ALG_HANDLE, BCryptHash, CERT_ECC_SIGNATURE, + CRYPT_INTEGER_BLOB, CryptEncodeObjectEx, X509_ASN_ENCODING, X509_ECC_SIGNATURE, }; use crate::key::{AlgorithmGroup, NCryptKey, SignaturePadding}; -// Convert IEEE-P1363 signature format to DER encoding. -// The maximum signature size we support is 254 bytes. -fn p1363_to_der(data: &[u8]) -> Result, Error> { - const SEQUENCE_TAG: u8 = 0x30; - const INTEGER_TAG: u8 = 0x02; - - if data.is_empty() || data.len() > 254 || !data.len().is_multiple_of(2) { +// Convert an IEEE-P1363 (raw r || s) signature into DER encoding using the Win32 API. +// CryptEncodeObjectEx with X509_ECC_SIGNATURE produces the DER `SEQUENCE { INTEGER r, INTEGER s }`, +// taking care of minimal-length and sign-byte padding of the integers. +fn p1363_to_der(data: &mut [u8]) -> Result, Error> { + if data.is_empty() || !data.len().is_multiple_of(2) { return Err(Error::General("Invalid signature size".to_owned())); } - let (mut r, mut s) = data.split_at(data.len() / 2); - - while !r.is_empty() && r[0] == 0x0 { - r = &r[1..]; - } - - while !s.is_empty() && s[0] == 0x0 { - s = &s[1..]; - } - - if r.is_empty() || s.is_empty() { - return Err(Error::General("Invalid signature".to_owned())); - } - - let r_sign: &[u8] = if r[0] >= 0x80 { &[0] } else { &[] }; - let s_sign: &[u8] = if s[0] >= 0x80 { &[0] } else { &[] }; - - let v_length = 4 + r_sign.len() + s_sign.len() + r.len() + s.len(); - - let length_len = if v_length < 128 { - 1 - } else if v_length < 256 { - 2 - } else { - 3 + let (r, s) = data.split_at_mut(data.len() / 2); + + // CNG integer blobs are little-endian, so reverse the big-endian halves in place. + r.reverse(); + s.reverse(); + + let sig = CERT_ECC_SIGNATURE { + r: CRYPT_INTEGER_BLOB { + cbData: r.len() as u32, + pbData: r.as_mut_ptr(), + }, + s: CRYPT_INTEGER_BLOB { + cbData: s.len() as u32, + pbData: s.as_mut_ptr(), + }, }; + let sig_ptr = std::ptr::from_ref(&sig).cast(); + + unsafe { + // First call retrieves the required output buffer size. + let mut len = 0u32; + let status = CryptEncodeObjectEx( + X509_ASN_ENCODING, + X509_ECC_SIGNATURE, + sig_ptr, + 0, + std::ptr::null(), + std::ptr::null_mut(), + &mut len, + ); + if status == 0 { + return Err(Error::General( + "CryptEncodeObjectEx failed to size the signature".to_owned(), + )); + } - let mut der = Vec::with_capacity(1 + length_len + v_length); - - der.push(SEQUENCE_TAG); + let mut der = vec![0u8; len as usize]; + let status = CryptEncodeObjectEx( + X509_ASN_ENCODING, + X509_ECC_SIGNATURE, + sig_ptr, + 0, + std::ptr::null(), + der.as_mut_ptr().cast(), + &mut len, + ); + if status == 0 { + return Err(Error::General( + "CryptEncodeObjectEx failed to encode the signature".to_owned(), + )); + } - if v_length < 128 { - der.push(v_length as u8); - } else if v_length < 256 { - der.push(0x81); - der.push(v_length as u8); - } else { - der.push(0x82); - der.extend((v_length as u16).to_be_bytes()); + der.truncate(len as usize); + Ok(der) } - - der.push(INTEGER_TAG); - der.push((r.len() + r_sign.len()) as u8); - der.extend(r_sign); - der.extend(r); - - der.push(INTEGER_TAG); - der.push((s.len() + s_sign.len()) as u8); - der.extend(s_sign); - der.extend(s); - - Ok(der) } /// Custom implementation of `rustls` SigningKey trait @@ -186,14 +188,14 @@ impl CngSigner { impl Signer for CngSigner { fn sign(self: Box, message: &[u8]) -> Result, Error> { let (hash, padding) = self.hash(message)?; - let signature = self + let mut signature = self .key .sign(&hash, padding) .map_err(|e| Error::Other(OtherError::new(Arc::new(e))))?; if padding == SignaturePadding::None { - // For ECDSA keys Windows produces IEEE-P1363 signatures which must be converted to DER format - p1363_to_der(&signature) + // For ECC keys Windows produces IEEE-P1363 signatures which must be converted to DER format + p1363_to_der(&mut signature) } else { Ok(signature) } @@ -225,63 +227,90 @@ impl SigningKey for CngSigningKey { #[cfg(test)] mod tests { - use asn1::{BigUint, Sequence}; - - #[allow(clippy::result_large_err)] - fn validate_der(data: &[u8], r: &BigUint, s: &BigUint) { - let (parsed_r, parsed_s) = asn1::parse(data, |parser| { - parser.read_element::()?.parse(|parser| { - Ok::<_, asn1::ParseError>((parser.read_element::()?, parser.read_element::()?)) - }) - }) - .unwrap(); + use std::ptr; + + use windows_sys::Win32::Security::Cryptography::{ + CERT_ECC_SIGNATURE, CRYPT_INTEGER_BLOB, CryptDecodeObjectEx, X509_ASN_ENCODING, X509_ECC_SIGNATURE, + }; + + // Extract the big-endian magnitude of a CNG integer blob, which is stored in little-endian order. + unsafe fn blob_to_be(blob: &CRYPT_INTEGER_BLOB) -> Vec { + let le = unsafe { std::slice::from_raw_parts(blob.pbData, blob.cbData as usize) }; + let mut be = le.iter().rev().copied().collect::>(); + while be.len() > 1 && be[0] == 0 { + be.remove(0); + } + be + } + + // Decode a DER-encoded ECDSA signature via the Win32 API and return the (r, s) integers + // as big-endian magnitude byte vectors. + fn decode_der(data: &[u8]) -> (Vec, Vec) { + unsafe { + // First call retrieves the required output buffer size. + let mut len = 0u32; + let status = CryptDecodeObjectEx( + X509_ASN_ENCODING, + X509_ECC_SIGNATURE, + data.as_ptr(), + data.len() as u32, + 0, + ptr::null(), + ptr::null_mut(), + &mut len, + ); + assert_ne!(status, 0, "CryptDecodeObjectEx failed to size the output"); + + let mut buf = vec![0u8; len as usize]; + let status = CryptDecodeObjectEx( + X509_ASN_ENCODING, + X509_ECC_SIGNATURE, + data.as_ptr(), + data.len() as u32, + 0, + ptr::null(), + buf.as_mut_ptr() as *mut core::ffi::c_void, + &mut len, + ); + assert_ne!(status, 0, "CryptDecodeObjectEx failed to decode"); - assert_eq!(parsed_r, *r); - assert_eq!(parsed_s, *s); + let sig = &*(buf.as_ptr() as *const CERT_ECC_SIGNATURE); + (blob_to_be(&sig.r), blob_to_be(&sig.s)) + } + } + + fn validate_der(data: &[u8], r: &[u8], s: &[u8]) { + let (parsed_r, parsed_s) = decode_der(data); + assert_eq!(parsed_r, r); + assert_eq!(parsed_s, s); } #[test] fn test_p1363_to_der() { - let p1363 = [1, 2, 3, 4, 5, 6, 7, 8]; - let der = super::p1363_to_der(&p1363).unwrap(); - validate_der( - &der, - &BigUint::new(&[1, 2, 3, 4]).unwrap(), - &BigUint::new(&[5, 6, 7, 8]).unwrap(), - ); + let mut p1363 = [1, 2, 3, 4, 5, 6, 7, 8]; + let der = super::p1363_to_der(&mut p1363).unwrap(); + validate_der(&der, &[1, 2, 3, 4], &[5, 6, 7, 8]); } #[test] fn test_p1363_to_der_signed() { - let p1363 = [0x81, 2, 3, 4, 0x85, 6, 7, 8]; - let der = super::p1363_to_der(&p1363).unwrap(); - validate_der( - &der, - &BigUint::new(&[0, 0x81, 2, 3, 4]).unwrap(), - &BigUint::new(&[0, 0x85, 6, 7, 8]).unwrap(), - ); + let mut p1363 = [0x81, 2, 3, 4, 0x85, 6, 7, 8]; + let der = super::p1363_to_der(&mut p1363).unwrap(); + validate_der(&der, &[0x81, 2, 3, 4], &[0x85, 6, 7, 8]); } #[test] fn test_p1363_to_der_zeroes_stripped() { - let p1363 = [0, 1, 2, 3, 4, 0, 5, 6, 7, 8]; - let der = super::p1363_to_der(&p1363).unwrap(); - validate_der( - &der, - &BigUint::new(&[1, 2, 3, 4]).unwrap(), - &BigUint::new(&[5, 6, 7, 8]).unwrap(), - ); + let mut p1363 = [0, 1, 2, 3, 4, 0, 5, 6, 7, 8]; + let der = super::p1363_to_der(&mut p1363).unwrap(); + validate_der(&der, &[1, 2, 3, 4], &[5, 6, 7, 8]); } #[test] fn test_p1363_to_der_signed_zeroes_stripped() { - let p1363 = [0, 0x81, 2, 3, 4, 0, 0x85, 6, 7, 8]; - let der = super::p1363_to_der(&p1363).unwrap(); - validate_der( - &der, - &BigUint::new(&[0, 0x81, 2, 3, 4]).unwrap(), - &BigUint::new(&[0, 0x85, 6, 7, 8]).unwrap(), - ); + let mut p1363 = [0, 0x81, 2, 3, 4, 0, 0x85, 6, 7, 8]; + let der = super::p1363_to_der(&mut p1363).unwrap(); + validate_der(&der, &[0x81, 2, 3, 4], &[0x85, 6, 7, 8]); } #[test] @@ -289,8 +318,11 @@ mod tests { let r = (1..128).collect::>(); let s = (128..254).chain([0]).rev().collect::>(); - let p1363 = r.clone().into_iter().chain(s.clone()).collect::>(); - let der = super::p1363_to_der(&p1363).unwrap(); - validate_der(&der, &BigUint::new(&r).unwrap(), &BigUint::new(&s).unwrap()); + let mut p1363 = r.clone().into_iter().chain(s.clone()).collect::>(); + let der = super::p1363_to_der(&mut p1363).unwrap(); + + // The decoded magnitude has the padding zero stripped. + let expected_s = (128..254).rev().collect::>(); + validate_der(&der, &r, &expected_s); } }