modules/HomeLab.Security/Public/New-VpnRootCertificate.ps1
<#
.SYNOPSIS Creates a new VPN root certificate and an initial client certificate. .DESCRIPTION Creates a self-signed root certificate and an initial client certificate for VPN authentication. Exports both certificates to the specified path. .PARAMETER RootCertName The name for the root certificate. .PARAMETER ClientCertName The name for the initial client certificate. .PARAMETER CreateNewRoot If specified, creates a new root certificate even if one already exists. .PARAMETER ExportPath The path where certificates will be exported. Defaults to %TEMP%. .PARAMETER CertPassword Optional secure string password for the exported certificates. .EXAMPLE New-VpnRootCertificate -RootCertName "MyVPN-Root" -ClientCertName "MyVPN-Client" .OUTPUTS Hashtable containing success status, certificate paths, and thumbprints. .NOTES Author: Jurie Smit Date: March 6, 2025 #> function New-VpnRootCertificate { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$RootCertName, [Parameter(Mandatory = $true)] [string]$ClientCertName, [Parameter(Mandatory = $false)] [switch]$CreateNewRoot, [Parameter(Mandatory = $false)] [string]$ExportPath = $env:TEMP, [Parameter(Mandatory = $false)] [securestring]$CertPassword ) # Sanitize certificate names $safeRootCertName = Get-SanitizedCertName -Name $RootCertName $safeClientCertName = Get-SanitizedCertName -Name $ClientCertName # Log any name changes if ($safeRootCertName -ne $RootCertName) { Write-LogSafely -Message "Root certificate name sanitized from '$RootCertName' to '$safeRootCertName'" -Level WARNING } if ($safeClientCertName -ne $ClientCertName) { Write-LogSafely -Message "Client certificate name sanitized from '$ClientCertName' to '$safeClientCertName'" -Level WARNING } Write-LogSafely -Message "Creating new VPN root certificate: $safeRootCertName with client certificate: $safeClientCertName" -Level INFO # Validate export path if (-not (Confirm-ExportPath -Path $ExportPath)) { return @{ Success = $false Message = "Failed to access or create export directory: $ExportPath" } } try { # Create a self-signed root certificate in the CurrentUser store valid for 5 years. $rootCert = New-SelfSignedCertificate -Subject "CN=$safeRootCertName" ` -CertStoreLocation "Cert:\CurrentUser\My" ` -KeyExportPolicy Exportable ` -KeyUsage CertSign, CRLSign, DigitalSignature ` -KeyUsageProperty All ` -KeyLength 2048 ` -HashAlgorithm SHA256 ` -NotAfter (Get-Date).AddYears(5) if ($CreateNewRoot) { Write-LogSafely -Message "A new root certificate was created with thumbprint: $($rootCert.Thumbprint)" -Level INFO } # Export the root certificate as a PFX file with a secure password $pfxPath = Join-Path -Path $ExportPath -ChildPath "$safeRootCertName.pfx" # If no password is provided, prompt for one securely if (-not $CertPassword) { $CertPassword = Read-Host -Prompt "Enter password for certificate export" -AsSecureString } Export-PfxCertificate -Cert $rootCert -FilePath $pfxPath -Password $CertPassword | Out-Null Write-LogSafely -Message "Exported root certificate to: $pfxPath" -Level INFO # Also export the public certificate (CER) for VPN gateway configuration $cerPath = Join-Path -Path $ExportPath -ChildPath "$safeRootCertName.cer" Export-Certificate -Cert $rootCert -FilePath $cerPath -Type CERT | Out-Null Write-LogSafely -Message "Exported public certificate to: $cerPath" -Level INFO # Also export as Base64 encoded text file for Azure VPN Gateway $txtPath = Join-Path -Path $ExportPath -ChildPath "$safeRootCertName.txt" $certData = [System.Convert]::ToBase64String($rootCert.Export('Cert')) [System.IO.File]::WriteAllText($txtPath, $certData) Write-LogSafely -Message "Exported Base64 encoded certificate to: $txtPath" -Level INFO # Immediately create an initial client certificate signed by the root certificate, valid for 2 years. $clientCert = New-SelfSignedCertificate -Subject "CN=$safeClientCertName" ` -CertStoreLocation "Cert:\CurrentUser\My" ` -Signer $rootCert ` -KeyExportPolicy Exportable ` -KeyLength 2048 ` -HashAlgorithm SHA256 ` -NotAfter (Get-Date).AddYears(2) Write-LogSafely -Message "Created initial client certificate with thumbprint: $($clientCert.Thumbprint)" -Level INFO # Export the client certificate as well $clientPfxPath = Join-Path -Path $ExportPath -ChildPath "$safeClientCertName.pfx" Export-PfxCertificate -Cert $clientCert -FilePath $clientPfxPath -Password $CertPassword | Out-Null Write-LogSafely -Message "Exported client certificate to: $clientPfxPath" -Level INFO return @{ Success = $true Message = "Root and initial client certificate created." RootCertThumbprint = $rootCert.Thumbprint ClientCertThumbprint = $clientCert.Thumbprint RootCertPath = $pfxPath RootCerPath = $cerPath RootTxtPath = $txtPath ClientCertPath = $clientPfxPath RootCertName = $safeRootCertName ClientCertName = $safeClientCertName } } catch { Write-LogSafely -Message "Error creating VPN root certificate: $_" -Level ERROR return @{ Success = $false; Message = "Failed to create root certificate: $_"; Error = $_ } } } |