stormpath / stormpath-sdk-java

Official Java SDK for the Stormpath User Management REST API
222 stars 155 forks source link

wrong handling of base64 encoding in jwt #170

Open trombka opened 9 years ago

trombka commented 9 years ago

I tried to use the oauth feature of the sdk. I generated a token by calling POST /oauth/token from the servlet plugin. Then I wanted to use the token in a request that was passed to OauthRequestAuthenticator. And it failed with signature mismatch.

I have dubugged it a little and in DefaultJwtSigningKeyResolver::getSigningKey method I found:

        //Stormpath API Keys are base-64-encoded secure random byte arrays:
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKeySecret);

But the apiKeySecret generated by Stormpath UI is not Base64 encoded. E.g. mine was PGGinnmKcv8AVxaFFqeUJdwdWOMJuccAEf3eOW+Fk/w, but after converting apiKeySecretBytes back to a Base64 string I have got PGGinnmKcv8AVxaFFqeUJdwdWOMJuccAEf3eOW+F

So I checked it with the command line:

$ echo "PGGinnmKcv8AVxaFFqeUJdwdWOMJuccAEf3eOW+Fk/w" | base64 -d | base64
base64: invalid input
PGGinnmKcv8AVxaFFqeUJdwdWOMJuccAEf3eOW+Fk/w=

It seems that DatatypeConverter.parseBase64Binary requires = padding otherwise it truncates the results.

This test fails:

    @Test
    public void shouldConvertBackToTheSameBase64Value(){
        String apiKeySecret="PGGinnmKcv8AVxaFFqeUJdwdWOMJuccAEf3eOW+Fk/w";

        //Stormpath API Keys are base-64-encoded secure random byte arrays:
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKeySecret);

        String apiKeySecretBackFromByteArray = DatatypeConverter.printBase64Binary(apiKeySecretBytes);
        assertEquals(apiKeySecret, apiKeySecretBackFromByteArray);

    }
lhazlewood commented 9 years ago

Thanks for the issue!

Is there a reason why you wanted to use OauthRequestAuthenticator instead of the plugin's built-in OAuth authentication support? OauthRequestAuthenticator was created before the Servlet plugin and requires more manual work to use it. The servlet plugin automates OAuth bearer authentication automatically and it is not expected that OauthRequestAuthenticator would be used when the Servlet plugin is used. Thoughts?

trombka commented 9 years ago

I wanted to have two independently deployable services. One just for authenticating users and issuing OAuth2 tokens and another with a bunch of rest (jersey) endpoints secured with these tokens. For the former I found it convenient to use the servlet plugin. Building the latter I think I followed the examples. :)

Anyway the problem with this signature mismatch is even bigger than =-padding. The token generated by POST /oauth/token has signature generated using a different byte array passed to HMAC function than the one used by OauthRequestAuthenticator.

In case of the servlet plugin it is the apikey secret decoded with Base64. And in case of OauthRequestAuthenticator look at the constructor of DefaultJwtSigner:

this.signingKey = signingKey.getBytes(UTF_8);

This is definitely not Base64 decoding and the two byte arrays have not a chance of being the same. :)

trombka commented 9 years ago

Relating to the online http://jwt.io/ tool, it is like having the token generated with the 'secret base64 encoded' checkbox on and verifying it with the checkbox off.

dogeared commented 8 years ago

Hi @trombka - I am reviewing all of our open issues. We've had many releases since your original comment. Would you please let us know if this is still an issue? If not, I will close this issue out. If so, I will prioritize it against our upcoming releases. Thanks!