Self Signed Certificate Creation

The number of times in the past that I have had to create self signed certificates is far too many to count! There have been various tools to help with it include IIS Server Management and the old standby MAKECERT utility. When I needed to create a couple new certificates for my Azure Drive Encryption I decided to see if there was a new and better way to do this. Low and behold PowerShell has a great cmdlet for this, but after reading the provider type issues some people were having I was gun-shy. After some investigation, I seem to have overcome the issue others were complaining about and have decided to post my PowerShell script I will use to generate self signed certificates moving forward.

The first thing I needed to do was to setup my standard parameters that I will want to offer. There are a couple things that are worth calling out here.

 Param
(
  [Parameter(Mandatory=$true, Position=1, HelpMessage="Certificates subject (e.g. CN=mysubject)")]
  [string]$subjectName,
  [Parameter(Mandatory=$true, Position=2, HelpMessage="PFX output file name (e.g. c:\certs\mycert.pfx)")]
  [string]$fileName,
  [Parameter(Mandatory=$true, Position=3, HelpMessage="Password for the PFX File")]
  [Security.SecureString]$password,
  [Parameter(Mandatory=$true, Position=4, HelpMessage="Expiry date of the certificate")]
  [DateTime]$expiryDate,
  [Parameter(Mandatory=$false, Position=5, HelpMessage="Friendly name of the certificate")]
  [string]$friendlyName,
  [Parameter(Mandatory=$false, Position=6, HelpMessage="Certificates description")]
  [string]$description,
  [Parameter(Mandatory=$true, Position=7, HelpMessage="Cert store location (e.g. Cert:\CurrentUser\My)")]
  [string]$certificateLocation,
  [Parameter(Mandatory=$false, HelpMessage="Indicates the V1 cryptographic provider should be used")]
  [switch]$useProviderV1
)

The standard certificate information should be more than self explanatory, but I have added a provider switch to get around some of the issues I have heard about (and seen). It seems that the issue with the provider type seems to be tied to the use of the CNG libraries so I will provide the ability to revert to “Microsoft Enhanced Cryptographic Provider v1.0” when desired through the inclusion of the -useProviderV1 switch.

The next thing I do is to make sure that I throw an error if any of the required parameters are null or empty strings.

 if($password -eq $null -or $password.Length -lt 1)
{
  Write-Error 'The password provided was null or empty'
  throw [System.ArgumentException] 'The password must not be null or empty'
}
else
{
  Write-Verbose 'The password valid'
}

if($subjectName -eq $null -or $subjectName.Length -lt 1)
{
  Write-Error 'The subject name provided was null or empty'
  throw [System.ArgumentException] 'The subject name must not be null or empty'
}
else
{
  Write-Verbose 'The subject name provided was valid'
}

if($subjectName -eq $null -or $subjectName.Length -lt 1)
{
  Write-Error 'The subject name provided was null or empty'
  throw [System.ArgumentException] 'The subject name must not be null or empty'
}
else
{
  Write-Verbose 'The subject name provided was valid'
}

Following this I will validate the output directory where the PFX file is to be placed is valid as well as ensuring the filename ends with the PFX extension.

 if($fileName -eq $null -or $fileName.Length -lt 1)
{
  $fileName = $null
  Write-Error 'The file name provided was null or empty'
  throw [System.ArgumentException] 'The file name must not be null or empty'
}
else
{
  if ($fileName.EndsWith('.pfx') -eq $false)
  { 
    $fileName = "$($fileName).pfx"
  }

  $directoryName = [System.IO.Path]::GetDirectoryName($fileName) 
 
  if(($directoryName -eq $null -or $directoryName.Length -lt 1) -or ((Test-Path -Path $directoryName) -eq $true))
  {
    Write-Verbose 'The file name provided was valid'
  }
  else
  {
    $fileName = $null
    Write-Error 'The directory does not exist'
    throw [System.ArgumentException] 'The directory does not exist'
  } 
}

The final preparation work to do is to make sure the expiry date is in the future.

 if($expiryDate -le (Get-Date))
{
  Write-Error 'Expiry date provided was not in the future.'
  throw [System.ArgumentException] 'Expiry date provided was not in the future.'
}
else
{
  Write-Verbose 'The date provided was valid.'
}

I can now create the certificates and add them to the current user’s certificate store. If the expiration date is not provided I will not set the expiration date on the certificate. If the legacy provider is required I also set that.

 if($useProviderV1 -eq $true)
{
  if($expiryDate -ne $null)
  {
    $cert = New-SelfSignedCertificate -Type SSLServerAuthentication -Subject "CN=$($subjectName)" -KeyAlgorithm RSA -KeyLength 2048 -NotAfter $expiryDate -CertStoreLocation Cert:\CurrentUser\My -FriendlyName $friendlyName -KeyDescription $description -KeyUsageProperty All -Provider 'Microsoft Enhanced Cryptographic Provider v1.0'
  }
  else
  {
    $cert = New-SelfSignedCertificate -Type SSLServerAuthentication -Subject "CN=$($subjectName)" -KeyAlgorithm RSA -KeyLength 2048 -CertStoreLocation Cert:\CurrentUser\My -FriendlyName $friendlyName -KeyDescription $description -KeyUsageProperty All -Provider 'Microsoft Enhanced Cryptographic Provider v1.0' 
  }
}
else
{
  if($expiryDate -ne $null)
  {
    $cert = New-SelfSignedCertificate -Type SSLServerAuthentication -Subject "CN=$($subjectName)" -KeyAlgorithm RSA -KeyLength 2048 -NotAfter $expiryDate -CertStoreLocation Cert:\CurrentUser\My -FriendlyName $friendlyName -KeyDescription $description -KeyUsageProperty All 
  }
  else
  {
    $cert = New-SelfSignedCertificate -Type SSLServerAuthentication -Subject "CN=$($subjectName)" -KeyAlgorithm RSA -KeyLength 2048 -CertStoreLocation Cert:\CurrentUser\My -FriendlyName $friendlyName -KeyDescription $description -KeyUsageProperty All 
  }
}

If I stop here the certificate will be added to my certificate store, but I must manually export to use it for my purposes later. Instead of sticking with that I will export the certificate and delete the old one.

 Export-PfxCertificate -FilePath $fileName -Cert $cert -Password $password
Remove-Item -path "$($certificateLocation)\$($cert.Thumbprint)"

After this process I no longer have to resort to the older techniques of certificate creation!