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