共用方式為


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.

Comments

  • Anonymous
    January 01, 2003
    2Andrewyou should wrap the code in a function and provide the parameter that accepts certificate holder name.

  • Anonymous
    November 24, 2010
    Great Article. I have a question. I have windows 2008 ADCS and i am looking to see if there are any API's or if there is a way for me use a Java application to request and install certificates for users. The users are internet users who are not a part of active directory and will need to install the certificate into a browser. I  run scripts currenty to generate SCCM and SCOM related certificates requests for workgroup servers and i can  tweek it to create request for non AD user certificate request. But are there available APIs that can be used by application readily. Thanks

  • Anonymous
    July 05, 2011
    Hi there, I'm writing a PowerShell script using the CertEnroll API to create a self-signed certificate for use with IIS 7 which includes subject alternative names. My script is similar to yours however I'm having interoperability issues with the resultant certificate and Java. The generated cert works fine with IIS and browsers however when I try to import into a Java keystore it says the certificate is not valid. I'm only having this issue with the certificate generated by CertEnroll. I have no issues with Java when the certificate is generated using the IIS 7 self-signed certificate wizard. I'm not sure what the difference is… You can see what I'm talking by creating a certificate with your script from this post and then examining it with Portecle (portecle.sourceforge.net) Portecle > Examine > Examine Certificate > Open exported CertEnroll generated certificate. Would you mind trying this and let me know if have any idea why this is?

  • Anonymous
    June 12, 2013
    What if I wanted to create more one at a time and change the who the certificate is issued to. What would recommend I change in the script.

  • Anonymous
    February 10, 2014
    Hi! I did it in c#, but I am getting an exception of CX509PrivateKey::Create: Access is denied. 0x80070005 (WIN32: 5) . I'm already have administrator rights. What am I missing? Please help. Thanks in advance!

  • Anonymous
    February 10, 2014
    Hi! I did it in c#, but I am getting an exception of CX509PrivateKey::Create: Access is denied. 0x80070005 (WIN32: 5) . I already have administrator rights. What am I missing? Please help. Thanks in advance! Btw, my OS is Windows 7. I already setup Public Key Policies etc in MMC. But still, I get that exception.

  • Anonymous
    February 12, 2014
    The comment has been removed

  • Anonymous
    July 18, 2014
    Rob - that's the single most-useful comment I've ever seen online. THANKS!

  • Anonymous
    August 18, 2014
    i am getting same error , CX509PrivateKey::Create: Access is denied. 0x80070005 (WIN32: 5) .
    Is there any solution let me know.
    Thank you.