support passing raw HC1 data via cmdline
[greenpass.git] / greenpass.py
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 Pillow
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 import os
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)
31 print(f"Usage: {sys.argv[0]} 'HC1:.....'", file=sys.stderr)
32 sys.exit(1)
33 else:
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
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'])
Imprint / Impressum