modules/HomeLab.Security/Public/New-VpnClientCertificate.ps1

<#
.SYNOPSIS
    Creates a new VPN client certificate signed by an existing root certificate.
.DESCRIPTION
    Creates a client certificate signed by the specified root certificate for VPN authentication.
    Exports the certificate to the specified path.
.PARAMETER RootCertName
    The name of the existing root certificate to use for signing.
.PARAMETER ClientCertName
    The name for the new client certificate.
.PARAMETER ExportPath
    The path where the certificate will be exported. Defaults to %TEMP%.
.PARAMETER CertPassword
    Optional secure string password for the exported certificate.
.EXAMPLE
    New-VpnClientCertificate -RootCertName "MyVPN-Root" -ClientCertName "MyVPN-Client2"
.OUTPUTS
    Hashtable containing success status, certificate path, and thumbprint.
.NOTES
    Author: Jurie Smit
    Date: March 6, 2025
#>

function New-VpnClientCertificate {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$RootCertName,
        
        [Parameter(Mandatory = $true)]
        [string]$ClientCertName,
        
        [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 client certificate: $safeClientCertName using root certificate: $safeRootCertName" -Level INFO
    
    # Validate export path if specified
    if ($ExportPath -and -not (Confirm-ExportPath -Path $ExportPath)) {
        return @{ 
            Success = $false
            Message = "Failed to access or create export directory: $ExportPath"
        }
    }
    
    try {
        # Retrieve the specified root certificate from the CurrentUser store.
        $rootCert = Get-ChildItem -Path Cert:\CurrentUser\My | 
                    Where-Object { $_.Subject -eq "CN=$safeRootCertName" -or $_.Subject -eq "CN=$RootCertName" } | 
                    Select-Object -First 1
        
        if (-not $rootCert) {
            throw "Root certificate '$safeRootCertName' not found in certificate store."
        }
        
        # Create a new client certificate signed by the retrieved root certificate.
        $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 "VPN client certificate created with thumbprint: $($clientCert.Thumbprint)" -Level INFO
        
        # If an export path was provided, export the certificate
        if ($ExportPath) {
            $clientPfxPath = Join-Path -Path $ExportPath -ChildPath "$safeClientCertName.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 $clientCert -FilePath $clientPfxPath -Password $CertPassword | Out-Null
            Write-LogSafely -Message "Exported client certificate to: $clientPfxPath" -Level INFO
            
            return @{
                Success = $true
                Message = "Client certificate created and exported."
                ClientCertThumbprint = $clientCert.Thumbprint
                ClientCertPath = $clientPfxPath
                ClientCertName = $safeClientCertName
            }
        }
        
        return @{
            Success = $true
            Message = "Client certificate created."
            ClientCertThumbprint = $clientCert.Thumbprint
            ClientCertName = $safeClientCertName
        }
    }
    catch {
        Write-LogSafely -Message "Error creating VPN client certificate: $_" -Level ERROR
        return @{ Success = $false; Message = "Failed to create client certificate: $_"; Error = $_ }
    }
}