initial commit
[greenpass.git] / greenpass.py
CommitLineData
eee81d79
TG
1#!/usr/bin/python3
2
3# Copyright 2021 Tobias Girstmair (https://gir.st). Consider this code GPLv3
4# licensed.
5
6# pip3 install flynn base45 PyPDF2 pyzbar
7# dnf install zbar || apt install libzbar0
8
9import sys
10import glob
11import json
12import zlib
13import flynn
14import base45
15import PyPDF2
16from PIL import Image
17from pyzbar import pyzbar
18from datetime import datetime
19from urllib.request import urlopen
20
21sch = urlopen('https://raw.githubusercontent.com/ehn-dcc-development/ehn-dcc-schema/release/1.3.0/DCC.combined-schema.json')
22
23if len(sys.argv) < 2:
24 try:
25 infile = glob.glob("COVID-19-*-*-*.pdf")[0]
26 print(f"Warning: using file {found}, since not specified\n", file=sys.stderr)
27 except:
28 print(f"Usage: {sys.argv[0]} COVID-19-*-*-*.pdf", file=sys.stderr)
29 print(f"Usage: {sys.argv[0]} QR_CODE.png", file=sys.stderr)
30 sys.exit(1)
31else:
32 infile = sys.argv[1]
33
34if open(infile, "rb").read(4) == b"%PDF":
35 # extract QR code from PDF using hard-coded index, size and bit depth.
36 # This will only work with the official Austrian green pass PDFs.
37 pdf=PyPDF2.PdfFileReader(open(infile, "rb"))
38 qr_img = pdf.getPage(0)['/Resources']['/XObject']['/Im3']
39 qr_pil = Image.frombytes("1", (400,400), qr_img.getData())
40else: # assume image
41 qr_pil = Image.open(infile)
42
43# decode QR code into raw bytes:
44qr_data_zlib_b45 = pyzbar.decode(qr_pil)[0].data
45
46# strip header ('HC1:') and decompress data:
47qr_data_zlib = base45.b45decode(qr_data_zlib_b45[4:])
48# decompress:
49qr_data = zlib.decompress(qr_data_zlib)
50
51# decode cose document:
52(_, (headers1, headers2, cbor_data, signature)) = flynn.decoder.loads(qr_data)
53# decode cbor-encoded payload:
54data = flynn.decoder.loads(cbor_data)
55
56date = lambda ts: datetime.utcfromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
57print("QR Code Issuer :", data[1])
58print("QR Code Expiry :", date(data[4]))
59print("QR Code Generated :", date(data[6]))
60
61glb_schema = json.load(sch)
62
63def annotate(data, schema, level=0):
64 for key, value in data.items():
65 description = schema[key].get('title') or schema[key].get('description') or key
66 description, _, _ = description.partition(' - ')
67 if type(value) is dict:
68 print(' '*level, description)
69 _, _, sch_ref = schema[key]['$ref'].rpartition('/')
70 annotate(value, glb_schema['$defs'][sch_ref]['properties'], level+1)
71 elif type(value) is list:
72 print(' '*level, description)
73 _, _, sch_ref = schema[key]['items']['$ref'].rpartition('/')
74 for v in value:
75 annotate(v, glb_schema['$defs'][sch_ref]['properties'], level+1)
76 else: # value is scalar
77 print(' '*level, description, ':', value)
78
79annotate(data[-260][1], glb_schema['properties'])
Imprint / Impressum