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

How am I supposed to parse tokens created with: jwt.NewSerializer. #1133

Closed
advdv opened this issue May 15, 2024 · 7 comments
Closed

How am I supposed to parse tokens created with: jwt.NewSerializer. #1133

advdv opened this issue May 15, 2024 · 7 comments
Assignees

Comments

@advdv
Copy link

advdv commented May 15, 2024

Thank you for this great library! I'm trying to store a refresh token in an encrypted JWT. Thinking I don't want to implement such critical security functionality from scratch I figured I used the high-level jwt.NewSerializer. It seems to allow for signing and encrypting a payload. But I don't understand how I'm supposed to parse it. I always seem to get "unsupported format (#2)"

I great the token like this

serialized, err := jwt.NewSerializer().
Encrypt(jwt.WithKey(encryptKey.Algorithm(), encryptKey)).
Sign(jwt.WithKey(signKey.Algorithm(), signKey)).
Serialize(tok)
if err != nil {
return "", fmt.Errorf("failed to serialize session token: %w", err)
}

The keys come from two key sets. One for encryption, one of signing (as I understand that we require different key for this). I then go ahead an try to parse it like this:

session, err := jwt.ParseString(cookie.Value,
   jwt.WithClock(e.clock),
   jwt.WithKeySet(e.keys.encrypt.private),
   jwt.WithKeySet(e.keys.signing.public))
if err != nil {
    return "", fmt.Errorf("failed to parse session cookie: %w", err)
}

The e.keys, looks like this:

// Keys hold our own private keys, and the WorkOS public keys.
type Keys struct {
	cfg    Config
	workos struct {
		public jwk.Set
	}
	signing struct {
		private jwk.Set
		public  jwk.Set
	}
	encrypt struct {
		private jwk.Set
		public  jwk.Set
	}
}

This is an example token: eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.ZXlKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V5TlRaSFEwMGlMQ0owZVhBaU9pSktWMVFpZlEubFVCR1NhcmFJOG9Sd29vRTBKblhnb3BFNndyS3gzZWxVUWdYczdSQ1RtUlpIR0NSVi1hdlpDdjQzUmltbUJ6RC1pa1JSWDJXNnVEcGVnbV9ZbVlxU3JKV2h2RW92SlRoSWpfQXJDaXN1OGR0M1NHRWJvR1RrWmU4TUE1VkN4RTFxNWthQTJ0bHhzeU5xWDN6MGcyd0daWXZSZ3JHSDJQZk5PVkxiQ2RxX0hmZmE5TER4U2pKNlRiZ0ZWZi1FSjJyMkdTS09HbHFFU2xPRGtoalp1b25RMXRkaWZvcDlDRXdsclp3S0ZPOUdRclBITkQxMkQwNmJvaVhnemhWWGUxTldsRklya3VLZ2xndXNoWGI1aDBHLWUzNnh4WWZEZUpOMTBJNXFtSTg1Y0tKV05vaHREWEtWa2JTaV94dnIwMWJCTGMzWHVSQmNabVJFSDdKQjdTYllBLklOUEY0YmlHbFlTTkpSdUkuWnNIQVVaeEtzb0tGTV84OUFVTDY2YU1GX21sUGFwYUNUSjEtLkdqNXNpRElRUWZvZ2YzR2pIY1JLOXc.w6o725mvW4bnZJ1XlzvnqAIQxIg13imuJwE6pLR1uyc

The testing keys I'm using look like this (well-known, just used for testing):

{
    "keys": [
        {
            "kty": "EC",
            "d": "Qj0DkYfqE4jJqB7iPhCnsQJet2po3014OXAXzOhZSds",
            "use": "sig",
            "crv": "P-256",
            "kid": "key1",
            "x": "6WoYjtPv1EbyPpqzdhn5sTcyxHnDS6hgoy1aJ6iZVAc",
            "y": "fQGoUnduRNTPzC3KnRlv8wcrghf9c1BH7BdDm5EEWG8",
            "alg": "ES256"
        }
    ]
}

And

{
    "keys": [
        {
            "kty": "EC",
            "d": "4DWqDmifdqsu3AJX_kcZYtDwA1ypD_XY24svDAqvV4k",
            "use": "enc",
            "crv": "P-256",
            "kid": "key1",
            "x": "LaQF_NldYtMMRTZ9tBc9HPwJDIA51VCMDGbQyUxTL-8",
            "y": "73PK1Y6VKBK_9_Ym1WYPyoff0Js5t7TiLISVDWCEjro",
            "alg": "ECDH-ES+A128KW"
        }
    ]
}
@lestrrat
Copy link
Collaborator

You are ENCRYPTING and then SIGNING. jwt.ParseXXXX only does signature verification/unwrapping. You need to use jwe.Decrypt yourself to decrypt your token before calling jwt.ParseXXX (or jws.Verify)

@advdv
Copy link
Author

advdv commented May 15, 2024

Ah, ok. Thank you for the super fast reply. I did try that (see below) but I got the error compact JWE format must have five parts (3) so I figured I should leave it to a higher level abstraction to do it. I wil do more research and learn about what "compact" means.

	decrypted, err := jwe.Decrypt([]byte(cookie.Value),
		jwe.WithKeySet(e.keys.encrypt.public))
	if err != nil {
		return "", fmt.Errorf("failed to decrypt session cookie: %w", err)
	}

@lestrrat
Copy link
Collaborator

lestrrat commented May 15, 2024

An, sorry, in your case you need to jws.Verify, then jwe.Decrypt, and then finally jwt.Parse.

That is, you are doing signed = Sign(Encrypt(JWT_payload)), so in order to get back the JWT_payload, you need to do ParseJWT(Decrypt(Verify(signed)))
I highly suggest you look into how these messages are constructed from the RFCs or similar.

@advdv
Copy link
Author

advdv commented May 15, 2024

I'm now doing the following:

	verified, err := jws.Verify([]byte(cookie.Value), jws.WithKeySet(e.keys.signing.public))
	if err != nil {
		return "", fmt.Errorf("failed to verify session token: %w", err)
	}

	decrypted, err := jwe.Decrypt(verified, jwe.WithKeySet(e.keys.encrypt.private))
	if err != nil {
		return "", fmt.Errorf("failed to decrypt session token: %w", err)
	}

	fmt.Println(string(decrypted)) // {"rt":"some.refresh.token"}

	parsed, err := jwt.Parse(decrypted,
		jwt.WithClock(e.clock),
		jwt.WithKeySet(e.keys.signing.public))
	if err != nil {
		return "", fmt.Errorf("failed to parse session token: %w", err)
	}

But it will fail on the final parse because "decrypted" now just looks like this: {"rt":"some.refresh.token"}. I will look into how this exactly works but I just want to report it here in case it's unexpected. The error is : failed to unmarshal jws message: required field "signatures" not present

Ofcourse, I now have the data I was looking for so it is fine. Just reporting here in case it's unexpected

@lestrrat
Copy link
Collaborator

The last error is because you're trying to jwt.Parse with the key set -- that is, you're verifying the message signature. If the payload you end up with is a JSON message, you could either simply use json.Unmarshal, or use jwt.Parse with the jwt.WithVerify(false) option. I'm not claiming I have the best documentation, but these are all documented, so please take a bit of time looking at the documentation or the examples directory.

@advdv
Copy link
Author

advdv commented May 15, 2024

I will, thank you again for the quick responses (and the great library). I will close this, maybe others in the future have use for the information in this thread.

@advdv advdv closed this as completed May 15, 2024
@lestrrat
Copy link
Collaborator

no prob. Thanks for the kind words

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