gen-cert.ps1
Rama main · réplica de lectura del repositorio original.
# Generate a self-signed TLS certificate for local/LAN HTTPS.
# Required for camera access (getUserMedia) on mobile browsers — Chrome on
# Android and Safari on iOS both refuse `getUserMedia` on plain HTTP origins.
#
# Usage:
# powershell -ExecutionPolicy Bypass -File gen-cert.ps1
# powershell -ExecutionPolicy Bypass -File gen-cert.ps1 -Domain myhost.duckdns.org
# powershell -ExecutionPolicy Bypass -File gen-cert.ps1 myhost.duckdns.org
#
# The optional domain is added to the certificate's SAN list alongside
# localhost + detected LAN IP, so the cert is valid for public-DNS access too.
# Can also be set via the CERT_DOMAIN environment variable.
#
# Tries openssl first (ships with Git for Windows), falls back to
# PowerShell's New-SelfSignedCertificate + PEM export.
param(
[Parameter(Position = 0)]
[string]$Domain = $env:CERT_DOMAIN
)
$ErrorActionPreference = 'Stop'
$certDir = Join-Path $PSScriptRoot 'certs'
# Whitelist DOMAIN before interpolating into the OpenSSL
# config heredoc / SAN text-extension. A newline or unexpected character
# in a hostile env file could otherwise inject extra cert extensions.
if ($Domain -and $Domain -notmatch '^[A-Za-z0-9.-]+$') {
Write-Host 'ERROR: Domain must contain only A-Za-z0-9.-' -ForegroundColor Red
exit 1
}
if ((Test-Path "$certDir\server.key") -and (Test-Path "$certDir\server.crt")) {
Write-Host ' -> TLS certificates found.'
exit 0
}
# Detect LAN IP
$lanIp = '192.168.1.100'
try {
$addr = Get-NetIPAddress -AddressFamily IPv4 |
Where-Object { $_.InterfaceAlias -notmatch 'Loopback' -and $_.IPAddress -notmatch '^169\.' -and $_.IPAddress -ne '127.0.0.1' } |
Select-Object -First 1
if ($addr) { $lanIp = $addr.IPAddress }
} catch {}
Write-Host " -> LAN IP: $lanIp"
if ($Domain) { Write-Host " -> Domain: $Domain" }
if (-not (Test-Path $certDir)) { New-Item -ItemType Directory -Path $certDir | Out-Null }
# Build SAN list: domain first (if provided), then localhost + LAN IP
$sanParts = @()
if ($Domain) { $sanParts += "DNS:$Domain" }
$sanParts += 'DNS:localhost', 'IP:127.0.0.1', "IP:$lanIp"
$sanOpenssl = $sanParts -join ','
# --- Method 1: openssl (preferred, ships with Git for Windows) ---
$opensslPath = Get-Command openssl -ErrorAction SilentlyContinue
if ($opensslPath) {
Write-Host ' -> Generating with openssl...'
$cnf = Join-Path $env:TEMP 'meisterpilze_cert.cnf'
@"
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = v3_ext
[dn]
CN = Meistertracker
[v3_ext]
subjectAltName = $sanOpenssl
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
"@ | Set-Content -Path $cnf -Encoding ASCII
# openssl prints key-generation progress to stderr; suppress it
$prev = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
& openssl req -x509 -newkey rsa:2048 -nodes `
-keyout "$certDir\server.key" `
-out "$certDir\server.crt" `
-days 365 `
-config $cnf 2>&1 | Out-Null
$ErrorActionPreference = $prev
Remove-Item $cnf -ErrorAction SilentlyContinue
if ((Test-Path "$certDir\server.key") -and (Test-Path "$certDir\server.crt")) {
Write-Host ' -> TLS certificate generated.'
exit 0
}
Write-Host ' -> openssl failed, trying PowerShell fallback...'
}
# --- Method 2: PowerShell New-SelfSignedCertificate ---
try {
Write-Host ' -> Generating with PowerShell...'
# Build SAN string for New-SelfSignedCertificate's text-extension syntax
$sanTextParts = @()
if ($Domain) { $sanTextParts += "DNS=$Domain" }
$sanTextParts += 'DNS=localhost', 'IPAddress=127.0.0.1', "IPAddress=$lanIp"
$sanText = $sanTextParts -join '&'
$cert = New-SelfSignedCertificate `
-Subject 'CN=Meistertracker' `
-TextExtension @("2.5.29.17={text}$sanText") `
-CertStoreLocation 'Cert:\CurrentUser\My' `
-KeyExportPolicy Exportable `
-KeySpec KeyExchange `
-NotAfter (Get-Date).AddDays(365)
# Export private key via CNG — works on Windows PowerShell 5.1 / .NET Framework
$rsaKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$keyBytes = $rsaKey.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)
$keyPem = "-----BEGIN PRIVATE KEY-----`n" +
[Convert]::ToBase64String($keyBytes, 'InsertLineBreaks') +
"`n-----END PRIVATE KEY-----"
[IO.File]::WriteAllText("$certDir\server.key", $keyPem)
$certPem = "-----BEGIN CERTIFICATE-----`n" +
[Convert]::ToBase64String($cert.RawData, 'InsertLineBreaks') +
"`n-----END CERTIFICATE-----"
[IO.File]::WriteAllText("$certDir\server.crt", $certPem)
Remove-Item "Cert:\CurrentUser\My\$($cert.Thumbprint)" -ErrorAction SilentlyContinue
Write-Host ' -> TLS certificate generated.'
exit 0
} catch {
Write-Host " -> WARNING: Certificate generation failed: $($_.Exception.Message)"
Write-Host ' Server will start in HTTP-only mode (mobile camera will not work).'
exit 1
}