Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,9 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("api_client.mustache", infrastructureFolder + "Private", apiNamePrefix + "ApiClient.ps1"));
supportingFiles.add(new SupportingFile("Get-CommonParameters.mustache", infrastructureFolder + File.separator + "Private" + File.separator, "Get-CommonParameters.ps1"));
supportingFiles.add(new SupportingFile("Out-DebugParameter.mustache", infrastructureFolder + File.separator + "Private" + File.separator, "Out-DebugParameter.ps1"));
supportingFiles.add(new SupportingFile("http_signature_auth.mustache", infrastructureFolder + "Private", apiNamePrefix + "HttpSignatureAuth.ps1"));
supportingFiles.add(new SupportingFile("rsa_provider.mustache", infrastructureFolder + "Private", apiNamePrefix + "RSAEncryptionProvider.cs"));


// en-US
supportingFiles.add(new SupportingFile("about_Org.OpenAPITools.help.txt.mustache", infrastructureFolder + File.separator + "en-US" + File.separator + "about_" + packageName + ".help.txt"));
Expand All @@ -626,7 +629,7 @@ public void processOpts() {
@SuppressWarnings("static-method")
@Override
public String escapeText(String input) {

Copy link
Member

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

if (input == null) {
return input;
}
Expand All @@ -643,6 +646,7 @@ public String escapeText(String input) {
.replaceAll("[\\t\\n\\r]", " ")
.replace("\\", "\\\\")
.replace("\"", "\"\""));

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ function Invoke-{{{apiNamePrefix}}}ApiClient {
$RequestBody = $Body
}

# http signature authentication
if ($null -ne $Configuration['ApiKey'] -and $Configuration['ApiKey'].Count -gt 0) {
$httpSignHeaderArgument = @{
Method = $Method
UriBuilder = $UriBuilder
Body = $Body
}
$signedHeader = Get-{{{apiNamePrefix}}}HttpSignedHeader @httpSignHeaderArgument
if($null -ne $signedHeader -and $signedHeader.Count -gt 0){
foreach($item in $signedHeader.GetEnumerator()){
$HeaderParameters[$item.Name] = $item.Value
}
}
}

if ($SkipCertificateCheck -eq $true) {
$Response = Invoke-WebRequest -Uri $UriBuilder.Uri `
-Method $Method `
Expand Down
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.
Copy link
Contributor

@sebastien-rosset sebastien-rosset May 7, 2020

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about HttpSignatureKey?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the same terminology as the HTTP signature spec:

The "signature" authentication scheme is based on the model that the client must authenticate itself with a digital signature produced by either a private asymmetric key (e.g., RSA) or a shared symmetric key (e.g., HMAC). The scheme is parameterized enough such that it is not bound to any particular key type or signing algorithm. However, it does explicitly assume that clients can send an HTTP Date header.

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'
Copy link
Contributor

@sebastien-rosset sebastien-rosset May 7, 2020

Choose a reason for hiding this comment

The 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:

Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",
   headers="(request-target) host date digest content-length",
   signature="Base64(RSA-SHA256(signing string))"

Copy link
Contributor

Choose a reason for hiding this comment

The 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"
}

if ($null -eq $ApiKeyPrefixList -or $ApiKeyPrefixList.Count -eq 0) {
Write-Verbose "Unable to reterieve the api key prefix details,setting it to default ""Signature"""
}

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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar: "it use"

Copy link
Contributor

Choose a reason for hiding this comment

The 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 "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: reterieve

Copy link
Contributor

Choose a reason for hiding this comment

The 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))
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

@sebastien-rosset sebastien-rosset May 7, 2020

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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}""",
Copy link
Contributor

@sebastien-rosset sebastien-rosset May 7, 2020

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The content-type header should not be hard-coded to application/json

$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.
.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"
Copy link
Contributor

Choose a reason for hiding this comment

The 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") {
Copy link
Contributor

Choose a reason for hiding this comment

The 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))
}
Loading