Skip to content

Instantly share code, notes, and snippets.

@joernheissler
Last active March 4, 2018 21:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joernheissler/04d9dcfb3a99e318871e451c9043f2dc to your computer and use it in GitHub Desktop.
Save joernheissler/04d9dcfb3a99e318871e451c9043f2dc to your computer and use it in GitHub Desktop.
Test program to dump + verify JWS
#!/usr/bin/env python3
data0 = """
{
"payload": "eyJjb250YWN0IjpbIm1haWx0bzphZG1pbkBleGFtcGxlLm5ldCJdLCJ0
ZXJtc09mU2VydmljZUFncmVlZCI6dHJ1ZX0",
"protected": "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0eS
I6IkVDIiwieCI6ImY0T2tsNnZhZEFiS1pDN2NWd0tNc1RCd1Z0dDRm
YlFDMWI1emJ3azFTUmciLCJ5IjoiblVtV0pmQVZpOG0wVy1rdEhnM3
VZS3pJWS1vUFdRcFB4bHhCVTRVLURLbyJ9LCJub25jZSI6IkFtTmpu
cUp2Vlc3Y3dEalJ5SlJXQldrYkZ2X1lneWQwU1o4bENOYXkzdTgiLC
J1cmwiOiJodHRwczovL2V4YW1wbGUuY29tL2FjbWUvbmV3LWFjY291
bnQifQ",
"signature": "RzIgePMgHF74on3-8-pzxliZRKl-a7ba_fCKqUS53RghGtj5hnhauJ
OgPzY1BxPDPTKiEMVrjKXJroPuZWYybg"
}
"""
data1 = """
{
"payload": "eyJwYXlsb2FkIjoiZXlKaFkyTnZkVzUwSWpvaWFIUjBjSE02THk5bGVH
RnRjR3hsTG1OdmJTOWhZMjFsTDJGalkzUXZNVEl6TkRVaUxDSnVaWGRM
WlhraU9uc2lZM0oySWpvaVVDMHlOVFlpTENKcmRIa2lPaUpGUXlJc0lu
Z2lPaUpmVVcxTmMxZG1RVVpCVGpBMWEwVnZYekI2WTNJM1IzWnpaMG94
VkVkSU1uVlJaR1IwV1RSS2NYTkZJaXdpZVNJNklrSk5Na0ZNU2tGTmFV
aHBkM0pWVUVOMWNUbHZUbFpNZFhCclowNUVSR0pLUTJaUVIzRXlVbDlW
YVVraWZYMCIsInByb3RlY3RlZCI6ImV5SmhiR2NpT2lKRlV6STFOaUlz
SW1wM2F5STZleUpqY25ZaU9pSlFMVEkxTmlJc0ltdDBlU0k2SWtWRElp
d2llQ0k2SWw5UmJVMXpWMlpCUmtGT01EVnJSVzlmTUhwamNqZEhkbk5u
U2pGVVIwZ3lkVkZrWkhSWk5FcHhjMFVpTENKNUlqb2lRazB5UVV4S1FV
MXBTR2wzY2xWUVEzVnhPVzlPVmt4MWNHdG5Ua1JFWWtwRFpsQkhjVEpT
WDFWcFNTSjlMQ0oxY213aU9pSm9kSFJ3Y3pvdkwyVjRZVzF3YkdVdVky
OXRMMkZqYldVdmEyVjVMV05vWVc1blpTSjkiLCJzaWduYXR1cmUiOiJn
LThkVUtHM1dRVTh5bWJZaXJWellkZTVPUEVyT3o1NkZmRWJlS2MyaDVV
d2R5VzJMeEd2VXlzcnRtd1pId1JyZTBRbFhteVlwMlk5SWN2R3FrREta
ZyJ9",
"protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5jb2
0vYWNtZS9hY2N0LzEyMzQ1Iiwibm9uY2UiOiJKUFF0by1RX1RNVENs
bmhHdjBERXA0ajk1b2RITGp6Qkp6LS1nR0lnZHBJIiwidXJsIjoiaH
R0cHM6Ly9leGFtcGxlLmNvbS9hY21lL2tleS1jaGFuZ2UifQ",
"signature": "C7ZkQahUwcw1orQh5RPNtWVQmytnnYTnH4VgGxt2Yq807lSealPnqR
jTBxvpfo_ceuaFj0G1fp-g_o4ZmjkFBg"
}
"""
import re
import json
import base64
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicNumbers, SECP256R1, ECDSA
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
def unwrap(data):
return json.loads(re.sub(r'\s+(?=[-_a-zA-Z0-9])', '', data))
def b64dec(data):
return base64.urlsafe_b64decode((data + '=' * (-len(data) % 4)))
def bytes_to_int(b):
return int.from_bytes(b, 'big')
def b64_to_int(data):
return bytes_to_int(b64dec(data))
def dump_jws(data, pub=None):
protected = json.loads(b64dec(data['protected']))
print("Protected:")
print(json.dumps(protected, indent=4, sort_keys=True))
print()
if protected['alg'] != 'ES256':
raise ValueError('Not supported')
if not pub:
if protected['jwk']['crv'] != 'P-256':
raise ValueError('Not supported')
if protected['jwk']['kty'] != 'EC':
raise ValueError('Not supported')
pub = EllipticCurvePublicNumbers(
b64_to_int(protected['jwk']['x']), b64_to_int(protected['jwk']['y']), SECP256R1()
).public_key(default_backend())
sig = b64dec(data['signature'])
if len(sig) != 64:
raise ValueError('Bad length')
sig = encode_dss_signature(bytes_to_int(sig[:32]), bytes_to_int(sig[32:]))
msg = (data['protected'] + '.' + data['payload']).encode()
pub.verify(sig, msg, ECDSA(hashes.SHA256()))
payload = json.loads(b64dec(data['payload']))
print("Payload:")
print(json.dumps(payload, indent=4, sort_keys=True))
print()
return pub, payload
pub0, create_acc = dump_jws(unwrap(data0))
print("-----\n")
pub0, key_change_outer = dump_jws(unwrap(data1), pub0)
print("-----\n")
pub1, key_change_inner = dump_jws(key_change_outer)
Protected:
{
"alg": "ES256",
"jwk": {
"crv": "P-256",
"kty": "EC",
"x": "f4Okl6vadAbKZC7cVwKMsTBwVtt4fbQC1b5zbwk1SRg",
"y": "nUmWJfAVi8m0W-ktHg3uYKzIY-oPWQpPxlxBU4U-DKo"
},
"nonce": "AmNjnqJvVW7cwDjRyJRWBWkbFv_Ygyd0SZ8lCNay3u8",
"url": "https://example.com/acme/new-account"
}
Payload:
{
"contact": [
"mailto:admin@example.net"
],
"termsOfServiceAgreed": true
}
-----
Protected:
{
"alg": "ES256",
"kid": "https://example.com/acme/acct/12345",
"nonce": "JPQto-Q_TMTClnhGv0DEp4j95odHLjzBJz--gGIgdpI",
"url": "https://example.com/acme/key-change"
}
Payload:
{
"payload": "eyJhY2NvdW50IjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9hY21lL2FjY3QvMTIzNDUiLCJuZXdLZXkiOnsiY3J2IjoiUC0yNTYiLCJrdHkiOiJFQyIsIngiOiJfUW1Nc1dmQUZBTjA1a0VvXzB6Y3I3R3ZzZ0oxVEdIMnVRZGR0WTRKcXNFIiwieSI6IkJNMkFMSkFNaUhpd3JVUEN1cTlvTlZMdXBrZ05ERGJKQ2ZQR3EyUl9VaUkifX0",
"protected": "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6Il9RbU1zV2ZBRkFOMDVrRW9fMHpjcjdHdnNnSjFUR0gydVFkZHRZNEpxc0UiLCJ5IjoiQk0yQUxKQU1pSGl3clVQQ3VxOW9OVkx1cGtnTkREYkpDZlBHcTJSX1VpSSJ9LCJ1cmwiOiJodHRwczovL2V4YW1wbGUuY29tL2FjbWUva2V5LWNoYW5nZSJ9",
"signature": "g-8dUKG3WQU8ymbYirVzYde5OPErOz56FfEbeKc2h5UwdyW2LxGvUysrtmwZHwRre0QlXmyYp2Y9IcvGqkDKZg"
}
-----
Protected:
{
"alg": "ES256",
"jwk": {
"crv": "P-256",
"kty": "EC",
"x": "_QmMsWfAFAN05kEo_0zcr7GvsgJ1TGH2uQddtY4JqsE",
"y": "BM2ALJAMiHiwrUPCuq9oNVLupkgNDDbJCfPGq2R_UiI"
},
"url": "https://example.com/acme/key-change"
}
Payload:
{
"account": "https://example.com/acme/acct/12345",
"newKey": {
"crv": "P-256",
"kty": "EC",
"x": "_QmMsWfAFAN05kEo_0zcr7GvsgJ1TGH2uQddtY4JqsE",
"y": "BM2ALJAMiHiwrUPCuq9oNVLupkgNDDbJCfPGq2R_UiI"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment