# iWinNAC per-kiosk TLS certificate helper (invoked by Run.bat / uninstall.bat)
param(
    [Parameter(Mandatory = $true)]
    [ValidateSet('Ensure', 'Trust', 'Remove', 'RemoveTrust', 'Cleanup')]
    [string]$Action,

    [string]$Thumbprint,
    [string]$OutputFile,
    [string]$OutputMetaFile,
    [int]$RenewBeforeDays = 30
)

$ErrorActionPreference = 'Stop'

$FriendlyNamePrefix = 'iWinNAC-HTTPS'
$FriendlyName       = "$FriendlyNamePrefix-$env:COMPUTERNAME"
$LegacyThumbprint   = 'F3C26347A7224962CF5E37BBE70831FEDDDEE63B'

function Write-CertError {
    param(
        [string]$Message,
        [string]$Detail = ''
    )

    $line = if ([string]::IsNullOrWhiteSpace($Detail)) { $Message } else { "$Message $Detail" }
    [Console]::Error.WriteLine($line)
}

function Test-Administrator {
    $identity  = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($identity)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Remove-CertFromStores {
    param(
        [string]$Thumb,
        [string[]]$StoreNames = @('My', 'Root')
    )

    if ([string]::IsNullOrWhiteSpace($Thumb)) { return }

    foreach ($storeName in $StoreNames) {
        $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($storeName, 'LocalMachine')
        $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
        $found = @($store.Certificates | Where-Object { $_.Thumbprint -eq $Thumb })
        foreach ($cert in $found) {
            $store.Remove($cert)
        }
        $store.Close()
    }
}

function Remove-LegacySharedCert {
    try {
        Remove-CertFromStores -Thumb $LegacyThumbprint
    } catch {
        Write-CertError -Message '[WARN] Could not remove legacy shared certificate.' -Detail $_.Exception.Message
    }
}

function Write-ThumbprintOutput {
    param([string]$Thumb)

    if ([string]::IsNullOrWhiteSpace($Thumb)) {
        throw 'Certificate thumbprint is empty.'
    }

    if (-not [string]::IsNullOrWhiteSpace($OutputFile)) {
        [System.IO.File]::WriteAllText($OutputFile, $Thumb.Trim())
    } else {
        Write-Output $Thumb.Trim()
    }
}

function Write-MetaOutput {
    param([string]$Value)

    if (-not [string]::IsNullOrWhiteSpace($OutputMetaFile)) {
        [System.IO.File]::WriteAllText($OutputMetaFile, $Value.Trim())
    }
}

function Get-OrCreateCertificate {
    Remove-LegacySharedCert

    $renewThreshold = (Get-Date).AddDays($RenewBeforeDays)
    $existing = Get-ChildItem Cert:\LocalMachine\My | Where-Object {
        $_.FriendlyName -eq $FriendlyName -and $_.NotAfter -gt $renewThreshold
    } | Select-Object -First 1

    if ($existing) {
        Write-MetaOutput -Value 'REUSED'
        Write-ThumbprintOutput -Thumb $existing.Thumbprint
        return
    }

    Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.FriendlyName -eq $FriendlyName } | ForEach-Object {
        Remove-CertFromStores -Thumb $_.Thumbprint
    }

    $cert = New-SelfSignedCertificate `
        -Subject 'CN=localhost' `
        -DnsName 'localhost' `
        -FriendlyName $FriendlyName `
        -CertStoreLocation 'Cert:\LocalMachine\My' `
        -KeyExportPolicy NonExportable `
        -KeyAlgorithm RSA `
        -KeyLength 2048 `
        -NotAfter (Get-Date).AddYears(2) `
        -KeyUsage DigitalSignature, KeyEncipherment `
        -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.1')

    if (-not $cert -or [string]::IsNullOrWhiteSpace($cert.Thumbprint)) {
        throw 'Certificate was created but no thumbprint was returned.'
    }

    Write-MetaOutput -Value 'NEW'
    Write-ThumbprintOutput -Thumb $cert.Thumbprint
}

function Install-Trust {
    param([string]$Thumb)

    if ([string]::IsNullOrWhiteSpace($Thumb)) {
        throw 'Thumbprint is required for Trust action.'
    }

    $cert = Get-ChildItem "Cert:\LocalMachine\My\$Thumb" -ErrorAction Stop
    $root = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root', 'LocalMachine')
    $root.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)

    $alreadyTrusted = @($root.Certificates | Where-Object { $_.Thumbprint -eq $Thumb })
    if ($alreadyTrusted.Count -eq 0) {
        $root.Add($cert)
    }

    $root.Close()
}

function Remove-Trust {
    param([string]$Thumb)

    if ([string]::IsNullOrWhiteSpace($Thumb)) { return }
    Remove-CertFromStores -Thumb $Thumb -StoreNames @('Root')
}

function Remove-Certificate {
    param([string]$Thumb)

    if ([string]::IsNullOrWhiteSpace($Thumb)) { return }
    Remove-CertFromStores -Thumb $Thumb
}

function Remove-KioskCertificates {
    Get-ChildItem Cert:\LocalMachine\My | Where-Object {
        $_.FriendlyName -like "$FriendlyNamePrefix-*"
    } | ForEach-Object {
        Remove-CertFromStores -Thumb $_.Thumbprint
    }

    Remove-LegacySharedCert
}

try {
    if (-not (Test-Administrator)) {
        throw 'Administrator privileges are required to manage certificates on this machine.'
    }

    switch ($Action) {
        'Ensure'      { Get-OrCreateCertificate }
        'Trust'       { Install-Trust -Thumb $Thumbprint }
        'Remove'      { Remove-Certificate -Thumb $Thumbprint }
        'RemoveTrust' { Remove-Trust -Thumb $Thumbprint }
        'Cleanup'     { Remove-KioskCertificates }
    }

    exit 0
}
catch {
    switch -Regex ($_.Exception.Message) {
        'Access is denied' {
            Write-CertError -Message '[ERROR] Certificate operation failed: Access denied.'
            Write-CertError -Message '        Run Run.bat as Administrator (right-click > Run as administrator).'
        }
        'Cannot find path' {
            Write-CertError -Message "[ERROR] Certificate not found in LocalMachine\My store."
            Write-CertError -Message "        Thumbprint: $Thumbprint"
            Write-CertError -Message '        Re-run Run.bat from the beginning.'
        }
        default {
            Write-CertError -Message "[ERROR] Certificate helper failed during action '$Action':"
            Write-CertError -Message "        $($_.Exception.Message)"
        }
    }

    exit 1
}
