Mastercard / oauth1-signer-python

Python 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
29 stars 22 forks source link

[BUG] Invalid base string encoding implementation #21

Closed huumanoid closed 2 years ago

huumanoid commented 3 years ago

Bug Report Checklist

Description We are using this library to authorize our requests to the mastercard Secure Card on File API.

Thank you for this library, guys. It is small and neat, which is perfect to be embeddable in our secure environments.

But we had experienced issues while performing GET request with query parameters in it, since some of our query parameters' values contain reserved URL characters e.g. serivceId=Foo#Bar. When supplying such a parameter in Mastercard's API and applying, of course, OAuth1 authorization, Mastercards' backend complains our OAuth1 base string is invalid. By comparing base string evaluated by oauth1-signer-python with the backends' one, we can see what differences occur in positions with reserved characters.

To Reproduce

  1. Put print(base_string) statement inside of OAuth.get_oauth_parameters methods right after base string evaluation
  2. Form any request with, for example, character # in URI. Like http://..../XXX%23YYY/ or https://.../?foo=b%23ar
  3. Apply OAuth1 signature performed by oauth1-signer-python.
  4. Send it to mastercard's backend (or any backend with rfc5849 OAuth1 validator)

Actual behavior oauth1-signer-python base string (a slice)

%26serviceId%3DMyService%23Identifier%26

mastercard's SCF backend base string (a slice from backend's error message)

%26serviceId%3DMyService%2523Identifier%26

Screenshots If applicable, add screenshots to help explain your problem.

Additional context My platform is Ubuntu 18 with Python 3.8 on the board, but for me it seems like issue is platform independent.

Related issues/PRs Has a similar issue/PR been reported/opened before?

Suggest a fix/enhancement My suggestion is to apply rfc3986 percent encoding algorithm in rfc5849-compliant way:

Details

After studying https://tools.ietf.org/html/rfc5849 I realized that oauth1-signer-python implementation is not rfc5849-compliant. These two pieces of code are the source of incompliance https://github.com/Mastercard/oauth1-signer-python/blob/master/oauth1/coreutils.py#L65 https://github.com/Mastercard/oauth1-signer-python/blob/master/oauth1/coreutils.py#L100

First incompliance

https://github.com/Mastercard/oauth1-signer-python/blob/master/oauth1/coreutils.py#L65 Let's discuss this first one, which is related to parameters normalization procedure Take a closer look at https://tools.ietf.org/html/rfc5849#section-3.4.1.3 describing request parameters transmission Taking about query parameters:

  1. First, we have to fully decode those parameters, what is performed here https://github.com/Mastercard/oauth1-signer-python/blob/master/oauth1/coreutils.py#L46
  2. Then we have to encode each parameter according to https://tools.ietf.org/html/rfc5849#section-3.6, which says what
    • (ALPHA, DIGIT, "-", ".", "_", "~") MUST NOT be encoded
    • All other characters MUST be encoded

This is where the first incompliance at. This line especially https://github.com/Mastercard/oauth1-signer-python/blob/master/oauth1/coreutils.py#L72 considers / to be safe while according to rfc5849 it MUST be encoded. So taking for example foo=b/r when rfc5849-compliant parameters normalization procedure produces foo=b%2Fr, oauth1-signer procedure produces foo=b/r, which is incorrect.

Second incompliance

https://github.com/Mastercard/oauth1-signer-python/blob/master/oauth1/coreutils.py#L100 This piece of code is used generally to perform rfc3986 encoding of URI, method, normalized params, oauth parameters. And this is actualy the reason we faced issues with Mastercards' API. Somehow this function considers % to be safe character, whereas it MUST be encoded according to https://tools.ietf.org/html/rfc5849#section-3.6.

Other solutions across the Internet

After hacking oauth1-signer in order to force it to produce correct base string (according to my understanding of specs), I want to validate my idea in other people code in the Internet. I found this library https://github.com/oauthlib/oauthlib This is another library which focuses on OAuth1/OAuth2 protocols. Although it's larger so it is harder for us to adopt this one and embed it in our secure environments. But still, let's look how they perform percent encoding procedure: https://github.com/oauthlib/oauthlib/blob/master/oauthlib/oauth1/rfc5849/utils.py#L53

Aha! This one perfectly matches to my idea about rfc5849 encoding

Demo playground

This is the comparison between oauth1-signer and oauthlib. oauthlibs' output matches my idea about rfc5849 and provides correct base string in order to authorize in Mastercards' API https://repl.it/@huumanoid/OAuth1SignerPythonBaseStrings