===========================================================================
Updated 5 August 2013: allow wildcard subject names e.g. “CN=*.showcase.kloud.com.au” which get written to disk as ‘star.domain’ e.g. ‘star.showcase.kloud.com.au’
===========================================================================
Automating a certificate request with PowerShell should not be hard – but it is. Exchange has had offline certificate requests with New-ExchangeCertificate since PowerShell was introduced with Exchange 2007. Lync has had online certificate requests using Request-CsCertificate since Lync 2010 and GUI based online requests from the OCS days. I had a requirement to script the request, issuing and importing of a certificate request including multiple domain SAN (Subject Alternate Name) entries. Using native PowerShell features this turned out to be a lot harder than expected. I could not find an easy way to do it so I created a function to generate certificates, request them online from a Certificate Authority and import the certificate.
Get-Certificate Testing
I wanted this to use default PowerShell features and not depend on any custom modules. Get-Certificate introduced with Windows 2012 looked promising but seems to be designed for computer and personal certificate requests. I encountered a few problems:
- it does not allow for creating exportable certificates
- no way to specify credentials unless you have Certificate Enrollment Policy (CEP) and Certificate Enrollment Services (CES) configured with username and password authentication
- in RPC ldap: mode it will only use the local computer account (for example ‘server1$’) or user credentials to request certificates
- does not work with older Windows versions
This meant that using the command below I could request and be issued a certificate – as long as I modified the CA Web Server certificate template security permissions to allow either the computer account or all Authenticated Users the ‘Enroll’ right.
[sourcecode language=”powershell”]
Get-Certificate -template "WebServer" -CertStoreLocation cert:\LocalMachine\My -SubjectName "CN= adfs2.showcase.kloud.com.au" -DnsName "adfs2.showcase.kloud.com.au" -Url ldap:
[/sourcecode]
The certificate could not be exported which didn’t meet my needs and this is not supported on Windows Server 2008 R2 or older machines. CEP and CES are also something we do not see being used a lot, especially not with username and password authentication.
What the Function Does
The function I created has been tested on Windows 2008 R2, Windows 2012 and Windows 2012 R2 Preview, although it should work on any computer with PowerShell 2.0 or later (version 1 not tested) that supports certreq.exe. I am using a function instead of a script as I think it can easily be dropped into other scripts without creating further dependencies. To satisfy the requirements I had, the script can be used in the following ways:
- offline mode to create a certificate request with a single name (Subject only)
- offline mode to create a certificate request with SANs
- online mode to create a certificate request with a single name, request a certificate directly from a Windows Enterprise Certificate Authority and import the certificate
- online mode to create a certificate request with SANs, request a certificate directly from a Windows Enterprise Certificate Authority and import the certificate
The certificates created are exportable and contain the private key.
Function
[sourcecode language=”powershell”]
function New-CertificateRequest {
param (
[Parameter(Mandatory=$true, HelpMessage = "Please enter the subject beginning with CN=")]
[ValidatePattern("CN=")]
[string]$subject,
[Parameter(Mandatory=$false, HelpMessage = "Please enter the SAN domains as a comma separated list")]
[array]$SANs,
[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
[string]$OnlineCA,
[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
[string]$CATemplate = "WebServer"
)
### Preparation
$subjectDomain = $subject.split(‘,’)[0].split(‘=’)[1]
if ($subjectDomain -match "\*.") {
$subjectDomain = $subjectDomain -replace "\*", "star"
}
$CertificateINI = "$subjectDomain.ini"
$CertificateREQ = "$subjectDomain.req"
$CertificateRSP = "$subjectDomain.rsp"
$CertificateCER = "$subjectDomain.cer"
### INI file generation
new-item -type file $CertificateINI -force
add-content $CertificateINI ‘[Version]’
add-content $CertificateINI ‘Signature="$Windows NT$"’
add-content $CertificateINI ”
add-content $CertificateINI ‘[NewRequest]’
$temp = ‘Subject="’ + $subject + ‘"’
add-content $CertificateINI $temp
add-content $CertificateINI ‘Exportable=TRUE’
add-content $CertificateINI ‘KeyLength=2048’
add-content $CertificateINI ‘KeySpec=1’
add-content $CertificateINI ‘KeyUsage=0xA0’
add-content $CertificateINI ‘MachineKeySet=True’
add-content $CertificateINI ‘ProviderName="Microsoft RSA SChannel Cryptographic Provider"’
add-content $CertificateINI ‘ProviderType=12’
add-content $CertificateINI ‘SMIME=FALSE’
add-content $CertificateINI ‘RequestType=PKCS10’
add-content $CertificateINI ‘[Strings]’
add-content $CertificateINI ‘szOID_ENHANCED_KEY_USAGE = "2.5.29.37"’
add-content $CertificateINI ‘szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"’
add-content $CertificateINI ‘szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"’
if ($SANs) {
add-content $CertificateINI ‘szOID_SUBJECT_ALT_NAME2 = "2.5.29.17"’
add-content $CertificateINI ‘[Extensions]’
add-content $CertificateINI ‘2.5.29.17 = "{text}"’
foreach ($SAN in $SANs) {
$temp = ‘_continue_ = "dns=’ + $SAN + ‘&"’
add-content $CertificateINI $temp
}
}
### Certificate request generation
if (test-path $CertificateREQ) {del $CertificateREQ}
certreq -new $CertificateINI $CertificateREQ
### Online certificate request and import
if ($OnlineCA) {
if (test-path $CertificateCER) {del $CertificateCER}
if (test-path $CertificateRSP) {del $CertificateRSP}
certreq -submit -attrib "CertificateTemplate:$CATemplate" -config $OnlineCA $CertificateREQ $CertificateCER
certreq -accept $CertificateCER
}
}
[/sourcecode]
Example Usage
- offline mode to create a certificate request with a single name (Subject only)
[sourcecode language=”powershell”]
New-CertificateRequest -subject CN=mail1.showcase.kloud.com.au
New-CertificateRequest -subject CN=mail1.showcase.kloud.com.au,OU=IT,O=Kloud,L=Melbourne,S=Victoria,C=AU
[/sourcecode]
- offline mode to create a certificate request with a subject and SANs
[sourcecode language=”powershell”]
New-CertificateRequest -subject CN=mail2.showcase.kloud.com.au -SANs mail2.showcase.kloud.com.au,adfs.showcase.kloud.com.au
[/sourcecode]
The output of the two offline requests above are shown in the picture below along with the Certificate Enrollment Requests which are waiting to be completed. These .req files can then be submitted to an internal or Public Certificate Authority.
- online mode to create a certificate request with SANs, request a certificate directly from a Windows Enterprise Certificate Authority and import the certificate
[sourcecode language=”powershell”]
New-CertificateRequest -subject CN=mail3.showcase.kloud.com.au -SANs mail3.showcase.kloud.com.au,adfs.showcase.kloud.com.au -OnlineCA dc01.kloud.internal\kloud-Showcase-CA
[/sourcecode]
The output of the online request can be seen below with the issued Certificate:
Usage Notes
- Run PowerShell as administrator to gain elevated rights
- All files will be generated and looked for in the local directory
- For backward compatibility with some applications, the first SAN should match the Subject CN= value. Some applications require this and some browsers will ignore the subject if SANs exist
- The Certificate Authority does not need any CEP, CES or web enrolment configured – it must accessible via RPC
Walk Through
The function takes 1 mandatory parameter and 3 additional parameters:
- Subject is required for the certificate subject. The minimum requirement is ‘CN=host.domain.com’ and has basic validation to check it contains ‘CN=”
- SANs is a comma separated list of Subject Alternate Name entries
- OnlineCA is an online Certificate Authority to request from in the format ‘host\Name’ for example ‘dc01.kloud.internal\kloud-Showcase-CA’
- CATemplate which is set to ‘WebServer’ which is the default but allows for modification
Based on the subject name (excluding ‘CN=’) the certificate request .ini file, request file, response file and certificate file are generated. A new .ini file is created using a basic template depending on whether a single name certificate or SANs are specified and written to disk.
Example of a single name certificate request .ini
[sourcecode language=”powershell”]
[Version]
Signature="$Windows NT$"
[NewRequest]
Subject="CN=mail1.showcase.kloud.com.au"
Exportable=TRUE
KeyLength=2048
KeySpec=1
KeyUsage=0xA0
MachineKeySet=True
ProviderName="Microsoft RSA SChannel Cryptographic Provider"
ProviderType=12
SMIME=FALSE
RequestType=PKCS10
[Strings]
szOID_ENHANCED_KEY_USAGE = "2.5.29.37"
szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"
szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"
[/sourcecode]
Example of a Subject Alternate Name certificate request .ini
[sourcecode language=”powershell”]
[Version]
Signature="$Windows NT$"
[NewRequest]
Subject="CN=mail2.showcase.kloud.com.au"
Exportable=TRUE
KeyLength=2048
KeySpec=1
KeyUsage=0xA0
MachineKeySet=True
ProviderName="Microsoft RSA SChannel Cryptographic Provider"
ProviderType=12
SMIME=FALSE
RequestType=PKCS10
[Strings]
szOID_ENHANCED_KEY_USAGE = "2.5.29.37"
szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"
szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"
szOID_SUBJECT_ALT_NAME2 = "2.5.29.17"
[Extensions]
2.5.29.17 = "{text}"
_continue_ = "dns=mail2.showcase.kloud.com.au&"
_continue_ = "dns=adfs.showcase.kloud.com.au&"
[/sourcecode]
Note: The script uses the RequestType of PKCS10 instead of CMC which is given in some Microsoft examples such as http://support.microsoft.com/kb/931351. CMC allows for key archival but is not supported by many public Certificate Authorities.
The certificate is then requested using ‘certreq –new’ based on the .ini file and output as a .req file (old versions of the .req file are deleted). If an online request was specified, any old versions of the response .rsp and certificate .cer files are deleted, the certificate is requested from the CA and written to disk as a .cer file. The final task is to import the issued certificate which places it into the ‘Local Computer / Personal / Certificates’ folder with the private key.
I now have a reusable function I can put into any script or into a PowerShell profile and easily request a certificate.
Very useful!
Great article I found it extremely helpful in my testing environment, well written and simple to follow
Awesome stuff
Thanks a lot, helps a lot now only for certs but to learn powershell:)