diff --git a/README.md b/README.md index 78f1697..01f67a7 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,26 @@ mdoc.disclosure_map >> ... dictionary containing all the disclosed attributes ... ```` +### Verify with Certificate Chain and Element Hashes + +For production use, verify both the X.509 certificate chain and element hashes: + +````python +from pymdoccbor.mdoc.verifier import MdocCbor +from cryptography import x509 +from cryptography.hazmat.backends import default_backend + +# Load trusted root certificates +with open('iaca_cert.pem', 'rb') as f: + iaca_cert = x509.load_pem_x509_certificate(f.read(), default_backend()) + +mdoc = MdocCbor() +mdoc.loads(device_response_bytes) +is_valid = mdoc.verify(trusted_root_certs=[iaca_cert], verify_hashes=True) +```` + +For complete documentation on certificate chain verification and hash verification, see [docs/CERTIFICATE-CHAIN-VERIFICATION.md](docs/CERTIFICATE-CHAIN-VERIFICATION.md). + ### Verify the Mobile Security Object ```` diff --git a/docs/certificate_chain_verification.md b/docs/CERTIFICATE-CHAIN-VERIFICATION.md similarity index 100% rename from docs/certificate_chain_verification.md rename to docs/CERTIFICATE-CHAIN-VERIFICATION.md diff --git a/pymdoccbor/mso/verifier.py b/pymdoccbor/mso/verifier.py index 7a54909..5678532 100644 --- a/pymdoccbor/mso/verifier.py +++ b/pymdoccbor/mso/verifier.py @@ -1,6 +1,8 @@ import cbor2 import cryptography +import hashlib import logging +from datetime import datetime, timezone from pycose.keys import CoseKey, EC2Key from pycose.messages import Sign1Message @@ -11,6 +13,11 @@ MsoX509ChainNotFound, UnsupportedMsoDataFormat ) + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes +from cryptography.exceptions import InvalidSignature + from pymdoccbor import settings from pymdoccbor.tools import bytes2CoseSign1, cborlist2CoseSign1 @@ -132,9 +139,7 @@ def attest_public_key(self, trusted_root_certs: list = None): return None # Verify certificate chain - from cryptography import x509 - from cryptography.hazmat.primitives import hashes - from cryptography.exceptions import InvalidSignature + # Load DS certificate (first in chain) ds_cert = self.x509_certificates[0] if self.x509_certificates else None @@ -164,7 +169,6 @@ def attest_public_key(self, trusted_root_certs: list = None): raise ValueError("DS certificate not signed by any trusted root") # Verify certificate validity dates - from datetime import datetime, timezone now = datetime.now(timezone.utc) if ds_cert.not_valid_before_utc > now: @@ -225,8 +229,6 @@ def verify_element_hashes(self, namespaces: dict) -> dict: Returns: dict: Results with 'valid' (bool), 'total' (int), 'verified' (int), 'failed' (list) """ - import hashlib - mso_data = self.payload_as_dict value_digests = mso_data.get('valueDigests', {}) diff --git a/pymdoccbor/tests/test_09_errors_field.py b/pymdoccbor/tests/test_09_errors_field.py index 16e4909..dc8524c 100644 --- a/pymdoccbor/tests/test_09_errors_field.py +++ b/pymdoccbor/tests/test_09_errors_field.py @@ -5,6 +5,8 @@ an 'errors' field describing which elements were not available. """ +import cbor2 + from pymdoccbor.mdoc.verifier import MobileDocument from pymdoccbor.mdoc.issuer import MdocCborIssuer from pymdoccbor.tests.micov_data import MICOV_DATA @@ -103,7 +105,6 @@ def test_mobile_document_dump_with_errors(): assert isinstance(dump, bytes) # Decode and verify errors field is present - import cbor2 decoded = cbor2.loads(dump) # The dump is wrapped in a CBORTag, so we need to access .value if hasattr(decoded, 'value'): @@ -138,7 +139,6 @@ def test_mobile_document_dump_without_errors(): assert isinstance(dump, bytes) # Decode and verify errors field is NOT present - import cbor2 decoded = cbor2.loads(dump) if hasattr(decoded, 'value'): decoded = decoded.value