Thalhammer / jwt-cpp

A header only library for creating and validating json web tokens in c++
https://thalhammer.github.io/jwt-cpp/
MIT License
874 stars 239 forks source link

Flattened JWS JSON Serialization #353

Open CristiF86 opened 3 months ago

CristiF86 commented 3 months ago

What would you like to see added?

Hello, current API seems to support only JWS in JWS Compact Serialization format (pls check https://datatracker.ietf.org/doc/html/rfc7515#section-7.1), any support for Flattened JWS JSON format can be provided? or we need to build the token by parsing the JSON format and extracting the protected header content, payload and signature fields?

Additional Context

Once the JSON is parsed to build the token with the formula: BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload) || '.' || BASE64URL(JWS Signature)

prince-chrismc commented 3 months ago

What part of the spec are you asking for? I found a related topic https://github.com/dvsekhvalnov/jose-jwt/issues/215#issue-1535985762 which points me to https://www.rfc-editor.org/rfc/rfc7515#section-7.2.2 which is not the same as the format you posted. So I want to clarify.

I do also need to point out this https://www.rfc-editor.org/rfc/rfc7516.html#section-9

CristiF86 commented 3 months ago

Thank you for your reply! So, indeed I need to validate a JWS received in the Flattened JSON format (https://www.rfc-editor.org/rfc/rfc7515#section-7.2.2 as was pointed by you). But from what I saw in the current implementation, there is no support for this format, and my idea is to parse the Flattened JSON, to extract the protected header content, the payload and the signature and serialize them in the JWS Compact Serialization format (see specs from https://www.rfc-editor.org/rfc/rfc7515#section-7.1). And the resulting content to be used as the token string from one of the RSA examples from the repo (similar usage as https://github.com/Thalhammer/jwt-cpp/blob/master/example/rsa-verify.cpp), since the public key in my case will be extracted from the unprotected header from the JSON. So do you think it should work, since jwt::decode seems to expect a token in this format (header.payload.siganture) https://github.com/Thalhammer/jwt-cpp/blob/a6927cb8140858c34e05d1a954626b9849fbcdfc/include/jwt-cpp/jwt.h#L3943

prince-chrismc commented 3 months ago

So do you think it should work

Probably not is the easy part. It's very important to note that a JWT is a very restricted set of JWS and JWE. The whole notion of "unprotected headers" skipped in RFC 7519 as I've understood things. (There's never a part of the header that is not included in the signature).

For this I would actually just implement a new container for the JWS it need more parts. Like https://stackoverflow.com/questions/40291116/when-would-you-use-an-unprotected-jws-header and it gets worse because they can be recursive https://stackoverflow.com/questions/50031985/what-is-a-use-case-for-having-multiple-signatures-in-a-jws-that-uses-jws-json-se.

Typically a spec implementation are quiet large. I would really love to see a contribution for this (even just the start) but to keep it simple, make a new header file.

Hopefully that helps you, others have asked for this too so a demand for this. https://docs.authlib.org/en/v0.15.4/jose/jws.html#json-serialize-and-deserialize has examples of both and does a decent job at illustrating the different APIs

CristiF86 commented 3 months ago

Thank you once again for your elaborated answer. Indeed in my case there is a Unprotected Header param since is a JWS, so it allows to have such a header, which is actually contains info about the public key (x5c will contain the certificate chain and the public key for signature verification is the one of the first certificate from the certs array). Also related to the multiple signatures, is not the case for Flattened JSON serialization which has been simplified since there is only one signature. I have added below the JSON sample, of course obfuscated:

{
    "protected": "eyJhbGciOiJQUzI1NiIsInR5cCI6Im....",
    "signature": "GKn41xM6Um9DJvoU8BLwNovIdG4RaDXQOK7ivDoh1uQO8q1fESG7o-567nHLYaw6j5wiYOstQqIhhu.....",
    "header": {
        "x5c": [
            "MIIGZDCCBMygAwICBNWZpY2F0aW9uUG9saWNpZXMvQWxsaWFuY2VDb25uZWN....."
            "MIIFRTCCAy2gAwIBAgIBDjANBgkqhkiG9w0BAQsFADBHMREwDwYDVQQKDAhBbG...."
        ]
    },
    "payload": "ew0KICAgICJ2ZWhfbWFuaWZlc3QiOi...."
}

And for example by concatenating the protected, payload and signature fields (in JWS Compact Serialization ) from the JSON above, I was able to verify the signature using the JWT debugger (https://jwt.io/#debugger-io).

prince-chrismc commented 3 months ago

Perfect example. JWT header == JWS Protected. You can see here in the code https://github.com/Thalhammer/jwt-cpp/blob/a6927cb8140858c34e05d1a954626b9849fbcdfc/include/jwt-cpp/jwt.h#L2717

There's no protected field. Signature and payload are the same AFAIK, would be worth double checking. The fact these two fields mean different things breaks down most of the logic - hence why I suggest a new container.

However you are correct that all the tools to do this are already supported. You could probably write an example using the helpers and parsing the JSON yourself without using the verifier class. The JWKS work brought this much closer to your use case.

CristiF86 commented 3 months ago

Ok, this will be the approach, to parse the JSON Flattened format to extract the header(protected), the payload and signature and to build the token. I also think it should work since the encoding is the same as the JWT ones and also for the protected header the "alg" parameter is present in my case also:

{
    "alg": "PS256",
    "typ": "jose+json",
    "cty": "json"
}

Thank you for your support!

prince-chrismc commented 3 months ago

Linking #55 for others to also find this.

Hopefully you can contribute some of the code back :)