Mastercard / oauth1-signer-csharp

Zero dependency library for generating a Mastercard API compliant OAuth signature.
https://developer.mastercard.com/platform/documentation/security-and-authentication/using-oauth-1a-to-access-mastercard-apis/
MIT License
25 stars 13 forks source link

OAuth signatures did not match error #6

Closed viktoria-zh closed 4 years ago

viktoria-zh commented 4 years ago

Hello,

I am trying to call Enhanced Currency Conversion Calculator API.

My code: var uri = "https://sandbox.api.mastercard.com/enhanced/settlement/currencyrate/subscribed/summary-rates?rate_date=2020-08-07&trans_curr=GBP&trans_amt=100&crdhld_bill_curr=EUR"; var authHeader= OAuth.GetAuthorizationHeader(uri, "GET", string.Empty,, Encoding.UTF8, consumerKey, signingKey); var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authHeader);

My generated base string: GET&https%3A%2F%2Fsandbox.api.mastercard.com%2Fenhanced%2Fsettlement%2Fcurrencyrate%2Fsubscribed%2Fsummary-rates&crdhld_bill_curr%3DEUR%26oauth_body_hash%3D47DEQpj8HBSa%2B%2FTImW%2B5JCeuQeRkm5NMpJWZG3hSuFU%3D%26oauth_consumer_key%3DzFlC3IqZ-f7lNmfFyVsC8xDGdE1zIPcMMWPbCqGy50ba7ea4%21499f2d41e5d540aaa8ae8ddf6bef610e0000000000000000%26oauth_nonce%3D5ee0d0cf9d5f710c%26oauth_signature_method%3DRSA-SHA256%26oauth_timestamp%3D1596795752%26oauth_version%3D1.0%26rate_date%3D2020-08-07%26trans_amt%3D100%26trans_curr%3DGBP

Acceptable signature base string from server error response: GET&https%3A%2F%2Fsandbox.api.mastercard.com%2Fenhanced%2Fsettlement%2Fcurrencyrate%2Fsubscribed%2Fsummary-rates&crdhld_bill_curr%3DEUR%26oauth_body_hash%3D47DEQpj8HBSa%252B%252FTImW%252B5JCeuQeRkm5NMpJWZG3hSuFU%253D%26oauth_consumer_key%3DzFlC3IqZ-f7lNmfFyVsC8xDGdE1zIPcMMWPbCqGy50ba7ea4%2521499f2d41e5d540aaa8ae8ddf6bef610e0000000000000000%26oauth_nonce%3D5ee0d0cf9d5f710c%26oauth_signature_method%3DRSA-SHA256%26oauth_timestamp%3D1596795752%26oauth_version%3D1.0%26rate_date%3D2020-08-07%26trans_amt%3D100%26trans_curr%3DGBP

Body hash of empty string is 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=. In my generated base string +/ encoded as %2B%2F, when in acceptable string from server it is %252B%252F. It seems that body hash should be encoded twice to be accepted. The same for the customer key.

How could I update my code to match accepted signature?

jaaufauvre commented 4 years ago

@viktoria-zh, thanks for having opened this issue.

I wouldn't use DefaultRequestHeaders, because your Authorization header will be different for every new request you send.

Here are 3 ways of calling the Enhanced Currency Conversion Calculator (in order of preference).

Here is a .NET solution with the same tests you can run: ViktoriaZh.zip

  1. By Generating and Configuring a Mastercard API Client and using the RestSharpOAuth1Authenticator class
[TestMethod]
public void TestConversionRateSummaryApi_WithOpenApiGeneratedClient()
{
    // Configure the generated ApiClient to sign requests
    var signingKey = AuthenticationUtils.LoadSigningKey(SigningKeyPkcs12FilePath, SigningKeyAlias, SigningKeyPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
    var config = Configuration.Default;
    config.BasePath = BaseServicePath;
    config.ApiClient.RestClient.Authenticator = new RestSharpOAuth1Authenticator(ConsumerKey, signingKey, new Uri(config.BasePath));

    // Call the service
    var conversionRateSummaryApi = new ConversionRateSummaryApi(Configuration.Default);
    var response = conversionRateSummaryApi.GetEnhancedConversionDetailsUsingGET(
        rateDate: "2020-08-07",
        transCurr: "GBP",
        transAmt: "100",
        crdhldBillCurr: "EUR"
    );
}
  1. By using HttpClient and the NetHttpClientSigner class
internal class RequestSignerHandler : HttpClientHandler
{
    private readonly NetHttpClientSigner signer;
    public RequestSignerHandler(string consumerKey, RSA signingKey)
    {
        signer = new NetHttpClientSigner(consumerKey, signingKey);
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        await signer.SignAsync(request);
        return await base.SendAsync(request, cancellationToken);
    }
}

[TestMethod]
public async Task TestConversionRateSummaryApi_WithNetHttpClientAndNetHttpClientSigner()
{
    // Configure the HttpClient to sign requests
    var signingKey = AuthenticationUtils.LoadSigningKey(SigningKeyPkcs12FilePath, SigningKeyAlias, SigningKeyPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
    var httpClient = new HttpClient(new RequestSignerHandler(ConsumerKey, signingKey)) { BaseAddress = new Uri(BaseServicePath) };

    // Call the service
    var uri = "summary-rates?rate_date=2020-08-07&trans_curr=GBP&trans_amt=100&crdhld_bill_curr=EUR";
    var response = await httpClient.GetAsync(uri);
    string responseBody = await response.Content.ReadAsStringAsync();
}
  1. By using HttpClient and the GetAuthorizationHeader method
[TestMethod]
public async Task TestConversionRateSummaryApi_WithNetHttpClientAndGetAuthorizationHeader()
{
    // Compute the Authorization header
    var signingKey = AuthenticationUtils.LoadSigningKey(SigningKeyPkcs12FilePath, SigningKeyAlias, SigningKeyPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
    var uri = BaseServicePath + "summary-rates?rate_date=2020-08-07&trans_curr=GBP&trans_amt=100&crdhld_bill_curr=EUR"; 
    var authorizationString = OAuth.GetAuthorizationHeader(uri, "GET", string.Empty, Encoding.UTF8, ConsumerKey, signingKey);

    // Manually add the Authorization header
    var httpClient = new HttpClient(); 
    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("OAuth", authorizationString.Replace("OAuth ", string.Empty));

    // Call the service
    var response = await httpClient.GetAsync(uri);
    string responseBody = await response.Content.ReadAsStringAsync();
}

Don't forget to update those before running the tests in ViktoriaZh.zip!

private const string ConsumerKey = "000000000000000000000000000000000000000000000000!000000000000000000000000000000000000000000000000";
private const string SigningKeyAlias = "replace-me!";
private const string SigningKeyPassword = "replace-me!";
private const string SigningKeyPkcs12FilePath = "./sandbox_key.p12";
private const string BaseServicePath = "https://sandbox.api.mastercard.com/enhanced/settlement/currencyrate/subscribed/";

I hope this helps.

Also, we have plans to review the set of Mastercard authentication libraries and fix small differences in encoding like the one you reported.