Az Azure Confidential Ledger írási tranzakcióinak nyugtáinak ellenőrzése
Az Azure Confidential Ledger írási tranzakciójának nyugtája egy kriptográfiai Merkle-bizonyíték arra vonatkozóan, hogy a megfelelő írási tranzakciót globálisan lekötötte a CCF-hálózat. Az Azure Confidential Ledger felhasználói bármikor megkaphatják a nyugtát egy lekötött írási tranzakcióról annak ellenőrzéséhez, hogy a megfelelő írási művelet sikeresen fel lett-e jegyezve a nem módosítható főkönyvbe.
Az Azure Confidential Ledger írási tranzakcióinak nyugtáival kapcsolatos további információkért tekintse meg a dedikált cikket.
Visszaigazolás-ellenőrzési lépések
Az írási tranzakció nyugtái az alábbi alszakaszokban ismertetett lépések meghatározott halmazát követve ellenőrizhetők. Ugyanezeket a lépéseket a CCF dokumentációja ismerteti.
Levélcsomópont számítása
Az első lépés a lekötött tranzakciónak megfelelő levélcsomópont SHA-256 kivonatának kiszámítása a Merkle-fában. A levélcsomópont a következő mezők rendezett összefűzéséből áll, amelyek egy Azure Confidential Ledger-visszaigazolásban találhatók, a következő alatt leafComponents
:
writeSetDigest
- SHA-256 kivonat
commitEvidence
claimsDigest
Mezők
Ezeket az értékeket bájtok tömbjeként kell összefűzni: mindkettőt writeSetDigest
claimsDigest
hexadecimális számjegyek sztringjeiből bájttömbökké kell konvertálni; másrészt commitEvidence
az SHA-256 kivonatfüggvény UTF-8 kódolt commitEvidence
sztringre való alkalmazásával is lekérhető.
Hasonlóképpen a levélcsomópont kivonatolási kivonata is kiszámítható az SHA-256 kivonatfüggvény alkalmazásával az eredményül kapott bájtok összefűzésével.
Gyökércsomópont számítása
A második lépés a Merkle-fa gyökerének SHA-256 kivonatának kiszámítása a tranzakció véglegesítésekor. A számítás az előző iteráció eredményének iteratív összefűzésével és kivonatolásával történik (az előző lépésben kiszámított levélcsomópont kivonatától kezdve) a megrendelt csomópontok nyugtamezőjében proof
megadott kivonataival. A proof
lista rendezett listaként van megadva, és elemeit a megadott sorrendben kell megszűrni.
Az összefűzést a bájtok ábrázolására kell elvégezni a mezőben megadott objektumokban megadott proof
relatív sorrend alapján (vagy right
).left
- Ha az aktuális elem
proof
kulcsa azleft
, akkor az előző iteráció eredményét hozzá kell fűzni az aktuális elem értékéhez. - Ha az aktuális elem
proof
kulcsa azright
, akkor az előző iteráció eredményét az aktuális elem értékére kell elővenni.
Minden összefűzés után az SHA-256 függvényt kell alkalmazni a következő iteráció bemenetének lekéréséhez. Ez a folyamat a Merkle Tree-adatstruktúra gyökércsomópontjának kiszámításához szükséges szokásos lépéseket követi, figyelembe véve a számításhoz szükséges csomópontokat.
Aláírás ellenőrzése a gyökércsomóponton
A harmadik lépés annak ellenőrzése, hogy a gyökércsomópont kivonatán létrehozott titkosítási aláírás érvényes-e az aláíró csomópont tanúsítványával a visszaigazolásban. Az ellenőrzési folyamat a digitális aláírás ellenőrzésének szabványos lépéseit követi az elliptikus görbe digitális aláírási algoritmusával (ECDSA) aláírt üzenetek esetében. Pontosabban a lépések a következők:
- A base64 sztringet
signature
bájtok tömbjeként dekódolja. - Bontsa ki az ECDSA nyilvános kulcsát az aláíró csomópont tanúsítványából
cert
. - Ellenőrizze, hogy a Merkle-fa gyökerén (az előző alszakasz utasításai alapján kiszámított) aláírás hiteles-e az előző lépésből kinyert nyilvános kulcs használatával. Ez a lépés hatékonyan megfelel az ECDSA-t használó szabványos digitális aláírás-ellenőrzési folyamatnak. Számos kódtár található a legnépszerűbb programozási nyelvekben, amelyek lehetővé teszik az ECDSA-aláírás ellenőrzését nyilvános kulcsú tanúsítvány használatával bizonyos adatokon keresztül (például a Python titkosítási könyvtárában ).
Az aláíró csomópont tanúsítványának ellenőrzése
Az előző lépésen kívül azt is ellenőriznie kell, hogy az aláíró csomópont tanúsítványát az aktuális főkönyvtanúsítvány támogatja-e (azaz aláírta). Ez a lépés nem függ az előző három lépéstől, és a többitől függetlenül végrehajtható.
Lehetséges, hogy a nyugtát kiállító aktuális szolgáltatásidentitás eltér az aláíró csomópontot jóváhagyótól (például tanúsítványmegújítás miatt). Ebben az esetben a tanúsítványláncot az aláíró csomópont tanúsítványától (azaz cert
a nyugta mezőjétől) a megbízható főtanúsítványig (azaz az aktuális szolgáltatásidentitás-tanúsítványig) kell ellenőrizni más korábbi szolgáltatásidentitásokon keresztül (azaz a serviceEndorsements
nyugta listamezőjétől). A serviceEndorsements
lista rendezett listaként van megadva a legrégebbitől a legújabb szolgáltatásazonosítóig.
A tanúsítványhitelesítést a teljes láncra vonatkozóan ellenőrizni kell, és pontosan ugyanazt a digitális aláírás-ellenőrzési folyamatot követi, amelyet az előző alszakaszban vázoltak fel. Vannak népszerű nyílt forráskódú titkosítási kódtárak (például OpenSSL), amelyek általában a tanúsítvány-jóváhagyási lépés végrehajtására használhatók.
Alkalmazásjogcímek kivonatának ellenőrzése
Opcionális lépésként abban az esetben, ha az alkalmazásjogcímek egy nyugtához vannak csatolva, a jogcímek kivonatát kiszámíthatja a közzétett jogcímekből (egy adott algoritmust követve), és ellenőrizheti, hogy az kivonat megegyezik-e a claimsDigest
nyugta hasznos adataiban található adatokkal. A közzétett jogcímobjektumokból származó kivonat kiszámításához iterálnia kell a listában szereplő összes alkalmazásjogcím-objektumot, és ellenőriznie kell a mezőjét kind
.
Ha a jogcím objektuma fajta LedgerEntry
, akkor a jogcím főkönyvgyűjtemény-azonosítóját (collectionId
) és tartalmát (contents
) ki kell nyerni, és a jogcímobjektumban megadott titkos kulcs (secretKey
) használatával kell kiszámítani a HMAC-kivonatokat. Ezt a két kivonatot a rendszer összefűzi, és kiszámítja az összefűzés SHA-256 kivonatát. A protokoll (protocol
) és az eredményül kapott jogcímadatok kivonata összefűzve lesz, és az összefűzés egy másik SHA-256 kivonata lesz kiszámítva a végső kivonat lekéréséhez.
Ha a jogcím objektuma fajta ClaimDigest
, a jogcím-kivonatot (value
) ki kell nyerni, összefűzve a protokolllal (protocol
), és az összefűzés SHA-256 kivonatát ki kell számítani a végső kivonat lekéréséhez.
Az egyes jogcím-kivonatok számítása után össze kell fűzni az összes számítási kivonatot az egyes alkalmazáskérelem-objektumokból (ugyanabban a sorrendben, mint a visszaigazolásban). Az összefűzést ezután a feldolgozott jogcímek számával kell előre felerősíteni. Az előző összefűzés SHA-256 kivonata létrehozza a végleges jogcím-kivonatot, amelynek meg kell egyeznie a claimsDigest
nyugtaobjektumban lévő jelenel.
További erőforrások
Az Azure Confidential Ledger írási tranzakció visszaigazolásának tartalmával és az egyes mezők magyarázatával kapcsolatos további információkért tekintse meg a dedikált cikket. A CCF dokumentációja további információt is tartalmaz a nyugta-ellenőrzésről és más kapcsolódó erőforrásokról az alábbi hivatkozásokon:
- Visszaigazolás ellenőrzése
- CCF-szószedet
- Merkle fa
- Titkosítás
- Tanúsítványok
- Alkalmazásjogcímek
- Felhasználó által definiált jogcímek a nyugtákban
Tranzakciós visszaigazolások írásának ellenőrzése
Nyugta-ellenőrző segédprogramok
A Pythonhoz készült Azure Confidential Ledger ügyfélkódtár segédprogramfüggvényeket biztosít az írási tranzakciók nyugtáinak ellenőrzéséhez és a jogcímek kivonatának kiszámításához az alkalmazásjogcímek listájából. Az adatsík SDK és a nyugtaspecifikus segédprogramok használatáról ebben a szakaszban és ebben a mintakódban talál további információt.
Telepítés és előfeltételek
Referenciaként a Pythonban mintakódot biztosítunk az Azure Confidential Ledger írási tranzakcióinak nyugtáinak teljes ellenőrzéséhez az előző szakaszban ismertetett lépések követésével.
A teljes ellenőrzési algoritmus futtatásához szükség van az aktuális szolgáltatás hálózati tanúsítványára és egy futó bizalmas könyvelési erőforrásból származó írási tranzakció nyugtára. Ebből a cikkből megtudhatja, hogyan kér le egy írási tranzakció nyugtát és a szolgáltatástanúsítványt egy bizalmas főkönyvi példányból.
Kódbemutató
Az alábbi kód a szükséges objektumok inicializálására és a visszaigazolás-ellenőrzési algoritmus futtatására használható. A teljes ellenőrzési algoritmus futtatásához külön segédprogram (verify_receipt
) használható, amely a válaszban GET_RECEIPT
a mező tartalmát receipt
szótárként, a szolgáltatástanúsítványt pedig egyszerű sztringként fogadja el. A függvény kivételt okoz, ha a nyugta érvénytelen, vagy hiba történt a feldolgozás során.
Feltételezzük, hogy a nyugta és a szolgáltatástanúsítvány is betölthető fájlokból. Mindenképpen frissítse az állandókat és receipt_file_name
az service_certificate_file_name
állandókat az ellenőrizni kívánt szolgáltatástanúsítvány és nyugta megfelelő fájlneveivel.
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
Mivel az ellenőrzési folyamat titkosítási és kivonatolási primitíveket igényel, a következő kódtárak segítik a számítást.
- A CCF Python-kódtár: a modul számos eszközt biztosít a nyugta-ellenőrzéshez.
- Python-titkosítási kódtár: széles körben használt kódtár, amely különböző titkosítási algoritmusokat és primitíveket tartalmaz.
- A hashlib modul, amely a Python standard kódtár része: egy modul, amely közös felületet biztosít a népszerű kivonatoló algoritmusokhoz.
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
A függvényen verify_receipt
belül ellenőrizzük, hogy a megadott visszaigazolás érvényes-e, és tartalmazza-e az összes szükséges mezőt.
# 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
Inicializáljuk a program többi részében használni kívánt változókat.
# 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"]
A titkosítási kódtár használatával betölthetjük a szolgáltatásidentitás PEM-tanúsítványait, az aláíró csomópontot és a korábbi szolgáltatásidentitások jóváhagyási tanúsítványait.
# 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
]
Az ellenőrzési folyamat első lépése a levélcsomópont kivonatának kiszámítása.
# 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
)
A compute_leaf_node
függvény paraméterként fogadja el a nyugta levélösszetevőit (a claimsDigest
, a commitEvidence
és a writeSetDigest
) és hexadecimális formában adja vissza a levélcsomópont kivonatát.
A korábban részletezett módon kiszámítjuk a kivonatot commitEvidence
(az SHA-256 hashlib
függvény használatával). Ezt követően bájtok tömbjeivé alakítjuk át mindkettőt writeSetDigest
claimsDigest
. Végül összefűzzük a három tömböt, és az eredményt az SHA256 függvénnyel emésztjük fel.
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()
A levél számítása után kiszámíthatjuk a Merkle fa gyökerét.
# Compute root of the Merkle Tree
root_node = root(leaf_node_hex, proof_list)
A CCF Python-kódtár részeként megadott függvényt root
használjuk. A függvény egymás után összefűzi az előző iteráció eredményét egy új elemmel proof
, megemészti az összefűzést, majd megismétli a korábban kiszámított kivonat minden elemének proof
lépését. Az összefűzésnek tiszteletben kell tartania a Merkle-fában lévő csomópontok sorrendjét, hogy a gyökér megfelelően legyen újrafordítva.
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()
A gyökércsomópont kivonatának kiszámítása után ellenőrizheti a visszaigazolásban található aláírást a gyökéren keresztül, hogy ellenőrizze, hogy az aláírás helyes-e.
# Verify signature of the signing node over the root of the tree
verify(root_node, root_node_signature, node_cert)
Hasonlóképpen a CCF-kódtár egy függvényt verify
is biztosít az ellenőrzés elvégzéséhez. Az aláíró csomópont tanúsítványának ECDSA nyilvános kulcsával ellenőrizzük az aláírást a fa gyökerén.
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())),
)
A visszaigazolás ellenőrzésének utolsó lépése a Merkle-fa gyökerének aláírásához használt tanúsítvány érvényesítése.
# Verify node certificate is endorsed by the service certificates through endorsements
check_endorsements(node_cert, service_cert, service_endorsements_certs)
Hasonlóképpen a CCF segédprogrammal check_endorsements
ellenőrizheti, hogy a szolgáltatásidentitás támogatja-e az aláíró csomópontot. A tanúsítványlánc korábbi szolgáltatástanúsítványokból állhat, ezért ellenőrizni kell, hogy az ellenőrzés tranzitív módon van-e alkalmazva, ha serviceEndorsements
nem üres lista.
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)
Másik lehetőségként a tanúsítványt az OpenSSL-kódtár hasonló módszerrel történő használatával is érvényesíthetjük.
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()
Mintakód
A kódbemutatóban használt teljes mintakód meg van adva.
Fő 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
Nyugta ellenőrzése
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()