How do I use Bouncy Castle from PowerShell?

17 Apr 2013 09:54 bouncy-castle powershell

I recently wrote a series of blog posts on how to use the cryptography libraries from the Legion of the BouncyCastle in C#.

Here’s where I show how to use the libraries from PowerShell.

I’ll assume that you’ve read the other blog posts; this is going to be a breakneck race through getting that lot to work in PowerShell.

Generating Random Numbers

$randomGenerator = New-Object Org.BouncyCastle.Crypto.Prng.CryptoApiRandomGenerator
$random = New-Object Org.BouncyCastle.Security.SecureRandom($randomGenerator)

One problem with PowerShell is that it doesn’t have a using declaration, and you have to fully-qualify type names. So, in order to make life easier, I usually wrap this sort of thing in a function…

function New-SecureRandom
{
    $randomGenerator = New-Object Org.BouncyCastle.Crypto.Prng.CryptoApiRandomGenerator
	$random = New-Object Org.BouncyCastle.Security.SecureRandom($randomGenerator)
}

The Certificate Generator

function New-CertificateGenerator
{
	$certificateGenerator = New-Object Org.BouncyCastle.X509.X509V3CertificateGenerator
	return $certificateGenerator
}

Then we can use it as follows:

$certificateGenerator = New-CertificateGenerator

Serial Number

function New-SerialNumber(
	[Org.BouncyCastle.Security.SecureRandom] $random)
{
	$serialNumber =
		[Org.BouncyCastle.Utilities.BigIntegers]::CreateRandomInRange(
			[Org.BouncyCastle.Math.BigInteger]::One,
			[Org.BouncyCastle.Math.BigInteger]::ValueOf([Int64]::MaxValue),
			$random)

	return $serialNumber
}

$random = New-SecureRandom
$serialNumber = New-SerialNumber $random

$certificateGenerator.SetSerialNumber($serialNumber)

Signature Algorithm

Note: You can get the list of available signature algorithms by:

PS> $certificateGenerator.SignatureAlgNames
SHA224WITHRSAANDMGF1
GOST3411WITHGOST3410-94
# ...
SHA256WITHRSA
# ...
RIPEMD128WITHRSAENCRYPTION

As with the C# example, we’ll use SHA1 with RSA…

$signatureAlgorithm = "SHA256WithRSA"
$certificateGenerator.SetSignatureAlgorithm($signatureAlgorithm)

Issuer and Subject Name

$issuerDN = New-Object Org.BouncyCastle.Asn1.X509.X509Name($issuerName)
$subjectDN = New-Object Org.BouncyCastle.Asn1.X509.X509Name($subjectName)
$certificateGenerator.SetIssuerDN($issuerDN)
$certificateGenerator.SetSubjectDN($subjectDN)

Valid For

$notBefore = [DateTime]::UtcNow.Date
$notAfter = $notBefore.AddYears(2)

$certificateGenerator.SetNotBefore($notBefore)
$certificateGenerator.SetNotAfter($notAfter)

Subject Public Key

function New-KeyPair
{
param(
    [Parameter(Mandatory = $true)]
    [Org.BouncyCastle.Security.SecureRandom] $random,

    [Parameter(Mandatory = $false)]
    [int] $strength = 2048
)

    $keyGenerationParameters =
		New-Object Org.BouncyCastle.Crypto.KeyGenerationParameters($random, $strength)

    $keyPairGenerator =
		New-Object Org.BouncyCastle.Crypto.Generators.RsaKeyPairGenerator

    $keyPairGenerator.Init($keyGenerationParameters)
    $keyPair = $keyPairGenerator.GenerateKeyPair()

    return $keyPair
}

$subjectKeyPair = New-KeyPair $random
$certificateGenerator.SetPublicKey($subjectKeyPair.Public)

Authority Key Identifier

See “Missing Certificate Attributes” for discussion of how to do this in C#.

function New-AuthorityKeyIdentifier
{
param(
    [Parameter(Mandatory = $true)]
    [string] $name,

    [Parameter(Mandatory = $true)]
    [Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters] $publicKey,

    [Parameter(Mandatory = $true)]
    [Org.BouncyCastle.Math.BigInteger] $serialNumber
)

    $publicKeyInfo =
		[Org.BouncyCastle.X509.SubjectPublicKeyInfoFactory]::CreateSubjectPublicKeyInfo($publicKey)

    $generalName = New-Object Org.BouncyCastle.Asn1.X509.GeneralName($name)
    $generalNames = New-Object Org.BouncyCastle.Asn1.X509.GeneralNames($generalName)

    $authorityKeyIdentifier =
        New-Object Org.BouncyCastle.Asn1.X509.AuthorityKeyIdentifier(
            $publicKeyInfo, $generalNames, $serialNumber)

	return $authorityKeyIdentifier
}

For the next bit, we’ll go a bit “fluent”. I’ll discuss the pros and cons of this in a later blog post:

function Add-AuthorityKeyIdentifier
{
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [Org.BouncyCastle.X509.X509V3CertificateGenerator] $certificateGenerator,

    [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $false)]
    [Org.BouncyCastle.Asn1.X509.AuthorityKeyIdentifier] $authorityKeyIdentifier
)

    $certificateGenerator.AddExtension(
        [Org.BouncyCastle.Asn1.X509.X509Extensions]::AuthorityKeyIdentifier.Id,
        $false,
        $authorityKeyIdentifier)

    return $certificateGenerator
}

This allows us to use the certificate generator as follows:

# Fluent; less repetition.
$certificateGenerator |
	Add-AuthorityKeyIdentifier $authorityKeyIdentifier |
	Add-SubjectKeyIdentifier $subjectKeyIdentifier

…which is a bit more readable (in my opinion) than the following:

# Non-fluent; more verbose.
Add-AuthorityKeyIdentifier $certificateGenerator $authorityKeyIdentifier
Add-SubjectKeyIdentifier $certificateGenerator $subjectKeyIdentifier

Subject Key Identifier

This bit’s simpler than the authority key identifer…

function New-SubjectKeyIdentifier
{
param(
    [Parameter(Mandatory = $true)]
    [Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters] $publicKey
)

    $publicKeyInfo =
        [Org.BouncyCastle.X509.SubjectPublicKeyInfoFactory]::CreateSubjectPublicKeyInfo($publicKey)

    $subjectKeyIdentifier =
        New-Object Org.BouncyCastle.Asn1.X509.SubjectKeyIdentifier($publicKeyInfo)

    return $subjectKeyIdentifier
}

This bit’s more-or-less the same:

function Add-SubjectKeyIdentifier
{
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [Org.BouncyCastle.X509.X509V3CertificateGenerator] $certificateGenerator,

    [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $false)]
    [Org.BouncyCastle.Asn1.X509.SubjectKeyIdentifier] $subjectKeyIdentifier
)

    $certificateGenerator.AddExtension(
        [Org.BouncyCastle.Asn1.X509.X509Extensions]::SubjectKeyIdentifier.Id,
        $false,
        $subjectKeyIdentifier)

    return $certificateGenerator
}

Basic Constraints

function Add-BasicConstraints
{
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [Org.BouncyCastle.X509.X509V3CertificateGenerator] $certificateGenerator,

    [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $false)]
    [bool] $isCertificateAuthority
)

    $basicConstraints =
        New-Object Org.BouncyCastle.Asn1.X509.BasicConstraints($isCertificateAuthority)
    $certificateGenerator.AddExtension(
        [Org.BouncyCastle.Asn1.X509.X509Extensions]::BasicConstraints.Id,
        $true,
        $basicConstraints)

    return $certificateGenerator
}

Generating the Certificate

If it’s self-signed, we can do this:

$issuerKeyPair = $subjectKeyPair
$certificate = $certificateGenerator.Generate($issuerKeyPair.Private, $random)

Converting to a .NET Certificate

See the discussion of how to do this in C# first. This is that, directly translated into PowerShell.

function ConvertFrom-BouncyCastleCertificate
{
param(
    [Parameter(Mandatory = $true)]
    [Org.BouncyCastle.X509.X509Certificate] $certificate,

    [Parameter(Mandatory = $true)]
    [Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair] $subjectKeyPair,

    [Parameter(Mandatory = $true)]
    [string] $friendlyName
)

    $store = New-Object Org.BouncyCastle.Pkcs.Pkcs12Store

    $certificateEntry = New-Object Org.BouncyCastle.Pkcs.X509CertificateEntry($certificate)
    $store.SetCertificateEntry($friendlyName, $certificateEntry)

    $keyEntry = New-Object Org.BouncyCastle.Pkcs.AsymmetricKeyEntry($subjectKeyPair.Private)
    $store.SetKeyEntry($friendlyName, $keyEntry, @($certificateEntry))

    # The password is re-used immediately, so it doesn't matter what it is.
    $password = 'password'
    $stream = New-Object System.IO.MemoryStream
    $store.Save($stream, $password, $random)

    $keyStorageFlags = 'PersistKeySet, Exportable'
    $result =
		New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
			$stream.ToArray(), $password, $keyStorageFlags)

    $stream.Dispose()

    return $result
}

Note that we don’t bother writing the certificate to a .PFX file, since it’s perfectly usable as it is.

Putting it all together

The source code’s in my PSBouncyCastle GitHub repository.