-
-
Notifications
You must be signed in to change notification settings - Fork 7.3k
[powershell-experimental] : http signature authentication implementation #6176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
49f2b4b
ad24d34
8bcd51d
ff00948
f865bd8
1d0d4e1
5372b5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| {{>partial_header}} | ||
| <# | ||
| .SYNOPSIS | ||
| Get the API key Id and API key file path. | ||
Ghufz marked this conversation as resolved.
Show resolved
Hide resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add some overview description explaining this code is for HTTP signature. See python example I just realized in Python we should give a better name instead of "private key" because in the HTTP signature spec, both asymmetric and symmetric keys are supported.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use the same terminology as the HTTP signature spec:
The spec does not talk about "API keys". |
||
|
|
||
| .DESCRIPTION | ||
| Get the API key Id and API key file path. If no api prefix is provided then it use default api key prefix 'Signature' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: document the meaning of api key prefix. Is this really needed? It looks like this is used to control the prefix of the "Authorization" HTTP header, but I don't see in the "HTTP signature" spec any option to control the name of the attribute. This is supposed to be exactly "Signature". On the other hand, other parameters are currently hard-coded and should be exposed as configuration parameters. Example:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good to document which of the methods are public versus which one are for internal use. |
||
| .OUTPUTS | ||
| PSCustomObject : This contains APIKeyId, APIKeyFilePath, APIKeyPrefix | ||
| #> | ||
| function Get-{{{apiNamePrefix}}}APIKeyInfo { | ||
| $ApiKeysList = $Script:Configuration['ApiKey'] | ||
| $ApiKeyPrefixList = $Script:Configuration['ApiKeyPrefix'] | ||
| $apiPrefix = "Signature" | ||
| if ($null -eq $ApiKeysList -or $ApiKeysList.Count -eq 0) { | ||
| throw "Unable to reterieve the api key details" | ||
Ghufz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| if ($null -eq $ApiKeyPrefixList -or $ApiKeyPrefixList.Count -eq 0) { | ||
| Write-Verbose "Unable to reterieve the api key prefix details,setting it to default ""Signature""" | ||
Ghufz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| foreach ($item in $ApiKeysList.GetEnumerator()) { | ||
| if (![string]::IsNullOrEmpty($item.Name)) { | ||
| if (Test-Path -Path $item.Value) { | ||
| $apiKey = $item.Value | ||
| $apikeyId = $item.Name | ||
| break; | ||
| } | ||
| else { | ||
| throw "API key file path does not exist." | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if ($ApiKeyPrefixList.ContainsKey($apikeyId)) { | ||
| $apiPrefix = ApiKeyPrefixList[$apikeyId] | ||
| } | ||
|
|
||
| if ($apikeyId -and $apiKey -and $apiPrefix) { | ||
| $result = New-Object -Type PSCustomObject -Property @{ | ||
| ApiKeyId = $apikeyId; | ||
| ApiKeyFilePath = $apiKey | ||
| ApiKeyPrefix = $apiPrefix | ||
| } | ||
| } | ||
| else { | ||
| return $null | ||
| } | ||
| return $result | ||
| } | ||
|
|
||
| <# | ||
| .SYNOPSIS | ||
| Gets the headers for http signed auth. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use case consistently (HTTP). |
||
|
|
||
| .DESCRIPTION | ||
| Gets the headers for the http signed auth. It use (targetpath), date, host and body digest to create authorization header. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Grammar: "it use"
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The HTTP signature spec does not mandate this specific sequence of HTTP headers. The headers are supposed to be provided by the caller. |
||
| .PARAMETER Method | ||
| Http method | ||
| .PARAMETER UriBuilder | ||
| UriBuilder for url and query parameter | ||
| .PARAMETER Body | ||
| Request body | ||
| .OUTPUTS | ||
| Hashtable | ||
| #> | ||
| function Get-{{{apiNamePrefix}}}HttpSignedHeader { | ||
| param( | ||
| [string]$Method, | ||
| [System.UriBuilder]$UriBuilder, | ||
| [string]$Body | ||
| ) | ||
| #Hash table to store singed headers | ||
| $HttpSignedHeader = @{} | ||
| $TargetHost = $UriBuilder.Host | ||
|
|
||
| #Check for Authentication type | ||
| $apiKeyInfo = Get-{{{apiNamePrefix}}}APIKeyInfo | ||
| if ($null -eq $apiKeyInfo) { | ||
| throw "Unable to reterieve the api key info " | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: reterieve
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove space before double quote |
||
| } | ||
|
|
||
| #get the body digest | ||
| $bodyHash = Get-{{{apiNamePrefix}}}StringHash -String $Body | ||
| $Digest = [String]::Format("SHA-256={0}", [Convert]::ToBase64String($bodyHash)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The HASH algorithm should be provided as an input parameter. |
||
|
|
||
| #get the date in UTC | ||
| $dateTime = Get-Date | ||
| $currentDate = $dateTime.ToUniversalTime().ToString("r") | ||
|
|
||
| $requestTargetPath = [string]::Format("{0} {1}{2}",$Method.ToLower(),$UriBuilder.Path.ToLower(),$UriBuilder.Query) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The URL must not be lowercase, per spec. |
||
| $h_requestTarget = [string]::Format("(request-target): {0}",$requestTargetPath) | ||
| $h_cdate = [string]::Format("date: {0}",$currentDate) | ||
| $h_digest = [string]::Format("digest: {0}",$Digest) | ||
| $h_targetHost = [string]::Format("host: {0}",$TargetHost) | ||
|
|
||
| $stringToSign = [String]::Format("{0}`n{1}`n{2}`n{3}", | ||
| $h_requestTarget,$h_cdate, | ||
| $h_targetHost,$h_digest) | ||
|
|
||
| $hashedString = Get-{{{apiNamePrefix}}}StringHash -String $stringToSign | ||
| $signedHeader = Get-{{{apiNamePrefix}}}RSASHA256SignedString -APIKeyFilePath $apiKeyInfo.ApiKeyFilePath -DataToSign $hashedString | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should make it clear which specific RSA signing algorithm is used. Is it RSASSA-PSS or RSA PKCS v1.5? Ideally this would be configurable by the client. |
||
| $authorizationHeader = [string]::Format("{0} keyId=""{1}"",algorithm=""rsa-sha256"",headers=""(request-target) date host digest"",signature=""{2}""", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The signature algorithm should not be hard-coded to "rsa-sha256" because 1) this algorithm has been deprecated in the HTTP signature spec and 2) the client should be able to decide which signature algorithm to use. |
||
| $apiKeyInfo.ApiKeyPrefix, $apiKeyInfo.ApiKeyId, $signedHeader) | ||
|
|
||
| $HttpSignedHeader["Date"] = $currentDate | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The list of signed HTTP headers should be configurable by the client. |
||
| $HttpSignedHeader["Host"] = $TargetHost | ||
| $HttpSignedHeader["Content-Type"] = "application/json" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The content-type header should not be hard-coded to |
||
| $HttpSignedHeader["Digest"] = $Digest | ||
| $HttpSignedHeader["Authorization"] = $authorizationHeader | ||
| return $HttpSignedHeader | ||
| } | ||
|
|
||
| <# | ||
| .SYNOPSIS | ||
| Gets the headers for http signed auth. | ||
|
|
||
| .DESCRIPTION | ||
| Gets the headers for the http signed auth. It use (targetpath), date, host and body digest to create authorization header. | ||
Ghufz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .PARAMETER APIKeyFilePath | ||
| Specify the API key file path | ||
| .PARAMETER DataToSign | ||
| Specify the data to sign | ||
| .OUTPUTS | ||
| String | ||
| #> | ||
| function Get-{{{apiNamePrefix}}}RSASHA256SignedString { | ||
| Param( | ||
| [string]$APIKeyFilePath, | ||
| [byte[]]$DataToSign | ||
| ) | ||
| try { | ||
| $rsa_provider_path = Join-Path -Path $PSScriptRoot -ChildPath "{{{apiNamePrefix}}}RSAEncryptionProvider.cs" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should support ECDSA keys. |
||
| $rsa_provider_sourceCode = Get-Content -Path $rsa_provider_path -Raw | ||
| Add-Type -TypeDefinition $rsa_provider_sourceCode | ||
| $signed_string = [RSAEncryption.RSAEncryptionProvider]::GetRSASignb64encode($APIKeyFilePath, $DataToSign) | ||
| if ($null -eq $signed_string) { | ||
| throw "Unable to sign the header using the API key" | ||
| } | ||
| return $signed_string | ||
| } | ||
| catch { | ||
| throw $_ | ||
| } | ||
| } | ||
| <# | ||
| .Synopsis | ||
| Gets the hash of string. | ||
| .Description | ||
| Gets the hash of string | ||
| .Outputs | ||
| String | ||
| #> | ||
| Function Get-{{{apiNamePrefix}}}StringHash([String] $String, $HashName = "SHA256") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be hard-coded to SHA256 |
||
| $hashAlogrithm = [System.Security.Cryptography.HashAlgorithm]::Create($HashName) | ||
| $hashAlogrithm.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String)) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove these empty spaces in my next PR to the PowerShell generator