Überprüfen von Belegen für Azure Confidential Ledger-Schreibtransaktionen
Ein Beleg für eine Azure Confidential Ledger-Schreibtransaktion stellt einen kryptografischen Merkle-Nachweis dafür dar, dass die entsprechende Schreibtransaktion vom CCF-Netzwerk global committet wurde. Azure Confidential Ledger-Benutzer können jederzeit einen Beleg für eine committete Schreibtransaktion erhalten, um zu überprüfen, ob der entsprechende Schreibvorgang erfolgreich im unveränderlichen Ledger erfasst wurde.
Weitere Informationen zu Belegen für Azure Confidential Ledger-Schreibtransaktionen finden Sie in diesem Artikel.
Schritte zur Belegüberprüfung
Belege für Schreibtransaktionen können mithilfe einiger Schritte überprüft werden. Diese werden in den folgenden Unterabschnitten beschrieben. Die gleichen Schritte sind in der CCF-Dokumentation beschrieben.
Berechnung des Blattknotens
Der erste Schritt besteht darin, den SHA-256-Hash des Blattknotens im Merkle-Baum für die committete Transaktion zu berechnen. Ein Blattknoten besteht aus der sortierten Verkettung der folgenden Felder, die in einem Azure Confidential Ledger-Beleg unter leafComponents
zu finden sind:
writeSetDigest
- SHA-256-Digest von
commitEvidence
- Felder vom Typ
claimsDigest
Diese Werte müssen als Bytearrays verkettet werden: writeSetDigest
und claimsDigest
müssen aus Zeichenfolgen mit hexadezimalen Ziffern in Bytearrays konvertiert werden. Der Hash von commitEvidence
(als Bytearray) kann dagegen durch Anwenden der SHA-256-Hashfunktion auf die UTF-8-codierte Zeichenfolge vom Typ commitEvidence
ermittelt werden.
Der Hash-Digest des Blattknotens kann auf ähnliche Weise berechnet werden, indem die SHA-256-Hashfunktion auf die Ergebnisverkettung der resultierenden Bytes angewendet wird.
Berechnung des Stammknotens
Im zweiten Schritt wird der SHA-256-Hash des Stamms des Merkle-Baums zum Zeitpunkt des Commits der Transaktion berechnet. Die Berechnung erfolgt durch iterative Verkettung und Hashing des Ergebnisses der vorherigen Iteration (ausgehend von dem im vorherigen Schritt berechneten Hash des Blattknotens) mit den im Feld proof
eines Belegs bereitgestellten Hashes der sortierten Knoten. Die Liste proof
wird als sortierte Liste bereitgestellt, und ihre Elemente müssen in der angegebenen Reihenfolge durchlaufen werden.
Die Verkettung muss für die Bytedarstellung unter Berücksichtigung der relativen Reihenfolge durchgeführt werden, die in den bereitgestellten Objekten im Feld proof
angegeben ist (entweder left
oder right
).
- Wenn es sich beim Schlüssel des aktuellen Elements in
proof
um einen Schlüssel vom Typleft
handelt, muss das Ergebnis der vorherigen Iteration an den aktuellen Elementwert angefügt werden. - Wenn es sich beim Schlüssel des aktuellen Elements in
proof
um einen Schlüssel vom Typright
handelt, muss das Ergebnis der vorherigen Iteration dem aktuellen Elementwert vorangestellt werden.
Nach jeder Verkettung muss die SHA-256-Funktion angewendet werden, um die Eingabe für die nächste Iteration abzurufen. Bei diesem Prozess werden die Standardschritte zum Berechnen des Stammknotens einer Datenstruktur vom Typ Merkle-Baum unter Berücksichtigung der für die Berechnung erforderlichen Knoten ausgeführt.
Überprüfen der Signatur über den Stammknoten
Im dritten Schritt wird mithilfe des Signaturknotenzertifikats im Beleg überprüft, ob die kryptografische Signatur, die für den Stammknotenhash generiert wurde, gültig ist. Bei dem Überprüfungsprozess werden die Standardschritte für die Überprüfung digitaler Signaturen von Nachrichten verwendet, die mit dem Elliptic Curve Digital Signature Algorithm (ECDSA) signiert wurden. Der Prozess umfasst folgende Schritte:
- Decodieren der Base64-Zeichenfolge
signature
in ein Bytearray - Extrahieren Sie des öffentlichen ECDSA-Schlüssels aus dem Signaturknotenzertifikat
cert
- Vergewissern, dass die Signatur für den Stamm des Merkle-Baums (berechnet gemäß der Anleitung aus dem vorherigen Unterabschnitt) authentisch ist – mithilfe des extrahierten öffentlichen Schlüssels aus dem vorherigen Schritt. Dieser Schritt entspricht im Grunde einem Standardprozess zur Überprüfung einer digitalen Signatur mit ECDSA. Zahlreiche Bibliotheken in den meisten gängigen Programmiersprachen ermöglichen die Überprüfung einer ECDSA-Signatur mithilfe eines Zertifikats mit öffentlichem Schlüssel für einige Daten (z. B. die Kryptografiebibliothek für Python).
Überprüfen der Unterstützung des Signaturknotenzertifikats
Neben obigem Schritt muss auch überprüft werden, ob das Signaturknotenzertifikat vom aktuellen Ledgerzertifikat unterstützt wird (also signiert wurde). Dieser Schritt hängt nicht von den drei vorherigen Schritten ab und kann unabhängig von den anderen Schritten durchgeführt werden.
Es kann sein, dass die aktuelle Dienstidentität, von der der Beleg stammt, nicht der Identität entspricht, von der der Signaturknoten unterstützt wurde. Ein möglicher Grund hierfür wäre beispielsweise eine Zertifikatverlängerung. In diesem Fall muss die Kette der Zertifikatvertrauensstellungen vom Signaturknotenzertifikat (Feld cert
im Beleg) bis zur vertrauenswürdigen Stammzertifizierungsstelle (aktuelles Dienstidentitätszertifikat) über andere vorherige Dienstidentitäten (Listenfeld serviceEndorsements
im Beleg) überprüft werden. Die Liste serviceEndorsements
ist absteigend nach dem Alter der Dienstidentität sortiert.
Die Zertifikatbestätigung muss für die gesamte Kette überprüft werden und läuft auf exakt die gleiche Weise ab wie die im vorherigen Unterabschnitt beschriebene Überprüfung einer digitalen Signatur. Es gibt beliebte Open-Source-Kryptografiebibliotheken wie OpenSSL, die üblicherweise für einen Zertifikatbestätigungsschritt verwendet werden.
Überprüfen des Digests von Anwendungsansprüchen
Wenn Anwendungsansprüche an einen Beleg angefügt werden, ist es in einem optionalen Schritt auch möglich, den Anspruchsdigest aus den verfügbar gemachten Ansprüchen (nach einem bestimmten Algorithmus) zu berechnen und zu überprüfen, ob der Digest mit dem claimsDigest
in den Belegnutzdaten übereinstimmt. Um den Digest aus den verfügbar gemachten Anspruchsobjekten zu berechnen, müssen Sie sämtliche Anwendungsanspruchsobjekte in der Liste durchlaufen und das zugehörige Feld kind
überprüfen.
Wenn das Anspruchsobjekt vom Typ LedgerEntry
ist, sollten die Ledgersammlungs-ID (collectionId
) und der Inhalt (contents
) des Anspruchs extrahiert und dazu verwendet werden, die HMAC-Digests mithilfe des geheimen Schlüssels (secretKey
) zu berechnen, der im Anspruchsobjekt angegeben ist. Diese beiden Digests werden dann verkettet, und der SHA-256-Hash der Verkettung wird berechnet. Das Protokoll (protocol
) und der resultierende Anspruchsdatendigest werden dann verkettet, und ein weiterer SHA-256-Hash der Verkettung wird berechnet, um den endgültigen Digest zu erhalten.
Wenn das Anspruchsobjekt vom Typ ClaimDigest
ist, sollte der Anspruchsdigest (value
) extrahiert und mit dem Protokoll (protocol
) verkettet und anschließend der SHA-256-Hash der Verkettung berechnet werden, um den endgültigen Digest zu erhalten.
Nach dem Berechnen jedes einzelnen Anspruchsdigests müssen alle berechneten Digests aus jedem Anwendungsanspruchsobjekt verkettet werden (in derselben Reihenfolge wie im Beleg). Der Verkettung sollte dann die Anzahl der verarbeiteten Ansprüche vorangestellt werden. Der SHA-256-Hash der vorherigen Verkettung generiert den endgültigen Anspruchsdigest, der mit dem claimsDigest
im Belegobjekt übereinstimmen sollte.
Weitere Ressourcen
Weitere Informationen zum Inhalt eines Belegs für Azure Confidential Ledger-Schreibtransaktionen sowie Erläuterungen zu den einzelnen Feldern finden Sie in diesem Artikel. Die CCF-Dokumentation enthält unter den folgenden Links auch weitere Informationen zur Belegüberprüfung und anderen zugehörigen Ressourcen:
- Belegüberprüfung
- CCF-Glossar
- Merkle-Baum
- Kryptografie
- Zertifikate
- Anwendungsansprüche
- Benutzerdefinierte Ansprüche in Belegen
Überprüfen der Belege für Schreibtransaktionen
Hilfsprogramme zur Belegbestätigung
Die Azure Confidential Ledger-Clientbibliothek für Python bietet Hilfsfunktionen zur Überprüfung von Schreibtransaktionsbelegen und zur Berechnung des Anspruchsdigests aus einer Liste von Anwendungsansprüchen. Weitere Informationen zur Verwendung des Datenebenen-SDK und der spezifischen Hilfsprogramme für Belege finden Sie in diesem Abschnitt und diesem Beispielcode.
Einrichtung und Voraussetzungen
Zu Referenzzwecken ist Beispielcode in Python verfügbar, mit dem Belege für Azure Confidential Ledger-Schreibtransaktionen gemäß den Schritten im obigen Abschnitt vollständig überprüft werden können.
Zum Ausführen des vollständigen Überprüfungsalgorithmus sind das aktuelle Dienstnetzwerkzertifikat und ein Beleg für eine Schreibtransaktion aus einer aktiven Confidential Ledger-Ressource erforderlich. Ausführliche Informationen zum Abrufen eines Belegs für eine Schreibtransaktion sowie des Dienstzertifikats aus einer Confidential Ledger-Instanz finden Sie in diesem Artikel.
Exemplarische Vorgehensweise mit Code
Der folgende Code kann verwendet werden, um die erforderlichen Objekte zu initialisieren und den Belegüberprüfungsalgorithmus auszuführen. Mit einem separaten Hilfsprogramm (verify_receipt
) wird der vollständige Überprüfungsalgorithmus ausgeführt. Es akzeptiert den Inhalt des Felds receipt
in einer GET_RECEIPT
-Antwort als Wörterbuch und das Dienstzertifikat als einfache Zeichenfolge. Die Funktion löst eine Ausnahme aus, wenn der Beleg ungültig ist oder wenn während der Verarbeitung ein Fehler aufgetreten ist.
Es wird davon ausgegangen, dass sowohl der Beleg als auch das Dienstzertifikat aus Dateien geladen werden können. Aktualisieren Sie sowohl die Konstante service_certificate_file_name
als auch die Konstante receipt_file_name
mit den entsprechenden Dateinamen des Dienstzertifikats und des Belegs, den Sie überprüfen möchten.
import json
# Constants
service_certificate_file_name = "<your-service-certificate-file>"
receipt_file_name = "<your-receipt-file>"
# Use the receipt and the service identity to verify the receipt content
with open(service_certificate_file_name, "r") as service_certificate_file, open(
receipt_file_name, "r"
) as receipt_file:
# Load relevant files content
receipt = json.loads(receipt_file.read())["receipt"]
service_certificate_cert = service_certificate_file.read()
try:
verify_receipt(receipt, service_certificate_cert)
print("Receipt verification succeeded")
except Exception as e:
print("Receipt verification failed")
# Raise caught exception to look at the error stack
raise e
Da für den Überprüfungsprozess einige Kryptografie- und Hash-Primitive erforderlich sind, werden die folgenden Bibliotheken verwendet, um die Berechnung zu erleichtern.
- CCF-Python-Bibliothek: Das Modul bietet eine Reihe von Tools für die Belegüberprüfung.
- Python-Kryptografiebibliothek: Eine weit verbreitete Bibliothek, die verschiedene Kryptografiealgorithmen und Primitive enthält.
- hashlib-Modul: (Teil der Python-Standardbibliothek): Ein Modul, das eine allgemeine Schnittstelle für gängige Hashalgorithmen bereitstellt.
from ccf.receipt import verify, check_endorsements, root
from cryptography.x509 import load_pem_x509_certificate, Certificate
from hashlib import sha256
from typing import Dict, List, Any
Innerhalb der Funktion verify_receipt
wird überprüft, ob der angegebene Beleg gültig ist und alle erforderlichen Felder enthält.
# Check that all the fields are present in the receipt
assert "cert" in receipt
assert "leafComponents" in receipt
assert "claimsDigest" in receipt["leafComponents"]
assert "commitEvidence" in receipt["leafComponents"]
assert "writeSetDigest" in receipt["leafComponents"]
assert "proof" in receipt
assert "signature" in receipt
Wir initialisieren die Variablen, die im Rest des Programms verwendet werden.
# Set the variables
node_cert_pem = receipt["cert"]
claims_digest_hex = receipt["leafComponents"]["claimsDigest"]
commit_evidence_str = receipt["leafComponents"]["commitEvidence"]
write_set_digest_hex = receipt["leafComponents"]["writeSetDigest"]
proof_list = receipt["proof"]
service_endorsements_certs_pem = receipt.get("serviceEndorsements", [])
root_node_signature = receipt["signature"]
Wir können die PEM-Zertifikate für die Dienstidentität, den Signaturknoten und die Bestätigungszertifikate früherer Dienstidentitäten mithilfe der Kryptografiebibliothek laden.
# Load service and node PEM certificates
service_cert = load_pem_x509_certificate(service_cert_pem.encode())
node_cert = load_pem_x509_certificate(node_cert_pem.encode())
# Load service endorsements PEM certificates
service_endorsements_certs = [
load_pem_x509_certificate(pem.encode())
for pem in service_endorsements_certs_pem
]
Der erste Schritt des Überprüfungsprozesses besteht darin, den Digest des Blattknotens zu berechnen.
# Compute leaf of the Merkle Tree corresponding to our transaction
leaf_node_hex = compute_leaf_node(
claims_digest_hex, commit_evidence_str, write_set_digest_hex
)
Die Funktion compute_leaf_node
akzeptiert als Parameter die Blattkomponenten des Belegs (claimsDigest
, commitEvidence
und writeSetDigest
) und gibt den Blattknotenhash in hexadezimaler Form zurück.
Wie bereits beschrieben wird der Digest von commitEvidence
(mit der SHA-256-Funktion hashlib
) berechnet. Anschließend konvertieren wir sowohl writeSetDigest
als auch claimsDigest
in Bytearrays. Schließlich verketten wir die drei Arrays und verarbeiten das Ergebnis mithilfe der SHA256-Funktion.
def compute_leaf_node(
claims_digest_hex: str, commit_evidence_str: str, write_set_digest_hex: str
) -> str:
"""Function to compute the leaf node associated to a transaction
given its claims digest, commit evidence, and write set digest."""
# Digest commit evidence string
commit_evidence_digest = sha256(commit_evidence_str.encode()).digest()
# Convert write set digest to bytes
write_set_digest = bytes.fromhex(write_set_digest_hex)
# Convert claims digest to bytes
claims_digest = bytes.fromhex(claims_digest_hex)
# Create leaf node by hashing the concatenation of its three components
# as bytes objects in the following order:
# 1. write_set_digest
# 2. commit_evidence_digest
# 3. claims_digest
leaf_node_digest = sha256(
write_set_digest + commit_evidence_digest + claims_digest
).digest()
# Convert the result into a string of hexadecimal digits
return leaf_node_digest.hex()
Nachdem wir das Blatt berechnet haben, können wir den Stamm des Merkle-Baums berechnen.
# Compute root of the Merkle Tree
root_node = root(leaf_node_hex, proof_list)
Wir verwenden die Funktion root
, die als Teil der CCF-Python-Bibliothek bereitgestellt wird. Die Funktion verkettet das Ergebnis der vorherigen Iteration nacheinander mit einem neuen Element aus proof
, verarbeitet die Verkettung und wiederholt dann den Schritt für jedes Element in proof
mit dem zuvor berechneten Digest. Bei der Verkettung muss die Reihenfolge der Knoten im Merkle-Baum berücksichtigt werden, um sicherzustellen, dass der Stamm ordnungsgemäß neu berechnet wird.
def root(leaf: str, proof: List[dict]):
"""
Recompute root of Merkle tree from a leaf and a proof of the form:
[{"left": digest}, {"right": digest}, ...]
"""
current = bytes.fromhex(leaf)
for n in proof:
if "left" in n:
current = sha256(bytes.fromhex(n["left"]) + current).digest()
else:
current = sha256(current + bytes.fromhex(n["right"])).digest()
return current.hex()
Nach der Berechnung des Stammknotenhashs können wir die im Beleg enthaltene Signatur über den Stamm überprüfen, um uns zu vergewissern, dass die Signatur korrekt ist.
# Verify signature of the signing node over the root of the tree
verify(root_node, root_node_signature, node_cert)
Die CCF-Bibliothek stellt auf ähnliche Weise eine Funktion vom Typ verify
für diese Überprüfung bereit. Wir verwenden den öffentlichen ECDSA-Schlüssel des Signaturknotenzertifikats, um die Signatur über den Stamm des Baums zu überprüfen.
def verify(root: str, signature: str, cert: Certificate):
"""
Verify signature over root of Merkle Tree
"""
sig = base64.b64decode(signature)
pk = cert.public_key()
assert isinstance(pk, ec.EllipticCurvePublicKey)
pk.verify(
sig,
bytes.fromhex(root),
ec.ECDSA(utils.Prehashed(hashes.SHA256())),
)
Der letzte Schritt der Belegüberprüfung besteht darin, das Zertifikat zu überprüfen, das zum Signieren des Stamms des Merkle-Baums verwendet wurde.
# Verify node certificate is endorsed by the service certificates through endorsements
check_endorsements(node_cert, service_cert, service_endorsements_certs)
Ebenso können Sie das CCF-Hilfsprogramm check_endorsements
verwenden, um zu überprüfen, ob die Dienstidentität den signierenden Knoten unterstützt. Die Zertifikatkette kann sich aus früheren Dienstzertifikaten zusammensetzen. Daher müssen wir uns vergewissern, dass die Bestätigung transitiv angewendet wird, wenn serviceEndorsements
keine leere Liste ist.
def check_endorsement(endorsee: Certificate, endorser: Certificate):
"""
Check endorser has endorsed endorsee
"""
digest_algo = endorsee.signature_hash_algorithm
assert digest_algo
digester = hashes.Hash(digest_algo)
digester.update(endorsee.tbs_certificate_bytes)
digest = digester.finalize()
endorser_pk = endorser.public_key()
assert isinstance(endorser_pk, ec.EllipticCurvePublicKey)
endorser_pk.verify(
endorsee.signature, digest, ec.ECDSA(utils.Prehashed(digest_algo))
)
def check_endorsements(
node_cert: Certificate, service_cert: Certificate, endorsements: List[Certificate]
):
"""
Check a node certificate is endorsed by a service certificate, transitively through a list of endorsements.
"""
cert_i = node_cert
for endorsement in endorsements:
check_endorsement(cert_i, endorsement)
cert_i = endorsement
check_endorsement(cert_i, service_cert)
Alternativ können wir das Zertifikat auch mithilfe der OpenSSL-Bibliothek und einer ähnlichen Methode überprüfen.
from OpenSSL.crypto import (
X509,
X509Store,
X509StoreContext,
)
def verify_openssl_certificate(
node_cert: Certificate,
service_cert: Certificate,
service_endorsements_certs: List[Certificate],
) -> None:
"""Verify that the given node certificate is a valid OpenSSL certificate through
the service certificate and a list of endorsements certificates."""
store = X509Store()
# pyopenssl does not support X509_V_FLAG_NO_CHECK_TIME. For recovery of expired
# services and historical receipts, we want to ignore the validity time. 0x200000
# is the bitmask for this option in more recent versions of OpenSSL.
X509_V_FLAG_NO_CHECK_TIME = 0x200000
store.set_flags(X509_V_FLAG_NO_CHECK_TIME)
# Add service certificate to the X.509 store
store.add_cert(X509.from_cryptography(service_cert))
# Prepare X.509 endorsement certificates
certs_chain = [X509.from_cryptography(cert) for cert in service_endorsements_certs]
# Prepare X.509 node certificate
node_cert_pem = X509.from_cryptography(node_cert)
# Create X.509 store context and verify its certificate
ctx = X509StoreContext(store, node_cert_pem, certs_chain)
ctx.verify_certificate()
Beispielcode
Der vollständige Beispielcode, der im Codedurchlauf verwendet wird, wird bereitgestellt.
Hauptprogramm
import json
# Use the receipt and the service identity to verify the receipt content
with open("network_certificate.pem", "r") as service_certificate_file, open(
"receipt.json", "r"
) as receipt_file:
# Load relevant files content
receipt = json.loads(receipt_file.read())["receipt"]
service_certificate_cert = service_certificate_file.read()
try:
verify_receipt(receipt, service_certificate_cert)
print("Receipt verification succeeded")
except Exception as e:
print("Receipt verification failed")
# Raise caught exception to look at the error stack
raise e
Belegüberprüfung
from cryptography.x509 import load_pem_x509_certificate, Certificate
from hashlib import sha256
from typing import Dict, List, Any
from OpenSSL.crypto import (
X509,
X509Store,
X509StoreContext,
)
from ccf.receipt import root, verify, check_endorsements
def verify_receipt(receipt: Dict[str, Any], service_cert_pem: str) -> None:
"""Function to verify that a given write transaction receipt is valid based
on its content and the service certificate.
Throws an exception if the verification fails."""
# Check that all the fields are present in the receipt
assert "cert" in receipt
assert "leafComponents" in receipt
assert "claimsDigest" in receipt["leafComponents"]
assert "commitEvidence" in receipt["leafComponents"]
assert "writeSetDigest" in receipt["leafComponents"]
assert "proof" in receipt
assert "signature" in receipt
# Set the variables
node_cert_pem = receipt["cert"]
claims_digest_hex = receipt["leafComponents"]["claimsDigest"]
commit_evidence_str = receipt["leafComponents"]["commitEvidence"]
write_set_digest_hex = receipt["leafComponents"]["writeSetDigest"]
proof_list = receipt["proof"]
service_endorsements_certs_pem = receipt.get("serviceEndorsements", [])
root_node_signature = receipt["signature"]
# Load service and node PEM certificates
service_cert = load_pem_x509_certificate(service_cert_pem.encode())
node_cert = load_pem_x509_certificate(node_cert_pem.encode())
# Load service endorsements PEM certificates
service_endorsements_certs = [
load_pem_x509_certificate(pem.encode())
for pem in service_endorsements_certs_pem
]
# Compute leaf of the Merkle Tree
leaf_node_hex = compute_leaf_node(
claims_digest_hex, commit_evidence_str, write_set_digest_hex
)
# Compute root of the Merkle Tree
root_node = root(leaf_node_hex, proof_list)
# Verify signature of the signing node over the root of the tree
verify(root_node, root_node_signature, node_cert)
# Verify node certificate is endorsed by the service certificates through endorsements
check_endorsements(node_cert, service_cert, service_endorsements_certs)
# Alternative: Verify node certificate is endorsed by the service certificates through endorsements
verify_openssl_certificate(node_cert, service_cert, service_endorsements_certs)
def compute_leaf_node(
claims_digest_hex: str, commit_evidence_str: str, write_set_digest_hex: str
) -> str:
"""Function to compute the leaf node associated to a transaction
given its claims digest, commit evidence, and write set digest."""
# Digest commit evidence string
commit_evidence_digest = sha256(commit_evidence_str.encode()).digest()
# Convert write set digest to bytes
write_set_digest = bytes.fromhex(write_set_digest_hex)
# Convert claims digest to bytes
claims_digest = bytes.fromhex(claims_digest_hex)
# Create leaf node by hashing the concatenation of its three components
# as bytes objects in the following order:
# 1. write_set_digest
# 2. commit_evidence_digest
# 3. claims_digest
leaf_node_digest = sha256(
write_set_digest + commit_evidence_digest + claims_digest
).digest()
# Convert the result into a string of hexadecimal digits
return leaf_node_digest.hex()
def verify_openssl_certificate(
node_cert: Certificate,
service_cert: Certificate,
service_endorsements_certs: List[Certificate],
) -> None:
"""Verify that the given node certificate is a valid OpenSSL certificate through
the service certificate and a list of endorsements certificates."""
store = X509Store()
# pyopenssl does not support X509_V_FLAG_NO_CHECK_TIME. For recovery of expired
# services and historical receipts, we want to ignore the validity time. 0x200000
# is the bitmask for this option in more recent versions of OpenSSL.
X509_V_FLAG_NO_CHECK_TIME = 0x200000
store.set_flags(X509_V_FLAG_NO_CHECK_TIME)
# Add service certificate to the X.509 store
store.add_cert(X509.from_cryptography(service_cert))
# Prepare X.509 endorsement certificates
certs_chain = [X509.from_cryptography(cert) for cert in service_endorsements_certs]
# Prepare X.509 node certificate
node_cert_pem = X509.from_cryptography(node_cert)
# Create X.509 store context and verify its certificate
ctx = X509StoreContext(store, node_cert_pem, certs_chain)
ctx.verify_certificate()