Functions/New-JsonWebToken.ps1
function New-JsonWebToken { <# .SYNOPSIS Generates a JSON Web Token. .DESCRIPTION Generates a signed JSON Web Token (JWS) with options for specifying a JWK URI in the header. .PARAMETER Claims The claims for the token expressed as a hash table. .PARAMETER SigningCertificate The X.509 certificate containing the private key that will sign the JSON Web Token. .PARAMETER TimeToLive The lifetime of the token expressed in seconds. .PARAMETER HashAlgorithm The hash algorthim for the signature. Acceptable values are SHA256, SHA384, and SHA512. Default value is SHA256. .PARAMETER JwkUri The URI for JSON Web Keys to verify the signature of the generated token. This is expressed as the attribute 'jku' in the header. .PARAMETER IncludeJwk Tells this function to include the public key of the X.509 certificate as a JSON Web Key in the header as the 'jwk' attribute. .PARAMETER X509Uri The URI for X.509 public key certificate or certificate chain to verify the signature of the generated token. This is expressed as the attribute 'x5u' in the header. .PARAMETER IncludeX509CertChain Tells the function to include the X.509 certificate chain as a collection of base64 encoded public keys in the header as the 'x5c' attribute. .PARAMETER NotBeforeSkew The amount of time in seconds that the not before claim (nbf) will be skewed in the past. This is useful in cases where identity providers and relying parties are not sharing the same source for time synchronization. .PARAMETER AddJtiClaim Adds a unique identifier for the token in the form of a jti claim to the payload. .PARAMETER Key This is the secret key used to generate an HMAC signature expressed as a string. .PARAMETER SecureKey This is the secret key used to generate an HMAC signature expressed as a System.Security.SecureString. .PARAMETER ExcludeDefaultClaims Excludes the nbf, iat, and exp default claims from the payload when using the HMAC parameter set. .PARAMETER NoSignature Tells this function to create an unsigned JWT. This is meant for security testing only and should never be used when a valid JWT is required! .PARAMETER CustomKeyIdentifier Tells this function to either add or override (in the case of the RSA parameter set) the kid attribute in the JWT header with the specified value. .EXAMPLE $claims = @{sub="$env:USERDOMAIN\$env:USERNAME"; iss="$env:COMPUTERNAME@$env:USERDNSDOMAIN"} $signingCertThumbprint = "706428667193645C3B4704FC824BEDFCEBB4F038" $certPath = Join-Path -Path Cert:\LocalMachine\My -ChildPath $signingCertThumbprint $signingCert = Get-Item -Path $certPath New-JsonWebToken -Claims $claims -SigningCertificate $signingCert -TimeToLive 300 -HashAlgorithm SHA256 Generates a signed JSON Web Token using the certificate found in the local machine store with a thumbprint of 6C85DF2F915D0E28B719AEC188367092A0FD0CD2. .EXAMPLE $hmacJwt = New-JsonWebToken -Claims @{sub="tonyg"} -HashAlgorithm SHA256 -Key "secretkey" -TimeToLive 300 Generates an HMAC-SHA256 signed JWT. .EXAMPLE $secureStringKey = "secretKey" | ConvertTo-SecureString -AsPlainText -Force $hmacJwt = New-JsonWebToken -Claims @{sub="tonyg"} -HashAlgorithm SHA256 -SecureKey $secureStringKey -TimeToLive 300 Generates an HMAC-SHA256 signed JWT with the HMAC key passed as a SecureString. .EXAMPLE New-JsonWebToken -Claims @{sub="tonyg"} -NoSignature Creates an unsigned JWT for testing purposes. .OUTPUTS System.String The JSON Web Token is returned as a base64 URL encoded string. .LINK https://tools.ietf.org/html/rfc7519 https://tools.ietf.org/html/rfc7515 New-JsonWebKeySet Test-JsonWebToken ConvertFrom-EncodedJsonWebToken ConvertTo-SecureString #> [CmdletBinding()] [Alias('njwt', 'NewJwt', 'CreateJwt')] [OutputType([System.String])] Param ( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [System.Collections.Hashtable]$Claims, [Parameter(Mandatory = $false, Position = 1)] [ValidateSet("SHA256", "SHA384", "SHA512")] [String]$HashAlgorithm = "SHA256", [Parameter(Mandatory = $false, Position = 2)] [ValidateRange(1, 300)] [System.Int32]$NotBeforeSkew, [Parameter(Mandatory = $false, Position = 3)] [Switch]$AddJtiClaim, [Parameter(Mandatory = $true, ParameterSetName = "RSA", Position = 4)][Alias("Certificate", "Cert")] [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningCertificate, [Parameter(Mandatory = $false, Position = 5)] [ValidateRange(1, 2147483647)] [System.Int32]$TimeToLive = 300, [Parameter(Mandatory = $false, ParameterSetName = "RSA", Position = 6)] [System.Uri]$JwkUri, [Parameter(Mandatory = $false, ParameterSetName = "RSA", Position = 7)] [Switch]$IncludeJwk, [Parameter(Mandatory = $false, ParameterSetName = "RSA", Position = 8)] [System.Uri]$X509Uri, [Parameter(Mandatory = $false, ParameterSetName = "RSA", Position = 8)] [Switch]$IncludeX509CertChain, [Parameter(Mandatory = $true, ParameterSetName = "HMAC", Position = 10)] [ValidateLength(1, 32768)] [String]$Key, [Parameter(Mandatory = $true, ParameterSetName = "HMACSecure", Position = 11)] [ValidateNotNullOrEmpty()] [System.Security.SecureString]$SecureKey, [Parameter(Mandatory = $false, Position = 12)][Alias('ExcludeDefaultDateClaims')][Switch]$ExcludeDefaultClaims, [Parameter(Mandatory = $false, ParameterSetName = "Unsigned", Position = 13)][Switch]$NoSignature, [Parameter(Mandatory = $false, Position = 14)][ValidateLength(0, 32768)][String]$CustomKeyIdentifier ) PROCESS { [string]$jwt = "" # Construct payload for HMAC or RSA: [string]$payload = "" [HashTable]$_claims = $Claims if ($PSBoundParameters.ContainsKey("AddJtiClaim")) { if ($_claims.ContainsKey("jti")) { $_claims.Remove("jti") | Out-Null } $_claims.Add("jti", (New-Guid).Guid) } if ($PSBoundParameters.ContainsKey("ExcludeDefaultClaims")) { $payload = $_claims | ConvertTo-JwtPart } else { if ($PSBoundParameters.ContainsKey("NotBeforeSkew")) { $payload = New-JwtPayloadString -Claims $_claims -NotBeforeSkew $NotBeforeSkew } else { $payload = New-JwtPayloadString -Claims $_claims } } # Short circuit if NoSignature is called: if ($PSBoundParameters.ContainsKey("NoSignature")) { $headerTable = @{} if ($PSBoundParameters.ContainsKey("CustomKeyIdentifier")) { $headerTable = [ordered]@{typ = "JWT"; alg = "none"; kid = $CustomKeyIdentifier } } else { $headerTable = [ordered]@{typ = "JWT"; alg = "none" } } $header = $headerTable | ConvertTo-JwtPart $jwtSansSig = "{0}.{1}." -f $header, $payload return $jwtSansSig } if ($PSCmdlet.ParameterSetName -eq "RSA") { if ($null -eq $SigningCertificate.PrivateKey.KeyExchangeAlgorithm) { $cryptographicExceptionMessage = "Private key either not found or inaccessible for certificate with thumbprint: {0} " -f $SigningCertificate.Thumbprint $CryptographicException = New-Object -TypeName System.Security.Cryptography.CryptographicException -ArgumentList $cryptographicExceptionMessage Write-Error -Exception $CryptographicException -Category SecurityError -ErrorAction Stop } #1. Construct header: [string]$rsaAlg = "" switch ($HashAlgorithm) { "SHA256" { $rsaAlg = "RS256" } "SHA384" { $rsaAlg = "RS384" } "SHA512" { $rsaAlg = "RS512" } default { $rsaAlg = "RS256" } } # If CustomKeyIdentifier is called, exclude x5t from the header: $headerTable = @{} if ($PSBoundParameters.ContainsKey("CustomKeyIdentifier")) { $headerTable = [ordered]@{typ = "JWT"; alg = $rsaAlg; kid = $CustomKeyIdentifier } } else { $encodedThumbprint = Get-JwtKeyIdentifier -Certificate $SigningCertificate $headerTable = [ordered]@{typ = "JWT"; alg = $rsaAlg; x5t = $encodedThumbprint; kid = $encodedThumbprint } } if ($PSBoundParameters.ContainsKey("JwkUri")) { $headerTable.Add("jku", $JwkUri) } if ($PSBoundParameters.ContainsKey("IncludeJwk")) { $jwk = New-JsonWebKey -Certificate $SigningCertificate $headerTable.Add("jwk", $jwk) } if ($PSBoundParameters.ContainsKey("X509Uri")) { $headerTable.Add("x5u", $X509Uri) } if ($PSBoundParameters.ContainsKey("IncludeX509CertChain")) { $chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new() $chain.Build($SigningCertificate) | Out-Null $allCertsInChain = $chain.ChainElements | Select-Object -ExpandProperty Certificate $base64certs = @($allCertsInChain | Convert-X509CertificateToBase64 -NoFormat) $headerTable.Add("x5c", $base64certs) } $header = $headerTable | ConvertTo-JwtPart #2. Payload is constructed at beginning of PROCESS block. #3. Concatenate encoded header and payload seperated by a full stop: $jwtSansSig = "{0}.{1}" -f $header, $payload #4. Generate signature for concatenated header and payload: [string]$rsaSig = "" try { $rsaSig = New-JwtSignature -JsonWebToken $jwtSansSig -HashAlgorithm $HashAlgorithm -SigningCertificate $SigningCertificate } catch { Write-Error -Exception $_.Exception -Category InvalidArgument -ErrorAction Stop } #5. Construct jws: $jwt = "{0}.{1}" -f $jwtSansSig, $rsaSig } else { # Parameter set is HMAC of HMACSecure [string]$hmacKey = "" if ($PSCmdlet.ParameterSetName -eq "HMACSecure") { $networkCredential = [System.Net.NetworkCredential]::new("", $SecureKey) $hmacKey = $networkCredential.Password } else { $hmacKey = $Key } $hmacAlg = "" switch ($HashAlgorithm) { "SHA256" { $hmacAlg = "HS256" } "SHA384" { $hmacAlg = "HS384" } "SHA512" { $hmacAlg = "HS512" } default { $hmacAlg = "HS256" } } #1. Construct header: $headerTable = @{} if ($PSBoundParameters.ContainsKey("CustomKeyIdentifier")) { $headerTable = [ordered]@{typ = "JWT"; alg = $hmacAlg; kid = $CustomKeyIdentifier } } else { $headerTable = [ordered]@{typ = "JWT"; alg = $hmacAlg } } $header = $headerTable | ConvertTo-JwtPart #2. Payload is constructed at beginning of PROCESS block. #3. Concatenate encoded header and payload seperated by a full stop: $jwtSansSig = "{0}.{1}" -f $header, $payload #4. Generate signature for concatenated header and payload: $hmacSig = New-JwtSignature -JsonWebToken $jwtSansSig -Key $hmacKey -HashAlgorithm $HashAlgorithm #5. Construct jws: $jwt = "{0}.{1}" -f $jwtSansSig, $hmacSig } return $jwt } } |