Thursday, December 20, 2018

CertAuto

For a developer environment I had a few certificates that needed automatic renewal at certain intervals. The script below uses the PSPKI module to check if certificates are expiring on IIS-Site in X days and if so, renews the agains a local certification authority.
The PSPKI -module is really god for sending and approving certification requests  via Powershell when Microsofts own tools leaves room for improvement.

This is really only for a testenviroment and offline-enviroments.
Folder structure needs to created to look below




Certini contains the template for the certificate like below for example
 [Version]
Signature = "$Windows NT$"
[NewRequest]
Subject = "C=US,S=CA,L=OHIO,O=Fabrikam,OU=IT,CN=ajax.aspnetcdn.com"
Exportable = TRUE
KeyLength = 4096
KeySpec = 1
KeyUsage = 0xa0
MachineKeySet = True
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
Silent = True
SMIME = False
RequestType = PKCS10
FriendlyName = "ajax.aspnetcdn.com"

$scriptPath = $(split-path -Parent $myinvocation.MyCommand.Definition)
Import-Module "$scriptPath\certAutoTools.psm1" -Force
import-module "$($scriptPath)\PSPKI-v3.4.1.0\PSPKI.psm1"
#settings
$logFile = "$($scriptPath)\lastRun.txt"
$destinationPath = "$($scriptPath)\generatedCertReq\"
$sourcePath = "$($scriptPath)\certIni" #where Ini-files are stored
$certPath = "$($scriptPath)\generatedCerts" #location for .cer output
$iisSites = "IISSite1","IISSite2"
$rootCertAuth = "myRootCertAuthority"
#settings end
Start-Transcript -Path $logFile
#check if certrenewal is needed
$certificate = get-certOnIIS -IisSites $iisSites |sort-object notafter |select -first 1
$certExpires = get-certExpires -certificate $certificate -days 238
if ($certExpires) {
write-output "Found expiring certificates, moving on with renewal"
#create certs on frontends
new-CertRequest -destinationPath $destinationPath -sourcePath $sourcePath
#renew certs on certsrv
submit-certRequest -sourcePath $destinationPath -certAuthName $rootCertAuth -outputPath $certPath
#import certs on frontends
$createdCerts = import-certs -sourcePath $certPath
#import certs to array
$newCerts = get-certsFromFile -certPath $certPath
#get current cert on sites
$iisMappings = get-iisBindingCerts -iisSites $iisSites
#set cert on iis-sites
set-sslBindings -iismappings $iisMappings -newCerts $newcerts
#clean up
Get-ChildItem -Path $certPath |copy-Item -Destination "$($certPath)\old\" -Force
Get-ChildItem -path $certpath |where-object {$_.psiscontainer -eq $false} |Remove-Item
Get-ChildItem -Path $destinationPath -Filter *.txt |Remove-Item
#remove previous certs
foreach ($mapping in $iismappings) {
Get-ChildItem -Path Cert:\LocalMachine\my |where-object {$_.Thumbprint -like $mapping.certhash} |remove-item
}
}#end check certexpires
else {
write-output "No certificate needed renewals"
}
Stop-Transcript
function new-CertRequest {
param(
$destinationPath,
$sourcePath
)
$outputFolder = $destinationPath
$items = Get-ChildItem -Path $sourcepath -Filter *.ini
$date= get-date -Format "yyyyMMdd_HHmm"
foreach ($item in $items) {
write-output "Creating certificate request for $($item.name)"
certreq.exe -new $item.VersionInfo.FileName "$outputFolder$($item.name.trimend('.ini'))_$($env:COMPUTERNAME)_$date.txt"
$filesToCopy += "$outputFolder$($item.name.trimend('.ini'))_$($env:COMPUTERNAME)_$date.txt"
write-output "$outputFolder$($item.name.trimend('.ini'))_$($env:COMPUTERNAME)_$date.txt"
}
}
function submit-certRequest {
param(
$sourcePath,
$certAuthName,
$outputPath,
$latest = 4
)
$certReqs = Get-ChildItem -Path $sourcePath -Filter "*.txt" |Sort-Object lastwritetime |select -First $latest
$ca = Get-CertificationAuthority -Name $certAuthName
$ids = @()
foreach ($certReq in $certReqs) {
$request = Submit-CertificateRequest -Path $certReq.FullName -CertificationAuthority $ca
$ids += $request.RequestID
}
Get-PendingRequest -CertificationAuthority $ca |select -first 1
foreach ($id in $ids) {
Get-PendingRequest -CertificationAuthority $ca -RequestID $id |Approve-CertificateRequest
$mycert = Get-IssuedRequest -CertificationAuthority $ca -RequestID $id |Receive-Certificate -Path $outputPath -Force
$File = Get-ChildItem -Path "$($outputPath)\RequestID_$id.cer"
$fileName = "cert_$($mycert.Subject.split(".")[-2]).cer"
Rename-Item -Path $file.FullName -NewName $fileName
}
}
function import-certs {
param(
$sourcePath
)
$items = Get-ChildItem -Path $sourcePath -Filter *.cer
if ($items.count -ne $null -and $items.count -gt 0) {
foreach ($item in $items) {
write-output "importing $($item.versioninfo.filename)..."
certreq.exe -accept -machine $item.VersionInfo.FileName
}
}
else { write-output "No files found in $sourcepath"}
}
function get-certOnIIS {
param(
$IisSites
)
$certCollection = @()
foreach ($IisSite in $IisSites) {
$bindings = Get-WebBinding -Name $IisSite |where-object {$_.protocol -like "https"}
foreach ($binding in $bindings) {
write-output "Adding $($binding.certificatehash) on $IisSite"
$certCollection += (Get-ChildItem -Path cert:\ -Recurse |where-object {$_.Thumbprint -like $binding.certificateHash})
}
}
return $certCollection
}
function get-certExpires {
param(
[System.Security.Cryptography.X509Certificates.X509Certificate]$certificate,
[int]$days
)
$timspan = New-TimeSpan -Start (get-date) -End $certificate.NotAfter
if ($timspan.Days -le $days) {
return $false
}
else {
return $true
}
}
function get-iisBindingCerts {
param(
$iisSites
)
$mappings = @()
foreach ($websiteName in $iisSites) {
$allbindings = Get-WebBinding -Name $websiteName -Protocol https
foreach ($allbinding in $allbindings) {
$currentCert = Get-ChildItem -Path Cert:\LocalMachine\my |where-object {$_.Thumbprint -eq $allbinding.certificateHash}
$mappings += new-object psobject -Property @{iis=$websiteName;bindingInformation=$allbinding.bindingInformation;certhash=$allbinding.certificateHash;friendlyName=$currentCert.FriendlyName;dnsName=$currentCert.DnsNameList.unicode;subject=$currentCert.Subject}
}
}
return $mappings
}
function get-certsFromFile {
param(
$certPath
)
$items = Get-ChildItem -Path $certpath -Filter *.cer
$collection = @()
foreach ($item in $items) {
if ($item -ne $null) {
$crt = new-object System.Security.Cryptography.X509Certificates.X509Certificate
$crt.Import($item.VersionInfo.FileName)
$collection += new-object psobject -Property @{thumbprint=$crt.GetCertHashString();subject=$crt.Subject}
}
}
return $collection
}
function set-sslBindings {
param(
$iisMappings,
$newCerts
)
write-output "has $($iisMappings.count) iismappings and $($newcerts.count) newcerts"
foreach ($iisMapping in $iisMappings) {
$httpsBinding = get-webBinding -name $iisMapping.iis -protocol https |where-object {$_.bindingInformation -like $iisMapping.bindingInformation}
if ($httpsBinding -ne $null) {
$bindCert = $newCerts |where-object {$_.subject -like $iisMapping.subject} |select -first 1
if ($bindCert -ne $null) {
write-output "adding $($bindCert.thumbprint) to $($iismapping.bindingInformation)"
$httpsBinding.AddSslCertificate($bindCert.Thumbprint,'my')
write-output "binding $($httpsBinding.bindingInformation) to $($bindCert.subject)"
}
else {
write-output "Couldn't bind $($httpsBinding.bindingInformation) to $($bindCert.subject)"
}
}
else {write-output "webbinding empty $($iismapping.iis)" $($httpsBinding.getttype())}
}
}


References:
https://www.sysadmins.lv/projects/pspki/default.aspx  - PSPKI Module

Powershell and Uptimerobot

Uptimerobot can be quite tedious when you need to update many monitors at once. For example say you bought the license for Uptimerobot and n...