| Yes, it is possible to create self-signed certificates from command line on nearly any computer today even without having the PowerShell 5 installed. The PowerShell 5 comes with the all new New-SelfSignedCertificate cmdlet but apparently requires this newer version of the PowerShell which I often cannot rely on. While it is possible, since ever, to do the same with CERTREQ command line utility which is available built-in on all currently supported corporate editions of Windows.
I just wrapped the CERTREQ into the following powershell script. Yes, it does need PowerShell 2 only. The script simply compiles INF file for the CERTREQ. You can get documentation on the INF contents here.
function global:Create-SelfSignedCert (
[Parameter(Mandatory = $true)] [string] $subject,
[string] $friendlyName,
[ValidateSet('md5', 'md4', 'md2', 'sha1', 'sha256', 'sha384', 'sha512')] [string] $hash = 'sha256',
[ValidateScript({ $_ -gt 0 })] [int] $keyLength = 2048,
[ValidateSet('seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years')] [string] $validityUnit = 'Months',
[ValidateScript({ $_ -gt 0 })] [int] $validityLen = 3,
[ValidateNotNullOrEmpty()] [string] $provider = "Microsoft Software Key Storage Provider",
[switch] $machineKey,
[ValidateSet('ClientAuthentication', 'ServerAuthentication')] [string[]] $eku = @('ClientAuthentication'),
[ValidateSet('DigitalSignature', 'KeyEncipherment', 'NonRepudiation', 'CertificateSigning', 'CrlSigning', 'OfflineCrlSigning')] [string[]] $keyUsage = @('DigitalSignature')
)
{
# Note: the file really must contain the last empty line
[string] $muster = @'
; Built with SAPHA toolkit - (C) Ondrej Sevecek, 2012-2020 - www.sevecek.com, ondrej@sevecek.com
[Version]
Signature = "$Windows NT$"
[NewRequest]
RequestType = Cert
Subject = "$subject$"
HashAlgorithm = $hash$
KeyLength = $keyLength$
KeySpec = $keySpec$
KeyUsage = $keyUsage$ ; CERT_DIGITAL_SIGNATURE_KEY_USAGE
ValidityPeriod = $validityUnit$
ValidityPeriodUnits = $validityLen$
ProviderName = "$provider$"
Silent = true
MachineKeySet = $machineKey$
Exportable = false
FriendlyName = "$friendlyName$"
[Extensions]
"2.5.29.37" = "{text}$eku$"
'@
[hashtable] $ekuOIDs = @{
ServerAuthentication = '1.3.6.1.5.5.7.3.1'
ClientAuthentication = '1.3.6.1.5.5.7.3.2'
}
[hashtable] $keyUsages = @{
DigitalSignature = 0x80
KeyEncipherment = 0x20
NonRepudiation = 0x40
CertificateSigning = 0x04
CrlSigning = 0x02
OfflineCrlSigning = 0x02
}
[string] $thumbprint = $null
[System.Management.Automation.ActionPreference] $local:errorActionInternalBackup = $global:errorActionPreference
try { $global:errorActionPreference = [System.Management.Automation.ActionPreference]::Stop
$muster = $muster.Replace('$subject$', $subject)
$muster = $muster.Replace('$friendlyName$', $friendlyName)
$muster = $muster.Replace('$hash$', $hash)
$muster = $muster.Replace('$keyLength$', $keyLength)
$muster = $muster.Replace('$validityUnit$', $validityUnit)
$muster = $muster.Replace('$validityLen$', $validityLen)
$muster = $muster.Replace('$provider$', $provider)
$muster = $muster.Replace('$machineKey$', $machineKey)
$muster = $muster.Replace('$eku$', (($eku | % { $ekuOIDs[$_] }) -join ','))
[int] $keyUsageInt = 0
foreach ($oneKeyUsage in $keyUsage) {
$keyUsageInt = $keyUsageInt -bor $keyUsages[$oneKeyUsage]
}
$muster = $muster.Replace('$keyUsage$', ('0x{0:X2}' -f $keyUsageInt))
if ($keyUsages -contains 'KeyEncipherment') {
$muster = $muster.Replace('$keySpec$', 'AT_SIGNATURE')
} else {
$muster = $muster.Replace('$keySpec$', 'AT_KEYEXCHANGE')
}
[string] $infFile = Join-Path $env:TEMP ('sapha-self-signed-certificate-{0}.inf' -f ([DateTime]::Now.ToString('yyyyMMddHHmmss')))
[string] $cerFile = [System.IO.Path]::ChangeExtension($infFile, '.cer')
Write-Host ('Creat a self-signed certificate: {0} | {1} | {2}' -f $subject, $friendlyName, $provider)
Write-Host ('Save certificate request file: {0}' -f $infFile)
$muster.Split("`n") | % { $_.Trim() } | Out-File -FilePath $infFile -Encoding Unicode -Force
Write-Host ('Create the certificate with CERTREQ: {0}' -f $cerFile)
[Collections.ArrayList] $cmdOut = @()
certreq -f -q -new $infFile $cerFile | % { [void] $cmdOut.Add($_) }
[int] $cmdRes = $LASTEXITCODE
if ($cmdRes -ne 0) { throw ('CERTREQ error: 0x{0:X8} | {1}' -f $cmdRes, ($cmdOut -join "`r`n")) }
if (-not (Test-Path $cerFile)) { throw ('No certificate file created: {0}' -f $cerFile) }
Write-Host ('Loading the created certificate from file: {0}' -f $cerFile)
[Security.Cryptography.X509Certificates.X509Certificate2] $certificateLoaded = New-Object Security.Cryptography.X509Certificates.X509Certificate2
$certificateLoaded.Import($cerFile)
if ($certificateLoaded.Thumbprint -notmatch '\A[a-fA-F0-9]{40}\Z') { throw ('Invalid certificate loaded: {0} | {1}' -f $cerFile, $certificateLoaded.Thumbprint) }
if ($machineKey) {
$certCreatePath = ('Cert:\LocalMachine\My\{0}' -f $certificateLoaded.Thumbprint)
} else {
$certCreatePath = ('Cert:\CurrentUser\My\{0}' -f $certificateLoaded.Thumbprint)
}
[Security.Cryptography.X509Certificates.X509Certificate2] $certificateCreated = Get-Item $certCreatePath
if ([object]::Equals($certificateCreated, $null)) { throw ('Cannot open the local certificate store: {0}' -f $certificateLoaded.Thumbprint) }
Write-Host ('Certificate created: {0} | {1} | exp = {2} | {3} | provider = {4}' -f $certificateLoaded.SubjectName.Name, $certificateLoaded.Thumbprint, $certificateLoaded.NotAfter.ToString('yyyy-MM-dd HH:mm:ss'), $certCreatePath, $provider)
if ($certificateLoaded.NotBefore -gt ([DateTime]::Now.AddMinutes(-10))) { throw ('Weird certificate expiration date: {0}' -f $certificateLoaded.NotBefore.ToString('yyyy-MM-dd HH:mm:ss')) }
$thumbprint = $certificateCreated.Thumbprint
} finally { $global:errorActionPreference = $local:errorActionInternalBackup
}
return $thumbprint
}
|