From 40872c67ab29208517315cbaa13e2f906e27fa33 Mon Sep 17 00:00:00 2001 From: Dmitry Pankratov Date: Fri, 26 Dec 2025 15:37:11 +0100 Subject: [PATCH 1/2] Added Pkcs12Flags to control how to import chains from the PKCS#12 --- Cargo.lock | 7 ++++--- Cargo.toml | 1 + examples/client.rs | 23 ++++++++++++++--------- examples/server.rs | 7 ++++++- src/store.rs | 29 ++++++++++++++++++++++------- tests/test_client_server.rs | 29 +++++++++++++++++++---------- tests/test_find.rs | 23 +++++++++++++++-------- tests/test_sign.rs | 8 ++++++-- 8 files changed, 87 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68c124c..881b2b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,9 +246,9 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cmake" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -506,7 +506,7 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustls" version = "0.24.0-dev.0" -source = "git+https://github.com/rustls/rustls.git#76228b82657f1d5f3f7a7b8d01a7099a516919a1" +source = "git+https://github.com/rustls/rustls.git#66319ca5165636e19e9c8cf4a9212f1076c4a8bd" dependencies = [ "aws-lc-rs", "brotli", @@ -525,6 +525,7 @@ name = "rustls-cng" version = "0.7.0-dev" dependencies = [ "anyhow", + "bitflags", "clap", "rustls", "rustls-pki-types", diff --git a/Cargo.toml b/Cargo.toml index 02d9f1d..7ebb320 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ no-default-features = true rustls = { git = "https://github.com/rustls/rustls.git", default-features = false, features = ["std"] } rustls-pki-types = "1" time = { version = "0.3.43", default-features = false, optional = true } +bitflags = "2" windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Security_Cryptography"] } [dev-dependencies] diff --git a/examples/client.rs b/examples/client.rs index e2f6321..b3cbb45 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -1,3 +1,11 @@ +use std::{ + hash::Hasher, + io::{Read, Write}, + net::{Shutdown, TcpStream}, + path::PathBuf, + sync::Arc, +}; + use clap::Parser; use rustls::{ ClientConfig, ClientConnection, RootCertStore, Stream, @@ -6,17 +14,10 @@ use rustls::{ enums::CertificateType, }; use rustls_pki_types::{CertificateDer, ServerName}; -use std::hash::Hasher; -use std::{ - io::{Read, Write}, - net::{Shutdown, TcpStream}, - path::PathBuf, - sync::Arc, -}; use rustls_cng::{ signer::CngSigningKey, - store::{CertStore, CertStoreType}, + store::{CertStore, CertStoreType, Pkcs12Flags}, }; const PORT: u16 = 8000; @@ -110,7 +111,11 @@ fn main() -> anyhow::Result<()> { let store = if let Some(ref keystore) = params.keystore { let data = std::fs::read(keystore)?; - CertStore::from_pkcs12(&data, params.password.as_deref().unwrap_or_default())? + CertStore::from_pkcs12( + &data, + params.password.as_deref().unwrap_or_default(), + Pkcs12Flags::default(), + )? } else { CertStore::open(CertStoreType::CurrentUser, "my")? }; diff --git a/examples/server.rs b/examples/server.rs index 1ea7966..7a07da9 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -11,6 +11,7 @@ use rustls::{ crypto::{Credentials, Identity, SelectedCredential, aws_lc_rs}, server::{ClientHello, ServerCredentialResolver, WebPkiClientVerifier}, }; +use rustls_cng::store::Pkcs12Flags; use rustls_cng::{ signer::CngSigningKey, store::{CertStore, CertStoreType}, @@ -132,7 +133,11 @@ fn main() -> anyhow::Result<()> { let store = if let Some(ref keystore) = params.keystore { let data = std::fs::read(keystore)?; - CertStore::from_pkcs12(&data, params.password.as_deref().unwrap_or_default())? + CertStore::from_pkcs12( + &data, + params.password.as_deref().unwrap_or_default(), + Pkcs12Flags::default(), + )? } else { CertStore::open(CertStoreType::CurrentUser, "my")? }; diff --git a/src/store.rs b/src/store.rs index 0475c28..ea2f1cf 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,7 +1,7 @@ //! Windows certificate store wrapper +use bitflags::bitflags; use std::{os::raw::c_void, ptr}; - use windows_sys::Win32::Security::Cryptography::*; use crate::{Result, cert::CertContext, error::CngError}; @@ -22,6 +22,24 @@ pub enum CertStoreType { CurrentService, } +bitflags! { + /// Set of flags to pass to the ` CertStore::from_pkcs12 ` method. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct Pkcs12Flags: u32 { + const INCLUDE_EXTENDED_PROPERTIES = 0x0010; + const PREFER_CNG_KSP = 0x0000_0100; + const ALWAYS_CNG_KSP = 0x0000_0200; + const ALLOW_OVERWRITE_KEY = 0x0000_4000; + const NO_PERSIST_KEY =0x0000_8000; + } +} + +impl Default for Pkcs12Flags { + fn default() -> Self { + Pkcs12Flags::INCLUDE_EXTENDED_PROPERTIES | Pkcs12Flags::PREFER_CNG_KSP + } +} + impl CertStoreType { fn as_flags(&self) -> u32 { match self { @@ -71,7 +89,7 @@ impl CertStore { } /// Import certificate store from PKCS12 file - pub fn from_pkcs12(data: &[u8], password: &str) -> Result { + pub fn from_pkcs12(data: &[u8], password: &str, flags: Pkcs12Flags) -> Result { unsafe { let blob = CRYPT_INTEGER_BLOB { cbData: data.len() as u32, @@ -79,11 +97,8 @@ impl CertStore { }; let password = utf16z!(password); - let store = PFXImportCertStore( - &blob, - password.as_ptr(), - CRYPT_EXPORTABLE | PKCS12_INCLUDE_EXTENDED_PROPERTIES | PKCS12_PREFER_CNG_KSP, - ); + let store = + PFXImportCertStore(&blob, password.as_ptr(), CRYPT_EXPORTABLE | flags.bits()); if store.is_null() { Err(CngError::from_win32_error()) } else { diff --git a/tests/test_client_server.rs b/tests/test_client_server.rs index 408ab35..6845d1a 100644 --- a/tests/test_client_server.rs +++ b/tests/test_client_server.rs @@ -4,6 +4,13 @@ const SERVER_PFX: &[u8] = include_bytes!("assets/rustls-server.pfx"); const PASSWORD: &str = "changeit"; mod client { + use std::{ + hash::Hasher, + io::{Read, Write}, + net::{Shutdown, TcpStream}, + sync::Arc, + }; + use rustls::{ ClientConfig, ClientConnection, RootCertStore, Stream, client::{ClientCredentialResolver, CredentialRequest}, @@ -11,14 +18,11 @@ mod client { enums::CertificateType, }; use rustls_pki_types::CertificateDer; - use std::hash::Hasher; - use std::{ - io::{Read, Write}, - net::{Shutdown, TcpStream}, - sync::Arc, - }; - use rustls_cng::{signer::CngSigningKey, store::CertStore}; + use rustls_cng::{ + signer::CngSigningKey, + store::{CertStore, Pkcs12Flags}, + }; #[derive(Debug)] pub struct ClientCertResolver(CertStore, String); @@ -59,7 +63,8 @@ mod client { } pub fn run_client(port: u16) -> anyhow::Result<()> { - let store = CertStore::from_pkcs12(super::CLIENT_PFX, super::PASSWORD)?; + let store = + CertStore::from_pkcs12(super::CLIENT_PFX, super::PASSWORD, Pkcs12Flags::default())?; let ca_cert_context = store.find_by_subject_str(super::CA_SUBJECT)?; let ca_cert = ca_cert_context.first().unwrap(); @@ -105,7 +110,10 @@ mod server { crypto::{Credentials, Identity, SelectedCredential, aws_lc_rs}, server::{ClientHello, ServerCredentialResolver, WebPkiClientVerifier}, }; - use rustls_cng::{signer::CngSigningKey, store::CertStore}; + use rustls_cng::{ + signer::CngSigningKey, + store::{CertStore, Pkcs12Flags}, + }; #[derive(Debug)] pub struct ServerCertResolver(CertStore); @@ -155,7 +163,8 @@ mod server { } pub fn run_server(sender: Sender) -> anyhow::Result<()> { - let store = CertStore::from_pkcs12(super::SERVER_PFX, super::PASSWORD)?; + let store = + CertStore::from_pkcs12(super::SERVER_PFX, super::PASSWORD, Pkcs12Flags::default())?; let ca_cert_context = store.find_by_subject_str(super::CA_SUBJECT)?; let ca_cert = ca_cert_context.first().unwrap(); diff --git a/tests/test_find.rs b/tests/test_find.rs index 8ce1d9d..3214896 100644 --- a/tests/test_find.rs +++ b/tests/test_find.rs @@ -1,11 +1,12 @@ -use rustls_cng::store::CertStore; +use rustls_cng::store::{CertStore, Pkcs12Flags}; const PFX: &[u8] = include_bytes!("assets/rustls-ec.p12"); const PASSWORD: &str = "changeit"; #[test] fn test_find_by_subject_str() { - let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store"); + let store = CertStore::from_pkcs12(PFX, PASSWORD, Pkcs12Flags::default()) + .expect("Cannot open cert store"); let context = store .find_by_subject_str("rustls") @@ -17,7 +18,8 @@ fn test_find_by_subject_str() { #[test] fn test_find_by_subject_name() { - let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store"); + let store = CertStore::from_pkcs12(PFX, PASSWORD, Pkcs12Flags::default()) + .expect("Cannot open cert store"); let context = store .find_by_subject_name("CN=rustls-ec") @@ -29,7 +31,8 @@ fn test_find_by_subject_name() { #[test] fn test_find_by_issuer_str() { - let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store"); + let store = CertStore::from_pkcs12(PFX, PASSWORD, Pkcs12Flags::default()) + .expect("Cannot open cert store"); let context = store .find_by_issuer_str("Inforce") @@ -41,7 +44,8 @@ fn test_find_by_issuer_str() { #[test] fn test_find_by_issuer_name() { - let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store"); + let store = CertStore::from_pkcs12(PFX, PASSWORD, Pkcs12Flags::default()) + .expect("Cannot open cert store"); let context = store .find_by_issuer_name("O=Inforce Technologies, CN=Inforce Technologies CA") @@ -53,7 +57,8 @@ fn test_find_by_issuer_name() { #[test] fn test_find_by_hash() { - let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store"); + let store = CertStore::from_pkcs12(PFX, PASSWORD, Pkcs12Flags::default()) + .expect("Cannot open cert store"); let sha1 = [ 0x66, 0xBF, 0xFD, 0xE5, 0xD2, 0x9D, 0x57, 0x97, 0x1B, 0x17, 0xBB, 0x81, 0x5D, 0x7A, 0xF8, @@ -65,7 +70,8 @@ fn test_find_by_hash() { #[test] fn test_find_by_hash256() { - let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store"); + let store = CertStore::from_pkcs12(PFX, PASSWORD, Pkcs12Flags::default()) + .expect("Cannot open cert store"); let sha256 = [ 0xC9, 0x7C, 0xD6, 0xA1, 0x3F, 0xF6, 0xBD, 0xF6, 0xD4, 0xE2, 0xFB, 0x0E, 0xCD, 0x74, 0x2F, @@ -79,7 +85,8 @@ fn test_find_by_hash256() { #[test] fn test_find_all() { - let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store"); + let store = CertStore::from_pkcs12(PFX, PASSWORD, Pkcs12Flags::default()) + .expect("Cannot open cert store"); let context = store.find_all().unwrap().into_iter().next(); assert!(context.is_some()); diff --git a/tests/test_sign.rs b/tests/test_sign.rs index c1458c3..16e2ae8 100644 --- a/tests/test_sign.rs +++ b/tests/test_sign.rs @@ -1,5 +1,8 @@ use rustls::crypto::{SignatureScheme, SigningKey}; -use rustls_cng::{signer::CngSigningKey, store::CertStore}; +use rustls_cng::{ + signer::CngSigningKey, + store::{CertStore, Pkcs12Flags}, +}; const PFX: &[u8] = include_bytes!("assets/rustls-ec.p12"); const PASSWORD: &str = "changeit"; @@ -7,7 +10,8 @@ const MESSAGE: &str = "Security is our business"; #[test] fn test_sign() { - let store = CertStore::from_pkcs12(PFX, PASSWORD).expect("Cannot open cert store"); + let store = CertStore::from_pkcs12(PFX, PASSWORD, Pkcs12Flags::default()) + .expect("Cannot open cert store"); let context = store .find_by_subject_str("rustls") From 1d4f407cccd14790e8deda94cfa033c4be5414a7 Mon Sep 17 00:00:00 2001 From: Dmitry Pankratov Date: Fri, 26 Dec 2025 15:39:41 +0100 Subject: [PATCH 2/2] Minor syntax fix. --- src/store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store.rs b/src/store.rs index ea2f1cf..dc15ab1 100644 --- a/src/store.rs +++ b/src/store.rs @@ -30,7 +30,7 @@ bitflags! { const PREFER_CNG_KSP = 0x0000_0100; const ALWAYS_CNG_KSP = 0x0000_0200; const ALLOW_OVERWRITE_KEY = 0x0000_4000; - const NO_PERSIST_KEY =0x0000_8000; + const NO_PERSIST_KEY = 0x0000_8000; } }