Reputation: 23
We have an app that generates a Self Signed Cert but now with Chrome 58 we need to add the Subject Alternative Name. The Cert is generated using C# but invoking the CertCreateSelfSignCertificate function in win32. So far all the examples I am finding are not passing the extensions param and I'm finding it hard to create an extension to pass to generate the SAN.
Note I will be cleaning this up once I get it working This is what I am using to create an Entry and then the Extension:
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
};
Structs Used
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;
}
Full Code - Pieced together from a few examples I found
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;
}
Upvotes: 2
Views: 3038
Reputation: 49544
I know this is very old, but I had a similar requirement, To Create a Self-Signed SSL Cert for Kestral in C# dotnet core. This is what I've gotten working:
.ConfigureKestrel(serverOptions =>
{
serverOptions.ConfigureHttpsDefaults(listenOptions =>
{
listenOptions.ServerCertificate = GetOrAddSelfSigned("cn=MyCert", StoreName.My, StoreLocation.CurrentUser);
});
})
...
private static X509Certificate2 GetOrAddSelfSigned(string subjectName, StoreName storeName, StoreLocation storeLocation)
{
var now = DateTimeOffset.Now;
using (var store = new X509Store(storeName, storeLocation))
{
store.Open(OpenFlags.ReadWrite);
var cert = (from X509Certificate2 c in store.Certificates
where c.Subject.Equals(subjectName, StringComparison.OrdinalIgnoreCase)
where c.NotBefore <= now && now < c.NotAfter
select c).FirstOrDefault();
if (cert == null)
{
using (var key = RSA.Create(2048))
{
var request = new CertificateRequest(subjectName, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature,
critical: false));
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.1"),
},
critical: false));
var san = new SubjectAlternativeNameBuilder();
san.AddIpAddress(IPAddress.Loopback);
san.AddIpAddress(IPAddress.IPv6Loopback);
san.AddDnsName("localhost");
san.AddDnsName(Environment.MachineName);
request.CertificateExtensions.Add(san.Build());
var password = Guid.NewGuid().ToString();
cert = request.CreateSelfSigned(now.AddMinutes(-4), now.AddYears(1));
cert = new X509Certificate2(cert.Export(X509ContentType.Pfx, password), password, X509KeyStorageFlags.MachineKeySet);
store.Add(cert);
}
}
return cert;
}
Largely adapted from:
Upvotes: 0
Reputation: 13944
Well, this going to be a hell of C#/C++ interop and is hard to understand without knowing how pointers, structs and C-like arrays work in C++ and how marshalong works in interop.
You have incorrectly defined CERT_ALT_NAME_ENTRY
structure. It used union
in C++ definition. When translating unions to C# they must be aligned to the largest struct size in union (which is CRYPTOAPI_BLOB
and which is 8 bytes) and plus other field size: 8 + 4 = 12 bytes. Your struct signature is only 8 bytes.
rgExtension
member in CERT_EXTENSIONS
structure doesn't accept single CERT_EXTENSION
struct, actually it is a pointer to an array of CERT_EXTENSION
structs. This means that rgExtension
member must be defined as IntPtr
.
Solution:
Drop your entire code and use examples below.
Correct struct signature definitions:
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
signature (haven't tried Ex version):
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);
}
}
and the whole story with comments:
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());
}
}
}
}
One note: the provided code do not release unmanaged resources. You have to release them after calling CertCreateSelfSignCertificate
function.
Upvotes: 2