【问题标题】:How to set certificate purposes?如何设置证书用途?
【发布时间】:2022-01-14 07:35:38
【问题描述】:

有一个 GlobalSign CA。在大多数情况下,它的根证书已经存在于 Windows 证书存储中。 但有时(尤其是在旧 Windows 版本上)存储不包含证书。

我需要检查证书是否存在,如果不存在则导入它。我将证书导出到一个文件并使用以下代码将其导入:

public void ImportCertificate(StoreName storeName,
    StoreLocation location,
    byte[] certificateData)
{
    X509Store x509Store = new X509Store(storeName, location);
    X509Certificate2 certificate = new X509Certificate2(certificateData);
    x509Store.Open(OpenFlags.ReadWrite);
    x509Store.Add(certificate);
    x509Store.Close();
}

代码添加了证书,但检查了所有证书用途:

我不想为证书添加额外的目的,只想设置那些具有其他根 CA 的证书,如下所示:

如何以编程方式进行?

【问题讨论】:

    标签: c# x509certificate2


    【解决方案1】:

    您需要使用CertSetCertificateContextProperty 函数来设置存储附加属性。

    dwPropId 参数中传递CERT_ENHKEY_USAGE_PROP_ID。您可以在 Wincrypt.h C++ 头文件中找到它的数值。在给定的情况下,dwPropId 是 9:

    #define CERT_ENHKEY_USAGE_PROP_ID           9
    

    dwFlags 中传递零 (0)。

    pvData 参数(托管签名中的 IntPtr)中,您将非托管指针传递给 ASN.1 编码的字节数组,该数组表示对象标识符的集合,其中每个 OID 表示显式启用的密钥用法。

    这是互操作签名:

    [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern Boolean CertSetCertificateContextProperty(
        [In] IntPtr pCertContext,
        [In] UInt32 dwPropId,
        [In] UInt32 dwFlags,
        [In] IntPtr pvData,
    );
    

    将 usings 引用添加到 System.Runtime.InteropServices 命名空间。

    接下来,准备一组关键用法:

    1. 创建OidCollection 类的新实例。
    2. 将所需的 OID 添加到集合中。
    3. 使用创建的 OID 集合来实例化 X509EnhancedKeyUsageExtension 类。这个ctor重载很好:X509EnhancedKeyUsageExtension(OidCollection, Boolean)
    4. EKU 扩展的RawData 属性将包含 ASN.1 编码的字节数组,该数组将传递给CertSetCertificateContextProperty 函数。

    CertSetCertificateContextProperty 函数的最后一个参数是IntPtr 类型,并且需要一个指向非托管内存块的指针,所以:

    1. 使用Marshal.AllocHGlobal(eku.RawData.Length) 在非托管内存中分配适当大小的缓冲区。
    2. 使用Marshal.Copy(byte[], IntPtr, int, int)静态方法重载将eku.RawData字节数组复制到步骤1中获取的非托管指针。
    3. 调用CertSetCertificateContextProperty 函数。如果返回 true 则一切正常。

    完成所有工作后,您必须释放非托管资源以避免内存泄漏。使用Marshal.FreeHGlobal(IntPtr)方法释放Marshal.AllocHGlobal调用过程中获取的指针。

    【讨论】:

    • Crypt32,非常感谢您的详细回答。它比我想象的要复杂。
    【解决方案2】:

    我缺乏评论其他答案的声誉,但 Crypt32 的答案实际上在两个地方不正确,所以我发布了一个基于 Crypt32 答案的新答案。

    1. CertSetCertificateContextPropertypvData 不能直接接受X509EnhancedKeyUsageExtension.RawData,它需要在一个CRYPT_INTEGER_BLOB 结构中
    2. Marshal.Copy的函数签名不正确,应该是Marshal.Copy(Byte[], Int32, IntPtr, Int32)

    考虑到这一点,让我们从头开始:

    1. 确保您获得所需的X509Certificate2 (Certificate),可能来自已打开为ReadWriteX509Store
    2. 导入互操作签名并使用System.Runtime.InteropServices命名空间。
    [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern Boolean CertSetCertificateContextProperty(
        [In] IntPtr pCertContext,
        [In] UInt32 dwPropId,
        [In] UInt32 dwFlags,
        [In] IntPtr pvData,
    );
    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct CRYPTOAPI_BLOB {
        public uint cbData;
        public IntPtr pbData;
    }
    
    1. 创建一个OidCollection 实例
    2. 将所需 EKU 的所需 OID 添加到 OidCollection 中
    3. 使用 OidCollection 创建一个 X509EnhancedKeyUsageExtension 实例 (eku)
    4. 使用Marshal.AllocHGlobal(eku.RawData.Length)在非托管内存中分配适当大小的缓冲区pbData
    5. 使用Marshal.AllocHGlobalCRYPT_INTEGER_BLOB 结构的大小在非托管内存中分配适当大小的缓冲区pvData
    6. 创建CRYPT_INTEGER_BLOB 结构的实例并为其分配pbData 并将长度eku.RawData.Length 分配给cbData
    7. 使用Marshal.StructureToPtrCRYPT_INTEGER_BLOB 结构分配给pvData
    8. 调用CertSetCertificateContextPropertypCertContextCertificate.HandledwPropIdCERT_ENHKEY_USAGE_PROP_ID,即9dwFlags0 和@9876543667@ 为@987654。如果成功,它将返回返回true,如果您传递给它的证书句柄是只读的,它可能会抛出有关内存访问异常的错误,请确保它来自以ReadWrite 打开的X509Store
    9. 通过Marshal.FreeHGlobal(pvData)Marshal.FreeHGlobal(pbData) 释放分配的非托管内存
    10. 关闭任何打开的X509Store

    再次感谢 Crypt32 的回答。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-15
      • 1970-01-01
      • 1970-01-01
      • 2017-09-22
      • 1970-01-01
      相关资源
      最近更新 更多