Generating Self-Signed X509 Certificates in Vanilla .NET and C#

To generate a self-signed X509 certificate out of the box with vanilla .NET and C#, you will need to Platform Invoke the Cryptography library in Windows. You don’t need to use any third party libraries for this, Windows is perfectly capable of tackling this task for you. Wired up for using the SHA512 hashing algorithm, here is the code to make the magic happen:

public struct SystemTime
{
	public Int16 Year;
	public Int16 Month;
	public Int16 DayOfWeek;
	public Int16 Day;
	public Int16 Hour;
	public Int16 Minute;
	public Int16 Second;
	public Int16 Milliseconds;
}

public static class MarshalHelper
{
	public static void CheckReturnValue(Boolean nativeCallSucceeded)
	{
		if (!nativeCallSucceeded)
			Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
	}
}

public static class DateTimeExtensions
{
	[DllImport("kernel32.dll", SetLastError = true)]
	static extern Boolean FileTimeToSystemTime(ref Int64 fileTime, out SystemTime systemTime);

	public static SystemTime ToSystemTime(this DateTime dateTime)
	{
		Int64 fileTime = dateTime.ToFileTime();
		SystemTime systemTime;
		MarshalHelper.CheckReturnValue(FileTimeToSystemTime(ref fileTime, out systemTime));
		return systemTime;
	}
}

class X509Certificate2Helper
{
	[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
	static extern Boolean CryptAcquireContextW(out IntPtr providerContext, String container, String provider, UInt32 providerType, UInt32 flags);
	
	[DllImport("advapi32.dll", SetLastError = true)]
	static extern Boolean CryptReleaseContext(IntPtr providerContext, Int32 flags);
	
	[DllImport("advapi32.dll", SetLastError = true)]
	static extern Boolean CryptGenKey(IntPtr providerContext, Int32 algorithmId, Int32 flags, out IntPtr cryptKeyHandle);
	
	[DllImport("advapi32.dll", SetLastError = true)]
	static extern Boolean CryptDestroyKey(IntPtr cryptKeyHandle);
	
	[DllImport("crypt32.dll", SetLastError = true)]
	static extern Boolean CertStrToNameW(Int32 certificateEncodingType, IntPtr x500, Int32 strType, IntPtr reserved, Byte[] encoded, ref Int32 encodedLength, out IntPtr errorString);
	
	[DllImport("crypt32.dll", SetLastError = true)]
	static extern IntPtr CertCreateSelfSignCertificate(IntPtr providerHandle, ref CryptoApiBlob subjectIssuerBlob, Int32 flags, ref CryptKeyProviderInformation keyProviderInformation, IntPtr signatureAlgorithm, ref SystemTime startTime, ref SystemTime endTime, IntPtr extensions);
	
	[DllImport("crypt32.dll", SetLastError = true)]
	static extern Boolean CertFreeCertificateContext(IntPtr certificateContext);
	
	[DllImport("crypt32.dll", SetLastError = true)]
	static extern Boolean CertSetCertificateContextProperty(IntPtr certificateContext, Int32 propertyId, Int32 flags, ref CryptKeyProviderInformation data);

	public static X509Certificate2 GenerateSelfSignedCertificate(String name = "", DateTime? startTime = null, DateTime? endTime = null)
	{
		if (startTime == null || (DateTime)startTime < DateTime.FromFileTimeUtc(0))
			startTime = DateTime.FromFileTimeUtc(0);
		var startSystemTime = ((DateTime)startTime).ToSystemTime();
		if (endTime == null)
			endTime = DateTime.MaxValue;
		var endSystemTime = ((DateTime)endTime).ToSystemTime();
		String containerName = Guid.NewGuid().ToString();
		GCHandle dataHandle = new GCHandle();
		IntPtr providerContext = IntPtr.Zero;
		IntPtr cryptKey = IntPtr.Zero;
		IntPtr certificateContext = IntPtr.Zero;
		IntPtr algorithmPoInt32er = IntPtr.Zero;
		RuntimeHelpers.PrepareConstrainedRegions();
		try
		{
			MarshalHelper.CheckReturnValue(CryptAcquireContextW(out providerContext, containerName, null, 0x1, 0x8));
			MarshalHelper.CheckReturnValue(CryptGenKey(providerContext, 0x1, 0x20000001, out cryptKey));
			IntPtr errorStringPtr;
			Int32 nameDataLength = 0;
			Byte[] nameData;
			dataHandle = GCHandle.Alloc(name, GCHandleType.Pinned);
			if (!CertStrToNameW(0x10001, dataHandle.AddrOfPinnedObject(), 3, IntPtr.Zero, null, ref nameDataLength, out errorStringPtr))
			{
				String error = Marshal.PtrToStringUni(errorStringPtr);
				throw new ArgumentException(error);
			}
			nameData = new Byte[nameDataLength];
			if (!CertStrToNameW(0x10001, dataHandle.AddrOfPinnedObject(), 3, IntPtr.Zero, nameData, ref nameDataLength, out errorStringPtr))
			{
				String error = Marshal.PtrToStringUni(errorStringPtr);
				throw new ArgumentException(error);
			}
			dataHandle.Free();
			dataHandle = GCHandle.Alloc(nameData, GCHandleType.Pinned);
			CryptoApiBlob nameBlob = new CryptoApiBlob { cbData = (UInt32)nameData.Length, pbData = dataHandle.AddrOfPinnedObject() };
			dataHandle.Free();
			CryptKeyProviderInformation keyProvider = new CryptKeyProviderInformation { pwszContainerName = containerName, dwProvType = 1, dwKeySpec = 1 };
			CryptAlgorithmIdentifier algorithm = new CryptAlgorithmIdentifier { pszObjId = "1.2.840.113549.1.1.13", Parameters = new CryptoApiBlob() };
			algorithmPoInt32er = Marshal.AllocHGlobal(Marshal.SizeOf(algorithm));
			Marshal.StructureToPtr(algorithm, algorithmPoInt32er, false);
			certificateContext = CertCreateSelfSignCertificate(providerContext, ref nameBlob, 0, ref keyProvider, algorithmPoInt32er, ref startSystemTime, ref endSystemTime, IntPtr.Zero);
			MarshalHelper.CheckReturnValue(certificateContext != IntPtr.Zero);
			return new X509Certificate2(certificateContext);
		}
		finally
		{
			if (dataHandle.IsAllocated)
				dataHandle.Free();
			if (certificateContext != IntPtr.Zero)
				CertFreeCertificateContext(certificateContext);
			if (cryptKey != IntPtr.Zero)
				CryptDestroyKey(cryptKey);
			if (providerContext != IntPtr.Zero)
				CryptReleaseContext(providerContext, 0);
			if (algorithmPoInt32er != IntPtr.Zero)
			{
				Marshal.DestroyStructure(algorithmPoInt32er, typeof(CryptAlgorithmIdentifier));
				Marshal.FreeHGlobal(algorithmPoInt32er);
			}
		}
   	}

	struct CryptoApiBlob
	{
		public UInt32 cbData;
		public IntPtr pbData;
	}
	
	struct CryptAlgorithmIdentifier {
		[MarshalAs(UnmanagedType.LPStr)]
		public String pszObjId;
		public CryptoApiBlob Parameters;
	}
	
	struct CryptKeyProviderInformation
	{
		[MarshalAs(UnmanagedType.LPWStr)]
		public String pwszContainerName;
		[MarshalAs(UnmanagedType.LPWStr)]
		public String pwszProvName;
		public UInt32 dwProvType;
		public UInt32 dwFlags;
		public UInt32 cProvParam;
		public IntPtr rgProvParam;
		public UInt32 dwKeySpec;
	}
}

To call this class, specify the common name to your self-signed certificate, and actually get a certificate out of it (in PFX format with a simple password for it’s private key):

var certificate = X509Certificate2Helper.GenerateSelfSignedCertificate("CN = localhost");
File.WriteAllBytes(@"C:\Users\User\Desktop\Certificate.pfx", certificate.Export(X509ContentType.Pfx, "password"));

Alexandru

"To avoid criticism, say nothing, do nothing, be nothing." - Aristotle

"It is wise to direct your anger towards problems - not people; to focus your energies on answers - not excuses." - William Arthur Ward

"Science does not know its debt to imagination." - Ralph Waldo Emerson

"Money was never a big motivation for me, except as a way to keep score. The real excitement is playing the game." - Donald Trump

"All our dreams can come true, if we have the courage to pursue them." - Walt Disney

"Mitch flashes back to a basketball game held in the Brandeis University gymnasium in 1979. The team is doing well and chants, 'We're number one!' Morrie stands and shouts, 'What's wrong with being number two?' The students fall silent." - Tuesdays with Morrie

I'm not entirely sure what makes me successful in general programming or development, but to any newcomers to this blood-sport, my best guess would be that success in programming comes from some strange combination of interest, persistence, patience, instincts (for example, someone might tell you that something can't be done, or that it can't be done a certain way, but you just know that can't be true, or you look at a piece of code and know something doesn't seem right with it at first glance, but you can't quite put your finger on it until you think it through some more), fearlessness of tinkering, and an ability to take advice because you should be humble. Its okay to be wrong or to have a bad approach, realize it, and try to find a better one, and even better to be wrong and find a better approach to solve something than to have had a bad approach to begin with. I hope that whatever fragments of information I sprinkle across here help those who hit the same roadblocks.

Leave a Reply

Your email address will not be published. Required fields are marked *