ECCP256 signature for JWT can't be validated

116 views Asked by At

I'm trying to sign a JWT using the private key (ECCP256) embedded in my Yubikey5.

Note that the ECCP384 is not working either.

However, the signature verification is not working (i'm trying on jwt.io)

The signature is valid if i'm checking it using openssl (and i'm signing the right data, i.e base64url(header)+"."+base64url(data) :

$ openssl dgst -sha256 -verify pubkey.pem -signature data.sig data.jwt 
Verified OK

My script is :

echo -n '{"alg":"ES256","typ":"JWT"}' | base64  | tr -d "\n" | tr '+/' '-_' | tr -d '='  > token.jwt
printf  "." >> token.jwt


cat data.json | base64 | tr -d "\n" | tr '+/' '-_' | tr -d '='  >> token.jwt

yubico-piv-tool -a verify-pin --sign -s 9c -H SHA256 -A ECCP256 -i token.jwt -o data.sig

cp token.jwt data.jwt #this is just to check that i signed the right data
printf "." >> token.jwt
cat data.sig | base64 | tr -d '\n=' | tr -- '+/' '-_' >> token.jwt

And one output example :

Enter PIN: 
Successfully verified PIN.
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAiSm9obiBEb2UiLAogICJpYXQiOiAxNTE2MjM5MDIyCn0K.MEUCIGI9uVtz_r14rgJw19YdPR5sNbezKB3vOa3bApcFWJg2AiEAzf9ITwL-6fsapZt0pBzNYA5zaHBcmjP-HtCOA5uo870

Am I doing someting wrong ? Note that the exact same script using RSA 2048 keys is working (RS256).

Note : using openssl for key generation is not working either for jwt validation (despite the signature being validated by OpenSSL)

$ openssl ecparam -genkey -name prime256v1 -noout -out private.pem
$ openssl ec -in private.pem -pubout -out public.pem
$ echo -n '{"alg":"ES256","typ":"JWT"}' | base64  | tr -d "\n" | tr '+/' '-_' | tr -d '='  > token.jwt
$ echo -n "." >> token.jwt
$ cat ../data.json | base64 | tr -d "\n" | tr '+/' '-_' | tr -d '='  >> token.jwt
$ openssl dgst -sha256 -sign private.pem token.jwt > signature.bin
$ openssl dgst -sha256 -verify public.pem -signature signature.bin token.jwt
Verified OK
$ echo -n "." >> token.jwt
$ cat signature.bin | base64 | tr -d '\n=' | tr -- '+/' '-_' >> token.jwt
$ cat token.jwt 
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAiSm9obiBEb2UiLAogICJpYXQiOiAxNTE2MjM5MDIyCn0K.MEUCIHY5OUbyovQX4jXFQVqF0ZIpoIBBjhQ6KMwnjHpp66JMAiEApIK2YspuRTXkzquunG-385QMFSACOKeKuQDGZC2mZcM
1

There are 1 answers

1
joostd On BEST ANSWER

Both openssl and yubico-piv tools generate signatures that are wrapped in an ASN.1 SEQUENCE. For example, here is a ECDSA signature using the p256 curve:

$ openssl dgst -sha256 -sign key.pem datatosign | openssl asn1parse -inform der
    0:d=0  hl=2 l=  69 cons: SEQUENCE          
    2:d=1  hl=2 l=  33 prim: INTEGER           :8F023B4A83817C6214533892D4F89E7DE2B81B0174A2F28F927E33B997A90ECA
   37:d=1  hl=2 l=  32 prim: INTEGER           :239D8BD1EA84C97A96F62634CDA56FF4A9CADA6663F84773620C80C8A62E2A37

JWT however uses raw signatures. In above example this means it should just contains the (r,s) pair of integers. To fix this, you can just concatenate the binary integers into a single file (containing 64 bytes in this example).

Parsing the ASN.1 structure is a bit tricky, as the encoding differs for different values of the integers, but a simple hack would be to use openssl for this:

cat signature.der | openssl asn1parse -inform der | egrep -o '[A-F0-9]{64}' | xxd -r -p > signature.bin 

For a complete example of signing a JWT using a YubiKey, see this gist.