JWT woes - crypto issues?

59 views
Skip to first unread message

Igor Clark

unread,
Feb 5, 2021, 2:40:16 PM2/5/21
to Erlang Questions
Hi folks,

I'm trying to use the jwerl library (https://github.com/G-Corp/jwerl) to sign JWTs using the ES384 algorithm, which is required by the system for which I'm generating the token.

It seems to work fine internally; I sign something with my private key, verify the result straight back and it works:

> 30> Claims = #{ blah => <<"load of blah">>, exp => 1612639232 }.
> #{blah => <<"load of blah">>,exp => 1612639232}
> 31> jwerl:sign( Claims, es384, PrivateKey ).
> <<"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJibGFoIjoibG9hZCBvZiBibGFoIiwiZXhwIjoxNjEyNjM5MjMyfQ.MGYCMQCSNwgjw_i4EVxhRxox"...>>
> 32> jwerl:verify( jwerl:sign( Claims, es384, PrivateKey ), es384, PublicKey ).
> {ok,#{blah => <<"load of blah">>,exp => 1612639232}}


However when the Python code at the other end tries to decode it, using the same public key, it says "Signature verification failed".

I try it the other way round, using the same private key to encode it on the Python side, and the same internal consistency check in the Python library works fine:

> >>> claims
> {'blah': 'load of blah', 'exp': 1612639232}
> >>> jwt.encode( claims, private_key, algorithm="ES384" )
> 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJibGFoIjoibG9hZCBvZiBibGFoIiwiZXhwIjoxNjEyNjM5MjMyfQ.FxPVtSx0R8JjgYhoDYmuJKOyE7bY[...]f'
> >>> jwt.decode( jwt.encode( claims, private_key, algorithm="ES384" ), public_key, algorithms=["ES384"] )
> {'blah': 'load of blah', 'exp': 1612639232}


But when I copy the token from the Python output and try to decode it in Erlang/jwerl with the same public key:

> 38> jwerl:verify( PythonJwt, es384, PublicKey ).
> {error,invalid_signature}


So they both can encode and decode the JWT using ECDSASHA384 without problem, but when either of them tries to decode a JWT encoded by the other one, *using the same keys*, it fails.

The reason I'm posting this here rather than on a Python forum is that when I paste the Python-generated JWT into the debuggger at jwt.io, and paste in the public key, I get "Signature Verified". When I paste in the Erlang/jwerl-generated JWT into the same box, with the same key in there, I get "Invalid Signature".

It could obviously be a problem with the jwerl library, but it seems reasonably recently maintained, lots of downloads on hex.pm, etc, etc, and it seems at least internally consistent.

So I'm wondering if it's something to do with the underlying crypto or related packages, or their setup, in my Erlang/OTP? I'm running Erlang/OTP 23 [erts-11.1.7] on MacOS Catalina 10.15.7, and although jwerl compiles and runs, rebar3 shell shows a warning:

> _build/default/lib/jwerl/src/jwerl_hs.erl:7: Warning: crypto:hmac/3 is deprecated and will be removed in OTP 24; use use crypto:mac/4 instead

I've tried several other Erlang JWT libraries, which won't even seem to build because the compile fails with that same warning (I guess this is something to do with warnings as errors, maybe, but I don't know how to switch that off in rebar3) - I've even tried importing Elixir libs but I got nowhere quickly with that. The jwerl library seems to work and is apparently in decently wide use, so there must be something off about my setup or *something*, given that interoperability is pretty much the point of JWT, and this is falling at the first hurdle.

Anyone got any ideas? Would really appreciate a pointer on how to track down what's going wrong!

Thanks,
Igor

Igor Clark

unread,
Feb 5, 2021, 3:11:04 PM2/5/21
to Łukasz Niemier, Erlang Questions
Thanks Łukasz,

I could have made a mistake for sure! But I'm as sure as I can be - that was pretty much the first thing I checked.

The Python is using the base64-encoded string copied from the PEM file, which works, generating JWTs that jwt.io validates successfully from the paired public key.

The Erlang jwerl code is using the same base64-encoded string in a binary (ie <<"-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDAt8OrDBu"...>>), because that's the only way the library would accept it, and because when I looked in the code for jwerl I saw it was using public_key:decode_pem/1 directly on the passed-in key, so it seemed like that was the right format to use.

Cheers,
Igor

> On 5 Feb 2021, at 19:47, Łukasz Niemier <luk...@niemier.pl> wrote:
>
> Are you sure that the secret is the same and is passed in the format that both libraries expect? Because one may require Base64 encoded string and other will require to pass raw data.
>
> --
> Łukasz Jan Niemier

Igor Clark

unread,
Feb 5, 2021, 3:56:04 PM2/5/21
to Łukasz Niemier, Erlang Questions
Yes just checked again to be 100% sure. Using identical keys, literally copy-pasted from one to the other, the Python library generates token signatures that it validates itself and which are validated by jwt.io with the public key, but which the erlang one says is invalid, with the same key; the erlang one generates token signatures that it validates itself, but which python and jwt.io say are invalid. I guess it's looking more and more like it's just the jwerl lib, or just the es384 routine, but that still makes me wonder if it's something about the underlying crypto stuff being used.

However, just to check, I ran the erlang version in a docker image with Debian 10, and that had exactly the same problem. So probably not to do with underlying OS crypto if it's the same on both ...

Really puzzled!

gri...@gmx.de

unread,
Feb 6, 2021, 2:02:12 PM2/6/21
to Erlang Questions
Hi,
 
> I've tried several other Erlang JWT libraries, which won't even seem to build because the compile fails with that same warning (I guess this is something to do with warnings as errors, maybe, but I don't know how to switch that off in rebar3) - I've even tried importing Elixir libs but I got nowhere quickly with that. The > jwerl library seems to work and is apparently in decently wide use, so there must be something off about my setup or *something*, given that interoperability is pretty much the point of JWT, and this is falling at the first hurdle.

> Anyone got any ideas? Would really appreciate a pointer on how to track down what's going wrong!

> Thanks,
> Igor
 
I used https://github.com/artemeff/jwt about a year ago and it worked fine for me.

Michael

Leonard B

unread,
Feb 6, 2021, 2:44:53 PM2/6/21
to gri...@gmx.de, Erlang Questions
Michael, one small issue with https://github.com/artemeff/jwt

It does not support ES384
See: https://github.com/artemeff/jwt/blob/master/src/jwt.erl#L47

Igor Clark

unread,
Feb 7, 2021, 7:01:27 AM2/7/21
to Erlang Questions
Thanks very much Michael and Leonard for your replies!

ES384 is mandated by the service I'm generating the tokens for ( Amazon IVS https://docs.aws.amazon.com/ivs/latest/userguide/private-channels-generate-tokens.html ).

On the assumption the problem not anything to do with the erlang/SSL version, or similar, but rather just an issue with jwerl, I've submitted an issue on jwerl's gitlab repo (as the Github one seems to be just a mirror), with sample code, keys and outputs: https://gitlab.com/glejeune/jwerl/-/issues/17

I'd be delighted if anybody here has any more light to throw on the matter, but hopefully will hear something back from the Gitlab issue. Will close the loop on this message FYI if so.

Thanks, all, for your time -
Igor

Igor Clark

unread,
Feb 7, 2021, 10:20:26 AM2/7/21
to Erlang Questions
Just to close the loop here, Leonard very kindly sent me a patch that makes the 'jwt' library support ES384, so I'm going to go ahead and use that instead. Wonderful.

Thanks Leonard, and thanks list,

best,
Igor
Reply all
Reply to author
Forward
0 new messages