jersey
  1. jersey
  2. JERSEY-2360

oauth1-client does not really support RSA-SHA1 fully

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Critical Critical
    • Resolution: Fixed
    • Affects Version/s: 2.5.1
    • Fix Version/s: 2.7
    • Component/s: security
    • Labels:
      None

      Description

      I spent about 2 days to find out why my code is not working and finally came to the conclusion it is jersey oauth1 client which does not fully support RSA-SHA1 signature method as I was able to get it working after some thorough investigation by ugly hacks involving reflection.

      Of course it could also be that I used it wrongly, then please point out my error and make it easier usable and better documented.

      What I'm trying to do:
      I write a command-line tool that needs to query information from a JIRA 6 instance via its REST API. As our JIRA is completely closed, some sort of authentication is needed and JIRA suggests OAuth as the preferred method of authenticating.

      What I did do:
      I configured the Consumer in JIRA with the JIRA Application Links feature, for this I created a public / private key pair as JIRA only supports RSA-SHA1 as signature method and requires the public key.
      (Commands to generate such a key pair would be handy in the Jersey documentation. The ones I used were:

      • "openssl genrsa -out key.pem 2048" to create the private key
      • "openssl pkcs8 -topk8 -in key.pem -nocrypt" to convert the private key into the format that is needed by jersey [needed me quite some time to find this out]
      • "openssl rsa -in key.pem -pubout" to extract the public key from the private key
        The output of the third command can be pasted into the service provider, the output of the second command can be used as consumerSecret directly)

      I then googled a lot to find a good example or tutorial on how to get this going until I found your oauth-client-twitter which helped me a lot as it does most of what I needed, I just needed to adapt this a bit and change the signature method to "RSA-SHA1".

      At least so I thought.

      Basically I did something like

      ConsumerCredentials consumerCredentials = new ConsumerCredentials("consumer-key", PRIVATE_KEY);
      OAuth1AuthorizationFlow authorizationFlow = OAuth1ClientSupport.builder(consumerCredentials).signatureMethod("RSA-SHA1").authorizationFlow(
            "https://jira.example.com/plugins/servlet/oauth/request-token",
            "https://jira.example.com/plugins/servlet/oauth/access-token",
            "https://jira.example.com/plugins/servlet/oauth/authorize").build();
      Desktop.getDesktop().browse(URI.create(authorizationFlow.start()));
      System.out.print("Press ENTER to continue after allowing access in your browser ");
      System.in.read();
      AccessToken accessToken = authorizationFlow.finish(null);
      

      And thought this should work. Unfortunately I got only the message that there is a 400 error without any more information. No HTTP Headers, no body, nothing and I was not able to analyze it with WireShark as our JIRA only supports HTTPS.

      After some digging and debugging I found out the problem is that the org.glassfish.jersey.client.oauth1.OAuth1AuthorizationFlowImpl.Builder#build call creates a new OAuth1AuthorizationFlowImpl instance which creates a new OAuth1Parameters instance that it uses and ignores completely the parameters set on the OAuth1BuilderImpl where the signatureMethod was set. I guess other settings like realm, timestamp, nonce and version are also ignored. And I found no legal method to get this signatureMethod set, so it uses the default HMAC-SHA1 which causes JIRA to give back error 400 (with additional information that HMAC is not supported but this was not retrievable anywhere).

      So to work-around this, I added the following three hacky lines that set the signatureMethod on the field, retrieved via reflection.
      So now at least the authorization flow was able to proceed. (Except that I don't get any verifier from JIRA but just have to continue with null as verifier after I allowed access in the browser, could be noted somewhere maybe that this is not necessarily required)

      ...
            "https://jira.example.com/plugins/servlet/oauth/authorize").build();
      Field parameters = authorizationFlow.getClass().getDeclaredField("parameters");
      parameters.setAccessible(true);
      ((OAuth1Parameters) parameters.get(authorizationFlow)).setSignatureMethod("RSA-SHA1");
      Desktop.getDesktop().browse(URI.create(authorizationFlow.start()));
      ...
      

      Then I wanted to do

      Feature filterFeature = authorizationFlow.getOAuth1Feature();
      Client client = ClientBuilder.newBuilder().register(filterFeature).register(JacksonFeature.class).build();
      WebTarget target = client.target("https://jira.example.com/rest/api/2/issue");
      Response response = target.path("Bug-4711").request(APPLICATION_JSON).get();
      

      which also failed with error 400.
      I then changed this to do instead (first line changed)

      Feature filterFeature = OAuth1ClientSupport.builder(consumerCredentials).signatureMethod("RSA-SHA1").feature().accessToken(accessToken).build();
      Client client = ClientBuilder.newBuilder().register(filterFeature).register(JacksonFeature.class).build();
      WebTarget target = client.target("https://jira.example.com/rest/api/2/issue");
      Response response = target.path("Bug-4711").request(APPLICATION_JSON).get();
      

      which works and I needed anyway for if the access token comes from the stored properties file, but I first tried to do it like shown in the oauth-client-twitter example. Now I only decide on how to create the AccessToken and then always use that line and it works finally.

        Activity

        Hide
        Miroslav Fuksa added a comment -

        Hi,

        thanks a lot for creating the issue and for all details.

        summary for fixing the issue:

        • OAuth1Parameters should be passed to the OAuth1AuthorizationFlowImpl (or maybe only signature method).
        • API should support null verifier (at least this should be documented).

        If I am missing something, please let me know.

        thanks
        Mira

        Show
        Miroslav Fuksa added a comment - Hi, thanks a lot for creating the issue and for all details. summary for fixing the issue: OAuth1Parameters should be passed to the OAuth1AuthorizationFlowImpl (or maybe only signature method). API should support null verifier (at least this should be documented). If I am missing something, please let me know. thanks Mira
        Hide
        vampire0 added a comment -

        Hi Mira,

        I'd slightly modify your two points and add another three, so the summary from my POV would be:

        • OAuth1Parameters (signatureMethod, realm, timestamp, nonce, version) should be passed to the OAuth1AuthorizationFlowImpl
        • authorizationFlow.getOAuth1Feature() needs to pass on the options (signatureMethod, realm, timestamp, nonce, version)
        • It should be documented that a null verifier is supported, the API supports this already
        • Add documentation on how to generate Public / Private Key for RSA-SHA1 to get the Private Key in the format Jersey will understand, because you can store an RSA private key in many formats (example commands were provided above)
        • provide more error information like Headers / Body of the response or similar; as is I had no real possibility to find the error other then debugging through your sourcecode
        Show
        vampire0 added a comment - Hi Mira, I'd slightly modify your two points and add another three, so the summary from my POV would be: OAuth1Parameters (signatureMethod, realm, timestamp, nonce, version) should be passed to the OAuth1AuthorizationFlowImpl authorizationFlow.getOAuth1Feature() needs to pass on the options (signatureMethod, realm, timestamp, nonce, version) It should be documented that a null verifier is supported, the API supports this already Add documentation on how to generate Public / Private Key for RSA-SHA1 to get the Private Key in the format Jersey will understand, because you can store an RSA private key in many formats (example commands were provided above) provide more error information like Headers / Body of the response or similar; as is I had no real possibility to find the error other then debugging through your sourcecode
        Hide
        Miroslav Fuksa added a comment -

        thanks for the update.

        I am moving the issue to the unplanned.

        Show
        Miroslav Fuksa added a comment - thanks for the update. I am moving the issue to the unplanned.

          People

          • Assignee:
            Michal Gajdos
            Reporter:
            vampire0
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Time Tracking

              Estimated:
              Original Estimate - 2 hours
              2h
              Remaining:
              0m
              Logged:
              Time Not Required
              1m