Ověření potvrzení o transakcích zápisu důvěrných registrů Azure

Potvrzení transakce zápisu důvěrného registru Azure představuje kryptografický důkaz Merkle, že odpovídající transakce zápisu byla globálně potvrzena sítí CCF. Uživatelé důvěrného registru Azure můžou získat potvrzení o potvrzené transakci zápisu v libovolném okamžiku, aby ověřili, že odpovídající operace zápisu byla úspěšně zaznamenána do neměnného registru.

Další informace o účtech transakcí zápisu důvěrného registru Azure najdete v vyhrazeném článku.

Kroky ověření potvrzení

Potvrzení o zápisu transakce lze ověřit podle konkrétní sady kroků popsaných v následujících pododdílech. Stejné kroky jsou popsané v dokumentaci CCF.

Výpočet uzlu typu list

Prvním krokem je výpočet hodnoty hash SHA-256 uzlu typu list ve stromu Merkle odpovídající potvrzené transakci. Uzel typu list se skládá z uspořádaného zřetězení následujících polí, která najdete v potvrzení o důvěrném registru Azure v části leafComponents:

  1. writeSetDigest
  2. SHA-256 digest of commitEvidence
  3. claimsDigest Pole

Tyto hodnoty musí být zřetězeny jako pole bajtů: obě writeSetDigest a claimsDigest je potřeba je převést z řetězců šestnáctkových číslic na pole bajtů. Na druhou stranu je možné hodnotu hash commitEvidence (jako pole bajtů) získat použitím funkce hash SHA-256 u zakódovaného commitEvidence řetězce UTF-8.

Podobně lze hodnotu hash uzlu typu list vypočítat použitím funkce hash SHA-256 u výsledného zřetězení výsledných bajtů.

Výpočet kořenového uzlu

Druhým krokem je výpočet hodnoty hash SHA-256 kořene stromu Merkle v době, kdy byla transakce potvrzena. Výpočet se provádí iterativním zřetězením a hashováním výsledku předchozí iterace (počínaje hodnotou hash uzlu typu list vypočítanou v předchozím kroku) s hodnotami hash seřazených uzlů zadanými v proof poli potvrzení. Seznam proof je poskytován jako seřazený seznam a jeho prvky musí být iterated v daném pořadí.

Zřetězení musí být provedeno u reprezentace bajtů s ohledem na relativní pořadí uvedené v objektech uvedených v proof poli (nebo leftright).

  • Pokud je leftklíč aktuálního prvku proof , pak by měl být výsledek předchozí iterace připojen k aktuální hodnotě prvku.
  • Pokud je klíč aktuálního prvku proofright, výsledek předchozí iterace by měl být předpendován na aktuální hodnotu prvku.

Po každé zřetězení je potřeba použít funkci SHA-256, aby bylo možné získat vstup pro další iteraci. Tento proces se řídí standardními kroky pro výpočet kořenového uzlu datové struktury Merkle Tree vzhledem k požadovaným uzlům pro výpočet.

Ověření podpisu přes kořenový uzel

Třetím krokem je ověření, že kryptografický podpis vytvořený přes hodnotu hash kořenového uzlu je platný pomocí certifikátu podpisového uzlu v potvrzení. Proces ověření se řídí standardními kroky pro ověření digitálního podpisu u zpráv podepsaných pomocí algoritmu ECDSA (Elliptic Curve Digital Signature Algorithm). Konkrétně se jedná o následující kroky:

  1. Dekódujte řetězec signature base64 do pole bajtů.
  2. Extrahujte veřejný klíč ECDSA z certifikátu certpodpisového uzlu .
  3. Pomocí extrahovaného veřejného klíče z předchozího kroku ověřte, že podpis přes kořen merkleového stromu (vypočítaný pomocí pokynů v předchozí pododdílu) je ověřený. Tento krok efektivně odpovídá standardnímu procesu ověřování digitálního podpisu pomocí ECDSA. V nejoblíbenějších programovacích jazycích je mnoho knihoven, které umožňují ověření podpisu ECDSA pomocí certifikátu veřejného klíče u některých dat (například kryptografické knihovny pro Python).

Ověření potvrzení certifikátu podpisového uzlu

Kromě předchozího kroku je také nutné ověřit, že certifikát podpisového uzlu je schválen (tj. podepsaný) aktuálním certifikátem registru. Tento krok nezávisí na ostatních třech předchozích krocích a dá se provádět nezávisle na ostatních krocích.

Je možné, že aktuální identita služby, která vydala potvrzení, se liší od identity, která schválila podpisový uzel (například kvůli prodloužení platnosti certifikátu). V tomto případě je nutné ověřit řetěz certifikátů důvěryhodnosti z certifikátu podpisového uzlu (to znamená cert pole v potvrzení) až po důvěryhodnou kořenovou certifikační autoritu (CA) (to znamená aktuální certifikát identity služby) prostřednictvím jiných předchozích identit služby (to znamená serviceEndorsements pole seznamu v potvrzení). Seznam serviceEndorsements se poskytuje jako seřazený seznam od nejstaršího po nejnovější identitu služby.

Potvrzení certifikátu musí být ověřeno pro celý řetězec a postupuje přesně podle stejného procesu ověření digitálního podpisu popsaného v předchozím pododdílu. K dispozici jsou oblíbené opensourcové kryptografické knihovny (například OpenSSL), které se obvykle používají k provedení kroku ověření certifikátu.

Ověření digestu deklarací deklarací identity aplikací

V případě, že jsou deklarace identity aplikace připojené k potvrzení, je možné vypočítat hodnotu hash deklarací identity od vystavených deklarací (podle konkrétního algoritmu) a ověřit, že hodnota hash odpovídá claimsDigest hodnotě hash obsažené v datové části příjmu. K výpočtu hodnoty hash z vystavených objektů deklarací identity je nutné iterovat každý objekt deklarace identity aplikace v seznamu a kontrolovat jeho kind pole.

Pokud je objekt deklarace identity druhem LedgerEntry, měl by se extrahovat ID kolekce registru (collectionId) a obsah (contents) deklarace identity a použít k výpočtu hodnot hash HMAC pomocí tajného klíče (secretKey) zadaného v objektu deklarace identity. Tyto dva hodnoty hash se pak zřetědí a vypočítá se hodnota hash SHA-256 zřetězení. Protokol (protocol) a výsledná hodnota hash dat deklarací identity se pak zřetězí a vypočítá se další hodnota hash SHA-256 zřetězení, aby získala konečnou hodnotu hash.

Pokud je objekt deklarace identity druh ClaimDigest, hodnota hash deklarace identity (value) by se měla extrahovat, zřetězena s protokolem (protocol) a hodnota hash SHA-256 zřetězení se vypočítá, aby získala konečnou hodnotu hash.

Po výpočtu hodnoty hash jednotlivých deklarací identity je nutné zřetězení všech vypočítaných přehledů z každého objektu deklarace identity aplikace (ve stejném pořadí, v jakém jsou uvedeny v potvrzení). Zřetězení by se pak mělo předcházet počtem zpracovaných deklarací identity. Hodnota hash SHA-256 předchozího zřetězení vytvoří konečnou hodnotu hash deklarací identity, která by se měla shodovat s současnou claimsDigest hodnotou v objektu příjmu.

Další materiály

Další informace o obsahu potvrzení transakce důvěrného registru Azure a vysvětlení jednotlivých polí najdete ve vyhrazeném článku. Dokumentace CCF obsahuje také další informace o ověření příjmu a dalších souvisejících zdrojích na následujících odkazech:

Ověření potvrzení o zápisu transakcí

Nástroje pro ověření účtenek

Klientská knihovna Azure Confidential Ledger pro Python poskytuje pomocné funkce k ověření potvrzení transakcí zápisu a výpočtu hodnoty hash deklarací identity ze seznamu deklarací identity aplikací. Další informace o tom, jak používat sadu SDK roviny dat a nástroje specifické pro příjem, najdete v této části a tomto ukázkovém kódu.

Nastavení a požadavky

Pro referenční účely poskytujeme vzorový kód v Pythonu, který plně ověří potvrzení transakcí zápisu důvěrného registru Azure podle kroků uvedených v předchozí části.

Pokud chcete spustit úplný ověřovací algoritmus, vyžaduje se aktuální síťový certifikát služby a potvrzení o zápisu z běžícího prostředku důvěrné knihy. Podrobnosti o tom, jak načíst potvrzení o transakci zápisu a certifikát služby z instance důvěrného registru, najdete v tomto článku.

Průvodce kódem

Následující kód lze použít k inicializaci požadovaných objektů a spuštění ověřovacího algoritmu potvrzení. Samostatný nástroj (verify_receipt) slouží ke spuštění úplného ověřovacího receipt algoritmu a přijímá obsah pole v GET_RECEIPT odpovědi jako slovník a certifikát služby jako jednoduchý řetězec. Funkce vyvolá výjimku, pokud potvrzení není platné nebo pokud během zpracování došlo k nějaké chybě.

Předpokládá se, že potvrzení i certifikát služby je možné načíst ze souborů. Nezapomeňte aktualizovat jak service_certificate_file_namereceipt_file_name konstanty, tak názvy příslušných souborů certifikátu služby a potvrzení, které chcete ověřit.

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 

Vzhledem k tomu, že proces ověřování vyžaduje několik primitiv kryptografických a hashovacích hodnot, používají se k usnadnění výpočtu následující knihovny.

  • Knihovna CCF Python: Modul poskytuje sadu nástrojů pro ověření potvrzení.
  • Kryptografická knihovna Pythonu : široce používaná knihovna, která zahrnuje různé kryptografické algoritmy a primitivy.
  • Modul hashlib, který je součástí standardní knihovny Pythonu: modul, který poskytuje společné rozhraní pro oblíbené algoritmy hash.
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 

verify_receipt Uvnitř funkce zkontrolujeme platnost daného potvrzení a obsahuje všechna povinná pole.

# 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 

Inicializujeme proměnné, které se budou používat ve zbytku programu.

# 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"] 

Pomocí kryptografické knihovny můžeme načíst certifikáty PEM pro identitu služby, podpisový uzel a ověřovací certifikáty z předchozích identit služeb.

# 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 
] 

Prvním krokem procesu ověření je výpočet hodnot hash uzlu typu list.

# 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 
)

Funkce compute_leaf_node přijímá jako parametry součásti listu účtenky ( claimsDigesttj. , commitEvidencea ) a writeSetDigestvrátí hodnotu hash uzlu typu list v šestnáctkové podobě.

Jak jsme si popsali dříve, vypočítáme hodnotu hash commitEvidence (pomocí funkce SHA-256 hashlib ). Pak převedeme obě writeSetDigest a claimsDigest na pole bajtů. Nakonec zřetědíme tři pole a výsledek použijeme funkci SHA256.

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

Po výpočtu listu můžeme vypočítat kořen merkleového stromu.

# Compute root of the Merkle Tree 
root_node = root(leaf_node_hex, proof_list) 

Funkci root poskytovanou jako součást knihovny CCF Python používáme. Funkce postupně zřetězí výsledek předchozí iterace s novým prvkem z proof, digests zřetězení a poté zopakuje krok pro každý prvek v proof dříve vypočítané hodnotě digest. Zřetězení musí respektovat pořadí uzlů ve stromu Merkle, aby se zajistilo, že se kořen správně přepočítá.

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

Po výpočtu hodnoty hash kořenového uzlu můžeme ověřit podpis obsažený v potvrzení v kořenovém adresáři a ověřit správnost podpisu.

# Verify signature of the signing node over the root of the tree 
verify(root_node, root_node_signature, node_cert) 

Podobně knihovna CCF poskytuje funkci verify pro toto ověření. K ověření podpisu ve stromu používáme veřejný klíč ECDSA certifikátu podpisového uzlu.

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

Posledním krokem ověření potvrzení je ověření certifikátu, který byl použit k podepsání kořene stromu Merkle.

# Verify node certificate is endorsed by the service certificates through endorsements 
check_endorsements(node_cert, service_cert, service_endorsements_certs) 

Podobně můžeme pomocí nástroje check_endorsements CCF ověřit, že identita služby doporučuje podpisový uzel. Řetěz certifikátů by se mohl skládat z předchozích certifikátů služby, proto bychom měli ověřit, jestli se doporučení používá tranzitivně, pokud serviceEndorsements není prázdný seznam.

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) 

Jako alternativu bychom také mohli certifikát ověřit pomocí knihovny OpenSSL pomocí podobné metody.

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

Ukázka kódu

Poskytuje se úplný vzorový kód použitý v názorném postupu kódu.

Hlavní program

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 

Ověření potvrzení

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

Další kroky