Diagnose potential JWT::verifyAndExtract regression
@xtopherbrandt has reported a potential regression in JWT-decoding - the regression potentially being between the old OCaml backend and the new F# backend. The report:
Something has broken in decodeJWT. Code that was working a few months ago is now throwing an ArgumentNullException. ...
JWT:
eyJhbGciOiJSUzI1NiIsImtpZCI6ImIxYTgyNTllYjA3NjYwZWYyMzc4MWM4NWI3ODQ5YmZhMGExYzgwNmMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2NTI5MDIyNzgsImF1ZCI6Ijg4MzgyNjY2NjExNS1xdTN1a3BnYW5uaGszaWFjdGJoanZ2amRjN3B0aGIxZi5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjEwNDgyMTk4NTE1MjYxNjM5NjczNCIsImVtYWlsIjoieHRvcGhlci5icmFuZHRAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6Ijg4MzgyNjY2NjExNS1xdTN1a3BnYW5uaGszaWFjdGJoanZ2amRjN3B0aGIxZi5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImlhdCI6MTY1MjkwMjU3OCwiZXhwIjoxNjUyOTA2MTc4LCJqdGkiOiI2NzFhZTkwYWVjMzRkMGE1N2NjMmViNGFmYThmNDZhOGQwNTQxOTc5In0.uSvFD7P-BRYp3UmeIHYhR6nK5TGkZLubl8_bCBvm7NOzN54t757dBXGGrOcwyW-w30C5hNfxiSqm8rf21Jz25RkSXEh_aZEdnXSt8C-csySgEKkHUsIfee-McVecbaiF5HuwBvCIVoQo_F_IDQNHLJvblrQn4_OoIWV7c1xIpuBFMULNRRqYSJGYEqWIs19lEymxUNmVsp0WA-MHO-DD3FjDsSEFwIa7I0MzmiaP1uhcZEVpl8g8hKOrBxcOnjd-URRCq2aYnDk-O154-754B2OD6LgVT4k1ZILxZoKa9hoMlQh69icZMYyjDjyqLheL76pTiocrNH9QR5XmQ_1w
@xtopherbrandt could you please confirm what function you're calling?
Is it JWT::verifyAndExtract? If so, v0 or v1? Could you point to the place in your code (via a URL at the relevant handler) where this is happening?
I've briefly started on investigation here, assuming this is with JWT::verifyAndExtract_v1.
Note: while I have the JWT, I don't have the cert/key to actually verify the signature against.
The backend code for this fn is here and here.
Since I don't have the key/cert, I've commented out the lines actually verifying the signature, leaving:
let verifyAndExtractV1 (key : RSA) (token : string) : Result<string * string, string> =
match String.split "." token with
| [ header; payload; signature ] ->
//do the minimum of parsing and decoding before verifying signature.
//c.f. "cryptographic doom principle".
try
let signature = signature |> Base64.fromUrlEncoded |> Base64.decodeOpt
match signature with
| None -> Error "Unable to base64-decode signature"
| Some signature ->
let hash =
(header + "." + payload) |> UTF8.toBytes |> SHA256.Create().ComputeHash
let rsaDeformatter = RSAPKCS1SignatureDeformatter key
rsaDeformatter.SetHashAlgorithm "SHA256"
//if rsaDeformatter.VerifySignature(hash, signature) then
let header = header |> Base64.fromUrlEncoded |> Base64.decodeOpt |> Option.bind UTF8.ofBytesOpt
let payload = payload |> Base64.fromUrlEncoded |> Base64.decodeOpt |> Option.bind UTF8.ofBytesOpt
match (header, payload) with
| Some header, Some payload -> Ok(header, payload)
| Some _, None -> Error "Unable to base64-decode header"
| _ -> Error "Unable to base64-decode payload"
// else
// Error "Unable to verify signature"
with
| e -> Error e.Message
| _ -> Error "Invalid token format"
When I do this, I see that I'm returned the same JSON that jwt.io shows me for the given JWT - expected result.
In other words: removing the bit of our code here where we verify the signature, results in the JWT being decoded successfully.
Is there any chance that you're referencing a different key? Or maybe referencing another function (not JWT::verifyAndDecode_v1)/
Ahh. Sorry. The problem is in my app. The JWT sent by my client has a kid that isn't in the current Google public key. Some kind of synchronization problem.
However, that ArgumentNullException did show up again as I was tracing. Here's a link to the function and trace. I can't easily reproduce it. I didn't appear right away, it took a while so I think it maybe a related to a timeout.
Yeah, this happens during some sort of GC process in the dotnet/webassembly runtime. We just need to make sure this error is not displayed, as I don't think it has any effect on anything.
This was fixed last month
