如何在Actix中用Windows存储的Schannel证书与Ncrypt密钥对接OpenSSL/Rustls?
从Windows证书存储加载证书与私钥到rust-openssl/rustls
适配rust-openssl的SslAcceptorBuilder
加载CertContext作为证书
无需写入临时文件,直接将CertContext导出为DER格式,再用openssl库解析为X509对象:
use openssl::x509::X509; use schannel::cert_context::CertContext; fn load_cert_to_openssl(cert_ctx: &CertContext) -> Result<X509, Box<dyn std::error::Error>> { // 从CertContext导出DER格式证书 let der_bytes = cert_ctx.to_der()?; // 解析为OpenSSL的X509证书 let x509 = X509::from_der(&der_bytes)?; Ok(x509) } // 使用示例 // let mut builder = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls())?; // let x509 = load_cert_to_openssl(&cert_ctx)?; // builder.certificate(&x509)?;
将NcryptKey转为PKey
NcryptKey是Windows CNG密钥对象,需通过CNG API导出为PKCS#8格式的DER数据,再用openssl加载:
use openssl::pkey::PKey; use schannel::ncrypt::NcryptKey; use winapi::um::ncrypt::{ NCryptExportKey, NCRYPT_PKCS8_PRIVATE_KEY_BLOB, NCRYPT_SILENT_FLAG, }; use std::ptr; use std::os::windows::ffi::OsStrExt; use winapi::ctypes::c_void; fn export_ncrypt_key_to_pkcs8(key: &NcryptKey) -> Result<Vec<u8>, Box<dyn std::error::Error>> { let key_handle = key.handle(); let mut blob_size = 0; // 先查询导出所需缓冲区大小 let status = unsafe { NCryptExportKey( key_handle, ptr::null_mut(), NCRYPT_PKCS8_PRIVATE_KEY_BLOB, ptr::null(), ptr::null_mut(), 0, &mut blob_size, NCRYPT_SILENT_FLAG, ) }; if status != 0 { return Err(format!("NCryptExportKey size query failed: {}", status).into()); } // 分配缓冲区并导出密钥 let mut blob = vec![0u8; blob_size as usize]; let status = unsafe { NCryptExportKey( key_handle, ptr::null_mut(), NCRYPT_PKCS8_PRIVATE_KEY_BLOB, ptr::null(), blob.as_mut_ptr() as *mut c_void, blob_size, &mut blob_size, NCRYPT_SILENT_FLAG, ) }; if status != 0 { return Err(format!("NCryptExportKey failed: {}", status).into()); } blob.truncate(blob_size as usize); Ok(blob) } // 转为OpenSSL的PKey<Private> fn ncrypt_key_to_openssl_pkey(key: &NcryptKey) -> Result<PKey<openssl::pkey::Private>, Box<dyn std::error::Error>> { let pkcs8_der = export_ncrypt_key_to_pkcs8(key)?; let pkey = PKey::private_key_from_der(&pkcs8_der)?; Ok(pkey) } // 使用示例 // let pkey = ncrypt_key_to_openssl_pkey(&ncrypt_key)?; // builder.private_key(&pkey)?;
适配rustls的ServerConfig
加载CertContext作为证书
导出DER格式后直接转为rustls的Certificate:
use rustls::Certificate; use schannel::cert_context::CertContext; fn load_cert_to_rustls(cert_ctx: &CertContext) -> Result<Certificate, Box<dyn std::error::Error>> { let der_bytes = cert_ctx.to_der()?; Ok(Certificate(der_bytes)) } // 使用示例 // let certs = vec![load_cert_to_rustls(&cert_ctx)?]; // let config = ServerConfig::builder() // .with_safe_defaults() // .with_no_client_auth() // .with_single_cert(certs, private_key)?;
将NcryptKey转为rustls的PrivateKey
复用上述export_ncrypt_key_to_pkcs8函数导出PKCS#8 DER,再转为rustls的PrivateKey:
use rustls::PrivateKey; fn ncrypt_key_to_rustls_private_key(key: &NcryptKey) -> Result<PrivateKey, Box<dyn std::error::Error>> { let pkcs8_der = export_ncrypt_key_to_pkcs8(key)?; Ok(PrivateKey(pkcs8_der)) }
注意事项
- 确保Cargo.toml添加依赖:
winapi(启用ncrypt、winbase特性)、openssl、schannel、rustls等。 - 导出密钥时使用
NCRYPT_SILENT_FLAG避免弹出系统授权提示。 - 所有操作均在内存中完成,无临时文件写入,规避安全风险。
内容的提问来源于stack exchange,提问作者VassilisM
相关产品推荐
相关产品推荐

