You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 05:25:01