Generating a certificate (self-signed) using powershell and CertEnroll interfaces

In this article I will explore using the certenroll interfaces to create certificates for testing/local usage. To scope the discussion, we would look at various options exposed via makecert.exe tool (https://msdn.microsoft.com/en-us/library/aa386968(VS.85).aspx .

 We will start by looking at a sample powershell script that creates a self-signed machine certificate that has "server auth" eku:

$name = new-object -com "X509Enrollment.CX500DistinguishedName.1"
$name.Encode("CN=TestServer", 0)

$key = new-object -com "X509Enrollment.CX509PrivateKey.1"
$key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
$key.KeySpec = 1
$key.Length = 1024
$key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
$key.MachineContext = 1
$key.Create()

$serverauthoid = new-object -com "X509Enrollment.CObjectId.1"
$serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1")
$ekuoids = new-object -com "X509Enrollment.CObjectIds.1"
$ekuoids.add($serverauthoid)
$ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"
$ekuext.InitializeEncode($ekuoids)

$cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1"
$cert.InitializeFromPrivateKey(2, $key, "")
$cert.Subject = $name
$cert.Issuer = $cert.Subject
$cert.NotBefore = get-date
$cert.NotAfter = $cert.NotBefore.AddDays(90)
$cert.X509Extensions.Add($ekuext)
$cert.Encode()

$enrollment = new-object -com "X509Enrollment.CX509Enrollment.1"
$enrollment.InitializeFromRequest($cert)
$certdata = $enrollment.CreateRequest(0)
$enrollment.InstallResponse(2, $certdata, 0, "")

Let's investigate the sample line by line and see how it connects to the various options in makecert.exe

The first 2 lines initializes the desired Subject name in the certificate:

$name = new-object -com "X509Enrollment.CX500DistinguishedName.1"
$name.Encode("CN=TestServer", 0)

This covers the -n option of the makecert.exe. Infact the X509Enrollment.CX500DistinguishedName exposes all the various encoding options available (for example if you have comma in the CN value) which makecert.exe might not be able to do.

The next block creates the subject's private key:

$key = new-object -com "X509Enrollment.CX509PrivateKey.1"
$key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
$key.KeySpec = 1
$key.Length = 1024
$key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
$key.MachineContext = 1
$key.Create()

X509Enrollment.CX509PrivateKey gives you full control of what type of public/private key pair you want to create, down to what the desired container name should be, what should be provider type, provider name, desired key length, desired key spec, machine key Vs User key, who should be able to access the key, etc. This covers -sp/-sy/-sky/-pe/-sk/-len options of makecert.exe

Next block goes over and showcases the powerful set of various extension interfaces exposed via certenroll.

$serverauthoid = new-object -com "X509Enrollment.CObjectId.1"
$serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1")
$ekuoids = new-object -com "X509Enrollment.CObjectIds.1"
$ekuoids.add($serverauthoid)
$ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"
$ekuext.InitializeEncode($ekuoids)

This particular sample covers EKU extension. Full list is available here: https://msdn.microsoft.com/en-us/library/ee338596(VS.85).aspx. For extensions not in the list, The generic IX509Extension interface can be used directly (although you own encoding of the extension value correctly in ASN.1). This should cover the options -l (for policy extension)/ -cy and -h (for basic constraint extension/ -eku/-nscp (you will have to use the generic IX509Extension interface here, I would be curious if anyone actually needs this extension. Drop me a comment if you are unable to create sample for that).

 Next block of script uses IX509CertificateRequestCertificate interface to actual create the self-signed certificate.

$cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1"
$cert.InitializeFromPrivateKey(2, $key, "")
$cert.Subject = $name
$cert.Issuer = $cert.Subject
$cert.NotBefore = get-date
$cert.NotAfter = $cert.NotBefore.AddDays(90)
$cert.X509Extensions.Add($ekuext)
$cert.Encode()

Various options in this interface are pretty self-explainatory. The NotBefore and NotAfter properties cover the -m and -e option of makecert.exe. SerialNumber property can be used to cover -# option of makecert.exe. SignatureInformation property covers the -a option of makecert.exe. SignerCertificate property can be used when you don't want the certificate to be self-signed.

The last block of script actually installs the just created certificate into the physical store so that it is available to the desired application for usage. 

$enrollment = new-object -com "X509Enrollment.CX509Enrollment.1"
$enrollment.InitializeFromRequest($cert)
$certdata = $enrollment.CreateRequest(0)
$enrollment.InstallResponse(2, $certdata, 0, "")

This is the critical step as IX509Enrollment interface ensures that Certificate is stored in appropriate store as well as ensure that the private key is accessible (by associated correct KeyProvInfo property on the certificate).

I've deliberately stayed away from Storage (Issuers/Subject store name/location) as those can be easily taken care by X509Store/X509Certificate2 class objects exposed via .NET.

The only thing that is missing is the direct support of PVK file (both input or output). However for that there are various tools (including third-party) that can convert the content from PFX to PVK and vice-versa. These tools can be leveraged to produce/consume PVK files as and when needed.