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 = 5000,
    [IPAddress]$IPAddress = [IPAddress]::Loopback
)

<#
.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 -IPAddress $IPAddress

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.

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)

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

References


Previous / Next

Go back to Generate a CSR or return to Certificates.