From 2679e6c3ebc181677acdd077df409bf73b7dc048 Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Fri, 31 Dec 2021 11:45:52 +0200 Subject: Lots of refactoring WIP --- README.md | 23 ++++---- greenpass/certificate.py | 58 +++++++++++++++++++++ greenpass/cli.py | 7 +-- greenpass/tests/samples/CovidCertificate_01.pdf | Bin 0 -> 535274 bytes greenpass/tests/samples/CovidCertificate_02.pdf | Bin 0 -> 544440 bytes greenpass/tests/samples/CovidCertificate_2_vac.pdf | Bin 0 -> 544572 bytes greenpass/tests/samples/CovidCertificate_3_vac.pdf | Bin 0 -> 545364 bytes greenpass/tests/test_verifier.py | 5 +- greenpass/verifier.py | 32 ------------ greenpass/verifiers/eu.py | 2 + greenpass/verifiers/il.py | 2 + 11 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 greenpass/certificate.py create mode 100644 greenpass/tests/samples/CovidCertificate_01.pdf create mode 100644 greenpass/tests/samples/CovidCertificate_02.pdf create mode 100644 greenpass/tests/samples/CovidCertificate_2_vac.pdf create mode 100644 greenpass/tests/samples/CovidCertificate_3_vac.pdf create mode 100644 greenpass/verifiers/eu.py create mode 100644 greenpass/verifiers/il.py diff --git a/README.md b/README.md index 22c7c7f..e75e1ef 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,11 @@ -# Green Pass - התו הירוק +# Green Pass -Signature verification for the Green Pass (התו הירוק). +COVID pass verification library and CLI. Implements verification for: -As (not really) specified in https://github.com/MohGovIL/Ramzor + - [EU Digital COVID certificates](https://ec.europa.eu/health/ehealth/covid-19_en) + - [Legacy Israeli Green Pass certificates](https://github.com/MohGovIL/Ramzor) -## Verification Script - -A pythonic implementation of the verification process can be found in [`verify.py`](verify.py). - -### Setup +## Setup Install [`pipenv`](https://pipenv.pypa.io/en/latest/) and sync the required dependencies: @@ -16,11 +13,11 @@ Install [`pipenv`](https://pipenv.pypa.io/en/latest/) and sync the required depe $ pipenv sync ``` -### Usage +### CLI Usage -Generate a Green Pass or Vaccination Certificate at https://corona.health.gov.il/green-pass/ +The CLI tool accepts PDF files, PNG images and raw QR code decoded contents. -#### Option 1 - from PDF +#### PDF Certificates Directly verify your `GreenPass.pdf` or `VaccinationCertificate.pdf`: @@ -28,7 +25,7 @@ Directly verify your `GreenPass.pdf` or `VaccinationCertificate.pdf`: $ pipenv run greenpass -p GreenPass.pdf ``` -#### Option 2 - from QR code image +#### QR Code Images Save the QR code as a `.png` file (e.g. from screenshot) and execute: @@ -36,7 +33,7 @@ Save the QR code as a `.png` file (e.g. from screenshot) and execute: $ pipenv run greenpass -i green_pass_image.png ``` -#### Option 3 - from QR code decoded textual content +#### Decoded QR Code Content Decode the QR code payload yourself, put it in a txt file and then execute: diff --git a/greenpass/certificate.py b/greenpass/certificate.py new file mode 100644 index 0000000..37d97d2 --- /dev/null +++ b/greenpass/certificate.py @@ -0,0 +1,58 @@ +import base64 +import fitz +import json + +from io import BytesIO +from pathlib import Path + +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding, ec +from cryptography.exceptions import InvalidSignature +from PIL import Image +from pyzbar import pyzbar + +from .verifiers.il import IsraeliVerifier +from .verifiers.eu import EuroVerifier + + +class CertificateData(object): + def __init__(self, data): + self.data = data_bytes + + @classmethod + def from_payload(cls, path): + with open(path, "rb") as f: + return cls(f.read().strip()) + + @classmethod + def from_qr(cls, path): + return cls(pyzbar.decode(Image.open(path))[0].data) + + @classmethod + def from_pdf(cls, path): + doc = fitz.open(path) + for i in range(len(doc)): + for img in doc.get_page_images(i): + xref, width = img[0], img[2] + try: + img = fitz.Pixmap(doc, xref) + data = img.tobytes(output="png") + with open(f"/tmp/greenpass/{xref}.png", "wb") as f: + f.write(data) + return cls.from_qr(BytesIO(data)) + except IndexError: + pass + else: + raise Exception("No QR found") + + def verify(self): + # EU certs start with HC1 + if self.data.startswith("HC1"): + return EuroVerifier(self.data) + else: + try: + # Legacy IL certs split the sig and the payload with '#' + _sig, _payload = self.data.split(b"#", maxsplit=1) + return IsraeliVerifier(self.data) + except ValueError: + raise Exception(f"Unknown certificate data: {self.data}") diff --git a/greenpass/cli.py b/greenpass/cli.py index 165ae38..6df49d0 100644 --- a/greenpass/cli.py +++ b/greenpass/cli.py @@ -1,5 +1,6 @@ import click +from .certificate import CertificateData from .verifier import GreenPassVerifier @@ -19,11 +20,11 @@ from .verifier import GreenPassVerifier ) def verify(pdf_path="", image_path="", txt_path=""): if image_path: - verifier = GreenPassVerifier.from_qr(image_path) + data = CertificateData.from_qr(image_path) elif pdf_path: - verifier = GreenPassVerifier.from_pdf(pdf_path) + data = CertificateData.from_pdf(pdf_path) elif txt_path: - verifier = GreenPassVerifier.from_payload(txt_path) + data = CertificateData.from_payload(txt_path) else: ctx = click.get_current_context() click.echo(ctx.get_help()) diff --git a/greenpass/tests/samples/CovidCertificate_01.pdf b/greenpass/tests/samples/CovidCertificate_01.pdf new file mode 100644 index 0000000..66b8763 Binary files /dev/null and b/greenpass/tests/samples/CovidCertificate_01.pdf differ diff --git a/greenpass/tests/samples/CovidCertificate_02.pdf b/greenpass/tests/samples/CovidCertificate_02.pdf new file mode 100644 index 0000000..20b041c Binary files /dev/null and b/greenpass/tests/samples/CovidCertificate_02.pdf differ diff --git a/greenpass/tests/samples/CovidCertificate_2_vac.pdf b/greenpass/tests/samples/CovidCertificate_2_vac.pdf new file mode 100644 index 0000000..b6436d3 Binary files /dev/null and b/greenpass/tests/samples/CovidCertificate_2_vac.pdf differ diff --git a/greenpass/tests/samples/CovidCertificate_3_vac.pdf b/greenpass/tests/samples/CovidCertificate_3_vac.pdf new file mode 100644 index 0000000..88d1f07 Binary files /dev/null and b/greenpass/tests/samples/CovidCertificate_3_vac.pdf differ diff --git a/greenpass/tests/test_verifier.py b/greenpass/tests/test_verifier.py index 2a44b3e..ae58efc 100644 --- a/greenpass/tests/test_verifier.py +++ b/greenpass/tests/test_verifier.py @@ -5,5 +5,6 @@ from ..verifier import GreenPassVerifier SAMPLES_DIR = Path(__file__).parent / "samples" -def test_good(): - assert 1 == 1 +def test_ramzor_samples(): + v = GreenPassVerifier.from_pdf(str(SAMPLES_DIR / "CovidCertificate_01.pdf")) + assert v.verify() is True diff --git a/greenpass/verifier.py b/greenpass/verifier.py index ec9cf69..abe6a4b 100644 --- a/greenpass/verifier.py +++ b/greenpass/verifier.py @@ -27,41 +27,9 @@ class GreenPassVerifier(object): self.ec_cert = self.get_cert_path("IL-NB-DSC-01.pem") self.rsa_cert = self.get_cert_path("RamzorQRPubKey.pem") - @classmethod - def from_payload(cls, path): - with open(path, "rb") as f: - return cls(f.read().strip()) - - @classmethod - def from_qr(cls, path): - return cls(pyzbar.decode(Image.open(path))[0].data) - - @classmethod - def from_pdf(cls, path): - doc = fitz.open(path) - for i in range(len(doc)): - for img in doc.get_page_images(i): - xref, width = img[0], img[2] - try: - img = fitz.Pixmap(doc, xref) - data = img.tobytes(output="png") - with open(f"/tmp/greenpass/{xref}.png", "wb") as f: - f.write(data) - return cls.from_qr(BytesIO(data)) - except IndexError: - pass - else: - raise Exception("No QR found") - def validate_bytes(self, bs): if bs.decode().startswith("GreenPass"): raise Exception("Green pass QR code contains no signature to verify") - # click.secho( - # "⚠️ ", - # fg="yellow", - # bold=True, - # ) - # click.get_current_context().exit() def validate_data(self): ct = self.data["ct"] diff --git a/greenpass/verifiers/eu.py b/greenpass/verifiers/eu.py new file mode 100644 index 0000000..20ac69e --- /dev/null +++ b/greenpass/verifiers/eu.py @@ -0,0 +1,2 @@ +class EuroVerifier(object): + pass diff --git a/greenpass/verifiers/il.py b/greenpass/verifiers/il.py new file mode 100644 index 0000000..c221e91 --- /dev/null +++ b/greenpass/verifiers/il.py @@ -0,0 +1,2 @@ +class IsraeliVerifier(object): + pass -- cgit v1.3.1