Signing with ES384 keys doesn't seem to work properly
Hi, I hope this is the right place to report this, 'issues' don't seem enabled on Github.
JWTs generated by jwerl
using ES384 are verified and decoded correctly by jwerl
, but not by the Python jwt
library, and not by jwt.io, or the service for which I'm generating the tokens (Amazon IVS). They both say it's an invalid signature. Also, JWTs generated by either Python jwt
or jwt.io
are correctly verified and decoded by each other, but not by jwerl
- it returns {error,invalid_signature}
.
It'd be great to know if this is something I'm doing wrong, or an issue with jwerl
's ES384 signing/verifying, and if the latter then whether it can be fixed. The other erlang JWT library I tried doesn't support ES384.
I'm using version 1.1.0 on erlang/OTP 23.2.3 on MacOS, and I've tried it with erlang 23 on Debian 10 with the same results.
To help reproduce, I've included simple sample code in python and erlang, screenshots of the jwt.io
results, along with some sample ECDSA keys I generated for the test, according to the IVS key specs - they're throwaway keys which won't be used anywhere else.
Thanks for any feedback, would be much appreciated!
new-priv.pem
:
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDBGV5UXMaYlAudY9f56cojR5A+4Uae9wN/ah1/30Ei7Ah5Etwnmq0/V
+DW9JRwD346gBwYFK4EEACKhZANiAAQU+DRUG0CEmuKqEEIW4F5vlVk1Vb09FlCn
HCy7b1EkR+ZEdZokbEzPtJ8O3rMP23ynPCOX6PObSX9p0QYHh0xvWs9K7yukZDFD
XWmbjMmGsGaY2MS9RUK0qgAalULYfhU=
-----END EC PRIVATE KEY-----
new-pub.pem
:
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFPg0VBtAhJriqhBCFuBeb5VZNVW9PRZQ
pxwsu29RJEfmRHWaJGxMz7SfDt6zD9t8pzwjl+jzm0l/adEGB4dMb1rPSu8rpGQx
Q11pm4zJhrBmmNjEvUVCtKoAGpVC2H4V
-----END PUBLIC KEY-----
Erlang code to sign/verify:
59> { ok, PrivPem } = file:read_file("new-priv.pem").
{ok,<<"-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDBGV5UXMaYlAudY9f56cojR5A+4Uae9wN/ah1/30Ei7Ah5Etwnmq0/V\n+DW9JRwD346g"...>>}
60> { ok, PubPem } = file:read_file("new-pub.pem").
{ok,<<"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFPg0VBtAhJriqhBCFuBeb5VZNVW9PRZQ\npxwsu29RJEfmRHWa"...>>}
61> ExpiryTimestamp = os:system_time( seconds ) + ( 120 * 60 ).
1612704210
62> JwtPayload = [
62> { 'aws:channel-arn', <<"sample-aws-arn">> },
62> { 'aws:access-control-allow-origin', <<"*">> },
62> { exp, ExpiryTimestamp }
62> ].
[{'aws:channel-arn',<<"sample-aws-arn">>},
{'aws:access-control-allow-origin',<<"*">>},
{exp,1612704210}]
63> Jwt = jwerl:sign( JwtPayload, es384, PrivPem ).
<<"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJhd3M6Y2hhbm5lbC1hcm4iOiJzYW1wbGUtYXdzLWFybiIsImF3czphY2Nlc3MtY29udHJvbC1hbGx"...>>
64> io:format( "~s~n", [ Jwt ] ).
eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJhd3M6Y2hhbm5lbC1hcm4iOiJzYW1wbGUtYXdzLWFybiIsImF3czphY2Nlc3MtY29udHJvbC1hbGxvdy1vcmlnaW4iOiIqIiwiZXhwIjoxNjEyNzA0MjEwfQ.MGQCMHYlgc0aMcZNVp2CTAWz09KkWuKuqCckyW3gyg7XCQBYgeCEzy8hFgAgIJ7cpYdOTAIwbaoOETtzZlLVR_z9V8kPk92VuHy01VO2COS61k7KqOQOTvNVTEELTIu-yrI3pvtk
ok
65> jwerl:verify( Jwt, es384, PubPem ).
{ok,#{'aws:access-control-allow-origin' => <<"*">>,
'aws:channel-arn' => <<"sample-aws-arn">>,
exp => 1612704210}}
Result on jwt.io
:
Result in Python:
Python 3.9.1 (default, Jan 8 2021, 17:17:17)
[Clang 12.0.0 (clang-1200.0.32.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import jwt
>>> from pathlib import Path
>>> pubkey = Path('new-pub.pem').read_text()
>>> pubkey
'-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFPg0VBtAhJriqhBCFuBeb5VZNVW9PRZQ\npxwsu29RJEfmRHWaJGxMz7SfDt6zD9t8pzwjl+jzm0l/adEGB4dMb1rPSu8rpGQx\nQ11pm4zJhrBmmNjEvUVCtKoAGpVC2H4V\n-----END PUBLIC KEY-----\n'
>>> jwt_str = 'eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJhd3M6Y2hhbm5lbC1hcm4iOiJzYW1wbGUtYXdzLWFybiIsImF3czphY2Nlc3MtY29udHJvbC1hbGxvdy1vcmlnaW4iOiIqIiwiZXhwIjoxNjEyNzA0MjEwfQ.MGQCMHYlgc0aMcZNVp2CTAWz09KkWuKuqCckyW3gyg7XCQBYgeCEzy8hFgAgIJ7cpYdOTAIwbaoOETtzZlLVR_z9V8kPk92VuHy01VO2COS61k7KqOQOTvNVTEELTIu-yrI3pvtk'
>>> jwt.decode( jwt_str, pubkey, algorithms=["ES384"] )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/igor/.local/share/virtualenvs/pog-Ir125d8X/lib/python3.9/site-packages/jwt/api_jwt.py", line 113, in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
File "/Users/igor/.local/share/virtualenvs/pog-Ir125d8X/lib/python3.9/site-packages/jwt/api_jwt.py", line 83, in decode_complete
decoded = api_jws.decode_complete(
File "/Users/igor/.local/share/virtualenvs/pog-Ir125d8X/lib/python3.9/site-packages/jwt/api_jws.py", line 149, in decode_complete
self._verify_signature(signing_input, header, signature, key, algorithms)
File "/Users/igor/.local/share/virtualenvs/pog-Ir125d8X/lib/python3.9/site-packages/jwt/api_jws.py", line 236, in _verify_signature
raise InvalidSignatureError("Signature verification failed")
jwt.exceptions.InvalidSignatureError: Signature verification failed
Result of generating the JWT in python:
>>> import time
>>> expires = 120 * 60
>>> claims = {"aws:channel-arn": "example arn", "aws:access-control-allow-origin": "*", "exp": int(time.time()) + expires}
>>> claims
{'aws:channel-arn': 'example arn', 'aws:access-control-allow-origin': '*', 'exp': 1612705034}
>>> privkey = Path('new-priv.pem').read_text()
>>> privkey
'-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDBGV5UXMaYlAudY9f56cojR5A+4Uae9wN/ah1/30Ei7Ah5Etwnmq0/V\n+DW9JRwD346gBwYFK4EEACKhZANiAAQU+DRUG0CEmuKqEEIW4F5vlVk1Vb09FlCn\nHCy7b1EkR+ZEdZokbEzPtJ8O3rMP23ynPCOX6PObSX9p0QYHh0xvWs9K7yukZDFD\nXWmbjMmGsGaY2MS9RUK0qgAalULYfhU=\n-----END EC PRIVATE KEY-----\n'
>>> jwt.encode( claims, privkey, algorithm="ES384" )
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJhd3M6Y2hhbm5lbC1hcm4iOiJleGFtcGxlIGFybiIsImF3czphY2Nlc3MtY29udHJvbC1hbGxvdy1vcmlnaW4iOiIqIiwiZXhwIjoxNjEyNzA1MDM0fQ.dtUmrIh94JSW5zLasXGWNCGyr744xfEYk5b1PWTYfjDXm29AOo3zKv6mojXqY9XZ0PcVCAdPBQEsHufuuqS4fAOcLQ9caYB-hyrDs3cc6xuIqzC0MqQax3cxQGAh-Qdj'
>>>
>>> jwt.decode( jwt.encode( claims, privkey, algorithm="ES384" ), pubkey, algorithms=["ES384"] )
{'aws:channel-arn': 'example arn', 'aws:access-control-allow-origin': '*', 'exp': 1612705034}
Result of jwerl:verify/3
on python-generated JWT:
69> PythonJwt = <<"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJhd3M6Y2hhbm5lbC1hcm4iOiJleGFtcGxlIGFybiIsImF3czphY2Nlc3MtY29udHJvbC1hbGxvdy1vcmlnaW4iOiIqIiwiZXhwIjoxNjEyNzA1MDM0fQ.dtUmrIh94JSW5zLasXGWNCGyr744xfEYk5b1PWTYfjDXm29AOo3zKv6mojXqY9XZ0PcVCAdPBQEsHufuuqS4fAOcLQ9caYB-hyrDs3cc6xuIqzC0MqQax3cxQGAh-Qdj">>.
<<"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJhd3M6Y2hhbm5lbC1hcm4iOiJleGFtcGxlIGFybiIsImF3czphY2Nlc3MtY29udHJvbC1hbGxvdy1"...>>
70> PrivPem.
<<"-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDBGV5UXMaYlAudY9f56cojR5A+4Uae9wN/ah1/30Ei7Ah5Etwnmq0/V\n+DW9JRwD346gBwYFK4EE"...>>
71> PubPem.
<<"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFPg0VBtAhJriqhBCFuBeb5VZNVW9PRZQ\npxwsu29RJEfmRHWaJGxMz7Sf"...>>
72> jwerl:verify(PythonJwt, es384, PubPem ).
{error,invalid_signature}
Result of trying to verify python JWT on jwt.io
: