using CryptoExchange.Net.Clients; using CryptoExchange.Net.Converters.SystemTextJson; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; using System; using System.Collections.Generic; using System.Globalization; using System.Net.Http; using System.Security.Cryptography; using System.Text; namespace CryptoExchange.Net.Authentication { /// /// Base class for authentication providers /// public abstract class AuthenticationProvider { internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider(); /// /// Provided credentials /// protected internal readonly ApiCredentials _credentials; /// /// Byte representation of the secret /// protected byte[] _sBytes; /// /// Get the API key of the current credentials /// public string ApiKey => _credentials.Key!; /// /// Get the Passphrase of the current credentials /// public string? Pass => _credentials.Pass; /// /// ctor /// /// protected AuthenticationProvider(ApiCredentials credentials) { if (credentials.Key == null || credentials.Secret == null) throw new ArgumentException("ApiKey/Secret needed"); _credentials = credentials; _sBytes = Encoding.UTF8.GetBytes(credentials.Secret); } /// /// Authenticate a request /// /// The Api client sending the request /// The request configuration public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig); /// /// SHA256 sign the data and return the bytes /// /// /// protected static byte[] SignSHA256Bytes(string data) { using var encryptor = SHA256.Create(); return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); } /// /// SHA256 sign the data and return the bytes /// /// /// protected static byte[] SignSHA256Bytes(byte[] data) { using var encryptor = SHA256.Create(); return encryptor.ComputeHash(data); } /// /// SHA256 sign the data and return the hash /// /// Data to sign /// String type /// protected static string SignSHA256(string data, SignOutputType? outputType = null) { using var encryptor = SHA256.Create(); var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// SHA256 sign the data and return the hash /// /// Data to sign /// String type /// protected static string SignSHA256(byte[] data, SignOutputType? outputType = null) { using var encryptor = SHA256.Create(); var resultBytes = encryptor.ComputeHash(data); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// SHA384 sign the data and return the hash /// /// Data to sign /// String type /// protected static string SignSHA384(string data, SignOutputType? outputType = null) { using var encryptor = SHA384.Create(); var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// SHA384 sign the data and return the hash /// /// Data to sign /// String type /// protected static string SignSHA384(byte[] data, SignOutputType? outputType = null) { using var encryptor = SHA384.Create(); var resultBytes = encryptor.ComputeHash(data); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// SHA384 sign the data and return the hash /// /// Data to sign /// protected static byte[] SignSHA384Bytes(string data) { using var encryptor = SHA384.Create(); return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); } /// /// SHA384 sign the data and return the hash /// /// Data to sign /// protected static byte[] SignSHA384Bytes(byte[] data) { using var encryptor = SHA384.Create(); return encryptor.ComputeHash(data); } /// /// SHA512 sign the data and return the hash /// /// Data to sign /// String type /// protected static string SignSHA512(string data, SignOutputType? outputType = null) { using var encryptor = SHA512.Create(); var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// SHA512 sign the data and return the hash /// /// Data to sign /// String type /// protected static string SignSHA512(byte[] data, SignOutputType? outputType = null) { using var encryptor = SHA512.Create(); var resultBytes = encryptor.ComputeHash(data); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// SHA512 sign the data and return the hash /// /// Data to sign /// protected static byte[] SignSHA512Bytes(string data) { using var encryptor = SHA512.Create(); return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); } /// /// SHA512 sign the data and return the hash /// /// Data to sign /// protected static byte[] SignSHA512Bytes(byte[] data) { using var encryptor = SHA512.Create(); return encryptor.ComputeHash(data); } /// /// MD5 sign the data and return the hash /// /// Data to sign /// String type /// protected static string SignMD5(string data, SignOutputType? outputType = null) { using var encryptor = MD5.Create(); var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// MD5 sign the data and return the hash /// /// Data to sign /// String type /// protected static string SignMD5(byte[] data, SignOutputType? outputType = null) { using var encryptor = MD5.Create(); var resultBytes = encryptor.ComputeHash(data); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// MD5 sign the data and return the hash /// /// Data to sign /// protected static byte[] SignMD5Bytes(string data) { using var encryptor = MD5.Create(); return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); } /// /// HMACSHA256 sign the data and return the hash /// /// Data to sign /// String type /// protected string SignHMACSHA256(string data, SignOutputType? outputType = null) => SignHMACSHA256(Encoding.UTF8.GetBytes(data), outputType); /// /// HMACSHA256 sign the data and return the hash /// /// Data to sign /// String type /// protected string SignHMACSHA256(byte[] data, SignOutputType? outputType = null) { using var encryptor = new HMACSHA256(_sBytes); var resultBytes = encryptor.ComputeHash(data); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// HMACSHA384 sign the data and return the hash /// /// Data to sign /// String type /// protected string SignHMACSHA384(string data, SignOutputType? outputType = null) => SignHMACSHA384(Encoding.UTF8.GetBytes(data), outputType); /// /// HMACSHA384 sign the data and return the hash /// /// Data to sign /// String type /// protected string SignHMACSHA384(byte[] data, SignOutputType? outputType = null) { using var encryptor = new HMACSHA384(_sBytes); var resultBytes = encryptor.ComputeHash(data); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// HMACSHA512 sign the data and return the hash /// /// Data to sign /// String type /// protected string SignHMACSHA512(string data, SignOutputType? outputType = null) => SignHMACSHA512(Encoding.UTF8.GetBytes(data), outputType); /// /// HMACSHA512 sign the data and return the hash /// /// Data to sign /// String type /// protected string SignHMACSHA512(byte[] data, SignOutputType? outputType = null) { using var encryptor = new HMACSHA512(_sBytes); var resultBytes = encryptor.ComputeHash(data); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// SHA256 sign the data /// /// /// /// protected string SignRSASHA256(byte[] data, SignOutputType? outputType = null) { using var rsa = CreateRSA(); using var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(data); var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); return outputType == SignOutputType.Base64? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// SHA384 sign the data /// /// /// /// protected string SignRSASHA384(byte[] data, SignOutputType? outputType = null) { using var rsa = CreateRSA(); using var sha384 = SHA384.Create(); var hash = sha384.ComputeHash(data); var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } /// /// SHA512 sign the data /// /// /// /// protected string SignRSASHA512(byte[] data, SignOutputType? outputType = null) { using var rsa = CreateRSA(); using var sha512 = SHA512.Create(); var hash = sha512.ComputeHash(data); var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } private RSA CreateRSA() { var rsa = RSA.Create(); if (_credentials.CredentialType == ApiCredentialsType.RsaPem) { #if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER // Read from pem private key var key = _credentials.Secret! .Replace("\n", "") .Replace("-----BEGIN PRIVATE KEY-----", "") .Replace("-----END PRIVATE KEY-----", "") .Trim(); rsa.ImportPkcs8PrivateKey(Convert.FromBase64String( key) , out _); #else throw new Exception("Pem format not supported when running from .NetStandard2.0. Convert the private key to xml format."); #endif } else if (_credentials.CredentialType == ApiCredentialsType.RsaXml) { // Read from xml private key format rsa.FromXmlString(_credentials.Secret!); } else { throw new Exception("Invalid credentials type"); } return rsa; } /// /// Convert byte array to hex string /// /// /// protected static string BytesToHexString(byte[] buff) { #if NET9_0_OR_GREATER return Convert.ToHexString(buff); #else var result = string.Empty; foreach (var t in buff) result += t.ToString("X2"); return result; #endif } /// /// Convert byte array to base64 string /// /// /// protected static string BytesToBase64String(byte[] buff) { return Convert.ToBase64String(buff); } /// /// Get current timestamp including the time sync offset from the api client /// /// /// protected DateTime GetTimestamp(RestApiClient apiClient) { return TimeProvider.GetTime().Add(apiClient.GetTimeOffset() ?? TimeSpan.Zero)!; } /// /// Get millisecond timestamp as a string including the time sync offset from the api client /// /// /// protected string GetMillisecondTimestamp(RestApiClient apiClient) { return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture); } /// /// Get millisecond timestamp as a long including the time sync offset from the api client /// /// /// protected long GetMillisecondTimestampLong(RestApiClient apiClient) { return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value; } /// /// Return the serialized request body /// /// /// /// protected static string GetSerializedBody(IMessageSerializer serializer, IDictionary parameters) { if (serializer is not IStringMessageSerializer stringSerializer) throw new InvalidOperationException("Non-string message serializer can't get serialized request body"); if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value)) return stringSerializer.Serialize(value); else return stringSerializer.Serialize(parameters); } } /// public abstract class AuthenticationProvider : AuthenticationProvider where TApiCredentials : ApiCredentials { /// protected new TApiCredentials _credentials => (TApiCredentials)base._credentials; /// /// ctor /// /// protected AuthenticationProvider(TApiCredentials credentials) : base(credentials) { } } }