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

<#
.SYNOPSIS
    Creates an additional VPN client certificate.
.DESCRIPTION
    Creates a new client certificate signed by an existing root certificate found using a pattern.
    Exports the certificate to the specified path.
.PARAMETER NewClientName
    The name for the new client certificate.
.PARAMETER RootCertPattern
    Pattern to find the root certificate. Defaults to "*vpn-root*".
.PARAMETER ExportPath
    The path where the certificate will be exported. Defaults to %TEMP%.
.PARAMETER CertPassword
    Optional secure string password for the exported certificate.
.EXAMPLE
    Add-VpnClientCertificate -NewClientName "MyVPN-Client3"
.OUTPUTS
    Hashtable containing success status, certificate path, and thumbprint.
.NOTES
    Author: Jurie Smit
    Date: March 6, 2025
#>

function Add-VpnClientCertificate {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$NewClientName,
        
        [Parameter(Mandatory = $false)]
        [string]$RootCertPattern = "*vpn-root*",
        
        [Parameter(Mandatory = $false)]
        [string]$ExportPath = $env:TEMP,
        
        [Parameter(Mandatory = $false)]
        [securestring]$CertPassword
    )
    
    # Sanitize client name
    $safeClientName = Get-SanitizedCertName -Name $NewClientName
    
    # Log any name changes
    if ($safeClientName -ne $NewClientName) {
        Write-LogSafely -Message "Client name sanitized from '$NewClientName' to '$safeClientName'" -Level WARNING
    }
    
    Write-LogSafely -Message "Adding additional VPN client certificate: $safeClientName" -Level INFO
    
    # Validate export path
    if (-not (Confirm-ExportPath -Path $ExportPath)) {
        return @{ 
            Success = $false
            Message = "Failed to access or create export directory: $ExportPath"
        }
    }
    
    try {
        # Retrieve the existing root certificate using the provided pattern
        $rootCert = Get-ChildItem -Path Cert:\CurrentUser\My | 
                    Where-Object { $_.Subject -like "CN=$RootCertPattern" -or $_.Subject -like $RootCertPattern } | 
                    Select-Object -First 1
        
        if (-not $rootCert) {
            throw "No VPN root certificate found matching pattern '$RootCertPattern'."
        }
        
        Write-LogSafely -Message "Found root certificate: $($rootCert.Subject) with thumbprint: $($rootCert.Thumbprint)" -Level DEBUG
        
        # Create a new client certificate signed by the root certificate.
        $clientCert = New-SelfSignedCertificate -Subject "CN=$safeClientName" `
                                               -CertStoreLocation "Cert:\CurrentUser\My" `
                                               -Signer $rootCert `
                                               -KeyExportPolicy Exportable `
                                               -KeyLength 2048 `
                                               -HashAlgorithm SHA256 `
                                               -NotAfter (Get-Date).AddYears(2)
        
        Write-LogSafely -Message "Additional VPN client certificate created with thumbprint: $($clientCert.Thumbprint)" -Level INFO
        
        # If a path was provided, export the certificate
        if ($ExportPath) {
            $clientPfxPath = Join-Path -Path $ExportPath -ChildPath "$safeClientName.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 = "Additional client certificate created and exported."
                ClientCertThumbprint = $clientCert.Thumbprint
                ClientCertPath = $clientPfxPath
                ClientCertName = $safeClientName
            }
        }
        
        return @{
            Success = $true
            Message = "Additional client certificate created."
            ClientCertThumbprint = $clientCert.Thumbprint
            ClientCertName = $safeClientName
        }
    }
    catch {
        Write-LogSafely -Message "Error adding additional VPN client certificate: $_" -Level ERROR
        return @{ Success = $false; Message = "Failed to add additional client certificate: $_"; Error = $_ }
    }
}