Private/HttpSignatureAuth.ps1
# # Cloud Governance Api # Contact: support@avepoint.com # <# .SYNOPSIS Gets the headers for HTTP signature. .DESCRIPTION Gets the headers for the http sigature. .PARAMETER Method HTTP method .PARAMETER UriBuilder UriBuilder for url and query parameter .PARAMETER Body Request body .OUTPUTS Hashtable #> function Get-HttpSignedHeader { param( [string]$Method, [System.UriBuilder]$UriBuilder, [string]$Body, [hashtable]$RequestHeader ) $HEADER_REQUEST_TARGET = '(request-target)' # The time when the HTTP signature was generated. $HEADER_CREATED = '(created)' # The time when the HTTP signature expires. The API server should reject HTTP requests # that have expired. $HEADER_EXPIRES = '(expires)' # The 'Host' header. $HEADER_HOST = 'Host' # The 'Date' header. $HEADER_DATE = 'Date' # When the 'Digest' header is included in the HTTP signature, the client automatically # computes the digest of the HTTP request body, per RFC 3230. $HEADER_DIGEST = 'Digest' # The 'Authorization' header is automatically generated by the client. It includes # the list of signed headers and a base64-encoded signature. $HEADER_AUTHORIZATION = 'Authorization' #Hash table to store singed headers $HttpSignedRequestHeader = @{ } $HttpSignatureHeader = @{ } $TargetHost = $UriBuilder.Host $httpSigningConfiguration = Get-ConfigurationHttpSigning $Digest = $null #get the body digest $bodyHash = Get-StringHash -String $Body -HashName $httpSigningConfiguration.HashAlgorithm if ($httpSigningConfiguration.HashAlgorithm -eq "SHA256") { $Digest = [String]::Format("SHA-256={0}", [Convert]::ToBase64String($bodyHash)) } elseif ($httpSigningConfiguration.HashAlgorithm -eq "SHA512") { $Digest = [String]::Format("SHA-512={0}", [Convert]::ToBase64String($bodyHash)) } $dateTime = Get-Date #get the date in UTC $currentDate = $dateTime.ToUniversalTime().ToString("r") foreach ($headerItem in $httpSigningConfiguration.HttpSigningHeader) { if ($headerItem -eq $HEADER_REQUEST_TARGET) { $requestTargetPath = [string]::Format("{0} {1}{2}", $Method.ToLower(), $UriBuilder.Path, $UriBuilder.Query) $HttpSignatureHeader.Add($HEADER_REQUEST_TARGET, $requestTargetPath) } elseif ($headerItem -eq $HEADER_CREATED) { $created = Get-UnixTime -Date $dateTime -TotalTime TotalSeconds $HttpSignatureHeader.Add($HEADER_CREATED, $created) } elseif ($headerItem -eq $HEADER_EXPIRES) { $expire = $dateTime.AddSeconds($httpSigningConfiguration.SignatureValidityPeriod) $expireEpocTime = Get-UnixTime -Date $expire -TotalTime TotalSeconds $HttpSignatureHeader.Add($HEADER_EXPIRES, $expireEpocTime) } elseif ($headerItem -eq $HEADER_HOST) { $HttpSignedRequestHeader[$HEADER_HOST] = $TargetHost $HttpSignatureHeader.Add($HEADER_HOST.ToLower(), $TargetHost) } elseif ($headerItem -eq $HEADER_DATE) { $HttpSignedRequestHeader[$HEADER_DATE] = $currentDate $HttpSignatureHeader.Add($HEADER_DATE.ToLower(), $currentDate) } elseif ($headerItem -eq $HEADER_DIGEST) { $HttpSignedRequestHeader[$HEADER_DIGEST] = $Digest $HttpSignatureHeader.Add($HEADER_DIGEST.ToLower(), $Digest) } elseif($RequestHeader.ContainsKey($headerItem)) { $HttpSignatureHeader.Add($headerItem.ToLower(), $RequestHeader[$headerItem]) } else { throw "Cannot sign HTTP request. Request does not contain the $headerItem header." } } # header's name separated by space $headersKeysString = $HttpSignatureHeader.Keys -join " " $headerValuesList = @() foreach ($item in $HttpSignatureHeader.GetEnumerator()) { $headerValuesList += [string]::Format("{0}: {1}", $item.Name, $item.Value) } #Concatinate headers value separated by new line $headerValuesString = $headerValuesList -join "`n" #Gets the hash of the headers value $signatureHashString = Get-StringHash -String $headerValuesString -HashName $httpSigningConfiguration.HashAlgorithm #Gets the Key type to select the correct signing alogorithm $KeyType = Get-KeyTypeFromFile -KeyFilePath $httpSigningConfiguration.KeyFilePath if ($keyType -eq "RSA") { $headerSignatureStr = Get-RSASignature -PrivateKeyFilePath $httpSigningConfiguration.KeyFilePath ` -DataToSign $signatureHashString ` -HashAlgorithmName $httpSigningConfiguration.HashAlgorithm ` -KeyPassPhrase $httpSigningConfiguration.KeyPassPhrase ` -SigningAlgorithm $httpSigningConfiguration.SigningAlgorithm } elseif ($KeyType -eq "EC") { $headerSignatureStr = Get-ECDSASignature -ECKeyFilePath $httpSigningConfiguration.KeyFilePath ` -DataToSign $signatureHashString ` -HashAlgorithmName $httpSigningConfiguration.HashAlgorithm ` -KeyPassPhrase $httpSigningConfiguration.KeyPassPhrase } #Depricated <#$cryptographicScheme = Get-CryptographicScheme -SigningAlgorithm $httpSigningConfiguration.SigningAlgorithm ` -HashAlgorithm $httpSigningConfiguration.HashAlgorithm #> $cryptographicScheme = "hs2019" $authorizationHeaderValue = [string]::Format("Signature keyId=""{0}"",algorithm=""{1}""", $httpSigningConfiguration.KeyId, $cryptographicScheme) if ($HttpSignatureHeader.ContainsKey($HEADER_CREATED)) { $authorizationHeaderValue += [string]::Format(",created={0}", $HttpSignatureHeader[$HEADER_CREATED]) } if ($HttpSignatureHeader.ContainsKey($HEADER_EXPIRES)) { $authorizationHeaderValue += [string]::Format(",expires={0}", $HttpSignatureHeader[$HEADER_EXPIRES]) } $authorizationHeaderValue += [string]::Format(",headers=""{0}"",signature=""{1}""", $headersKeysString , $headerSignatureStr) $HttpSignedRequestHeader[$HEADER_AUTHORIZATION] = $authorizationHeaderValue return $HttpSignedRequestHeader } <# .SYNOPSIS Gets the RSA signature .DESCRIPTION Gets the RSA signature for the http signing .PARAMETER PrivateKeyFilePath Specify the API key file path .PARAMETER DataToSign Specify the data to sign .PARAMETER HashAlgorithmName HashAlgorithm to calculate the hash .PARAMETER KeyPassPhrase KeyPassPhrase for the encrypted key .OUTPUTS Base64String #> function Get-RSASignature { Param( [string]$PrivateKeyFilePath, [byte[]]$DataToSign, [string]$HashAlgorithmName, [string]$SigningAlgorithm, [securestring]$KeyPassPhrase ) try { if ($hashAlgorithmName -eq "sha256") { $hashAlgo = [System.Security.Cryptography.HashAlgorithmName]::SHA256 } elseif ($hashAlgorithmName -eq "sha512") { $hashAlgo = [System.Security.Cryptography.HashAlgorithmName]::SHA512 } if ($PSVersionTable.PSVersion.Major -ge 7) { $ecKeyHeader = "-----BEGIN RSA PRIVATE KEY-----" $ecKeyFooter = "-----END RSA PRIVATE KEY-----" $keyStr = Get-Content -Path $PrivateKeyFilePath -Raw $ecKeyBase64String = $keyStr.Replace($ecKeyHeader, "").Replace($ecKeyFooter, "").Trim() $keyBytes = [System.Convert]::FromBase64String($ecKeyBase64String) $rsa = [System.Security.Cryptography.RSA]::Create() [int]$bytCount = 0 $rsa.ImportRSAPrivateKey($keyBytes, [ref] $bytCount) if ($SigningAlgorithm -eq "RSASSA-PSS") { $signedBytes = $rsa.SignHash($DataToSign, $hashAlgo, [System.Security.Cryptography.RSASignaturePadding]::Pss) } else { $signedBytes = $rsa.SignHash($DataToSign, $hashAlgo, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1) } } else { $rsa_provider_path = Join-Path -Path $PSScriptRoot -ChildPath "RSAEncryptionProvider.cs" $rsa_provider_sourceCode = Get-Content -Path $rsa_provider_path -Raw Add-Type -TypeDefinition $rsa_provider_sourceCode [System.Security.Cryptography.RSA]$rsa = [RSAEncryption.RSAEncryptionProvider]::GetRSAProviderFromPemFile($PrivateKeyFilePath, $KeyPassPhrase) if ($SigningAlgorithm -eq "RSASSA-PSS") { throw "$SigningAlgorithm is not supported on $($PSVersionTable.PSVersion)" } else { $signedBytes = $rsa.SignHash($DataToSign, $hashAlgo, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1) } } $signedString = [Convert]::ToBase64String($signedBytes) return $signedString } catch { throw $_ } } <# .SYNOPSIS Gets the ECDSA signature .DESCRIPTION Gets the ECDSA signature for the http signing .PARAMETER PrivateKeyFilePath Specify the API key file path .PARAMETER DataToSign Specify the data to sign .PARAMETER HashAlgorithmName HashAlgorithm to calculate the hash .PARAMETER KeyPassPhrase KeyPassPhrase for the encrypted key .OUTPUTS Base64String #> function Get-ECDSASignature { param( [Parameter(Mandatory = $true)] [string]$ECKeyFilePath, [Parameter(Mandatory = $true)] [byte[]]$DataToSign, [Parameter(Mandatory = $false)] [String]$HashAlgorithmName, [Parameter(Mandatory = $false)] [securestring]$KeyPassPhrase ) if (!(Test-Path -Path $ECKeyFilePath)) { throw "key file path does not exist." } if ($PSVersionTable.PSVersion.Major -lt 7) { throw "ECDSA key is not supported on PowerShell version $($PSVersionTable.PSVersion), Use PowerShell v7.0 and above" } $ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----" $ecKeyFooter = "-----END EC PRIVATE KEY-----" $keyStr = Get-Content -Path $ECKeyFilePath -Raw $ecKeyBase64String = $keyStr.Replace($ecKeyHeader, "").Replace($ecKeyFooter, "").Trim() $keyBytes = [System.Convert]::FromBase64String($ecKeyBase64String) $ecdsa = [System.Security.Cryptography.ECDsa]::Create() [int]$bytCount =0 if (![string]::IsNullOrEmpty($KeyPassPhrase)) { $ecdsa.ImportEncryptedPkcs8PrivateKey($KeyPassPhrase,$keyBytes,[ref]$bytCount) } else { $ecdsa.ImportPkcs8PrivateKey($keyBytes,[ref]$bytCount) } $signedBytes = $ecdsa.SignHash($DataToSign) $derBytes = ConvertTo-ECDSAANS1Format -RawBytes $signedBytes $signedString = [System.Convert]::ToBase64String($derBytes) return $signedString } <# .Synopsis Gets the hash of string. .Description Gets the hash of string .Parameter String Specifies the string to calculate the hash .Parameter HashName Specifies the hash name to calculate the hash, Accepted values are "SHA1", "SHA256" and "SHA512" It is recommneded not to use "SHA1" to calculate the Hash .Outputs String #> Function Get-StringHash { param( [Parameter(Mandatory = $true)] [AllowEmptyString()] [string]$String, [Parameter(Mandatory = $true)] [ValidateSet("SHA1", "SHA256", "SHA512")] $HashName ) $hashAlogrithm = [System.Security.Cryptography.HashAlgorithm]::Create($HashName) $hashAlogrithm.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String)) } <# .Synopsis Gets the Unix time. .Description Gets the Unix time .Parameter Date Specifies the date to calculate the unix time .Parameter ToTalTime Specifies the total time , Accepted values are "TotalDays", "TotalHours", "TotalMinutes", "TotalSeconds" and "TotalMilliseconds" .Outputs Integer #> function Get-UnixTime { param( [Parameter(Mandatory = $true)] [DateTime]$Date, [Parameter(Mandatory = $false)] [ValidateSet("TotalDays", "TotalHours", "TotalMinutes", "TotalSeconds", "TotalMilliseconds")] [string]$TotalTime = "TotalSeconds" ) $date1 = Get-Date -Date "01/01/1970" $timespan = New-TimeSpan -Start $date1 -End $Date switch ($TotalTime) { "TotalDays" { [int]$timespan.TotalDays } "TotalHours" { [int]$timespan.TotalHours } "TotalMinutes" { [int]$timespan.TotalMinutes } "TotalSeconds" { [int]$timespan.TotalSeconds } "TotalMilliseconds" { [int]$timespan.TotalMilliseconds } } } function Get-CryptographicScheme { param( [Parameter(Mandatory = $true)] [string]$SigningAlgorithm, [Parameter(Mandatory = $true)] [string]$HashAlgorithm ) $rsaSigntureType = @("RSASSA-PKCS1-v1_5", "RSASSA-PSS") $SigningAlgorithm = $null if ($rsaSigntureType -contains $SigningAlgorithm) { switch ($HashAlgorithm) { "sha256" { $SigningAlgorithm = "rsa-sha256" } "sha512" { $SigningAlgorithm = "rsa-sha512" } } } return $SigningAlgorithm } <# .Synopsis Gets the key type from the pem file. .Description Gets the key type from the pem file. .Parameter KeyFilePath Specifies the key file path (pem file) .Outputs String #> function Get-KeyTypeFromFile { param( [Parameter(Mandatory = $true)] [string]$KeyFilePath ) if (-not(Test-Path -Path $KeyFilePath)) { throw "Key file path does not exist." } $ecPrivateKeyHeader = "BEGIN EC PRIVATE KEY" $ecPrivateKeyFooter = "END EC PRIVATE KEY" $rsaPrivateKeyHeader = "BEGIN RSA PRIVATE KEY" $rsaPrivateFooter = "END RSA PRIVATE KEY" $pkcs8Header = "BEGIN PRIVATE KEY" $pkcs8Footer = "END PRIVATE KEY" $keyType = $null $key = Get-Content -Path $KeyFilePath if ($key[0] -match $rsaPrivateKeyHeader -and $key[$key.Length - 1] -match $rsaPrivateFooter) { $KeyType = "RSA" } elseif ($key[0] -match $ecPrivateKeyHeader -and $key[$key.Length - 1] -match $ecPrivateKeyFooter) { $keyType = "EC" } elseif ($key[0] -match $ecPrivateKeyHeader -and $key[$key.Length - 1] -match $ecPrivateKeyFooter) { <#this type of key can hold many type different types of private key, but here due lack of pem header Considering this as EC key #> #TODO :- update the key based on oid $keyType = "EC" } else { throw "Either the key is invalid or key is not supported" } return $keyType } <# .Synopsis Converts sequence of R and S bytes to ANS1 format for ECDSASIgnature. .Description Converts sequence of R and S bytes to ANS1 format for ECDSASIgnature. .Parameter RawBytes[] Specifies the R and S bytes of ECDSA signature. .Outputs Byte[] #> function ConvertTo-ECDSAANS1Format{ Param( [Parameter(Mandatory = $true)] [byte[]]$RawBytes ) $derLength = 68 #default lenght for ECDSA code signinged bit 0x44 $rbytesLength = 32 #R length 0x20 $sbytesLength = 32 #S length 0x20 [byte[]]$rBytes = $signedBytes[0..31] [byte[]]$sBytes = $signedBytes[32..63] if($rBytes[0] -gt 0x7F){ $derLength++ $rbytesLength++ $rBytes = [byte[]]@(0x00) + $rBytes } if($sBytes[0] -gt 0x7F){ $derLength++ $sbytesLength++ $sBytes = [byte[]]@(0x00) + $sBytes } [byte[]]$derBytes = @() $derBytes += 48 # start of the sequence 0x30 $derBytes += $derLength # total length r lenth, type and r bytes $derBytes += 2 # tag for integer $derBytes += $rbytesLength # length of r $derBytes += $rBytes $derBytes += 2 #tag for integer $derBytes += $sbytesLength #length of s $derBytes += $sBytes return $derBytes } |