Functions/Certificate/New-DomainSignedCertificate.ps1
<#
.SYNOPSIS Create a new certificate signed by a domain-based enterprise CA. .DESCRIPTION Use the tools certreq.exe and optionally openssl.exe to request a domain signed certificate. The certificate is always a SAN certificate where wildcard entries and e.g. IP adresses are allowed. The subject is inserted at the first position of the SAN list. The certificate will be exported in Windows compatible binary formats as X.509 DER (.cer) and PKCS #12 (.pfx). If the flag -Base64 is specified, the certifcate is converted into Linux/Unix compatible Base64 PEM format as X.509 PEM (.pem) and RSA (.key). .EXAMPLE PS C:\> New-DomainSignedCertificate Create a new certificate. PowerShell will prompt for subject, dns name and password. .EXAMPLE PS C:\> New-DomainSignedCertificate -Subject 'contoso.com' -DnsName 'contoso.com', '*.contoso.com' Create a new wildcard SAN certificate for contoso. .EXAMPLE PS C:\> New-DomainSignedCertificate -Subject 'contoso.com' -DnsName 'contoso.com', '*.contoso.com' -Base64 Create a new wildcard SAN certificate for contoso and include Linux/Unix formatted certificate and key files. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function New-DomainSignedCertificate { [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])] param ( # Subject of the certificate, without the 'CN=' prefix. This subject is # always included as dns name or IP address in the subject alternative # name. [Parameter(Mandatory = $true, Position = 0)] [System.String] $Subject, # Add dns names to the subject alternative name. [Parameter(Mandatory = $false)] [AllowEmptyCollection()] [System.String[]] $DnsName, # Add IP addresses to the subject alternative name. [Parameter(Mandatory = $false)] [AllowEmptyCollection()] [System.String[]] $IPAddress, # Optional firendly name to set on the imported certificate. [Parameter(Mandatory = $false)] [System.String] $FriendlyName, # Specify the key usage extensions. [Parameter(Mandatory = $false)] [ValidateSet('ServerAuthentication', 'ClientAuthentication')] [System.String[]] $EnhancedKeyUsage = 'ServerAuthentication', # Length of the private key, by default 2048. [Parameter(Mandatory = $false)] [System.Int32] $KeyLength = 2048, # Name of the CA certificate template to use. [Parameter(Mandatory = $false)] [System.String] $CertificateTemplate = 'InternalWebServer', # Password to encrypt the pfx file. [Parameter(Mandatory = $true)] [System.Security.SecureString] $Password, # Option to export the certificate as Base64. Requires openssl.exe. [Parameter(Mandatory = $false)] [switch] $Base64, # Path to the working folder where the files will be stored. [Parameter(Mandatory = $false)] [System.String] $Path = (Get-Location).Path, # Overwrite the existing files. [Parameter(Mandatory = $false)] [switch] $Force ) $ErrorActionPreference = 'Stop' try { $activity = "Generate Certificate for CN=$Subject" Write-Progress -Activity $activity -Status 'Setup' -PercentComplete 0 # Check if the current user is admin. This is required to request the # certifcate and create the private key. if (-not (Test-AdministratorRole)) { throw 'Access denied. Restart this command as administrator.' } # Check and get native commands $certReqCmd = Get-CommandPath -Name 'certreq.exe' if ($Base64.IsPresent) { $openSslCmd = Get-CommandPath -Name 'openssl.exe' -WarningMessage 'Download OpenSSL for Windows from https://slproweb.com/products/Win32OpenSSL.html' } # If the friendly name wasn't specified, use the subject if (-not $PSBoundParameters.ContainsKey('FriendlyName')) { $FriendlyName = $Subject } # Append subject to the dns name or ip address if ($Subject -match '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$') { if ($IPAddress -notcontains $Subject) { $IPAddress = @($Subject) + $IPAddress } } else { if ($DnsName -notcontains $Subject) { $DnsName = @($Subject) + $DnsName } } # Prepare certificate definition $policy = @() $policy += '[Version]' $policy += 'Signature = "$Windows NT$"' $policy += '' $policy += '[NewRequest]' $policy += 'Subject = "CN={0}"' -f $Subject $policy += 'Exportable = TRUE' $policy += 'KeyLength = {0}' -f $KeyLength $policy += 'KeySpec = 1' $policy += 'KeyUsage = 0xA0' $policy += 'MachineKeySet = True' $policy += 'ProviderName = "Microsoft RSA SChannel Cryptographic Provider"' $policy += 'RequestType = PKCS10' $policy += '' $policy += '[EnhancedKeyUsageExtension]' foreach ($currentEnhancedKeyUsage in $EnhancedKeyUsage) { switch ($currentEnhancedKeyUsage) { 'ServerAuthentication' { $policy += 'OID = 1.3.6.1.5.5.7.3.1' } 'ClientAuthentication' { $policy += 'OID = 1.3.6.1.5.5.7.3.2' } } } $policy += '' $policy += '[Extensions]' $policy += '2.5.29.17 = "{text}"' foreach ($currentDnsName in $DnsName) { if (-not [System.String]::IsNullOrEmpty($currentDnsName)) { $policy += '_continue_ = "DNS={0}&"' -f $currentDnsName } } foreach ($currentIPAddress in $IPAddress) { if (-not [System.String]::IsNullOrEmpty($currentIPAddress)) { $policy += '_continue_ = "IPAddress={0}&"' -f $currentIPAddress } } $policy += '' $policy += '[RequestAttributes]' $policy += 'CertificateTemplate = {0}' -f $CertificateTemplate if ($PSCmdlet.ShouldProcess("CN=$Subject", 'Create')) { # Trim the path and cleanup existing files if the -Force parameter # is specified. Else stop the script with an exception. $Path = $Path.TrimEnd('\') $extensions = 'inf', 'req', 'rsp', 'cer', 'pfx' if ($Base64.IsPresent) { $extensions += 'pem', 'key' } foreach ($extension in $extensions) { $filePath = "$Path\$Subject.$extension" if (Test-Path -Path $filePath) { if ($Force.IsPresent) { Remove-Item -Path $filePath -Force -Confirm:$false } else { throw "The file $filePath already exists!" } } } # Step 1 # Store the policy file Write-Verbose "Create policy file $Subject.inf" Write-Progress -Activity $activity -Status "Create policy file $Subject.inf" -PercentComplete 14 Set-Content -Path "$Path\$Subject.inf" -Value $policy # Step 2 # Create a certificate request and store the private key in the # current user or computer session (if admin) Write-Verbose "Create request file $Subject.req" Write-Verbose "> certreq.exe -new -q -f `"$Path\$Subject.inf`" `"$Path\$Subject.req`"" Write-Progress -Activity $activity -Status "Create request file $Subject.req" -PercentComplete 28 $result = (& $certReqCmd -new -q -f "`"$Path\$Subject.inf`"" "`"$Path\$Subject.req`"") if ($Global:LASTEXITCODE -ne 0) { throw "Failed to create the certificate request!`n`n$result" } # Step 3 # Submit the certificate request to the CA Write-Verbose "Sign request and export to $Subject.cer" Write-Verbose "> certreq.exe -submit -f `"$Path\$Subject.req`" `"$Path\$Subject.cer`"" Write-Progress -Activity $activity -Status "Sign request and export to $Subject.cer" -PercentComplete 28 $result = (& $certReqCmd -submit -f "`"$Path\$Subject.req`"" "`"$Path\$Subject.cer`"") if ($Global:LASTEXITCODE -ne 0) { throw "Failed to submit the certificate request!`n`n$result" } # Step 4 # Accept the request and import it into the local cert store Write-Verbose "Accept and import signed certificate $Subject.cer" Write-Verbose "> certreq.exe -accept -q `"$Path\$Subject.cer`"" Write-Progress -Activity $activity -Status "Accept and import signed certificate $Subject.cer" -PercentComplete 42 $result = (& $certReqCmd -accept -q "`"$Path\$Subject.cer`"") if ($Global:LASTEXITCODE -ne 0) { throw "Failed to accept the certificate request!`n`n$result" } # Step 5 # Export certificate as PKCS #12 (.pfx) file Write-Verbose "Export the certificate as PKCS #12 to $Subject.pfx" Write-Verbose "> Export-PfxCertificate -FilePath '$Path\$Subject.pfx'" Write-Progress -Activity $activity -Status "Export the certificate as PKCS #12 to $Subject.pfx" -PercentComplete 56 # Extract thumbprint from the cer file $thumbprint = Get-PfxCertificate -FilePath "$Path\$Subject.cer" | Select-Object -ExpandProperty 'Thumbprint' # Export the pfx protected by a password Get-Item -Path "Cert:\LocalMachine\My\$thumbprint" | Export-PfxCertificate -FilePath "$Path\$Subject.pfx" -Password $Password | Out-Null # Step 6 # Export certificate as X.509 PEM (.pem) file if ($Base64.IsPresent) { Write-Verbose "Export the certificate as X.509 PEM to $Subject.pem" Write-Verbose "> openssl.exe pkcs12 -passin pass:`"***`" -in `"$Path\$Subject.pfx`" -clcerts -nokeys -out `"$Path\$Subject.pem`"" Write-Verbose "> openssl.exe x509 -in `"$Path\$Subject.pem`" -out `"$Path\$Subject.pem`"" Write-Progress -Activity $activity -Status "Export the certificate as X.509 PEM to $Subject.pem" -PercentComplete 70 $result = (& $openSslCmd pkcs12 -passin "pass:`"$(Unprotect-SecureString -SecureString $Password)`"" -in "`"$Path\$Subject.pfx`"" -clcerts -nokeys -out "`"$Path\$Subject.pem`"") if ($Global:LASTEXITCODE -ne 0) { throw "Failed to convert the certificate from pfx to pem!" } $result = (& $openSslCmd x509 -in "`"$Path\$Subject.pem`"" -out "`"$Path\$Subject.pem`"") if ($Global:LASTEXITCODE -ne 0) { throw "Failed to convert the certificate from pfx to pem!" } } # Step 7 # Export certificate as RSA (.key) file if ($Base64.IsPresent) { Write-Verbose "Export the certificate as RSA to $Subject.key" Write-Verbose "> openssl.exe pkcs12 -passin pass:`"***`" -in `"$Path\$Subject.pfx`" -nocerts -out `"$Path\$Subject.key`" -nodes" Write-Verbose "> openssl.exe rsa -in `"$Path\$Subject.key`" -out `"$Path\$Subject.key`"" Write-Progress -Activity $activity -Status "Export the certificate as RSA to $Subject.key" -PercentComplete 84 $result = (& $openSslCmd pkcs12 -passin "pass:`"$(Unprotect-SecureString -SecureString $Password)`"" -in "`"$Path\$Subject.pfx`"" -nocerts -out "`"$Path\$Subject.key`"" -nodes) if ($Global:LASTEXITCODE -ne 0) { throw "Failed to convert the certificate from pfx to key!" } $ErrorActionPreference = 'SilentlyContinue' $result = (& $openSslCmd rsa -in "`"$Path\$Subject.key`"" -out "`"$Path\$Subject.key`"" 2>&1) $ErrorActionPreference = 'Stop' if ($Global:LASTEXITCODE -ne 0) { throw "Failed to convert the certificate from pfx to key!" } } # Finally, update the friendly name, get the certificate object and # return it to the pipeline $certificate = Get-Item -Path "Cert:\LocalMachine\My\$thumbprint" $certificate.FriendlyName = $FriendlyName Write-Output $certificate } } catch { throw $_ } finally { Write-Progress -Activity $activity -PercentComplete 100 -Completed } } |