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

[Feature request] Decode JWT without verification #75

Closed
hobofan opened this issue Apr 24, 2017 · 9 comments
Closed

[Feature request] Decode JWT without verification #75

hobofan opened this issue Apr 24, 2017 · 9 comments

Comments

@hobofan
Copy link
Contributor

hobofan commented Apr 24, 2017

As far as I can see it, right now there is no way to access to payload/decoding a JWT without verifying the signature. I can see how it generally isn't good to encourage such a usage, but I think there are some valid reasons to allow it.

My use-case:
I am using JWTs in a microservice environment, where there is a central auth server that gives out the JWTs. Currently, the other microservices get the public key to verify the JWT from a fixed route on the auth service. This limits the system to one key pair for the whole system, which is rather inflexible.

What I would like to do is allow multiple auth servers/multiple key pairs to exist in the system. To do this, I would put the URL of the respective public key into the payload of the JWT. As long as the URL is limited to a whitelist of acceptable public key URLs, this should represent a secure system. However, this would require the payload to be read without the JWT signature being verified since the relevant public key is not yet known at that point.


EDIT: The same thing applies to the JWT headers, since the jku, jwk, kid, x5u, x5c, x5t, and x5t#S256 header fields provide a way to achieve the use-case I wrote about above.

@lawliet89
Copy link
Owner

lawliet89 commented Apr 25, 2017

Thank you for writing up your use case, and your suggestion.

You should put the key set in one of the header parameters. I have not built support for those headers yet. Support is tracked in #15.

The philosophy I am trying to adopt for this library (inspired by the design of others such as ring and even Rust itself) is for it to be as easy to use as possible, and helping the user to avoid mistakes if possible. If the user would like to do something that is not the norm, or might be unsafe, the library should provide the means to do so, but in a slightly more onerous fashion so that only users who know what they are doing will use it. Rust's unsafe comes to mind. What you suggest is somewhat outside of that philosophy and I am not sure if I should add an API for this.

That being said, it is certainly doable with the public API that the library provides now. It requires a little bit of digging through the implementation detail. Let me give you an example.

The Encoded variant of the jws::Compact enum is a wrapper around the Compact struct.

The Compact struct holds a bunch of Base64URL encoded payloads, concatenated by periods (.). This is basically the Compact serialization common to JWS and JWE. You can use the part method to extract and deserialize the relevant part of your JWS.

So, assuming you want to extract values from the JWS header prior to verifying its signature, you can do this:

extern crate biscuit;

use biscuit::{Compact, Empty};
use biscuit::jws;

fn main() {
    // Token's payload is arbitrary bytes
    let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IlJhbmRvbSBieXRlcyJ9.\
                 eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcG\
                 xlLmNvbS9pc19yb290Ijp0cnVlfQ.\
                 E5ahoj_gMO8WZzSUhquWuBkPLGZm18zaLbyHUQA7TIs";

    let compact = Compact::decode(&token);

    let header: jws::Header<Empty> = compact.part(0).unwrap();
    println!("{:?}", header);

    let payload: Vec<u8> = compact.part(1).unwrap();
    println!("{:?}", payload);

    let signature: Vec<u8> = compact.part(2).unwrap();
    println!("{:?}", signature);
}
$ cargo run
   Compiling jwt-decode-without-verification v0.1.0 (file:///home/yongwen/work/scratch/jwt-decode-without-verification)
    Finished dev [unoptimized + debuginfo] target(s) in 2.6 secs
     Running `target/debug/jwt-decode-without-verification`
Header { registered: RegisteredHeader { algorithm: HS256, media_type: Some("JWT"), content_type: Some("Random bytes"), web_key_url: None, web_key: None, key_id: None, x509_url: None, x509_chain: None, x509_fingerprint: None, critical: None }, private: Empty }
[123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125]
[19, 150, 161, 162, 63, 224, 48, 239, 22, 103, 52, 148, 134, 171, 150, 184, 25, 15, 44, 102, 102, 215, 204, 218, 45, 188, 135, 81, 0, 59, 76, 139]

Please let me know if you disagree with my sentiment above, and I would gladly add an API for this operation, or at least provide examples.

@hobofan
Copy link
Contributor Author

hobofan commented Apr 25, 2017

Thanks for the example!

I generally agree with the sentiment, that is should be obvious to the user that they are potentially shooting themselves in the foot when using trickier parts of the library. However, I think that this can be achieved in better ways than forcing the use of a more cumbersome API.

One good way to prevent misuse in Rust in my experience is to put the "risky" operations into a trait that is not imported with the default wildcard import (e.g. UncheckedDecoded on jws::Compact). This forces the user to go out of his way to achieve his goals, but still allows the usage of a pleasant interface for people that know what they are doing.

Another way to prevent misuse (which I think is the main philosophy of the std), is by providing proper documentation, where every risky operation has a Warning section.

@lawliet89
Copy link
Owner

lawliet89 commented Apr 25, 2017

Thanks for your views.

Do you think what I've provided as example code is sufficient, or I should have something closer to the jws::Compact (and jwe::Compact) types like a trait like you said, for example?

My code example requires more understanding of the various "parts" of a JWS (and subsequently JWE) compact serialisation.

@neverfox
Copy link

neverfox commented Aug 16, 2017

@lawliet89 I appreciate your dedication to the philosophy of safety in the API. However, I'm not sure that should come at the expense of the philosophy of JWTs themselves, particularly in a JWT library. JWTs, as I'm sure you know, are transparent by design, in contrast to, say, OAuth. I think the API of any JWT library should reflect this and it should be considered a first-class, straightforward feature to "peer into" an unencrypted JWT, especially for the use case of determining the very means necessary to do the verification in the first place. I cannot think of a library I've come across that doesn't allow this. Whether or not Rust is doing them all one better is an argument, but I cannot say I find the argument convincing yet. In other words, I cannot really imagine what an API might be that wasn't clearly intentional and thereby safe enough.

Perhaps I'm missing something about the use-case for the None variants of Secret and SignatureAlgorithm, but I had originally assumed they could be used to skip verification, rather than errorring out on non-empty signatures. Assuming that you do have use-cases in mind that warrant keeping those as is, then why isn't the feature we want a matter of adding additional variants that don't error out (and don't verify)? And wouldn't having to choose that variant be enough of a clear indication of intention for safety concerns? If there were convenience methods called decode_unverified (not necessarily endorsing that), haven't you done your due diligence to save the developer from themselves? I apologize if I'm overlooking a deeper concern.

If I'm not completely off-target about how the API might change here, I'd be interested in helping put together a PR.

@neverfox
Copy link

neverfox commented Aug 16, 2017

I guess there's also a concern about having hold of a Decoded token and not knowing if it was verified at the time it was decoded. That's understandable, I suppose, but would depend on how central the possibility is of being handed a decoded token that you couldn't be sure was verified if it needs to be (you could always verify it again, to be sure). Perhaps, the verification status should be unlinked from the en/de-coding status, i.e. expressed orthogonally. In any case, that, in combination with having the ability to simply do the unverified decoding and verify and unverified/decoded token (converting it to regular Decoded) should cover all bases, yeah?

@lawliet89
Copy link
Owner

lawliet89 commented Aug 17, 2017

JWTs, as I'm sure you know, are transparent by design, in contrast to, say, OAuth.

I am not sure what you mean by this statement. If by transparent, you mean that the contents are transparent, then yes: unencrypted JWTs are already transparent. But encrypted JWTs are still not transparent. This issue is primarily about using signatures to check the authenticity and integrity of the JWT themselves.

Perhaps I'm missing something about the use-case for the None variants of Secret and SignatureAlgorithm, but I had originally assumed they could be used to skip verification, rather than errorring out on non-empty signatures.

It's in the spec to allow you to simply not have a signature for your JWT.

I will look into adding a function to allow "peering" into the contents of the payload without checking the signature.

I guess there's also a concern about having hold of a Decoded token and not knowing if it was verified at the time it was decoded. That's understandable, I suppose, but would depend on how central the possibility is of being handed a decoded token that you couldn't be sure was verified if it needs to be (you could always verify it again, to be sure). Perhaps, the verification status should be unlinked from the en/de-coding status, i.e. expressed orthogonally. In any case, that, in combination with having the ability to simply do the unverified decoding and verify and unverified/decoded token (converting it to regular Decoded) should cover all bases, yeah?

That's my main concern. I don't really want to complicate the design of the library too much, though.

Anyway, please note that if you use encrypted JWTs (type JWE in the library), you HAVE to go through verification and decryption at the same time. It's designed into the cryptographic algorithm themselves.

@neverfox
Copy link

neverfox commented Aug 17, 2017

@lawliet89 Thanks for the response.

unencrypted JWTs are already transparent

Correct, but do you mean they're already transparent with biscuit, meaning there's a first-class API for viewing the contents without providing the secret needed to verify the signature? If so, then I might have jumped the gun, but I had come to the same conclusion as @hobofan in my poking around. I think we're both focused on the fact that I can, for example, use a tool like https://www.jsonwebtoken.io/ without knowing the signing key, but we can't (afaik) with biscuit or not easily. A flow I have in mind is something like this:

  1. Receive a JWS token created and signed by some arbitrary client I might recognize.
  2. Decode the JWS (without yet validating it)
  3. Determine who claims to have issued the token
  4. If I believe I know the claimed issuer, validate the token using a public key I know how to find, perhaps using additional information in the token, such as the algorithm used or a key server URL.

I also might want to know if I'm even the intended audience before I even bother to determine if I know the issuer and how I might go about validating them and their token. If I'm not, I'll 403 them and carry on.

The algorithm you posted above gets most of the way there for these use-cases.

But encrypted JWTs are still not transparent.

Anyway, please note that if you use encrypted JWTs (type JWE in the library), you HAVE to go through verification and decryption at the same time. It's designed into the cryptographic algorithm themselves.

Of course. I assumed the conversation was already focused on JWS not JWE, but I should have been clear that that was what I was focused on.

It's in the spec to allow you to simply not have a signature for your JWT.

Got it. Makes sense.

I will look into adding a function to allow "peering" into the contents of the payload without checking the signature.

Awesome. Let me know if I can help. It would be lovely to receive a token and then immediately be able to do things like token.issuer(), token.audience() or token.algorithm() for example.

@lawliet89
Copy link
Owner

I am adding them as we speak. The functions will basically be convenient wrappers around the example code I've posted earlier in this issue.

@lawliet89
Copy link
Owner

Please take a look at #88. If the API looks OK, I'll merge that in.

lawliet89 added a commit that referenced this issue Aug 17, 2017
…erification (#88)

* Add APIs to `jws::Compact` for retrieval of parts without signature verification

Fixes #75

* Minor cleanups

* Fix formatting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants