Last active
June 14, 2022 07:08
-
-
Save Nillth/a50c1f67b9f8cb901c5535e7787891fb to your computer and use it in GitHub Desktop.
Create JWT.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.NOTES | |
=========================================================================== | |
Created on: 2020-11-14 | |
Updated on: 2022-06-14 | |
Created by: Marc Collins | |
Organization: Qlik Professional Services | |
Filename: JWTToken.ps1 | |
=========================================================================== | |
#> | |
function Convert-PemtoX509 | |
{ | |
param | |
( | |
[Parameter(Mandatory = $true)] | |
[System.IO.FileInfo]$PublicKey, | |
[System.IO.FileInfo]$PrivateKey, | |
[System.IO.FileInfo]$OutPFX, | |
[securestring]$PFXPassword, | |
[switch]$NoPassword | |
) | |
BEGIN | |
{ | |
if ($PFXPassword.Length -eq 0 -and $null -ne $OutPFX -and $NoPassword.IsPresent -eq $false) | |
{ | |
$PFXPassword = Read-Host -Prompt 'Enter PFX Password' -AsSecureString | |
} | |
[string]$KEY_HEADER = '-----BEGIN RSA PRIVATE KEY-----' | |
[string]$KEY_FOOTER = '-----END RSA PRIVATE KEY-----'; | |
[System.Security.Cryptography.X509Certificates.X509Certificate2]$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 | |
function Get-RSA | |
{ | |
param | |
( | |
[Parameter(Mandatory = $false)] | |
[string]$PEM | |
) | |
if (Test-IsRSAPrivateKeyAvailable -PEM $PEM) | |
{ | |
[System.Security.Cryptography.RSAParameters]$PrivateKey = Convert-PEMToRSAParameters -PEM $PEM | |
[System.Security.Principal.SecurityIdentifier]$everyoneSI = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::WorldSid, $null) | |
[System.Security.AccessControl.CryptoKeyAccessRule]$rule = New-Object System.Security.AccessControl.CryptoKeyAccessRule($everyoneSI, [System.Security.AccessControl.CryptoKeyRights]::FullControl, [System.Security.AccessControl.AccessControlType]::Allow) | |
[System.Security.Cryptography.CspParameters]$cspParameters = New-Object System.Security.Cryptography.CspParameters; | |
$cspParameters.KeyContainerName = 'MY_C_NAME'; | |
$cspParameters.ProviderName = 'Microsoft Strong Cryptographic Provider'; | |
$cspParameters.ProviderType = 1; | |
$cspParameters.Flags = [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore; | |
$cspParameters.CryptoKeySecurity = New-Object System.Security.AccessControl.CryptoKeySecurity | |
$cspParameters.CryptoKeySecurity.SetAccessRule($rule); | |
[System.Security.Cryptography.RSACryptoServiceProvider]$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider($cspParameters); | |
$rsa.PersistKeyInCsp = $true; | |
$rsa.ImportParameters($privateKey); | |
} | |
return $rsa; | |
} | |
function Convert-PEMToRSAParameters | |
{ | |
param | |
( | |
[Parameter(Mandatory = $true)] | |
[string]$PEM | |
) | |
if (Test-IsRSAPrivateKeyAvailable -PEM $PEM) | |
{ | |
$keyFormatted = $PEM | |
[int]$cutIndex = $keyFormatted.IndexOf($KEY_HEADER); | |
$keyFormatted = $keyFormatted.Substring($cutIndex, $keyFormatted.Length - $cutIndex); | |
$cutIndex = $keyFormatted.IndexOf($KEY_FOOTER); | |
$keyFormatted = $keyFormatted.Substring(0, $cutIndex + $KEY_FOOTER.Length); | |
$keyFormatted = $keyFormatted.Replace($KEY_HEADER, ''); | |
$keyFormatted = $keyFormatted.Replace($KEY_FOOTER, ''); | |
$keyFormatted = $keyFormatted.Replace('\r', ''); | |
$keyFormatted = $keyFormatted.Replace('\n', ''); | |
$keyFormatted = $keyFormatted.Trim(); | |
[byte[]]$privateKeyInDER = [System.Convert]::FromBase64String($keyFormatted); | |
$MemoryStream = New-Object System.IO.MemoryStream( , $privateKeyInDER) | |
$BinaryReader = New-Object System.IO.BinaryReader( , $MemoryStream) | |
[System.UInt16]$twobytes = 0; | |
[int]$elements = 0; | |
[byte]$bt = 0; | |
try | |
{ | |
$twobytes = $binaryReader.ReadUInt16(); | |
if ($twobytes -eq 0x8130) | |
{ | |
$binaryReader.ReadByte() | Out-Null | |
} | |
elseif ($twobytes -eq 0x8230) | |
{ | |
$binaryReader.ReadInt16() | Out-Null | |
} | |
else | |
{ | |
throw 'Wrong data'; | |
} | |
$twobytes = $binaryReader.ReadUInt16(); | |
if ($twobytes -ne 0x0102) | |
{ | |
throw 'Wrong data'; | |
} | |
$bt = $binaryReader.ReadByte(); | |
if ($bt -ne 0x00) | |
{ | |
throw 'Wrong data' | |
} | |
$elements = Get-IntegerSize($binaryReader) | |
$paramModulus = $binaryReader.ReadBytes($elements); | |
$elements = Get-IntegerSize($binaryReader) | |
$paramE = $binaryReader.ReadBytes($elements); | |
$elements = Get-IntegerSize($binaryReader) | |
$paramD = $binaryReader.ReadBytes($elements); | |
$elements = Get-IntegerSize($binaryReader) | |
$paramP = $binaryReader.ReadBytes($elements); | |
$elements = Get-IntegerSize($binaryReader) | |
$paramQ = $binaryReader.ReadBytes($elements); | |
$elements = Get-IntegerSize($binaryReader) | |
$paramDP = $binaryReader.ReadBytes($elements); | |
$elements = Get-IntegerSize($binaryReader) | |
$paramDQ = $binaryReader.ReadBytes($elements); | |
$elements = Get-IntegerSize($binaryReader) | |
$paramIQ = $binaryReader.ReadBytes($elements); | |
Assert-Length -Data ([ref]$paramD) -DesiredLength 256 | |
Assert-Length -Data ([ref]$paramDP) -DesiredLength 128 | |
Assert-Length -Data ([ref]$paramDQ) -DesiredLength 128 | |
Assert-Length -Data ([ref]$paramE) -DesiredLength 3 | |
Assert-Length -Data ([ref]$paramIQ) -DesiredLength 128 | |
Assert-Length -Data ([ref]$paramModulus) -DesiredLength 256 | |
Assert-Length -Data ([ref]$paramP) -DesiredLength 128 | |
Assert-Length -Data ([ref]$paramQ) -DesiredLength 128 | |
[System.Security.Cryptography.RSAParameters]$rsaParameters = New-Object System.Security.Cryptography.RSAParameters | |
$rsaParameters.Modulus = $paramModulus; | |
$rsaParameters.Exponent = $paramE; | |
$rsaParameters.D = $paramD; | |
$rsaParameters.P = $paramP; | |
$rsaParameters.Q = $paramQ; | |
$rsaParameters.DP = $paramDP; | |
$rsaParameters.DQ = $paramDQ; | |
$rsaParameters.InverseQ = $paramIQ; | |
return $rsaParameters; | |
} | |
finally | |
{ | |
$BinaryReader.Close() | Out-Null | |
} | |
} | |
} | |
function Get-IntegerSize | |
{ | |
param | |
( | |
[Parameter(Mandatory = $true)] | |
[System.IO.BinaryReader]$Binary | |
) | |
[byte]$bt = 0; | |
[byte]$lowbyte = 0x00; | |
[byte]$highbyte = 0x00; | |
[int]$count = 0; | |
$bt = $binary.ReadByte(); | |
if ($bt -ne 0x02) | |
{ | |
return 0 | |
} | |
$bt = $binary.ReadByte(); | |
if ($bt -eq 0x81) | |
{ | |
$count = $binary.ReadByte() | |
} | |
elseif ($bt -eq 0x82) | |
{ | |
$highbyte = $binary.ReadByte(); | |
$lowbyte = $binary.ReadByte(); | |
[byte[]]$modint = $lowbyte, $highbyte, 0x00, 0x00; | |
$count = [System.BitConverter]::ToInt32($modint, 0); | |
} | |
else | |
{ | |
$count = $bt; | |
} | |
while ($binary.ReadByte() -eq 0x00) | |
{ | |
$count -= 1; | |
} | |
$binary.BaseStream.Seek(-1, [System.IO.SeekOrigin]::Current) | Out-Null | |
return $count; | |
} | |
function Assert-Length | |
{ | |
param | |
( | |
[Parameter(Mandatory = $true)] | |
[ref]$Data, | |
[Parameter(Mandatory = $true)] | |
[int]$DesiredLength | |
) | |
if ($null -eq $data -or $data.Value.Length -ge $desiredLength) | |
{ | |
return; | |
} | |
[int]$zeros = $desiredLength - $data.Value.Length; | |
[byte[]]$newData = [byte[]]::new($desiredLength) | |
[System.Array]::Copy($data.Value, 0, $newData, $zeros, $data.Value.Length); | |
$data.Value = $newData; | |
} | |
function Test-IsRSAPrivateKeyAvailable | |
{ | |
param | |
( | |
[Parameter(Mandatory = $false)] | |
[string]$PEM | |
) | |
return (($null -ne $PEM) -and $PEM.Contains($KEY_HEADER) -and $PEM.Contains($KEY_FOOTER)) | |
} | |
} | |
PROCESS | |
{ | |
if ($PublicKey.Exists) | |
{ | |
[string]$PEMPublicKey = [System.IO.File]::ReadAllText($PublicKey) | |
} | |
if ($PrivateKey.Exists) | |
{ | |
[string]$PEMPrivateKey = [System.IO.File]::ReadAllText($PrivateKey) | |
} | |
[byte[]]$pemCertWithPrivateKey = [System.Text.Encoding]::ASCII.GetBytes($PEMPublicKey) | |
[System.Security.Cryptography.RSACryptoServiceProvider]$rsaPK = Get-RSA -PEM $PEMPrivateKey | |
$cert.Import($pemCertWithPrivateKey, '', ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)) | |
if ($null -ne $rsaPK) | |
{ | |
$cert.PrivateKey = $rsaPK; | |
} | |
} | |
END | |
{ | |
if ($null -ne $OutPFX) | |
{ | |
if ($PFXPassword.Length -gt 0 -and $NoPassword.IsPresent -eq $false) | |
{ | |
[system.io.file]::WriteAllBytes($OutPFX.fullname, $Cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $PFXPassword)) | |
} | |
else | |
{ | |
[system.io.file]::WriteAllBytes($OutPFX.fullname, $Cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx)) | |
} | |
} | |
else | |
{ | |
return $cert; | |
} | |
} | |
} | |
function New-JWTToken | |
{ | |
[CmdletBinding(DefaultParameterSetName = 'DefaultPEM')] | |
param | |
( | |
[Parameter(ParameterSetName = 'DefaultPEM', | |
Mandatory = $true)] | |
[Parameter(ParameterSetName = 'DefaultX509', | |
Mandatory = $true)] | |
[string]$UserID, | |
[Parameter(ParameterSetName = 'DefaultPEM', | |
Mandatory = $true)] | |
[Parameter(ParameterSetName = 'DefaultX509', | |
Mandatory = $true)] | |
[string]$UserDirectory, | |
[Parameter(ParameterSetName = 'DefaultPEM', | |
Mandatory = $false)] | |
[Parameter(ParameterSetName = 'DefaultX509', | |
Mandatory = $false)] | |
[string]$Roles, | |
[Parameter(ParameterSetName = 'DefaultPEM', | |
Mandatory = $false)] | |
[Parameter(ParameterSetName = 'DefaultX509', | |
Mandatory = $false)] | |
[Hashtable]$Attributes, | |
[Parameter(ParameterSetName = 'PayloadPEM', | |
Mandatory = $true)] | |
[Parameter(ParameterSetName = 'PayloadX509', | |
Mandatory = $true)] | |
[Hashtable]$Payload, | |
[Parameter(ParameterSetName = 'PayloadPEM', | |
Mandatory = $true)] | |
[Parameter(ParameterSetName = 'DefaultPEM', | |
Mandatory = $true)] | |
[system.IO.FileInfo]$PublicKeyPEM, | |
[Parameter(ParameterSetName = 'PayloadPEM', | |
Mandatory = $true)] | |
[Parameter(ParameterSetName = 'DefaultPEM', | |
Mandatory = $true)] | |
[system.IO.FileInfo]$PrivateKeyPEM, | |
[Parameter(ParameterSetName = 'PayloadX509', | |
Mandatory = $true)] | |
[Parameter(ParameterSetName = 'DefaultX509', | |
Mandatory = $true)] | |
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, | |
[datetime]$Expiry, | |
[hashtable]$AdditionalHeader | |
) | |
BEGIN | |
{ | |
Add-Type -AssemblyName System.IdentityModel | |
if ($null -eq $Certificate) | |
{ | |
$Certificate = Convert-PemtoX509 -PublicKey $PublicKeyPEM.FullName -PrivateKey $PrivateKeyPEM.FullName | |
} | |
Write-Verbose $Certificate.Thumbprint | |
$X509SigningCredentials = New-Object System.IdentityModel.Tokens.X509SigningCredentials($Certificate) | |
$SignatureFormatter = $X509SigningCredentials.SigningKey.GetSignatureFormatter($X509SigningCredentials.SignatureAlgorithm) | |
$HashAlgorithm = [System.Security.Cryptography.HashAlgorithm]::Create([System.Security.Cryptography.HashAlgorithmName]::SHA256) | |
$Algorithm = 'RS256' | |
$type = 'JWT' | |
if ($PSBoundParameters.ContainsKey('AdditionalHeader')) | |
{ | |
$AdditionalHeader.alg = $Algorithm; | |
$AdditionalHeader.typ = $type | |
$headerjson = $AdditionalHeader | ConvertTo-Json -Compress | |
} | |
else | |
{ | |
[hashtable]$header = @{ | |
alg = $Algorithm; typ = $type | |
} | |
$headerjson = $header | ConvertTo-Json -Compress | |
} | |
Write-Verbose $headerjson | |
} | |
PROCESS | |
{ | |
$JWT = $Null | |
if ($PSBoundParameters.ContainsKey('Expiry')) | |
{ | |
$exp = [int][double]::parse((Get-Date -Date $($Expiry.ToUniversalTime()) -UFormat %s)) # Grab Unix Epoch Timestamp and add desired expiration. | |
} | |
else | |
{ | |
$ValidforSeconds = 240 | |
$exp = [int][double]::parse((Get-Date -Date $((Get-Date).addseconds($ValidforSeconds).ToUniversalTime()) -UFormat %s)) # Grab Unix Epoch Timestamp and add desired expiration. | |
} | |
if ($PSCmdlet.ParameterSetName -in 'DefaultPEM', 'DefaultX509') | |
{ | |
[hashtable]$Payload = @{ | |
'UserId' = "$UserID" | |
'UserDirectory' = "$UserDirectory" | |
'exp' = $exp | |
} | |
if (![string]::IsNullOrEmpty($Attributes)) | |
{ | |
$Payload.Attributes = $Attributes | |
} | |
if (![string]::IsNullOrEmpty($Roles)) | |
{ | |
$Payload.roles = $Roles | |
} | |
} | |
else | |
{ | |
#Add the Exp to the PayLoad Hashtable | |
$Payload.exp = $exp | |
} | |
$JsonPayload = $payload | ConvertTo-Json -Compress | |
Write-Verbose $JsonPayload | |
$headerjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerjson)).Split('=')[0].Replace('+', '-').Replace('/', '_') | |
$payloadjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($JsonPayload)).Split('=')[0].Replace('+', '-').Replace('/', '_') | |
$ToBeSigned = "$($headerjsonbase64).$($payloadjsonbase64)" | |
$BytesToBeSigned = ([System.Text.Encoding]::UTF8).GetBytes($ToBeSigned) | |
$ToBeSignedHash = $HashAlgorithm.ComputeHash($BytesToBeSigned) | |
$Sig = [Convert]::ToBase64String($SignatureFormatter.CreateSignature($ToBeSignedHash)) -replace '\+', '-' -replace '/', '_' -replace '=' | |
$JWT = "$($ToBeSigned).$($sig)" | |
return $JWT | |
} | |
} | |
<# # Examples | |
#Using PEMs with UserID & UserDirectory | |
$PublicKeyFile = "C:\Path\To\PublicKey.pem" | |
$PrivatekeyFile = "C:\Path\To\PrivateKey.pem" | |
New-JWTToken -PublicKeyPEM $PublicKeyFile -PrivateKeyPEM $PrivatekeyFile -UserID "MarcTestxxx" -UserDirectory "JWTTest" | |
#Using X509Cert with UserID & UserDirectory | |
$Cert = Get-ChildItem Cert:\CurrentUser\My\6FB985D49DA34EAF58B47BE9F4D9BFB482F5D211 | |
New-JWTToken -Certificate $Cert -UserID "MarcTestxxx" -UserDirectory "SSS" | |
#Using Custom Payload | |
$Payload = @{ | |
CustomUID = "UserID" | |
CustomUD = "UserDirectory" | |
OtherAttributes = @{ | |
as = "many" | |
asyou = "need" | |
} | |
OrWhatever = "data you want" | |
email = "marc.collins@qlik.com" | |
} | |
New-JWTToken -Certificate $Cert -Payload $Payload | |
New-JWTToken -PublicKeyPEM $PublicKeyFile -PrivateKeyPEM $PrivatekeyFile -Payload $Payload | |
#> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment