JSON Web Token (JWT) is an open, industry standard (RFC 7519) method for representing claims securely between two parties.
Pre-OAuth Entity Trust (POET) is a specific JWT designed to facilitate claims about consumer-facing health applications. https://github.com/transparenthealth/poet
This is a reference implementation for signing and verifying POET JWTs in Python2 and Python3.
POET uses the eliptic curve algorithm secp256k1
.
It contains both libraries and command line utilities.
To install on Ubuntu/Linux first install the prerequisites:
$ sudo apt-get install python-dev
The above Ubuntu Linux instructions will differ on Mac, Redhat, Windows, etc. A C compier and Python development files are required for the next step to run without error. A quick Google search about your platform is better than any instructions that can be offered here.
To install poetri, on the command line type:
$ sudo pip install python-poetri
You may be asked for a password.
With these tools you can see operate as an endorsing body. Primarily thios means you can sign applications' software statements for access to particular data sources (that you own). There are a lot of ways to generate keys and work with JWKs and JWTs. You could create your own or use other libraries to accomplish the same thing. These methods provided here are for convenience.
generate_jwk_private.py
mints a new private keypair in JWK format. This file is used to sign JWTs (a.k.a. JWSs).
The only positional command line argument is the key id kid
. If kid
is ommitted, the thumbprint ius used.
Its output is to standard out stdout
.
$ generate_jwk_private.py example.com > private.example.com.jwk
{
"crv": "secp256k1",
"d": "GJNyqolZdTN28PzKx118NonXUD16aiDYihA6lhjaxts",
"kty": "EC",
"x": "DTUGGdtDPsn0Pr7EFfHdSgbd0mRLxNOo8CqksqWVskg",
"y": "j5xnzYrdE12sYsPMhQzygSZR2Ud_ZiHNxhY4xEfIQQo",
"kid": "example.com"
}
Keep your private key safe!!!
generate_jwk_public.py
creates a public version of the private key just created.
Here is an example
$ generate_jwk_prublic.py private.example.com.jwk > public.example.com.jwk
{
"crv": "secp256k1",
"kty": "EC",
"x": "DTUGGdtDPsn0Pr7EFfHdSgbd0mRLxNOo8CqksqWVskg",
"y": "j5xnzYrdE12sYsPMhQzygSZR2Ud_ZiHNxhY4xEfIQQo",
"kid": "example.com"
}
You'll need to add necessary information and claims into a JSON document that represents your desired payload in your JWT.
You will also need to supply the private key for signing. This utility sets the iat
(issued at), exp
(expiration) and iss
(issuer) based on the system clock and your input. The required command line arguments are payload
, keypair
, issuer
. expiration
is optional and defaults to 31536000 (one year from now.)
$ sign_jwk.py ../tests/payload.json private.example.com.jwk example.com 315360
eyJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUuY29tIiwidHlwIjoiSldUIn0.eyJ0b2tlbl9lbmRwb2ludF9hdXRoX21ldGhvZCI6ImNsaWVudF9zZWNyZXRfYmFzaWMiLCJpc3MiOiJleGFtcGxlLmNvbSIsImNsaWVudF91cmkiOiJodHRwczovL2FwcHMtZHN0dTIuc21hcnRoZWFsdGhpdC5vcmcvY2FyZGlhYy1yaXNrLyIsImluaXRpYXRlX2xvZ2luX3VyaSI6Imh0dHBzOi8vYXBwcy1kc3R1Mi5zbWFydGhlYWx0aGl0Lm9yZy9jYXJkaWFjLXJpc2svbGF1bmNoLmh0bWwiLCJzb2Z0d2FyZV9pZCI6IjROUkIxLTBYWkFCWkk5RTYtNVNNM1IiLCJyZWRpcmVjdF91cmlzIjpbImh0dHBzOi8vYXBwcy1kc3R1Mi5zbWFydGhlYWx0aGl0Lm9yZy9jYXJkaWFjLXJpc2svIl0sImdyYW50X3R5cGVzIjpbImF1dGhvcml6YXRpb25fY29kZSJdLCJjbGllbnRfbmFtZSI6IkNhcmRpYWMgUmlzayBBcHAiLCJleHAiOjE1NjM2NTY3NTcsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgcGF0aWVudC8qLnJlYWQiLCJpYXQiOjE1MDA1ODQ3NTcsImxvZ29fdXJpIjoiaHR0cHM6Ly9nYWxsZXJ5LnNtYXJ0aGVhbHRoaXQub3JnL2ltZy9hcHBzLzY2LnBuZyJ9.GVRUGPLo_jpq3_yBBJmNmxNb6Wk9ZLhGxjRcynP7VAgQ_QlECLlR8fQWDhtwU0e33Ev0MlGKF1OB-xh5Kc3JBkX2qrtUotA1H-MsNmxhhmL1fXg4fV1R2wa6hcqcSjM1zP1iNhzLIPbbsXtq27qDivN6D5pJzrkFIOdvg1lgvmeZxttYndqpn3SUEsdwBLxi66-OWyiPeFdigAfJAtf8EyHk0picgkJZrjK0Zoa3H-Wvwa88fWYGTeFxBpjET7G2nGXdcRNKVgQ-0SDJoasJSM5uoqbJAO7A1h0zpNdFpRY9pjtem4FnN_6LLpZp8b0J0PFXaXqOAxeyU7UFTNiqOw
...redirect it to a file...
$ sign_jwk.py ../tests/payload.json private.example.com.jwk example.com 315360 > 4NRB1-0XZABZI9E6-5SM3R.jws
Now 4NRB1-0XZABZI9E6-5SM3R.jws
hold the endorsement that you can distribute.
verify_jws_with_jwk.py
verifies the signature on a JWS using the public key.
It has two positional arguments; the jws
and the public jwk
and if the JWS
signature is verified, then it outputs the JWK's payload to standard out stdout
.
$ verify_jws_with_jwk.py ./4NRB1-0XZABZI9E6-5SM3R.jws public.example.com.jwk
{
"scope": "openid profile patient/*.read",
"initiate_login_uri": "https://apps-dstu2.smarthealthit.org/cardiac-risk/launch.html",
"exp": 1563657181,
"iss": "example.com",
"software_id": "4NRB1-0XZABZI9E6-5SM3R",
"token_endpoint_auth_method": "client_secret_basic",
"client_name": "Cardiac Risk App",
"logo_uri": "https://gallery.smarthealthit.org/img/apps/66.png",
"client_uri": "https://apps-dstu2.smarthealthit.org/cardiac-risk/",
"redirect_uris": [
"https://apps-dstu2.smarthealthit.org/cardiac-risk/"
],
"iat": 1500585181,
"grant_types": [
"authorization_code"
]
}
If the key doesn't match, then you'll get an error instead of the payload.
Hey where did you get that public key used in the last step? How do you know it's really from example.com
?
You don't unless you fetch it from example.com
to prove its provenance. Let's give that a shot.
verify_jws_with_jwk_url.py
attempts to get the public key from the well-known URL in the POET specification.
It has one required, positional argument, the jws
and outputs the payload to stdout if the key is found and the signature matches.
An error is returned if not. The well-known URL is derived from the iss
field in the payload and the details of the POET specification.
$ verify_jws_with_jwk_url.py 4NRB1-0XZABZI9E6-5SM3R.jws
The key could not be fetched
That's exactly what we would expect to happen. For this command to run as expected, I would need to place and make public the file poet.jwk
at
https://example.com/.well-known/poet.jwk. Since I do not have control over that domain or its website, I cannot impersonate example.com's signature.
All of the above functionality is accessible directly in Python as a module. More documentation is on the way. For now, the best source of information and examples can be found in the tests.