Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not able to verify using RSA public key #19

Closed
pavananms opened this issue Jun 14, 2016 · 5 comments
Closed

Not able to verify using RSA public key #19

pavananms opened this issue Jun 14, 2016 · 5 comments

Comments

@pavananms
Copy link

Hi @potatosalad

We have an authentication server where a public key will be hosted. So we will get the public key from a url like :- http://localhost/jwks.json
The public key will be a json of the form;

{
 "keys": [
   {
     "kty": "RSA",
     "e": "AQAB",
     "use": "sig",
     "kid": "mak1",
     "n": "ie4OsY4wbR9RSlxqYfY26ZA7dj_eK9kCmUEKCRZnCsPvOb0d3ZLySFbe7x5kQJAHgswR-AcxZXSHNsLhwA1bb5oul7TTa4ea4Z65ivFentzQJ87BBi8AGiGRO6laOLsRbv_9rd9WCtpNkyCMNflqzMO707g7GpbnyCzm0bPQxQmVvY6STCm2apmIgRs59KRsygLnhdDAWc4Gzajoc4Zbvpcd3IpEjSeqDn806lDLAtCKTAzU2IDc26jK38eJFhz2BA66QEOADn9CFEIP2afpVbWVbf-6Nb-OeBL0A_CsIR7RA6pOCMJTDi-0dG1jFnttVkR_gnceXcruOQmQSq0R_Q"
   }
 ]
}

We tried the following code sample to verify a jwt ;

PrivateKeys = <<"{\"keys\":[{\"kty\":\"RSA\",\"d\":\"BEwStO4KhE_NM9wOhW1vcZ-s_nSxAvyt3k1ZjyqP_D5EJjlKRlELVw9o3DwijFydC-iAHz7wLUJyZtRxb8PwsnqG3IvcXUqjmUt28sc0s7KVKNMiaZFzA3zCiO4_Am_tWNCvDBr4trlv-D7NokPrYjPlX6Io7WKj6bkFlcMsjwmRr60VnUplrt0YWQmSZa18Nn2Xg4f3U0qMVM9HsSztegayZlAQJhVQT3Z_FP9MXcnxNP8RCADcfSD7CvbwHDp3ttxLN89qEiUVezyjmGSmrnCOVEcDdy2RDbTbxToAEBbFgIXIEIXTpAT60mi04e3HwCe8ZH1Lk5Z-GxNSpVnqgQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"mak1\",\"alg\":\"RS256\",\"n\":\"v5SM4zitKoxHsM6bmZepdTk6iYYAo_7Beh3t-nLKgJD1_BIyyIB2cKGY12wb81Cz0DRACvtfHsJKwiZb_MG8J5IruWvIeQjcz2xdCcd8JuKLN25HLOgRk_zegmZju4xTuJqe9kA6z0Ow4Xv5OFjBU9COuIRzjmENpZrfZXy_QNANVdnvx9Yc_OzjfW0eV91ay2nXVQ9YTprdcgezPnrZSZsol6hw5Zgg6N7ptCN-N_-S8AgLyRnesLFYsodkNQeDs3jv73MfLkB7pU_HVlK2Cob3nwKANVC1mgVD5-MV98D_t_AtuID6cfRy25UBAPHd-d7hOYne0iM_rhWF8z8DLw\"}]}">>.

PrivateKeysMap = jsx:decode(PrivateKeys, [return_maps]).

AllKeys = maps:get(<<"keys">>, PrivateKeysMap).

[PrivateKey] = lists:filter(fun(KeyMap) -> maps:get(<<"kid">>, KeyMap) =:=  <<"mak1">>  end, AllKeys).

PrivateJwk = jose_jwk:from_map(PrivateKey).

JWS = #{
  <<"alg">> => <<"RS256">>,
  <<"kid">> => <<"mak1">>
}.

JWT = #{
  <<"iss">> => <<"joe">>,
  <<"exp">> => 1300819380,
  <<"http://example.com/is_root">> => true
}.

Signed = jose_jwt:sign(PrivateJwk, JWS, JWT).

CompactSigned = jose_jws:compact(Signed).

And if we verify the JWK by ;

jose_jwt:verify(PrivateJwk, CompactSigned).
{true,
    {jose_jwt,
        #{<<"exp">> => 1300819380,
          <<"http://example.com/is_root">> => true,
          <<"iss">> => <<"joe">>}},
    #jose_jws{
        alg = 
            {jose_jws_alg_rsa_pkcs1_v1_5,
                {jose_jws_alg_rsa_pkcs1_v1_5,sha256}},
        b64 = undefined,
        fields = #{<<"kid">> => <<"mak1">>,<<"typ">> => <<"JWT">>}}}

It gets verified properly and gives {true, _, _}.

But if we use the public key:

PublicKeys = <<"{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"mak1\",\"n\":\"ie4OsY4wbR9RSlxqYfY26ZA7dj_eK9kCmUEKCRZnCsPvOb0d3ZLySFbe7x5kQJAHgswR-AcxZXSHNsLhwA1bb5oul7TTa4ea4Z65ivFentzQJ87BBi8AGiGRO6laOLsRbv_9rd9WCtpNkyCMNflqzMO707g7GpbnyCzm0bPQxQmVvY6STCm2apmIgRs59KRsygLnhdDAWc4Gzajoc4Zbvpcd3IpEjSeqDn806lDLAtCKTAzU2IDc26jK38eJFhz2BA66QEOADn9CFEIP2afpVbWVbf-6Nb-OeBL0A_CsIR7RA6pOCMJTDi-0dG1jFnttVkR_gnceXcruOQmQSq0R_Q\"}]}">>.

PublicKeysMap = jsx:decode(PublicKeys, [return_maps]).

AllPublicKeys = maps:get(<<"keys">>, PublicKeysMap).

[PublicKey] = lists:filter(fun(KeyMap) -> maps:get(<<"kid">>, KeyMap) =:=  <<"mak1">>  end, AllPublicKeys).

PublicJwk = jose_jwk:from_map(PublicKey). `

And tried;

jose_jws:verify(PublicJwk, CompactSigned).
{false,
    <<"{\"exp\":1300819380,\"http://example.com/is_root\":true,\"iss\":\"joe\"}">>,
    #jose_jws{
        alg = 
            {jose_jws_alg_rsa_pkcs1_v1_5,
                {jose_jws_alg_rsa_pkcs1_v1_5,sha256}},
        b64 = undefined,
        fields = #{<<"kid">> => <<"mak1">>,<<"typ">> => <<"JWT">>}}}

This is verified false.

How can we verify with public key?

Also, can you please explain the details of how the verify function works?
What all does it check?
Can we verify whether the token claim contains attributes like, a valid expiration time, a subject etc.?
An equivalent java example would be ;

HttpsJwks httpsJkws = new HttpsJwks("http://localhost/jwks.json");
HttpsJwksVerificationKeyResolver httpsJwksKeyResolver = new HttpsJwksVerificationKeyResolver(httpsJkws);
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setMaxFutureValidityInMinutes(300) // but the  expiration time can't be too crazy
                .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to  account for clock skew
                .setRequireSubject() // the JWT must have a subject claim
                .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by
                .setExpectedAudience("Audience") // to whom the JWT is intended for
                .setVerificationKeyResolver(httpsJwksKeyResolver) // verify the signature with the public key
                .build(); // create the JwtConsumer instance
@potatosalad
Copy link
Owner

@pavananms I believe there are two issues with the example you provided.

  1. jose currently does not fully support RSA keys that only have the n, e, and d parameters. They only work for sign/verify operations, but are otherwise unsupported. Java in particular drops the dp, dq, p, q, and qi parameters from their keys as mentioned in RFC 7517. If I can find a way to restore those parameters, I'll add better support for these keys to the library.
  2. The public key you are using does not seem to be related to the secret key in your example.

A public RSA key is essentially the n and e values, which should be identical to the ones of the secret key. In your example, the e values match, but the n values are completely different:

%% secret key: n
<<"v5SM4zitKoxHsM6bmZepdTk6iYYAo_7Beh3t-nLKgJD1_BIyyIB2cKGY12wb81Cz0DRACvtfHsJKwiZb_MG8J5IruWvIeQjcz2xdCcd8JuKLN25HLOgRk_zegmZju4xTuJqe9kA6z0Ow4Xv5OFjBU9COuIRzjmENpZrfZXy_QNANVdnvx9Yc_OzjfW0eV91ay2nXVQ9YTprdcgezPnrZSZsol6hw5Zgg6N7ptCN-N_-S8AgLyRnesLFYsodkNQeDs3jv73MfLkB7pU_HVlK2Cob3nwKANVC1mgVD5-MV98D_t_AtuID6cfRy25UBAPHd-d7hOYne0iM_rhWF8z8DLw">>
%% public key: n
<<"ie4OsY4wbR9RSlxqYfY26ZA7dj_eK9kCmUEKCRZnCsPvOb0d3ZLySFbe7x5kQJAHgswR-AcxZXSHNsLhwA1bb5oul7TTa4ea4Z65ivFentzQJ87BBi8AGiGRO6laOLsRbv_9rd9WCtpNkyCMNflqzMO707g7GpbnyCzm0bPQxQmVvY6STCm2apmIgRs59KRsygLnhdDAWc4Gzajoc4Zbvpcd3IpEjSeqDn806lDLAtCKTAzU2IDc26jK38eJFhz2BA66QEOADn9CFEIP2afpVbWVbf-6Nb-OeBL0A_CsIR7RA6pOCMJTDi-0dG1jFnttVkR_gnceXcruOQmQSq0R_Q">>

If we take the n value from your secret key and replace the n value of your public key, we get the following JSON:

{
  "keys": [
    {
      "kty": "RSA",
      "e": "AQAB",
      "use": "sig",
      "kid": "mak1",
      "n": "v5SM4zitKoxHsM6bmZepdTk6iYYAo_7Beh3t-nLKgJD1_BIyyIB2cKGY12wb81Cz0DRACvtfHsJKwiZb_MG8J5IruWvIeQjcz2xdCcd8JuKLN25HLOgRk_zegmZju4xTuJqe9kA6z0Ow4Xv5OFjBU9COuIRzjmENpZrfZXy_QNANVdnvx9Yc_OzjfW0eV91ay2nXVQ9YTprdcgezPnrZSZsol6hw5Zgg6N7ptCN-N_-S8AgLyRnesLFYsodkNQeDs3jv73MfLkB7pU_HVlK2Cob3nwKANVC1mgVD5-MV98D_t_AtuID6cfRy25UBAPHd-d7hOYne0iM_rhWF8z8DLw"
    }
  ]
}

This public key works as expected, as shown below:

%% Secret Key

SecretKeys = <<"{\"keys\":[{\"kty\":\"RSA\",\"d\":\"BEwStO4KhE_NM9wOhW1vcZ-s_nSxAvyt3k1ZjyqP_D5EJjlKRlELVw9o3DwijFydC-iAHz7wLUJyZtRxb8PwsnqG3IvcXUqjmUt28sc0s7KVKNMiaZFzA3zCiO4_Am_tWNCvDBr4trlv-D7NokPrYjPlX6Io7WKj6bkFlcMsjwmRr60VnUplrt0YWQmSZa18Nn2Xg4f3U0qMVM9HsSztegayZlAQJhVQT3Z_FP9MXcnxNP8RCADcfSD7CvbwHDp3ttxLN89qEiUVezyjmGSmrnCOVEcDdy2RDbTbxToAEBbFgIXIEIXTpAT60mi04e3HwCe8ZH1Lk5Z-GxNSpVnqgQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"mak1\",\"alg\":\"RS256\",\"n\":\"v5SM4zitKoxHsM6bmZepdTk6iYYAo_7Beh3t-nLKgJD1_BIyyIB2cKGY12wb81Cz0DRACvtfHsJKwiZb_MG8J5IruWvIeQjcz2xdCcd8JuKLN25HLOgRk_zegmZju4xTuJqe9kA6z0Ow4Xv5OFjBU9COuIRzjmENpZrfZXy_QNANVdnvx9Yc_OzjfW0eV91ay2nXVQ9YTprdcgezPnrZSZsol6hw5Zgg6N7ptCN-N_-S8AgLyRnesLFYsodkNQeDs3jv73MfLkB7pU_HVlK2Cob3nwKANVC1mgVD5-MV98D_t_AtuID6cfRy25UBAPHd-d7hOYne0iM_rhWF8z8DLw\"}]}">>.

[SecretKey | _] = [K || K = #{<<"kid">> := <<"mak1">>} <- maps:get(<<"keys">>, jose:decode(SecretKeys))].

SecretJWK = jose_jwk:from_map(SecretKey).

%% Sign

JWS = #{
  <<"alg">> => <<"RS256">>,
  <<"kid">> => <<"mak1">>
}.

JWT = #{
  <<"iss">> => <<"joe">>,
  <<"exp">> => 1300819380,
  <<"http://example.com/is_root">> => true
}.

{_, SignedBinary} = jose_jws:compact(jose_jwt:sign(SecretJWK, JWS, JWT)).
% SignedBinary = <<"eyJhbGciOiJSUzI1NiIsImtpZCI6Im1hazEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlLCJpc3MiOiJqb2UifQ.QYPJNennVKwYYEtSIASrSNlKMjyd3MmwuEcb1Q03WUz1kLBzXMkxTO2W_u7XGKSbI9QhBXi-09Vhxc36yJfgyty9KYPsojX0nN6gylFA6yQhhOpuPeRwHbjGOrdEhipS3wC6KC7tSKM5YjQFhRDUJlzVOYubqn3xLcBvhgDVdUMbzEbCQJXjHKezwQ591KPJTgmxONu7jGcAr1m6m1Xf0cXrfdLuO8d89R7Z5tXbMS57E-hcctiJP3IAR_eUB5LQjXI-ERtJzq8SHV2hlIiRx_Evw07tf3JaPcX8wMoZsCE-VU-heRKWkKqUPqMrZN67H1WA0XJulA4TlGWp_7_Seg">>.

%% Public Key

PublicKeys = <<"{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"mak1\",\"n\":\"v5SM4zitKoxHsM6bmZepdTk6iYYAo_7Beh3t-nLKgJD1_BIyyIB2cKGY12wb81Cz0DRACvtfHsJKwiZb_MG8J5IruWvIeQjcz2xdCcd8JuKLN25HLOgRk_zegmZju4xTuJqe9kA6z0Ow4Xv5OFjBU9COuIRzjmENpZrfZXy_QNANVdnvx9Yc_OzjfW0eV91ay2nXVQ9YTprdcgezPnrZSZsol6hw5Zgg6N7ptCN-N_-S8AgLyRnesLFYsodkNQeDs3jv73MfLkB7pU_HVlK2Cob3nwKANVC1mgVD5-MV98D_t_AtuID6cfRy25UBAPHd-d7hOYne0iM_rhWF8z8DLw\"}]}">>.

[PublicKey | _] = [K || K = #{<<"kid">> := <<"mak1">>} <- maps:get(<<"keys">>, jose:decode(PublicKeys))].

PublicJWK = jose_jwk:from_map(PublicKey).

%% Verify

jose_jwt:verify_strict(PublicJWK, [<<"RS256">>], SignedBinary).                                          
{true,#jose_jwt{fields = #{<<"exp">> => 1300819380,
                           <<"http://example.com/is_root">> => true,
                           <<"iss">> => <<"joe">>}},
      #jose_jws{alg = {jose_jws_alg_rsa_pkcs1_v1_5,'RS256'},
                b64 = undefined,
                fields = #{<<"kid">> => <<"mak1">>,<<"typ">> => <<"JWT">>}}}

If jose had better support for these Java specific keys, calling

PublicJWK = jose_jwk:to_public(SecretJWK).

would have worked for converting your secret to a public key.

However, as I mentioned at the beginning, the library doesn't currently support "n, e, d only" RSA keys.

@pavananms
Copy link
Author

Hi @potatosalad ,
Thanks! That was a mistake from my part.
But can you elaborate on what you mean by a "better support "? What are the limits ?

Also, can you address that last question i mentioned above.? Can we verify whether a token is valid or invalid by looking at the different attributes in the claim?

@potatosalad
Copy link
Owner

@pavananms Sorry I neglected to answer your questions earlier:

But can you elaborate on what you mean by a "better support "? What are the limits ?

In most cases, there are 8 numbers that make up a RSA key: d, dp, dq, e, n, p, q, and qi. Java drops 5 of these and only retains d, e, and n. Simple signing and verifying operations work just fine with these 3 numbers, but other operations (like converting to PEM format) don't work with the builtin Erlang libraries. Better support might involve recovering the discarded values, but I'm uncertain whether this is mathematically possible or not.

Can we verify whether a token is valid or invalid by looking at the different attributes in the claim?

Yes, but there is nothing built into jose specifically. The JWT spec is a little ambiguous on the exact usage for claims, so I felt it was best left to the developer to decide. Here is roughly equivalent code in Erlang for the Java code you provided:

Now = erlang:system_time(milli_seconds),
try jose_jwt:peek_payload(SignedBinary) of
    JWT = #jose_jwt{fields = #{
            <<"exp">> := Exp, % the JWT must have an expiration time
            <<"sub">> := _, % the JWT must have a subject claim
            <<"iss">> := <<"Issuer">>, % whom the JWT needs to have been issued by
            <<"aud">> := <<"Audience">> % to whom the JWT is intended for
        }}
            when is_integer(Exp)
            andalso (Exp - Now) < (30 * 60 * 1000) % but the  expiration time can't be too crazy
            andalso (Exp + (30 * 1000)) > Now -> % allow some leeway in validating time based claims to  account for clock skew
        % verify the signature with the public key
        case jose_jwt:verify_strict(PublicJWK, [<<"RS256">>], SignedBinary) of
            {true, JWT, _JWS} ->
                {ok, JWT};
            _ ->
                error
        end;
    _ ->
        error
catch
    _:_ ->
        error
end.

potatosalad added a commit that referenced this issue Jun 30, 2016
* Enhancements
  * Improved handling of RSA private keys in SMF (Straightforward
    Method) form to CRT (Chinese Remainder Theorem) form, see #19.
    This is especially useful for keys produced by Java programs
    using the RSAPrivateKeySpec API as mentioned in Section 9.3
    of RFC 7517.
  * Updated EdDSA operations to comply with draft 02 of
    draft-ietf-jose-cfrg-curves-02.
@potatosalad
Copy link
Owner

@pavananms RSA keys from Java in SMF format are better supported as of jose 1.7.7 (see entry in changelog).

@pavananms
Copy link
Author

Ok, I will look into this .Thank you so much.

potatosalad added a commit to potatosalad/ruby-jose that referenced this issue Jul 8, 2016
* Enhancements
  * Improved handling of RSA private keys in SMF (Straightforward
  * Method) form to CRT (Chinese Remainder Theorem) form, see
  * potatosalad/erlang-jose#19  This is especially useful for keys
  * produced by Java programs using the `RSAPrivateKeySpec` API as
  * mentioned in Section 9.3 of RFC 7517:
  * https://tools.ietf.org/html/rfc7517#section-9.3
  * Updated EdDSA operations to comply with draft 04 of
  * https://tools.ietf.org/html/draft-ietf-jose-cfrg-curves-04.

* Fixes
  * Fixed compression encoding bug for `{"zip":"DEF"}` operations
  * (thanks to @amadden734 see #3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants