Simple Python script to decode and verify an European Health Certificate QR-code

Related tags

Hardware verify-ehc
Overview

Verify EHC

A simple Python script to decode and verify an European Health Certificate QR-code.

Note that the expiration date read from the EHC doesn't seem to be the propper expiration date of tests or vaccinations as defined by EU or local law. In all examples I saw it is much longer. Therefore you need to implement your own logic with the rules defined by your government to get propper expirations dates.

Here in Austria the rules as of writing (2021-06-26) are as follows (Source: gesundheit.gv.at):

For tests:

  • Self-Test: 24 hours (though as far as I know self-tests don't get an EHC)
  • Antigen-Tests: 48 hours
  • PCR-Tests: 72 hours

For vaccinations:

  • For vaccines with 2 vacciantions:
    • 1st vaccination is valid starting from 22 days and ending at 90 days after the vaccination
    • 2nd vaccination adds 180 days to that (i.e. it's valid for 270 days from the 1st vaccination, though the date of the 1st vaccination is not included in the EHC of the 2nd vaccination!)
  • For vaccines with only 1 vaccination (e.g. Johnson & Johnson):
    • valid starting from 22 days and ending at to 270 days after the vaccination
  • For people recovered from COVID-19 that only need 1 vaccination:
    • valid starting from the day of vaccination and ending at 270 days after

NOTE: These rules might be different in different countries and are subject to change. This information is supplied without liability.

Usage

usage: verify_ehc.py [-h] [--certs-file FILE] [--no-verify] [--image] [ehc_code ...]

positional arguments:
  ehc_code

optional arguments:
  -h, --help         show this help message and exit
  --certs-file FILE  Trust list in CBOR format. If not given it will be downloaded from the internet.
  --no-verify        Skip certificate verification.
  --image            Input is an image containing a qr-code.
Comments
  • NOVERIFY flag

    NOVERIFY flag

    Note that by simplifying the chain validation code - we are now no longer checken the chain properly. It is no longer anchored.

    Unfortunately solving this in pyopenss is not easy -- see https://github.com/pyca/pyopenssl/issues/1031

    opened by dirkx 17
  • pyca/cryptography backwards incompatible since 35.0.0 / faulty country data?

    pyca/cryptography backwards incompatible since 35.0.0 / faulty country data?

    Disclaimer: I think this is lowest priority / documentation. I did not modify any script-related file when this appeared. Using no further parameters I was able to verify my German QR-code before. ./verify_ehc.py --image qr.jpeg

    First appearance

    I was playing around with --certs-from and --save-certs using different combination of countries, separately choosing json or cbor. I ran ./verify_ehc.py --certs-from AT,DE,SE,NL --save-certs trust_list.json successful. I got errors running ./verify_ehc.py --certs-from AT,DE,SE --save-certs trust_list.json (no NL). Error:

    Traceback (most recent call last):
      File "./verify_ehc.py", line 2435, in <module>
        main()
    File "./verify_ehc.py", line 2373, in main
        save_certs(certs, certs_path, args.allow_public_key_only)
      File "./verify_ehc.py", line 1879, in save_certs
        'usage': sorted(get_key_usage(cert)),
      File "./verify_ehc.py", line 2022, in get_key_usage
        ext_key_usage = cert.extensions.get_extension_for_oid(ExtensionOID.EXTENDED_KEY_USAGE)
    ValueError: error parsing asn1 value: ParseError { kind: EncodedDefault, location: ["BasicConstraints::ca"] } 
    

    Investigation

    Downloading JSON from all countries

    I ran ./verify_ehc.py --certs-from XX --save-certs trust_list.json with X being all countries one by one. (AT DE SE NL GB CH FR NO)

    Failed with "No Token" (This is expected, see Readme)

    CH FR NO

    Failed with Type Error

    DE, SE, AT no json file was created

    Successful

    GB NL

    I then tried verifying my qr-code with these trust_lists. trust_GB:

    KeyError: 'Key ID not found in trust list
    

    ok - expected.

    trust_NL:

     TypeError: can't compare offset-naive and offset-aware datetimes
    

    that's weird! something for another time, moving on...

    Downloading CBOR from all countries

    I was wondering why ./verify_ehc.py --image qr.jpeg still worked. Upon reading the README.md once again I realized JSON could be at fault. I then did the same as before, collecting certs for all countries one by one but using cbor.

    Successful

    DE SE AT

    faulty (expected)

    NL GB got a bunch of these or similiar looking. ERROR: Cannot store entry 840d7ea7010ec422 / hA1+pwEOxCI= in CBOR trust list: cannot serialize certificate from public-key only these countries provided JSON already. see README.md

    no token (expected)

    FR CH NO

    Conclusion

    might not be correct

    DE, SE, AT provide CBOR NL GB provide JSON CH FR NO need token. I didn't bother to get them.

    In my humble opinion this was not clearly stated in README. Proposal: Include which countries provide which format in README.

    Could you please have a look? Thank you so much!

    opened by nukerxy 8
  • Any changes with the 3rd vaccination?

    Any changes with the 3rd vaccination?

    Hello,

    Did something change with the implementation of the 3rd vaccination? Whenever i try to read a person's 3rd vac-qr-code, the script throws errors.

    looking like this:

    Traceback (most recent call last): File "C:\Users\User\Desktop\verify-ehc-main\verify_ehc.py", line 2456, in main() File "C:\Users\User\Desktop\verify-ehc-main\verify_ehc.py", line 2425, in main ehc_msg = decode_ehc(ehc_code) File "C:\Users\User\Desktop\verify-ehc-main\verify_ehc.py", line 1651, in decode_ehc data = zlib.decompress(data) zlib.error: Error -3 while decompressing data: invalid literal/length code

    Teststing 1st and 2nd vacc there are no problems. I was under the impression that everything stays the same, no matter the vaccination numbers, only the payload would change accordingly

    opened by Peonlicious 7
  • [Errno 22] Invalid argument

    [Errno 22] Invalid argument

    verify_ehc.py --certs-from CH--save-certs test.cbor --allow-pubkey-only OR verify_ehc.py --certs-from GB --save-certs test.cbor --allow-pubkey-only OR verify_ehc.py --certs-from NO --save-certs test.cbor --allow-pubkey-only

    RESULT

    Traceback (most recent call last):
      File "D:\[...]\verify_ehc.py", line 2491, in <module>
        main()
      File "D:\[...]\verify_ehc.py", line 2429, in main
        save_certs(certs, certs_path, args.allow_public_key_only)
      File "D:\[...]\verify_ehc.py", line 1955, in save_certs
        entry['na'] = int(not_valid_after.timestamp())
    OSError: [Errno 22] Invalid argument
    
    opened by trasgu82 6
  • Provide some info on what actually happens

    Provide some info on what actually happens

    Provide some info on what actually happens and include CMS validation of the trust list signature (with a hardcoded link to the National PKI root of the Kingdom of the Netherlands.

    opened by dirkx 5
  • [Feature] Check if certificates are revoked through a CRL

    [Feature] Check if certificates are revoked through a CRL

    This one is a big one, and I don't know if it's already in your TODO list.

    For an ehc to be valid, the signature must be checked against the certificate, the dates should match, and the certificate shouldn't be revoked.
    Right now, you do the first two. Doing the third would help completness.

    Of course, there are several big hurdles, notably that I don't know if any CRL are released right now, and that CRLs could differ country from country.

    In any case, it's not a necessary feature. I'm not expecting it, I just thought it would be useful to put it on your radar. Don't hesitate to close if you don't want to do it :)

    opened by Cqoicebordel 5
  • Exception: error parsing asn1 value: ParseError

    Exception: error parsing asn1 value: ParseError

    I am seeing a new error (to me) loading a X509 certificate after updating my Python version and the cryptography library. Searching I ran across #6386 that seems similar. I was hoping that someone familiar with the ASN.1 might be causing this, and if that is the case, provide some advice on what version of what library I need to load to correct this issue.

    The full exception I am seeing is: ValueError: error parsing asn1 value: ParseError { kind: EncodedDefault, location: ["RawCertificate::tbs_cert", "TbsCertificate::extensions", "2", "Extension::critical"] }

    My end goal is to load this certificate containing a public key, then use that key to verify a signature attached to a binary file. Because this is a public key only I will release (attach) an example script I have put together that shows this issue I am seeing. The full 'verify' script was working previously under Python 3.8.1 (I believe) and an earlier cryptography library, version unrecorded/unknown. That earlier configuration was able to load signatures created using both SHA256 and SHA512 hashes using the appropriate certificates that went with those signatures. The current configuration works for the SHA256 signature but fails loading the certificated associated with the SHA512 hash. The example contains the public part of the failing certificate.

    I am running on Windows 10 Enterprise, V21H2 with Python 3.10.6, but have regressed and see the same failure with both 3.9.13 and 3.8.10. All of these versions are using the crypto library version 37.0.4.

    C:>python --version Python 3.10.6 C:>pip list Package Version


    cffi 1.15.1 cryptography 37.0.4 Pillow 9.2.0 pip 22.2.2 pycparser 2.21 setuptools 65.0.1 six 1.16.0

    CrypoError.zip

    opened by rbroberts115 4
  • CERTS_URL_AT_GREENCHECK = 'https://greencheck.gv.at/api/v2/masterdata'

    CERTS_URL_AT_GREENCHECK = 'https://greencheck.gv.at/api/v2/masterdata'

    CERTS_URL_AT_GREENCHECK = 'https://greencheck.gv.at/api/v2/masterdata'

    { "message" : "Die Version dieser Anwendung ist veraltet! Bitte installieren Sie die aktuellste Version!", "detailedMessage" : "Die Version dieser Anwendung ist veraltet! Bitte installieren Sie die aktuellste Version!" }

    opened by tech-2 4
  • [Feature request] Display more infos about the `ehc_msg`

    [Feature request] Display more infos about the `ehc_msg`

    Right now, the only info about the ehc COSE message displayed is the payload.

    Having access to the signature and, more generally, what is available outside of the payload would be very welcome.

    Note that I'm not sure of anything at this point. I don't know what is actually accessible in the ehc qr code itself. That's why I love your script, as it allows me to follow by "hand" what is done to verify an ehc.
    Except for the signature part, that's why I filing this FR :)

    opened by Cqoicebordel 4
  • error with Python 3.10.0

    error with Python 3.10.0

    here is the error description: (should the py file be changed or Python problem?)

    Traceback (most recent call last): File "/ehc/verify_ehc.py", line 2493, in main() File "/ehc/verify_ehc.py", line 2400, in main certs = download_ehc_certs(parse_sources(args.certs_from), check_kid, certs_table) File "/ehc/verify_ehc.py", line 1400, in download_ehc_certs certs.update(downloader(check_kid)) File "/ehc/verify_ehc.py", line 747, in download_de_certs pubkey = get_root_cert('DE').public_key() # type: ignore File "/ehc/verify_ehc.py", line 1525, in get_root_cert return ROOT_CERT_DOWNLOADERSsource File "/ehc/verify_ehc.py", line 1483, in get_de_root_cert return HackCertificate(get_de_root_pubkey()) TypeError: Can't instantiate abstract class HackCertificate with abstract method tbs_precertificate_bytes

    opened by sky59 3
  • Strip Revoked

    Strip Revoked

    Hello panzi,

    Great job!

    I'm testing your script and I having an issue to make parameter "--strip-revoked" work. Am I doing something wrong or is it just broken right now? I found some testing fake QR and in most apps few days ago they worked but now they already result as revoked. With your script I was not able to detect them (I think it's also related with the problems downloading revoked certificates lists)

    Thanks in advanced

    ERROR: loading revokation list https://e-certs.gouv.tg/crl/hcert/covid19 https://e-certs.gouv.tg/crl/hcert/covid19 401 Unauthorized ERROR: loading revokation list http://greenca.diia.gov.ua/download/crls/CA-7904C820-Full.crl http://greenca.diia.gov.ua/download/crls/CA-7904C820-Full.crl 502 Bad Gateway ERROR: loading revokation list http://crl.exampledomain.example/CRL/CSCA.crl HTTPConnectionPool(host='crl.exampledomain.example', port=80): Max retries exceeded with url: /CRL/CSCA.crl (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000002186877B910>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) ERROR: loading revokation list http://crl.cra.ge/dgccountrysigningca.crl http://crl.cra.ge/dgccountrysigningca.crl 404 Not Found ERROR: loading revokation list http://crl.his.bg/csca1.crl HTTPConnectionPool(host='crl.his.bg', port=80): Max retries exceeded with url: /csca1.crl (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x00000218687ADB80>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) ERROR: loading revokation list http://NBMorocco.ma/CRLs/MA-Health.crl HTTPSConnectionPool(host='nbmorocco.ma', port=443): Max retries exceeded with url: /CRLs/MA-Health.crl (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)'))) ERROR: loading revokation list http://ants.gouv.fr/csca_crl http://ants.gouv.fr/csca_crl 404 Not Found ERROR: loading revokation list http://cdp.health.gov.il/crl/CSCA-Health-DCG-IL-01.crl http://cdp.health.gov.il/crl/CSCA-Health-DCG-IL-01.crl 404 Not Found ERROR: loading revokation list https://csca-mco.gouv.mc/MCO.crl Unable to load CRL ERROR: loading revokation list https://csca-mco.gouv.mc/MCO.crl https://csca-mco.gouv.mc/MCO.crl 200 OK ERROR: loading revokation list http://cert.gov.ie/CRL/CSCA.crl HTTPConnectionPool(host='cert.gov.ie', port=80): Max retries exceeded with url: /CRL/CSCA.crl (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x00000218687F3DC0>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) ERROR: loading revokation list https://gen.digitalcovidcertificates.gov.ie/api/CSCA.crl https://gen.digitalcovidcertificates.gov.ie/api/CSCA.crl 404 Not Found ERROR: loading revokation list http://www.smdcc.sm/CRL/CSCA.crl Unable to load CRL ERROR: loading revokation list http://crl.eudcc.gov.cy/dsc.crl HTTPConnectionPool(host='crl.eudcc.gov.cy', port=80): Max retries exceeded with url: /dsc.crl (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021868800040>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) ERROR: loading revokation list http://dgc1.dgc.hr/croatia-dgc-csca.crl HTTPConnectionPool(host='dgc1.dgc.hr', port=80): Max retries exceeded with url: /croatia-dgc-csca.crl (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021868804760>: Failed to establish a new connection: [Errno 11002] getaddrinfo failed')) ERROR: loading revokation list http://dgc2.dgc.hr/croatia-dgc-csca.crl HTTPConnectionPool(host='dgc2.dgc.hr', port=80): Max retries exceeded with url: /croatia-dgc-csca.crl (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021868804850>: Failed to establish a new connection: [Errno 11002] getaddrinfo failed')) ERROR: loading revokation list http://dgc1.dgc.hr/croatia-dgc-csca.crl HTTPConnectionPool(host='dgc1.dgc.hr', port=80): Max retries exceeded with url: /croatia-dgc-csca.crl (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021868807670>: Failed to establish a new connection: [Errno 11002] getaddrinfo failed')) ERROR: loading revokation list http://dgc2.dgc.hr/croatia-dgc-csca.crl HTTPConnectionPool(host='dgc2.dgc.hr', port=80): Max retries exceeded with url: /croatia-dgc-csca.crl (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021868807760>: Failed to establish a new connection: [Errno 11002] getaddrinfo failed')) ERROR: loading revokation list http://pki.nhsx.nhs.uk/CRL/ENG_CSCA.crl http://pki.nhsx.nhs.uk/CRL/ENG_CSCA.crl 404 Not Found ERROR: loading revokation list http://covid-status.service.nhsx.nhs.uk/CRL/ENG_CSCA.crl http://covid-status.service.nhsx.nhs.uk/CRL/ENG_CSCA.crl 403 Forbidden ERROR: loading revokation list http://pki.nhsx.nhs.uk/CRL/ENG_CSCA.crl http://pki.nhsx.nhs.uk/CRL/ENG_CSCA.crl 404 Not Found ERROR: loading revokation list http://covid-status.service.nhsx.nhs.uk/CRL/ENG_CSCA.crl http://covid-status.service.nhsx.nhs.uk/CRL/ENG_CSCA.crl 403 Forbidden ERROR: loading revokation list http://pki.nhsx.nhs.uk/CRL/ENG_CSCA.crl http://pki.nhsx.nhs.uk/CRL/ENG_CSCA.crl 404 Not Found ERROR: loading revokation list http://covid-status.service.nhsx.nhs.uk/CRL/ENG_CSCA.crl http://covid-status.service.nhsx.nhs.uk/CRL/ENG_CSCA.crl 403 Forbidden ERROR: loading revokation list http://pki.nhsx.nhs.uk/CRL/ENG_CSCA.crl http://pki.nhsx.nhs.uk/CRL/ENG_CSCA.crl 404 Not Found ERROR: loading revokation list http://covid-status.service.nhsx.nhs.uk/CRL/ENG_CSCA.crl http://covid-status.service.nhsx.nhs.uk/CRL/ENG_CSCA.crl 403 Forbidden ERROR: loading revokation list http://pki.nhsx.nhs.uk/CRL/SCO_CSCA.crl http://pki.nhsx.nhs.uk/CRL/SCO_CSCA.crl 404 Not Found ERROR: loading revokation list http://covid-status.service.nhsx.nhs.uk/CRL/SCO_CSCA.crl http://covid-status.service.nhsx.nhs.uk/CRL/SCO_CSCA.crl 403 Forbidden ERROR: loading revokation list https://www.notarise.gov.sg/csca.crl Unable to load CRL ERROR: loading revokation list http://crl.exampledomain.example/CRL/CSCA.crl HTTPConnectionPool(host='crl.exampledomain.example', port=80): Max retries exceeded with url: /CRL/CSCA.crl (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021868865BE0>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) ERROR: loading revokation list http://crldes.izenpe.com/cgi-bin/crlbcizenpe http://crldes.izenpe.com/cgi-bin/crlbcizenpe 404 Not Found


    Just in case you're interested, I think here you can see how to download Italian certificate list. You may be interested in including it to your script... https://github.com/ministero-salute/dcc-utils/blob/master/examples/fetch_certificates.js

    opened by trasgu82 3
  • cose error?

    cose error?

    Hey!

    Do you have any hint what cose is complaining after the json output? image

    (if you need the pdf for testing, just tell me)

    Kind regards Martin Hochreiter

    opened by idefixgallier 1
  • Harmonize JSON output

    Harmonize JSON output

    It would be great to have verify-ehc enable full JSON output. At the moment the used signing certificate is rather human-readable plain text and only the vaccination certificate is JSON formatted. Having a common JSON format would make parsing easier, maybe have a --all-json flag.

    If that's not possible it would be good to just add the two fields Valid Key Usage: True and Signature Valid: True to the payload JSON and only output JSON when --only-json is set.

    opened by bluepuma77 1
Owner
Mathias Panzenböck
Mathias Panzenböck
Pihole-eink-display - A simple Python script to display PiHole statistics on an eInk Display

Pihole-eink-display - A simple Python script to display PiHole statistics on an eInk Display

Mark McIntyre 64 Oct 11, 2022
Code and build instructions for Snap, a simple Raspberry Pi and LED machine to show you how expensive the electricyty is at the moment

Code and build instructions for Snap, a simple Raspberry Pi and LED machine to show you how expensive the electricyty is at the moment. On row of LEDs shows the cost of the hour, the other row the cost of the day.

Johan Jonk Stenström 3 Sep 8, 2022
A Python script to monitor the latest block on an LCD.

PiHole-Monitoring A Python script to monitor the latest block on a lcd display. The first number represents the dns queries from the last 24h, the sec

Maxi 4 Dec 5, 2022
Python script: Enphase Envoy mqtt json for Home Assistant

A Python script that takes a real time stream from Enphase Envoy and publishes to a mqtt broker. This can then be used within Home Assistant or for other applications. The data updates at least once per second with negligible load on the Envoy.

null 29 Dec 27, 2022
A python script for Homeassistant that counts down the days to birthdays, anniversaries etc

Date Countdown A python script for Homeassistant that counts down the days to birthdays, anniversaries etc Important note I no longer use homeassistan

Marc Forth 21 Mar 12, 2022
Python script for printing to the Hanshow price-tag

This repository contains Python code for talking to the ATC_TLSR_Paper open-source firmware for the Hanshow e-paper pricetag. Installation # Clone the

null 12 Oct 6, 2022
A python script for macOS to enable scrolling with the 3M ergonomic mouse EM500GPS in any application.

A python script for macOS to enable scrolling with the 3M ergonomic mouse EM500GPS in any application.

null 3 Feb 19, 2022
A script and GUI for controlling stepper motors from an arduino

A script and GUI for controlling stepper motors from an arduino (nema 23 in my case but should work for others in general)

Pip 2 Aug 1, 2022
A install script for installing qtile and my configs on Raspberry Pi OS

QPI OS - Qtile + Raspberry PI OS Qtile + Raspberry Pi OS :) Installation Run this command in the terminal

RPICoder 3 Dec 19, 2021
A script that publishes power usage data of iDrac enabled servers to an MQTT broker for integration into automation and power monitoring systems

iDracPowerMonitorMQTT This script publishes iDrac power draw data for iDrac 6 enabled servers to an MQTT broker. This can be used to integrate the pow

Lucas Zanchetta 10 Oct 6, 2022
ArucoFollow - A script for Robot Operating System and it is a part of a project Robot

ArucoFollow ArucoFollow is a script for Robot Operating System and it is a part

null 5 Jan 25, 2022
Doughskript interpreter for converting simple command sequences into executable Arduino C++ code.

Doughskript interpreter for converting simple command sequences into executable Arduino C++ code.

Svjatoslav 2 Jan 11, 2022
A script for performing OTA update over BLE on ESP32

A script for performing OTA update over BLE on ESP32

Felix Biego 18 Dec 15, 2022
A lightweight script for updating custom components for Home Assistant

Updater for Home Assistant This is a lightweight script for updating custom components for Home Assistant. If for some reason you do not want to use H

Alex X 12 Sep 21, 2022
A module for cross-platform control of the mouse and keyboard in python that is simple to install and use.

PyUserInput PyUserInput is a group project so we've moved the project over to a group organization: https://github.com/PyUserInput/PyUserInput . That

Paul Barton 1k Dec 27, 2022
This application works with serial communication. Use a simple gui to send and receive serial data from arduino and control leds and motor direction

This application works with serial communication. Use a simple gui to send and receive serial data from arduino and control leds and motor direction

ThyagoKZKR 2 Jul 18, 2022
Run this code to blink your ThinkPad LED with a hidden mysterious Morse code! ;)

TMorse Run this code to blink your ThinkPad LED with a hidden mysterious Morse code! ;) Compatible with python3.9+. No third-party library is required

Mahyar 2 Jul 11, 2022
A simple portable USB MIDI controller based on Raspberry-PI Pico and a 16-button keypad, written in Circuit Python

RPI-Pico-16-BTn-MIDI-Controller-using-CircuitPython A simple portable USB MIDI controller based on Raspberry-PI Pico, written in Circuit Python. Link

Rounak Dutta 3 Dec 4, 2022
A Simple Python KeyLogger App

✨ Kurulum Uygulamayı bilgisayarınızda kullana bilmek için bazı işlemler yapmanız gerekiyor. Aşağıdaki yönlendirmeleri takip ederek bunu yapabilirsiniz

VorteX 7 Jun 11, 2022