diff --git a/CryptoExchange.Net/Authentication/ApiCredentials.cs b/CryptoExchange.Net/Authentication/ApiCredentials.cs
index 141ff6a..721735f 100644
--- a/CryptoExchange.Net/Authentication/ApiCredentials.cs
+++ b/CryptoExchange.Net/Authentication/ApiCredentials.cs
@@ -1,27 +1,88 @@
using System;
+using System.Security;
namespace CryptoExchange.Net.Authentication
{
- public class ApiCredentials
+ public class ApiCredentials: IDisposable
{
///
- /// The api key
+ /// The api key to authenticate requests
///
- public string Key { get; }
+ public SecureString Key { get; }
+
///
- /// The api secret
+ /// The api secret to authenticate requests
///
- public string Secret { get; }
+ public SecureString Secret { get; }
- public ApiCredentials() { }
+ ///
+ /// The private key to authenticate requests
+ ///
+ public SecureString PrivateKey { get; }
- public ApiCredentials(string key, string secret)
+ ///
+ /// Create Api credentials providing a private key for authenication
+ ///
+ /// The private key used for signing
+ public ApiCredentials(SecureString privateKey)
{
- if(string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
- throw new ArgumentException("Apikey or apisecret not provided");
+ PrivateKey = Key;
+ }
+ ///
+ /// Create Api credentials providing a private key for authenication
+ ///
+ /// The private key used for signing
+ public ApiCredentials(string privateKey)
+ {
+ if(string.IsNullOrEmpty(privateKey))
+ throw new ArgumentException("Private key can't be null/empty");
+
+ var securePrivateKey = new SecureString();
+ foreach (var c in privateKey)
+ securePrivateKey.AppendChar(c);
+ securePrivateKey.MakeReadOnly();
+ PrivateKey = securePrivateKey;
+ }
+
+ ///
+ /// Create Api credentials providing a api key and secret for authenciation
+ ///
+ /// The api key used for identification
+ /// The api secret used for signing
+ public ApiCredentials(SecureString key, SecureString secret)
+ {
Key = key;
Secret = secret;
}
+
+ ///
+ /// Create Api credentials providing a private key for authenication
+ ///
+ /// The private key used for signing
+ public ApiCredentials(string key, string secret)
+ {
+ if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
+ throw new ArgumentException("Key and secret can't be null/empty");
+
+ var secureApiKey = new SecureString();
+ foreach (var c in key)
+ secureApiKey.AppendChar(c);
+ secureApiKey.MakeReadOnly();
+ Key = secureApiKey;
+
+ var secureApiSecret = new SecureString();
+ foreach (var c in secret)
+ secureApiSecret.AppendChar(c);
+ secureApiSecret.MakeReadOnly();
+ Secret = secureApiSecret;
+ }
+
+ public void Dispose()
+ {
+ Key?.Dispose();
+ Secret?.Dispose();
+ PrivateKey?.Dispose();
+ }
}
}
diff --git a/CryptoExchange.Net/ExchangeClient.cs b/CryptoExchange.Net/ExchangeClient.cs
index 15a34dc..6bf89cd 100644
--- a/CryptoExchange.Net/ExchangeClient.cs
+++ b/CryptoExchange.Net/ExchangeClient.cs
@@ -15,7 +15,7 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net
{
- public abstract class ExchangeClient: IDisposable
+ public abstract class ExchangeClient : IDisposable
{
public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
@@ -29,7 +29,7 @@ namespace CryptoExchange.Net
{
DateTimeZoneHandling = DateTimeZoneHandling.Utc
});
-
+
protected ExchangeClient(ExchangeOptions exchangeOptions, AuthenticationProvider authenticationProvider)
{
log = new Log();
@@ -47,14 +47,14 @@ namespace CryptoExchange.Net
log.Level = exchangeOptions.LogVerbosity;
apiProxy = exchangeOptions.Proxy;
- if(apiProxy != null)
+ if (apiProxy != null)
log.Write(LogVerbosity.Info, $"Setting api proxy to {exchangeOptions.Proxy.Host}:{exchangeOptions.Proxy.Port}");
rateLimiters = new List();
foreach (var rateLimiter in exchangeOptions.RateLimiters)
rateLimiters.Add(rateLimiter);
}
-
+
///
/// Adds a rate limiter to the client. There are 2 choices, the and the .
///
@@ -71,7 +71,7 @@ namespace CryptoExchange.Net
{
rateLimiters.Clear();
}
-
+
///
/// Set the authentication provider
///
@@ -104,12 +104,17 @@ namespace CryptoExchange.Net
}
string paramString = null;
- if (parameters != null) {
- paramString = "with parameters ";
+ if (parameters != null)
+ {
+ paramString = "with parameters";
+
foreach (var param in parameters)
- paramString += $"{param.Key}={param.Value}, ";
+ paramString += $" {param.Key}={(param.Value.GetType().IsArray ? $"[{string.Join(", ", ((object[])param.Value).Select(p => p.ToString()))}]": param.Value )},";
+
+ paramString = paramString.Trim(',');
}
- log.Write(LogVerbosity.Debug, $"Sending {(signed ? "signed": "")} request to {request.Uri} {(paramString ?? "")}");
+
+ log.Write(LogVerbosity.Debug, $"Sending {(signed ? "signed" : "")} request to {request.Uri} {(paramString ?? "")}");
var result = await ExecuteRequest(request).ConfigureAwait(false);
return result.Error != null ? new CallResult(null, result.Error) : Deserialize(result.Data);
}
@@ -123,7 +128,14 @@ namespace CryptoExchange.Net
if (!uriString.EndsWith("?"))
uriString += "?";
- uriString += $"{string.Join("&", parameters.Select(s => $"{s.Key}={s.Value}"))}";
+ var arraysParameters = parameters.Where(p => p.Value.GetType().IsArray).ToList();
+ foreach(var arrayEntry in arraysParameters)
+ {
+ uriString += $"{string.Join("&", ((object[])arrayEntry.Value).Select(v => $"{arrayEntry.Key}[]={v}"))}&";
+ }
+
+ uriString += $"{string.Join("&", parameters.Where(p => !p.Value.GetType().IsArray).Select(s => $"{s.Key}={s.Value}"))}";
+ uriString = uriString.TrimEnd('&');
}
if (authProvider != null)
@@ -189,7 +201,7 @@ namespace CryptoExchange.Net
return new ServerError(error);
}
- protected CallResult Deserialize(string data, bool checkObject = true) where T: class
+ protected CallResult Deserialize(string data, bool checkObject = true) where T : class
{
try
{
@@ -202,7 +214,7 @@ namespace CryptoExchange.Net
CheckObject(typeof(T), o);
else
{
- var ary = (JArray) obj;
+ var ary = (JArray)obj;
if (ary.HasValues && ary[0] is JObject jObject)
CheckObject(typeof(T).GetElementType(), jObject);
}
@@ -227,7 +239,7 @@ namespace CryptoExchange.Net
log.Write(LogVerbosity.Error, info);
return new CallResult(null, new DeserializeError(info));
}
- catch(Exception ex)
+ catch (Exception ex)
{
var info = $"Deserialize Unknown Exception: {ex.Message}. Received data: {data}";
log.Write(LogVerbosity.Error, info);
@@ -237,6 +249,10 @@ namespace CryptoExchange.Net
private void CheckObject(Type type, JObject obj)
{
+ if (type.GetCustomAttribute(true) != null)
+ // If type has a custom JsonConverter we assume this will handle property mapping
+ return;
+
bool isDif = false;
var properties = new List();
var props = type.GetProperties();
@@ -247,7 +263,7 @@ namespace CryptoExchange.Net
if (ignore != null)
continue;
- properties.Add(attr == null ? prop.Name : ((JsonPropertyAttribute) attr).PropertyName);
+ properties.Add(attr == null ? prop.Name : ((JsonPropertyAttribute)attr).PropertyName);
}
foreach (var token in obj)
{
@@ -256,7 +272,7 @@ namespace CryptoExchange.Net
{
d = properties.SingleOrDefault(p => p.ToLower() == token.Key.ToLower());
if (d == null && !(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)))
- {
+ {
log.Write(LogVerbosity.Warning, $"Didn't find property `{token.Key}` in object of type `{type.Name}`");
isDif = true;
continue;
@@ -269,9 +285,9 @@ namespace CryptoExchange.Net
continue;
if (!IsSimple(propType) && propType != typeof(DateTime))
{
- if(propType.IsArray && token.Value.HasValues && ((JArray)token.Value).Any() && ((JArray)token.Value)[0] is JObject)
+ if (propType.IsArray && token.Value.HasValues && ((JArray)token.Value).Any() && ((JArray)token.Value)[0] is JObject)
CheckObject(propType.GetElementType(), (JObject)token.Value[0]);
- else if(token.Value is JObject)
+ else if (token.Value is JObject)
CheckObject(propType, (JObject)token.Value);
}
}
@@ -282,7 +298,7 @@ namespace CryptoExchange.Net
log.Write(LogVerbosity.Warning, $"Didn't find key `{prop}` in returned data object of type `{type.Name}`");
}
- if(isDif)
+ if (isDif)
log.Write(LogVerbosity.Debug, "Returned data: " + obj);
}
@@ -298,7 +314,7 @@ namespace CryptoExchange.Net
}
else
{
- if (((JsonPropertyAttribute) attr).PropertyName == name)
+ if (((JsonPropertyAttribute)attr).PropertyName == name)
return prop;
}
}
@@ -320,6 +336,7 @@ namespace CryptoExchange.Net
public virtual void Dispose()
{
+ authProvider?.Credentials?.Dispose();
log.Write(LogVerbosity.Debug, "Disposing exchange client");
}
}
diff --git a/CryptoExchange.Net/ExtensionMethods.cs b/CryptoExchange.Net/ExtensionMethods.cs
index 83dec83..f3fc934 100644
--- a/CryptoExchange.Net/ExtensionMethods.cs
+++ b/CryptoExchange.Net/ExtensionMethods.cs
@@ -1,4 +1,7 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Security;
namespace CryptoExchange.Net
{
@@ -25,5 +28,30 @@ namespace CryptoExchange.Net
if (value != null)
parameters.Add(key, value);
}
+
+ public static string GetString(this SecureString source)
+ {
+ string result = null;
+ int length = source.Length;
+ IntPtr pointer = IntPtr.Zero;
+ char[] chars = new char[length];
+
+ try
+ {
+ pointer = Marshal.SecureStringToBSTR(source);
+ Marshal.Copy(pointer, chars, 0, length);
+
+ result = string.Join("", chars);
+ }
+ finally
+ {
+ if (pointer != IntPtr.Zero)
+ {
+ Marshal.ZeroFreeBSTR(pointer);
+ }
+ }
+
+ return result;
+ }
}
}