如何设置证书用途?

How to set certificate purposes?

有一个 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 的证书,如下所示:

如何以编程方式进行?

您需要使用 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,
);

添加对 System.Runtime.InteropServices 命名空间的使用引用。

接下来,准备一个关键用法集合:

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

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

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

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

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

  1. pvDataCertSetCertificateContextProperty cannot accept X509EnhancedKeyUsageExtension.RawData directly, it needs to be in a CRYPT_INTEGER_BLOB 结构
  2. Marshal.Copy的函数签名不正确,应该是Marshal.Copy(Byte[], Int32, IntPtr, Int32)

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

  1. 确保获得 X509Certificate2 you require (Certificate), probably from a X509Store that has been opened as ReadWrite
  2. 导入 Interop 签名并使用 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
  4. 创建一个 X509EnhancedKeyUsageExtension 实例 (eku)
  5. 使用 Marshal.AllocHGlobal(eku.RawData.Length) 在非托管内存中分配适当大小的缓冲区作为 pbData
  6. Marshal.AllocHGlobalCRYPT_INTEGER_BLOB 结构的大小一起使用,以在非托管内存中分配适当大小的缓冲区,如 pvData
  7. 创建 CRYPT_INTEGER_BLOB 结构的实例并为其分配 pbData 并将长度 eku.RawData.Length 分配给 cbData
  8. 使用Marshal.StructureToPtrCRYPT_INTEGER_BLOB结构分配给pvData
  9. pCertContext 调用 CertSetCertificateContextProperty 作为 Certificate.HandledwPropIdCERT_ENHKEY_USAGE_PROP_ID9dwFlags of 0pvDatapvData。如果成功,它将 return return true,如果您传递给它的证书句柄是只读的,它可能会抛出有关内存访问异常的错误,确保它来自 X509Store 打开为 ReadWrite.
  10. 通过 Marshal.FreeHGlobal(pvData)Marshal.FreeHGlobal(pbData)
  11. 释放分配的非托管内存
  12. 关闭任何打开的 X509Store

再次感谢 Crypt32 的回答。