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
    }
}

Mar 19, 2017

Day 0: Blog opens

Finally, a blog about my adventures in the land of IT !

During my 10+ years in IT I've found many blogs and posts very helpful.As my daily activities in the IT world consist of many different kind of tasks and many of those are not so trivial, I decided it's time to return something back to the community. 

So my plan is to write something not about basic instructions of setting things up but more of those Level 400 things that really counts. Like "why and how should you setup tempdb in MSSQL" or "SCCM and what I did with CI/CB".

Active Directory, oh man! "Creating shadow groups that automatically update" will be one interesting topic. "Running AD without Domain Admin rights (aka 'Ditch Admin Rights' @samilaiho)" is my target and it already has risen some serious opinions against it in my current workplace. PKI isn't that hard as you might think.

PowerShell - sure. I'll show you what I did and why. Scripts and tools. I do, when ever possible, everything remotelly and with PS. And PKI with PowerShell is sooo easy.

Sure there will be more topics and posts, in the meanwhile please follow and DM me in Twitter @arisaastamoinen