英文:
eBay Digital Signature for Restful APIs / sign Signature-Base with given Private Key in C#
问题
我花了大约两天的时间来创建特定API调用的数字签名,具体信息请参见:https://developer.ebay.com/develop/guides/digital-signatures-for-apis
由于文档,特别是针对C#的文档相当欠缺,尤其是在调用Restful API时进行的这一关键更改方面,我想分享我最终有效的解决方案。
总的来说,我的问题是创建所有标头和签名签名基础。
已经使用了通过客户端凭据授予流程创建的访问令牌的“OAuth范围”,通过“密钥管理API”创建签名密钥并不是一件大事。
我的第一个问题是错误215114,提示“签名参数的创建时间不在正确的范围内”,因为我以为我需要使用创建的签名密钥的创建时间。但实际上需要使用调用的时间。
我首先使用RSA-SHA256密码创建了签名密钥,但由于我在签署签名基础和eBay建议使用Ed25519密码方面遇到了困难,我尝试了Ed25519并使用它创建了新的密钥。
尽管如此,我仍然在签署“Signature”标头的签名基础方面遇到了困难,在此期间收到了错误215002(“获取主密钥时的内部错误”),最后以错误215120或215122(“签名验证失败”)结束。
我正在使用.NET 6和RestSharp进行实际的API调用,尝试使用System.Security.Cryptography的“RSA类”,但现在在我的工作解决方案中使用BouncyCastle进行签名。
英文:
I struggled and messed around for about two days to create the digital signature for the specific API calls mentioned at:
https://developer.ebay.com/develop/guides/digital-signatures-for-apis
As the documentation - especially for C# - is pretty poor, particularly for this critical change in calling the Restful APIs, I want to share my finally working solution.
Overall, my question was to create all the headers and the signed signature base.
Already using the 'OAuth scope' with access token created with the client credentials grant flow, creating the signing keys by the 'Key Management API' was not a big deal.
My first problem was Error 215114 saying 'The create time of signature parameters is not in right range', as I thought I need to use the creation time of the created signing keys. But you need to use the time of the call.
I first created the signing keys with RSA-SHA256 cipher, but as I struggled to sign the signature base and eBay recommends Ed25519 cipher, I gave Ed25519 a try and created new keys with it.
Nevertheless I struggled with signing the signature base for the 'Signature'-header, received 215002 error ('Internal errors as fetching master key') in the meantime, and after all ended with error 215120 resp. 215122 'Signature validation failed'.
I'm using .NET 6 and RestSharp for the actual API call, tried the 'RSA Class' from System.Security.Cryptography, but now use BouncyCastle for signing in my working solution.
答案1
得分: 1
这是我的工作解决方案,使用了RestSharp和BouncyCastle,带有给定的eBay访问令牌和Ed25519签名密钥的成功响应。
密钥不是PEM格式,格式如下:
JWE:
eyJ6aXAiOiJERUYiLCJraWQiOiJiNmI4ZW[...]
私钥:
MC4CAQAwBQYDK2VwBCIEIDIpPLbihtModG[...]
公钥:
MCowBQYDK2VwAyEA0CjbDt2NDS7LKbQS6i[...]
我认为我成功解决了通过使用"-----BEGIN ... KEY-----"和"-----END ... KEY-----"将其标记,并使用PemReader创建密钥参数,成功签名基础的问题。
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using RestSharp;
// 用于获取有效访问令牌的外部方法
EbayAccessToken ebayAccessToken = EbayAccessToken.Instance;
if (await ebayAccessToken.GetValidToken(CommonInterfaces.Resources.CommonDataPath) == null)
{
ebayAccessToken = await WebViewCommonCalls.RenewEbayApiToken(ebayAccessToken);
}
// 用于获取签名密钥的外部方法
SigningKeyResponse keyResponse = await KeyManagement.Instance(CommonInterfaces.Resources.CommonDataPath).GetSigningKeys(ebayAccessToken.AccessToken.Token);
long timeStamp = DateTimeOffset.Now.ToUnixTimeSeconds();
string url = "https://apiz.ebay.com";
string endpoint = "/sell/finances/v1/seller_funds_summary"; // 用于测试的示例调用
// 开始为POST调用中的负载实现Content-Digest
//string sha256Hash = ComputeSha256Hash(JsonConvert.SerializeObject(payload));
string signatureParams = $"(\"x-ebay-signature-key\" \"@method\" \"@path\" \"@authority\");created=" + timeStamp;
string signatureBase =
$"\"x-ebay-signature-key\": {keyResponse.Jwe}\n" +
$"\"@method\": GET\n" +
$"\"@path\": {endpoint}\n" +
$"\"@authority\": apiz.ebay.com\n" +
$"\"@signature-params\": {signatureParams}";
byte[] signatureBaseBytes = Encoding.UTF8.GetBytes(signatureBase);
string signatureBase64 = string.Empty;
// 最终这是唯一对我有效的方法,通过注意将密钥记在PEM格式中,并使用PemReader加载它
string privKeyString = $"-----BEGIN PRIVATE KEY-----\n{keyResponse.PrivateKey}\n-----END PRIVATE KEY-----";
string pubKeyString = $"-----BEGIN PUBLIC KEY-----\n{keyResponse.PublicKey}\n-----END PUBLIC KEY-----";
// 签名
PemReader pemReader = new(new StringReader(privKeyString));
AsymmetricKeyParameter pemPrivKey = (AsymmetricKeyParameter)pemReader.ReadObject();
ISigner signer = SignerUtilities.GetSigner("Ed25519");
signer.Init(true, pemPrivKey);
signer.BlockUpdate(signatureBaseBytes , 0, signatureBaseBytes.Length);
byte[] signedSignatureBytes = signer.GenerateSignature();
signatureBase64 = Convert.ToBase64String(signedSignatureBytes);
// 验证
PemReader pemPubReader = new(new StringReader(pubKeyString));
AsymmetricKeyParameter pemPubKey = (AsymmetricKeyParameter)pemPubReader.ReadObject();
ISigner verifier = SignerUtilities.GetSigner("Ed25519");
verifier.Init(false, pemPubKey);
verifier.BlockUpdate(Encoding.UTF8.GetBytes(signatureBase), 0, signatureBase.Length);
bool verified = verifier.VerifySignature(signedSignatureBytes);
// 如果未经验证,则返回
RestClient client = new(url)
{
Authenticator = new RestSharp.Authenticators.OAuth2.OAuth2AuthorizationRequestHeaderAuthenticator(ebayAccessToken.AccessToken.Token, "Bearer"),
};
RestRequest request = new(endpoint, method: Method.Get);
Dictionary<string, string> headers = new()
{
{ "Content-Type", "application/json" },
{ "X-EBAY-C-MARKETPLACE-ID", "EBAY-DE" },
{ "x-ebay-signature-key", keyResponse.Jwe },
{ "x-ebay-enforce-signature", "true" },
{ "Signature", $"sig1=:{signatureBase64}:" },
{ "Signature-Input", "sig1=" + signatureParams },
};
request.AddHeaders(headers);
RestResponse response = await client.ExecuteAsync(request);
if (response.StatusCode != HttpStatusCode.OK)
{
// 用于反序列化响应错误的自定义类'RestfulErrorResponse'
RestfulErrorResponse errors = JsonConvert.DeserializeObject<RestfulErrorResponse>(response.Content);
}
英文:
So, this is my working solution with successful response, using RestSharp and BouncyCastle, with given eBay access token and Ed25519-signing keys.
The keys are not in PEM format, like so:<br/>
JWE:<br/>
eyJ6aXAiOiJERUYiLCJraWQiOiJiNmI4ZW[...]<br/>
Private Key:<br/>
MC4CAQAwBQYDK2VwBCIEIDIpPLbihtModG[...]<br/>
Public Key:<br/>
MCowBQYDK2VwAyEA0CjbDt2NDS7LKbQS6i[...]<br/>
I think I solved my problem with successfully signing the base by noting it with "-----BEGIN ... KEY-----" and "-----END ... KEY-----" and create the key parameters with PemReader.
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using RestSharp;
// EXTERNAL METHOD TO RETRIEVE A VALID ACCESS TOKEN
EbayAccessToken ebayAccessToken = EbayAccessToken.Instance;
if (await ebayAccessToken.GetValidToken(CommonInterfaces.Resources.CommonDataPath) == null)
{
ebayAccessToken = await WebViewCommonCalls.RenewEbayApiToken(ebayAccessToken);
}
// EXTERNAL METHOD TO RETRIEVE THE SIGNING KEYS
SigningKeyResponse keyResponse = await KeyManagement.Instance(CommonInterfaces.Resources.CommonDataPath).GetSigningKeys(ebayAccessToken.AccessToken.Token);
long timeStamp = DateTimeOffset.Now.ToUnixTimeSeconds();
string url = "https://apiz.ebay.com";
string endpoint = "/sell/finances/v1/seller_funds_summary"; // EXAMPLE CALL FOR TESTING
// BEGAN TO IMPLEMENT THE Content-Digest FOR THE PAYLOAD IN POST CALLS
//string sha256Hash = ComputeSha256Hash(JsonConvert.SerializeObject(payload));
string signatureParams = "(\"x-ebay-signature-key\" \"@method\" \"@path\" \"@authority\");created=" + timeStamp;
string signatureBase =
$"\"x-ebay-signature-key\": {keyResponse.Jwe}\n" +
$"\"@method\": GET\n" +
$"\"@path\": {endpoint}\n" +
$"\"@authority\": apiz.ebay.com\n" +
$"\"@signature-params\": {signatureParams}";
byte[] signatureBaseBytes = Encoding.UTF8.GetBytes(signatureBase);
string signatureBase64 = string.Empty;
// FINALLY THIS WAS THE ONLY METHOD WHICH WORKED FOR ME, BY NOTING THE KEYS IN PEM FORMAT AND LOAD IT WITH PEM READER
string privKeyString = $"-----BEGIN PRIVATE KEY-----\n{keyResponse.PrivateKey}\n-----END PRIVATE KEY-----";
string pubKeyString = $"-----BEGIN PUBLIC KEY-----\n{keyResponse.PublicKey}\n-----END PUBLIC KEY-----";
// Sign
PemReader pemReader = new(new StringReader(privKeyString));
AsymmetricKeyParameter pemPrivKey = (AsymmetricKeyParameter)pemReader.ReadObject();
ISigner signer = SignerUtilities.GetSigner("Ed25519");
signer.Init(true, pemPrivKey);
signer.BlockUpdate(signatureBaseBytes , 0, signatureBaseBytes.Length);
byte[] signedSignatureBytes = signer.GenerateSignature();
signatureBase64 = Convert.ToBase64String(signedSignatureBytes);
// Verify
PemReader pemPubReader = new(new StringReader(pubKeyString));
AsymmetricKeyParameter pemPubKey = (AsymmetricKeyParameter)pemPubReader.ReadObject();
ISigner verifier = SignerUtilities.GetSigner("Ed25519");
verifier.Init(false, pemPubKey);
verifier.BlockUpdate(Encoding.UTF8.GetBytes(signatureBase), 0, signatureBase.Length);
bool verified = verifier.VerifySignature(signedSignatureBytes);
//if (!verified) return;
RestClient client = new(url)
{
Authenticator = new RestSharp.Authenticators.OAuth2.OAuth2AuthorizationRequestHeaderAuthenticator(ebayAccessToken.AccessToken.Token, "Bearer"),
};
RestRequest request = new(endpoint, method: Method.Get);
Dictionary<string, string> headers = new()
{
{ "Content-Type", "application/json" },
{ "X-EBAY-C-MARKETPLACE-ID", "EBAY-DE" },
{ "x-ebay-signature-key", keyResponse.Jwe },
{ "x-ebay-enforce-signature", "true" },
{ "Signature", $"sig1=:{signatureBase64}:" },
{ "Signature-Input", "sig1=" + signatureParams },
};
request.AddHeaders(headers);
RestResponse response = await client.ExecuteAsync(request);
if (response.StatusCode != HttpStatusCode.OK)
{
// CUSTOM CLASS 'RestfulErrorResponse' FOR DESERIALIZING RESPONSE ERRORS
RestfulErrorResponse errors = JsonConvert.DeserializeObject<RestfulErrorResponse>(response.Content);
}
答案2
得分: 0
对于从响应中解码密钥,似乎你可以这样解码私钥:
Org.BouncyCastle.Security.PrivateKeyFactory.CreateKey(Convert.FromBase64String(keyResponse.PrivateKey))
类似地,对于公钥:
Org.BouncyCastle.Security.PublicKeyFactory.CreateKey(Convert.FromBase64String(keyResponse.PublicKey))
英文:
For the decoding of the keys from the response, it appears that you could decode the private key like this:
Org.BouncyCastle.Security.PrivateKeyFactory.CreateKey(Convert.FromBase64String(keyResponse.PrivateKey))
Similarly the public key:
Org.BouncyCastle.Security.PublicKeyFactory.CreateKey(Convert.FromBase64String(keyResponse.PublicKey))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论