Hello folks, today I want to present you my another product in PKI integration with Windows PowerShell. I worked hard on server-side extensions: PowerShell PKI Module, which is (so far) the biggest project I have developed.
Now I got a time to work on client side extensions. Some prototypes are already published in this blog. The first complete tool (which is a part of client-side extensions) is self-signed certificate creation for testing purposes. The reason why I developed this tool is that makecert.exe (from Windows SDK) is now deprecated. The blog post provides a replacement for makecert — certreq.exe tool. Although, certreq is very cool, there are few things to note:
My script relies on the same APIs as certreq and more flexible. Also it demonstrates the techniques of CertEnroll API usage in action.
The script defines the following parameters:
IsCA — Specifies whether the certificate is CA (IsCA = $true) or end entity (IsCA = $false) certificate. If this parameter is set to $false, PathLength parameter is ignored. Basic Constraints extension is marked as critical.
PathLength — Specifies the number of additional CA certificates in the chain under this certificate. If PathLength parameter is set to zero, then no additional (subordinate) CA certificates are permitted under this CA.
CustomExtension — Specifies the custom extension to include to a self-signed certificate. This parameter must not be used to specify the extension that is supported via other parameters. In order to use this parameter, the extension must be formed in a collection of initialized X509Extension objects.
SignatureAlgorithm — Specifies signature algorithm used to sign the certificate. By default 'SHA1' algorithm is used.
FriendlyName — Specifies friendly name for the certificate.
StoreLocation — Specifies the store location to store self-signed certificate. Possible values are: 'CurrentUser' and 'LocalMachine'. 'CurrentUser' store is intended for user certificates and computer (as well as CA) certificates must be stored in 'LocalMachine' store.
StoreName — Specifies the container name in the certificate store. Possible container names are:
AddressBook
AuthRoot
CertificateAuthority
Disallowed
My
Root
TrustedPeople
TrustedPublisher
Path — Specifies the path to a PFX file to export a self-signed certificate.
Password — Specifies the password for PFX file.
AllowSMIME — Enables Secure/Multipurpose Internet Mail Extensions for the certificate.
Exportable — Marks private key as exportable. Smart card providers usually do not allow
exportable keys.
And several useful examples:
New-SelfsignedCertificateEx -Subject "CN=Test Code Signing" -EKU "Code Signing" -KeySpec "Signature" `-KeyUsage "DigitalSignature" -FriendlyName "Test code signing" -NotAfter [datetime]::now.AddYears(5)
Creates a self-signed certificate intended for code signing and which is valid for 5 years. Certificate is saved in the Personal store of the current user account.
New-SelfsignedCertificateEx -Subject "CN=www.domain.com" -EKU "Server Authentication", "Client authentication" `-KeyUsage "KeyEcipherment, DigitalSignature" -SAN "sub.domain.com","www.domain.com","192.168.1.1" `-AllowSMIME -Path C:\test\ssl.pfx -Password (ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force) -Exportable `-StoreLocation "LocalMachine"
Creates a self-signed SSL certificate with multiple subject names and saves it to a file. Additionally, the certificate is saved in the Personal store of the Local Machine store. Private key is marked as exportable, so you can export the certificate with a associated private key to a file at any time. The certificate includes SMIME capabilities.
New-SelfsignedCertificateEx -Subject "CN=www.domain.com" -EKU "Server Authentication", "Client authentication" `-KeyUsage "KeyEcipherment, DigitalSignature" -SAN "sub.domain.com","www.domain.com","192.168.1.1" `-StoreLocation "LocalMachine" -ProviderName "Microsoft Software Key Storae Provider" -AlgorithmName ecdh_256 `-KeyLength 256 -SignatureAlgorithm sha256
Creates a self-signed SSL certificate with multiple subject names and saves it to a file. Additionally, the certificate is saved in the Personal store of the Local Machine store. Private key is marked as exportable, so you can export the certificate with a associated private key to a file at any time. Certificate uses Elliptic Curve Cryptography (ECC) key algorithm ECDH with 256-bit key. The certificate is signed by using SHA256 algorithm.
New-SelfsignedCertificateEx -Subject "CN=Test Root CA, OU=Sandbox" -IsCA $true -ProviderName `"Microsoft Software Key Storage Provider -Exportable
Creates self-signed root CA certificate.
Here is the code of the script. If you don’t want to read all this mess, scroll down and download ready file:
##################################################################### # New-SelfSignedCertificateEx.ps1 # Version 1.6 # # Creates self-signed certificate. This tool is a base replacement # for deprecated makecert.exe # # Vadims Podans (c) 2013 # http://en-us.sysadmins.lv/ ##################################################################### #requires -Version 3.0 function New-SelfSignedCertificateEx { [CmdletBinding(DefaultParameterSetName = '__store')] param ( [Parameter(Mandatory = $true, Position = 0)] [string]$Subject, [Parameter(Position = 1)] [datetime]$NotBefore = [DateTime]::Now.AddDays(-1), [Parameter(Position = 2)] [datetime]$NotAfter = $NotBefore.AddDays(365), [string]$SerialNumber, [Alias('CSP')] [string]$ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0", [string]$AlgorithmName = "RSA", [int]$KeyLength = 2048, [validateSet("Exchange","Signature")] [string]$KeySpec = "Exchange", [Alias('EKU')] [Security.Cryptography.Oid[]]$EnhancedKeyUsage, [Alias('KU')] [Security.Cryptography.X509Certificates.X509KeyUsageFlags]$KeyUsage, [Alias('SAN')] [String[]]$SubjectAlternativeName, [bool]$IsCA, [int]$PathLength = -1, [Security.Cryptography.X509Certificates.X509ExtensionCollection]$CustomExtension, [ValidateSet('MD5','SHA1','SHA256','SHA384','SHA512')] [string]$SignatureAlgorithm = "SHA1", [string]$FriendlyName, [Parameter(ParameterSetName = '__store')] [Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = "CurrentUser", [Parameter(Mandatory = $true, ParameterSetName = '__file')] [Alias('OutFile','OutPath','Out')] [IO.FileInfo]$Path, [Parameter(Mandatory = $true, ParameterSetName = '__file')] [Security.SecureString]$Password, [switch]$AllowSMIME, [switch]$Exportable ) $ErrorActionPreference = "Stop" if ([Environment]::OSVersion.Version.Major -lt 6) { $NotSupported = New-Object NotSupportedException -ArgumentList "Windows XP and Windows Server 2003 are not supported!" throw $NotSupported } $ExtensionsToAdd = @() #region constants # contexts New-Variable -Name UserContext -Value 0x1 -Option Constant New-Variable -Name MachineContext -Value 0x2 -Option Constant # encoding New-Variable -Name Base64Header -Value 0x0 -Option Constant New-Variable -Name Base64 -Value 0x1 -Option Constant New-Variable -Name Binary -Value 0x3 -Option Constant New-Variable -Name Base64RequestHeader -Value 0x4 -Option Constant # SANs New-Variable -Name OtherName -Value 0x1 -Option Constant New-Variable -Name RFC822Name -Value 0x2 -Option Constant New-Variable -Name DNSName -Value 0x3 -Option Constant New-Variable -Name DirectoryName -Value 0x5 -Option Constant New-Variable -Name URL -Value 0x7 -Option Constant New-Variable -Name IPAddress -Value 0x8 -Option Constant New-Variable -Name RegisteredID -Value 0x9 -Option Constant New-Variable -Name Guid -Value 0xa -Option Constant New-Variable -Name UPN -Value 0xb -Option Constant # installation options New-Variable -Name AllowNone -Value 0x0 -Option Constant New-Variable -Name AllowNoOutstandingRequest -Value 0x1 -Option Constant New-Variable -Name AllowUntrustedCertificate -Value 0x2 -Option Constant New-Variable -Name AllowUntrustedRoot -Value 0x4 -Option Constant # PFX export options New-Variable -Name PFXExportEEOnly -Value 0x0 -Option Constant New-Variable -Name PFXExportChainNoRoot -Value 0x1 -Option Constant New-Variable -Name PFXExportChainWithRoot -Value 0x2 -Option Constant #endregion #region Subject processing # http://msdn.microsoft.com/en-us/library/aa377051(VS.85).aspx $SubjectDN = New-Object -ComObject X509Enrollment.CX500DistinguishedName $SubjectDN.Encode($Subject, 0x0) #endregion #region Extensions #region Enhanced Key Usages processing if ($EnhancedKeyUsage) { $OIDs = New-Object -ComObject X509Enrollment.CObjectIDs $EnhancedKeyUsage | %{ $OID = New-Object -ComObject X509Enrollment.CObjectID $OID.InitializeFromValue($_.Value) # http://msdn.microsoft.com/en-us/library/aa376785(VS.85).aspx $OIDs.Add($OID) } # http://msdn.microsoft.com/en-us/library/aa378132(VS.85).aspx $EKU = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage $EKU.InitializeEncode($OIDs) $ExtensionsToAdd += "EKU" } #endregion #region Key Usages processing if ($KeyUsage -ne $null) { $KU = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage $KU.InitializeEncode([int]$KeyUsage) $KU.Critical = $true $ExtensionsToAdd += "KU" } #endregion #region Basic Constraints processing if ($PSBoundParameters.Keys.Contains("IsCA")) { # http://msdn.microsoft.com/en-us/library/aa378108(v=vs.85).aspx $BasicConstraints = New-Object -ComObject X509Enrollment.CX509ExtensionBasicConstraints if (!$IsCA) {$PathLength = -1} $BasicConstraints.InitializeEncode($IsCA,$PathLength) $BasicConstraints.Critical = $IsCA $ExtensionsToAdd += "BasicConstraints" } #endregion #region SAN processing if ($SubjectAlternativeName) { $SAN = New-Object -ComObject X509Enrollment.CX509ExtensionAlternativeNames $Names = New-Object -ComObject X509Enrollment.CAlternativeNames foreach ($altname in $SubjectAlternativeName) { $Name = New-Object -ComObject X509Enrollment.CAlternativeName if ($altname.Contains("@")) { $Name.InitializeFromString($RFC822Name,$altname) } else { try { $Bytes = [Net.IPAddress]::Parse($altname).GetAddressBytes() $Name.InitializeFromRawData($IPAddress,$Base64,[Convert]::ToBase64String($Bytes)) } catch { try { $Bytes = [Guid]::Parse($altname).ToByteArray() $Name.InitializeFromRawData($Guid,$Base64,[Convert]::ToBase64String($Bytes)) } catch { try { $Bytes = ([Security.Cryptography.X509Certificates.X500DistinguishedName]$altname).RawData $Name.InitializeFromRawData($DirectoryName,$Base64,[Convert]::ToBase64String($Bytes)) } catch {$Name.InitializeFromString($DNSName,$altname)} } } } $Names.Add($Name) } $SAN.InitializeEncode($Names) $ExtensionsToAdd += "SAN" } #endregion #region Custom Extensions if ($CustomExtension) { $count = 0 foreach ($ext in $CustomExtension) { # http://msdn.microsoft.com/en-us/library/aa378077(v=vs.85).aspx $Extension = New-Object -ComObject X509Enrollment.CX509Extension $EOID = New-Object -ComObject X509Enrollment.CObjectId $EOID.InitializeFromValue($ext.Oid.Value) $EValue = [Convert]::ToBase64String($ext.RawData) $Extension.Initialize($EOID,$Base64,$EValue) $Extension.Critical = $ext.Critical New-Variable -Name ("ext" + $count) -Value $Extension $ExtensionsToAdd += ("ext" + $count) $count++ } } #endregion #endregion #region Private Key # http://msdn.microsoft.com/en-us/library/aa378921(VS.85).aspx $PrivateKey = New-Object -ComObject X509Enrollment.CX509PrivateKey $PrivateKey.ProviderName = $ProviderName $AlgID = New-Object -ComObject X509Enrollment.CObjectId $AlgID.InitializeFromValue(([Security.Cryptography.Oid]$AlgorithmName).Value) $PrivateKey.Algorithm = $AlgID # http://msdn.microsoft.com/en-us/library/aa379409(VS.85).aspx $PrivateKey.KeySpec = switch ($KeySpec) {"Exchange" {1}; "Signature" {2}} $PrivateKey.Length = $KeyLength # key will be stored in current user certificate store switch ($PSCmdlet.ParameterSetName) { '__store' { $PrivateKey.MachineContext = if ($StoreLocation -eq "LocalMachine") {$true} else {$false} } '__file' { $PrivateKey.MachineContext = $false } } $PrivateKey.ExportPolicy = if ($Exportable) {1} else {0} $PrivateKey.Create() #endregion # http://msdn.microsoft.com/en-us/library/aa377124(VS.85).aspx $Cert = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate if ($PrivateKey.MachineContext) { $Cert.InitializeFromPrivateKey($MachineContext,$PrivateKey,"") } else { $Cert.InitializeFromPrivateKey($UserContext,$PrivateKey,"") } $Cert.Subject = $SubjectDN $Cert.Issuer = $Cert.Subject $Cert.NotBefore = $NotBefore $Cert.NotAfter = $NotAfter foreach ($item in $ExtensionsToAdd) {$Cert.X509Extensions.Add((Get-Variable -Name $item -ValueOnly))} if (![string]::IsNullOrEmpty($SerialNumber)) { if ($SerialNumber -match "[^0-9a-fA-F]") {throw "Invalid serial number specified."} if ($SerialNumber.Length % 2) {$SerialNumber = "0" + $SerialNumber} $Bytes = $SerialNumber -split "(.{2})" | ?{$_} | %{[Convert]::ToByte($_,16)} $ByteString = [Convert]::ToBase64String($Bytes) $Cert.SerialNumber.InvokeSet($ByteString,1) } if ($AllowSMIME) {$Cert.SmimeCapabilities = $true} $SigOID = New-Object -ComObject X509Enrollment.CObjectId $SigOID.InitializeFromValue(([Security.Cryptography.Oid]$SignatureAlgorithm).Value) $Cert.SignatureInformation.HashAlgorithm = $SigOID # completing certificate request template building $Cert.Encode() # interface: http://msdn.microsoft.com/en-us/library/aa377809(VS.85).aspx $Request = New-Object -ComObject X509Enrollment.CX509enrollment $Request.InitializeFromRequest($Cert) $Request.CertificateFriendlyName = $FriendlyName $endCert = $Request.CreateRequest($Base64) $Request.InstallResponse($AllowUntrustedCertificate,$endCert,$Base64,"") switch ($PSCmdlet.ParameterSetName) { '__file' { $PFXString = $Request.CreatePFX( [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)), $PFXExportEEOnly, $Base64 ) Set-Content -Path $Path -Value ([Convert]::FromBase64String($PFXString)) -Encoding Byte } } }
Hi Vadim, The script looks fantastic and perfectly timed for some work I'm doing where I need to create a self-signed certificate with more "detail" than I can achieve using MakeCert. I have Windows 7 with PowerShell 3 and .Net 4 (and also a Windows 8 PC). However, I'm not sure how to execute the script (I do have executionpolicy set to remotesigned). If I either run the script with no parameters, or follow it by an example nothing happens - the PowerShell prompt returns - with no errors. I'm sure this is just because I have too limited a knowledge of PowerShell. Could you advise how we actually run the script? Kind regards, Dave
I guess, you incorrectly calling the command. In the PowerShell console, you need to do the following: . .\New-SelfSignedCertificateEx.ps1 note the first dot. It is dot-source operator in PowerShell which attaches the function in the current PowerShell session. Taht is, you need to type dot, space and the path to a PS1 file. And then you can run the function with parameters.
Quite interesting however an item I'm looking for is the ability to create a SAN certificate signed off a specified root CA cert. MakeCert does it but unfortunately lacks the ability to add the SAN attribute. Can you advise? I'm not a developer so lets not get too technical ;-)
If you want to sign certificate by a CA, then you should generate certificate request and submit it to a CA server. I guess, you want to get random certificate and self-signed certificate? No, I don't provide such functionality and it falls to another story (which is not common nowadays).
Great work! just a remark in my PowerShell 4.0 -NotAfter [datetime]::now.AddYears(5) does work only that way ([datetime]::now.AddYears(5)) in parenthesis.
Yes, it is a typo in the example. Thanks for clarification.
Hi there, First: Thanks for this great script. My question: how is this script licensed? Is it possible to use this script in a powershell module which should be published on codeplex for everyone. I will not use this code in any commercial manner. Regards JayS
The script is licensed under MS-PL license. You can use the script as is and modify as per your needs. However, you will have to maintain a copyright note.
Hello Vadims,
Thank you for creating this great script! I ran into the following error when trying to generate a self-signed script:
Method invocation failed because [System.Collections.Generic.Dictionary`2+KeyCollection[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]] doesn't contain a method named 'Contains'.
At C:\Users\test\Desktop\New-SelfSignedCertificateEx.ps1:267 char:38
I was able to fix this error by enclosing $PSBoundParameters within double-quotation marks on line 267:
#region Basic Constraints processing
if ("$PSBoundParameters".Keys.Contains("IsCA")) {
Then I encountered the following error:
You cannot call a method on a null-valued expression.
At C:\Users\test\Desktop\New-SelfSignedCertificateEx.ps1:267 char:40
I ended up commenting out the entire region for Basic Constrains Processing from line 266 to 276. This allowed me to run your script and generate a self-signed certificate. I just wanted to let you know the error I encountered. Again, thank you for creating this script!
Hello Vadims,
Thank you for creating this great script! I ran into the following error when trying to generate a self-signed script:
Method invocation failed because [System.Collections.Generic.Dictionary`2+KeyCollection[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]] doesn't contain a method named 'Contains'.
At C:\Users\test\Desktop\New-SelfSignedCertificateEx.ps1:267 char:38
I was able to fix this error by enclosing $PSBoundParameters within double-quotation marks on line 267:
#region Basic Constraints processing
if ("$PSBoundParameters".Keys.Contains("IsCA")) {
Then I encountered the following error:
You cannot call a method on a null-valued expression.
At C:\Users\test\Desktop\New-SelfSignedCertificateEx.ps1:267 char:40
I ended up commenting out the entire region for Basic Constrains Processing from line 266 to 276. This allowed me to run your script and generate a self-signed certificate. I just wanted to let you know the error I encountered. Again, thank you for creating this script!
The script runs without errors and no output on the console. However I cannot find the certificate. Looked at both user and computer stores.
Where is the cert supposed to be placed?
Thank you.
> Where is the cert supposed to be placed?
it should be installed in either, CurrentUser\My
or LocalMachine\My
store. What parameters did you use?
Hi,
I tried to run this script from the powershell with administrator rights but it runs without errors and no output on the console. I also cannot find the certificate in CurrentUser and LocalMachine store.
.\New-SelfsignedCertificateEx.ps1 -Subject "CN=portal-dev-app.corp.com" -EKU "Server Authentication", "Client authentication" -KeyUsage "KeyEcipherment, DigitalSignature" -Path "C:\Shared\ssl.pfx" -Password (ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force) -Exportable -StoreLocation "LocalMachine" -NotAfter [datetime]::now.AddYears(5)
Please advise.
Thanks
You didn't import the function to the PowerShell console. You need to dot-source the script and then run the New-SelfSignedCertificateEx function.
Can you help me please in this question ? :
Why in New-SelfSignedCertificateEx there is no parameter "Signer" which can create a leaf certificate under existed CA ? allthough "Signer" parameter is existed in New-SelfSignedCertificate (which is built-in Windows 10 )
> Why in New-SelfSignedCertificateEx there is no parameter "Signer" which can create a leaf certificate under existed CA ?
because such certificate is no longer self-signer. For existing CAs you have to generate certificate request and use CA tools to sign the request.
Hi. I get the error when try to pass -Path param
"New-SelfSignedCertificateEx : Parameter set cannot be resolved using the specified named parameters.
At C:\CreateSslCert.ps1:52 char:11
+ $cert = New-SelfSignedCertificateEx -Subject "CN=localhost" -StoreL .."
$cert = New-SelfSignedCertificateEx -Subject "CN=localhost" -StoreLocation "LocalMachine" -SubjectAlternativeName $hosts -FriendlyName $name -NotAfter $((Get-Date).AddYears(20)) -Exportable -IsCA $true -Path "$HOME\AppData\Local\cert.pfx" -Password (ConvertTo-SecureString "123" -AsPlainText -Force) -Exportable
The reaseon is in -Path for sure. But why?
Well, the issue is that "-Path" and "-StoreLocation" parameters are mutually exclusive and cannot be used together.
> Why in New-SelfSignedCertificateEx there is no parameter "Signer" which can create a leaf certificate under existed CA ?
because such certificate is no longer self-signer. For existing CAs you have to generate certificate request and use CA tools to sign the request.
@Vadims do you have examples for how to use the CA certificate to sign client certificates that can be used to bind HTTPS to a website just using PowerShell?
> @Vadims do you have examples for how to use the CA certificate to sign client certificates that can be used to bind HTTPS to a website just using PowerShell?
this functionality exists in standard New-SelfSignedCertificate cmdlet with -Signer parameter that accepts signer certificate with private key.
>this functionality exists in standard New-SelfSignedCertificate cmdlet with -Signer parameter that accepts signer certificate with private key.
The "-Signer" and many other params aren't available with New-SelfSignedCeriticate in Windows 8.1, even if you install WMF 5.1. Actually I ended up on this page, I think , by a comment you made elsewhere stating exactly this fact, which is why I was trying to use your script.
I successfully installed a self-signed key (your first examlpe) and used it to sign a DLL for VirtualBox (which requires injected DLLs to have a signature), but it's failing with error VERR_CR_X509_CPV_NO_TRUSTED_PATHS. I'm thinking the "No trusted paths" error means that it wants a separate CA certificate, even though I imported that signing key into the "trusted root certicate authority." But I don't think I can use powershell to achieve this, as stated.
> I imported that signing key into the "trusted root certicate authority."
I can think that public part of certificate must be imported into Local Machine store, not current user store.
Hi Vadim,
I've just been trying to use your script on a win11 machine. After getting stuck I tried your examples which you posted avode and they do not work either.
For example, if I execute New-SelfsignedCertificateEx -Subject "CN=Test Code Signing" -EKU "Code Signing" -FriendlyName "Test code signing" -NotAfter (Get-Date).addyears(10) -notbefore (get-date)
I get an error
% : CertEnroll::CObjectId::InitializeFromValue: El parámetro no es correcto. 0x80070057 (WIN32: 87
ERROR_INVALID_PARAMETER)
En C:\temp\New-SelfSignedCertificateEx.ps1: 99 Carácter: 29
+ $EnhancedKeyUsage | %{
+ ~~
+ CategoryInfo : OperationStopped: (:) [ForEach-Object], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.ForEachObjectCommand
Most of your examples seem to provoke errores.
I'll keep on digging but I'm getting errors with many of the params.
Post your comment:
Comments: