← Back to Files
new-lan-certificate.ps1
#requires -Version 7.0
param(
[string]$PfxPath = "",
[string]$RootCertificatePath = "",
[string]$Password = "",
[string[]]$DnsName = @(),
[string[]]$IpAddress = @(),
[int]$Years = 3,
[switch]$TrustCurrentUser
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
if ([string]::IsNullOrWhiteSpace($PfxPath)) {
$PfxPath = Join-Path $repoRoot ".kaichat-lan.pfx"
}
if ([string]::IsNullOrWhiteSpace($RootCertificatePath)) {
$RootCertificatePath = Join-Path $repoRoot ".kaichat-lan-root.cer"
}
$hostNames = New-Object System.Collections.Generic.List[string]
$hostNames.Add("localhost")
if (-not [string]::IsNullOrWhiteSpace($env:COMPUTERNAME)) {
$hostNames.Add($env:COMPUTERNAME)
$hostNames.Add("$env:COMPUTERNAME.local")
}
foreach ($name in $DnsName) {
if (-not [string]::IsNullOrWhiteSpace($name)) {
$hostNames.Add($name.Trim())
}
}
$ipNames = New-Object System.Collections.Generic.List[string]
$ipNames.Add("127.0.0.1")
try {
Get-NetIPAddress -AddressFamily IPv4 |
Where-Object { $_.IPAddress -and $_.IPAddress -ne "127.0.0.1" -and $_.IPAddress -notlike "169.254*" } |
ForEach-Object { $ipNames.Add($_.IPAddress) }
} catch {
Write-Warning "Could not auto-detect local IP addresses: $($_.Exception.Message)"
}
foreach ($ip in $IpAddress) {
if (-not [string]::IsNullOrWhiteSpace($ip)) {
$ipNames.Add($ip.Trim())
}
}
$uniqueHostNames = $hostNames | Sort-Object -Unique
$uniqueIpNames = $ipNames | Sort-Object -Unique
$notBefore = [DateTimeOffset]::UtcNow.AddDays(-1)
$notAfter = [DateTimeOffset]::UtcNow.AddYears([Math]::Max(1, $Years))
$rootKey = [System.Security.Cryptography.RSA]::Create(4096)
$serverKey = [System.Security.Cryptography.RSA]::Create(2048)
try {
$rootSubject = [System.Security.Cryptography.X509Certificates.X500DistinguishedName]::new("CN=KaiChat Local Root CA")
$rootRequest = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new(
$rootSubject,
$rootKey,
[System.Security.Cryptography.HashAlgorithmName]::SHA256,
[System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
$rootRequest.CertificateExtensions.Add(
[System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($true, $false, 0, $true))
$rootRequest.CertificateExtensions.Add(
[System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new(
[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyCertSign -bor
[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::CrlSign,
$true))
$rootRequest.CertificateExtensions.Add(
[System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension]::new(
$rootRequest.PublicKey,
$false))
$rootCertificate = $rootRequest.CreateSelfSigned($notBefore, $notAfter)
$serverSubject = [System.Security.Cryptography.X509Certificates.X500DistinguishedName]::new("CN=KaiChat LAN")
$serverRequest = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new(
$serverSubject,
$serverKey,
[System.Security.Cryptography.HashAlgorithmName]::SHA256,
[System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
$serverRequest.CertificateExtensions.Add(
[System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($false, $false, 0, $true))
$serverRequest.CertificateExtensions.Add(
[System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new(
[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::DigitalSignature -bor
[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyEncipherment,
$true))
$eku = [System.Security.Cryptography.OidCollection]::new()
[void]$eku.Add([System.Security.Cryptography.Oid]::new("1.3.6.1.5.5.7.3.1", "Server Authentication"))
$serverRequest.CertificateExtensions.Add(
[System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($eku, $false))
$san = [System.Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder]::new()
foreach ($name in $uniqueHostNames) {
$san.AddDnsName($name)
}
foreach ($ip in $uniqueIpNames) {
$parsedIp = $null
if ([System.Net.IPAddress]::TryParse($ip, [ref]$parsedIp)) {
$san.AddIpAddress($parsedIp)
} else {
Write-Warning "Skipping invalid IP address '$ip'."
}
}
$serverRequest.CertificateExtensions.Add($san.Build())
$serial = New-Object byte[] 16
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
try {
$rng.GetBytes($serial)
} finally {
$rng.Dispose()
}
$serial[0] = $serial[0] -band 0x7F
$serverCertificate = $serverRequest.Create($rootCertificate, $notBefore, $notAfter, $serial)
$serverCertificateWithKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey(
$serverCertificate,
$serverKey)
$pfxBytes = $serverCertificateWithKey.Export(
[System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx,
$Password)
[System.IO.File]::WriteAllBytes($PfxPath, $pfxBytes)
$rootBytes = $rootCertificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
[System.IO.File]::WriteAllBytes($RootCertificatePath, $rootBytes)
if ($TrustCurrentUser) {
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new(
[System.Security.Cryptography.X509Certificates.StoreName]::Root,
[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser)
try {
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$store.Add($rootCertificate)
} finally {
$store.Close()
}
}
Write-Host "Created KaiChat LAN HTTPS certificate:"
Write-Host " PFX: $PfxPath"
Write-Host " Root: $RootCertificatePath"
Write-Host ""
Write-Host "Certificate names:"
foreach ($name in $uniqueHostNames) { Write-Host " DNS: $name" }
foreach ($ip in $uniqueIpNames) { Write-Host " IP: $ip" }
Write-Host ""
Write-Host "Restart KaiChat without --urls, then open https://localhost:5201 or https://<lan-ip>:5201."
Write-Host "Trust/import the root .cer on each browser/device that needs to accept this certificate."
} finally {
$rootKey.Dispose()
$serverKey.Dispose()
}