JWT.psm1
function New-Jwt { <# .SYNOPSIS Creates a JWT (JSON Web Token). .DESCRIPTION Creates signed JWT given a signing certificate and claims in JSON. .PARAMETER Payload Specifies the claim to sign in JSON. Mandatory. .PARAMETER Cert Specifies the signing certificate. Mandatory. .PARAMETER Header Specifies a JWT header. Optional. Defaults to '{"alg":"RS256","typ":"JWT"}'. .INPUTS You can pipe a string object to New-Jwt. .OUTPUTS System.String. New-Jwt returns a string with the signed JWT. .EXAMPLE PS Variable:\> $cert = (Get-ChildItem Cert:\CurrentUser\My)[1] PS Variable:\> New-Jwt -Cert $cert -PayloadJson '{"token1":"value1","token2":"value2"}' eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbjEiOiJ2YWx1ZTEiLCJ0b2tlbjIiOiJ2YWx1ZTIifQ.Kd12ryF7Uuk9Y1UWsqdSk6cXNoYZBf9GBoqcEz7R5e4ve1Kyo0WmSr-q4XEjabcbaG0hHJyNGhLDMq6BaIm-hu8ehKgDkvLXPCh15j9AzabQB4vuvSXSWV3MQO7v4Ysm7_sGJQjrmpiwRoufFePcurc94anLNk0GNkTWwG59wY4rHaaHnMXx192KnJojwMR8mK-0_Q6TJ3bK8lTrQqqavnCW9vrKoWoXkqZD_4Qhv2T6vZF7sPkUrgsytgY21xABQuyFrrNLOI1g-EdBa7n1vIyeopM4n6_Uk-ttZp-U9wpi1cgg2pRIWYV5ZT0AwZwy0QyPPx8zjh7EVRpgAKXDAg .EXAMPLE $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $cert.import("c:\ps\jwt.pfx","jwt","Exportable,PersistKeySet") $now = (Get-Date).ToUniversalTime() $createDate = [Math]::Floor([decimal](Get-Date($now) -UFormat "%s")) $expiryDate = [Math]::Floor([decimal](Get-Date($now.AddHours(1)) -UFormat "%s")) $rawclaims = [Ordered]@{ iss = "examplecom:apikey:uaqCinPt2Enb" iat = $createDate exp = $expiryDate } | ConvertTo-Json $jwt = New-Jwt -PayloadJson $rawclaims -Cert $cert $apiendpoint = "https://api.example.com/api/1.0/systems" $splat = @{ Method="GET" Uri=$apiendpoint ContentType="application/json" Headers = @{authorization="bearer $jwt"} } Invoke-WebRequest @splat .LINK https://github.com/SP3269/posh-jwt .LINK https://jwt.io/ #> [CmdletBinding()] param ( [Parameter(Mandatory=$false)][string]$Header = '{"alg":"RS256","typ":"JWT"}', [Parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$PayloadJson, [Parameter(Mandatory=$true)][System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert ) Write-Verbose "Payload to sign: $PayloadJson" Write-Verbose "Signing certificate: $($Cert.Subject)" try { ConvertFrom-Json -InputObject $payloadJson -ErrorAction Stop | Out-Null } # Validating that the parameter is actually JSON - if not, generate breaking error catch { throw "The supplied JWT payload is not JSON: $payloadJson" } $encodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Header)) -replace '\+','-' -replace '/','_' -replace '=' $encodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($PayloadJson)) -replace '\+','-' -replace '/','_' -replace '=' $jwt = $encodedHeader + '.' + $encodedPayload # The first part of the JWT $toSign = [System.Text.Encoding]::UTF8.GetBytes($jwt) $rsa = $Cert.PrivateKey if ($null -eq $rsa) { # Requiring the private key to be present; else cannot sign! throw "There's no private key in the supplied certificate - cannot sign" } else { $sig = [Convert]::ToBase64String($rsa.SignData($toSign,"SHA256")) -replace '\+','-' -replace '/','_' -replace '=' } $jwt = $jwt + '.' + $sig return $jwt } function Test-Jwt { <# .SYNOPSIS Tests cryptographic integrity of a JWT (JSON Web Token). .DESCRIPTION Verifies a digital signature of a JWT given a signing certificate. Assumes SHA-256 hashing algorithm. .PARAMETER Payload Specifies the JWT. Mandatory string. .PARAMETER Cert Specifies the signing certificate. Mandatory X509Certificate2. .INPUTS You can pipe JWT as a string object to Test-Jwt. .OUTPUTS Boolean. Test-Jwt returns $true if the signature successfully verifies. .EXAMPLE PS Variable:> $jwt | Test-Jwt -cert $cert -Verbose VERBOSE: Verifying JWT: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbjEiOiJ2YWx1ZTEiLCJ0b2tlbjIiOiJ2YWx1ZTIifQ.Kd12ryF7Uuk9Y1UWsqdSk6cXNoYZBf9GBoqcEz7R5e4ve1Kyo0WmSr-q4XEjabcbaG0hHJyNGhLDMq6BaIm-hu8ehKgDkvLXP Ch15j9AzabQB4vuvSXSWV3MQO7v4Ysm7_sGJQjrmpiwRoufFePcurc94anLNk0GNkTWwG59wY4rHaaHnMXx192KnJojwMR8mK-0_Q6TJ3bK8lTrQqqavnCW9vrKoWoXkqZD_4Qhv2T6vZF7sPkUrgsytgY21xABQuyFrrNLOI1g-EdBa7n1vIyeopM4n6_Uk-ttZp-U9wpi1cgg2p RIWYV5ZT0AwZwy0QyPPx8zjh7EVRpgAKXDAg VERBOSE: Using certificate with subject: CN=jwt_signing_test True .LINK https://github.com/SP3269/posh-jwt .LINK https://jwt.io/ #> [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$jwt, [Parameter(Mandatory=$true)][System.Security.Cryptography.X509Certificates.X509Certificate2]$cert ) Write-Verbose "Verifying JWT: $jwt" Write-Verbose "Using certificate with subject: $($Cert.Subject)" $parts = $jwt.Split('.') $SHA256 = New-Object Security.Cryptography.SHA256Managed $computed = $SHA256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($parts[0]+"."+$parts[1])) # Computing SHA-256 hash of the JWT parts 1 and 2 - header and payload $signed = $parts[2].replace('-','+').replace('_','/') # Decoding Base64url to the original byte array $mod = $signed.Length % 4 switch ($mod) { 0 { $signed = $signed } 1 { $signed = $signed.Substring(0,$signedToDecode.Length-1) } 2 { $signed = $signed + "==" } 3 { $signed = $signed + "=" } } $bytes = [Convert]::FromBase64String($signed) # Conversion completed return $cert.PublicKey.Key.VerifyHash($computed,"SHA256",$bytes) # Returns True if the hash verifies successfully } |