using CryptoExchange.Net.Clients;
using CryptoExchange.Net.Converters.SystemTextJson;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
#if NET8_0_OR_GREATER
using NSec.Cryptography;
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using CryptoExchange.Net.Sockets;
using CryptoExchange.Net.Sockets.Default;
using System.Net;
namespace CryptoExchange.Net.Authentication
{
///
/// Base class for authentication providers
///
public abstract class AuthenticationProvider
{
internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider();
///
/// The public identifier for the provided credentials
///
public abstract string Key { get; }
///
/// Authenticate a REST request
///
/// The API client sending the request
/// The request configuration
public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig);
///
/// Get an authentication query for a websocket
///
/// The API client sending the request
/// The connection to authenticate
/// Optional context required for creating the authentication query
public virtual Query? GetAuthenticationQuery(SocketApiClient apiClient, SocketConnection connection, Dictionary? context = null) => null;
///
/// 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
///
protected string SignHMACSHA256(HMACCredential credential, string data, SignOutputType? outputType = null)
=> SignHMACSHA256(credential,Encoding.UTF8.GetBytes(data), outputType);
///
/// HMACSHA256 sign the data and return the hash
///
protected string SignHMACSHA256(HMACCredential credential, byte[] data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA256(credential.GetSBytes());
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
///
/// HMACSHA384 sign the data and return the hash
///
protected string SignHMACSHA384(HMACCredential credential, string data, SignOutputType? outputType = null)
=> SignHMACSHA384(credential, Encoding.UTF8.GetBytes(data), outputType);
///
/// HMACSHA384 sign the data and return the hash
///
protected string SignHMACSHA384(HMACCredential credential, byte[] data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA384(credential.GetSBytes());
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
///
/// HMACSHA512 sign the data and return the hash
///
protected string SignHMACSHA512(HMACCredential credential, string data, SignOutputType? outputType = null)
=> SignHMACSHA512(credential, Encoding.UTF8.GetBytes(data), outputType);
///
/// HMACSHA512 sign the data and return the hash
///
protected string SignHMACSHA512(HMACCredential credential, byte[] data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA512(credential.GetSBytes());
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
///
/// SHA256 sign the data
///
protected string SignRSASHA256(RSACredential credential, byte[] data, SignOutputType? outputType = null)
{
var rsa = credential.GetSigner();
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(RSACredential credential, byte[] data, SignOutputType? outputType = null)
{
var rsa = credential.GetSigner();
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(RSACredential credential, byte[] data, SignOutputType? outputType = null)
{
var rsa = credential.GetSigner();
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);
}
#if NET8_0_OR_GREATER
///
/// Ed25519 sign the data
///
public string SignEd25519(Ed25519Credential credential, string data, SignOutputType? outputType = null)
=> SignEd25519(credential, Encoding.ASCII.GetBytes(data), outputType);
///
/// Ed25519 sign the data
///
public string SignEd25519(Ed25519Credential credential, byte[] data, SignOutputType? outputType = null)
{
var signKey = credential.GetSigningKey();
var resultBytes = SignatureAlgorithm.Ed25519.Sign(signKey, data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
#endif
///
/// Convert byte array to hex string
///
///
///
protected static string BytesToHexString(byte[] buff)
=> BytesToHexString(new ArraySegment(buff));
///
/// Convert byte array to hex string
///
///
///
protected static string BytesToHexString(ArraySegment 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 a hex encoded string to byte array
///
///
///
protected static byte[] HexToBytesString(string hexString)
{
if (hexString.StartsWith("0x"))
hexString = hexString.Substring(2);
byte[] bytes = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
string hexSubstring = hexString.Substring(i, 2);
bytes[i / 2] = Convert.ToByte(hexSubstring, 16);
}
return bytes;
}
///
/// 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, bool includeOneSecondOffset = true)
{
var result = TimeProvider.GetTime().Add(TimeOffsetManager.GetRestOffset(apiClient.ClientName) ?? TimeSpan.Zero)!;
if (includeOneSecondOffset)
result = result.AddSeconds(-1);
return result;
}
///
/// Get current timestamp including the time sync offset from the api client
///
protected DateTime GetTimestamp(SocketApiClient apiClient, bool includeOneSecondOffset = true)
{
var timestamp = TimeProvider.GetTime();
if(apiClient.ApiOptions.AutoTimestamp ?? apiClient.ClientOptions.AutoTimestamp)
timestamp = timestamp.Add(-TimeOffsetManager.GetSocketOffset(apiClient.ClientName) ?? TimeSpan.Zero)!;
if (includeOneSecondOffset)
timestamp = timestamp.AddSeconds(-1);
return timestamp;
}
///
/// Get millisecond timestamp as a string including the time sync offset from the api client
///
protected string GetMillisecondTimestamp(RestApiClient apiClient, bool includeOneSecondOffset = true)
=> DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient, includeOneSecondOffset)).Value.ToString(CultureInfo.InvariantCulture);
///
/// Get millisecond timestamp as a string including the time sync offset from the api client
///
protected string GetMillisecondTimestamp(SocketApiClient apiClient, bool includeOneSecondOffset = true)
=> DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient, includeOneSecondOffset)).Value.ToString(CultureInfo.InvariantCulture);
///
/// Get millisecond timestamp as a long including the time sync offset from the api client
///
protected long GetMillisecondTimestampLong(RestApiClient apiClient, bool includeOneSecondOffset = true)
=> DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient, includeOneSecondOffset)).Value;
///
/// Get millisecond timestamp as a long including the time sync offset from the api client
///
protected long GetMillisecondTimestampLong(SocketApiClient apiClient, bool includeOneSecondOffset = true)
=> DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient, includeOneSecondOffset)).Value;
///
/// Return the serialized request body
///
///
///
///
protected static string GetSerializedBody(IMessageSerializer serializer, Parameters? parameters)
{
if (serializer is not IStringMessageSerializer stringSerializer)
throw new InvalidOperationException("Non-string message serializer can't get serialized request body");
if (parameters?.BodyValue != null)
return stringSerializer.Serialize(parameters.BodyValue);
else
return stringSerializer.Serialize(parameters);
}
}
///
public abstract class AuthenticationProvider : AuthenticationProvider
where TApiCredentials: ApiCredentials
{
///
/// API credentials used for signing requests
///
public TApiCredentials ApiCredentials { get; set; }
///
/// ctor
///
protected AuthenticationProvider(TApiCredentials credentials)
{
credentials.Validate();
ApiCredentials = credentials;
}
}
///
public abstract class AuthenticationProvider : AuthenticationProvider
where TApiCredentials : ApiCredentials
where TCredentialType : CredentialSet
{
///
/// The specific credential type used for signing requests.
///
public TCredentialType Credential { get; }
///
public override string Key => Credential.Key;
///
/// ctor
///
protected AuthenticationProvider(
TApiCredentials credentials,
TCredentialType? credential) : base(credentials)
{
if (credential == null)
throw new ArgumentException($"Missing \"{typeof(TCredentialType).Name}\" credentials on \"{credentials.GetType().Name}\"");
Credential = credential;
}
///
/// 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)
{
if (Credential is not HMACCredential hmacCredential)
throw new InvalidOperationException($"Invalid HMAC signing without HMAC credentials provided");
return SignHMACSHA256(hmacCredential, data, outputType);
}
///
/// 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)
{
if (Credential is not HMACCredential hmacCredential)
throw new InvalidOperationException($"Invalid HMAC signing without HMAC credentials provided");
return SignHMACSHA384(hmacCredential, data, outputType);
}
///
/// 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)
{
if (Credential is not HMACCredential hmacCredential)
throw new InvalidOperationException($"Invalid HMAC signing without HMAC credentials provided");
return SignHMACSHA512(hmacCredential, data, outputType);
}
///
/// SHA256 sign the data
///
///
///
///
protected string SignRSASHA256(byte[] data, SignOutputType? outputType = null)
{
if (Credential is not RSACredential rsaCredential)
throw new InvalidOperationException($"Invalid RSA signing without RSA credentials provided");
return SignRSASHA256(rsaCredential, data, outputType);
}
///
/// SHA384 sign the data
///
///
///
///
protected string SignRSASHA384(byte[] data, SignOutputType? outputType = null)
{
if (Credential is not RSACredential rsaCredential)
throw new InvalidOperationException($"Invalid RSA signing without RSA credentials provided");
return SignRSASHA384(rsaCredential, data, outputType);
}
///
/// SHA512 sign the data
///
///
///
///
protected string SignRSASHA512(byte[] data, SignOutputType? outputType = null)
{
if (Credential is not RSACredential rsaCredential)
throw new InvalidOperationException($"Invalid RSA signing without RSA credentials provided");
return SignRSASHA512(rsaCredential, data, outputType);
}
#if NET8_0_OR_GREATER
///
/// Ed25519 sign the data
///
public string SignEd25519(string data, SignOutputType? outputType = null)
=> SignEd25519(Encoding.ASCII.GetBytes(data), outputType);
///
/// Ed25519 sign the data
///
public string SignEd25519(byte[] data, SignOutputType? outputType = null)
{
if (Credential is not Ed25519Credential ed25519Credential)
throw new InvalidOperationException($"Invalid Ed25519 signing without Ed25519 credentials provided");
return SignEd25519(ed25519Credential, data, outputType);
}
#endif
}
}