Ü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:

  1. writeSetDigest
  2. SHA-256-Digest von commitEvidence
  3. 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 Typ left 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 Typ right 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:

  1. Decodieren der Base64-Zeichenfolge signature in ein Bytearray
  2. Extrahieren Sie des öffentlichen ECDSA-Schlüssels aus dem Signaturknotenzertifikat cert
  3. 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:

Ü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() 

Nächste Schritte