src/TLS.ps1
using namespace System.Security.Cryptography.X509Certificates using module .\ValidateSet $CaRootDir = Join-Path $env:DEVOPTOOLS_HOME ca root $CaSubDir = Join-Path $env:DEVOPTOOLS_HOME ca sub <# .DESCRIPTION Creates a new root certificate authority (CA) if it doesn't exist. #> function New-RootCA() { [CmdletBinding()] param() if (Test-CA -Root $CaRootDir -Name root_ca) { Write-Warning 'Root CA already exists (skipping)' return } $rootCa = Initialize-CA -Root $CaRootDir -Name root_ca $script = "$PSScriptRoot\CertificateAuthority\create_root.sh" | ConvertTo-WSLPath bash $script --home $rootCa.Home } <# .DESCRIPTION Creates a new subordinate certificate authority (CA) from the root CA if one with the given name doesn't exist. If the root CA doesn't exist, New-SubordinateCA will create it with a warning. .PARAMETER Name The name of the new subordinate CA. It will be used a reference in other command like New-Certificate. .PARAMETER PermittedDNS A list of DNS names that the subordinate CA is permitted to issue. They will be added to the X.509v3 name constraints extension. #> function New-SubordinateCA() { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Name, [string[]] $PermittedDNS ) if (-not (Test-CA -Root $CaRootDir -Name root_ca)) { Write-Warning 'Subordinate CA cannot be created without a root CA (creating)' New-RootCA } if (Test-CA -Root $CaSubDir -Name $Name) { Write-Warning "Subordinate CA with name '$Name' already exists (skipping)" return } $subCa = Initialize-CA -Root $CaSubDir -Name $Name $subCaExt = @" [sub_ca_ext] authorityKeyIdentifier = keyid:always basicConstraints = critical,CA:true,pathlen:0 extendedKeyUsage = serverAuth,clientAuth keyUsage = critical,keyCertSign,cRLSign subjectKeyIdentifier = hash "@ if ($PermittedDNS) { $subCaExt += @" nameConstraints = @name_constraints [name_constraints] excluded;IP.0 = 0.0.0.0/0.0.0.0 excluded;IP.1 = 0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0 $(($PermittedDNS | ForEach-Object { 'permitted;DNS.' + $i++ + " = $_" }) -join [System.Environment]::NewLine) "@ } Out-File -FilePath "$CaRootDir\root_ca\include\sub_ca_ext.conf" -InputObject $subCaExt $script = "$PSScriptRoot\CertificateAuthority\create_sub.sh" | ConvertTo-WSLPath bash $script ` --home $subCa.Home ` --home-root (Join-Path $CaRootDir root_ca | ConvertTo-WSLPath) ` --name $Name } <# .DESCRIPTION Returns the names of available subordinate certificate authorities (CAs). #> function Get-SuboridinateCAName() { Get-ChildItem $CaSubDir -Name } <# .DESCRIPTION Creates a new X.509 certificate. .PARAMETER Issuer The name of the subordinate certificate authority (CA) to issue the certificate. .PARAMETER Request The path to the certificate signing request (CSR) config file. It will be used by the openssl-req command. .PARAMETER Type The type of certificate. Valid values are 'server' and 'client'. .PARAMETER Name The name of the key ([name].key) and certificate ([name].crt) file. .PARAMETER Destination The directory where the key and certificate are created. #> function New-Certificate() { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet([ValidIssuer])] [string] $Issuer, [Parameter(Mandatory)] [string] $Request, [ValidateSet('server', 'client')] [string] $Type = 'server', [string] $Name = 'tls', [string] $Destination = (Resolve-Path .) ) if (-not (Test-CA -Root $CaSubDir -Name $Issuer)) { Write-Error "Subordinate CA '$Issuer' does not exist!" return } if (-not (Test-Path $Request)) { Write-Error "Request file '$Request' does not exist!" return } if (-not (Test-Path $Destination)) { New-Item $Destination -ItemType Directory 1> $null } $script = "$PSScriptRoot\CertificateAuthority\new_cert.sh" | ConvertTo-WSLPath bash $script ` --home (Join-Path $CaSubDir $Issuer | ConvertTo-WSLPath) ` --home-root (Join-Path $CaRootDir root_ca | ConvertTo-WSLPath) ` --request (ConvertTo-WSLPath -Path $Request) ` --destination (ConvertTo-WSLPath -Path $Destination) ` --name $Name ` --type $Type Remove-Item $Destination\$Name.csr -ErrorAction Ignore } <# .DESCRIPTION Installs the root certificate authority (CA) into the current user's trusted root store. #> function Install-RootCA() { $certPath = Join-Path $CaRootDir root_ca ca.crt Install-Certificate -Path $certPath -StoreName Root -FriendlyName 'DevOpTools Development Root CA' } <# .DESCRIPTION Uninstalls the root certificate authority (CA) from the current user's trusted root store. #> function Uninstall-RootCA() { $certPath = Join-Path $CaRootDir root_ca ca.crt Uninstall-Certificate -Path $certPath -StoreName Root } function Install-Certificate() { [OutputType([X509Certificate2])] param( [Parameter(Mandatory)] [string]$Path, [Parameter(Mandatory)] [string]$StoreName, [string]$FriendlyName ) $store = Open-X509Store -StoreName $StoreName -OpenFlags ([OpenFlags]::ReadWrite) try { $cert = [X509Certificate2]::new($Path) if ($FriendlyName) { $cert.FriendlyName = $FriendlyName } $store.Add($cert) } finally { $store.Close() } return $cert } function Uninstall-Certificate() { param( [Parameter(Mandatory)] [string]$Path, [Parameter(Mandatory)] [string]$StoreName ) $store = Open-X509Store -StoreName $StoreName try { $cert = [X509Certificate2]::new($Path) $store.Remove($cert) } finally { $store.Close() } } function Open-X509Store() { [OutputType([X509Store])] param( [Parameter(Mandatory)] [string]$StoreName, [StoreLocation]$StoreLocation = [StoreLocation]::CurrentUser, [OpenFlags]$OpenFlags = [OpenFlags]::MaxAllowed ) $store = [X509Store]::new($StoreName, $StoreLocation) if (-not $?) { throw "Failed to access the $StoreLocation\$StoreName certificate store!" } $store.open($OpenFlags) if (-not $?) { throw "Failed to open the $StoreLocation\$StoreName certificate store with $OpenFlags privileges!" } return $store; } function Test-CA() { [OutputType([bool])] param( [Parameter(Mandatory)] [string]$Root, [Parameter(Mandatory)] [string]$Name ) return Test-Path "$Root\$Name\ca.pfx" -PathType Leaf } function Initialize-CA() { param( [Parameter(Mandatory)] [string]$Root, [Parameter(Mandatory)] [string]$Name ) $caHome = Join-Path $Root $Name Write-Verbose "Initializing CA '$Name' at '$caHome'" Remove-Item -Recurse -Force $caHome -ErrorAction Ignore $certs = Join-Path $caHome certs $db = Join-Path $caHome db $private = Join-Path $caHome private $include = Join-Path $caHome include New-Item $certs, $db, $private, $include -ItemType Directory 1> $null $dbIndex = Join-Path $db index New-Item $dbIndex -ItemType File 1> $null return [PSCustomObject]@{ Home = ConvertTo-WSLPath $caHome } } |