-
-
Save ixe013/f3a7ca48e327a7652554f29be3ee7d46 to your computer and use it in GitHub Desktop.
from __future__ import print_function | |
import base64 | |
import json | |
import os.path | |
import pprint | |
import sys | |
import time | |
import zlib | |
def pad_base64(data): | |
"""Makes sure base64 data is padded | |
""" | |
missing_padding = len(data) % 4 | |
if missing_padding != 0: | |
data += '='* (4 - missing_padding) | |
return data | |
def decompress_partial(data): | |
"""Decompress arbitrary deflated data. Works even if header and footer is missing | |
""" | |
decompressor = zlib.decompressobj() | |
return decompressor.decompress(data) | |
def decompress(JWT): | |
"""Split a JWT to its constituent parts. | |
Decodes base64, decompress if required. Returns but does not validate the signature. | |
""" | |
header, jwt, signature = JWT.split('.') | |
printable_header = base64.urlsafe_b64decode(pad_base64(header)).decode('utf-8') | |
if json.loads(printable_header).get("zip", "").upper() == "DEF": | |
printable_jwt = decompress_partial(base64.urlsafe_b64decode(pad_base64(jwt))) | |
else: | |
printable_jwt = base64.urlsafe_b64decode(pad_base64(jwt)).decode('utf-8') | |
printable_signature = base64.urlsafe_b64decode(pad_base64(signature)) | |
return json.loads(printable_header), json.loads(printable_jwt), printable_signature | |
def showJWT(JWT): | |
header, jwt, signature = decompress(JWT) | |
print("Header: ", end="") | |
pprint.pprint(header) | |
print("Token: ", end="") | |
pprint.pprint(jwt) | |
print("Issued at: {} (localtime)".format(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(jwt['iat'])) if 'iat' in jwt else 'Undefined')) | |
print("Not before: {} (localtime)".format(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(jwt['nbf'])) if 'nbf' in jwt else 'Undefined')) | |
print("Expiration: {} (localtime)".format(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(jwt['exp'])) if 'exp' in jwt else 'Undefined')) | |
if __name__ == "__main__": | |
if len(sys.argv) > 1: | |
jwt = sys.argv[1] | |
if os.path.exists(jwt): | |
with open(sys.argv[1], "r") as input_file: | |
jwt = input_file.read().strip() | |
showJWT(jwt) |
Can you paste the failing JWT? No risk, it should be expired by now...
This particular one was a SMART Health card so its not expired and has private data in it. I'm still working it to validate the signature with a ES256 encryption
I get the same error "decompressing DEFLATED jwt: incorrect header check". I used the JWT from this other notebook (https://colab.research.google.com/github/dvci/health-cards-walkthrough/blob/main/SMART%20Health%20Cards.ipynb).
JTW: eyJ6aXAiOiJERUYiLCJhbGciOiJFUzI1NiIsImtpZCI6Ik9CenRCR1JleFYwbWU0eWNQVEJwLWxBTVdRbVUxX09ZMXE4bTRhd1dfMzQifQ.3VJNj9MwEP0rq-HaJnEKtM0JWiQ-DgiJXS6oB9eZNkaOHdlO2rLKf2fGbReQdvfECd_GfvPmvee5Bx0CVNDE2IUqz0MrfWxQmthkSvo65HiUbWcw5ATs0cME7HYHlXhdFstlOVvMslIsJjAoqO7hjXI24jFC9f2B8nA4ZIdZ5vw-LwuxyJXHGm3U0oR8ELCZQDx1yB3f0OudlluD6wcMzXtS24tzMeXiWZxu297qnzJqZ58FKjfoWixZ1G-ZX_vtD1SR_e0a7UllYJ4KXmZFJoiPb1e9rQ0yxmNwvVd4m1zB5eHqEpQzhtjOSmiAP5F1Yu6NufOGANf-qiDAtXiE-AvZoX7-ENnimUS22hAfvLWE8SHN2OsBLcf7yTVcrzLYjGRwq8n8OxmZSyxfiWkhpmUB4zh5VI14Xs3HvyMOUcY-JLu8PRH5gwaplLa4dnViUK7Wdp-Eh1OI2F72kH6mMfO0MJxsHnSdq-FIBCp1QlnMYdyME-guESQ5O_RoWdufCRLIKdX79MRmb3V7piiT4YJtUVQ751tabtYiVXSeKWsdOiNTnKv1zXu06KW5-eBCpyMt5sghGhc_9-2WW6FIRzyZYPlfJlgu_3WCc34Y6fwC.RH5TVWB-aYrPnbtb2LXU9gpC1WRra0gQHjZxSE_htNScq8NdIdgoUt5C1kvdiXbYqD79W87si9x66fFCwmCmgw
After re-reading hotcobra's comment, I changed line 23 to the following and it started working:
decompressor = zlib.decompressobj(wbits=-15)
In case someone is wondering how to use this script with "shc:/12496934958......238484329" string that the Vaccine QR Code gives, the following code can be used:
qrcode="shc:/12496934958......238484329"
raw="12496934958......238484329"
jwt = ""
for (char1, char2) in zip(raw[0::2], raw[1::2]):
jwt = jwt + chr(int(char1+char2)+45)
# jwt = "eyJ6aXAiOi...."
Another thing worth mentioning, if the decoded json payload contains French characters (e.g. SiteName... "VACCINATION À L'AUTO..."), then pprint library will throw an error, and if you print json payload without using pprint library, you will see the french character in unicode (e.g. VACCINATION \u00c0 L'AUTO).
I'm no Python expert but shouldn't the final JWS be numerically encoded? (Smarthealthcard: "the JWS string SHALL be encoded as Numerical Mode QR codes consisting of the digits 0-9") Or was this what MohRaza is referring to?
JWT Tokens are three base64 strings concatenated with a "." as described by the official spec https://jwt.io/.
SmartHealthCard (SHC) is numerically encoded JWT prepended with "shc:/". A SmartHealthCard QR Code is the "shc:/123456....123456" string encoded as a QR Code.
And JWT is numerically encoded by taking the ASCII value of the JWT token's base64 characters, and subtracting 45 from it. Therefore when converting shc:/123456...123432 to JWT, you take two digits at a time, add 45 to the digits, and use ASCII mapping to get the corresponding base64 character.
thanks, this is very useful!
Great script. However, I'm getting an error in line 24.zlib.error: Error -3 while decompressing DEFLATED jwt: incorrect header check. I php'd it and verified good jwt. The header does have zip::DEF so its DEFLATED. In php I used gzinflate(base64url_decode($jwt]) to get the ascii jwt.
Is there an issue with zlib.decompressobj? Or maybe I need more parameters?
Cripes. I'm so dumb. Its exactly what the error said incorrect header. Just had to add a -15 to the decompressobj so it ignores the header