[csharp-netcore]Http signing for csharp-netcore sdk (#7437)

* HTTPSigning implementation for CSharpSDK

* Updated the sample for HTTPSigning

* Updated the sample2 for HTTPSigning

* update samples

* HTTPSigning_For_Async

* update samples

Co-authored-by: William Cheng <wing328hk@gmail.com>
This commit is contained in:
Ghufz 2020-09-19 09:39:35 +05:30 committed by GitHub
parent 3f7a3f2b59
commit 92f7a306a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2380 additions and 3 deletions

View File

@ -610,6 +610,7 @@ public class CSharpNetCoreClientCodegen extends AbstractCSharpCodegen {
supportingFiles.add(new SupportingFile("OpenAPIDateConverter.mustache", clientPackageDir, "OpenAPIDateConverter.cs")); supportingFiles.add(new SupportingFile("OpenAPIDateConverter.mustache", clientPackageDir, "OpenAPIDateConverter.cs"));
supportingFiles.add(new SupportingFile("ClientUtils.mustache", clientPackageDir, "ClientUtils.cs")); supportingFiles.add(new SupportingFile("ClientUtils.mustache", clientPackageDir, "ClientUtils.cs"));
supportingFiles.add(new SupportingFile("HttpMethod.mustache", clientPackageDir, "HttpMethod.cs")); supportingFiles.add(new SupportingFile("HttpMethod.mustache", clientPackageDir, "HttpMethod.cs"));
supportingFiles.add(new SupportingFile("HTTPSigningConfiguration.mustache",clientPackageDir,"HTTPSigningConfiguration.cs"));
if (supportsAsync) { if (supportsAsync) {
supportingFiles.add(new SupportingFile("IAsynchronousClient.mustache", clientPackageDir, "IAsynchronousClient.cs")); supportingFiles.add(new SupportingFile("IAsynchronousClient.mustache", clientPackageDir, "IAsynchronousClient.cs"));
} }

View File

@ -83,6 +83,10 @@ namespace {{packageName}}.Client
private string _dateTimeFormat = ISO8601_DATETIME_FORMAT; private string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
private string _tempFolderPath = Path.GetTempPath(); private string _tempFolderPath = Path.GetTempPath();
/// <summary>
/// HTTPSigning configuration
/// </summary>
private HTTPSigningConfiguration _HTTPSigningConfiguration = null;
#endregion Private Members #endregion Private Members
#region Constructors #region Constructors
@ -334,6 +338,15 @@ namespace {{packageName}}.Client
} }
} }
/// <summary>
/// Gets and Sets the HTTPSigningConfiuration
/// </summary>
public HTTPSigningConfiguration HTTPSigningConfiguration
{
get { return _HTTPSigningConfiguration; }
set { _HTTPSigningConfiguration = value; }
}
#endregion Properties #endregion Properties
#region Methods #region Methods
@ -411,7 +424,8 @@ namespace {{packageName}}.Client
Password = second.Password ?? first.Password, Password = second.Password ?? first.Password,
AccessToken = second.AccessToken ?? first.AccessToken, AccessToken = second.AccessToken ?? first.AccessToken,
TempFolderPath = second.TempFolderPath ?? first.TempFolderPath, TempFolderPath = second.TempFolderPath ?? first.TempFolderPath,
DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat,
HTTPSigningConfiguration = second.HTTPSigningConfiguration ?? first.HTTPSigningConfiguration
}; };
return config; return config;
} }

View File

@ -0,0 +1,711 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace {{packageName}}.Client
{
/// <summary>
/// Class for HTTPSigning auth related parameter and methods
/// </summary>
public class HTTPSigningConfiguration
{
#region
/// <summary>
/// Initailize the HashAlgorithm and SigningAlgorithm to default value
/// </summary>
public HTTPSigningConfiguration()
{
HashAlgorithm = HashAlgorithmName.SHA256;
SigningAlgorithm = "PKCS1-v15";
}
#endregion
#region Properties
/// <summary>
///Gets the Api keyId
/// </summary>
public string KeyId { get; set; }
/// <summary>
/// Gets the Key file path
/// </summary>
public string KeyFilePath { get; set; }
/// <summary>
/// Gets the key pass phrase for password protected key
/// </summary>
public SecureString KeyPassPhrase { get; set; }
/// <summary>
/// Gets the HTTP signing header
/// </summary>
public List<string> HTTPSigningHeader { get; set; }
/// <summary>
/// Gets the hash algorithm sha256 or sha512
/// </summary>
public HashAlgorithmName HashAlgorithm { get; set; }
/// <summary>
/// Gets the signing algorithm
/// </summary>
public string SigningAlgorithm { get; set; }
/// <summary>
/// Gets the Signature validaty period in seconds
/// </summary>
public int SignatureValidityPeriod { get; set; }
#endregion
#region enum
private enum PrivateKeyType
{
None = 0,
RSA = 1,
ECDSA = 2,
}
#endregion
#region Methods
/// <summary>
/// Gets the Headers for HTTpSIgning
/// </summary>
/// <param name="method"></param>
/// <param name="path"></param>
/// <param name="requestOptions"></param>
/// <returns></returns>
internal Dictionary<string, string> GetHttpSignedHeader(string basePath,string method, string path, RequestOptions requestOptions)
{
const string HEADER_REQUEST_TARGET = "(request-target)";
//The time when the HTTP signature expires. The API server should reject HTTP requests
//that have expired.
const string HEADER_EXPIRES = "(expires)";
//The 'Date' header.
const string HEADER_DATE = "Date";
//The 'Host' header.
const string HEADER_HOST = "Host";
//The time when the HTTP signature was generated.
const string HEADER_CREATED = "(created)";
//When the 'Digest' header is included in the HTTP signature, the client automatically
//computes the digest of the HTTP request body, per RFC 3230.
const string HEADER_DIGEST = "Digest";
//The 'Authorization' header is automatically generated by the client. It includes
//the list of signed headers and a base64-encoded signature.
const string HEADER_AUTHORIZATION = "Authorization";
//Hash table to store singed headers
var httpSignedRequestHeader = new Dictionary<string, string>();
var httpSignatureHeader = new Dictionary<string, string>();
if (HTTPSigningHeader.Count == 0)
{
HTTPSigningHeader.Add("(created)");
}
if (requestOptions.PathParameters != null)
{
foreach (var pathParam in requestOptions.PathParameters)
{
var tempPath = path.Replace(pathParam.Key, "0");
path = string.Format(tempPath, pathParam.Value);
}
}
var httpValues = HttpUtility.ParseQueryString(String.Empty);
foreach (var parameter in requestOptions.QueryParameters)
{
if (parameter.Value.Count > 1)
{ // array
foreach (var value in parameter.Value)
{
httpValues.Add(parameter.Key + "[]", value);
}
}
else
{
httpValues.Add(parameter.Key, parameter.Value[0]);
}
}
var uriBuilder = new UriBuilder(string.Concat(basePath, path));
uriBuilder.Query = httpValues.ToString();
var dateTime = DateTime.Now;
String Digest = String.Empty;
//get the body
string requestBody = string.Empty;
if (requestOptions.Data != null)
{
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
requestBody = JsonConvert.SerializeObject(requestOptions.Data, serializerSettings);
}
if (HashAlgorithm == HashAlgorithmName.SHA256)
{
var bodyDigest = GetStringHash(HashAlgorithm.ToString(), requestBody);
Digest = string.Format("SHA-256={0}", Convert.ToBase64String(bodyDigest));
}
else if (HashAlgorithm == HashAlgorithmName.SHA512)
{
var bodyDigest = GetStringHash(HashAlgorithm.ToString(), requestBody);
Digest = string.Format("SHA-512={0}", Convert.ToBase64String(bodyDigest));
}
else
{
throw new Exception(string.Format("{0} not supported", HashAlgorithm));
}
foreach (var header in HTTPSigningHeader)
{
if (header.Equals(HEADER_REQUEST_TARGET))
{
var targetUrl = string.Format("{0} {1}{2}", method.ToLower(), uriBuilder.Path, uriBuilder.Query);
httpSignatureHeader.Add(header.ToLower(), targetUrl);
}
else if (header.Equals(HEADER_EXPIRES))
{
var expireDateTime = dateTime.AddSeconds(SignatureValidityPeriod);
httpSignatureHeader.Add(header.ToLower(), GetUnixTime(expireDateTime).ToString());
}
else if (header.Equals(HEADER_DATE))
{
var utcDateTime = dateTime.ToString("r").ToString();
httpSignatureHeader.Add(header.ToLower(), utcDateTime);
httpSignedRequestHeader.Add(HEADER_DATE, utcDateTime);
}
else if (header.Equals(HEADER_HOST))
{
httpSignatureHeader.Add(header.ToLower(), uriBuilder.Host);
httpSignedRequestHeader.Add(HEADER_HOST, uriBuilder.Host);
}
else if (header.Equals(HEADER_CREATED))
{
httpSignatureHeader.Add(header.ToLower(), GetUnixTime(dateTime).ToString());
}
else if (header.Equals(HEADER_DIGEST))
{
httpSignedRequestHeader.Add(HEADER_DIGEST, Digest);
httpSignatureHeader.Add(header.ToLower(), Digest);
}
else
{
bool isHeaderFound = false;
foreach (var item in requestOptions.HeaderParameters)
{
if (string.Equals(item.Key, header, StringComparison.OrdinalIgnoreCase))
{
httpSignatureHeader.Add(header.ToLower(), item.Value.ToString());
isHeaderFound = true;
break;
}
}
if (!isHeaderFound)
{
throw new Exception(string.Format("Cannot sign HTTP request.Request does not contain the {0} header.",header));
}
}
}
var headersKeysString = String.Join(" ", httpSignatureHeader.Keys);
var headerValuesList = new List<string>();
foreach (var keyVal in httpSignatureHeader)
{
headerValuesList.Add(string.Format("{0}: {1}", keyVal.Key, keyVal.Value));
}
//Concatinate headers value separated by new line
var headerValuesString = string.Join("\n", headerValuesList);
var signatureStringHash = GetStringHash(HashAlgorithm.ToString(), headerValuesString);
string headerSignatureStr = null;
var keyType = GetKeyType(KeyFilePath);
if (keyType == PrivateKeyType.RSA)
{
headerSignatureStr = GetRSASignature(signatureStringHash);
}
else if (keyType == PrivateKeyType.ECDSA)
{
headerSignatureStr = GetECDSASignature(signatureStringHash);
}
var cryptographicScheme = "hs2019";
var authorizationHeaderValue = string.Format("Signature keyId=\"{0}\",algorithm=\"{1}\"",
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.Add(HEADER_AUTHORIZATION, authorizationHeaderValue);
return httpSignedRequestHeader;
}
private byte[] GetStringHash(string hashName, string stringToBeHashed)
{
var hashAlgorithm = System.Security.Cryptography.HashAlgorithm.Create(hashName);
var bytes = Encoding.UTF8.GetBytes(stringToBeHashed);
var stringHash = hashAlgorithm.ComputeHash(bytes);
return stringHash;
}
private int GetUnixTime(DateTime date2)
{
DateTime date1 = new DateTime(1970, 01, 01);
TimeSpan timeSpan = date2 - date1;
return (int)timeSpan.TotalSeconds;
}
private string GetRSASignature(byte[] stringToSign)
{
RSA rsa = GetRSAProviderFromPemFile(KeyFilePath, KeyPassPhrase);
if (SigningAlgorithm == "RSASSA-PSS")
{
var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pss);
return Convert.ToBase64String(signedbytes);
}
else if (SigningAlgorithm == "PKCS1-v15")
{
var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signedbytes);
}
return string.Empty;
}
/// <summary>
/// Gets the ECDSA signature
/// </summary>
/// <param name="dataToSign"></param>
/// <returns></returns>
private string GetECDSASignature(byte[] dataToSign)
{
if (!File.Exists(KeyFilePath))
{
throw new Exception("key file path does not exist.");
}
var ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----";
var ecKeyFooter = "-----END EC PRIVATE KEY-----";
var keyStr = File.ReadAllText(KeyFilePath);
var ecKeyBase64String = keyStr.Replace(ecKeyHeader, "").Replace(ecKeyFooter, "").Trim();
var keyBytes = System.Convert.FromBase64String(ecKeyBase64String);
var ecdsa = ECDsa.Create();
var bytCount = 0;
#if (NETCOREAPP3_0 || NETCOREAPP3_1 || NET5_0)
if (configuration.KeyPassPhrase != null)
{
ecdsa.ImportEncryptedPkcs8PrivateKey(keyPassPhrase, keyBytes, out bytCount);
}
else
{
ecdsa.ImportPkcs8PrivateKey(keyBytes, out bytCount);
}
var signedBytes = ecdsa.SignHash(dataToSign);
var derBytes = ConvertToECDSAANS1Format(signedBytes);
var signedString = System.Convert.ToBase64String(derBytes);
return signedString;
#else
throw new Exception("ECDSA signing is supported only on NETCOREAPP3_0 and above");
#endif
}
private byte[] ConvertToECDSAANS1Format(byte[] signedBytes)
{
var derBytes = new List<byte>();
byte derLength = 68; //default lenght for ECDSA code signinged bit 0x44
byte rbytesLength = 32; //R length 0x20
byte sbytesLength = 32; //S length 0x20
var rBytes = new List<byte>();
var sBytes = new List<byte>();
for (int i = 0; i < 32; i++)
{
rBytes.Add(signedBytes[i]);
}
for (int i = 32; i < 64; i++)
{
sBytes.Add(signedBytes[i]);
}
if (rBytes[0] > 0x7F)
{
derLength++;
rbytesLength++;
var tempBytes = new List<byte>();
tempBytes.AddRange(rBytes);
rBytes.Clear();
rBytes.Add(0x00);
rBytes.AddRange(tempBytes);
}
if (sBytes[0] > 0x7F)
{
derLength++;
sbytesLength++;
var tempBytes = new List<byte>();
tempBytes.AddRange(sBytes);
sBytes.Clear();
sBytes.Add(0x00);
sBytes.AddRange(tempBytes);
}
derBytes.Add(48); //start of the sequence 0x30
derBytes.Add(derLength); //total length r lenth, type and r bytes
derBytes.Add(2); //tag for integer
derBytes.Add(rbytesLength); //length of r
derBytes.AddRange(rBytes);
derBytes.Add(2); //tag for integer
derBytes.Add(sbytesLength); //length of s
derBytes.AddRange(sBytes);
return derBytes.ToArray();
}
private RSACryptoServiceProvider GetRSAProviderFromPemFile(String pemfile, SecureString keyPassPharse = null)
{
const String pempubheader = "-----BEGIN PUBLIC KEY-----";
const String pempubfooter = "-----END PUBLIC KEY-----";
bool isPrivateKeyFile = true;
byte[] pemkey = null;
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;
}
private 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;
}
}
private RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
private int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current);
//last ReadByte wasn't a removed zero, so back up a byte
return count;
}
private byte[] GetEncryptedKey(byte[] salt, SecureString secpswd, int count, int miter)
{
IntPtr unmanagedPswd = IntPtr.Zero;
int HASHLENGTH = 16; //MD5 bytes
byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store contatenated Mi hashed results
byte[] psbytes = new byte[secpswd.Length];
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
// --- contatenate salt and pswd bytes into fixed data array ---
byte[] data00 = new byte[psbytes.Length + salt.Length];
Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes
// ---- do multi-hashing and contatenate results D1, D2 ... into keymaterial bytes ----
MD5 md5 = new MD5CryptoServiceProvider();
byte[] result = null;
byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget
for (int j = 0; j < miter; j++)
{
// ---- Now hash consecutively for count times ------
if (j == 0)
result = data00; //initialize
else
{
Array.Copy(result, hashtarget, result.Length);
Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
result = hashtarget;
}
for (int i = 0; i < count; i++)
result = md5.ComputeHash(result);
Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //contatenate to keymaterial
}
byte[] deskey = new byte[24];
Array.Copy(keymaterial, deskey, deskey.Length);
Array.Clear(psbytes, 0, psbytes.Length);
Array.Clear(data00, 0, data00.Length);
Array.Clear(result, 0, result.Length);
Array.Clear(hashtarget, 0, hashtarget.Length);
Array.Clear(keymaterial, 0, keymaterial.Length);
return deskey;
}
private byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
{
MemoryStream memst = new MemoryStream();
TripleDES alg = TripleDES.Create();
alg.Key = desKey;
alg.IV = IV;
try
{
CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherData, 0, cipherData.Length);
cs.Close();
}
catch (Exception)
{
return null;
}
byte[] decryptedData = memst.ToArray();
return decryptedData;
}
/// <summary>
/// Detect the key type from the pem file.
/// </summary>
/// <param name="keyFilePath">key file path in pem format</param>
/// <returns></returns>
private PrivateKeyType GetKeyType(string keyFilePath)
{
if (!File.Exists(keyFilePath))
{
throw new Exception("Key file path does not exist.");
}
var ecPrivateKeyHeader = "BEGIN EC PRIVATE KEY";
var ecPrivateKeyFooter = "END EC PRIVATE KEY";
var rsaPrivateKeyHeader = "BEGIN RSA PRIVATE KEY";
var rsaPrivateFooter = "END RSA PRIVATE KEY";
var pkcs8Header = "BEGIN PRIVATE KEY";
var pkcs8Footer = "END PRIVATE KEY";
var keyType = PrivateKeyType.None;
var key = File.ReadAllLines(keyFilePath);
if (key[0].ToString().Contains(rsaPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(rsaPrivateFooter))
{
keyType = PrivateKeyType.RSA;
}
else if (key[0].ToString().Contains(ecPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter))
{
keyType = PrivateKeyType.ECDSA;
}
else if (key[0].ToString().Contains(ecPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(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 = PrivateKeyType.ECDSA;
}
else
{
throw new Exception("Either the key is invalid or key is not supported");
}
return keyType;
}
#endregion
}
}

View File

@ -96,5 +96,10 @@ namespace {{packageName}}.Client
/// </summary> /// </summary>
/// <value>X509 Certificate collection.</value> /// <value>X509 Certificate collection.</value>
X509CertificateCollection ClientCertificates { get; } X509CertificateCollection ClientCertificates { get; }
/// <summary>
/// Gets the HTTPSigning configuration
/// </summary>
HTTPSigningConfiguration HTTPSigningConfiguration { get; }
} }
} }

View File

@ -369,6 +369,23 @@ namespace {{packageName}}.{{apiPackage}}
localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken); localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
} }
{{/isOAuth}} {{/isOAuth}}
{{#isHttpSignature}}
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "{{{httpMethod}}}", "{{{path}}}", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
{{/isHttpSignature}}
{{/authMethods}} {{/authMethods}}
// make the HTTP request // make the HTTP request
@ -547,6 +564,23 @@ namespace {{packageName}}.{{apiPackage}}
localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken); localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
} }
{{/isOAuth}} {{/isOAuth}}
{{#isHttpSignature}}
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "{{{httpMethod}}}", "{{{path}}}", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
{{/isHttpSignature}}
{{/authMethods}} {{/authMethods}}
// make the HTTP request // make the HTTP request

View File

@ -73,6 +73,7 @@ src/Org.OpenAPITools/Client/ClientUtils.cs
src/Org.OpenAPITools/Client/Configuration.cs src/Org.OpenAPITools/Client/Configuration.cs
src/Org.OpenAPITools/Client/ExceptionFactory.cs src/Org.OpenAPITools/Client/ExceptionFactory.cs
src/Org.OpenAPITools/Client/GlobalConfiguration.cs src/Org.OpenAPITools/Client/GlobalConfiguration.cs
src/Org.OpenAPITools/Client/HTTPSigningConfiguration.cs
src/Org.OpenAPITools/Client/HttpMethod.cs src/Org.OpenAPITools/Client/HttpMethod.cs
src/Org.OpenAPITools/Client/IApiAccessor.cs src/Org.OpenAPITools/Client/IApiAccessor.cs
src/Org.OpenAPITools/Client/IAsynchronousClient.cs src/Org.OpenAPITools/Client/IAsynchronousClient.cs

View File

@ -1084,6 +1084,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.Data = pet; localVarRequestOptions.Data = pet;
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "GET", "/fake/http-signature-test", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// make the HTTP request // make the HTTP request
var localVarResponse = this.Client.Get<Object>("/fake/http-signature-test", localVarRequestOptions, this.Configuration); var localVarResponse = this.Client.Get<Object>("/fake/http-signature-test", localVarRequestOptions, this.Configuration);
@ -1156,6 +1171,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.Data = pet; localVarRequestOptions.Data = pet;
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "GET", "/fake/http-signature-test", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// make the HTTP request // make the HTTP request

View File

@ -85,6 +85,10 @@ namespace Org.OpenAPITools.Client
private string _dateTimeFormat = ISO8601_DATETIME_FORMAT; private string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
private string _tempFolderPath = Path.GetTempPath(); private string _tempFolderPath = Path.GetTempPath();
/// <summary>
/// HTTPSigning configuration
/// </summary>
private HTTPSigningConfiguration _HTTPSigningConfiguration = null;
#endregion Private Members #endregion Private Members
#region Constructors #region Constructors
@ -336,6 +340,15 @@ namespace Org.OpenAPITools.Client
} }
} }
/// <summary>
/// Gets and Sets the HTTPSigningConfiuration
/// </summary>
public HTTPSigningConfiguration HTTPSigningConfiguration
{
get { return _HTTPSigningConfiguration; }
set { _HTTPSigningConfiguration = value; }
}
#endregion Properties #endregion Properties
#region Methods #region Methods
@ -407,7 +420,8 @@ namespace Org.OpenAPITools.Client
Password = second.Password ?? first.Password, Password = second.Password ?? first.Password,
AccessToken = second.AccessToken ?? first.AccessToken, AccessToken = second.AccessToken ?? first.AccessToken,
TempFolderPath = second.TempFolderPath ?? first.TempFolderPath, TempFolderPath = second.TempFolderPath ?? first.TempFolderPath,
DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat,
HTTPSigningConfiguration = second.HTTPSigningConfiguration ?? first.HTTPSigningConfiguration
}; };
return config; return config;
} }

View File

@ -0,0 +1,711 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace Org.OpenAPITools.Client
{
/// <summary>
/// Class for HTTPSigning auth related parameter and methods
/// </summary>
public class HTTPSigningConfiguration
{
#region
/// <summary>
/// Initailize the HashAlgorithm and SigningAlgorithm to default value
/// </summary>
public HTTPSigningConfiguration()
{
HashAlgorithm = HashAlgorithmName.SHA256;
SigningAlgorithm = "PKCS1-v15";
}
#endregion
#region Properties
/// <summary>
///Gets the Api keyId
/// </summary>
public string KeyId { get; set; }
/// <summary>
/// Gets the Key file path
/// </summary>
public string KeyFilePath { get; set; }
/// <summary>
/// Gets the key pass phrase for password protected key
/// </summary>
public SecureString KeyPassPhrase { get; set; }
/// <summary>
/// Gets the HTTP signing header
/// </summary>
public List<string> HTTPSigningHeader { get; set; }
/// <summary>
/// Gets the hash algorithm sha256 or sha512
/// </summary>
public HashAlgorithmName HashAlgorithm { get; set; }
/// <summary>
/// Gets the signing algorithm
/// </summary>
public string SigningAlgorithm { get; set; }
/// <summary>
/// Gets the Signature validaty period in seconds
/// </summary>
public int SignatureValidityPeriod { get; set; }
#endregion
#region enum
private enum PrivateKeyType
{
None = 0,
RSA = 1,
ECDSA = 2,
}
#endregion
#region Methods
/// <summary>
/// Gets the Headers for HTTpSIgning
/// </summary>
/// <param name="method"></param>
/// <param name="path"></param>
/// <param name="requestOptions"></param>
/// <returns></returns>
internal Dictionary<string, string> GetHttpSignedHeader(string basePath,string method, string path, RequestOptions requestOptions)
{
const string HEADER_REQUEST_TARGET = "(request-target)";
//The time when the HTTP signature expires. The API server should reject HTTP requests
//that have expired.
const string HEADER_EXPIRES = "(expires)";
//The 'Date' header.
const string HEADER_DATE = "Date";
//The 'Host' header.
const string HEADER_HOST = "Host";
//The time when the HTTP signature was generated.
const string HEADER_CREATED = "(created)";
//When the 'Digest' header is included in the HTTP signature, the client automatically
//computes the digest of the HTTP request body, per RFC 3230.
const string HEADER_DIGEST = "Digest";
//The 'Authorization' header is automatically generated by the client. It includes
//the list of signed headers and a base64-encoded signature.
const string HEADER_AUTHORIZATION = "Authorization";
//Hash table to store singed headers
var httpSignedRequestHeader = new Dictionary<string, string>();
var httpSignatureHeader = new Dictionary<string, string>();
if (HTTPSigningHeader.Count == 0)
{
HTTPSigningHeader.Add("(created)");
}
if (requestOptions.PathParameters != null)
{
foreach (var pathParam in requestOptions.PathParameters)
{
var tempPath = path.Replace(pathParam.Key, "0");
path = string.Format(tempPath, pathParam.Value);
}
}
var httpValues = HttpUtility.ParseQueryString(String.Empty);
foreach (var parameter in requestOptions.QueryParameters)
{
if (parameter.Value.Count > 1)
{ // array
foreach (var value in parameter.Value)
{
httpValues.Add(parameter.Key + "[]", value);
}
}
else
{
httpValues.Add(parameter.Key, parameter.Value[0]);
}
}
var uriBuilder = new UriBuilder(string.Concat(basePath, path));
uriBuilder.Query = httpValues.ToString();
var dateTime = DateTime.Now;
String Digest = String.Empty;
//get the body
string requestBody = string.Empty;
if (requestOptions.Data != null)
{
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
requestBody = JsonConvert.SerializeObject(requestOptions.Data, serializerSettings);
}
if (HashAlgorithm == HashAlgorithmName.SHA256)
{
var bodyDigest = GetStringHash(HashAlgorithm.ToString(), requestBody);
Digest = string.Format("SHA-256={0}", Convert.ToBase64String(bodyDigest));
}
else if (HashAlgorithm == HashAlgorithmName.SHA512)
{
var bodyDigest = GetStringHash(HashAlgorithm.ToString(), requestBody);
Digest = string.Format("SHA-512={0}", Convert.ToBase64String(bodyDigest));
}
else
{
throw new Exception(string.Format("{0} not supported", HashAlgorithm));
}
foreach (var header in HTTPSigningHeader)
{
if (header.Equals(HEADER_REQUEST_TARGET))
{
var targetUrl = string.Format("{0} {1}{2}", method.ToLower(), uriBuilder.Path, uriBuilder.Query);
httpSignatureHeader.Add(header.ToLower(), targetUrl);
}
else if (header.Equals(HEADER_EXPIRES))
{
var expireDateTime = dateTime.AddSeconds(SignatureValidityPeriod);
httpSignatureHeader.Add(header.ToLower(), GetUnixTime(expireDateTime).ToString());
}
else if (header.Equals(HEADER_DATE))
{
var utcDateTime = dateTime.ToString("r").ToString();
httpSignatureHeader.Add(header.ToLower(), utcDateTime);
httpSignedRequestHeader.Add(HEADER_DATE, utcDateTime);
}
else if (header.Equals(HEADER_HOST))
{
httpSignatureHeader.Add(header.ToLower(), uriBuilder.Host);
httpSignedRequestHeader.Add(HEADER_HOST, uriBuilder.Host);
}
else if (header.Equals(HEADER_CREATED))
{
httpSignatureHeader.Add(header.ToLower(), GetUnixTime(dateTime).ToString());
}
else if (header.Equals(HEADER_DIGEST))
{
httpSignedRequestHeader.Add(HEADER_DIGEST, Digest);
httpSignatureHeader.Add(header.ToLower(), Digest);
}
else
{
bool isHeaderFound = false;
foreach (var item in requestOptions.HeaderParameters)
{
if (string.Equals(item.Key, header, StringComparison.OrdinalIgnoreCase))
{
httpSignatureHeader.Add(header.ToLower(), item.Value.ToString());
isHeaderFound = true;
break;
}
}
if (!isHeaderFound)
{
throw new Exception(string.Format("Cannot sign HTTP request.Request does not contain the {0} header.",header));
}
}
}
var headersKeysString = String.Join(" ", httpSignatureHeader.Keys);
var headerValuesList = new List<string>();
foreach (var keyVal in httpSignatureHeader)
{
headerValuesList.Add(string.Format("{0}: {1}", keyVal.Key, keyVal.Value));
}
//Concatinate headers value separated by new line
var headerValuesString = string.Join("\n", headerValuesList);
var signatureStringHash = GetStringHash(HashAlgorithm.ToString(), headerValuesString);
string headerSignatureStr = null;
var keyType = GetKeyType(KeyFilePath);
if (keyType == PrivateKeyType.RSA)
{
headerSignatureStr = GetRSASignature(signatureStringHash);
}
else if (keyType == PrivateKeyType.ECDSA)
{
headerSignatureStr = GetECDSASignature(signatureStringHash);
}
var cryptographicScheme = "hs2019";
var authorizationHeaderValue = string.Format("Signature keyId=\"{0}\",algorithm=\"{1}\"",
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.Add(HEADER_AUTHORIZATION, authorizationHeaderValue);
return httpSignedRequestHeader;
}
private byte[] GetStringHash(string hashName, string stringToBeHashed)
{
var hashAlgorithm = System.Security.Cryptography.HashAlgorithm.Create(hashName);
var bytes = Encoding.UTF8.GetBytes(stringToBeHashed);
var stringHash = hashAlgorithm.ComputeHash(bytes);
return stringHash;
}
private int GetUnixTime(DateTime date2)
{
DateTime date1 = new DateTime(1970, 01, 01);
TimeSpan timeSpan = date2 - date1;
return (int)timeSpan.TotalSeconds;
}
private string GetRSASignature(byte[] stringToSign)
{
RSA rsa = GetRSAProviderFromPemFile(KeyFilePath, KeyPassPhrase);
if (SigningAlgorithm == "RSASSA-PSS")
{
var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pss);
return Convert.ToBase64String(signedbytes);
}
else if (SigningAlgorithm == "PKCS1-v15")
{
var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signedbytes);
}
return string.Empty;
}
/// <summary>
/// Gets the ECDSA signature
/// </summary>
/// <param name="dataToSign"></param>
/// <returns></returns>
private string GetECDSASignature(byte[] dataToSign)
{
if (!File.Exists(KeyFilePath))
{
throw new Exception("key file path does not exist.");
}
var ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----";
var ecKeyFooter = "-----END EC PRIVATE KEY-----";
var keyStr = File.ReadAllText(KeyFilePath);
var ecKeyBase64String = keyStr.Replace(ecKeyHeader, "").Replace(ecKeyFooter, "").Trim();
var keyBytes = System.Convert.FromBase64String(ecKeyBase64String);
var ecdsa = ECDsa.Create();
var bytCount = 0;
#if (NETCOREAPP3_0 || NETCOREAPP3_1 || NET5_0)
if (configuration.KeyPassPhrase != null)
{
ecdsa.ImportEncryptedPkcs8PrivateKey(keyPassPhrase, keyBytes, out bytCount);
}
else
{
ecdsa.ImportPkcs8PrivateKey(keyBytes, out bytCount);
}
var signedBytes = ecdsa.SignHash(dataToSign);
var derBytes = ConvertToECDSAANS1Format(signedBytes);
var signedString = System.Convert.ToBase64String(derBytes);
return signedString;
#else
throw new Exception("ECDSA signing is supported only on NETCOREAPP3_0 and above");
#endif
}
private byte[] ConvertToECDSAANS1Format(byte[] signedBytes)
{
var derBytes = new List<byte>();
byte derLength = 68; //default lenght for ECDSA code signinged bit 0x44
byte rbytesLength = 32; //R length 0x20
byte sbytesLength = 32; //S length 0x20
var rBytes = new List<byte>();
var sBytes = new List<byte>();
for (int i = 0; i < 32; i++)
{
rBytes.Add(signedBytes[i]);
}
for (int i = 32; i < 64; i++)
{
sBytes.Add(signedBytes[i]);
}
if (rBytes[0] > 0x7F)
{
derLength++;
rbytesLength++;
var tempBytes = new List<byte>();
tempBytes.AddRange(rBytes);
rBytes.Clear();
rBytes.Add(0x00);
rBytes.AddRange(tempBytes);
}
if (sBytes[0] > 0x7F)
{
derLength++;
sbytesLength++;
var tempBytes = new List<byte>();
tempBytes.AddRange(sBytes);
sBytes.Clear();
sBytes.Add(0x00);
sBytes.AddRange(tempBytes);
}
derBytes.Add(48); //start of the sequence 0x30
derBytes.Add(derLength); //total length r lenth, type and r bytes
derBytes.Add(2); //tag for integer
derBytes.Add(rbytesLength); //length of r
derBytes.AddRange(rBytes);
derBytes.Add(2); //tag for integer
derBytes.Add(sbytesLength); //length of s
derBytes.AddRange(sBytes);
return derBytes.ToArray();
}
private RSACryptoServiceProvider GetRSAProviderFromPemFile(String pemfile, SecureString keyPassPharse = null)
{
const String pempubheader = "-----BEGIN PUBLIC KEY-----";
const String pempubfooter = "-----END PUBLIC KEY-----";
bool isPrivateKeyFile = true;
byte[] pemkey = null;
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;
}
private 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;
}
}
private RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
private int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current);
//last ReadByte wasn't a removed zero, so back up a byte
return count;
}
private byte[] GetEncryptedKey(byte[] salt, SecureString secpswd, int count, int miter)
{
IntPtr unmanagedPswd = IntPtr.Zero;
int HASHLENGTH = 16; //MD5 bytes
byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store contatenated Mi hashed results
byte[] psbytes = new byte[secpswd.Length];
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
// --- contatenate salt and pswd bytes into fixed data array ---
byte[] data00 = new byte[psbytes.Length + salt.Length];
Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes
// ---- do multi-hashing and contatenate results D1, D2 ... into keymaterial bytes ----
MD5 md5 = new MD5CryptoServiceProvider();
byte[] result = null;
byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget
for (int j = 0; j < miter; j++)
{
// ---- Now hash consecutively for count times ------
if (j == 0)
result = data00; //initialize
else
{
Array.Copy(result, hashtarget, result.Length);
Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
result = hashtarget;
}
for (int i = 0; i < count; i++)
result = md5.ComputeHash(result);
Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //contatenate to keymaterial
}
byte[] deskey = new byte[24];
Array.Copy(keymaterial, deskey, deskey.Length);
Array.Clear(psbytes, 0, psbytes.Length);
Array.Clear(data00, 0, data00.Length);
Array.Clear(result, 0, result.Length);
Array.Clear(hashtarget, 0, hashtarget.Length);
Array.Clear(keymaterial, 0, keymaterial.Length);
return deskey;
}
private byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
{
MemoryStream memst = new MemoryStream();
TripleDES alg = TripleDES.Create();
alg.Key = desKey;
alg.IV = IV;
try
{
CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherData, 0, cipherData.Length);
cs.Close();
}
catch (Exception)
{
return null;
}
byte[] decryptedData = memst.ToArray();
return decryptedData;
}
/// <summary>
/// Detect the key type from the pem file.
/// </summary>
/// <param name="keyFilePath">key file path in pem format</param>
/// <returns></returns>
private PrivateKeyType GetKeyType(string keyFilePath)
{
if (!File.Exists(keyFilePath))
{
throw new Exception("Key file path does not exist.");
}
var ecPrivateKeyHeader = "BEGIN EC PRIVATE KEY";
var ecPrivateKeyFooter = "END EC PRIVATE KEY";
var rsaPrivateKeyHeader = "BEGIN RSA PRIVATE KEY";
var rsaPrivateFooter = "END RSA PRIVATE KEY";
var pkcs8Header = "BEGIN PRIVATE KEY";
var pkcs8Footer = "END PRIVATE KEY";
var keyType = PrivateKeyType.None;
var key = File.ReadAllLines(keyFilePath);
if (key[0].ToString().Contains(rsaPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(rsaPrivateFooter))
{
keyType = PrivateKeyType.RSA;
}
else if (key[0].ToString().Contains(ecPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter))
{
keyType = PrivateKeyType.ECDSA;
}
else if (key[0].ToString().Contains(ecPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(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 = PrivateKeyType.ECDSA;
}
else
{
throw new Exception("Either the key is invalid or key is not supported");
}
return keyType;
}
#endregion
}
}

View File

@ -104,5 +104,10 @@ namespace Org.OpenAPITools.Client
/// </summary> /// </summary>
/// <value>X509 Certificate collection.</value> /// <value>X509 Certificate collection.</value>
X509CertificateCollection ClientCertificates { get; } X509CertificateCollection ClientCertificates { get; }
/// <summary>
/// Gets the HTTPSigning configuration
/// </summary>
HTTPSigningConfiguration HTTPSigningConfiguration { get; }
} }
} }

View File

@ -104,6 +104,7 @@ src/Org.OpenAPITools/Client/ClientUtils.cs
src/Org.OpenAPITools/Client/Configuration.cs src/Org.OpenAPITools/Client/Configuration.cs
src/Org.OpenAPITools/Client/ExceptionFactory.cs src/Org.OpenAPITools/Client/ExceptionFactory.cs
src/Org.OpenAPITools/Client/GlobalConfiguration.cs src/Org.OpenAPITools/Client/GlobalConfiguration.cs
src/Org.OpenAPITools/Client/HTTPSigningConfiguration.cs
src/Org.OpenAPITools/Client/HttpMethod.cs src/Org.OpenAPITools/Client/HttpMethod.cs
src/Org.OpenAPITools/Client/IApiAccessor.cs src/Org.OpenAPITools/Client/IApiAccessor.cs
src/Org.OpenAPITools/Client/IAsynchronousClient.cs src/Org.OpenAPITools/Client/IAsynchronousClient.cs

View File

@ -604,6 +604,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.Data = pet; localVarRequestOptions.Data = pet;
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "POST", "/pet", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// authentication (petstore_auth) required // authentication (petstore_auth) required
// oauth required // oauth required
if (!String.IsNullOrEmpty(this.Configuration.AccessToken)) if (!String.IsNullOrEmpty(this.Configuration.AccessToken))
@ -670,6 +685,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.Data = pet; localVarRequestOptions.Data = pet;
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "POST", "/pet", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// authentication (petstore_auth) required // authentication (petstore_auth) required
// oauth required // oauth required
if (!String.IsNullOrEmpty(this.Configuration.AccessToken)) if (!String.IsNullOrEmpty(this.Configuration.AccessToken))
@ -861,6 +891,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.QueryParameters.Add(Org.OpenAPITools.Client.ClientUtils.ParameterToMultiMap("csv", "status", status)); localVarRequestOptions.QueryParameters.Add(Org.OpenAPITools.Client.ClientUtils.ParameterToMultiMap("csv", "status", status));
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "GET", "/pet/findByStatus", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// authentication (petstore_auth) required // authentication (petstore_auth) required
// oauth required // oauth required
if (!String.IsNullOrEmpty(this.Configuration.AccessToken)) if (!String.IsNullOrEmpty(this.Configuration.AccessToken))
@ -928,6 +973,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.QueryParameters.Add(Org.OpenAPITools.Client.ClientUtils.ParameterToMultiMap("csv", "status", status)); localVarRequestOptions.QueryParameters.Add(Org.OpenAPITools.Client.ClientUtils.ParameterToMultiMap("csv", "status", status));
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "GET", "/pet/findByStatus", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// authentication (petstore_auth) required // authentication (petstore_auth) required
// oauth required // oauth required
if (!String.IsNullOrEmpty(this.Configuration.AccessToken)) if (!String.IsNullOrEmpty(this.Configuration.AccessToken))
@ -992,6 +1052,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.QueryParameters.Add(Org.OpenAPITools.Client.ClientUtils.ParameterToMultiMap("csv", "tags", tags)); localVarRequestOptions.QueryParameters.Add(Org.OpenAPITools.Client.ClientUtils.ParameterToMultiMap("csv", "tags", tags));
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "GET", "/pet/findByTags", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// authentication (petstore_auth) required // authentication (petstore_auth) required
// oauth required // oauth required
if (!String.IsNullOrEmpty(this.Configuration.AccessToken)) if (!String.IsNullOrEmpty(this.Configuration.AccessToken))
@ -1059,6 +1134,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.QueryParameters.Add(Org.OpenAPITools.Client.ClientUtils.ParameterToMultiMap("csv", "tags", tags)); localVarRequestOptions.QueryParameters.Add(Org.OpenAPITools.Client.ClientUtils.ParameterToMultiMap("csv", "tags", tags));
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "GET", "/pet/findByTags", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// authentication (petstore_auth) required // authentication (petstore_auth) required
// oauth required // oauth required
if (!String.IsNullOrEmpty(this.Configuration.AccessToken)) if (!String.IsNullOrEmpty(this.Configuration.AccessToken))
@ -1241,6 +1331,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.Data = pet; localVarRequestOptions.Data = pet;
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "PUT", "/pet", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// authentication (petstore_auth) required // authentication (petstore_auth) required
// oauth required // oauth required
if (!String.IsNullOrEmpty(this.Configuration.AccessToken)) if (!String.IsNullOrEmpty(this.Configuration.AccessToken))
@ -1307,6 +1412,21 @@ namespace Org.OpenAPITools.Api
localVarRequestOptions.Data = pet; localVarRequestOptions.Data = pet;
// authentication (http_signature_test) required // authentication (http_signature_test) required
if(this.Configuration.HTTPSigningConfiguration != null)
{
var HttpSigningHeaders = this.Configuration.HTTPSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "PUT", "/pet", localVarRequestOptions);
foreach (var headerItem in HttpSigningHeaders)
{
if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
{
localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
}
else
{
localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
}
}
}
// authentication (petstore_auth) required // authentication (petstore_auth) required
// oauth required // oauth required
if (!String.IsNullOrEmpty(this.Configuration.AccessToken)) if (!String.IsNullOrEmpty(this.Configuration.AccessToken))

View File

@ -89,6 +89,10 @@ namespace Org.OpenAPITools.Client
private string _dateTimeFormat = ISO8601_DATETIME_FORMAT; private string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
private string _tempFolderPath = Path.GetTempPath(); private string _tempFolderPath = Path.GetTempPath();
/// <summary>
/// HTTPSigning configuration
/// </summary>
private HTTPSigningConfiguration _HTTPSigningConfiguration = null;
#endregion Private Members #endregion Private Members
#region Constructors #region Constructors
@ -340,6 +344,15 @@ namespace Org.OpenAPITools.Client
} }
} }
/// <summary>
/// Gets and Sets the HTTPSigningConfiuration
/// </summary>
public HTTPSigningConfiguration HTTPSigningConfiguration
{
get { return _HTTPSigningConfiguration; }
set { _HTTPSigningConfiguration = value; }
}
#endregion Properties #endregion Properties
#region Methods #region Methods
@ -412,7 +425,8 @@ namespace Org.OpenAPITools.Client
Password = second.Password ?? first.Password, Password = second.Password ?? first.Password,
AccessToken = second.AccessToken ?? first.AccessToken, AccessToken = second.AccessToken ?? first.AccessToken,
TempFolderPath = second.TempFolderPath ?? first.TempFolderPath, TempFolderPath = second.TempFolderPath ?? first.TempFolderPath,
DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat,
HTTPSigningConfiguration = second.HTTPSigningConfiguration ?? first.HTTPSigningConfiguration
}; };
return config; return config;
} }

View File

@ -0,0 +1,711 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace Org.OpenAPITools.Client
{
/// <summary>
/// Class for HTTPSigning auth related parameter and methods
/// </summary>
public class HTTPSigningConfiguration
{
#region
/// <summary>
/// Initailize the HashAlgorithm and SigningAlgorithm to default value
/// </summary>
public HTTPSigningConfiguration()
{
HashAlgorithm = HashAlgorithmName.SHA256;
SigningAlgorithm = "PKCS1-v15";
}
#endregion
#region Properties
/// <summary>
///Gets the Api keyId
/// </summary>
public string KeyId { get; set; }
/// <summary>
/// Gets the Key file path
/// </summary>
public string KeyFilePath { get; set; }
/// <summary>
/// Gets the key pass phrase for password protected key
/// </summary>
public SecureString KeyPassPhrase { get; set; }
/// <summary>
/// Gets the HTTP signing header
/// </summary>
public List<string> HTTPSigningHeader { get; set; }
/// <summary>
/// Gets the hash algorithm sha256 or sha512
/// </summary>
public HashAlgorithmName HashAlgorithm { get; set; }
/// <summary>
/// Gets the signing algorithm
/// </summary>
public string SigningAlgorithm { get; set; }
/// <summary>
/// Gets the Signature validaty period in seconds
/// </summary>
public int SignatureValidityPeriod { get; set; }
#endregion
#region enum
private enum PrivateKeyType
{
None = 0,
RSA = 1,
ECDSA = 2,
}
#endregion
#region Methods
/// <summary>
/// Gets the Headers for HTTpSIgning
/// </summary>
/// <param name="method"></param>
/// <param name="path"></param>
/// <param name="requestOptions"></param>
/// <returns></returns>
internal Dictionary<string, string> GetHttpSignedHeader(string basePath,string method, string path, RequestOptions requestOptions)
{
const string HEADER_REQUEST_TARGET = "(request-target)";
//The time when the HTTP signature expires. The API server should reject HTTP requests
//that have expired.
const string HEADER_EXPIRES = "(expires)";
//The 'Date' header.
const string HEADER_DATE = "Date";
//The 'Host' header.
const string HEADER_HOST = "Host";
//The time when the HTTP signature was generated.
const string HEADER_CREATED = "(created)";
//When the 'Digest' header is included in the HTTP signature, the client automatically
//computes the digest of the HTTP request body, per RFC 3230.
const string HEADER_DIGEST = "Digest";
//The 'Authorization' header is automatically generated by the client. It includes
//the list of signed headers and a base64-encoded signature.
const string HEADER_AUTHORIZATION = "Authorization";
//Hash table to store singed headers
var httpSignedRequestHeader = new Dictionary<string, string>();
var httpSignatureHeader = new Dictionary<string, string>();
if (HTTPSigningHeader.Count == 0)
{
HTTPSigningHeader.Add("(created)");
}
if (requestOptions.PathParameters != null)
{
foreach (var pathParam in requestOptions.PathParameters)
{
var tempPath = path.Replace(pathParam.Key, "0");
path = string.Format(tempPath, pathParam.Value);
}
}
var httpValues = HttpUtility.ParseQueryString(String.Empty);
foreach (var parameter in requestOptions.QueryParameters)
{
if (parameter.Value.Count > 1)
{ // array
foreach (var value in parameter.Value)
{
httpValues.Add(parameter.Key + "[]", value);
}
}
else
{
httpValues.Add(parameter.Key, parameter.Value[0]);
}
}
var uriBuilder = new UriBuilder(string.Concat(basePath, path));
uriBuilder.Query = httpValues.ToString();
var dateTime = DateTime.Now;
String Digest = String.Empty;
//get the body
string requestBody = string.Empty;
if (requestOptions.Data != null)
{
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
requestBody = JsonConvert.SerializeObject(requestOptions.Data, serializerSettings);
}
if (HashAlgorithm == HashAlgorithmName.SHA256)
{
var bodyDigest = GetStringHash(HashAlgorithm.ToString(), requestBody);
Digest = string.Format("SHA-256={0}", Convert.ToBase64String(bodyDigest));
}
else if (HashAlgorithm == HashAlgorithmName.SHA512)
{
var bodyDigest = GetStringHash(HashAlgorithm.ToString(), requestBody);
Digest = string.Format("SHA-512={0}", Convert.ToBase64String(bodyDigest));
}
else
{
throw new Exception(string.Format("{0} not supported", HashAlgorithm));
}
foreach (var header in HTTPSigningHeader)
{
if (header.Equals(HEADER_REQUEST_TARGET))
{
var targetUrl = string.Format("{0} {1}{2}", method.ToLower(), uriBuilder.Path, uriBuilder.Query);
httpSignatureHeader.Add(header.ToLower(), targetUrl);
}
else if (header.Equals(HEADER_EXPIRES))
{
var expireDateTime = dateTime.AddSeconds(SignatureValidityPeriod);
httpSignatureHeader.Add(header.ToLower(), GetUnixTime(expireDateTime).ToString());
}
else if (header.Equals(HEADER_DATE))
{
var utcDateTime = dateTime.ToString("r").ToString();
httpSignatureHeader.Add(header.ToLower(), utcDateTime);
httpSignedRequestHeader.Add(HEADER_DATE, utcDateTime);
}
else if (header.Equals(HEADER_HOST))
{
httpSignatureHeader.Add(header.ToLower(), uriBuilder.Host);
httpSignedRequestHeader.Add(HEADER_HOST, uriBuilder.Host);
}
else if (header.Equals(HEADER_CREATED))
{
httpSignatureHeader.Add(header.ToLower(), GetUnixTime(dateTime).ToString());
}
else if (header.Equals(HEADER_DIGEST))
{
httpSignedRequestHeader.Add(HEADER_DIGEST, Digest);
httpSignatureHeader.Add(header.ToLower(), Digest);
}
else
{
bool isHeaderFound = false;
foreach (var item in requestOptions.HeaderParameters)
{
if (string.Equals(item.Key, header, StringComparison.OrdinalIgnoreCase))
{
httpSignatureHeader.Add(header.ToLower(), item.Value.ToString());
isHeaderFound = true;
break;
}
}
if (!isHeaderFound)
{
throw new Exception(string.Format("Cannot sign HTTP request.Request does not contain the {0} header.",header));
}
}
}
var headersKeysString = String.Join(" ", httpSignatureHeader.Keys);
var headerValuesList = new List<string>();
foreach (var keyVal in httpSignatureHeader)
{
headerValuesList.Add(string.Format("{0}: {1}", keyVal.Key, keyVal.Value));
}
//Concatinate headers value separated by new line
var headerValuesString = string.Join("\n", headerValuesList);
var signatureStringHash = GetStringHash(HashAlgorithm.ToString(), headerValuesString);
string headerSignatureStr = null;
var keyType = GetKeyType(KeyFilePath);
if (keyType == PrivateKeyType.RSA)
{
headerSignatureStr = GetRSASignature(signatureStringHash);
}
else if (keyType == PrivateKeyType.ECDSA)
{
headerSignatureStr = GetECDSASignature(signatureStringHash);
}
var cryptographicScheme = "hs2019";
var authorizationHeaderValue = string.Format("Signature keyId=\"{0}\",algorithm=\"{1}\"",
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.Add(HEADER_AUTHORIZATION, authorizationHeaderValue);
return httpSignedRequestHeader;
}
private byte[] GetStringHash(string hashName, string stringToBeHashed)
{
var hashAlgorithm = System.Security.Cryptography.HashAlgorithm.Create(hashName);
var bytes = Encoding.UTF8.GetBytes(stringToBeHashed);
var stringHash = hashAlgorithm.ComputeHash(bytes);
return stringHash;
}
private int GetUnixTime(DateTime date2)
{
DateTime date1 = new DateTime(1970, 01, 01);
TimeSpan timeSpan = date2 - date1;
return (int)timeSpan.TotalSeconds;
}
private string GetRSASignature(byte[] stringToSign)
{
RSA rsa = GetRSAProviderFromPemFile(KeyFilePath, KeyPassPhrase);
if (SigningAlgorithm == "RSASSA-PSS")
{
var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pss);
return Convert.ToBase64String(signedbytes);
}
else if (SigningAlgorithm == "PKCS1-v15")
{
var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signedbytes);
}
return string.Empty;
}
/// <summary>
/// Gets the ECDSA signature
/// </summary>
/// <param name="dataToSign"></param>
/// <returns></returns>
private string GetECDSASignature(byte[] dataToSign)
{
if (!File.Exists(KeyFilePath))
{
throw new Exception("key file path does not exist.");
}
var ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----";
var ecKeyFooter = "-----END EC PRIVATE KEY-----";
var keyStr = File.ReadAllText(KeyFilePath);
var ecKeyBase64String = keyStr.Replace(ecKeyHeader, "").Replace(ecKeyFooter, "").Trim();
var keyBytes = System.Convert.FromBase64String(ecKeyBase64String);
var ecdsa = ECDsa.Create();
var bytCount = 0;
#if (NETCOREAPP3_0 || NETCOREAPP3_1 || NET5_0)
if (configuration.KeyPassPhrase != null)
{
ecdsa.ImportEncryptedPkcs8PrivateKey(keyPassPhrase, keyBytes, out bytCount);
}
else
{
ecdsa.ImportPkcs8PrivateKey(keyBytes, out bytCount);
}
var signedBytes = ecdsa.SignHash(dataToSign);
var derBytes = ConvertToECDSAANS1Format(signedBytes);
var signedString = System.Convert.ToBase64String(derBytes);
return signedString;
#else
throw new Exception("ECDSA signing is supported only on NETCOREAPP3_0 and above");
#endif
}
private byte[] ConvertToECDSAANS1Format(byte[] signedBytes)
{
var derBytes = new List<byte>();
byte derLength = 68; //default lenght for ECDSA code signinged bit 0x44
byte rbytesLength = 32; //R length 0x20
byte sbytesLength = 32; //S length 0x20
var rBytes = new List<byte>();
var sBytes = new List<byte>();
for (int i = 0; i < 32; i++)
{
rBytes.Add(signedBytes[i]);
}
for (int i = 32; i < 64; i++)
{
sBytes.Add(signedBytes[i]);
}
if (rBytes[0] > 0x7F)
{
derLength++;
rbytesLength++;
var tempBytes = new List<byte>();
tempBytes.AddRange(rBytes);
rBytes.Clear();
rBytes.Add(0x00);
rBytes.AddRange(tempBytes);
}
if (sBytes[0] > 0x7F)
{
derLength++;
sbytesLength++;
var tempBytes = new List<byte>();
tempBytes.AddRange(sBytes);
sBytes.Clear();
sBytes.Add(0x00);
sBytes.AddRange(tempBytes);
}
derBytes.Add(48); //start of the sequence 0x30
derBytes.Add(derLength); //total length r lenth, type and r bytes
derBytes.Add(2); //tag for integer
derBytes.Add(rbytesLength); //length of r
derBytes.AddRange(rBytes);
derBytes.Add(2); //tag for integer
derBytes.Add(sbytesLength); //length of s
derBytes.AddRange(sBytes);
return derBytes.ToArray();
}
private RSACryptoServiceProvider GetRSAProviderFromPemFile(String pemfile, SecureString keyPassPharse = null)
{
const String pempubheader = "-----BEGIN PUBLIC KEY-----";
const String pempubfooter = "-----END PUBLIC KEY-----";
bool isPrivateKeyFile = true;
byte[] pemkey = null;
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;
}
private 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;
}
}
private RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
private int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current);
//last ReadByte wasn't a removed zero, so back up a byte
return count;
}
private byte[] GetEncryptedKey(byte[] salt, SecureString secpswd, int count, int miter)
{
IntPtr unmanagedPswd = IntPtr.Zero;
int HASHLENGTH = 16; //MD5 bytes
byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store contatenated Mi hashed results
byte[] psbytes = new byte[secpswd.Length];
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
// --- contatenate salt and pswd bytes into fixed data array ---
byte[] data00 = new byte[psbytes.Length + salt.Length];
Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes
// ---- do multi-hashing and contatenate results D1, D2 ... into keymaterial bytes ----
MD5 md5 = new MD5CryptoServiceProvider();
byte[] result = null;
byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget
for (int j = 0; j < miter; j++)
{
// ---- Now hash consecutively for count times ------
if (j == 0)
result = data00; //initialize
else
{
Array.Copy(result, hashtarget, result.Length);
Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
result = hashtarget;
}
for (int i = 0; i < count; i++)
result = md5.ComputeHash(result);
Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //contatenate to keymaterial
}
byte[] deskey = new byte[24];
Array.Copy(keymaterial, deskey, deskey.Length);
Array.Clear(psbytes, 0, psbytes.Length);
Array.Clear(data00, 0, data00.Length);
Array.Clear(result, 0, result.Length);
Array.Clear(hashtarget, 0, hashtarget.Length);
Array.Clear(keymaterial, 0, keymaterial.Length);
return deskey;
}
private byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
{
MemoryStream memst = new MemoryStream();
TripleDES alg = TripleDES.Create();
alg.Key = desKey;
alg.IV = IV;
try
{
CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherData, 0, cipherData.Length);
cs.Close();
}
catch (Exception)
{
return null;
}
byte[] decryptedData = memst.ToArray();
return decryptedData;
}
/// <summary>
/// Detect the key type from the pem file.
/// </summary>
/// <param name="keyFilePath">key file path in pem format</param>
/// <returns></returns>
private PrivateKeyType GetKeyType(string keyFilePath)
{
if (!File.Exists(keyFilePath))
{
throw new Exception("Key file path does not exist.");
}
var ecPrivateKeyHeader = "BEGIN EC PRIVATE KEY";
var ecPrivateKeyFooter = "END EC PRIVATE KEY";
var rsaPrivateKeyHeader = "BEGIN RSA PRIVATE KEY";
var rsaPrivateFooter = "END RSA PRIVATE KEY";
var pkcs8Header = "BEGIN PRIVATE KEY";
var pkcs8Footer = "END PRIVATE KEY";
var keyType = PrivateKeyType.None;
var key = File.ReadAllLines(keyFilePath);
if (key[0].ToString().Contains(rsaPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(rsaPrivateFooter))
{
keyType = PrivateKeyType.RSA;
}
else if (key[0].ToString().Contains(ecPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter))
{
keyType = PrivateKeyType.ECDSA;
}
else if (key[0].ToString().Contains(ecPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(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 = PrivateKeyType.ECDSA;
}
else
{
throw new Exception("Either the key is invalid or key is not supported");
}
return keyType;
}
#endregion
}
}

View File

@ -104,5 +104,10 @@ namespace Org.OpenAPITools.Client
/// </summary> /// </summary>
/// <value>X509 Certificate collection.</value> /// <value>X509 Certificate collection.</value>
X509CertificateCollection ClientCertificates { get; } X509CertificateCollection ClientCertificates { get; }
/// <summary>
/// Gets the HTTPSigning configuration
/// </summary>
HTTPSigningConfiguration HTTPSigningConfiguration { get; }
} }
} }