Weryfikowanie potwierdzeń transakcji zapisu poufnych rejestru platformy Azure
Potwierdzenie zapisu poufnego rejestru platformy Azure reprezentuje kryptograficzny dowód Merkle, że odpowiednia transakcja zapisu została globalnie zatwierdzona przez sieć CCF. Użytkownicy rejestru poufnego platformy Azure mogą uzyskać potwierdzenie transakcji zapisu zatwierdzonego w dowolnym momencie, aby sprawdzić, czy odpowiednia operacja zapisu została pomyślnie zarejestrowana w niezmienialnym rejestrze.
Aby uzyskać więcej informacji na temat potwierdzeń dotyczących zapisu transakcji poufnych rejestru platformy Azure, zobacz dedykowany artykuł.
Kroki weryfikacji potwierdzenia
Potwierdzenie transakcji zapisu można zweryfikować zgodnie z określonym zestawem kroków opisanych w poniższych podsekcjach. Te same kroki opisano w dokumentacji programu CCF.
Obliczenia węzła liścia
Pierwszym krokiem jest obliczenie skrótu SHA-256 węzła liścia w drzewie Merkle odpowiadającym zatwierdzonej transakcji. Węzeł liścia składa się z uporządkowanego łączenia następujących pól, które można znaleźć w paragonie poufnej księgi platformy Azure w obszarze leafComponents
:
writeSetDigest
- Skrót SHA-256
commitEvidence
claimsDigest
Pola
Te wartości muszą być łączone jako tablice bajtów: oba writeSetDigest
claimsDigest
te wartości muszą zostać przekonwertowane z ciągów cyfr szesnastkowych na tablice bajtów. Z drugiej strony commitEvidence
skrót (jako tablica bajtów) można uzyskać, stosując funkcję skrótu SHA-256 w ciągu zakodowanym za pomocą zakodowanego commitEvidence
ciągu UTF-8.
Podobnie skrót węzła liścia można obliczyć, stosując funkcję skrótu SHA-256 w wyniku łączenia wynikowych bajtów.
Obliczanie węzła głównego
Drugim krokiem jest obliczenie skrótu SHA-256 katalogu głównego drzewa Merkle w momencie zatwierdzenia transakcji. Obliczenia są wykonywane przez iteracyjne łączenie i wyznaczanie wartości skrótu wyniku poprzedniej iteracji (począwszy od skrótu węzła liścia obliczonego w poprzednim kroku) przy użyciu skrótów uporządkowanych węzłów podanych w proof
polu potwierdzenia. Lista proof
jest dostarczana jako uporządkowana lista, a jej elementy muszą być iterated w podanej kolejności.
Łączenie należy wykonać na reprezentacji bajtów w odniesieniu do względnej kolejności wskazanej w obiektach podanych w proof
polu ( left
lub right
).
- Jeśli klucz bieżącego elementu w
proof
pliku toleft
, wynik poprzedniej iteracji powinien zostać dołączony do bieżącej wartości elementu. - Jeśli klucz bieżącego elementu w
proof
elemencie toright
, wynik poprzedniej iteracji powinien być poprzedzany wartością bieżącego elementu.
Po każdym skontenacji należy zastosować funkcję SHA-256, aby uzyskać dane wejściowe dla następnej iteracji. Ten proces jest zgodny ze standardowymi krokami, aby obliczyć węzeł główny struktury danych Merkle Tree , biorąc pod uwagę wymagane węzły do obliczeń.
Weryfikowanie podpisu za pośrednictwem węzła głównego
Trzecim krokiem jest sprawdzenie, czy sygnatura kryptograficzna utworzona za pośrednictwem skrótu węzła głównego jest prawidłowa przy użyciu certyfikatu węzła podpisywania w potwierdzeniu. Proces weryfikacji jest zgodny ze standardowymi krokami weryfikacji podpisu cyfrowego dla komunikatów podpisanych przy użyciu algorytmu podpisu cyfrowego Elliptic Curve (ECDSA). W szczególności kroki są następujące:
- Zdekoduj ciąg
signature
base64 do tablicy bajtów. - Wyodrębnij klucz publiczny ECDSA z certyfikatu
cert
węzła podpisywania . - Sprawdź, czy podpis w katalogu głównym drzewa Merkle (obliczony przy użyciu instrukcji w poprzedniej podsekcji) jest uwierzytelniony przy użyciu wyodrębnionego klucza publicznego z poprzedniego kroku. Ten krok skutecznie odpowiada standardowemu procesowi weryfikacji podpisu cyfrowego przy użyciu ecDSA. Istnieje wiele bibliotek w najpopularniejszych językach programowania, które umożliwiają weryfikowanie podpisu ECDSA przy użyciu certyfikatu klucza publicznego za pośrednictwem niektórych danych (na przykład biblioteki kryptografii języka Python).
Weryfikowanie poręczenia certyfikatu węzła podpisywania
Oprócz poprzedniego kroku wymagane jest również sprawdzenie, czy certyfikat węzła podpisywania jest zatwierdzony (tj. podpisany) przez bieżący certyfikat rejestru. Ten krok nie zależy od pozostałych trzech poprzednich kroków i może być wykonywany niezależnie od innych.
Istnieje możliwość, że bieżąca tożsamość usługi, która wystawiła potwierdzenie, różni się od tożsamości, która zatwierdziła węzeł podpisywania (na przykład ze względu na odnowienie certyfikatu). W takim przypadku wymagane jest zweryfikowanie łańcucha zaufania certyfikatów z certyfikatu węzła podpisywania (czyli cert
pola paragonu) do zaufanego głównego urzędu certyfikacji (ca) (czyli bieżącego certyfikatu tożsamości usługi) za pośrednictwem innych poprzednich tożsamości usługi (czyli serviceEndorsements
pola listy w paragonie). Lista serviceEndorsements
jest udostępniana jako uporządkowana lista od najstarszej do najnowszej tożsamości usługi.
Potwierdzenie certyfikatu musi zostać zweryfikowane dla całego łańcucha i jest zgodne z dokładnie tym samym procesem weryfikacji podpisu cyfrowego opisanym w poprzedniej podsekcji. Istnieją popularne biblioteki kryptograficzne typu open source (na przykład OpenSSL), które mogą być zwykle używane do przeprowadzania kroku poręczenia certyfikatu.
Weryfikowanie skrótu oświadczeń aplikacji
W ramach opcjonalnego kroku oświadczenia aplikacji są dołączane do potwierdzenia, można obliczyć skrót oświadczeń z uwidocznionych oświadczeń (zgodnie z określonym algorytmem) i sprawdzić, czy skrót pasuje claimsDigest
do zawartego w ładunku paragonu. Aby obliczyć skrót z uwidocznionych obiektów oświadczeń, należy wykonać iterację po każdym obiekcie oświadczenia aplikacji na liście i sprawdzić jego kind
pole.
Jeśli obiekt oświadczenia jest rodzaju LedgerEntry
, identyfikator kolekcji rejestru (collectionId
) i zawartość (contents
) oświadczenia powinny zostać wyodrębnione i użyte do obliczenia skrótów HMAC przy użyciu klucza tajnego (secretKey
) określonego w obiekcie oświadczenia. Te dwa skróty są następnie łączone, a skrót SHA-256 łączenia jest obliczany. Protokół (protocol
) i wynikowy skrót danych oświadczenia są następnie łączone, a kolejny skrót SHA-256 łączenia jest obliczany w celu uzyskania końcowego skrótu.
Jeśli obiekt oświadczenia jest rodzaju ClaimDigest
, skrót oświadczenia (value
) powinien zostać wyodrębniony, połączony z protokołem (protocol
), a skrót SHA-256 łączenia jest obliczany w celu uzyskania końcowego skrótu.
Po obliczeniu każdego pojedynczego skrótu oświadczenia należy połączyć wszystkie obliczone skróty z każdego obiektu oświadczenia aplikacji (w tej samej kolejności, w której są prezentowane w potwierdzeniu). Łączenie powinno być następnie poprzedzane liczbą przetworzonych oświadczeń. Skrót SHA-256 poprzedniej łączenia generuje ostateczny skrót oświadczeń, który powinien być zgodny z wartością obecną claimsDigest
w obiekcie paragonu.
Więcej zasobów
Aby uzyskać więcej informacji na temat zawartości potwierdzenia transakcji zapisu poufnej księgi platformy Azure i wyjaśnienia każdego pola, zobacz dedykowany artykuł. Dokumentacja programu CCF zawiera również więcej informacji na temat weryfikacji potwierdzenia i innych powiązanych zasobów pod następującymi linkami:
- Weryfikacja potwierdzenia
- Słownik CCF
- Drzewo Merkle
- Kryptografia
- Certyfikaty
- Oświadczenia aplikacji
- Oświadczenia zdefiniowane przez użytkownika w paragonach
Weryfikowanie potwierdzeń transakcji zapisu
Narzędzia weryfikacji potwierdzenia
Biblioteka klienta poufnej księgi platformy Azure dla języka Python udostępnia funkcje narzędzi umożliwiające weryfikowanie potwierdzeń transakcji zapisu i obliczanie skrótów oświadczeń z listy oświadczeń aplikacji. Aby uzyskać więcej informacji na temat korzystania z zestawu SDK płaszczyzny danych i narzędzi specyficznych dla paragonów, zobacz tę sekcję i ten przykładowy kod.
Konfigurowanie i wymagania wstępne
W celach referencyjnych udostępniamy przykładowy kod w języku Python, aby w pełni zweryfikować potwierdzenia transakcji zapisu poufnych rejestru platformy Azure, wykonując kroki opisane w poprzedniej sekcji.
Aby uruchomić pełny algorytm weryfikacji, wymagany jest bieżący certyfikat sieci usługi i potwierdzenie transakcji zapisu z uruchomionego zasobu Poufne rejestry. Zapoznaj się z tym artykułem, aby uzyskać szczegółowe informacje na temat pobierania potwierdzenia transakcji zapisu i certyfikatu usługi z wystąpienia poufnego rejestru.
Przewodnik po kodzie
Poniższy kod może służyć do inicjowania wymaganych obiektów i uruchamiania algorytmu weryfikacji potwierdzenia. Oddzielne narzędzie (verify_receipt
) służy do uruchamiania pełnego algorytmu weryfikacji i akceptuje zawartość receipt
pola w GET_RECEIPT
odpowiedzi jako słownik i certyfikat usługi jako prosty ciąg. Funkcja zgłasza wyjątek, jeśli potwierdzenie nie jest prawidłowe lub wystąpił błąd podczas przetwarzania.
Zakłada się, że zarówno potwierdzenie, jak i certyfikat usługi można załadować z plików. Pamiętaj, aby zaktualizować zarówno stałe, jak service_certificate_file_name
i receipt_file_name
przy użyciu odpowiednich nazw plików certyfikatu usługi i potwierdzenia, które chcesz zweryfikować.
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
Ponieważ proces weryfikacji wymaga niektórych typów pierwotnych kryptograficznych i skrótów, następujące biblioteki są używane do ułatwienia obliczeń.
- Biblioteka języka Python CCF: moduł udostępnia zestaw narzędzi do weryfikacji potwierdzenia.
- Biblioteka kryptograficzna języka Python: powszechnie używana biblioteka zawierająca różne algorytmy kryptograficzne i typy pierwotne.
- Moduł hashlib, część standardowej biblioteki języka Python: moduł, który udostępnia wspólny interfejs dla popularnych algorytmów tworzenia skrótów.
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
Wewnątrz funkcji sprawdzamy, czy podane potwierdzenie jest prawidłowe i zawiera wszystkie wymagane pola.
# 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
Zainicjujemy zmienne, które będą używane w pozostałej części 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"]
Certyfikaty PEM dla tożsamości usługi, węzła podpisywania i certyfikatów poręczenia można załadować z poprzednich tożsamości usługi przy użyciu biblioteki kryptograficznej.
# 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
]
Pierwszym krokiem procesu weryfikacji jest obliczenie skrótu węzła liścia.
# 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
)
Funkcja compute_leaf_node
przyjmuje jako parametry składników liścia potwierdzenia ( claimsDigest
, , commitEvidence
i writeSetDigest
) i zwraca skrót węzła liścia w postaci szesnastkowej.
Jak opisano wcześniej, obliczamy skrót commitEvidence
(przy użyciu funkcji SHA-256 hashlib
). Następnie konwertujemy wartości i writeSetDigest
claimsDigest
na tablice bajtów. Na koniec łączymy trzy tablice i szyfrujemy wynik przy użyciu funkcji 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 obliczeniu liścia możemy obliczyć pierwiastek drzewa Merkle.
# Compute root of the Merkle Tree
root_node = root(leaf_node_hex, proof_list)
Używamy funkcji root
udostępnionej w ramach biblioteki języka Python CCF. Funkcja z kolei łączy wynik poprzedniej iteracji z nowym elementem z proof
, szyfruje łączenie, a następnie powtarza krok dla każdego elementu z proof
wcześniej obliczonym skrótem. Połączenie musi uwzględniać kolejność węzłów w drzewie Merkle, aby upewnić się, że katalog główny został poprawnie skompilowany.
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 obliczeniu skrótu węzła głównego możemy zweryfikować podpis zawarty w paragonie w katalogu głównym, aby sprawdzić, czy podpis jest poprawny.
# Verify signature of the signing node over the root of the tree
verify(root_node, root_node_signature, node_cert)
Podobnie biblioteka CCF udostępnia funkcję verify
do wykonania tej weryfikacji. Używamy klucza publicznego ECDSA certyfikatu węzła podpisywania, aby zweryfikować podpis w katalogu głównym drzewa.
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())),
)
Ostatni krok weryfikacji potwierdzenia polega na weryfikacji certyfikatu, który został użyty do podpisania katalogu głównego drzewa Merkle.
# Verify node certificate is endorsed by the service certificates through endorsements
check_endorsements(node_cert, service_cert, service_endorsements_certs)
Podobnie możemy użyć narzędzia check_endorsements
CCF, aby sprawdzić, czy tożsamość usługi obsługuje węzeł podpisywania. Łańcuch certyfikatów może składać się z poprzednich certyfikatów usługi, dlatego należy sprawdzić, czy poparcie jest stosowane przechodnio, jeśli serviceEndorsements
nie jest pustą listą.
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)
Alternatywnie możemy również zweryfikować certyfikat przy użyciu biblioteki OpenSSL przy użyciu podobnej 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()
Przykładowy kod
Podano pełny przykładowy kod używany w przewodniku po kodzie.
Główny 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
Weryfikacja potwierdzenia
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()