-
-
Notifications
You must be signed in to change notification settings - Fork 374
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement new Coinbase JWT Bearer token for Authentication header (#857)
- Loading branch information
Showing
5 changed files
with
141 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
src/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI_Const.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
src/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI_JWT.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
using System; | ||
using System.IdentityModel.Tokens.Jwt; | ||
using System.Net.Http; | ||
using System.Security.Cryptography; | ||
using Microsoft.IdentityModel.Tokens; | ||
using Org.BouncyCastle.Crypto; | ||
using Org.BouncyCastle.Crypto.Parameters; | ||
using Org.BouncyCastle.OpenSsl; | ||
using Org.BouncyCastle.Security; | ||
using System.IO; | ||
|
||
namespace ExchangeSharp | ||
{ | ||
public partial class ExchangeCoinbaseAPI | ||
{ // Currently using .NET 4.7.2 version of code from https://docs.cdp.coinbase.com/advanced-trade/docs/rest-api-auth | ||
// since we currently target netstandard2.0. If we upgrade in the future, we can change to the simpler .NET core code | ||
static string GenerateToken(string name, string privateKeyPem, string uri) | ||
{ | ||
// Load EC private key using BouncyCastle | ||
var ecPrivateKey = LoadEcPrivateKeyFromPem(privateKeyPem); | ||
|
||
// Create security key from the manually created ECDsa | ||
var ecdsa = GetECDsaFromPrivateKey(ecPrivateKey); | ||
var securityKey = new ECDsaSecurityKey(ecdsa); | ||
|
||
// Signing credentials | ||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.EcdsaSha256); | ||
|
||
var now = DateTimeOffset.UtcNow; | ||
|
||
// Header and payload | ||
var header = new JwtHeader(credentials); | ||
header["kid"] = name; | ||
header["nonce"] = GenerateNonce(); // Generate dynamic nonce | ||
|
||
var payload = new JwtPayload | ||
{ | ||
{ "iss", "coinbase-cloud" }, | ||
{ "sub", name }, | ||
{ "nbf", now.ToUnixTimeSeconds() }, | ||
{ "exp", now.AddMinutes(2).ToUnixTimeSeconds() }, | ||
{ "uri", uri } | ||
}; | ||
|
||
var token = new JwtSecurityToken(header, payload); | ||
|
||
var tokenHandler = new JwtSecurityTokenHandler(); | ||
return tokenHandler.WriteToken(token); | ||
} | ||
|
||
// Method to generate a dynamic nonce | ||
static string GenerateNonce(int length = 64) | ||
{ | ||
byte[] nonceBytes = new byte[length / 2]; // Allocate enough space for the desired length (in hex characters) | ||
using (var rng = RandomNumberGenerator.Create()) | ||
{ | ||
rng.GetBytes(nonceBytes); | ||
} | ||
return BitConverter.ToString(nonceBytes).Replace("-", "").ToLower(); // Convert byte array to hex string | ||
} | ||
|
||
// Method to load EC private key from PEM using BouncyCastle | ||
static ECPrivateKeyParameters LoadEcPrivateKeyFromPem(string privateKeyPem) | ||
{ | ||
using (var stringReader = new StringReader(privateKeyPem)) | ||
{ | ||
var pemReader = new PemReader(stringReader); | ||
var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair; | ||
if (keyPair == null) | ||
throw new InvalidOperationException("Failed to load EC private key from PEM"); | ||
|
||
return (ECPrivateKeyParameters)keyPair.Private; | ||
} | ||
} | ||
|
||
// Method to convert ECPrivateKeyParameters to ECDsa | ||
static ECDsa GetECDsaFromPrivateKey(ECPrivateKeyParameters privateKey) | ||
{ | ||
var q = privateKey.Parameters.G.Multiply(privateKey.D).Normalize(); | ||
var qx = q.AffineXCoord.GetEncoded(); | ||
var qy = q.AffineYCoord.GetEncoded(); | ||
|
||
var ecdsaParams = new ECParameters | ||
{ | ||
Curve = ECCurve.NamedCurves.nistP256, // Adjust if you're using a different curve | ||
Q = | ||
{ | ||
X = qx, | ||
Y = qy | ||
}, | ||
D = privateKey.D.ToByteArrayUnsigned() | ||
}; | ||
|
||
return ECDsa.Create(ecdsaParams); | ||
} | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
src/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseExchangeAPI.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace ExchangeSharp.Coinbase | ||
{ | ||
/// <summary> | ||
/// partial implementation for Coinbase Exchange, which is for businesses (rather than Advanced which is for individuals). Since there may not be many users of Coinbase Exchange, will not expose this for now to avoid confusion | ||
/// </summary> | ||
public sealed partial class ExchangeCoinbaseExchangeAPI : ExchangeCoinbaseAPI | ||
{ | ||
protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary<string, object> payload) | ||
{ // Coinbase Exchange uses the old signing method rather than JWT | ||
if (CanMakeAuthenticatedRequest(payload)) | ||
{ | ||
string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToStringInvariant(); // If you're skittish about the local clock, you may retrieve the timestamp from the Coinbase Site | ||
string body = CryptoUtility.GetJsonForPayload(payload); | ||
|
||
// V2 wants PathAndQuery, V3 wants LocalPath for the sig (I guess they wanted to shave a nano-second or two - silly) | ||
string path = request.RequestUri.AbsoluteUri.StartsWith(BaseUrlV2) ? request.RequestUri.PathAndQuery : request.RequestUri.LocalPath; | ||
string signature = CryptoUtility.SHA256Sign(timestamp + request.Method.ToUpperInvariant() + path + body, PrivateApiKey.ToUnsecureString()); | ||
|
||
request.AddHeader("CB-ACCESS-KEY", PublicApiKey.ToUnsecureString()); | ||
request.AddHeader("CB-ACCESS-SIGN", signature); | ||
request.AddHeader("CB-ACCESS-TIMESTAMP", timestamp); | ||
if (request.Method == "POST") await CryptoUtility.WriteToRequestAsync(request, body); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters