Although Microsoft has written volumes of tools and scripts in PowerShell to aide in the day-to-day administration, some gaps remain in the available tools.
When using the AD LDS feature in Windows, of course it is always recemmended to use secure communications (LDAPS). This is one feature that requires the use of a service-based certificate. Enter PowerShell and a solution I drafted to fill this gap.
In a previous role, I drafted a PowerShell module to help manage certificates and utilize this to manage the certificates via code.
It was challenging to develop this module since there is limited information about managing service based certificates online and from support.
The solution was to register several types, create an array of “application” policies (OIDs), map the extended key usages, and then execute a function to import the certficate for a service.
The function itself to import the certificate is shown below (the other supporting functions and types are included in the complete script found on GitHub):
function Add-Certificate2
{
[cmdletBinding(DefaultParameterSetName = 'File')]
param(
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'File')][string]$Path,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByteArray')][byte[]]$Cert,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][System.Security.Cryptography.X509Certificates.StoreName]$Store,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][System.Security.Cryptography.X509Certificates.CertStoreLocation]$Location,
[Parameter(ValueFromPipelineByPropertyName = $true)][string]$ServiceName,
[Parameter(ValueFromPipelineByPropertyName = $true)][ValidateSet('CER', 'PFX')][string]$CertificateType = 'PFX',
[Parameter(Mandatory = $true)][securestring]$Password
)
process
{
if ($Location -eq 'CERT_SYSTEM_STORE_SERVICES' -and (-not $ServiceName))
{
Write-Output "Please specify a ServiceName if the Location is set to 'CERT_SYSTEM_STORE_SERVICES'"
return
}
$storePath = $Store
if ($Path -and -not (Test-Path -Path $Path))
{
Write-Output "The path '$Path' does not exist."
continue
}
if ($ServiceName)
{
if (-not (Get-Service -Name $ServiceName))
{
Write-Output "The service '$ServiceName' could not be found."
return
}
else
{
$RealSvcName = (Get-Service -Name $ServiceName).Name
$storePath = "$RealSvcName\$Store"
}
}
$storeProvider = [System.Security.Cryptography.X509Certificates.CertStoreProvider]::CERT_STORE_PROV_SYSTEM_REGISTRY
$Location = $Location -bor [System.Security.Cryptography.X509Certificates.CertStoreFlags]::CERT_STORE_MAXIMUM_ALLOWED_FLAG
$storePtr = [System.Security.Cryptography.X509Certificates.Win32]::CertOpenStore($storeProvider, 0, 0, $Location, $storePath)
if ($storePtr -eq [System.IntPtr]::Zero)
{
Write-Output "Store '$Store' in location '$Location' could not be opened."
return
}
$s = New-Object System.Security.Cryptography.X509Certificates.X509Store($storePtr)
$newCert = if ($Path)
{
if ($CertificateType -eq 'CER')
{
New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($Path) -ErrorAction Stop
}
else
{
New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($Path, $password, ('Exportable', 'PersistKeySet')) -ErrorAction Stop
}
}
else
{
if ($CertificateType -eq 'CER')
{
New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(, $Cert) -ErrorAction Stop
}
else
{
New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($Cert, $password, ('Exportable', 'PersistKeySet')) -ErrorAction Stop
}
}
if (-not $newCert)
{
return
}
Write-Output "Store '$Store' in location '$Location' knows about $($s.Certificates.Count) certificates before import."
$s.Add($newCert)
Write-Output "Store '$Store' in location '$Location' knows about $($s.Certificates.Count) certificates after import."
[void][System.Security.Cryptography.X509Certificates.Win32]::CertCloseStore($storePtr, 0)
}
}
Once the certificate itself has been imported the “NT AUTHORITY\NETWORK SERVICE” must be given read-only access to the certificates private key via the path “C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys” (presuming this is not different in a given environment) This function worked as written in Windows 2016. It has not yet been tested on Windows Server 2019 or Windows Server 2022.
The source script can be found here: https://github.com/cjramseyer/CertMgmt
Leave a comment