Generate a JWT with ECDSA keys

To generate a JWT signed with the ES256 algorithm and ECDSA keys using the P-256 (secp256k1) curve, you need to use openssl commands or the auth0 library.

This procedure explains how to generate a JWT with openssl commands.

A JWT consists of three parts separated by dots.Take a look at this pseudo code showing how a JWT is constructed:
Y = Base64URLEncode(header) + ‘.’ + Base64URLEncode(payload)
JWT token = Y + ‘.’ + Base64URLEncode(ECDSASHA256(Y))

Header

The header of a token signed with ES256:
{
"alg": "ES256",
"typ": "JWT"
}
To encode the header, you can use the following command:
echo -n '{"alg":"ES256","typ":"JWT"}' | base64 | sed s/\+/-/ | sed -E s/=+$//
The result is the encoded header:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9

Payload

The payload of a token signed with ES256 is similar to this one:

{
"sub": "ES256InOTA",
"name": "John Doe"
}
To encode the payload, use a command similar to this one:
echo -n '{"sub":"ES256inOTA","name":"John Doe"}' | base64 | sed s/\+/-/ | sed -E s/=+$//
The result is the encoded payload:
eyJzdWIiOiJFUzI1NmluT1RBIiwibmFtZSI6IkpvaG4gRG9lIn0

Signature

To create a signature with the an ECDSA P-256 (secp256k1) private key, you can use a command similar to this one:
echo -n "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJFUzI1NmluT1RBIiwibmFtZSI6IkpvaG4gRG9lIn0" | openssl dgst -sha256 -binary -sign ec-secp256k1-priv-key.pem | openssl enc -base64 | tr -d '\n=' | tr -- '+/' '-_'
where ec-secp256k1-priv-key.pem is the ECDSA private key that you created previously. See Generate ECDSA keys with the P-256 (secp256k1) curve.
The result is the encoded signature:
MEQCICRphRrc0GWowZgJAy0gL6At628Kw8YPE22iD-aKIi4PAiA0JWU-qFNL8I0tP0ws3Bbmg0FfVMn4_yk2lGGquAGOXA

A complete JWT

After putting together your encoded header, payload, and signature, your token should look similar to this one:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJFUzI1NmluT1RBIiwibmFtZSI6IkpvaG4gRG9lIn0.MEQCICRphRrc0GWowZgJAy0gL6At628Kw8YPE22iD-aKIi4PAiA0JWU-qFNL8I0tP0ws3Bbmg0FfVMn4_yk2lGGquAGOXA

A request with a JWT

Having the JWT ready, you can make a request.

The request:
curl -ki -vvv https://demo1.ota-updates.com/example.zip -H "X-Akamai-OTA-Uid: es256k1" 
-H "X-Akamai-JWT-Token: eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtdGsxIiwibmFtZSI6ImVzMjU2azEifQo.MEUCIC4QDKMEUPL294Cy6RBN2pgDBlAq5WwD8us_47TC_nkCAiEArbtg-eiPp_USg3yl9j7iiMH23OK3sgBRgQ9Vc1aosVY"
The response:
* Added demo1.ota-updates.com:443:198.18.91.88 to DNS cache
* Hostname demo1.ota-updates.com was found in DNS cache
* Trying 198.18.91.88...
* TCP_NODELAY set
* Connected to demo1.ota-updates.com (198.18.91.88) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: CN=demo.ota-updates.com
* start date: Apr 14 07:00:51 2020 GMT
* expire date: Jul 13 07:00:51 2020 GMT
* issuer: CN=h2ppy h2cker fake CA
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> GET /example.zip HTTP/1.1
> Host: demo1.ota-updates.com
> User-Agent: curl/7.64.1
> Accept: */*
> X-Akamai-OTA-Uid: es256k1
> X-Akamai-JWT-Token: eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtdGsxIiwibmFtZSI6ImVzMjU2azEifQo.MEUCIC4QDKMEUPL294Cy6RBN2pgDBlAq5WwD8us_47TC_nkCAiEArbtg-eiPp_USg3yl9j7iiMH23OK3sgBRgQ9Vc1aosVY
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Accept-Ranges: bytes
Accept-Ranges: bytes
< Content-Type: application/zip
Content-Type: application/zip
< ETag: "fc1b24f7ac7f087a7e8072fc27df7f09:1496993002"
ETag: "fc1b24f7ac7f087a7e8072fc27df7f09:1496993002"
< Last-Modified: Fri, 09 Jun 2017 07:23:22 GMT
Last-Modified: Fri, 09 Jun 2017 07:23:22 GMT
< Server: AkamaiNetStorage
Server: AkamaiNetStorage
< Content-Length: 16588461
Content-Length: 16588461
< Date: Tue, 21 Apr 2020 11:58:56 GMT
Date: Tue, 21 Apr 2020 11:58:56 GMT
< Connection: keep-alive
Connection: keep-alive
< AK_EIP_FORWARDER_IP:
AK_EIP_FORWARDER_IP: