]>
Commit | Line | Data |
---|---|---|
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 | ||
9 | import sys | |
10 | import glob | |
11 | import json | |
12 | import zlib | |
13 | import flynn | |
14 | import base45 | |
15 | import PyPDF2 | |
16 | from PIL import Image | |
17 | from pyzbar import pyzbar | |
18 | from datetime import datetime | |
19 | from urllib.request import urlopen | |
20 | ||
21 | sch = urlopen('https://raw.githubusercontent.com/ehn-dcc-development/ehn-dcc-schema/release/1.3.0/DCC.combined-schema.json') | |
22 | ||
23 | if 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) | |
31 | else: | |
32 | infile = sys.argv[1] | |
33 | ||
34 | if 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()) | |
40 | else: # assume image | |
41 | qr_pil = Image.open(infile) | |
42 | ||
43 | # decode QR code into raw bytes: | |
44 | qr_data_zlib_b45 = pyzbar.decode(qr_pil)[0].data | |
45 | ||
46 | # strip header ('HC1:') and decompress data: | |
47 | qr_data_zlib = base45.b45decode(qr_data_zlib_b45[4:]) | |
48 | # decompress: | |
49 | qr_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: | |
54 | data = flynn.decoder.loads(cbor_data) | |
55 | ||
56 | date = lambda ts: datetime.utcfromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') | |
57 | print("QR Code Issuer :", data[1]) | |
58 | print("QR Code Expiry :", date(data[4])) | |
59 | print("QR Code Generated :", date(data[6])) | |
60 | ||
61 | glb_schema = json.load(sch) | |
62 | ||
63 | def 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 | ||
79 | annotate(data[-260][1], glb_schema['properties']) |