C# 生成具有使用者备用名称的自签名证书?



我们有一个生成自签名证书的应用程序,但现在在Chrome 58中,我们需要添加主题备用名称。 证书是使用 C# 生成的,但在 win32 中调用 CertCreateSelfSignCertificate 函数。 到目前为止,我找到的所有示例都没有传递扩展参数,并且我发现很难创建一个扩展来传递以生成 SAN。

请注意,一旦我让它工作,我将清理它这是我用来创建一个条目,然后是扩展:

CERT_ALT_NAME_ENTRY entry = new CERT_ALT_NAME_ENTRY {
dwAltNameChoice = AlternativeNameType.Dns, // 3
Name = Marshal.StringToHGlobalUni("127.0.0.1")
};
IntPtr entryBlob Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CERT_ALT_NAME_ENTRY)));
var pvStructInfo = new CERT_ALT_NAME_INFO { cAltEntry = 1, rgAltEntry = entryBlob };
IntPtr pvEncoded = IntPtr.Zero;
int pcbEncoded = 0;
var status = InvokeMethods.CryptEncodeObjectEx(
CertEncodingType.X509_ASN_ENCODING | CertEncodingType.PKCS_7_ASN_ENCODING,  // 1 | 0x10000
new IntPtr(12),
ref pvStructInfo,
EncodeObjectFlags.CRYPT_ENCODE_ALLOC_FLAG, // 0x8000
IntPtr.Zero,
ref pvEncoded,
ref pcbEncoded);
Marshal.FreeHGlobal(entryBlob);
if (!status)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
var extension = new CERT_EXTENSION
{
ExtensionOid = OidSubjectAltName, //2.5.29.17
IsCritical = false,
Value = new CRYPTOAPI_BLOB
{
Length = (uint)pcbEncoded,
Data = pvEncoded
}
};
var result = new CertExtensions
{
cExtension = 1,
rgExtension = extension
}; 

使用的结构

internal struct CertExtensions
{
public uint cExtension;
public CERT_EXTENSION rgExtension;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct CRYPTOAPI_BLOB
{
public uint Length;
public IntPtr Data;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal class CERT_EXTENSION
{
[MarshalAs(UnmanagedType.LPWStr)]
public string ExtensionOid;
public bool IsCritical;
public CRYPTOAPI_BLOB Value;
}
[StructLayoutAttribute(LayoutKind.Sequential)]
internal struct CERT_ALT_NAME_INFO
{
/// DWORD->unsigned int
public uint cAltEntry;
public IntPtr rgAltEntry;
}
[StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
internal struct CERT_ALT_NAME_ENTRY
{
public AlternativeNameType dwAltNameChoice;
public IntPtr Name;
}

完整代码 - 从我找到的几个例子中拼凑而成

private static CertExtensions CreateExtensions(IList<CERT_ALT_NAME_ENTRY> items)
{
IntPtr itemBlob = Marshal.AllocHGlobal(items.Count * Marshal.SizeOf(typeof(CERT_ALT_NAME_ENTRY)));
for (int i = 0; i < items.Count; i++)
{
var offset = (IntPtr)((long)itemBlob + i * Marshal.SizeOf(typeof(CERT_ALT_NAME_ENTRY)));
Marshal.StructureToPtr(items[i], offset, false);
}
var pvStructInfo = new CERT_ALT_NAME_INFO { cAltEntry = (uint)items.Count, rgAltEntry = itemBlob };
IntPtr pvEncoded = IntPtr.Zero;
int pcbEncoded = 0;
var status = InvokeMethods.CryptEncodeObjectEx(
CertEncodingType.X509_ASN_ENCODING | CertEncodingType.PKCS_7_ASN_ENCODING,
new IntPtr(12),
ref pvStructInfo,
EncodeObjectFlags.CRYPT_ENCODE_ALLOC_FLAG,
IntPtr.Zero,
ref pvEncoded,
ref pcbEncoded);

Marshal.FreeHGlobal(itemBlob);
if (!status)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
var extension = new CERT_EXTENSION
{
ExtensionOid = OidSubjectAltName,
IsCritical = false,
Value = new CRYPTOAPI_BLOB
{
Length = (uint)pcbEncoded,
Data = pvEncoded
}
};
var result = new CertExtensions
{
cExtension = 1,
rgExtension = extension
};
return result;
}

好吧,这将是 C#/C++ 互操作的地狱,如果不了解指针、结构和类 C 数组在C++中的工作原理以及 marshalong 如何在互操作中工作,就很难理解。

您错误地定义了CERT_ALT_NAME_ENTRY结构。它在C++定义中使用了union。将联合转换为 C# 时,它们必须与联合中的最大结构大小(即CRYPTOAPI_BLOB个字节,即 8 个字节)并加上其他字段大小对齐:8 + 4 = 12 个字节。您的结构签名只有 8 个字节。

CERT_EXTENSIONS结构中的rgExtension成员不接受单个结构CERT_EXTENSION,实际上它是指向CERT_EXTENSION结构数组的指针。这意味着rgExtension成员必须定义为IntPtr

溶液:

删除整个代码并使用下面的示例。

正确的结构签名定义:

using System;
using System.Runtime.InteropServices;
namespace TestApp {
static class Wincrypt {
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CRYPTOAPI_BLOB {
public UInt32 cbData;
public IntPtr pbData;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]    
public struct CERT_EXTENSION {
[MarshalAs(UnmanagedType.LPStr)]
public String pszObjId;
public Boolean fCritical;
public CRYPTOAPI_BLOB Value;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CERT_EXTENSIONS {
public UInt32 cExtension;
public IntPtr rgExtension;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CERT_ALT_NAME_INFO {
public UInt32 cAltEntry;
public IntPtr rgAltEntry;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CERT_ALT_NAME_ENTRY {
public UInt32 dwAltNameChoice;
// since there is no direct translation from C-like unions in C#
// make additional struct to represent union options.
public CERT_ALT_NAME_UNION Value;
}
// create mapping to dwAltNameChoice
public const UInt32 CERT_ALT_NAME_OTHER_NAME = 1;
public const UInt32 CERT_ALT_NAME_RFC822_NAME = 2;
public const UInt32 CERT_ALT_NAME_DNS_NAME = 3;
public const UInt32 CERT_ALT_NAME_X400_ADDRESS = 4;
public const UInt32 CERT_ALT_NAME_DIRECTORY_NAME = 5;
public const UInt32 CERT_ALT_NAME_EDI_PARTY_NAME = 6;
public const UInt32 CERT_ALT_NAME_URL = 7;
public const UInt32 CERT_ALT_NAME_IP_ADDRESS = 8;
public const UInt32 CERT_ALT_NAME_REGISTERED_ID = 9;
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct CERT_ALT_NAME_UNION {
[FieldOffset(0)]
public IntPtr pOtherName;
[FieldOffset(0)]
public IntPtr pwszRfc822Name;
[FieldOffset(0)]
public IntPtr pwszDNSName;
[FieldOffset(0)]
public CRYPTOAPI_BLOB DirectoryName;
[FieldOffset(0)]
public IntPtr pwszURL;
[FieldOffset(0)]
public IntPtr IPAddress;
[FieldOffset(0)]
public IntPtr pszRegisteredID;
}
// not really used in this scenario, but is necessary when want to add
// UPN alt name, for example.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CERT_OTHER_NAME {
[MarshalAs(UnmanagedType.LPStr)]
public String pszObjId;
public CRYPTOAPI_BLOB Value;
}
}
}

CryptEncodeObject签名(尚未尝试Ex版本):

using System;
using System.Runtime.InteropServices;
namespace TestApp {
static class Crypt32 {
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Boolean CryptEncodeObject(
[In] UInt32 CertEncodingType,
[In] UInt32 lpszStructType,
[In, Out]ref Wincrypt.CERT_ALT_NAME_INFO pvStructInfo,
[Out] Byte[] pbEncoded,
[In, Out] ref UInt32 cbEncoded);
}
}

以及整个故事的评论:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace TestApp {
class Program {
static void Main(String[] args) {
//return;
// suppose we want to add three alternative DNS names to SAN extension
String[] dnsNames = { "contoso.com", "www.contoso.com", "mail.contoso.com" };
// calculate size of CERT_ALT_NAME_ENTRY structure. Since it is C-like
// struct, use Marshal.SizeOf(), not C# sizeof().
var altEntrySize = Marshal.SizeOf(typeof(Wincrypt.CERT_ALT_NAME_ENTRY));
// create CERT_ALT_NAME_INFO structure and set initial data:
// cAltEntry -- number of alt names in the extension
// rgAltEntry -- starting pointer in unmanaged memory to an array of alt names
// the size is calculated as: CERT_ALT_NAME_ENTRY size * alt name count
var altInfo = new Wincrypt.CERT_ALT_NAME_INFO {
cAltEntry = (UInt32)dnsNames.Length,
rgAltEntry = Marshal.AllocHGlobal(altEntrySize * dnsNames.Length)
};
// now create CERT_ALT_NAME_ENTRY for each alt name and copy structure to
// a pointer allocated in altInfo structure with a shift.
// Create a loop to save some coding
for (Int32 i = 0; i < dnsNames.Length; i++) {
var altEntry = new Wincrypt.CERT_ALT_NAME_ENTRY {
dwAltNameChoice = Wincrypt.CERT_ALT_NAME_DNS_NAME,
// use Uni, because the pwszDNSName is defined as LPWStr (unicode)
Value = { pwszDNSName = Marshal.StringToHGlobalUni(dnsNames[i]) },
};
// copy alt name entry to altInfo.rgAltEntry at the specified index.
// In unmanaged memory you have to calculate shift based on managed
// index and structure size
Marshal.StructureToPtr(altEntry, altInfo.rgAltEntry + i * altEntrySize, false);
}
// encode CERT_ALT_NAME_INFO to ASN.1 DER byte array
UInt32 pcbEncoded = 0;
if (Crypt32.CryptEncodeObject(1, 12, ref altInfo, null, ref pcbEncoded)) {
Byte[] encodedSANvalue = new Byte[pcbEncoded];
Crypt32.CryptEncodeObject(1, 12, ref altInfo, encodedSANvalue, ref pcbEncoded);
// create certificate extension array:
var extensions = new Wincrypt.CERT_EXTENSIONS {
cExtension = 1,
rgExtension = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Wincrypt.CERT_EXTENSION)))
};
// create SAN extension:
var san = new Wincrypt.CERT_EXTENSION {
fCritical = false,
pszObjId = "2.5.29.17",
Value = { cbData = (UInt32)encodedSANvalue.Length, pbData = Marshal.AllocHGlobal(encodedSANvalue.Length) }
};
// copy SAN bytes to SAN extension:
Marshal.Copy(encodedSANvalue,0,san.Value.pbData, encodedSANvalue.Length);
// copy CERT_EXTENSION structure to extensions:
Marshal.StructureToPtr(san, extensions.rgExtension, false);
// use 'extensions' variable in CertCreateSelfSignCertificate call.
} else {
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
}

一个注意事项:提供的代码不会释放非托管资源。您必须在调用函数后释放它们CertCreateSelfSignCertificate

相关内容

最新更新