Create a Self‑Signed Certificate

Generate a development CA root, issue a localhost leaf certificate from it, and bind the leaf to an HTTPS listener.

Prerequisites: see Introduction.

Full source

<#
    Create a development root CA, issue a localhost leaf certificate, optionally trust the root,
    and start an HTTPS listener.
    FileName: 6.1-Cert-SelfSigned.ps1
#>
param(
    [int]$Port = $env:PORT ?? 5000,

    [switch]$TrustRoot
)

# Initialize Kestrun root directory
Initialize-KrRoot -Path $PSScriptRoot

# Configure default logging
New-KrLogger |
    Set-KrLoggerLevel -Value Debug |
    Add-KrSinkConsole |
    Register-KrLogger -Name 'myLogger' -SetAsDefault

$bundle = New-KrSelfSignedCertificate -Development `
    -DnsNames 'localhost', '127.0.0.1', '::1' `
    -Exportable `
    -LeafValidDays 30 `
    -RootValidDays 3650 `
    -TrustRoot:$TrustRoot.IsPresent

$root = $bundle.RootCertificate
$cert = $bundle.LeafCertificate

Write-Host "Development root: $($root.Subject)" -ForegroundColor Cyan
Write-Host "Leaf certificate: $($cert.Subject)" -ForegroundColor Cyan

if ($bundle.RootTrusted) {
    Write-Host 'Development root is present in CurrentUser\\Root.' -ForegroundColor Green
} elseif ($TrustRoot.IsPresent) {
    Write-Host 'TrustRoot was requested but no Windows trust action was performed.' -ForegroundColor Yellow
} else {
    Write-Host 'Root trust skipped. Use -TrustRoot on Windows if you want Chromium-family browsers to trust the root.' -ForegroundColor Yellow
}

# Show EKUs
Get-KrCertificatePurpose -Certificate $cert | Out-Host

# Configure HTTPS listener with the certificate
New-KrServer -Name 'HTTPS Demo'
Add-KrEndpoint -Port $Port -X509Certificate $cert -Protocols Http1

# Minimal route to verify HTTPS works

Enable-KrConfiguration
Add-KrMapRoute -Verbs Get -Pattern '/hello' -ScriptBlock { Write-KrTextResponse 'hello https' }

Start-KrServer

# Clean up and close all the loggers when the server stops
Close-KrLogger

Step-by-step

  1. Initialize root: Initialize-KrRoot -Path $PSScriptRoot so relative paths resolve predictably.
  2. Logging: create and register a default console logger at Debug (helps trace setup and requests).
  3. Create the development bundle with New-KrSelfSignedCertificate -Development:
    • Root: generates a CA root when you do not pass -RootCertificate.
    • Leaf: issues a localhost certificate with DNS:localhost and IP SANs for 127.0.0.1 / ::1.
    • Trust (optional): add -TrustRoot on Windows to place the root in CurrentUser\Root.
  4. Extract the returned certificates:
    • $bundle.RootCertificate is the development root CA.
    • $bundle.LeafCertificate is the localhost server certificate.
  5. Inspect EKU (optional): Get-KrCertificatePurpose -Certificate $bundle.LeafCertificate to verify usages like ServerAuth.
  6. Validate the leaf (optional): use Test-KrCertificate -Certificate $bundle.LeafCertificate -CertificateChain $bundle.RootCertificate when the development root is not trusted by the OS.
  7. Confirm localhost coverage:
    • DNS:localhost
    • IP:127.0.0.1
    • IP:::1
  8. Create server: New-KrServer.
  9. Bind HTTPS listener: Add-KrEndpoint -Port 5000 -IPAddress Loopback -X509Certificate $bundle.LeafCertificate.
    • Optionally keep HTTP on 5000 for plaintext testing.
    • You can restrict protocols, e.g., -Protocols Http1.
  10. Apply config: Enable-KrConfiguration.
  11. Map a minimal route (e.g., GET /hello) returning JSON so you can verify TLS termination.
  12. Start the server: Start-KrServer.
  13. Client notes:
    • For self‑signed certs, use curl -k or Invoke-WebRequest -SkipCertificateCheck during development.
    • For Windows Chromium browsers, prefer trusting the generated development root when possible.

Try it

Save the sample locally so it’s easy to run. Copy the contents of pwsh/tutorial/examples/6.1-Cert-SelfSigned.ps1 into a new file in an empty working folder (for example, cert-https.ps1), then run:

# From your working folder
pwsh .\cert-https.ps1
curl -k https://127.0.0.1:5000/hello
Invoke-WebRequest -SkipCertificateCheck -Uri 'https://127.0.0.1:5000/hello' | Select-Object -ExpandProperty Content

# On Windows, trust the generated root first for browser testing
pwsh .\cert-https.ps1 -TrustRoot

Note: -k/SkipCertificateCheck is used because this is a self‑signed development cert.

Key points

Recommended localhost development bundle:

$bundle = New-KrSelfSignedCertificate -Development -TrustRoot
$cert = $bundle.LeafCertificate

Validate a development leaf when the root is not in the platform trust store:

$valid = Test-KrCertificate `
    -Certificate $bundle.LeafCertificate `
    -CertificateChain $bundle.RootCertificate `
    -FailureReasonVariable 'reason'

If $valid is $false and $reason contains PartialChain, the leaf certificate is usually fine, but the issuing root is not trusted or was not supplied for validation.

Manual CA + leaf flow:

$root = New-KrSelfSignedCertificate `
    -DnsNames 'Kestrun Development Root CA' `
    -CertificateAuthority `
    -Exportable `
    -ValidDays 3650

$cert = New-KrSelfSignedCertificate `
    -DnsNames 'localhost','127.0.0.1','::1' `
    -IssuerCertificate $root `
    -Exportable `
    -ValidDays 30

When to use which parameter:

  • Use -CertificateAuthority when you are creating the root/intermediate CA certificate itself.
  • Use -IssuerCertificate when you are creating a leaf certificate that should be signed by an existing CA.

Reuse across sessions:

$password = ConvertTo-SecureString 'p@ssw0rd!' -AsPlainText -Force
$bundle = New-KrSelfSignedCertificate -Development -Exportable

Export-KrCertificate -Certificate $bundle.RootCertificate -FilePath './certs/dev-root' -Format Pfx -Password $password -IncludePrivateKey
Export-KrCertificate -Certificate $bundle.LeafCertificate -FilePath './certs/localhost' -Format Pfx -Password $password -IncludePrivateKey

$root = Import-KrCertificate -FilePath './certs/dev-root.pfx' -Password $password
$nextBundle = New-KrSelfSignedCertificate -Development -RootCertificate $root -Exportable

For a fuller import/export walkthrough, see Import / Export / Validate.

For ECDSA:

$cert = New-KrSelfSignedCertificate `
    -DnsNames 'localhost','::1' `
    -KeyType Ecdsa `
    -KeyLength 256 `
    -KeyUsage DigitalSignature `
    -Exportable `
    -ValidDays 30

Troubleshooting

Symptom Cause Fix
Browser shows certificate warning Self‑signed not trusted Trust the certificate locally or use curl -k for quick tests
Listener fails to start on 5000 Port in use Pick another port or stop the conflicting process
TLS handshake errors Wrong IP/DNS in certificate Include localhost, 127.0.0.1, and ::1 in -DnsNames
JSON route still on HTTP only Wrong HTTPS port in test URL Verify listener and test URL use https://127.0.0.1:5000
Edge / Brave reject localhost cert Chromium trust/store behavior Prefer a trusted dev CA on Windows instead of a raw self-signed leaf
Issuer-signed leaf creation fails Issuer is not a CA or lacks exportable private key Create/import the issuer as an exportable CA root certificate
Test-KrCertificate shows PartialChain Development root is not trusted or not supplied for validation Pass -CertificateChain $bundle.RootCertificate or trust the root on Windows

References


Previous / Next

Previous: Logging Next: Generate a CSR