Private/ConvertFrom-WindowsDeviceLinkAssociationJwtBytes.ps1

function ConvertFrom-WindowsDeviceLinkAssociationJwtBytes {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][byte[]]$Bytes,
        [string]$ExpectedLinkId
    )

    function ConvertFrom-Base64UrlString {
        param([Parameter(Mandatory)][string]$Value)
        $s = $Value.Replace('-', '+').Replace('_', '/')
        switch ($s.Length % 4) {
            2 { $s += '==' }
            3 { $s += '=' }
        }
        [Convert]::FromBase64String($s)
    }

    function Try-GetUtf8JwtText {
        param([byte[]]$InputBytes)
        try {
            $text = [Text.Encoding]::UTF8.GetString($InputBytes).Trim([char]0).Trim()
            if ($text -match '^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*$') { return $text }
        }
        catch {}
        $null
    }

    $jwtText = Try-GetUtf8JwtText -InputBytes $Bytes
    $encoding = 'Utf8'

    if (-not $jwtText -and $Bytes.Length -ge 2 -and $Bytes[0] -eq 0x1f -and $Bytes[1] -eq 0x8b) {
        try {
            $inputStream = New-Object IO.MemoryStream(,$Bytes)
            $gzip = New-Object IO.Compression.GZipStream($inputStream, [IO.Compression.CompressionMode]::Decompress)
            $outputStream = New-Object IO.MemoryStream
            $gzip.CopyTo($outputStream)
            $gzip.Dispose(); $inputStream.Dispose()
            $jwtText = Try-GetUtf8JwtText -InputBytes $outputStream.ToArray()
            $outputStream.Dispose()
            $encoding = 'GZip'
        }
        catch {}
    }

    if (-not $jwtText) {
        try {
            $inputStream = New-Object IO.MemoryStream(,$Bytes)
            $deflate = New-Object IO.Compression.DeflateStream($inputStream, [IO.Compression.CompressionMode]::Decompress)
            $outputStream = New-Object IO.MemoryStream
            $deflate.CopyTo($outputStream)
            $deflate.Dispose(); $inputStream.Dispose()
            $jwtText = Try-GetUtf8JwtText -InputBytes $outputStream.ToArray()
            $outputStream.Dispose()
            if ($jwtText) { $encoding = 'Deflate' }
        }
        catch {}
    }

    if (-not $jwtText) {
        return [pscustomobject]@{
            State='Malformed'; Encoding='Unknown'; Algorithm=$null; IssuedAtUtc=$null; NotBeforeUtc=$null; ExpiresUtc=$null
            IdentityClaimPresent=$false; IdentityMatch=$null; SignatureValidation='NotPerformed'; Reason='The firmware value could not be decoded as a supported JWT encoding.'
        }
    }

    try {
        $parts = $jwtText.Split('.')
        if ($parts.Count -ne 3) { throw 'Invalid segment count.' }
        $headerJson = [Text.Encoding]::UTF8.GetString((ConvertFrom-Base64UrlString $parts[0]))
        $payloadJson = [Text.Encoding]::UTF8.GetString((ConvertFrom-Base64UrlString $parts[1]))
        $header = $headerJson | ConvertFrom-Json -ErrorAction Stop
        $payload = $payloadJson | ConvertFrom-Json -ErrorAction Stop
    }
    catch {
        return [pscustomobject]@{
            State='Malformed'; Encoding=$encoding; Algorithm=$null; IssuedAtUtc=$null; NotBeforeUtc=$null; ExpiresUtc=$null
            IdentityClaimPresent=$false; IdentityMatch=$null; SignatureValidation='NotPerformed'; Reason='The decoded value is not a valid parseable JWT structure.'
        }
    }

    $epoch = [DateTimeOffset]::FromUnixTimeSeconds(0)
    function ConvertFrom-EpochClaim {
        param($Value)
        if ($null -eq $Value) { return $null }
        try { return $epoch.AddSeconds([int64]$Value).UtcDateTime } catch { return $null }
    }

    $iat = ConvertFrom-EpochClaim $payload.iat
    $nbf = ConvertFrom-EpochClaim $payload.nbf
    $exp = ConvertFrom-EpochClaim $payload.exp
    $now = [DateTime]::UtcNow

    $identityClaimPresent = $false
    $identityMatch = $null
    foreach ($claimName in @('linkId','deviceLinkId','device_link_id')) {
        if ($payload.PSObject.Properties.Name -contains $claimName) {
            $identityClaimPresent = $true
            if (-not [string]::IsNullOrWhiteSpace($ExpectedLinkId)) {
                $identityMatch = ([string]$payload.$claimName).Trim('{}') -ieq ([string]$ExpectedLinkId).Trim('{}')
            }
            break
        }
    }

    $state = 'Valid'
    $reason = 'JWT structure and observed temporal claims are valid. Cryptographic signature validation was not performed.'
    if ($nbf -and $nbf -gt $now) { $state='NotYetValid'; $reason='The JWT not-before time is in the future.' }
    elseif ($exp -and $exp -le $now) { $state='Expired'; $reason='The JWT expiration time has passed.' }
    elseif ($identityClaimPresent -and $identityMatch -eq $false) { $state='IdentityMismatch'; $reason='An identity claim was present but did not match the observed local LinkId.' }

    [pscustomobject]@{
        State=$state
        Encoding=$encoding
        Algorithm=if ($header.alg) { [string]$header.alg } else { $null }
        IssuedAtUtc=$iat
        NotBeforeUtc=$nbf
        ExpiresUtc=$exp
        IdentityClaimPresent=$identityClaimPresent
        IdentityMatch=$identityMatch
        SignatureValidation='NotPerformed'
        Reason=$reason
    }
}