forked from loafle/openapi-generator-original
[PowerShell] Add useOneOfDiscriminatorLookup option (#6516)
* add useOneOfDiscriminatorLookup option * update doc
This commit is contained in:
@@ -11,6 +11,7 @@ sidebar_label: powershell
|
|||||||
|packageName|Client package name (e.g. PSTwitter).| |PSOpenAPITools|
|
|packageName|Client package name (e.g. PSTwitter).| |PSOpenAPITools|
|
||||||
|packageVersion|Package version (e.g. 0.1.2).| |0.1.2|
|
|packageVersion|Package version (e.g. 0.1.2).| |0.1.2|
|
||||||
|powershellGalleryUrl|URL to the module in PowerShell Gallery (e.g. https://www.powershellgallery.com/packages/PSTwitter/).| |null|
|
|powershellGalleryUrl|URL to the module in PowerShell Gallery (e.g. https://www.powershellgallery.com/packages/PSTwitter/).| |null|
|
||||||
|
|useOneOfDiscriminatorLookup|Use the discriminator's mapping in oneOf to speed up the model lookup. IMPORTANT: Validation (e.g. one and onlye one match in oneOf's schemas) will be skipped.| |null|
|
||||||
|
|
||||||
## IMPORT MAPPING
|
## IMPORT MAPPING
|
||||||
|
|
||||||
|
|||||||
@@ -375,4 +375,6 @@ public class CodegenConstants {
|
|||||||
" 2) Boolean values of the 'additionalProperties' keyword are ignored. It's as if additional properties are NOT allowed." +
|
" 2) Boolean values of the 'additionalProperties' keyword are ignored. It's as if additional properties are NOT allowed." +
|
||||||
"Note: the root cause are issues #1369 and #1371, which must be resolved in the swagger-parser project.";
|
"Note: the root cause are issues #1369 and #1371, which must be resolved in the swagger-parser project.";
|
||||||
|
|
||||||
|
public static final String USE_ONEOF_DISCRIMINATOR_LOOKUP = "useOneOfDiscriminatorLookup";
|
||||||
|
public static final String USE_ONEOF_DISCRIMINATOR_LOOKUP_DESC = "Use the discriminator's mapping in oneOf to speed up the model lookup. IMPORTANT: Validation (e.g. one and onlye one match in oneOf's schemas) will be skipped.";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenCo
|
|||||||
protected HashSet powershellVerbs;
|
protected HashSet powershellVerbs;
|
||||||
protected Map<String, String> commonVerbs; // verbs not in the official ps verb list but can be mapped to one of the verbs
|
protected Map<String, String> commonVerbs; // verbs not in the official ps verb list but can be mapped to one of the verbs
|
||||||
protected HashSet methodNames; // store a list of method names to detect duplicates
|
protected HashSet methodNames; // store a list of method names to detect duplicates
|
||||||
|
protected boolean useOneOfDiscriminatorLookup = false; // use oneOf discriminator's mapping for model lookup
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance of `PowerShellClientCodegen`.
|
* Constructs an instance of `PowerShellClientCodegen`.
|
||||||
@@ -498,7 +499,7 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenCo
|
|||||||
cliOptions.add(new CliOption(CodegenConstants.OPTIONAL_PROJECT_GUID, "GUID for PowerShell module (e.g. a27b908d-2a20-467f-bc32-af6f3a654ac5). A random GUID will be generated by default."));
|
cliOptions.add(new CliOption(CodegenConstants.OPTIONAL_PROJECT_GUID, "GUID for PowerShell module (e.g. a27b908d-2a20-467f-bc32-af6f3a654ac5). A random GUID will be generated by default."));
|
||||||
cliOptions.add(new CliOption(CodegenConstants.API_NAME_PREFIX, "Prefix that will be appended to all PS objects. Default: empty string. e.g. Pet => PSPet."));
|
cliOptions.add(new CliOption(CodegenConstants.API_NAME_PREFIX, "Prefix that will be appended to all PS objects. Default: empty string. e.g. Pet => PSPet."));
|
||||||
cliOptions.add(new CliOption("commonVerbs", "PS common verb mappings. e.g. Delete=Remove:Patch=Update to map Delete with Remove and Patch with Update accordingly."));
|
cliOptions.add(new CliOption("commonVerbs", "PS common verb mappings. e.g. Delete=Remove:Patch=Update to map Delete with Remove and Patch with Update accordingly."));
|
||||||
|
cliOptions.add(new CliOption(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP_DESC));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CodegenType getTag() {
|
public CodegenType getTag() {
|
||||||
@@ -535,6 +536,14 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenCo
|
|||||||
this.powershellGalleryUrl = powershellGalleryUrl;
|
this.powershellGalleryUrl = powershellGalleryUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseOneOfDiscriminatorLookup(boolean useOneOfDiscriminatorLookup) {
|
||||||
|
this.useOneOfDiscriminatorLookup = useOneOfDiscriminatorLookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getUseOneOfDiscriminatorLookup() {
|
||||||
|
return this.useOneOfDiscriminatorLookup;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processOpts() {
|
public void processOpts() {
|
||||||
super.processOpts();
|
super.processOpts();
|
||||||
@@ -550,6 +559,12 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenCo
|
|||||||
additionalProperties.put("powershellGalleryUrl", powershellGalleryUrl);
|
additionalProperties.put("powershellGalleryUrl", powershellGalleryUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (additionalProperties.containsKey(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP)) {
|
||||||
|
setUseOneOfDiscriminatorLookup(convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP));
|
||||||
|
} else {
|
||||||
|
additionalProperties.put(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, useOneOfDiscriminatorLookup);
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(powershellGalleryUrl)) {
|
if (StringUtils.isNotBlank(powershellGalleryUrl)) {
|
||||||
// get the last segment of the URL
|
// get the last segment of the URL
|
||||||
// e.g. https://www.powershellgallery.com/packages/PSTwitter => PSTwitter
|
// e.g. https://www.powershellgallery.com/packages/PSTwitter => PSTwitter
|
||||||
|
|||||||
@@ -38,6 +38,37 @@ function ConvertFrom-{{{apiNamePrefix}}}JsonTo{{{classname}}} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{{/isNullable}}
|
{{/isNullable}}
|
||||||
|
{{#useOneOfDiscriminatorLookup}}
|
||||||
|
{{#discriminator}}
|
||||||
|
{{#mappedModels}}
|
||||||
|
{{#-first}}
|
||||||
|
$JsonData = ConvertFrom-Json -InputObject $Json
|
||||||
|
{{/-first}}
|
||||||
|
# check if the discriminator value is '{{{mappingName}}}'
|
||||||
|
if ($JsonData.PSobject.Properties["{{{propertyBaseName}}}"].value == "{{{mappingName}}}") {
|
||||||
|
# try to match {{{modelName}}} defined in the oneOf schemas
|
||||||
|
try {
|
||||||
|
$matchInstance = ConvertFrom-{{{apiNamePrefix}}}JsonTo{{{modelName}}} $Json
|
||||||
|
|
||||||
|
foreach($property in $matchInstance.PsObject.Properties) {
|
||||||
|
if ($null -ne $property.Value) {
|
||||||
|
$matchType = "{{{modelName}}}"
|
||||||
|
return [PSCustomObject]@{
|
||||||
|
"ActualType" = ${matchType}
|
||||||
|
"ActualInstance" = ${matchInstance}
|
||||||
|
"oneOfSchemas" = @({{#oneOf}}"{{{.}}}"{{^-last}}, {{/-last}}{{/oneOf}})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# fail to match the schema defined in oneOf with the discriminator lookup, proceed to the next one
|
||||||
|
Write-Debug "Failed to match '{{{modelName}}}' defined in oneOf ({{{apiNamePrefix}}}{{{classname}}}) using the discriminator lookup ({{{mappingName}}}). Proceeding with the typical oneOf type matching."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/mappedModels}}
|
||||||
|
{{/discriminator}}
|
||||||
|
{{/useOneOfDiscriminatorLookup}}
|
||||||
{{#oneOf}}
|
{{#oneOf}}
|
||||||
# try to match {{{.}}} defined in the oneOf schemas
|
# try to match {{{.}}} defined in the oneOf schemas
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -388,3 +388,140 @@ function Get-PSUrlFromHostSetting {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Sets the configuration for http signing.
|
||||||
|
.DESCRIPTION
|
||||||
|
|
||||||
|
Sets the configuration for the HTTP signature security scheme.
|
||||||
|
The HTTP signature security scheme is used to sign HTTP requests with a key
|
||||||
|
which is in possession of the API client.
|
||||||
|
An 'Authorization' header is calculated by creating a hash of select headers,
|
||||||
|
and optionally the body of the HTTP request, then signing the hash value using
|
||||||
|
a key. The 'Authorization' header is added to outbound HTTP requests.
|
||||||
|
|
||||||
|
Ref: https://openapi-generator.tech
|
||||||
|
|
||||||
|
.PARAMETER KeyId
|
||||||
|
KeyId for HTTP signing
|
||||||
|
|
||||||
|
.PARAMETER KeyFilePath
|
||||||
|
KeyFilePath for HTTP signing
|
||||||
|
|
||||||
|
.PARAMETER KeyPassPhrase
|
||||||
|
KeyPassPhrase, if the HTTP signing key is protected
|
||||||
|
|
||||||
|
.PARAMETER HttpSigningHeader
|
||||||
|
HttpSigningHeader list of HTTP headers used to calculate the signature. The two special signature headers '(request-target)' and '(created)'
|
||||||
|
SHOULD be included.
|
||||||
|
The '(created)' header expresses when the signature was created.
|
||||||
|
The '(request-target)' header is a concatenation of the lowercased :method, an
|
||||||
|
ASCII space, and the :path pseudo-headers.
|
||||||
|
If no headers are specified then '(created)' sets as default.
|
||||||
|
|
||||||
|
.PARAMETER HashAlgorithm
|
||||||
|
HashAlgrithm to calculate the hash, Supported values are "sha256" and "sha512"
|
||||||
|
|
||||||
|
.PARAMETER SigningAlgorithm
|
||||||
|
SigningAlgorithm specifies the signature algorithm, supported values are "RSASSA-PKCS1-v1_5" and "RSASSA-PSS"
|
||||||
|
RSA key : Supported values "RSASSA-PKCS1-v1_5" and "RSASSA-PSS", for ECDSA key this parameter is not applicable
|
||||||
|
|
||||||
|
.PARAMETER SignatureValidityPeriod
|
||||||
|
SignatureValidityPeriod specifies the signature maximum validity time in seconds. It accepts integer value
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
|
||||||
|
System.Collections.Hashtable
|
||||||
|
#>
|
||||||
|
function Set-PSConfigurationHttpSigning {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string]$KeyId,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$KeyFilePath,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[securestring]$KeyPassPhrase,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string[]] $HttpSigningHeader = @("(created)"),
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateSet("sha256", "sha512")]
|
||||||
|
[string] $HashAlgorithm = "sha256",
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateSet("RSASSA-PKCS1-v1_5", "RSASSA-PSS")]
|
||||||
|
[string]$SigningAlgorithm ,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[int]$SignatureValidityPeriod
|
||||||
|
)
|
||||||
|
|
||||||
|
Process {
|
||||||
|
$httpSignatureConfiguration = @{ }
|
||||||
|
|
||||||
|
if (Test-Path -Path $KeyFilePath) {
|
||||||
|
$httpSignatureConfiguration["KeyId"] = $KeyId
|
||||||
|
$httpSignatureConfiguration["KeyFilePath"] = $KeyFilePath
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw "Private key file path does not exist"
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyType = Get-PSKeyTypeFromFile -KeyFilePath $KeyFilePath
|
||||||
|
if ([String]::IsNullOrEmpty($SigningAlgorithm)) {
|
||||||
|
if ($keyType -eq "RSA") {
|
||||||
|
$SigningAlgorithm = "RSASSA-PKCS1-v1_5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($keyType -eq "RSA" -and
|
||||||
|
($SigningAlgorithm -ne "RSASSA-PKCS1-v1_5" -and $SigningAlgorithm -ne "RSASSA-PSS" )) {
|
||||||
|
throw "Provided Key and SigningAlgorithm : $SigningAlgorithm is not compatible."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($HttpSigningHeader -contains "(expires)" -and $SignatureValidityPeriod -le 0) {
|
||||||
|
throw "SignatureValidityPeriod must be greater than 0 seconds."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($HttpSigningHeader -contains "(expires)") {
|
||||||
|
$httpSignatureConfiguration["SignatureValidityPeriod"] = $SignatureValidityPeriod
|
||||||
|
}
|
||||||
|
if ($null -ne $HttpSigningHeader -and $HttpSigningHeader.Length -gt 0) {
|
||||||
|
$httpSignatureConfiguration["HttpSigningHeader"] = $HttpSigningHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -ne $HashAlgorithm ) {
|
||||||
|
$httpSignatureConfiguration["HashAlgorithm"] = $HashAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -ne $SigningAlgorithm) {
|
||||||
|
$httpSignatureConfiguration["SigningAlgorithm"] = $SigningAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -ne $KeyPassPhrase) {
|
||||||
|
$httpSignatureConfiguration["KeyPassPhrase"] = $KeyPassPhrase
|
||||||
|
}
|
||||||
|
|
||||||
|
$Script:Configuration["HttpSigning"] = New-Object -TypeName PSCustomObject -Property $httpSignatureConfiguration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
|
||||||
|
Get the configuration object 'PSConfigurationHttpSigning'.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
|
||||||
|
Get the configuration object 'PSConfigurationHttpSigning'.
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
|
||||||
|
[PSCustomObject]
|
||||||
|
#>
|
||||||
|
function Get-PSConfigurationHttpSigning{
|
||||||
|
|
||||||
|
$httpSignatureConfiguration = $Script:Configuration["HttpSigning"]
|
||||||
|
return $httpSignatureConfiguration
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,64 +7,11 @@
|
|||||||
|
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Get the API key Id and API key file path.
|
Gets the headers for HTTP signature.
|
||||||
|
|
||||||
.DESCRIPTION
|
.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'
|
Gets the headers for the http sigature.
|
||||||
.OUTPUTS
|
|
||||||
PSCustomObject : This contains APIKeyId, APIKeyFilePath, APIKeyPrefix
|
|
||||||
#>
|
|
||||||
function Get-PSAPIKeyInfo {
|
|
||||||
$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.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Gets the headers for the http signed auth. It use (targetpath), date, host and body digest to create authorization header.
|
|
||||||
.PARAMETER Method
|
.PARAMETER Method
|
||||||
Http method
|
HTTP method
|
||||||
.PARAMETER UriBuilder
|
.PARAMETER UriBuilder
|
||||||
UriBuilder for url and query parameter
|
UriBuilder for url and query parameter
|
||||||
.PARAMETER Body
|
.PARAMETER Body
|
||||||
@@ -76,93 +23,393 @@ function Get-PSHttpSignedHeader {
|
|||||||
param(
|
param(
|
||||||
[string]$Method,
|
[string]$Method,
|
||||||
[System.UriBuilder]$UriBuilder,
|
[System.UriBuilder]$UriBuilder,
|
||||||
[string]$Body
|
[string]$Body,
|
||||||
|
[hashtable]$RequestHeader
|
||||||
)
|
)
|
||||||
|
|
||||||
#Hash table to store singed headers
|
$HEADER_REQUEST_TARGET = '(request-target)'
|
||||||
$HttpSignedHeader = @{}
|
# The time when the HTTP signature was generated.
|
||||||
$TargetHost = $UriBuilder.Host
|
$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'
|
||||||
|
|
||||||
#Check for Authentication type
|
#Hash table to store singed headers
|
||||||
$apiKeyInfo = Get-PSAPIKeyInfo
|
$HttpSignedRequestHeader = @{ }
|
||||||
if ($null -eq $apiKeyInfo) {
|
$HttpSignatureHeader = @{ }
|
||||||
throw "Unable to reterieve the api key info "
|
$TargetHost = $UriBuilder.Host
|
||||||
}
|
$httpSigningConfiguration = Get-PSConfigurationHttpSigning
|
||||||
|
$Digest = $null
|
||||||
|
|
||||||
#get the body digest
|
#get the body digest
|
||||||
$bodyHash = Get-PSStringHash -String $Body
|
$bodyHash = Get-PSStringHash -String $Body -HashName $httpSigningConfiguration.HashAlgorithm
|
||||||
|
if ($httpSigningConfiguration.HashAlgorithm -eq "SHA256") {
|
||||||
$Digest = [String]::Format("SHA-256={0}", [Convert]::ToBase64String($bodyHash))
|
$Digest = [String]::Format("SHA-256={0}", [Convert]::ToBase64String($bodyHash))
|
||||||
|
}
|
||||||
|
elseif ($httpSigningConfiguration.HashAlgorithm -eq "SHA512") {
|
||||||
|
$Digest = [String]::Format("SHA-512={0}", [Convert]::ToBase64String($bodyHash))
|
||||||
|
}
|
||||||
|
|
||||||
#get the date in UTC
|
|
||||||
$dateTime = Get-Date
|
$dateTime = Get-Date
|
||||||
|
#get the date in UTC
|
||||||
$currentDate = $dateTime.ToUniversalTime().ToString("r")
|
$currentDate = $dateTime.ToUniversalTime().ToString("r")
|
||||||
|
|
||||||
$requestTargetPath = [string]::Format("{0} {1}{2}",$Method.ToLower(),$UriBuilder.Path.ToLower(),$UriBuilder.Query)
|
foreach ($headerItem in $httpSigningConfiguration.HttpSigningHeader) {
|
||||||
$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}",
|
if ($headerItem -eq $HEADER_REQUEST_TARGET) {
|
||||||
$h_requestTarget,$h_cdate,
|
$requestTargetPath = [string]::Format("{0} {1}{2}", $Method.ToLower(), $UriBuilder.Path, $UriBuilder.Query)
|
||||||
$h_targetHost,$h_digest)
|
$HttpSignatureHeader.Add($HEADER_REQUEST_TARGET, $requestTargetPath)
|
||||||
|
}
|
||||||
|
elseif ($headerItem -eq $HEADER_CREATED) {
|
||||||
|
$created = Get-PSUnixTime -Date $dateTime -TotalTime TotalSeconds
|
||||||
|
$HttpSignatureHeader.Add($HEADER_CREATED, $created)
|
||||||
|
}
|
||||||
|
elseif ($headerItem -eq $HEADER_EXPIRES) {
|
||||||
|
$expire = $dateTime.AddSeconds($httpSigningConfiguration.SignatureValidityPeriod)
|
||||||
|
$expireEpocTime = Get-PSUnixTime -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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$hashedString = Get-PSStringHash -String $stringToSign
|
# header's name separated by space
|
||||||
$signedHeader = Get-PSRSASHA256SignedString -APIKeyFilePath $apiKeyInfo.ApiKeyFilePath -DataToSign $hashedString
|
$headersKeysString = $HttpSignatureHeader.Keys -join " "
|
||||||
$authorizationHeader = [string]::Format("{0} keyId=""{1}"",algorithm=""rsa-sha256"",headers=""(request-target) date host digest"",signature=""{2}""",
|
$headerValuesList = @()
|
||||||
$apiKeyInfo.ApiKeyPrefix, $apiKeyInfo.ApiKeyId, $signedHeader)
|
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"
|
||||||
|
|
||||||
$HttpSignedHeader["Date"] = $currentDate
|
#Gets the hash of the headers value
|
||||||
$HttpSignedHeader["Host"] = $TargetHost
|
$signatureHashString = Get-PSStringHash -String $headerValuesString -HashName $httpSigningConfiguration.HashAlgorithm
|
||||||
$HttpSignedHeader["Content-Type"] = "application/json"
|
|
||||||
$HttpSignedHeader["Digest"] = $Digest
|
#Gets the Key type to select the correct signing alogorithm
|
||||||
$HttpSignedHeader["Authorization"] = $authorizationHeader
|
$KeyType = Get-PSKeyTypeFromFile -KeyFilePath $httpSigningConfiguration.KeyFilePath
|
||||||
return $HttpSignedHeader
|
|
||||||
|
if ($keyType -eq "RSA") {
|
||||||
|
$headerSignatureStr = Get-PSRSASignature -PrivateKeyFilePath $httpSigningConfiguration.KeyFilePath `
|
||||||
|
-DataToSign $signatureHashString `
|
||||||
|
-HashAlgorithmName $httpSigningConfiguration.HashAlgorithm `
|
||||||
|
-KeyPassPhrase $httpSigningConfiguration.KeyPassPhrase `
|
||||||
|
-SigningAlgorithm $httpSigningConfiguration.SigningAlgorithm
|
||||||
|
}
|
||||||
|
elseif ($KeyType -eq "EC") {
|
||||||
|
$headerSignatureStr = Get-PSECDSASignature -ECKeyFilePath $httpSigningConfiguration.KeyFilePath `
|
||||||
|
-DataToSign $signatureHashString `
|
||||||
|
-HashAlgorithmName $httpSigningConfiguration.HashAlgorithm `
|
||||||
|
-KeyPassPhrase $httpSigningConfiguration.KeyPassPhrase
|
||||||
|
}
|
||||||
|
#Depricated
|
||||||
|
<#$cryptographicScheme = Get-PSCryptographicScheme -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
|
.SYNOPSIS
|
||||||
Gets the headers for http signed auth.
|
Gets the RSA signature
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Gets the headers for the http signed auth. It use (targetpath), date, host and body digest to create authorization header.
|
Gets the RSA signature for the http signing
|
||||||
.PARAMETER APIKeyFilePath
|
.PARAMETER PrivateKeyFilePath
|
||||||
Specify the API key file path
|
Specify the API key file path
|
||||||
.PARAMETER DataToSign
|
.PARAMETER DataToSign
|
||||||
Specify the data to sign
|
Specify the data to sign
|
||||||
|
.PARAMETER HashAlgorithmName
|
||||||
|
HashAlgorithm to calculate the hash
|
||||||
|
.PARAMETER KeyPassPhrase
|
||||||
|
KeyPassPhrase for the encrypted key
|
||||||
.OUTPUTS
|
.OUTPUTS
|
||||||
String
|
Base64String
|
||||||
#>
|
#>
|
||||||
function Get-PSRSASHA256SignedString {
|
function Get-PSRSASignature {
|
||||||
Param(
|
Param(
|
||||||
[string]$APIKeyFilePath,
|
[string]$PrivateKeyFilePath,
|
||||||
[byte[]]$DataToSign
|
[byte[]]$DataToSign,
|
||||||
|
[string]$HashAlgorithmName,
|
||||||
|
[string]$SigningAlgorithm,
|
||||||
|
[securestring]$KeyPassPhrase
|
||||||
)
|
)
|
||||||
try {
|
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.RSACng]::new()
|
||||||
|
[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 "PSRSAEncryptionProvider.cs"
|
$rsa_provider_path = Join-Path -Path $PSScriptRoot -ChildPath "PSRSAEncryptionProvider.cs"
|
||||||
$rsa_provider_sourceCode = Get-Content -Path $rsa_provider_path -Raw
|
$rsa_provider_sourceCode = Get-Content -Path $rsa_provider_path -Raw
|
||||||
Add-Type -TypeDefinition $rsa_provider_sourceCode
|
Add-Type -TypeDefinition $rsa_provider_sourceCode
|
||||||
$signed_string = [RSAEncryption.RSAEncryptionProvider]::GetRSASignb64encode($APIKeyFilePath, $DataToSign)
|
|
||||||
if ($null -eq $signed_string) {
|
[System.Security.Cryptography.RSA]$rsa = [RSAEncryption.RSAEncryptionProvider]::GetRSAProviderFromPemFile($PrivateKeyFilePath, $KeyPassPhrase)
|
||||||
throw "Unable to sign the header using the API key"
|
|
||||||
|
if ($SigningAlgorithm -eq "RSASSA-PSS") {
|
||||||
|
throw "$SigningAlgorithm is not supported on $($PSVersionTable.PSVersion)"
|
||||||
}
|
}
|
||||||
return $signed_string
|
else {
|
||||||
|
$signedBytes = $rsa.SignHash($DataToSign, $hashAlgo, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$signedString = [Convert]::ToBase64String($signedBytes)
|
||||||
|
return $signedString
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
throw $_
|
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-PSECDSASignature {
|
||||||
|
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 $($PSVersionTable.PSVersion), Use PSVersion 7.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)
|
||||||
|
|
||||||
|
#$cngKey = [System.Security.Cryptography.CngKey]::Import($keyBytes,[System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)
|
||||||
|
#$ecdsa = [System.Security.Cryptography.ECDsaCng]::New($cngKey)
|
||||||
|
$ecdsa = [System.Security.Cryptography.ECDsaCng]::New()
|
||||||
|
[int]$bytCount =0
|
||||||
|
if(![string]::IsNullOrEmpty($KeyPassPhrase)){
|
||||||
|
$ecdsa.ImportEncryptedPkcs8PrivateKey($KeyPassPhrase,$keyBytes,[ref]$bytCount)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$ecdsa.ImportPkcs8PrivateKey($keyBytes,[ref]$bytCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($HashAlgorithmName -eq "sha512") {
|
||||||
|
$ecdsa.HashAlgorithm = [System.Security.Cryptography.CngAlgorithm]::Sha512
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ecdsa.HashAlgorithm = [System.Security.Cryptography.CngAlgorithm]::Sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
$signedBytes = $ecdsa.SignHash($DataToSign)
|
||||||
|
$signedString = [System.Convert]::ToBase64String($signedBytes)
|
||||||
|
return $signedString
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
<#
|
<#
|
||||||
.Synopsis
|
.Synopsis
|
||||||
Gets the hash of string.
|
Gets the hash of string.
|
||||||
.Description
|
.Description
|
||||||
Gets the hash of string
|
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
|
.Outputs
|
||||||
String
|
String
|
||||||
#>
|
#>
|
||||||
Function Get-PSStringHash([String] $String, $HashName = "SHA256") {
|
Function Get-PSStringHash {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[AllowEmptyString()]
|
||||||
|
[string]$String,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateSet("SHA1", "SHA256", "SHA512")]
|
||||||
|
$HashName
|
||||||
|
)
|
||||||
$hashAlogrithm = [System.Security.Cryptography.HashAlgorithm]::Create($HashName)
|
$hashAlogrithm = [System.Security.Cryptography.HashAlgorithm]::Create($HashName)
|
||||||
$hashAlogrithm.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))
|
$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-PSUnixTime {
|
||||||
|
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-PSCryptographicScheme {
|
||||||
|
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-PSKeyTypeFromFile {
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
@@ -10,15 +11,97 @@ namespace RSAEncryption
|
|||||||
{
|
{
|
||||||
public class RSAEncryptionProvider
|
public class RSAEncryptionProvider
|
||||||
{
|
{
|
||||||
|
public static RSACryptoServiceProvider GetRSAProviderFromPemFile(String pemfile,SecureString keyPassPharse = null)
|
||||||
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
|
{
|
||||||
const String pemprivfooter = "-----END RSA PRIVATE KEY-----";
|
|
||||||
const String pempubheader = "-----BEGIN PUBLIC KEY-----";
|
const String pempubheader = "-----BEGIN PUBLIC KEY-----";
|
||||||
const String pempubfooter = "-----END PUBLIC KEY-----";
|
const String pempubfooter = "-----END PUBLIC KEY-----";
|
||||||
const String pemp8header = "-----BEGIN PRIVATE KEY-----";
|
bool isPrivateKeyFile = true;
|
||||||
const String pemp8footer = "-----END PRIVATE KEY-----";
|
byte[] pemkey = null;
|
||||||
const String pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
|
|
||||||
const String pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----";
|
if (!File.Exists(pemfile))
|
||||||
|
{
|
||||||
|
throw new Exception("private key file does not exist.");
|
||||||
|
}
|
||||||
|
string pemstr = File.ReadAllText(pemfile).Trim();
|
||||||
|
|
||||||
|
if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
|
||||||
|
{
|
||||||
|
isPrivateKeyFile = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrivateKeyFile)
|
||||||
|
{
|
||||||
|
pemkey = ConvertPrivateKeyToBytes(pemstr,keyPassPharse);
|
||||||
|
if (pemkey == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return DecodeRSAPrivateKey(pemkey);
|
||||||
|
}
|
||||||
|
return null ;
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] ConvertPrivateKeyToBytes(String instr, SecureString keyPassPharse = null)
|
||||||
|
{
|
||||||
|
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
|
||||||
|
const String pemprivfooter = "-----END RSA PRIVATE KEY-----";
|
||||||
|
String pemstr = instr.Trim();
|
||||||
|
byte[] binkey;
|
||||||
|
|
||||||
|
if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(pemstr);
|
||||||
|
sb.Replace(pemprivheader, "");
|
||||||
|
sb.Replace(pemprivfooter, "");
|
||||||
|
String pvkstr = sb.ToString().Trim();
|
||||||
|
|
||||||
|
try
|
||||||
|
{ // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
|
||||||
|
binkey = Convert.FromBase64String(pvkstr);
|
||||||
|
return binkey;
|
||||||
|
}
|
||||||
|
catch (System.FormatException)
|
||||||
|
{
|
||||||
|
StringReader str = new StringReader(pvkstr);
|
||||||
|
|
||||||
|
//-------- read PEM encryption info. lines and extract salt -----
|
||||||
|
if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
|
||||||
|
return null;
|
||||||
|
String saltline = str.ReadLine();
|
||||||
|
if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
|
||||||
|
return null;
|
||||||
|
String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
|
||||||
|
byte[] salt = new byte[saltstr.Length / 2];
|
||||||
|
for (int i = 0; i < salt.Length; i++)
|
||||||
|
salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);
|
||||||
|
if (!(str.ReadLine() == ""))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
//------ remaining b64 data is encrypted RSA key ----
|
||||||
|
String encryptedstr = str.ReadToEnd();
|
||||||
|
|
||||||
|
try
|
||||||
|
{ //should have b64 encrypted RSA key now
|
||||||
|
binkey = Convert.FromBase64String(encryptedstr);
|
||||||
|
}
|
||||||
|
catch (System.FormatException)
|
||||||
|
{ //data is not in base64 fromat
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] deskey = GetEncryptedKey(salt, keyPassPharse, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
|
||||||
|
if (deskey == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
//------ Decrypt the encrypted 3des-encrypted RSA private key ------
|
||||||
|
byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
|
||||||
|
return rsakey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
|
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
|
||||||
{
|
{
|
||||||
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
|
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
|
||||||
@@ -46,7 +129,6 @@ namespace RSAEncryption
|
|||||||
if (bt != 0x00)
|
if (bt != 0x00)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
||||||
//------ all private key components are Integer sequences ----
|
//------ all private key components are Integer sequences ----
|
||||||
elems = GetIntegerSize(binr);
|
elems = GetIntegerSize(binr);
|
||||||
MODULUS = binr.ReadBytes(elems);
|
MODULUS = binr.ReadBytes(elems);
|
||||||
@@ -72,19 +154,6 @@ namespace RSAEncryption
|
|||||||
elems = GetIntegerSize(binr);
|
elems = GetIntegerSize(binr);
|
||||||
IQ = binr.ReadBytes(elems);
|
IQ = binr.ReadBytes(elems);
|
||||||
|
|
||||||
/*Console.WriteLine("showing components ..");
|
|
||||||
if (true)
|
|
||||||
{
|
|
||||||
showBytes("\nModulus", MODULUS);
|
|
||||||
showBytes("\nExponent", E);
|
|
||||||
showBytes("\nD", D);
|
|
||||||
showBytes("\nP", P);
|
|
||||||
showBytes("\nQ", Q);
|
|
||||||
showBytes("\nDP", DP);
|
|
||||||
showBytes("\nDQ", DQ);
|
|
||||||
showBytes("\nIQ", IQ);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
|
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
|
||||||
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
|
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
|
||||||
RSAParameters RSAparams = new RSAParameters();
|
RSAParameters RSAparams = new RSAParameters();
|
||||||
@@ -140,94 +209,17 @@ namespace RSAEncryption
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] DecodeOpenSSLPrivateKey(String instr)
|
static byte[] GetEncryptedKey(byte[] salt, SecureString secpswd, int count, int miter)
|
||||||
{
|
|
||||||
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
|
|
||||||
const String pemprivfooter = "-----END RSA PRIVATE KEY-----";
|
|
||||||
String pemstr = instr.Trim();
|
|
||||||
byte[] binkey;
|
|
||||||
if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder(pemstr);
|
|
||||||
sb.Replace(pemprivheader, ""); //remove headers/footers, if present
|
|
||||||
sb.Replace(pemprivfooter, "");
|
|
||||||
|
|
||||||
String pvkstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
|
|
||||||
|
|
||||||
try
|
|
||||||
{ // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
|
|
||||||
binkey = Convert.FromBase64String(pvkstr);
|
|
||||||
return binkey;
|
|
||||||
}
|
|
||||||
catch (System.FormatException)
|
|
||||||
{ //if can't b64 decode, it must be an encrypted private key
|
|
||||||
//Console.WriteLine("Not an unencrypted OpenSSL PEM private key");
|
|
||||||
}
|
|
||||||
|
|
||||||
StringReader str = new StringReader(pvkstr);
|
|
||||||
|
|
||||||
//-------- read PEM encryption info. lines and extract salt -----
|
|
||||||
if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
|
|
||||||
return null;
|
|
||||||
String saltline = str.ReadLine();
|
|
||||||
if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
|
|
||||||
return null;
|
|
||||||
String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
|
|
||||||
byte[] salt = new byte[saltstr.Length / 2];
|
|
||||||
for (int i = 0; i < salt.Length; i++)
|
|
||||||
salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);
|
|
||||||
if (!(str.ReadLine() == ""))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
//------ remaining b64 data is encrypted RSA key ----
|
|
||||||
String encryptedstr = str.ReadToEnd();
|
|
||||||
|
|
||||||
try
|
|
||||||
{ //should have b64 encrypted RSA key now
|
|
||||||
binkey = Convert.FromBase64String(encryptedstr);
|
|
||||||
}
|
|
||||||
catch (System.FormatException)
|
|
||||||
{ // bad b64 data.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//------ Get the 3DES 24 byte key using PDK used by OpenSSL ----
|
|
||||||
|
|
||||||
SecureString despswd = GetSecPswd("Enter password to derive 3DES key==>");
|
|
||||||
//Console.Write("\nEnter password to derive 3DES key: ");
|
|
||||||
//String pswd = Console.ReadLine();
|
|
||||||
byte[] deskey = GetOpenSSL3deskey(salt, despswd, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
|
|
||||||
if (deskey == null)
|
|
||||||
return null;
|
|
||||||
//showBytes("3DES key", deskey) ;
|
|
||||||
|
|
||||||
//------ Decrypt the encrypted 3des-encrypted RSA private key ------
|
|
||||||
byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
|
|
||||||
if (rsakey != null)
|
|
||||||
return rsakey; //we have a decrypted RSA private key
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("Failed to decrypt RSA private key; probably wrong password.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static byte[] GetOpenSSL3deskey(byte[] salt, SecureString secpswd, int count, int miter)
|
|
||||||
{
|
{
|
||||||
IntPtr unmanagedPswd = IntPtr.Zero;
|
IntPtr unmanagedPswd = IntPtr.Zero;
|
||||||
int HASHLENGTH = 16; //MD5 bytes
|
int HASHLENGTH = 16; //MD5 bytes
|
||||||
byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store contatenated Mi hashed results
|
byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store contatenated Mi hashed results
|
||||||
|
|
||||||
|
|
||||||
byte[] psbytes = new byte[secpswd.Length];
|
byte[] psbytes = new byte[secpswd.Length];
|
||||||
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
|
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
|
||||||
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
|
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
|
||||||
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
|
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
|
||||||
|
|
||||||
//UTF8Encoding utf8 = new UTF8Encoding();
|
|
||||||
//byte[] psbytes = utf8.GetBytes(pswd);
|
|
||||||
|
|
||||||
// --- contatenate salt and pswd bytes into fixed data array ---
|
// --- contatenate salt and pswd bytes into fixed data array ---
|
||||||
byte[] data00 = new byte[psbytes.Length + salt.Length];
|
byte[] data00 = new byte[psbytes.Length + salt.Length];
|
||||||
Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
|
Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
|
||||||
@@ -248,15 +240,12 @@ namespace RSAEncryption
|
|||||||
Array.Copy(result, hashtarget, result.Length);
|
Array.Copy(result, hashtarget, result.Length);
|
||||||
Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
|
Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
|
||||||
result = hashtarget;
|
result = hashtarget;
|
||||||
//Console.WriteLine("Updated new initial hash target:") ;
|
|
||||||
//showBytes(result) ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
result = md5.ComputeHash(result);
|
result = md5.ComputeHash(result);
|
||||||
Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //contatenate to keymaterial
|
Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //contatenate to keymaterial
|
||||||
}
|
}
|
||||||
//showBytes("Final key material", keymaterial);
|
|
||||||
byte[] deskey = new byte[24];
|
byte[] deskey = new byte[24];
|
||||||
Array.Copy(keymaterial, deskey, deskey.Length);
|
Array.Copy(keymaterial, deskey, deskey.Length);
|
||||||
|
|
||||||
@@ -265,46 +254,9 @@ namespace RSAEncryption
|
|||||||
Array.Clear(result, 0, result.Length);
|
Array.Clear(result, 0, result.Length);
|
||||||
Array.Clear(hashtarget, 0, hashtarget.Length);
|
Array.Clear(hashtarget, 0, hashtarget.Length);
|
||||||
Array.Clear(keymaterial, 0, keymaterial.Length);
|
Array.Clear(keymaterial, 0, keymaterial.Length);
|
||||||
|
|
||||||
return deskey;
|
return deskey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetRSASignb64encode(string private_key_path, byte[] digest)
|
|
||||||
{
|
|
||||||
RSACryptoServiceProvider cipher = new RSACryptoServiceProvider();
|
|
||||||
cipher = GetRSAProviderFromPemFile(private_key_path);
|
|
||||||
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(cipher);
|
|
||||||
RSAFormatter.SetHashAlgorithm("SHA256");
|
|
||||||
byte[] signedHash = RSAFormatter.CreateSignature(digest);
|
|
||||||
return Convert.ToBase64String(signedHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RSACryptoServiceProvider GetRSAProviderFromPemFile(String pemfile)
|
|
||||||
{
|
|
||||||
bool isPrivateKeyFile = true;
|
|
||||||
if (!File.Exists(pemfile))
|
|
||||||
{
|
|
||||||
throw new Exception("pemfile does not exist.");
|
|
||||||
}
|
|
||||||
string pemstr = File.ReadAllText(pemfile).Trim();
|
|
||||||
if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
|
|
||||||
isPrivateKeyFile = false;
|
|
||||||
|
|
||||||
byte[] pemkey = null;
|
|
||||||
if (isPrivateKeyFile)
|
|
||||||
pemkey = DecodeOpenSSLPrivateKey(pemstr);
|
|
||||||
|
|
||||||
|
|
||||||
if (pemkey == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (isPrivateKeyFile)
|
|
||||||
{
|
|
||||||
return DecodeRSAPrivateKey(pemkey);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
|
static byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
|
||||||
{
|
{
|
||||||
MemoryStream memst = new MemoryStream();
|
MemoryStream memst = new MemoryStream();
|
||||||
@@ -317,61 +269,11 @@ namespace RSAEncryption
|
|||||||
cs.Write(cipherData, 0, cipherData.Length);
|
cs.Write(cipherData, 0, cipherData.Length);
|
||||||
cs.Close();
|
cs.Close();
|
||||||
}
|
}
|
||||||
catch (Exception exc)
|
catch (Exception){
|
||||||
{
|
|
||||||
Console.WriteLine(exc.Message);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
byte[] decryptedData = memst.ToArray();
|
byte[] decryptedData = memst.ToArray();
|
||||||
return decryptedData;
|
return decryptedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SecureString GetSecPswd(String prompt)
|
|
||||||
{
|
|
||||||
SecureString password = new SecureString();
|
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Gray;
|
|
||||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
ConsoleKeyInfo cki = Console.ReadKey(true);
|
|
||||||
if (cki.Key == ConsoleKey.Enter)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Gray;
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
else if (cki.Key == ConsoleKey.Backspace)
|
|
||||||
{
|
|
||||||
// remove the last asterisk from the screen...
|
|
||||||
if (password.Length > 0)
|
|
||||||
{
|
|
||||||
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
|
|
||||||
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
|
|
||||||
password.RemoveAt(password.Length - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (cki.Key == ConsoleKey.Escape)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Gray;
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
else if (Char.IsLetterOrDigit(cki.KeyChar) || Char.IsSymbol(cki.KeyChar))
|
|
||||||
{
|
|
||||||
if (password.Length < 20)
|
|
||||||
{
|
|
||||||
password.AppendChar(cki.KeyChar);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.Beep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.Beep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user