Import, Export, Validate & EKU

Expose API endpoints to import a certificate and export it in different formats, with simple validation and EKU reporting.

Prerequisites: see Introduction.

Full source

<#
    Run a small API for importing and exporting certificates.
    - POST /certs/import { filePath, format, password? }
    - POST /certs/export { filePath, format, outPath, includePrivateKey?, password? }
    FileName: 6.3-Cert-Import-Export.ps1
#>
param(
    [int]$Port = $env:PORT ?? 5000
)

<#
.SYNOPSIS
    Converts a plain text password to a SecureString or returns $null if the input is null or empty.
.DESCRIPTION
    This function takes a plain text password as input and converts it to a SecureString.
    If the input is null or empty, it returns $null.
.PARAMETER Password
    The plain text password to convert.
.EXAMPLE
    $securePassword = Convert-ToSecureStringOrNull -Password "myP@ssword"
    Converts the plain text password "myP@ssword" to a SecureString.
#>
function Convert-ToSecureStringOrNull {
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
    param([string]$Password)
    if ([string]::IsNullOrWhiteSpace($Password)) { return $null }
    return (ConvertTo-SecureString $Password -AsPlainText -Force)
}


Initialize-KrRoot -Path $PSScriptRoot

New-KrLogger |
    Set-KrLoggerLevel -Value Debug |
    Add-KrSinkConsole |
    Register-KrLogger -Name 'myLogger' -SetAsDefault

New-KrServer -Name "Cert Ops API"
Add-KrEndpoint -Port $Port

Enable-KrConfiguration

# POST /certs/import
# Body JSON example: { "filePath": "./devcert.pfx", "password": "p@ss" }
Add-KrMapRoute -Verbs Post -Pattern "/certs/import" -ScriptBlock {
    $body = Get-KrRequestBody
    try {
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Importing certificate from {filePath}" -Values $body.filePath
        $password = Convert-ToSecureStringOrNull -Password ([string]$body.password)
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Password provided: {hasPassword}" -Values ($null -ne $password)
        $cert = Import-KrCertificate -FilePath ([string]$body.filePath) -Password $password
        if ( $null -eq $cert) {
            throw "Certificate import failed."
        } else {
            Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Certificate imported successfully"
        }
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Certificate imported successfully: {subject}" -Values $cert.Subject
        $purposes = Get-KrCertificatePurpose -Certificate $cert
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Certificate purposes: {purposes}" -Values $purposes
        $valid = Test-KrCertificate -Certificate $cert -DenySelfSigned:$false -AllowWeakAlgorithms:$false
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Certificate validity: {valid}" -Values $valid
        Write-KrJsonResponse -StatusCode 200 -InputObject @{ subject = $cert.Subject; notAfter = $cert.NotAfter; valid = $valid; purposes = $purposes }
    } catch {
        Write-KrLog -Level Error -LoggerName 'myLogger' -Message "Certificate import failed" -ErrorRecord $_
        Write-KrJsonResponse -StatusCode 400 -InputObject @{ error = $_.Exception.Message }
    }
}

# POST /certs/export
# Body JSON example: { "filePath": "./devcert.pfx", "format": "Pfx", "outPath": "./devcert", "includePrivateKey": true, "password": "p@ss" }
Add-KrMapRoute -Verbs Post -Pattern "/certs/export" -ScriptBlock {
    $body = Get-KrRequestBody
    try {
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Exporting certificate from {filePath} with format {format} to {outPath}" -Values $body.filePath, $body.format, $body.outPath
        if (-not $body.outPath) {
            throw "Output path (outPath) is required for export."
        }
        $password = Convert-ToSecureStringOrNull -Password ([string]$body.password)
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Password provided: {hasPassword}" -Values ($null -ne $password)
        $cert = New-KrSelfSignedCertificate -DnsNames "temp.example.com" -Exportable -ValidDays 1
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Temporary self-signed certificate created: {subject}" -Values $cert.Subject
        $params = @{ Certificate = $cert; FilePath = ([string]$body.outPath); Format = ([string]$body.outFormat) }
        if ($body.includePrivateKey) { $params.IncludePrivateKey = [bool]$body.includePrivateKey }
        if ($password) { $params.Password = $password }
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Exporting certificate to {outPath} with format {format}" -Values $body.outPath, $body.outFormat

        Export-KrCertificate @params | Out-Null
        Write-KrLog -Level Debug -LoggerName 'myLogger' -Message "Certificate exported successfully to {outPath} with format {format}" -Values $body.outPath, $body.outFormat
        Write-KrJsonResponse -StatusCode 200 -InputObject @{ exported = $true; path = $body.outPath; format = $body.outFormat }
    } catch {
        Write-KrLog -Level Error -LoggerName 'myLogger' -Message "Certificate export failed" -ErrorRecord $_
        Write-KrJsonResponse -StatusCode 400 -InputObject @{ error = $_.Exception.Message }
    }
}

Start-KrServer -CloseLogsOnExit
# Clean up and close all the loggers when the server stops

Step-by-step

  1. Initialize: Initialize-KrRoot -Path $PSScriptRoot.
  2. Logging: a console logger named myLogger is registered at Debug to trace operations.
  3. Server and listener: - New-KrServer -Name 'Cert Ops API' - Add-KrEndpoint -Port 5000 -IPAddress Loopback - Enable-KrConfiguration
  4. Map routes: - POST /certs/import reads JSON { filePath, password? } and returns subject, notAfter, valid (from Test-KrCertificate), and purposes (from Get-KrCertificatePurpose). Format is auto-detected by Import-KrCertificate. - POST /certs/export reads JSON { outPath, outFormat, includePrivateKey?, password? }, creates a temporary self‑signed certificate, and exports it to the requested format/path; returns { exported, path, format }.
  5. Supported formats: - Import: auto‑detects Pfx, Pem, or Der based on file content/extension. - Export: outFormat accepts Pfx or Pem (PEM may split key/cert; PFX can bundle with password).
  6. Errors: - Import: returns HTTP 200 with { error: '...' } on failure (demo behavior in sample). - Export: returns HTTP 400 with { error: '...' } on failure.
  7. Start server: Start-KrServer.

For issuer-signed development certificates, Test-KrCertificate can also:

  • capture the failure text with -FailureReasonVariable
  • accept a private root or intermediate certificate with -CertificateChain

Try it

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

# From your working folder
pwsh .\cert-ops.ps1

# Export a temporary self-signed cert to PFX (include private key)
curl -X POST http://127.0.0.1:5000/certs/export -H "Content-Type: application/json" `
         -d '{"outPath":"./exported","outFormat":"Pfx","includePrivateKey":true,"password":"p@ss"}'

# Import a PFX
curl -X POST http://127.0.0.1:5000/certs/import -H "Content-Type: application/json" `
        -d '{"filePath":"./exported.pfx","password":"p@ss"}'

PowerShell alternatives:

# Export a temporary self-signed cert to PFX (include private key)
Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:5000/certs/export' -ContentType 'application/json' -Body (@{
    outPath           = './exported'
    outFormat         = 'Pfx'
    includePrivateKey = $true
    password          = 'p@ss'
} | ConvertTo-Json)

# Import a PFX
Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:5000/certs/import' -ContentType 'application/json' -Body (@{
    filePath = './exported.pfx'
    password = 'p@ss'
} | ConvertTo-Json)

Validate a development leaf certificate outside the trusted OS store:

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

Troubleshooting

Symptom Cause Fix
Import returns error Wrong path/password or unsupported file Verify file exists; pass PFX password; use supported formats
Export file not created Permission issues or bad path Choose a writable path; avoid restricted directories
Purposes empty Certificate lacks EKU Generate/choose a cert with desired EKUs (e.g., ServerAuth)
200 with error payload Sample returns 200 on import failure Treat presence of error as failure in your client logic
Validation returns PartialChain Issuing root is not trusted locally Pass the issuing certificate with -CertificateChain or trust the root certificate

References


Previous / Next

Previous: Generate a CSR Next: Endpoints