11

Scenario: I am using PowerShell on Windows Server 2012r2 to generate a Root certificate and want to use that to sign a newly created Intermediate and Web certificate in dynamic generated (and destroyed) dev/test environments. The scripts are deployed remotely, and the intent is to keep it pure PowerShell if possible. In Windows 10/2016 this is relatively easy, after generating the Root certificate:

$Cert = New-SelfSignedCertificate -Signer $Root -Subject "CN=$Subject"

I've generated the Root certificate using COM X509Enrollment.CX509CertificateRequestCertificate and Security.Cryptography.X509Certificates.X509Certificate2 in a bastardized PS that I've had for some time, mainly because I needed to ensure that the Subject and Usage were set very specifically. I am not quite certain how to use this to sign the standard certificate without the above (which I have used before).

There are some examples using Bouncy Castle (see below) in C# that I could tie into PowerShell, but then I would need to deploy this additionally on the dynamic dev/test environments and I want to be able to do this in Powershell (via COM if needed) with the least dependencies.

Community
  • 1
  • 1
LimpingNinja
  • 618
  • 1
  • 5
  • 16
  • Why not use your Puppet server's CA? Besides, a certificate is either self-signed or signed by a CA, not both. – Ansgar Wiechers May 16 '17 at 20:13
  • Sorry, realized I wrote "CA" and that could be confusing -- I meant self-signed root certificate. I'm generating a root certificate and utilizing the root certificate as the signing certificate for the new certificate. This is still self-signed, obviously, and is what we are going for - on 2016 this is not an issue, I'm looking for as close to a pure solution on 2012r2 Powershell. It may just be that I'm stuck with Bouncy Castle. – LimpingNinja May 16 '17 at 20:30
  • You could use [`makecert`](https://blogs.technet.microsoft.com/jhoward/2005/02/02/how-to-use-makecert-for-trusted-root-certification-authority-and-ssl-certificate-issuance/) (for obtaining the utility see [here](https://msdn.microsoft.com/en-us/library/windows/desktop/aa386968%28v=vs.85%29.aspx)). But again, why not use the Puppet CA for this purpose? All managed nodes already have a host certificate signed by it. – Ansgar Wiechers May 16 '17 at 20:36
  • Because the sec. team doesn't want that for their testing. There is no desire to dual-use a puppet certificate for our IIS and API; or for me to argue the use case (I would rather use a CSR to our CA). They have a specific ask for the cert type and usages, so my requirements are to generate a Root, Intermediate and Web certificate with specific details - using the root to sign on Dynamic creation. As for MakeCert, yes -- I am aware of OpenSSL as well -- but I was asking if there was a 2012 PoSh specific solve similar to that in 2016 PoSh to avoid the external package dependency. – LimpingNinja May 16 '17 at 21:10
  • I don't think there is. My recommendation is to go with `makecert` for your purpose. – Ansgar Wiechers May 16 '17 at 21:12

4 Answers4

12

The ultimate solution in my case, avoiding makecert and openssl was to use Powershell and BouncyCastle. I forked the PSBouncyCastle repo from PSBouncyCastle by RLipscombe and pushed 1.8.1 Bouncy Castle in. My forked version is the one I've used for the script, the fork resides at Forked: PSBouncyCastle.New.

I then used StackOverflow: C# Generate Certificates on the Fly as inspiration to write the following powershell below, I will be adding this to my GitHub and commenting, and I will amend this as soon as I do:

Import-Module -Name PSBouncyCastle.New

function New-SelfSignedCertificate {
  [CmdletBinding()]
  param (
    [string]$SubjectName,
    [string]$FriendlyName = "New Certificate",
    [object]$Issuer,
    [bool]$IsCA = $false,
    [int]$KeyStrength = 2048,
    [int]$ValidYears = 2,
    [hashtable]$EKU = @{}
  )

  # Needed generators
  $random = New-SecureRandom
  $certificateGenerator = New-CertificateGenerator

  if($Issuer -ne $null -and $Issuer.HasPrivateKey -eq $true)
  {
    $IssuerName = $Issuer.IssuerName.Name
    $IssuerPrivateKey = $Issuer.PrivateKey
  }
  # Create and set a random certificate serial number
  $serial = New-SerialNumber -Random $random
  $certificateGenerator.SetSerialNumber($serial)

  # The signature algorithm
  $certificateGenerator.SetSignatureAlgorithm('SHA256WithRSA')

  # Basic Constraints - certificate is allowed to be used as intermediate.
  # Powershell requires either a $null or reassignment or it will return this from the function
  $certificateGenerator = Add-BasicConstraints -isCertificateAuthority $IsCA -certificateGenerator $certificateGenerator

  # Key Usage
  if($EKU.Count -gt 0) 
  {
    $certificateGenerator = $certificateGenerator | Add-ExtendedKeyUsage @EKU
  }
  # Create and set the Issuer and Subject name
  $subjectDN = New-X509Name -Name ($SubjectName)
  if($Issuer -ne $null) {
    $IssuerDN = New-X509Name -Name ($IssuerName)
  }
  else 
  {
    $IssuerDN = New-X509Name -Name ($SubjectName)
  }  
  $certificateGenerator.SetSubjectDN($subjectDN)
  $certificateGenerator.SetIssuerDN($IssuerDN)

  # Authority Key and Subject Identifier
  if($Issuer -ne $null)
  {
    $IssuerKeyPair = ConvertTo-BouncyCastleKeyPair -PrivateKey $IssuerPrivateKey
    $IssuerSerial = [Org.BouncyCastle.Math.BigInteger]$Issuer.GetSerialNumber()
    $authorityKeyIdentifier = New-AuthorityKeyIdentifier -name $Issuer.IssuerName.Name -publicKey $IssuerKeyPair.Public -serialNumber $IssuerSerial
    $certificateGenerator = Add-AuthorityKeyIdentifier -certificateGenerator $certificateGenerator -authorityKeyIdentifier $authorityKeyIdentifier
  }

  # Validity range of the certificate
  [DateTime]$notBefore = (Get-Date).AddDays(-1)
  if($ValidYears -gt 0) {
    [DateTime]$notAfter = $notBefore.AddYears($ValidYears)
  }
  $certificateGenerator.SetNotBefore($notBefore)
  $certificateGenerator.SetNotAfter($notAfter)


  # Subject public key ~and private
  $subjectKeyPair = New-KeyPair -Strength $keyStrength -Random $random
  if($IssuerPrivateKey -ne $null)
  {
    $IssuerKeyPair = [Org.BouncyCastle.Security.DotNetUtilities]::GetKeyPair($IssuerPrivateKey)
  }
  else 
  {
    $IssuerKeyPair = $subjectKeyPair
  }
  $certificateGenerator.SetPublicKey($subjectKeyPair.Public)

  # Create the Certificate
  $IssuerKeyPair = $subjectKeyPair
  $certificate = $certificateGenerator.Generate($IssuerKeyPair.Private, $random)
  # At this point you have the certificate and need to convert it and export, I return the private key for signing the next cert
  $pfxCertificate = ConvertFrom-BouncyCastleCertificate -certificate $certificate -subjectKeyPair $subjectKeyPair -friendlyName $FriendlyName
  return $pfxCertificate
}

A few examples of usage for this powershell would be:

Generate a Root CA

$TestRootCA = New-SelfSignedCertificate -subjectName "CN=TestRootCA" -IsCA $true
Export-Certificate -Certificate $test -OutputFile "TestRootCA.pfx" -X509ContentType Pfx

Generate a Standard Self Signed

$TestSS = New-SelfSignedCertificate -subjectName "CN=TestLocal"
Export-Certificate -Certificate $TestSS -OutputFile "TestLocal.pfx" -X509ContentType Pfx

Generate a certificate, signing with a root certificate

$TestRootCA = New-SelfSignedCertificate -subjectName "CN=TestRootCA" -IsCA $true
$TestSigned = New-SelfSignedCertificate -subjectName "CN=TestSignedByRoot" -issuer $TestRootCA

Export-Certificate -Certificate $test -OutputFile "TestRootCA.pfx" -X509ContentType Pfx
Export-Certificate -Certificate $test -OutputFile "TestRootCA.pfx" -X509ContentType Pfx

Generate a Self-Signed with Specific Usage

$TestServerCert = New-SelfSignedCertificate -subjectName "CN=TestServerCert" -EKU @{ "ServerAuthentication" = $true }

Note that the -EKU parameter accepts via splatting, it does this to ensure that anything added to Add-ExtendedKeyUsage is validly passed. It accepts the following certificate usages:

  • DigitalSignature
  • NonRepudiation
  • KeyEncipherment
  • DataEncipherment
  • KeyAgreement
  • KeyCertSign
  • CrlSign
  • EncipherOnly
  • DecipherOnly

This fits my need and seems to work across all Windows Platforms we are using for dynamic environments.

LimpingNinja
  • 618
  • 1
  • 5
  • 16
  • *-IsCA* and *-Issuer* aren't valid parameters for **New-SelfSignedCertificate**? – Mikael Dúi Bolinder Jul 31 '17 at 13:19
  • 1
    Not that I see, the *Is-CA* parameter is part of a gallery script (**New-SelfSignedCertEx**), that doesn't provide signing ability. The need was "root/Is-CA" and "Signer" together. On 2012r2, this functionality is not available; **New-SelfSignedCertificate** was(is?) 2016/Win10 only. System.Security.Cryptography.X509Certificates was not exposing the functionality, that I saw. The use case for this was narrow: Generate a self-signed root and use it to sign other s.s.s certificates with Powershell (not exe. bound) on Server 2012r2 and prior. – LimpingNinja Aug 01 '17 at 14:14
  • 1
    ah, didn't see that the question was about Server 2012. – Mikael Dúi Bolinder Aug 02 '17 at 07:40
  • Thank you for this reply - incredibly helpful. Is there a way to sign a certificate with a root CA without creating the CA in the process? For example, if I wanted to re-use an existing CA. Thanks! – Jake Apr 27 '18 at 22:32
  • Hey @Jake - Depends on the version of Powershell and how you are loading the certificate. If it already exists in your certificate store it's as easy as using `Get-ChildItem -path cert:\*` and changing that star to the right path or filtering out by Subject Name, etc. [Get Certs](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/providers/get-childitem-for-certificate) In Powershell 6 you can use [Get-PfxCertificate from a file](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/get-pfxcertificate) – LimpingNinja Oct 06 '18 at 02:54
  • Otherwise, you will need to use `New-Object System.Security.Cryptography.X509Certificates.X509Certificate2` and use it's import method. – LimpingNinja Oct 06 '18 at 02:55
4

How about simply doing this:

$cert = New-SelfSignedCertificate -FriendlyName "MyCA"
      -KeyExportPolicy ExportableEncrypted 
      -Provider "Microsoft Strong Cryptographic Provider" 
      -Subject "SN=TestRootCA" -NotAfter (Get-Date).AddYears($ExpiryInYears) 
      -CertStoreLocation Cert:\LocalMachine\My -KeyUsageProperty All 
      -KeyUsage CertSign, CRLSign, DigitalSignature

Important parameters are -KeyUsageProperty and -KeyUsage.

Adriaan de Beer
  • 1,136
  • 13
  • 23
  • Hey there, thanks for stopping by! See second comment to the accepted answer as well as first sentence of the request. This functionality is not exposed in `New-SelfSignedCertificate` for Server 2012r2 and prior. [New-SelfSignedCertificate old](https://technet.microsoft.com/fr-fr/library/hh848633(v=wps.620).aspx) [New-SelfSignedCertificate new](https://learn.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=win10-ps) Which was the exact reason for the question. – LimpingNinja Oct 06 '18 at 02:43
  • 1
    My mistake! Sometimes I take my speed reading too far! That certainly explains the recommended answer! – Adriaan de Beer Oct 07 '18 at 09:42
3

"Itiverba Self-Signed certificate generator" (http://www.itiverba.com/en/software/itisscg.php) is a free GUI tool for Windows that allows you to create your own CA certificates and sign end-certificates with it. You can export the certificates in PEM, CER, DER, PFX file formats.

It's just 3 lines to encode :
Subject: CN="Testcorp - Private CA"
Basic Constraints: V (checked)
Basic Constraints / Subject Type: CA

Give a file name and select a file format, then click on the "create certificate" button. Your Custom CA certificate is done.

Steph
  • 70
  • 2
  • Thanks for the reply, but I was looking for a Powershell method to do this within a scripted process; does this have that option? It doesn't appear to. – LimpingNinja Sep 10 '18 at 15:19
1

The easy way of creating a root certificate would be to do the following. Please note the text extension which makes sure that the certificate is a root certificate. Such a certificate must be placed in a root certificate store to indicate trust. E.g. The 'cert:\LocalMachine\My' store.

Make sure that the KeyUsage is what you want. This can of course be changed, but Microsoft is not that good at documenting why you should do what they suggest.

The moving/copying of the certificate must be done done by exporting the certificate and importing it again. Or create the certificate in the correct place. Note that in general, the certificate will only be created in a My store. Some support commands are described in Certificate Provider PowerShell functions.

The certificate will be exportable by default.

$rootCert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My `
            -DnsName "RootCA" `
            -TextExtension @("2.5.29.19={text}CA=true") `
            -KeyUsage CertSign,CrlSign,DigitalSignature;

The code was lifted from https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-create-temporary-certificates-for-use-during-development

  • Hello! Thanks for answering, but this comment was related to 2012r2 Powershell specifically as per the first sentence and previous answer comments. In 2012r2 and prior, PowerShell only has the following params: -DnsName, -CloneCert, -CertStoreLocation, -WhatIf, -Confirm. This means the accepted answer (my own) is still the most correct AFAIK. – LimpingNinja Dec 20 '21 at 21:53
  • @Tarjei Jensen Thanks for posting this. The -KeyUsage CertSign,CrlSign,DigitalSignature parameter was what I was missing in my case... – Zack A Mar 28 '23 at 17:18