如何使用私钥进行身份验证 - Walmart Affiliate

时间:2021-02-16 06:17:36

标签: c# cryptography bouncycastle walmart-api

我正在尝试使用 Walmart Affiliate API,该 API 使用公共/私有令牌进行身份验证。我无法弄清楚步骤 provided 中遗漏了什么。

我目前有一个 DelegatingHandler 来添加所需的 Headers 值。我正在使用 BouncyCastle 来帮助进行私人令牌签名,这就是我目前所拥有的。

    public static string Generate(string version, string consumerId, string timestamp)
    {
        // Canonicalize the headers, following after the java code in the docs.
        string[] canonicalStrings = Canonicalize(version, consumerId, timestamp);

        // Read the file with the password protected private key
        StreamReader stream= new StreamReader(@"..\key");
        PasswordFinder finder = new PasswordFinder("1234");

        // Actually get the private key
        PemReader pemReader= new PemReader(stream, finder);
        AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
        RSAParameters rsa = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyPair.Private);
        
        // Create the RSA Provider and import the private key
        RSACryptoServiceProvider provider = new RSACryptoServiceProvider(2048);
        provider.ImportParameters(rsa);

        // Sign the canonicalized data
        byte[] signedData = provider.SignData(Encoding.UTF8.GetBytes(canonicalStrings[1]), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

        // Convert the bytes to a base-64 string.
        return Convert.ToBase64String(signedData);
    }

    private static string[] Canonicalize(string version, string consumerId, string timestamp)
    {
        // Follow after the java code, which just orders the keys/values.
        StringBuilder keyBuilder = new StringBuilder();
        StringBuilder valueBuilder = new StringBuilder();
        SortedDictionary<string, string> dictionary = new SortedDictionary<string, string>() { { Constants.HEADER_COMSUMER_ID, consumerId }, { Constants.HEADER_TIMESTAMP, timestamp }, { Constants.HEADER_KEY_VERSION, version } };

        foreach (string key in dictionary.Keys)
        {
            keyBuilder.Append($"{key.Trim()};");
            valueBuilder.AppendLine($"{dictionary[key].Trim()}");
        }

        return new string[] {keyBuilder.ToString(), valueBuilder.ToString()};
    }

这是通过我的 DelegatingHandler 调用的:

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        string version = _walmartConfig.CurrentValue.Version; // Get Version from config
        string consumerId = _walmartConfig.CurrentValue.StageConsumerId; // Get ConsumerID from config
        string timestamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
        string signature = Generator.Generate(version, consumerId, timestamp); // Generate signature

        request.Headers.Add(Constants.HEADER_KEY_VERSION, version);
        request.Headers.Add(Constants.HEADER_COMSUMER_ID, consumerId);
        request.Headers.Add(Constants.HEADER_TIMESTAMP, timestamp);
        request.Headers.Add(Constants.HEADER_SIGNATURE, signature);
        return base.SendAsync(request, cancellationToken);
    }

它是通过 docs 中提到的示例调用启动的:

        using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy"))
        {
            HttpResponseMessage response = await _client.SendAsync(request); // This returns HTTP 401.

            return response.Content.ToString();
        }

我的私钥是按照 Windows here 中提到的步骤生成的,但我使用 PuTTy 菜单项导出了私钥:Conversions -> Export OpenSSH key

那个私钥文件看起来像:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,F014B20CAD95382A

0CE3...
-----END RSA PRIVATE KEY-----

我认为我正确地遵循了指南,但我仍然从他们的 API 获取 HTTP 401。有人能弄清楚我做错了什么吗?

1 个答案:

答案 0 :(得分:0)

我最终通过主要使用通过 unix 终端创建 OpenSSL 密钥来解决它,但如果对其他人有帮助,这里是最终产品。

用法:

string signature = Signer.SignData(Signer.Canonicalize(version, consumerId, timestamp)[1], _keyManager.Key);

_keyManager.Key 是通过使用 BouncyCastle 读取受密码保护的私钥找到的。

StreamReader sr = File.OpenText("c:\key.pem");
PemReader pr = new PemReader(sr, new PasswordFinder("123"));
RsaPrivateCrtKeyParameters keyPair = pr.ReadObject() as RsaPrivateCrtKeyParameters;
return DotNetUtilities.ToRSAParameters(keyPair);

最后是 Signer.SignData 实现。

public static string SignData(string message, RSAParameters privateKey)
{
    byte[] signedBytes;
    using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
    {
        byte[] originalBytes = Encoding.UTF8.GetBytes(message);

        try
        {
            rsa.ImportParameters(privateKey);

            signedBytes = rsa.SignData(originalBytes, CryptoConfig.MapNameToOID("SHA256"));
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            return null;
        }
        finally
        {
            rsa.PersistKeyInCsp = false;
            }
    }

    return Convert.ToBase64String(signedBytes);
}

为了确认它有效,我使用了公钥来验证。公钥的获取与私钥的获取类似。

public static bool Verify(string originalData, string base64SignedData, RSAParameters publicKey)
{
    bool success = false;
    byte[] signedBytes = Convert.FromBase64String(base64SignedData);
    byte[] bytesToVerify = Encoding.UTF8.GetBytes(originalData);

    using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
    {
        try
        {
            rsa.ImportParameters(publicKey);
            SHA256 sha256 = new SHA256Managed();

            byte[] hashedData = sha256.ComputeHash(signedBytes);

            success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA256"), signedBytes);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            return false;
        }
        finally
        {
            rsa.PersistKeyInCsp = false;
        }
    }

    return success;
}

将它们放在一起作为测试:

public void SignTest()
{
    // Arrange
    string version = "1";
    string consumerId = "8644d500-eyue-47gh-9b2b-54d5a4b9d45t";
    string timestamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();

    StreamReader sr = File.OpenText(@"C:\privateKey.pem");
    PemReader pr = new PemReader(sr, new PasswordFinder("123"));
    RsaPrivateCrtKeyParameters keyPair = (RsaPrivateCrtKeyParameters)pr.ReadObject();
    RSAParameters rsaPrivateParameters = DotNetUtilities.ToRSAParameters(keyPair);

    StreamReader sr2 = File.OpenText(@"C:\publicKey.pem");
    PemReader pr2 = new PemReader(sr2, new PasswordFinder("123"));
    var keyPair2 = pr2.ReadObject();
    RSAParameters rsaPublicParameters = DotNetUtilities.ToRSAParameters((RsaKeyParameters)keyPair2);

    string[] canonicalForm = Signer.Canonicalize(version, consumerId, timestamp);
    
    // Act
    string signedData = Signer.SignData(canonicalForm[1], rsaPrivateParameters);
    bool validated = Signer.Verify(canonicalForm[1], signedData, rsaPublicParameters);

    // Assert
    Assert.True(validated);
}