Digitaler-Impfnachweis / certification-apis

API Documentation
Apache License 2.0
169 stars 46 forks source link

Where to get the Public Key with which the Trust List is signed? #157

Closed panzi closed 3 years ago

panzi commented 3 years ago

The first line of the Trust List is a signature, but in order to verify that I'd need the public key (to the private key with which the signature was created). Where can I find that?

timokoenig commented 3 years ago

@panzi https://github.com/Digitaler-Impfnachweis/covpass-ios/blob/main/Certificates/DEMO/CA/pubkey.pem

panzi commented 3 years ago

Thank you! Are you sure this is the right one, though? Because I'm getting this:

>>> import requests
>>> from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
>>> from cryptography.hazmat.primitives.serialization import load_pem_public_key
>>> from cryptography.hazmat.primitives import hashes
>>> from base64 import b64decode
>>> certs_signed_json = requests.get('https://de.dscg.ubirch.com/trustList/DSC/').content
>>> pubkey_pem = requests.get('https://github.com/Digitaler-Impfnachweis/covpass-ios/raw/main/Certificates/DEMO/CA/pubkey.pem').content
>>> pubkey = load_pem_public_key(pubkey_pem)
>>> sign_b64, body_json = certs_signed_json.split(b'\n', 1)
>>> sign = b64decode(sign_b64)
>>> pubkey.verify(sign, body_json, ECDSA(hashes.SHA256()))
Traceback (most recent call last):
  File "<pyshell#167>", line 1, in <module>
    pubkey.verify(sign, body_json, ECDSA(hashes.SHA256()))
  File "/home/panzi/.local/lib/python3.9/site-packages/cryptography/hazmat/backends/openssl/ec.py", line 378, in verify
    _ecdsa_sig_verify(self._backend, self, signature, data)
  File "/home/panzi/.local/lib/python3.9/site-packages/cryptography/hazmat/backends/openssl/ec.py", line 106, in _ecdsa_sig_verify
    raise InvalidSignature
cryptography.exceptions.InvalidSignature

(Using PROD or PROD_RKI instead of the DEMO in the URL doesn't change anything.)

e7p commented 3 years ago

Keep in mind that the verify method of hazmat.primitives.asymmetric.ec takes signatures in the DER encoded DSS format only. To achieve this format, add to the code:

from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
r = int.from_bytes(sign[:len(sign)//2], byteorder="big", signed=False)
s = int.from_bytes(sign[len(sign)//2:], byteorder="big", signed=False)
sign = encode_dss_signature(r, s)
panzi commented 3 years ago

Thank you! 😄

e7p commented 3 years ago

@panzi maybe you are also interested in my Python port of the relevant kotlin code in the SDK to be able to verify and dump contents of CovPass QR-Codes. After all it is just a quick'n'dirty script which while writing helped me to understand the architecture of the system better. Another good read might be https://harrisonsand.com/posts/covid-certificates/

panzi commented 3 years ago

@e7p Thank you but I already did that part myself, see: https://github.com/panzi/verify-ehc Note that I've also seen RSA keys being used, not just EC. My script handles that. What my script currently doesn't do is validating the signature of the trust list itself, but I know how to do that now (for Germany).

loelkes commented 2 years ago

I stumbled across this. Here's my solution, with some help from python-ecdsa:

import hashlib
from ecdsa import VerifyingKey
import requests

with open('pubkey.pem') as f:
    vk = VerifyingKey.from_pem(f.read())

result = requests.get(...)
signature, message = result.content.split(b'\n')
vk.verify(base64.b64decode(signature), message, hashfunc=hashlib.sha256)
e7p commented 2 years ago

Looks like a good solution. I thought I should eventually share the python snippet which is basically just the kotlin code ported to python: https://gist.github.com/e7p/66dde9002fcc0cb197f3bcab7c3ce975

As this is somehow just quick'n'dirty, it doesn't parse the signature so good as @loelkes solution though. It simply splits the signature in half to get the two parameters r and s.