Covid-QR-Decoder/covid-qr-decode.py

118 lines
3.8 KiB
Python
Executable File

#!/usr/bin/python3
import argparse
import json
import os
import re
import sys
import zlib
from jose import jwk
from jose.utils import base64url_decode
from PIL import Image
from pyzbar.pyzbar import decode
from rich import print_json
from rich.console import Console
parser = argparse.ArgumentParser()
parser.add_argument('data', help='The path to the QR code image file.')
parser.add_argument('--raw', help='Decode raw QR code data instead of an image file.', action='store_true')
parser.add_argument('--validate', help='Validate the QR code signature against a public key. Must be the path to a public key.', nargs='?', default=False)
# Print help if no input
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)
args = parser.parse_args()
QRImageFile = args.data
# Load data
if (not args.raw):
if (os.path.exists(QRImageFile)):
decodedQR = decode(Image.open(QRImageFile))
# Check that the QR code was decoded
if (len(decodedQR) == 0):
print(f'\033[91mError:\033[00m could not read the QR code.')
sys.exit(1)
QRData = decodedQR[0].data.decode("utf-8") # Get first element (this library supports multiple QR codes in a file)
else:
print(f'\033[91mError:\033[00m could not find QR code file: {QRImageFile}')
sys.exit(1)
if (args.raw):
QRData = QRImageFile
# Rebuild JWS token
QRNumericData = re.sub("[^0-9]", "", QRData) # Only keep numeric values
QRNumericPairs = re.findall("..", QRNumericData) # Split into groups of 2 numeric characters each of which represent a single JWS char
JWSToken = ""
for n in QRNumericPairs:
JWSToken += chr(int(n) + 45) # Recreate the JWS string
# Extract JWS Content.
try:
header, payload, signature = JWSToken.rsplit('.')
except ValueError:
print(f'\033[91mError:\033[00m only found {len(JWSToken.rsplit("."))} out of 3 parts of the JWS token.')
if (not args.raw):
print('The QR code couldn\'t be decoded.')
elif (args.raw):
print('The raw data couldn\'t be decoded. Make sure your provided string starts with \'shc:/\'.')
sys.exit(1)
# Print
console = Console()
if (not args.raw):
console.rule("[bold bright_red]RAW QR CODE DATA")
print(QRData + " \n")
elif (args.raw):
print('\n') # the raw qr code data can be confusing so lets put a line between that and the command above
console.rule("[bold bright_red]JWS TOKEN")
print(JWSToken + "\n")
console.rule("[bold bright_red]JWS HEADER")
decHeader = base64url_decode(header.encode('utf-8'))
print_json(decHeader.decode('utf-8'))
print('\n')
console.rule("[bold bright_red]JWS SIGNATURE (base64)")
print(signature + "\n")
console.rule("[bold bright_red]JWS PAYLOAD")
decPayload = base64url_decode(payload.encode('utf-8'))
uncompressedPayload = zlib.decompress(decPayload, -15).decode("utf-8")
print_json(uncompressedPayload)
# Verify the JWS token signature
if (args.validate != False):
print('\n')
console.rule("[bold bright_red]Validate")
if (args.validate == None or len(args.validate) == 0):
print(f'\033[91mError:\033[00m must provide the path to the public key file (--validate [path to public key]) to validate the QR code signature against.')
else:
if (os.path.exists(args.validate)):
pass
else:
print(f'\033[91mError:\033[00m could not find public key file: {args.validate}')
print('Not implemented.')
# hmac_key = {
# "kid": "### INSERT KID FROM QR PAYLOAD HERE ###",
# "alg": "ES256",
# "kty": "EC",
# "crv": "P-256",
# "use": "sig",
# "x": "### INSERT X MATCHING KID HERE ###",
# "y": "### INSERT Y MATCHING KID HERE ###"
# }
# key = jwk.construct(hmac_key)
#
# signedMessage, encodedSignature = JWSToken.rsplit('.', 1)
# decoded_sig = base64url_decode(encodedSignature.encode('utf-8'))
# key.verify(signedMessage, decoded_sig)