Skip to content

Instantly share code, notes, and snippets.

@LucaFilipozzi
Last active October 18, 2021 19:25
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 LucaFilipozzi/361051800f3f843f7af4148eeb4769ed to your computer and use it in GitHub Desktop.
Save LucaFilipozzi/361051800f3f843f7af4148eeb4769ed to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
from base64 import urlsafe_b64decode as b64decode
from box import Box
from jwcrypto.jwk import JWKSet
from jwcrypto.jws import JWS
from orjson import dumps, loads, OPT_INDENT_2
from PIL import Image
from pyxtension.streams import stream
from pyzbar.pyzbar import decode as qrdecode
from re import findall
from requests import get
from zlib import decompress, MAX_WBITS
# extract SHC from image
# nb: BC's Vaccine Card fits in a single chunk, so no need to process multiple QR codes to combine chunks
shc = qrdecode(Image.open('qrcode.png'))[0].data.decode('utf-8')
# convert SHC to JWS parts (header, claims, signature), adding padding if necessary
# nb: SHC spec indicates that JWT is compressed
parts = [y + '=' * (4 - len(y) % 4) for y in ''.join([chr(int(x) + 45) for x in findall(r'\d\d', shc)]).split('.')]
raw_jws = '.'.join(parts)
header = Box(loads(b64decode(parts[0])))
claims = Box(loads(decompress(b64decode(parts[1]), -MAX_WBITS))) # claims unverified
# retrieve JWK from issuer
jwks = JWKSet.from_json(get(f"{claims.iss}/.well-known/jwks.json").text)
jwk = jwks.get_key(header.kid)
# verify JWS using JWK
jws = JWS()
jws.deserialize(raw_jws)
jws.verify(jwk)
if not(jws.is_valid):
print("invalid QR code")
exit(1)
# claims now verified; use already-decompressed claims rather than still-compressed jws.payload
#print(dumps(claims, option=OPT_INDENT_2).decode('utf-8'))
#exit(1)
# extract display_name from claims
names = stream(claims.vc.credentialSubject.fhirBundle.entry) \
.filter(lambda entry: entry.resource.resourceType == 'Patient') \
.map(lambda entry: entry.resource.name) \
.next()
display_name = " ".join([" ".join(names[0].given), names[0].family])
# extract immunizations from claims
immunizations = stream(claims.vc.credentialSubject.fhirBundle.entry) \
.filter(lambda entry: entry.resource.resourceType == 'Immunization' and entry.resource.status == 'completed') \
.map(lambda entry: entry.resource.vaccineCode) \
.flatMap(lambda vaccineCode: vaccineCode.coding) \
.filter(lambda coding: coding.system == 'http://hl7.org/fhir/sid/cvx') \
.map(lambda coding: coding.code) \
.toList()
one_dose_vaccines = {
'212' # Janssen
}
one_dose_immunizations = [x for x in immunizations if x in one_dose_vaccines]
two_dose_vaccines = {
'207', # Moderna
'208', # Pfizer
'210' # Astra-Zeneca
}
two_dose_immunizations = [x for x in immunizations if x in two_dose_vaccines]
# determine vaccination status
if len(one_dose_immunizations) >= 1 or len(two_dose_immunizations) >= 2:
print(f"{display_name} is fully vaccinated")
elif len(two_dose_immunizations) == 1:
print(f"{display_name} is partially vaccinated")
else:
print(f"{display_name} is not vaccinated")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment