private/functions/Get-AxisCertificateKeyType.ps1

function Get-AxisCertificateKeyType {
    <#
    .SYNOPSIS
    Picks the key type and keystore to use when generating a certificate key on an Axis device.

    .DESCRIPTION
    Reads the keystores the device offers (GET /config/rest/cert/v1/keystores, AXIS OS 11.6+) and selects
    the most secure keystore first (security_level STRONG e.g. TPM0/SE0 > TRUSTED_ENVIRONMENT e.g. TEE >
    SOFTWARE e.g. SOFTWARE0) and then the strongest key type that keystore supports:
    NIST-P384 > NIST-P256 > RSA-4096 > RSA-3072 > RSA-2048.
    The device documentation uses both the "NIST-P256" (examples) and "EC-P256" (data type enum) spelling;
    both are accepted and the spelling advertised by the keystore is what is sent back to the device.
    Explicit -KeyType / -Keystore values are honoured (validated against the keystore list when possible).
    On firmware without the JSON API (SOAP only, < 11.6) the device chooses the key itself; RSA-2048 with no
    keystore is returned so callers can still report what will be created.
    Source: https://developer.axis.com/vapix/device-configuration/certificate-management/ (keystores, key_types)

    .PARAMETER Device
    The IP address or hostname of the Axis device.

    .PARAMETER KeyType
    Auto (default) or an explicit key type (NIST-P256, EC-P384, RSA-2048, ...).

    .PARAMETER Keystore
    Auto (default) or an explicit keystore id (TPM0, SE0, TEE, SOFTWARE0, ...).

    .PARAMETER AllowedKeyTypes
    Optional whitelist. Useful when the CA template only accepts e.g. RSA keys: -AllowedKeyTypes RSA-4096,RSA-2048
    #>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory)]
        [String]$Device,

        [Parameter()]
        [String]$KeyType="Auto",

        [Parameter()]
        [String]$Keystore="Auto",

        [Parameter()]
        [String[]]$AllowedKeyTypes
    )

    #Canonical spelling used for comparison (device may say EC-P256 or NIST-P256)
    $Normalize = {
        Param($Name)
        return ([String]$Name).ToUpper().Replace("EC-P","NIST-P").Replace("ECC-P","NIST-P")
    }

    $KeyPreference = @("NIST-P384","NIST-P256","RSA-4096","RSA-3072","RSA-2048")
    $LevelRank = @{ STRONG = 0; TRUSTED_ENVIRONMENT = 1; SOFTWARE = 2 }

    if($AllowedKeyTypes) {
        $Allowed = @($AllowedKeyTypes | ForEach-Object { & $Normalize $_ })
    }
    else {
        $Allowed = $KeyPreference
    }

    #Fetch keystores; SOAP-only firmware cannot choose keys at all
    $Keystores = $null
    Try {
        $Keystores = @(Invoke-AxisCertApi -Device $Device -Path "/keystores")
    }
    Catch {
        if($_.Exception.Message -like "*Command not supported*") {
            Write-Verbose "Device has no certificate REST API; key type is chosen by the device (SOAP CreateCertificate2)."
            $Result = "RSA-2048"
            if($KeyType -ne "Auto") { $Result = $KeyType }
            return [pscustomobject]@{
                KeyType = $Result
                Keystore = $null
                SecurityLevel = $null
                Source = "SOAP"
            }
        }
        Throw "Unable to read keystores from $($Device): $_"
    }

    #Order keystores by security level, then by id so the result is stable
    $Ordered = @($Keystores | Sort-Object -Property @{Expression={ $Rank = $LevelRank[[String]$_.security_level]; if($null -eq $Rank) { 9 } else { $Rank } }}, @{Expression={ $_.id }})

    if($Keystore -ne "Auto") {
        $Candidates = @($Ordered | Where-Object { $_.id -eq $Keystore })
        if($Candidates.Count -eq 0) {
            if($KeyType -eq "Auto") {
                Throw "Keystore '$Keystore' was not reported by $Device. Available: $(($Keystores | ForEach-Object { $_.id }) -join ', ')"
            }
            #Both explicit: trust the caller
            Write-Warning "Keystore '$Keystore' was not reported by $Device; using the explicit values without validation."
            return [pscustomobject]@{ KeyType = $KeyType; Keystore = $Keystore; SecurityLevel = $null; Source = "REST" }
        }
    }
    else {
        $Candidates = $Ordered
    }

    ForEach($Store in $Candidates) {
        $Supported = @($Store.key_types)
        if($KeyType -ne "Auto") {
            $Match = $Supported | Where-Object { (& $Normalize $_) -eq (& $Normalize $KeyType) } | Select-Object -First 1
            if($Match) {
                return [pscustomobject]@{ KeyType = [String]$Match; Keystore = [String]$Store.id; SecurityLevel = [String]$Store.security_level; Source = "REST" }
            }
            if($Supported.Count -eq 0) {
                #Keystore does not advertise key types; send what was asked for
                return [pscustomobject]@{ KeyType = $KeyType; Keystore = [String]$Store.id; SecurityLevel = [String]$Store.security_level; Source = "REST" }
            }
            continue
        }

        ForEach($Preferred in $KeyPreference) {
            if($Allowed -notcontains $Preferred) { continue }
            $Match = $Supported | Where-Object { (& $Normalize $_) -eq $Preferred } | Select-Object -First 1
            if($Match) {
                Write-Verbose "Selected key type $Match in keystore $($Store.id) ($($Store.security_level))"
                return [pscustomobject]@{ KeyType = [String]$Match; Keystore = [String]$Store.id; SecurityLevel = [String]$Store.security_level; Source = "REST" }
            }
        }
    }

    if($KeyType -ne "Auto") {
        Throw "Key type '$KeyType' is not supported by the selected keystore(s) on $Device"
    }
    Throw "No keystore on $Device supports any of the allowed key types ($($Allowed -join ', '))"
}