Parser exception in JWT when encryption and signing is enabled

43k views Asked by At

I'm new to JWT, learning through standalone code to understand JWT API's. Below code sign and encrypt JWT token from sender's end and it get validated at receiver's end.

Library: JOSE 0.4.1

package com.one00bytes.jwt;

public class JWTSignEncryption {

public static void main(String[] args) throws Exception {

    /***************************SENDER'S END ***********************************/

    JwtClaims claims = new JwtClaims();
    claims.setAudience("Admins");
    claims.setIssuer("CA");
    claims.setSubject("users");
    claims.setClaim("email", "[email protected]");
    claims.setClaim("Country", "Antartica");
    System.out.println(claims.toJson());

    //SIGNING
    RsaJsonWebKey jsonSignKey = RsaJwkGenerator.generateJwk(2048);
    JsonWebSignature jws = new JsonWebSignature();
    jws.setKey(jsonSignKey.getPrivateKey());
    jws.setPayload(claims.toJson());
    jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA512);
    String signedJwt = jws.getCompactSerialization();
    System.out.println("Signed ::" + signedJwt);


    //ENCRYPTING
    RsaJsonWebKey keyEncrypt = RsaJwkGenerator.generateJwk(2048);
    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(256);
    SecretKey contentEncryptKey = keyGen.generateKey();

    JsonWebEncryption jwe = new JsonWebEncryption();
    jwe.setKey(keyEncrypt.getPublicKey());
    jwe.setPayload(signedJwt);
    jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
    jwe.setContentEncryptionKey(contentEncryptKey.getEncoded());
    jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
    SecureRandom iv = SecureRandom.getInstance("SHA1PRNG");
    jwe.setIv(iv.generateSeed(32));
    String encryptedJwt = jwe.getCompactSerialization();
    System.out.println("Encrypted ::" + encryptedJwt);


    /***************************RECEIVER'S END ***********************************/ 

    JwtConsumer consumer = new JwtConsumerBuilder()
                            .setExpectedAudience("Admins")
                            .setExpectedIssuer("CA")
                            .setRequireSubject()
                            .setDecryptionKey(keyEncrypt.getPrivateKey())
                            .setVerificationKey(jsonSignKey.getPublicKey())
                            .build();
    JwtClaims receivedClaims = consumer.processToClaims(encryptedJwt);
    System.out.println("SUCESS :: JWT Validation :: " + receivedClaims);

}

}

Observing below exception when running this program:

Exception in thread "main" org.jose4j.jwt.consumer.InvalidJwtException: Unable to parse JWT Claim Set JSON: eyJhbGciOiJSUzUxMiJ9.eyJhdWQiOiJBZG1pbnMiLCJpc3MiOiJDQSIsInN1YiI6InVzZXJzIiwiaWF0IjoxNDM0NTM0MDgxLCJleHAiOjE0MzQ1MzQ2ODEsImp0aSI6IjJxUUpuMDVGY3RrLWF1VG1vVktuWXciLCJuYmYiOjE0MzQ1MzM5NjEsImVtYWlsIjoidXNlcnNAMTAwYnl0ZXMuY29tIiwiQ291bnRyeSI6IkFudGFydGljYSIsImhvYmJpZXMiOlsiQmxvZ2dpbmciLCJQbGF5aW5nIGNhcmRzIiwiR2FtZXMiXX0.soY_5Hbam569I-CnUW1F4GWdaqprh-XAOtAMOcb7zZSiRcIhXYUdJjEslrDbwphAP135SvmoXO4nVaVmo-d8oWREFYUeXEDzHbrqHNp7pp5pH6hGTJ5C4uE1UVzZ4bis3g_KEgZvEn31NnV4RcU_oRn2Q4inkrTlYKY-juEtCmpPQ0sSP4GiDbwVIfCj-kxZsKh_i9n28SSK890K3DIGiFWOUDwrnY4Yfr1UffsUS9ovyhtqrOcN4YsJR4XzGPaLehlR-qD7eOdAdmVb8RDtGKufNuCd7Q9OFfeKzBmGITHsvd6IPVYLLCfSCzO6PqQSIzkupl5D6HqoOqID8JZLxA
    at org.jose4j.jwt.JwtClaims.<init>(JwtClaims.java:50)
    at org.jose4j.jwt.JwtClaims.parse(JwtClaims.java:56)
    at org.jose4j.jwt.consumer.JwtConsumer.process(JwtConsumer.java:267)
    at org.jose4j.jwt.consumer.JwtConsumer.processToClaims(JwtConsumer.java:115)
    at com.one00bytes.jwt.JWTSignEncryption.main(JWTSignEncryption.java:76)
Caused by: org.jose4j.lang.JoseException: Parsing error: org.jose4j.json.internal.json_simple.parser.ParseException: Unexpected character (e) at position 0.
    at org.jose4j.json.JsonUtil.parseJson(JsonUtil.java:66)
    at org.jose4j.jwt.JwtClaims.<init>(JwtClaims.java:45)
    ... 4 more
Caused by: org.jose4j.json.internal.json_simple.parser.ParseException: Unexpected character (e) at position 0.
    at org.jose4j.json.internal.json_simple.parser.Yylex.yylex(Yylex.java:612)
    at org.jose4j.json.internal.json_simple.parser.JSONParser.nextToken(JSONParser.java:269)
    at org.jose4j.json.internal.json_simple.parser.JSONParser.parse(JSONParser.java:118)
    at org.jose4j.json.internal.json_simple.parser.JSONParser.parse(JSONParser.java:81)
    at org.jose4j.json.JsonUtil.parseJson(JsonUtil.java:62)
    ... 5 more

Signed JWT

eyJhbGciOiJSUzUxMiJ9.eyJhdWQiOiJBZG1pbnMiLCJpc3MiOiJDQSIsInN1YiI6InVzZXJzIiwiZW1haWwiOiJ1c2Vyc0B0ZXN0LmNvbSIsIkNvdW50cnkiOiJBbnRhcnRpY2EifQ.5Xu7v2MosIQmtAOlqfM2PE9eJeT0iZzL9x6RIvqx_PAHKer0ylo-0wT9eON_qX1H_QZekTWMf8ok4fxdZNv2KP_AkNqSKLXYJ65TjPnfcX8-dooDJM9txfRWOFqJWx4yj4CTMPNR6rNhizkC9jUaLisPIjogc_a_61qTSnvHXFnuaYmkovN2Y3WfuXjhUZCH98hodRL_ATg1_SpO0bPb7_N1Z76yrcv0RYQan0Y5kICWYdhHlk8Dw6I2fLMVsl3HiYiRq4XBJE8AY_g742Uq5kTS62PKohg3IjfRa-g2rjgKo1XW2sRLVc7vnns2L3TqESo5vgvorTjKnCTQKuHpIg

Encrypted JWT

eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.lZ2nqCeiPzsPmJShsrDD3uA55-06A649CMtwOyuY9nNzMtUGyzV-G8qc4w4ui1uWrtzypBs5Eyq4GfjnTtVHbcDVkS1HVc3tfxNAPY8dfjVrWNz59HyKt4bCjBdqqhBOdZezLtWB9aoWIwZoHLf4D8aUcVUtDsFELVcScmiQNtzHwvpDHZb4oxRfPl-OuOTkKA23C8lnnDMO1KUy8ZXHD4p0jQKAcaV877gYm8NbHDwOBEf-ItWJOGx2jV60apWd0hKqwfFR2QKD9wmGgXpbFZ08ro7X2fj8rTgKWhDgoBT_JVZdVFhVI4T4RLRDrCJqkyeciXhLm7W_xNhWBXAMrA.94SuB596ZLuUtw53wrofwN5jZXfT5f-ZarJhQc9Mj0M.0Ow5DXfilYX3ty49H4lNMNPljlWAFASc49zljhRSIIUSlmUHLZo0SAezn-n_FdxexAIYLk_FtRgnkMHDEyxJ1V1yHhqa1Jvdb36lTYyptqCJhMkOV1XGn58L4Z9QQmdrIZnn5iHxZ9-N1Jfjs0eoKiLBgR9O7ZEcs7QrWZVT6n_HrGrIloYQu_lFgmk5O7k47_15CVXaFqIohpHXETejoHEwjQj-iTToNRaHWNFAKvlpUBz4mUgk9RSIQCxK1GxxS8wxP44w5G4HdOIjFNwTsRDXeSZy0mU9zTNUCmDEUT9MFESfmVU1nPurdT-VoiPvVklbJZW8Sas0hWgqQkdQdP35nFY1sjCgfMB9iYUeEU-TCE219wkm1XXrLJwLEYZclL_4ckl4zExo2wb3Czwd8f5iO9fBQQWZ4mdwThK4VtZaPs1JEkxwGLI0SHA8Jr-e2PsDrkGEnxs74FsJ5MKluU2ZKvKcGXyQPaaTRa0ecJLD5-YYBuTtxOnU3gM_5aZm97pd_wiPk_h81r5aiwjSfRF3Ihxp37KNPfNOMJoA9xe2F51m1AvmjrOUgSM156LwmFyJFebVfarb9NPtJ_q1wU891sCu2Vmv520BR4QfIc-ayIwTVxLgZSN-BP7PhEJb_x8.XhZpINBxRdFFEgwPTcAgJg

Same code runs seperately for signing and encryption, but didn't run, if I include both.

Please help me to understand what I'm doing wrong.

Thanks In Advance

2

There are 2 answers

1
frasertweedale On BEST ANSWER

A JWT has as its payload, or Message, the UTF-8 representation of the Claims Set. From RFC 7519:

Let the Message be the octets of the UTF-8 representation of the JWT Claims Set.

This is the case for both signed JWTs (which are JWS objects), and encrypted JWTs (using JWE):

if the JWT is a JWE, create a JWE using the Message as the plaintext for the JWE; all steps specified in JWE for creating a JWE MUST be followed.

Accordingly, for verification of an encrypted JWT, the payload is interpreted as a Claims Set:

Else, if the JWT is a JWE, follow the steps specified in JWE for validating a JWE. Let the Message be the resulting plaintext.

The mistake you have made in your program is using the serialization of the signed JWT as the payload of a JWE, but then attempting to process the resulting object as n encrypted JWE. Accordingly, the library attempts to interpret a serialized signed JWT (the JWS Flattened Serialization) as a serialized JWT Claims Set (a JSON object). This explains the exception you are getting:

Caused by: org.jose4j.lang.JoseException: Parsing error:
    org.jose4j.json.internal.json_simple.parser.ParseException:
    Unexpected character (e) at position 0.

It appears that you are attempting to produce a JWT that is both encrypted and authenticated. All JWE algorithms are authenticated encryption algorithms, so there is no need to do anything with JWS to achieve this - an encrypted JWT is sufficient.

1
Brian Campbell On

For a nested JWT (i.e. JWE[JWS[JSON Claims]] which is what you're dong), the "cty" (content type) header of the the JWE is supposed to have a value of "JWT" to indicate that the payload is itself a JWT. The definition of"cty" in the JWT spec, RFC 7519, talks about that a bit more. It helps the consumer/receiver know how to process things.

The exception you're seeing is the result of the library trying to parse the JWS compact serialization, which is the payload of the JWE, as JSON.

According to spec, you really should set the cty header to "JWT" on the JWE, which indicates that the JWE payload is itself a JWT. That can be done with jwe.setHeader(HeaderParameterNames.CONTENT_TYPE, "JWT"); or jwe.setContentTypeHeaderValue("JWT") as of v0.4.2.

You can also tell the JwtConsumer to be a bit more liberal in processing and make a best effort when the cty header isn’t present and the payload doesn’t parse as JSON but can be parsed into a JOSE object. This can be done with .setEnableLiberalContentTypeHandling() on the JwtConsumerBuilder.

Couple more observations:

You don't need to set the content encryption key or the IV on the JWE. The library uses a secure random to generate those for you with the appropriate lengths. So the following should be sufficent,

JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setHeader(HeaderParameterNames.CONTENT_TYPE, "JWT");
jwe.setKey(keyEncrypt.getPublicKey());
jwe.setPayload(signedJwt);
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
String encryptedJwt = jwe.getCompactSerialization();
System.out.println("Encrypted ::" + encryptedJwt);

Also, I'm guessing from the use of RSA_OAEP_256 and AES_256_GCM that you're using Bouncy Castle. I'd strongly recommend upgrading to jose4j 0.4.4 due to a security vulnerability that was identified when using the library with the Bouncy Castle security provider. See the Release Notes on v 0.4.4 for more info https://bitbucket.org/b_c/jose4j/wiki/Release%20Notes#!jose4j-044-july-24-2015