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

Surface rawToken for JWT wrapper #757

Closed
wants to merge 2 commits into from
Closed

Surface rawToken for JWT wrapper #757

wants to merge 2 commits into from

Conversation

salrashid123
Copy link

Adds ability to recall the raw JWT encoded token from JsonWebSignature and JsonWebToken.

The current .toString() method for both classes returns a JSON object. Its useful to have access to the raw token directly to be used in an http client (as Authorization: Bearer <rawToken>, for example)

ref:
googleapis/google-auth-library-java#303 (review)

@salrashid123 salrashid123 requested a review from a team as a code owner August 3, 2019 18:40
@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Aug 3, 2019
@elharo elharo added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 5, 2019
@kokoro-team kokoro-team removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 5, 2019
@@ -361,6 +362,12 @@ public String toString() {
return Objects.toStringHelper(this).add("header", header).add("payload", payload).toString();
}

public String getRawToken() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method needs JavaDoc. Also note that the word "raw" does not appear in the RFC. Is there a term in the RFC that does describe this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cloest term maybe getEncodedToken()

Copy link
Contributor

@elharo elharo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test failing:

[ERROR] testRawToken(com.google.api.client.json.webtoken.JsonWebSignatureTest) Time elapsed: 0.008 s <<< ERROR!
java.lang.IllegalArgumentException: no JSON input found
at com.google.api.client.json.webtoken.JsonWebSignatureTest.testRawToken(JsonWebSignatureTest.java:52)

@@ -361,6 +362,12 @@ public String toString() {
return Objects.toStringHelper(this).add("header", header).add("payload", payload).toString();
}

public String getRawToken() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the spec, I think what this is, is a "compact serialized form". Furthermore, I'm beginning to think it belongs in the JsonWebSignature class rather than JsonWebToken, because it isn't defined for JsonWebToken.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, thats kind of what i meant that i don't know where i'd use the internal method directly (i.,e JsonWebToken.getRawToken(). i'lonly make this visible to JsonWebToken class

@@ -482,6 +482,11 @@ private static X509TrustManager getDefaultX509TrustManager() {
return signedContentBytes;
}

/** Returns the URL safe Base64 encoded JWS token. */
public String getRawToken() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're unlikely to get the API perfect on the first try, we should mark this @beta to warn users it might change.

@@ -361,6 +362,12 @@ public String toString() {
return Objects.toStringHelper(this).add("header", header).add("payload", payload).toString();
}

public String getRawToken() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're unlikely to get the API perfect on the first try, we should mark this @beta to warn users it might change.

@@ -482,6 +482,11 @@ private static X509TrustManager getDefaultX509TrustManager() {
return signedContentBytes;
}

/** Returns the URL safe Base64 encoded JWS token. */
public String getRawToken() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Override

@salrashid123
Copy link
Author

ok, this became a bit more complicated. The classes are structured such that the original tokenString in JsonWebSignature parse(String tokenString) gets marshalled into a json object which itself sorts the claims. the reverse mechanism i use to unmarshall creates a different string representaton (i.,e the order is different).

so a snippet like this

String rawToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikp"
+ "vaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
JsonWebSignature signature = JsonWebSignature.parse(JacksonFactory.getDefaultInstance(), rawToken);
System.out.println(signature.getEncodedToken());
System.out.println(rawToken);

gives

{
  "alg": "HS256",
  "typ": "JWT"
}.
{
  "iat": 1516239022,
  "sub": "1234567890",
  "name": "John Doe"
}

vs

{
  "alg": "HS256",
  "typ": "JWT"
}.
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

@elharo
Copy link
Contributor

elharo commented Aug 5, 2019

Per the JSON specification "An object is an unordered set of name/value pairs". Nothing here, tests included, should depend on this order.

@salrashid123
Copy link
Author

The ordering thing doesn't matter for JSON but in this case i'm 'attaching' a singature part of some claims that were in a specifc order to another..the net result is the JWT string representation that the output of these methods isn't valid anymore.

For example, if i take a valid rawToken that i know works on a serivce like Cloud Run

String rawToken = "a.b.c";
JsonWebSignature signature = JsonWebSignature.parse(JacksonFactory.getDefaultInstance(), rawToken);
System.out.println(signature.getEncodedToken());

the output of signatuere.getEncodedToken() isn't the same jwt and doens't work anymore against Cloud Run. Basically, i probably should figure out how to 'just save and return' the raw token thats passed into the .parse() object w/o marshalling/unmarshalling....

@elharo
Copy link
Contributor

elharo commented Aug 5, 2019

The signature should be independent of order as well. If it isn't, then there's a bug somewhere, maybe more than one. Are you saying that

{
  "iat": 1516239022,
  "sub": "1234567890",
  "name": "John Doe"
}

produces a different signature than

{
  "sub": "1234567890",
  "name": "John Doe"
  "iat": 1516239022,
}

@salrashid123
Copy link
Author

the order does matter...to sign, basically i'll take

{
  "iat": 1516239022,
  "sub": "1234567890",
  "name": "John Doe"
}

use it as string, covert to base64 then digitally sign that to get the signature

which isn't going to produce the same out put if i alter the claims around.

@elharo
Copy link
Contributor

elharo commented Aug 5, 2019

I need to check the spec, but if that's really how the JWT spec defines signing, then the spec is broken. The usual way of handling this problem is defininf a canonical and reprodubile ordering; e.g. like canonical XML does for attributes.

@elharo
Copy link
Contributor

elharo commented Aug 5, 2019

Also, I think we might be jumping too fast to code here. This is more complex than I realized and I'm not sure we understand the problem sufficiently well. It's probabaly worth starting with a design doc that lays out the plansd, tradeoffs, and issues involed.

@elharo
Copy link
Contributor

elharo commented Aug 6, 2019

So I've confirmed that the spec is indeed broken. You will need to figure out how to 'just save and return' the raw token that's passed into the .parse() object w/o marshalling/unmarshalling....

@salrashid123
Copy link
Author

yep...thats what i figured (i initially started this thinking the 'easiest' way was to 'just save and retun' the token thats passed in thorugh this

public JsonWebSignature parse(String tokenString)

...but then realized there is also a constructor that a constructor which immediately messes up the ordering

public JsonWebSignature(
      Header header, Payload payload, byte[] signatureBytes, byte[] signedContentBytes) {

what that means is while i could use the token passed into parse() anyone using the direct constructor won't be able to get .getEncodedToken() properly.

I could just throw an exception if someone tries to use that method without going through static .parse()....but that doens't seem all too clean...

@elharo
Copy link
Contributor

elharo commented Aug 6, 2019

Can you remove that constructor or make it private?

@salrashid123
Copy link
Author

i don't think i can remove or make it not visible (it'd break anyone using it (including our tests):
https://github.com/googleapis/google-http-java-client/blob/master/google-http-client/src/main/java/com/google/api/client/testing/json/webtoken/TestCertificates.java#L312

i could create second constructor pass through the string format (one that'd set the raw token and then call the existing constructor.

public JsonWebSignature(
      Header header, Payload payload, byte[] signatureBytes, byte[] signedContentBytes, String encodedToken)

at this point, i kinda don't know what to do since there doesn't seem to be a good way out of this

@elharo
Copy link
Contributor

elharo commented Aug 6, 2019

You could deprecate this constructor. Might be worth doing that if it's definitely broken like this.

@elharo
Copy link
Contributor

elharo commented Aug 6, 2019

I think I see a way to fix this. We need to canonicalize field order before signing, and do all our own signing. It will need some new runtime exceptions, but should be plausible. I do wonder if it might be easier to build a better library. This class has other problems too, such as exposing mutable internal state.

@elharo
Copy link
Contributor

elharo commented Aug 6, 2019

@BenWhitehead suggested we look at https://github.com/auth0/java-jwt to see if it might work better than rolling our own.

@salrashid123
Copy link
Author

ugh...this is a bit of a mess..I'm not sure if canonicalizing before signing is even an option anymore...
the existing surface

public JsonWebSignature(
      Header header, Payload payload, 
      byte[] signatureBytes, 
      byte[] signedContentBytes, 

precludes us from doing anything since the Payload is already provided in whatever alpha(?) order it uses...and the signed portion is separate (meaning it was signed already with whatever propr string format it was...).

This means JWT Header, Payload objects used in the code is disconnected from the signature that was itself passed

better way maybe to just do a rewrite after annotating as deprecated and use the auth0 stuff

@elharo
Copy link
Contributor

elharo commented Aug 7, 2019

I think it's legitimate to have the public JsonWebSignature(Header header, Payload payload, byte[] signatureBytes, byte[] signedContentBytes) throw a runtime exception if the signatureBytes and signedContentBytes don't match the header and payload. It's API compatible, and a runtime exception works because this is an illegal argument problem, i.e. a programming error on the part of the caller.

If we use auth0 perhaps that could be an internal implementation detail?

clundin25 pushed a commit to clundin25/google-http-java-client that referenced this pull request Aug 11, 2022
Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Co-authored-by: Emily Ball <emilyball@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants