Issue
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
Solution
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.
Answered By - joostd Answer Checked By - Terry (WPSolving Volunteer)