如何设置证书用途?
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
命名空间的使用引用。
接下来,准备一个关键用法集合:
- 创建
OidCollection
class 的新实例。
- 将所需的 OID 添加到集合中。
- 使用创建的 OID 集合实例化
X509EnhancedKeyUsageExtension
class。这个 ctor 重载很好:X509EnhancedKeyUsageExtension(OidCollection, Boolean)
.
EKU 扩展的 RawData
属性 将包含 ASN.1 编码的字节数组,该数组将传递给 CertSetCertificateContextProperty
函数。
CertSetCertificateContextProperty
函数的最后一个参数是 IntPtr
类型,需要一个指向非托管内存块的指针,因此:
- 使用
Marshal.AllocHGlobal(eku.RawData.Length)
在非托管内存中分配适当大小的缓冲区。
- 使用
Marshal.Copy(byte[], IntPtr, int, int)
静态方法重载将eku.RawData
字节数组复制到步骤1中获取的非托管指针。
- 调用
CertSetCertificateContextProperty
函数。如果是 returns true
那么一切正常。
完成所有作业后,您必须释放非托管资源以避免内存泄漏。使用 Marshal.FreeHGlobal(IntPtr)
方法释放在 Marshal.AllocHGlobal
调用期间获取的指针。
我没有评论其他答案的声誉,但 Crypt32 的答案实际上有两个地方不正确,所以我发布了一个基于 Crypt32 的答案的新答案。
pvData
的 CertSetCertificateContextProperty
cannot accept X509EnhancedKeyUsageExtension.RawData
directly, it needs to be in a CRYPT_INTEGER_BLOB
结构
Marshal.Copy
的函数签名不正确,应该是Marshal.Copy(Byte[], Int32, IntPtr, Int32)
考虑到这一点,让我们从头开始:
- 确保获得
X509Certificate2
you require (Certificate
), probably from a X509Store
that has been opened as ReadWrite
。
- 导入 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;
}
- 创建一个
OidCollection
实例
- 将所需的 EKU 的 OID 添加到 OidCollection
- 使用 OidCollection
创建一个 X509EnhancedKeyUsageExtension
实例 (eku
)
- 使用
Marshal.AllocHGlobal(eku.RawData.Length)
在非托管内存中分配适当大小的缓冲区作为 pbData
- 将
Marshal.AllocHGlobal
与 CRYPT_INTEGER_BLOB
结构的大小一起使用,以在非托管内存中分配适当大小的缓冲区,如 pvData
- 创建
CRYPT_INTEGER_BLOB
结构的实例并为其分配 pbData
并将长度 eku.RawData.Length
分配给 cbData
- 使用
Marshal.StructureToPtr
将CRYPT_INTEGER_BLOB
结构分配给pvData
- 用
pCertContext
调用 CertSetCertificateContextProperty
作为 Certificate.Handle
,dwPropId
为 CERT_ENHKEY_USAGE_PROP_ID
即 9
,dwFlags
of 0
和 pvData
为 pvData
。如果成功,它将 return return true
,如果您传递给它的证书句柄是只读的,它可能会抛出有关内存访问异常的错误,确保它来自 X509Store
打开为 ReadWrite
.
- 通过
Marshal.FreeHGlobal(pvData)
和 Marshal.FreeHGlobal(pbData)
释放分配的非托管内存
- 关闭任何打开的
X509Store
再次感谢 Crypt32 的回答。
有一个 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
命名空间的使用引用。
接下来,准备一个关键用法集合:
- 创建
OidCollection
class 的新实例。 - 将所需的 OID 添加到集合中。
- 使用创建的 OID 集合实例化
X509EnhancedKeyUsageExtension
class。这个 ctor 重载很好:X509EnhancedKeyUsageExtension(OidCollection, Boolean)
.
EKU 扩展的 RawData
属性 将包含 ASN.1 编码的字节数组,该数组将传递给CertSetCertificateContextProperty
函数。
CertSetCertificateContextProperty
函数的最后一个参数是 IntPtr
类型,需要一个指向非托管内存块的指针,因此:
- 使用
Marshal.AllocHGlobal(eku.RawData.Length)
在非托管内存中分配适当大小的缓冲区。 - 使用
Marshal.Copy(byte[], IntPtr, int, int)
静态方法重载将eku.RawData
字节数组复制到步骤1中获取的非托管指针。 - 调用
CertSetCertificateContextProperty
函数。如果是 returnstrue
那么一切正常。
完成所有作业后,您必须释放非托管资源以避免内存泄漏。使用 Marshal.FreeHGlobal(IntPtr)
方法释放在 Marshal.AllocHGlobal
调用期间获取的指针。
我没有评论其他答案的声誉,但 Crypt32 的答案实际上有两个地方不正确,所以我发布了一个基于 Crypt32 的答案的新答案。
pvData
的CertSetCertificateContextProperty
cannot acceptX509EnhancedKeyUsageExtension.RawData
directly, it needs to be in aCRYPT_INTEGER_BLOB
结构Marshal.Copy
的函数签名不正确,应该是Marshal.Copy(Byte[], Int32, IntPtr, Int32)
考虑到这一点,让我们从头开始:
- 确保获得
X509Certificate2
you require (Certificate
), probably from aX509Store
that has been opened asReadWrite
。 - 导入 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;
}
- 创建一个
OidCollection
实例 - 将所需的 EKU 的 OID 添加到 OidCollection
- 使用 OidCollection 创建一个
- 使用
Marshal.AllocHGlobal(eku.RawData.Length)
在非托管内存中分配适当大小的缓冲区作为pbData
- 将
Marshal.AllocHGlobal
与CRYPT_INTEGER_BLOB
结构的大小一起使用,以在非托管内存中分配适当大小的缓冲区,如pvData
- 创建
CRYPT_INTEGER_BLOB
结构的实例并为其分配pbData
并将长度eku.RawData.Length
分配给cbData
- 使用
Marshal.StructureToPtr
将CRYPT_INTEGER_BLOB
结构分配给pvData
- 用
pCertContext
调用CertSetCertificateContextProperty
作为Certificate.Handle
,dwPropId
为CERT_ENHKEY_USAGE_PROP_ID
即9
,dwFlags
of0
和pvData
为pvData
。如果成功,它将 return returntrue
,如果您传递给它的证书句柄是只读的,它可能会抛出有关内存访问异常的错误,确保它来自X509Store
打开为ReadWrite
. - 通过
Marshal.FreeHGlobal(pvData)
和Marshal.FreeHGlobal(pbData)
释放分配的非托管内存
- 关闭任何打开的
X509Store
X509EnhancedKeyUsageExtension
实例 (eku
)
再次感谢 Crypt32 的回答。