Skip to content

TransparentHealth/python-poetri

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

69 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

poetri - Pre OAuth Entity Trust (POET) Reference Implementation

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.

Command Line Tools

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.

Minting a New Keypair

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"
} 

Signing a JWT:

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.

Verifying a JWT

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.

Python Library

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.

Releases

No releases published

Packages

No packages published

Languages

  • Python 100.0%