Skip Ribbon Commands
Skip to main content

Ondrej Sevecek's English Pages

:

Engineering and troubleshooting by Directory Master!
MCM: Directory

Quick Launch

Ondrej Sevecek's English Pages > Posts > How to create self-signed certificate from PowerShell without having PowerShell 5 cmdlets
April 27
How to create self-signed certificate from PowerShell without having PowerShell 5 cmdlets

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
}

Comments

There are no comments for this post.

Add Comment

Sorry comments are disable due to the constant load of spam *


This simple antispam field seems to work well. Just put here the number.

Title


You do not need to provide any value this column. It will automatically fill with the name of the article itself.

Author *


Body *


Attachments