Private/Cryptography.ps1
function Invoke-PodeHMACSHA256Hash { [CmdletBinding(DefaultParameterSetName='String')] param( [Parameter(Mandatory=$true)] [string] $Value, [Parameter(Mandatory=$true, ParameterSetName='String')] [string] $Secret, [Parameter(Mandatory=$true, ParameterSetName='Bytes')] [byte[]] $SecretBytes ) if (![string]::IsNullOrWhiteSpace($Secret)) { $SecretBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret) } if ($SecretBytes.Length -eq 0) { throw "No secret supplied for HMAC256 hash" } $crypto = [System.Security.Cryptography.HMACSHA256]::new($SecretBytes) return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } function Invoke-PodeHMACSHA384Hash { [CmdletBinding(DefaultParameterSetName='String')] param( [Parameter(Mandatory=$true)] [string] $Value, [Parameter(Mandatory=$true, ParameterSetName='String')] [string] $Secret, [Parameter(Mandatory=$true, ParameterSetName='Bytes')] [byte[]] $SecretBytes ) if (![string]::IsNullOrWhiteSpace($Secret)) { $SecretBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret) } if ($SecretBytes.Length -eq 0) { throw "No secret supplied for HMAC384 hash" } $crypto = [System.Security.Cryptography.HMACSHA384]::new($SecretBytes) return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } function Invoke-PodeHMACSHA512Hash { [CmdletBinding(DefaultParameterSetName='String')] param( [Parameter(Mandatory=$true)] [string] $Value, [Parameter(Mandatory=$true, ParameterSetName='String')] [string] $Secret, [Parameter(Mandatory=$true, ParameterSetName='Bytes')] [byte[]] $SecretBytes ) if (![string]::IsNullOrWhiteSpace($Secret)) { $SecretBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret) } if ($SecretBytes.Length -eq 0) { throw "No secret supplied for HMAC512 hash" } $crypto = [System.Security.Cryptography.HMACSHA512]::new($SecretBytes) return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } function Invoke-PodeSHA256Hash { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Value ) $crypto = [System.Security.Cryptography.SHA256]::Create() return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } function Invoke-PodeSHA1Hash { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Value ) $crypto = [System.Security.Cryptography.SHA1]::Create() return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } function ConvertTo-PodeBase64Auth { param( [Parameter(Mandatory=$true)] [string] $Username, [Parameter(Mandatory=$true)] [string] $Password ) return [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($Username):$($Password)")) } function Invoke-PodeMD5Hash { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Value ) $crypto = [System.Security.Cryptography.MD5]::Create() return [System.BitConverter]::ToString($crypto.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($Value))).Replace('-', '').ToLowerInvariant() } function Get-PodeRandomBytes { param ( [Parameter()] [int] $Length = 16 ) return (Use-PodeStream -Stream ([System.Security.Cryptography.RandomNumberGenerator]::Create()) { param($p) $bytes = [byte[]]::new($Length) $p.GetBytes($bytes) return $bytes }) } function New-PodeSalt { param ( [Parameter()] [int] $Length = 8 ) $bytes = [byte[]](Get-PodeRandomBytes -Length $Length) return [System.Convert]::ToBase64String($bytes) } function New-PodeGuid { param ( [Parameter()] [int] $Length = 16, [switch] $Secure, [switch] $NoDashes ) # generate a cryptographically secure guid if ($Secure) { $bytes = [byte[]](Get-PodeRandomBytes -Length $Length) $guid = ([guid]::new($bytes)).ToString() } # return a normal guid else { $guid = ([guid]::NewGuid()).ToString() } if ($NoDashes) { $guid = ($guid -ireplace '-', '') } return $guid } function Invoke-PodeValueSign { param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [ValidateNotNullOrEmpty()] [string] $Value, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Secret ) return "s:$($Value).$(Invoke-PodeHMACSHA256Hash -Value $Value -Secret $Secret)" } function Invoke-PodeValueUnsign { param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [ValidateNotNullOrEmpty()] [string] $Value, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Secret ) # the signed value must start with "s:" if (!$Value.StartsWith('s:')) { return $null } # the signed value mised contain a dot - splitting value and signature $Value = $Value.Substring(2) $periodIndex = $Value.LastIndexOf('.') if ($periodIndex -eq -1) { return $null } # get the raw value and signature $raw = $Value.Substring(0, $periodIndex) $sig = $Value.Substring($periodIndex + 1) if ((Invoke-PodeHMACSHA256Hash -Value $raw -Secret $Secret) -ne $sig) { return $null } return $raw } function Test-PodeJwt { param( [Parameter(Mandatory=$true)] [pscustomobject] $Payload ) $now = [datetime]::Now $unixStart = [datetime]::new(1970, 1, 1) # validate expiry if (![string]::IsNullOrWhiteSpace($Payload.exp)) { if ($now -gt $unixStart.AddSeconds($Payload.exp)) { throw "The JWT has expired" } } # validate not-before if (![string]::IsNullOrWhiteSpace($Payload.nbf)) { if ($now -lt $unixStart.AddSeconds($Payload.nbf)) { throw "The JWT is not yet valid for use" } } } function New-PodeJwtSignature { param( [Parameter(Mandatory=$true)] [string] $Algorithm, [Parameter(Mandatory=$true)] [string] $Token, [Parameter()] [byte[]] $SecretBytes ) if (($Algorithm -ine 'none') -and (($null -eq $SecretBytes) -or ($SecretBytes.Length -eq 0))) { throw "No Secret supplied for JWT signature" } if (($Algorithm -ieq 'none') -and (($null -ne $secretBytes) -and ($SecretBytes.Length -gt 0))) { throw "Expected no secret to be supplied for no signature" } $sig = $null switch ($Algorithm.ToUpperInvariant()) { 'HS256' { $sig = Invoke-PodeHMACSHA256Hash -Value $Token -SecretBytes $SecretBytes $sig = ConvertTo-PodeBase64UrlValue -Value $sig -NoConvert } 'HS384' { $sig = Invoke-PodeHMACSHA384Hash -Value $Token -SecretBytes $SecretBytes $sig = ConvertTo-PodeBase64UrlValue -Value $sig -NoConvert } 'HS512' { $sig = Invoke-PodeHMACSHA512Hash -Value $Token -SecretBytes $SecretBytes $sig = ConvertTo-PodeBase64UrlValue -Value $sig -NoConvert } 'NONE' { $sig = [string]::Empty } default { throw "The JWT algorithm is not currently supported: $($Algorithm)" } } return $sig } function ConvertTo-PodeBase64UrlValue { param( [Parameter(Mandatory=$true)] [string] $Value, [switch] $NoConvert ) if (!$NoConvert) { $Value = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Value)) } $Value = ($Value -ireplace '\+', '-') $Value = ($Value -ireplace '/', '_') $Value = ($Value -ireplace '=', '') return $Value } function ConvertFrom-PodeJwtBase64Value { param( [Parameter(Mandatory=$true)] [string] $Value ) # map chars $Value = ($Value -ireplace '-', '+') $Value = ($Value -ireplace '_', '/') # add padding switch ($Value.Length % 4) { 1 { $Value = $Value.Substring(0, $Value.Length - 1) } 2 { $Value += '==' } 3 { $Value += '=' } } # convert base64 to string try { $Value = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Value)) } catch { throw "Invalid Base64 encoded value found in JWT" } # return json try { return ($Value | ConvertFrom-Json) } catch { throw "Invalid JSON value found in JWT" } } |