Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements for rustls-cng crate #6

Merged
merged 8 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ no-default-features = true

[dependencies]
rustls = { version = "0.23", default-features = false, features = ["std"] }
sha2 = "0.10"
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_Security_Cryptography"] }
aws-lc-rs = { version = "1", optional = true }
ancwrd1 marked this conversation as resolved.
Show resolved Hide resolved

[dev-dependencies]
anyhow = "1"
Expand All @@ -29,7 +27,6 @@ rustls-pki-types = "1"
default = ["logging", "tls12", "aws-lc-rs"]
aws-lc-rs = ["rustls/aws_lc_rs"]
early-data = []
aws-lc-bindgen = ["aws-lc-rs/bindgen"]
fips = ["rustls/fips"]
logging = ["rustls/logging"]
ring = ["rustls/ring"]
Expand Down
33 changes: 28 additions & 5 deletions src/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use windows_sys::Win32::Security::Cryptography::*;

use crate::{error::CngError, key::NCryptKey, Result};

const HCCE_LOCAL_MACHINE: HCERTCHAINENGINE = 0x1 as HCERTCHAINENGINE;

#[derive(Debug)]
enum InnerContext {
Owned(*const CERT_CONTEXT),
Expand Down Expand Up @@ -87,18 +89,33 @@ impl CertContext {
}
}

/// Return DER-encoded X.509 certificate chain
/// Return DER-encoded X.509 certificate chain.
// (1) exclude the root. (2) check leaf cert to determine to use HKLM engine or HKCU engine
pub fn as_chain_der(&self) -> Result<Vec<Vec<u8>>> {
unsafe {
let param = CERT_CHAIN_PARA {
cbSize: mem::size_of::<CERT_CHAIN_PARA>() as u32,
RequestedUsage: std::mem::zeroed(),
};

let mut context: *mut CERT_CHAIN_CONTEXT = ptr::null_mut();
let mut dw_access_state_flags: u32 = 0;
let mut cb_data = mem::size_of::<u32>() as u32;

let chain_engine = if CertGetCertificateContextProperty(
self.inner(),
CERT_ACCESS_STATE_PROP_ID,
&mut dw_access_state_flags as *mut _ as *mut _,
&mut cb_data as *mut _,
) != 0
&& (dw_access_state_flags & CERT_ACCESS_STATE_LM_SYSTEM_STORE_FLAG) != 0
{
HCCE_LOCAL_MACHINE
} else {
HCERTCHAINENGINE::default()
};

let result = CertGetCertificateChain(
HCERTCHAINENGINE::default(),
chain_engine,
self.inner(),
ptr::null(),
ptr::null_mut(),
Expand All @@ -117,14 +134,20 @@ impl CertContext {
(*chain_ptr).rgpElement,
(*chain_ptr).cElement as usize,
);
for element in elements {

for (index, element) in elements.iter().enumerate() {
if index != 0 {
if 0 != ((**element).TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) {
break;
}
}

let context = (**element).pCertContext;
chain.push(Self::new_borrowed(context).as_der().to_vec());
}
}

CertFreeCertificateChain(&*context);

Ok(chain)
} else {
Err(CngError::from_win32_error())
Expand Down
87 changes: 53 additions & 34 deletions src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ use rustls::{
sign::{Signer, SigningKey},
Error, OtherError, SignatureAlgorithm, SignatureScheme,
};
use sha2::digest::Digest;
MS-megliu marked this conversation as resolved.
Show resolved Hide resolved

use crate::key::{AlgorithmGroup, NCryptKey, SignaturePadding};

use windows_sys::Win32::Security::Cryptography::BCryptHash;
use windows_sys::Win32::Security::Cryptography::{
BCRYPT_SHA256_ALG_HANDLE, BCRYPT_SHA384_ALG_HANDLE, BCRYPT_SHA512_ALG_HANDLE,
};

// Convert IEEE-P1363 signature format to DER encoding.
// We assume the length of the r and s parts is less than 256 bytes.
fn p1363_to_der(data: &[u8]) -> Vec<u8> {
Expand Down Expand Up @@ -100,42 +104,57 @@ struct CngSigner {
}

impl CngSigner {
// hash function using BCryptHash function which uses FIPS certified SymCrypt
fn hash(&self, message: &[u8]) -> Result<(Vec<u8>, SignaturePadding), Error> {
let (hash, padding) = match self.scheme {
SignatureScheme::RSA_PKCS1_SHA256 => (
sha2::Sha256::digest(message).to_vec(),
SignaturePadding::Pkcs1,
),
SignatureScheme::RSA_PKCS1_SHA384 => (
sha2::Sha384::digest(message).to_vec(),
SignaturePadding::Pkcs1,
),
SignatureScheme::RSA_PKCS1_SHA512 => (
sha2::Sha512::digest(message).to_vec(),
SignaturePadding::Pkcs1,
),
SignatureScheme::RSA_PSS_SHA256 => (
sha2::Sha256::digest(message).to_vec(),
SignaturePadding::Pss,
),
SignatureScheme::RSA_PSS_SHA384 => (
sha2::Sha384::digest(message).to_vec(),
SignaturePadding::Pss,
),
SignatureScheme::RSA_PSS_SHA512 => (
sha2::Sha512::digest(message).to_vec(),
SignaturePadding::Pss,
),
SignatureScheme::ECDSA_NISTP256_SHA256 => (
sha2::Sha256::digest(message).to_vec(),
SignaturePadding::None,
),
SignatureScheme::ECDSA_NISTP384_SHA384 => (
sha2::Sha384::digest(message).to_vec(),
SignaturePadding::None,
),
let (alg, padding) = match self.scheme {
SignatureScheme::RSA_PKCS1_SHA256 => {
(BCRYPT_SHA256_ALG_HANDLE, SignaturePadding::Pkcs1)
}
SignatureScheme::RSA_PKCS1_SHA384 => {
(BCRYPT_SHA384_ALG_HANDLE, SignaturePadding::Pkcs1)
}
SignatureScheme::RSA_PKCS1_SHA512 => {
(BCRYPT_SHA512_ALG_HANDLE, SignaturePadding::Pkcs1)
}
SignatureScheme::RSA_PSS_SHA256 => (BCRYPT_SHA256_ALG_HANDLE, SignaturePadding::Pss),
SignatureScheme::RSA_PSS_SHA384 => (BCRYPT_SHA384_ALG_HANDLE, SignaturePadding::Pss),
SignatureScheme::RSA_PSS_SHA512 => (BCRYPT_SHA512_ALG_HANDLE, SignaturePadding::Pss),
MS-megliu marked this conversation as resolved.
Show resolved Hide resolved
SignatureScheme::ECDSA_NISTP256_SHA256 => {
(BCRYPT_SHA256_ALG_HANDLE, SignaturePadding::None)
}
SignatureScheme::ECDSA_NISTP384_SHA384 => {
(BCRYPT_SHA384_ALG_HANDLE, SignaturePadding::None)
}
_ => return Err(Error::General("Unsupported signature scheme".to_owned())),
};

let hash_len = match alg {
BCRYPT_SHA256_ALG_HANDLE => 32,
BCRYPT_SHA384_ALG_HANDLE => 48,
BCRYPT_SHA512_ALG_HANDLE => 64,
_ => return Err(Error::General("Unsupported hash algorithm!".to_owned())),
};

let mut hash = vec![0u8; hash_len];

unsafe {
let status = BCryptHash(
alg as *mut core::ffi::c_void,
std::ptr::null_mut(), // pbSecret
0, // cbSecret
message.as_ptr() as *mut u8,
message.len() as u32,
hash.as_mut_ptr(),
hash_len as u32,
);

if status != 0 {
return Err(Error::General(format!(
"BCryptHash failed with status: 0x{:X}",
status
)));
}
}
Ok((hash, padding))
}
}
Expand Down
59 changes: 59 additions & 0 deletions src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ impl CertStore {
unsafe { self.do_find(CERT_FIND_HASH, &hash_blob as *const _ as _) }
}

// On later OS releases, we added CERT_FIND_SHA256_HASH.
// However, rustls-cng could be installed on earlier OS release where this FIND_SHA256 isn't present.
// But the CERT_SHA256_HASH_PROP_ID is present.
// So will need to add a new internal find function that gets and compares the SHA256 property.
// Also, since SHA1 is being deprecated, Windows components should not use.
// Therefore, the need to find via SHA256 instead of SHA1.

/// Find list of certificates matching the SHA256 hash
pub fn find_by_sha256<D>(&self, hash: D) -> Result<Vec<CertContext>>
where
D: AsRef<[u8]>,
{
let hash_blob = CRYPT_INTEGER_BLOB {
cbData: hash.as_ref().len() as u32,
pbData: hash.as_ref().as_ptr() as _,
};
unsafe { self.do_find_by_sha256_property(&hash_blob as *const _ as _) }
}

/// Find list of certificates matching the key identifier
pub fn find_by_key_id<D>(&self, key_id: D) -> Result<Vec<CertContext>>
where
Expand Down Expand Up @@ -180,6 +199,46 @@ impl CertStore {
Ok(certs)
}

unsafe fn do_find_by_sha256_property(
&self,
find_param: *const c_void,
) -> Result<Vec<CertContext>> {
let mut certs = Vec::new();
let mut cert: *mut CERT_CONTEXT = ptr::null_mut();
let hash_blob = &*(find_param as *const CRYPT_INTEGER_BLOB);
let sha256_hash = std::slice::from_raw_parts(hash_blob.pbData, hash_blob.cbData as usize);
loop {
cert = CertFindCertificateInStore(
self.0,
MY_ENCODING_TYPE,
0,
CERT_FIND_ANY,
find_param,
cert,
);
if cert.is_null() {
break;
} else {
let mut prop_data = [0u8; 32];
let mut prop_data_len = prop_data.len() as u32;

if CertGetCertificateContextProperty(
cert,
CERT_SHA256_HASH_PROP_ID,
prop_data.as_mut_ptr() as *mut c_void,
&mut prop_data_len,
) != 0
{
if prop_data[..prop_data_len as usize] == sha256_hash[..] {
let cert = CertDuplicateCertificateContext(cert);
certs.push(CertContext::new_owned(cert))
}
}
}
}
Ok(certs)
}

fn find_by_str(&self, pattern: &str, flags: CERT_FIND_FLAGS) -> Result<Vec<CertContext>> {
let u16pattern = utf16z!(pattern);
unsafe { self.do_find(flags, u16pattern.as_ptr() as _) }
Expand Down
14 changes: 14 additions & 0 deletions tests/test_find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ fn test_find_by_hash() {
assert!(context.is_some());
}

#[test]
fn test_find_by_hash256() {
let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store");

let sha256 = [
0xC9, 0x7C, 0xD6, 0xA1, 0x3F, 0xF6, 0xBD, 0xF6, 0xD4, 0xE2, 0xFB, 0x0E, 0xCD, 0x74, 0x2F,
0x14, 0x30, 0x53, 0xB0, 0x89, 0xFA, 0x4D, 0xA5, 0xE5, 0x8B, 0xA3, 0x9F, 0x72, 0xED, 0x2F,
0x9F, 0xB6,
];

let context = store.find_by_sha256(sha256).unwrap().into_iter().next();
assert!(context.is_some());
}

#[test]
fn test_find_all() {
let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store");
Expand Down