Compartilhar via


Recibos de transação de gravação do Razão Confidencial do Azure

Para impor garantias de integridade de transação, um Razão Confidencial do Azure usa uma estrutura de dados de árvore Merkle para registrar o hash de todos os blocos de transações anexados à razão imutável. Depois que uma transação de gravação é confirmada, os usuários do Razão Confidencial do Azure podem obter uma prova criptográfica do Merkle, ou recibo, sobre a entrada produzida em um Razão Confidencial para verificar se a operação de gravação foi salva corretamente. Um recibo de transação de gravação é a prova de que o sistema confirmou a transação correspondente e pode ser usado para verificar se a entrada foi efetivamente acrescentada ao Razão.

Mais detalhes sobre como uma Árvore Merkle é usada em um Razão Confidencial podem ser encontrados na documentação do CCF.

Obter recibos de transação de gravação

Configuração e pré-requisitos

Os usuários do Razão Confidencial do Azure podem obter um recibo de uma transação específica usando a biblioteca de clientes do Razão Confidencial do Azure. O exemplo a seguir mostra como obter um recibo de gravação usando a biblioteca de clientes para Python, mas as etapas são as mesmas com qualquer outro SDK com suporte para o Razão Confidencial do Azure.

Presumimos que um recurso do Razão Confidencial já tenha sido criado usando a biblioteca de Gerenciamento do Razão Confidencial do Azure. Se você ainda não tiver um recurso do Razão, crie um usando as instruções a seguir.

Passo a passo do código

Começamos configurando as importações para nosso programa Python.

import json 

# Import the Azure authentication library 
from azure.identity import DefaultAzureCredential 

# Import the Confidential Ledger Data Plane SDK 
from azure.confidentialledger import ConfidentialLedgerClient 
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient 

Veja a seguir os valores constantes usados para configurar o cliente do Razão Confidencial do Azure. Atualize a constante ledger_name com o nome exclusivo do recurso do Razão Confidencial.

# Constants for our program 
ledger_name = "<your-unique-ledger-name>" 
identity_url = "https://identity.confidential-ledger.core.azure.com" 
ledger_url = "https://" + ledger_name + ".confidential-ledger.azure.com" 

Autenticamos usando a classe DefaultAzureCredential.

# Setup authentication 
credential = DefaultAzureCredential() 

Em seguida, obtemos e salvamos o certificado de serviço do Razão Confidencial usando o cliente Certificado doURL de Identidade do Razão Confidencial. O certificado de serviço é um certificado de chave pública de identidade de rede usado como raiz de confiança para autenticação de servidor do TLS. Em outras palavras, ele é usado como a AC (Autoridade de Certificação) para estabelecer uma conexão do TLS com qualquer um dos nós na rede do CCF.

# Create a Certificate client and use it to 
# get the service identity for our ledger 
identity_client = ConfidentialLedgerCertificateClient(identity_url) 
network_identity = identity_client.get_ledger_identity( 
     ledger_id=ledger_name 
)

# Save network certificate into a file for later use 
ledger_tls_cert_file_name = "network_certificate.pem" 

with open(ledger_tls_cert_file_name, "w") as cert_file: 
    cert_file.write(network_identity["ledgerTlsCertificate"]) 

Em seguida, podemos usar nossas credenciais, o certificado de rede buscado e nosso URL do Razão exclusivo para criar um cliente do Razão Confidencial.

# Create Confidential Ledger client 
ledger_client = ConfidentialLedgerClient( 
     endpoint=ledger_url,  
     credential=credential, 
     ledger_certificate_path=ledger_tls_cert_file_name 
) 

Usando o cliente do Razão Confidencial, podemos executar quaisquer operações com suporte em uma instância do Razão Confidencial do Azure. Por exemplo, podemos acrescentar uma nova entrada ao Razão e aguardar a confirmação da transação de gravação correspondente.

# The method begin_create_ledger_entry returns a poller that  
# we can use to wait for the transaction to be committed 
create_entry_poller = ledger_client.begin_create_ledger_entry( 
    {"contents": "Hello World!"} 
)

create_entry_result = create_entry_poller.result() 

Depois que a transação for confirmada, podemos usar o cliente para obter um recibo sobre a entrada acrescentada ao Razão na etapa anterior usando a respectiva ID de transação.

# The method begin_get_receipt returns a poller that  
# we can use to wait for the receipt to be available by the system 
get_receipt_poller = ledger_client.begin_get_receipt( 
    create_entry_result["transactionId"] 
)

get_receipt_result = get_receipt_poller.result() 

Código de exemplo

O código de exemplo completo usado no passo a passo do código é fornecido.

import json 

# Import the Azure authentication library 
from azure.identity import DefaultAzureCredential 

# Import the Confidential Ledger Data Plane SDK 
from azure.confidentialledger import ConfidentialLedgerClient 
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient 

from receipt_verification import verify_receipt 

# Constants 
ledger_name = "<your-unique-ledger-name>" 
identity_url = "https://identity.confidential-ledger.core.azure.com" 
ledger_url = "https://" + ledger_name + ".confidential-ledger.azure.com" 

# Setup authentication 
credential = DefaultAzureCredential() 

# Create Ledger Certificate client and use it to 
# retrieve the service identity for our ledger 
identity_client = ConfidentialLedgerCertificateClient(identity_url) 
network_identity = identity_client.get_ledger_identity(ledger_id=ledger_name) 

# Save network certificate into a file for later use 
ledger_tls_cert_file_name = "network_certificate.pem" 

with open(ledger_tls_cert_file_name, "w") as cert_file: 
    cert_file.write(network_identity["ledgerTlsCertificate"]) 

# Create Confidential Ledger client 
ledger_client = ConfidentialLedgerClient( 
    endpoint=ledger_url, 
    credential=credential, 
    ledger_certificate_path=ledger_tls_cert_file_name, 
) 

# The method begin_create_ledger_entry returns a poller that 
# we can use to wait for the transaction to be committed 
create_entry_poller = ledger_client.begin_create_ledger_entry( 
    {"contents": "Hello World!"} 
) 
create_entry_result = create_entry_poller.result() 

# The method begin_get_receipt returns a poller that 
# we can use to wait for the receipt to be available by the system 
get_receipt_poller = ledger_client.begin_get_receipt( 
    create_entry_result["transactionId"] 
) 
get_receipt_result = get_receipt_poller.result() 

# Save fetched receipt into a file
with open("receipt.json", "w") as receipt_file: 
    receipt_file.write(json.dumps(get_receipt_result, sort_keys=True, indent=2)) 

Gravar o conteúdo do recibo da transação

Aqui está um exemplo de um payload de resposta JSON retornado por uma instância do Razão Confidencial do Azure ao chamar o ponto de extremidade GET_RECEIPT.

{
    "receipt": {
        "cert": "-----BEGIN CERTIFICATE-----\nMIIB0jCCAXmgAwIBAgIQPxdrEtGY+SggPHETin1XNzAKBggqhkjOPQQDAjAWMRQw\nEgYDVQQDDAtDQ0YgTmV0d29yazAeFw0yMjA3MjAxMzUzMDFaFw0yMjEwMTgxMzUz\nMDBaMBMxETAPBgNVBAMMCENDRiBOb2RlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD\nQgAEWy81dFeEZ79gVJnfHiPKjZ54fZvDcFlntFwJN8Wf6RZa3PaV5EzwAKHNfojj\noXT4xNkJjURBN7q+1iE/vvc+rqOBqzCBqDAJBgNVHRMEAjAAMB0GA1UdDgQWBBQS\nwl7Hx2VkkznJNkVZUbZy+TOR/jAfBgNVHSMEGDAWgBTrz538MGI/SdV8k8EiJl5z\nfl3mBTBbBgNVHREEVDBShwQK8EBegjNhcGljY2lvbmUtdGVzdC1sZWRnZXIuY29u\nZmlkZW50aWFsLWxlZGdlci5henVyZS5jb22CFWFwaWNjaW9uZS10ZXN0LWxlZGdl\ncjAKBggqhkjOPQQDAgNHADBEAiAsGawDcYcH/KzF2iK9Ldx/yABUoYSNti2Cyxum\n9RRNKAIgPB/XGh/FQS3nmZLExgBVXkDYdghQu/NCY/hHjQ9AvWg=\n-----END CERTIFICATE-----\n",
        "leafComponents": {
            "claimsDigest": "0000000000000000000000000000000000000000000000000000000000000000",
            "commitEvidence": "ce:2.40:f36ffe2930ec95d50ebaaec26e2bec56835abd051019eb270f538ab0744712a4",
            "writeSetDigest": "8452624d10bdd79c408c0f062a1917aa96711ea062c508c745469636ae1460be"
        },
        "nodeId": "70e995887e3e6b73c80bc44f9fbb6e66b9f644acaddbc9c0483cfc17d77af24f",
        "proof": [
            {
                "left": "b78230f9abb27b9b803a9cae4e4cec647a3be1000fc2241038867792d59d4bc1"
            },
            {
                "left": "a2835d4505b8b6b25a0c06a9c8e96a5204533ceac1edf2b3e0e4dece78fbaf35"
            }
        ],
        "signature": "MEUCIQCjtMqk7wOtUTgqlHlCfWRqAco+38roVdUcRv7a1G6pBwIgWKpCSdBmhzgEdwguUW/Cj/Z5bAOA8YHSoLe8KzrlqK8="
    },
    "state": "Ready",
    "transactionId": "2.40"
}

A resposta JSON contém os campos a seguir no nível raiz.

  • recibo: contém os valores que podem ser usados para verificar a validade do recibo da transação de gravação correspondente.

  • estado: o status da resposta JSON retornada. A seguir estão os possíveis valores permitidos:

    • Ready: o recibo retornado na resposta está disponível
    • Loading: O recibo ainda não está disponível para ser recuperado e a solicitação tem que ser repetida
  • transactionId: a ID da transação associada ao recibo da transação de gravação.

O campo receipt contém os seguintes campos.

  • certificado: cadeia de caracteres com o certificado de chave pública PEM do nó de CCF que assinou a transação de gravação. O certificado de identidade de serviço deve sempre endossar o certificado do nó de assinatura. Confira também mais detalhes sobre como as transações são assinadas regularmente e como as transações de assinatura são acrescentadas ao Razão no CCF no link a seguir.

  • nodeId: cadeia de caracteres hexadecimal que representa o código hash SHA-256 da chave pública do nó CCF de assinatura.

  • leafComponents: os componentes do hash de nó folha na Árvore Merkle que estão associados à transação especificada. Uma Árvore Merkle é uma estrutura de dados de árvore que registra o hash de cada transação e garante a integridade do Razão. Para obter mais informações sobre como uma Árvore Merkle é usada no CCF, consulte a documentação do CCF relacionada.

  • prova: lista de pares chave-valor que representam os hashes de nós Árvore Merkle que, quando combinados com o hash de nó folha correspondente à transação fornecida, permitem a recomputação do hash raiz da árvore. Graças às propriedades de uma Árvore Merkle, é possível recalcular o hash raiz da árvore apenas para um subconjunto de nós. Os elementos nesta lista estão na forma de pares chave-valor: as chaves indicam a posição relativa em relação ao nó pai na árvore em um determinado nível. Os valores são os códigos hash SHA-256 do nó fornecido, como cadeias de caracteres hexadecimal.

  • serviceEndorsements: lista de cadeias de caracteres de certificados codificados em PEM que representam certificados de identidades de serviço anteriores. É possível que a identidade de serviço que endossou o nó de assinatura não seja igual à que emitiu o recibo. Por exemplo, o certificado de serviço é renovado após uma recuperação de desastre de um Razão Confidencial. A lista de certificados de serviço anteriores permite que os auditores criem a cadeia de confiança do nó de assinatura do CCF para o certificado de serviço atual.

  • assinatura: cadeia de caracteres Base64 que representa a assinatura da raiz da Árvore Merkle na transação determinada, pelo nó CCF de assinatura.

O campo leafComponents contém os seguintes campos.

  • claimsDigest: cadeia de caracteres hexadecimal que representa o código hash SHA-256 da declaração de aplicativo anexada pelo aplicativo do Razão Confidencial no momento em que a transação foi executada. No momento, as declarações de aplicativo não têm suporte, pois o aplicativo do Razão Confidencial não anexa nenhuma declaração ao executar uma transação de gravação.

  • commitEvidence: uma cadeia de caracteres exclusiva produzida por transação, derivada da ID da transação e dos segredos do Razão. Para obter mais informações sobre as evidências de confirmação, consulte a documentação do CCF relacionada.

  • writeSetDigest: cadeia de caracteres hexadecimal que representa o código hash SHA-256 do repositório Chave-Valor, que contém todas as chaves e valores gravados no momento em que a transação foi concluída. Para obter mais informações sobre o conjunto de gravação, consulte a documentação do CCF relacionada.

Declarações do aplicativo

Os aplicativos do Razão Confidencial do Azure podem anexar dados arbitrários, chamados de declarações de aplicativo, para gravar transações. Essas declarações representam as ações executadas durante uma operação de gravação. Quando anexado a uma transação, o resumo SHA-256 do objeto de declarações é incluído no razão e confirmado como parte da transação de gravação. A inclusão da declaração na transação de gravação garante que o resumo da declaração esteja conectado e não possa ser adulterado.

Posteriormente, as declarações de aplicativo podem ser reveladas em seu formato sem formatação na carga de recibo correspondente à mesma transação em que foram adicionadas. As declarações expostas permitem que os usuários recomputem o mesmo resumo de declarações que foi anexado e conectado pelo razão durante a transação. O resumo de declarações pode ser usado como parte do processo de verificação de recibo de transação de gravação, fornecendo uma maneira off-line para que os usuários verifiquem totalmente a autenticidade das reivindicações registradas.

Atualmente, há suporte para declarações de aplicativo na versão prévia da API 2023-01-18-preview.

Gravar o conteúdo do recibo da transação com declarações do aplicativo

Aqui está um exemplo de um payload de resposta JSON retornado por uma instância do Razão Confidencial do Azure que gravou declarações de aplicativo ao chamar o ponto de extremidade GET_RECEIPT.

{
  "applicationClaims": [
    {
      "kind": "LedgerEntry",
      "ledgerEntry": {
        "collectionId": "subledger:0",
        "contents": "Hello world",
        "protocol": "LedgerEntryV1",
        "secretKey": "Jde/VvaIfyrjQ/B19P+UJCBwmcrgN7sERStoyHnYO0M="
      }
    }
  ],
  "receipt": {
    "cert": "-----BEGIN CERTIFICATE-----\nMIIBxTCCAUygAwIBAgIRAMR89lUNeIghDUfpyHi3QzIwCgYIKoZIzj0EAwMwFjEU\nMBIGA1UEAwwLQ0NGIE5ldHdvcmswHhcNMjMwNDI1MTgxNDE5WhcNMjMwNzI0MTgx\nNDE4WjATMREwDwYDVQQDDAhDQ0YgTm9kZTB2MBAGByqGSM49AgEGBSuBBAAiA2IA\nBB1DiBUBr9/qapmvAIPm1o3o3LRViSOkfFVI4oPrw3SodLlousHrLz+HIe+BqHoj\n4nBjt0KAS2C0Av6Q+Xg5Po6GCu99GQSoSfajGqmjy3j3bwjsGJi5wHh1pNbPmMm/\nTqNhMF8wDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUCPaDohOGjVgQ2Lb8Pmubg7Y5\nDJAwHwYDVR0jBBgwFoAU25KejcEmXDNnKvSLUwW/CQZIVq4wDwYDVR0RBAgwBocE\nfwAAATAKBggqhkjOPQQDAwNnADBkAjA8Ci9myzieoLoIy+7mUswVEjUG3wrEXtxA\nDRmt2PK9bTDo2m3aJ4nCQJtCWQRUlN0CMCMOsXL4NnfsSxaG5CwAVkDwLBUPv7Zy\nLfSh2oZ3Wn4FTxL0UfnJeFOz/CkDUtJI1A==\n-----END CERTIFICATE-----\n",
    "leafComponents": {
      "claimsDigest": "d08d8764437d09b2d4d07d52293cddaf40f44a3ea2176a0528819a80002df9f6",
      "commitEvidence": "ce:2.13:850a25da46643fa41392750b6ca03c7c7d117c27ae14e3322873de6322aa7cd3",
      "writeSetDigest": "6637eddb8741ab54cc8a44725be67fd9be390e605f0537e5a278703860ace035"
    },
    "nodeId": "0db9a22e9301d1167a2a81596fa234642ad24bc742451a415b8d653af056795c",
    "proof": [
      {
        "left": "bcce25aa51854bd15257cfb0c81edc568a5a5fa3b81e7106c125649db93ff599"
      },
      {
        "left": "cc82daa27e76b7525a1f37ed7379bb80f6aab99f2b36e2e06c750dd9393cd51b"
      },
      {
        "left": "c53a15cbcc97e30ce748c0f44516ac3440e3e9cc19db0852f3aa3a3d5554dfae"
      }
    ],
    "signature": "MGYCMQClZXVAFn+vflIIikwMz64YZGoH71DKnfMr3LXkQ0lhljSsvDrmtmi/oWwOsqy28PsCMQCMe4n9aXXK4R+vY0SIfRWSCCfaADD6teclFCkVNK4317ep+5ENM/5T/vDJf3V4IvI="
  },
  "state": "Ready",
  "transactionId": "2.13"
}

Em comparação com o exemplo de recibo mostrado na seção anterior, a resposta JSON contém outro campo applicationClaims que representa a lista de declarações de aplicativo registradas pelo razão durante a transação de gravação. Cada objeto dentro da lista applicationClaims contém os campos a seguir.

  • tipo: representa o tipo de declaração do aplicativo. O valor indica como analisar o objeto de declaração de aplicativo para o tipo fornecido.

  • ledgerEntry: representa uma declaração de aplicativo derivada de dados de entrada do razão. A declaração conteria os dados registrados pelo aplicativo durante uma transação de gravação (por exemplo, a ID da coleção e o conteúdo fornecido pelo usuário) e as informações necessárias para calcular o resumo correspondente ao único objeto de declaração.

  • digest: representa uma declaração de aplicativo em forma digerida. Esse objeto de declaração conteria o resumo pré-compilado pelo aplicativo e o protocolo usado para a computação.

O campo ledgerEntry contém os seguintes campos.

  • protocolo: representa o protocolo a ser usado para calcular o resumo de uma declaração dos dados de declaração fornecidos.

  • collectionId: o identificador da coleção gravada durante a transação de gravação correspondente.

  • contents: o conteúdo do razão gravado durante a transação de gravação correspondente.

  • secretKey: uma chave secreta codificada em base64. Essa chave deve ser usada no algoritmo HMAC com os valores fornecidos na declaração do aplicativo para obter o resumo da declaração.

O campo digest contém os seguintes campos.

  • protocolo: representa o protocolo usado para calcular o resumo da declaração fornecida.

  • value: o resumo da declaração do aplicativo, na forma hexadecimal. Esse valor teria que ser hash com o valor protocol para calcular o resumo completo da declaração do aplicativo.

Mais recursos

Para obter mais informações sobre recibos de transação de gravação e como o CCF garante a integridade de cada transação, consulte os seguintes links:

Próximas etapas