#!/usr/bin/env pwsh [CmdletBinding()] param( [Parameter(Mandatory)] [System.Net.IPAddress]$IPAddress, [string[]]$DnsName = @("localhost") ) $ErrorActionPreference = "Stop" $outputDirectory = Join-Path $PSScriptRoot ".local\cmroubao-tls" $caCertificatePath = Join-Path $outputDirectory "ca.crt" $caPrivateKeyPath = Join-Path $outputDirectory "ca.key" $serverCertificatePath = Join-Path $outputDirectory "server.crt" $serverPrivateKeyPath = Join-Path $outputDirectory "server.key" if ((Test-Path -LiteralPath $serverCertificatePath -PathType Leaf) -or (Test-Path -LiteralPath $serverPrivateKeyPath -PathType Leaf)) { throw "TLS server certificate already exists: $outputDirectory. Rotate it explicitly before regenerating." } if (-not (Test-Path -LiteralPath $outputDirectory)) { New-Item -ItemType Directory -Path $outputDirectory | Out-Null } function Export-PemCertificate { param( [Parameter(Mandatory)] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [Parameter(Mandatory)] [string]$Path ) [System.IO.File]::WriteAllText( $Path, $Certificate.ExportCertificatePem(), [System.Text.UTF8Encoding]::new($false) ) } function Export-PemPrivateKey { param( [Parameter(Mandatory)] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [Parameter(Mandatory)] [string]$Path ) $key = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate) try { [System.IO.File]::WriteAllText( $Path, $key.ExportPkcs8PrivateKeyPem(), [System.Text.UTF8Encoding]::new($false) ) } finally { $key.Dispose() } } $notBefore = [DateTimeOffset]::UtcNow.AddMinutes(-5) $notAfter = $notBefore.AddYears(2) $hashAlgorithm = [System.Security.Cryptography.HashAlgorithmName]::SHA256 $signaturePadding = [System.Security.Cryptography.RSASignaturePadding]::Pkcs1 $caKey = [System.Security.Cryptography.RSA]::Create(4096) try { $caRequest = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new( "CN=cmroubao local development CA", $caKey, $hashAlgorithm, $signaturePadding ) $caRequest.CertificateExtensions.Add( [System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($true, $false, 0, $true) ) $caRequest.CertificateExtensions.Add( [System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new( [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyCertSign -bor [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::CrlSign, $true ) ) $caRequest.CertificateExtensions.Add( [System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension]::new($caRequest.PublicKey, $false) ) $caCertificate = $caRequest.CreateSelfSigned($notBefore, $notAfter) try { Export-PemCertificate -Certificate $caCertificate -Path $caCertificatePath Export-PemPrivateKey -Certificate $caCertificate -Path $caPrivateKeyPath $serverKey = [System.Security.Cryptography.RSA]::Create(2048) try { $serverRequest = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new( "CN=$IPAddress", $serverKey, $hashAlgorithm, $signaturePadding ) $serverRequest.CertificateExtensions.Add( [System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($false, $false, 0, $false) ) $serverRequest.CertificateExtensions.Add( [System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new( [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::DigitalSignature -bor [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyEncipherment, $true ) ) $serverAuth = [System.Security.Cryptography.OidCollection]::new() $serverAuth.Add([System.Security.Cryptography.Oid]::new("1.3.6.1.5.5.7.3.1")) | Out-Null $serverRequest.CertificateExtensions.Add( [System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($serverAuth, $false) ) $subjectAlternativeNames = [System.Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder]::new() $subjectAlternativeNames.AddIpAddress($IPAddress) foreach ($name in $DnsName) { if (-not [string]::IsNullOrWhiteSpace($name)) { $subjectAlternativeNames.AddDnsName($name.Trim()) } } $serverRequest.CertificateExtensions.Add($subjectAlternativeNames.Build()) $serverRequest.CertificateExtensions.Add( [System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension]::new($serverRequest.PublicKey, $false) ) $serial = New-Object byte[] 16 [System.Security.Cryptography.RandomNumberGenerator]::Fill($serial) $serverCertificate = $serverRequest.Create($caCertificate, $notBefore, $notAfter, $serial) $serverCertificateWithKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey( $serverCertificate, $serverKey ) try { Export-PemCertificate -Certificate $serverCertificateWithKey -Path $serverCertificatePath Export-PemPrivateKey -Certificate $serverCertificateWithKey -Path $serverPrivateKeyPath } finally { $serverCertificateWithKey.Dispose() $serverCertificate.Dispose() } } finally { $serverKey.Dispose() } } finally { $caCertificate.Dispose() } } finally { $caKey.Dispose() } Write-Host "Created local CA certificate: $caCertificatePath" Write-Host "Created TLS server certificate: $serverCertificatePath" Write-Host "Server SAN IP: $IPAddress" Write-Host "Install only ca.crt on the Debug phone. Never copy ca.key or server.key to the phone."