【问题标题】:Exception on attempt to create not self-signed certificate尝试创建非自签名证书的例外情况
【发布时间】:2016-11-07 15:02:41
【问题描述】:

我想从每个设备的自签名证书切换到一对证书,其中一个是先前生成的,放置在受信任的根证书颁发机构存储中,对于所有设备都是相同的,并且作为第二个证书的根 CA,它每个设备生成,并放置在个人存储中。

我不想使用 makecert,因为创建签名证书会显示 UI,我想避免这种情况。此外,由于某些与许可证相关的东西,OpenSSL 无法使用(尽管我有可用的解决方案)。所以,现在我正在使用基于 CertEnroll 库的小型 C# 工具。

这就是我为第一个根 CA 证书创建 pfx 的方式。

makecert -n "CN=Root CA" -cy authority -r -a sha256 -len 2048 -sv root.pvk root.cer
pvk2pfx -pvk root.pvk -spc root.cer -pfx root.pfx -pi 123 -po 123

要从 C# 代码创建证书,我参考了问题 How to create self-signed certificate programmatically for WCF service?C# Generate a non self signed client CX509Certificate Request without a CA using the certenroll.dll

到目前为止,我有以下代码。证书生成方法:

/// <summary>
/// Generates self-signed certificate with specified subject, which will expire after specified timespan.
/// </summary>
public X509Certificate2 CreateCertificate(string subjectName, TimeSpan expirationLength, X509Certificate2 issuer = null)
{
    // create DN for subject and issuer
    var dn = new CX500DistinguishedName();
    dn.Encode("CN=" + subjectName);

    var issuerName = new CX500DistinguishedName();
    if(issuer != null)
    {
        issuerName.Encode(issuer.Subject);
    }

    var privateKey = new CX509PrivateKey
    {
        ProviderName = "Microsoft Strong Cryptographic Provider",
        Length = 2048,
        KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE,
        KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG |
                       X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG,
        MachineContext = true,
        ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG
    };

    privateKey.Create();

    // Use the stronger SHA512 hashing algorithm
    var hashobj = new CObjectId();
    hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
            ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
            AlgorithmFlags.AlgorithmFlagsNone, "SHA512");

    var cert = new CX509CertificateRequestCertificate();
    cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
    cert.Subject = dn;
    if (issuer != null)
        cert.Issuer = issuerName;
    else
        cert.Issuer = dn;
    cert.NotBefore = DateTime.Now.Date;
    cert.NotAfter = cert.NotBefore + expirationLength;
    cert.HashAlgorithm = hashobj; // Specify the hashing algorithm

    if(issuer != null)
    {
        var signerCertificate = new CSignerCertificate();
        signerCertificate.Initialize(true, X509PrivateKeyVerify.VerifyAllowUI, EncodingType.XCN_CRYPT_STRING_HEX, issuer.GetRawCertDataString());
        cert.SignerCertificate = signerCertificate; 
    }
    cert.Encode();

    // Do the final enrollment process
    var enroll = new CX509Enrollment();
    enroll.InitializeFromRequest(cert); // load the certificate
    enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name

    var csr = enroll.CreateRequest(); // Output the request in base64
    // and install it back as the response
    enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
            csr, EncodingType.XCN_CRYPT_STRING_BASE64, ""); // no password
    // output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
    var base64encoded = enroll.CreatePFX("", // no password, this is for internal consumption
            PFXExportOptions.PFXExportChainWithRoot);

    // instantiate the target class with the PKCS#12 data (and the empty password)
    return new X509Certificate2(Convert.FromBase64String(base64encoded), "",
        X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
}

查找现有证书并基于它创建新证书的简单应用程序:

static void Main(string[] args)
{
    var certificateGenerator = new CertificateGenerator();

    X509Certificate2 rootCA;

    using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
    {
        store.Open(OpenFlags.ReadWrite);

        rootCA = store.Certificates.OfType<X509Certificate2>()
            .FirstOrDefault(c => c.Subject.StartsWith("CN=Root CA", StringComparison.Ordinal));

        store.Close();
    }

    if (rootCA == null)
        throw new Exception("Can't find root CA certificate");

    var testCert = certificateGenerator.CreateCertificate("Test", new TimeSpan(3650, 0, 0, 0), rootCA);
    using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
    {
        store.Open(OpenFlags.ReadWrite);
        store.Add(testCert);
        store.Close();
    }
}

问题是,如果我尝试引用的证书不是在受信任的根证书颁发机构中,而是在个人中(即使我在证书上有密码),它的效果很好。但是,如果我尝试根据受信任的根证书颁发机构的 CA 证书创建证书,我会在 signerCertificate.Initialize 收到异常,说

Cannot find object or property. 0x80092004 (-2146885628 CRYPT_E_NOT_FOUND)

那么,我错过了什么?

【问题讨论】:

    标签: c# ssl x509certificate2 certenroll


    【解决方案1】:

    ISignerCertificate::Initialize 要求通过 Requests 或 My store 绑定私钥:

    https://msdn.microsoft.com/en-us/library/windows/desktop/aa376832(v=vs.85).aspx:

    如果需要私钥,则只有个人和请求存储 搜索。

    如果不需要私钥,则根 CA 和中间 CA 商店也会被搜索。

    Windows 期望您只将 CA 的公共部分放入 CA(中间)或 Root/ThirdPartyRoot 存储中,并且如果您是颁发者,您还将安装它(现在使用私钥)到CurrentUser\My 或 LocalMachine\My。

    【讨论】:

    • 如果证书(甚至只是它的公共部分)在 CA 存储中的意义何在,如果它的带有私钥的版本已经在我的存储中?我怀疑我可以删除它,因为生成的证书将引用我的商店中的 CA 证书,而不是 Root/CA 商店。
    • @lentinant 这不是它的工作原理。当它查找私钥来生成签名时使用“我的”存储,但它为颁发者写下的信息只是标识符,而不是“它在我的存储中”。 CA store 仅用于链构建中的加速查找,Root/TrustedRoot 用于链构建中的信任决策。 “证书”没有存储,或者不知道存储是什么……存储仅在某些代码执行路径期间提供功能。
    • 让它工作。 CA 证书也应该放在个人存储中,正如您所说,应用程序不应自行分配 Issuer 属性 - 如果在编码之前分配 IssuerCertificate 属性,它会自动设置。手动设置很可能会导致证书信息上显示“Windows 没有足够的信息来验证此证书”。
    猜你喜欢
    • 2013-06-02
    • 1970-01-01
    • 1970-01-01
    • 2019-11-07
    • 2016-01-11
    • 1970-01-01
    • 2019-03-21
    • 2021-01-11
    • 2013-02-05
    相关资源
    最近更新 更多