Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
csharp.HttpSigningConfiguration.mustache Maven / Gradle / Ivy
{{>partial_header}}
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
{
///
/// Class for HttpSigning auth related parameter and methods
///
public class HttpSigningConfiguration
{
///
/// Initialize the HashAlgorithm and SigningAlgorithm to default value
///
public HttpSigningConfiguration()
{
HashAlgorithm = HashAlgorithmName.SHA256;
SigningAlgorithm = "PKCS1-v15";
}
///
///Gets the Api keyId
///
public string KeyId { get; set; }
///
/// Gets the Key file path
///
public string KeyFilePath { get; set; }
///
/// Specify the API key in the form of a string, either configure the KeyString property or configure the KeyFilePath property.
///
public string KeyString { get; set; }
///
/// Gets the key pass phrase for password protected key
///
public SecureString KeyPassPhrase { get; set; }
///
/// Gets the HTTP signing header
///
public List HttpSigningHeader { get; set; }
///
/// Gets the hash algorithm sha256 or sha512
///
public HashAlgorithmName HashAlgorithm { get; set; }
///
/// Gets the signing algorithm
///
public string SigningAlgorithm { get; set; }
///
/// Gets the Signature validity period in seconds
///
public int SignatureValidityPeriod { get; set; }
private enum PrivateKeyType
{
None = 0,
RSA = 1,
ECDSA = 2,
}
///
/// Gets the Headers for HttpSigning
///
/// Base path
/// HTTP method
/// Path
/// Request options
/// Http signed headers
internal Dictionary 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";
//Read the api key from the file
if(string.IsNullOrEmpty(this.KeyString))
{
this.KeyString = ReadApiKeyFromFile(KeyFilePath);
}
if(string.IsNullOrEmpty(KeyString))
{
throw new Exception("No API key has been provided.");
}
//Hash table to store singed headers
var HttpSignedRequestHeader = new Dictionary();
var HttpSignatureHeader = new Dictionary();
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 (NETCOREAPP)
if (parameter.Value.Count > 1)
{ // array
foreach (var value in parameter.Value)
{
httpValues.Add(HttpUtility.UrlEncode(parameter.Key) + "[]", value);
}
}
else
{
httpValues.Add(HttpUtility.UrlEncode(parameter.Key), parameter.Value[0]);
}
#else
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]);
}
#endif
}
var uriBuilder = new UriBuilder(string.Concat(basePath, path));
uriBuilder.Query = httpValues.ToString().Replace("+", "%20");
var dateTime = DateTime.Now;
string Digest = string.Empty;
//get the body
string requestBody = string.Empty;
if (requestOptions.Data != null)
{
var serializerSettings = new JsonSerializerSettings();
requestBody = JsonConvert.SerializeObject(requestOptions.Data, serializerSettings);
}
if (HashAlgorithm == HashAlgorithmName.SHA256)
{
var bodyDigest = GetStringHash(HashAlgorithm, requestBody);
Digest = string.Format("SHA-256={0}", Convert.ToBase64String(bodyDigest));
}
else if (HashAlgorithm == HashAlgorithmName.SHA512)
{
var bodyDigest = GetStringHash(HashAlgorithm, 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.ToUniversalTime().ToString("r");
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();
foreach (var keyVal in HttpSignatureHeader)
{
headerValuesList.Add(string.Format("{0}: {1}", keyVal.Key, keyVal.Value));
}
//Concatenate headers value separated by new line
var headerValuesString = string.Join("\n", headerValuesList);
var signatureStringHash = GetStringHash(HashAlgorithm, headerValuesString);
string headerSignatureStr = null;
var keyType = GetKeyType(KeyString);
if (keyType == PrivateKeyType.RSA)
{
headerSignatureStr = GetRSASignature(signatureStringHash);
}
else if (keyType == PrivateKeyType.ECDSA)
{
headerSignatureStr = GetECDSASignature(signatureStringHash);
}
else
{
throw new Exception(string.Format("Private key type {0} not supported", keyType));
}
const string 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(HashAlgorithmName hashAlgorithmName, string stringToBeHashed)
{
HashAlgorithm{{nrt?}} hashAlgorithm = null;
if (hashAlgorithmName == HashAlgorithmName.SHA1)
hashAlgorithm = SHA1.Create();
if (hashAlgorithmName == HashAlgorithmName.SHA256)
hashAlgorithm = SHA256.Create();
if (hashAlgorithmName == HashAlgorithmName.SHA512)
hashAlgorithm = SHA512.Create();
if (hashAlgorithmName == HashAlgorithmName.MD5)
hashAlgorithm = MD5.Create();
if (hashAlgorithm == null)
throw new NullReferenceException($"{ nameof(hashAlgorithm) } was null.");
byte[] bytes = Encoding.UTF8.GetBytes(stringToBeHashed);
byte[] 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(KeyString, 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);
}
else
{
return string.Empty;
}
}
///
/// Gets the ECDSA signature
///
///
/// ECDSA signature
private string GetECDSASignature(byte[] dataToSign)
{
{{#net60OrLater}}
if (!File.Exists(KeyFilePath))
throw new Exception("key file path does not exist.");
var keyStr = KeyString;
const string ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----";
const string ecKeyFooter = "-----END EC PRIVATE KEY-----";
var ecKeyBase64String = keyStr.Replace(ecKeyHeader, "").Replace(ecKeyFooter, "").Trim();
var keyBytes = System.Convert.FromBase64String(ecKeyBase64String);
var ecdsa = ECDsa.Create();
var byteCount = 0;
if (KeyPassPhrase != null)
{
IntPtr unmanagedString = IntPtr.Zero;
try
{
// convert secure string to byte array
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(KeyPassPhrase);
ecdsa.ImportEncryptedPkcs8PrivateKey(Encoding.UTF8.GetBytes(Marshal.PtrToStringUni(unmanagedString)), keyBytes, out byteCount);
}
finally
{
if (unmanagedString != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(unmanagedString);
}
}
}
else
ecdsa.ImportPkcs8PrivateKey(keyBytes, out byteCount);
var derBytes = ecdsa.SignHash(dataToSign, DSASignatureFormat.Rfc3279DerSequence);
var signedString = System.Convert.ToBase64String(derBytes);
return signedString;
{{/net60OrLater}}
{{^net60OrLater}}
throw new Exception("ECDSA signing is supported only on NETCOREAPP3_0 and above");
{{/net60OrLater}}
}
///
/// Convert ANS1 format to DER format. Not recommended to use because it generate inavlid signature occationally.
///
///
///
private byte[] ConvertToECDSAANS1Format(byte[] signedBytes)
{
var derBytes = new List();
byte derLength = 68; //default length for ECDSA code signing bit 0x44
byte rbytesLength = 32; //R length 0x20
byte sbytesLength = 32; //S length 0x20
var rBytes = new List();
var sBytes = new List();
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();
tempBytes.AddRange(rBytes);
rBytes.Clear();
rBytes.Add(0x00);
rBytes.AddRange(tempBytes);
}
if (sBytes[0] > 0x7F)
{
derLength++;
sbytesLength++;
var tempBytes = new List();
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 length, 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 keyString, SecureString keyPassPhrase = null)
{
const string pempubheader = "-----BEGIN PUBLIC KEY-----";
const string pempubfooter = "-----END PUBLIC KEY-----";
bool isPrivateKeyFile = true;
byte[] pemkey = null;
string pemstr = keyString;
if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
{
isPrivateKeyFile = false;
}
if (isPrivateKeyFile)
{
pemkey = ConvertPrivateKeyToBytes(pemstr, keyPassPhrase);
if (pemkey == null)
{
return null;
}
return DecodeRSAPrivateKey(pemkey);
}
return null;
}
private byte[] ConvertPrivateKeyToBytes(string instr, SecureString keyPassPhrase = 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 format
return null;
}
byte[] deskey = GetEncryptedKey(salt, keyPassPhrase, 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[] bytesModulus, bytesE, bytesD, bytesP, bytesQ, bytesDP, bytesDQ, bytesIQ;
// --------- 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);
bytesModulus = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
bytesE = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
bytesD = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
bytesP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
bytesQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
bytesDP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
bytesDQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
bytesIQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = bytesModulus;
RSAparams.Exponent = bytesE;
RSAparams.D = bytesD;
RSAparams.P = bytesP;
RSAparams.Q = bytesQ;
RSAparams.DP = bytesDP;
RSAparams.DQ = bytesDQ;
RSAparams.InverseQ = bytesIQ;
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;
const int HASHLENGTH = 16; //MD5 bytes
byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store concatenated Mi hashed results
byte[] psbytes = new byte[secpswd.Length];
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
// --- concatenate 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 concatenate results D1, D2 ... into keymaterial bytes ----
MD5 md5 = MD5.Create();
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); //concatenate 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;
}
///
/// Detect the key type from the pem file.
///
/// api key in string format
/// Private Key Type
private PrivateKeyType GetKeyType(string keyString)
{
string[] key = null;
if (string.IsNullOrEmpty(keyString))
{
throw new Exception("No API key has been provided.");
}
const string ecPrivateKeyHeader = "BEGIN EC PRIVATE KEY";
const string ecPrivateKeyFooter = "END EC PRIVATE KEY";
const string rsaPrivateKeyHeader = "BEGIN RSA PRIVATE KEY";
const string rsaPrivateFooter = "END RSA PRIVATE KEY";
//var pkcs8Header = "BEGIN PRIVATE KEY";
//var pkcs8Footer = "END PRIVATE KEY";
PrivateKeyType keyType;
key = KeyString.TrimEnd().Split('\n');
if (key[0].Contains(rsaPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(rsaPrivateFooter))
{
keyType = PrivateKeyType.RSA;
}
else if (key[0].Contains(ecPrivateKeyHeader) &&
key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter))
{
keyType = PrivateKeyType.ECDSA;
}
else
{
throw new Exception("The key file path does not exist or key is invalid or key is not supported");
}
return keyType;
}
///
/// Read the api key form the api key file path and stored it in KeyString property.
///
/// api key file path
private string ReadApiKeyFromFile(string apiKeyFilePath)
{
string apiKeyString = null;
if(File.Exists(apiKeyFilePath))
{
apiKeyString = File.ReadAllText(apiKeyFilePath);
}
else
{
throw new Exception("Provided API key file path does not exists.");
}
return apiKeyString;
}
}
}