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
:
writeSetDigest
- SHA-256 digest of
commitEvidence
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 left
right
).
- Pokud je
left
klíč aktuálního prvkuproof
, 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
proof
right
, 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:
- Dekódujte řetězec
signature
base64 do pole bajtů. - Extrahujte veřejný klíč ECDSA z certifikátu
cert
podpisového uzlu . - 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í
- Glosář CCF
- Merkle Tree
- Kryptografie
- Certifikáty
- Deklarace identity aplikací
- Deklarace identity definované uživatelem v účtech
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_name
receipt_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 ( claimsDigest
tj. , commitEvidence
a ) a writeSetDigest
vrá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ázkový kód
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()