Mar 22, 2017

PKI & PowerShell: Request, Issue and Retrieve certificate with PowerShell

Updated 2017-06-08: Fixed some bugs (d'oh!) and added option to export certificate to .pfx

Assuming you have Microsoft PKI in place in your organization, then requesting webserver certificates is easy. But when you suddenly got 20 or 2000 new certificates that you need, clicking through the Microsoft Active Directory Certificate Services dialogues and submitting requests, manually issuing pending requests (you should not automatically issue a certificate with any subject name, only after approval - hey, it's security!) and copying the certificates back to you computer - starts to feel like too much work.

I stumbled upon a nice PowerShell script which after some modifications I turned into a custom function "New-MyCompanyCertificate". To the function you can pass hostname, SANs, IPAddresses, Emails and other values as parameters and it will generate the certificate request, submit it, issue pending certificate request and retrieve the newly generated certificate for you. With this updated version (June 8th, 2017) you can export the certificate to .pfx file. Nice, huh?

I hope you'll find this helpful, please ping me at Twitter @arisaastamoinen

Cheers, Ari


function New-MyCompanyCertificate{
<#
.Synopsis
   Create new Certificate
.DESCRIPTION
   Create new MyCompany CA issued certificate
.EXAMPLE
   New-MyCompanyCertificate -Hostname my-dmz-host -ExportPFX -Verbose
   This will generate new .cer and .pfx certificate files to be copied and imported to host "my-dmz-host"
.EXAMPLE
   New-MyCompanyCertificate -Hostname MyHost -FriendlyName "My Super Host" -SubjectAlternateName "host1","host2" -OrganizationalUnit "Dev"
  
   Creates a new certificate for MyHost with a frienldy name My Super Host. Certificate has two alternate names, issuing organization unit is Dev.
.EXAMPLE
    New-MyCompanyCertificate -Hostname mhheltps1 -FriendlyName "MyCompany Super Server" -SubjectAlternateName "aaa1.local","aaa1.dmz","aaa1.dmz.local" -Email admin@MyCompany.com -IPAddress 172.20.0.34 -ExportPFX -Verbose
    Create new certificate for My Super Server with three alternate names
#>
    [CmdletBinding()]
    param(
        [Parameter(ParameterSetName='Default',
            Mandatory=$true,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Hostname,
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$FriendlyName=$Hostname,
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$SubjectAlternateName,
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Email,
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$IPAddress,
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Organization='MyCompany Co.',
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$OrganizationalUnit='IT',
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Locality='City',
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$State='Province',
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Country='FI', # hell yeah!
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet("mycompanyca01.local\MyCompany CA 01", "mycompanyca02.local\MyCompany CA 02", IgnoreCase=$true)]
        [string]$CertificateAuthority='mycompanyca01.local\MyCompany CA 01',
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [int]$KeyLength = 2048,
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet("MyCompanyWebServer", IgnoreCase=$true)]
        [string]$CertificateTemplate = "MyCompanyWebServer",
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [switch]$ExportPFX=$true,
        [Parameter(ParameterSetName='Default',
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [string]$PFXPassword='Passw0rd!'
    )
    [string]$workDir = $env:Temp
    [string]$outDir = 'C:\Temp'
    [string]$fileBaseName = $Hostname -replace "\.", "_"
    $fileBaseName = $fileBaseName -replace "\*", ""
    [string]$infFile = $workDir + "\" + $fileBaseName + ".inf"
    [string]$requestFile = $workDir + "\" + $fileBaseName + ".req"
    [string]$CertFileOut = $workDir + "\" + $fileBaseName + ".cer"
    [string]$PFXFileOut = $workDir + "\" + $fileBaseName + ".pfx"
    [string]$certSubject = "CN=$Hostname, OU=$OrganizationalUnit, O=$Organization, L=$Locality, S=$State, C=$Country"
    Try {
        Write-Verbose "Creating the certificate request information file ..."
        $inf = @"
[Version]
Signature="`$Windows NT`$"
[NewRequest]
Subject = "$certSubject"
KeySpec = 1
KeyLength = $Keylength
Exportable = TRUE
FriendlyName = "$FriendlyName"
MachineKeySet = TRUE
SMIME = False
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0
"@
if ($SubjectAlternateName -ne $null -or $Email -ne $null -or $IPAddress -ne $null) {
        $inf += @"
`n
[Extensions]
2.5.29.17 = "{text}"`n
"@
    Write-Verbose "Include Hostname in SAN"
        $inf += @"
_continue_ = "dns=$Hostname&"`n
"@
if ($SubjectAlternateName -ne $null) {   
    $arr = $SubjectAlternateName.Split(",")
    foreach ($item in $arr) {
        $inf += @"
_continue_ = "dns=$item&"`n
"@
    }
} # if SubjectAlternateName
if ($Email -ne $null) {   
    $arr = $Email.Split(",")
    foreach ($item in $arr) {
        $inf += @"
_continue_ = "email=$item&"`n
"@
    }
} # if Email
if ($IPAddress -ne $null) {   
    $arr = $IPAddress.Split(",")
    foreach ($item in $arr) {
        $inf += @"
_continue_ = "ipaddress=$item&"`n
"@
    }
} # if IPAddress
}
        write-verbose "Generating INF: $inf"
        $inf | Set-Content -Path $infFile
        Write-Verbose "Creating the certificate request ..."
        $catchOutput = [string](& certreq.exe -new "$infFile" "$requestFile")
        Write-Verbose $catchOutput
        Write-Verbose "Submitting the certificate request to the certificate authority ..."
        $catchOutput = [string](& certreq.exe -submit -config "$CertificateAuthority" -attrib "CertificateTemplate:$($CertificateTemplate)" "$requestFile" "$CertFileOut")
        Write-Verbose $catchOutput
       
        $findString = [string]$catchOutput
        if ($findString -match "RequestId:(?<requestid>.*)RequestId:") {
            $RequestId = [int]$matches['requestid']
            Write-Verbose "Found RequestID ($RequestId) in reponse, trying to re-submit and recieve"
            Get-ChildItem "$workDir\$fileBaseName.rsp" | remove-item
            $catchOutput = [string](& certutil.exe -config $CertificateAuthority -resubmit $RequestId )
            Write-Verbose [$catchOutput
           
            $catchOutput = [string](& certreq.exe -config $CertificateAuthority -Retrieve $RequestId $CertFileOut)
            Write-Verbose $catchOutput
           
            write-Verbose "Recieved certificate:"
            Get-Content $CertFileOut
            Copy-Item $CertFileOut $outDir + "\" + $fileBaseName.cer
        }
        if ($ExportPFX) {
            Write-Verbose "Exporting certificate to PFX file"
            Write-Verbose "Importing certificate to Personal Store"
            $catchOutput = [string](& certutil.exe -addstore -f MY $CertFileOut)
            Write-Verbose $catchOutput
            Write-Verbose "Repairing Personal Store"
            $catchOutput = [string](& certutil.exe -repairstore MY $Hostname)
            Write-Verbose $catchOutput
            Write-Verbose "Exporting certificate to PFX file"
            $catchOutput = [string](& certutil.exe -p $PFXPassword -exportPFX $Hostname $PFXFileOut)
            Write-Verbose $catchOutput
            Write-Verbose "Delete certificate from Personal Store"
            $catchOutput = [string](& certutil.exe -privatekey -delstore MY $Hostname)
            Write-Verbose $catchOutput
            Copy-Item $PFXFileOut $outDir + "\" + $fileBaseName.pfx
        }
    }
    Finally {
        Get-ChildItem "$workDir\$fileBaseName.*" | remove-item
    }
}

No comments:

Post a Comment