Edit

Share via


Authenticating Azure confidential ledger nodes

Azure confidential ledger runs on hardware-backed secure enclaves that provide strong isolation and protection for your data. Before exchanging sensitive data with the ledger, you should verify the authenticity of the ledger nodes to ensure you're communicating with legitimate Azure confidential ledger instances and not a malicious actor.

This article explains the trust model behind Azure confidential ledger node authentication, walks through the service identity certificate retrieval process, and shows how to verify node authenticity using SDK code and Intel SGX attestation.

Why authenticate ledger nodes?

Azure confidential ledger uses the TLS 1.3 protocol for client connections, with connections terminating inside hardware-backed security enclaves (Intel SGX enclaves). Because the TLS connection terminates inside the enclave, neither Azure administrators nor anyone else can access the data in transit — the hardware enforces this guarantee.

However, TLS alone doesn't prove that the remote endpoint is a genuine Azure confidential ledger node running trusted code. Authenticating ledger nodes provides additional assurance that:

  • The nodes are running within genuine Intel SGX enclaves. The hardware attestation proves the execution environment is legitimate.
  • The nodes are running trusted, unmodified code. The MRENCLAVE measurement confirms the exact code running inside the enclave.
  • Your data flows only to legitimate Azure confidential ledger instances. Certificate verification prevents impersonation.
  • Man-in-the-middle attacks are prevented. Combining TLS with node authentication closes the gap between transport encryption and endpoint trust.

Understand the trust model

Azure confidential ledger is built on the Confidential Consortium Framework (CCF), which establishes trust through a chain of cryptographic evidence:

  1. Network identity certificate: Each Azure confidential ledger instance has a unique network identity certificate (also called the service identity certificate). This certificate serves as the root of trust for TLS connections to any node in the ledger network.

  2. Node identity: Each ledger node has its own identity key pair. The node's public key is endorsed by the network identity certificate, creating a chain of trust from individual nodes to the overall ledger instance.

  3. Intel SGX attestation: Each node generates an Intel SGX quote — a hardware-signed attestation that includes the cryptographic hash of the node's identity public key and the MRENCLAVE value (a measurement of the enclave code). This quote proves the node is running within a genuine SGX enclave with trusted code.

  4. Consensus-based trust: Azure confidential ledger runs across three or more identical instances, each in a dedicated hardware-backed enclave. The integrity of the ledger is maintained through consensus-based blockchain, so a single compromised node can't alter the ledger state.

Retrieve the service identity certificate

The service identity certificate is a network identity public key certificate that serves as the root of trust (Certificate Authority) for TLS connections to the ledger. You must retrieve this certificate from the Azure confidential ledger identity service, which runs at a well-known endpoint separate from the ledger itself.

Azure CLI

curl https://identity.confidential-ledger.core.azure.com/ledgerIdentity/<ledger-name> \
  --silent | jq '.ledgerTlsCertificate' | xargs echo -e > service_cert.pem

Replace <ledger-name> with the name of your confidential ledger instance.

Python

from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient

identity_url = "https://identity.confidential-ledger.core.azure.com"
ledger_name = "<ledger-name>"

identity_client = ConfidentialLedgerCertificateClient(identity_url)
network_identity = identity_client.get_ledger_identity(ledger_id=ledger_name)

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

.NET

using Azure.Security.ConfidentialLedger;

var identityClient = new ConfidentialLedgerCertificateClient(
    new Uri("https://identity.confidential-ledger.core.azure.com"));

string ledgerName = "<ledger-name>";
Azure.Response response = identityClient.GetLedgerIdentity(ledgerName);

var certificateResponse = System.Text.Json.JsonDocument.Parse(response.Content.ToString());
string serviceCert = certificateResponse.RootElement
    .GetProperty("ledgerTlsCertificate").GetString();

File.WriteAllText("service_cert.pem", serviceCert);

Java

import com.azure.security.confidentialledger.certificate.ConfidentialLedgerCertificateClient;
import com.azure.security.confidentialledger.certificate.ConfidentialLedgerCertificateClientBuilder;
import com.azure.core.http.HttpClient;
import com.azure.core.http.rest.Response;
import com.azure.core.util.BinaryData;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

ConfidentialLedgerCertificateClient identityClient =
    new ConfidentialLedgerCertificateClientBuilder()
        .certificateEndpoint("https://identity.confidential-ledger.core.azure.com")
        .credential(new DefaultAzureCredentialBuilder().build())
        .httpClient(HttpClient.createDefault())
        .buildClient();

String ledgerName = "<ledger-name>";
Response<BinaryData> certResponse = identityClient
    .getLedgerIdentityWithResponse(ledgerName, null);

ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(certResponse.getValue().toBytes());
String serviceCert = jsonNode.get("ledgerTlsCertificate").asText();

Connect using the service identity certificate

After you retrieve the service identity certificate, use it as the trusted CA when creating a confidential ledger client. The SDKs use this certificate to verify the TLS connection to the ledger node.

Python

from azure.identity import DefaultAzureCredential
from azure.confidentialledger import ConfidentialLedgerClient

credential = DefaultAzureCredential()

ledger_client = ConfidentialLedgerClient(
    endpoint="https://<ledger-name>.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path="service_cert.pem"
)

.NET

using Azure.Identity;
using Azure.Security.ConfidentialLedger;

var ledgerClient = new ConfidentialLedgerClient(
    new Uri("https://<ledger-name>.confidential-ledger.azure.com"),
    new DefaultAzureCredential());

Note

The .NET SDK handles certificate verification internally. You don't need to pass the service certificate file explicitly.

Java

import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;

SslContext sslContext = SslContextBuilder.forClient()
    .trustManager(new ByteArrayInputStream(
        serviceCert.getBytes(StandardCharsets.UTF_8)))
    .build();

reactor.netty.http.client.HttpClient reactorClient =
    reactor.netty.http.client.HttpClient.create()
        .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));

HttpClient httpClient = new NettyAsyncHttpClientBuilder(reactorClient)
    .wiretap(true).build();

ConfidentialLedgerClient ledgerClient = new ConfidentialLedgerClientBuilder()
    .credential(new DefaultAzureCredentialBuilder().build())
    .httpClient(httpClient)
    .ledgerEndpoint("https://<ledger-name>.confidential-ledger.azure.com")
    .buildClient();

TypeScript

import ConfidentialLedger, { getLedgerIdentity }
    from "@azure-rest/confidential-ledger";
import { DefaultAzureCredential } from "@azure/identity";

const ledgerIdentity = await getLedgerIdentity("<ledger-name>");

const ledgerClient = ConfidentialLedger(
    "https://<ledger-name>.confidential-ledger.azure.com",
    ledgerIdentity.ledgerIdentityCertificate,
    new DefaultAzureCredential()
);

Verify Intel SGX enclave attestation

TLS certificate verification confirms you're connecting to a legitimate ledger instance, but it doesn't prove that the node is running inside a genuine Intel SGX enclave. For the highest level of assurance, you can verify the Intel SGX quote presented by ledger nodes.

The SGX quote is a hardware-signed attestation report that contains:

  • The MRENCLAVE value: A measurement of the code running inside the enclave. This value uniquely identifies the exact binary loaded into the enclave.
  • The node identity public key hash: The cryptographic hash (DER-encoded) of the node's identity public key, embedded in the SGX report data field.
  • Platform attestation: Intel's attestation infrastructure signs the quote, proving the enclave is running on genuine Intel SGX hardware.

When a node joins the Azure confidential ledger network, it must present a valid quote. The network accepts the node only if the quote is valid and the MRENCLAVE value matches one of the allowed values in the auditable governance configuration.

Verify a node quote

You can verify node quotes using the verify_quote.sh script that ships with the CCF Python package or the CCF installation. You can also use the oeverify tool from the Open Enclave SDK.

Prerequisites:

Steps:

  1. Download the service identity certificate if you haven't already:

    curl https://identity.confidential-ledger.core.azure.com/ledgerIdentity/<ledger-name> \
      --silent | jq '.ledgerTlsCertificate' | xargs echo -e > service_cert.pem
    
  2. Run the verification script:

    /opt/ccf_virtual/bin/verify_quote.sh \
      https://<ledger-name>.confidential-ledger.azure.com:443 \
      --cacert service_cert.pem
    

The script performs two checks:

  • It verifies that the cryptographic hash of the node's identity public key (DER-encoded) matches the SGX report data.
  • It verifies that the MRENCLAVE value present in the quote is trusted.

Verify against a specific MRENCLAVE value

You can download the list of trusted MRENCLAVE values from the ledger's governance endpoint:

curl https://<ledger-name>.confidential-ledger.azure.com/node/code \
  --cacert service_cert.pem --silent | jq

To verify that a node is running a specific version of the trusted code, supply the mrenclave parameter:

/opt/ccf_virtual/bin/verify_quote.sh \
  https://<ledger-name>.confidential-ledger.azure.com:443 \
  --cacert service_cert.pem \
  --mrenclave <expected-mrenclave-value>

If supplied, the MRENCLAVE value in the quote must match exactly.

For step-by-step details on the quote verification process, see Establish trust on Azure confidential ledger.

Best practices

  • Always retrieve the service identity certificate from the identity service. Don't hardcode or cache the certificate indefinitely. Retrieve it from https://identity.confidential-ledger.core.azure.com and refresh periodically, because service certificates can be rotated during disaster recovery or maintenance events.

  • Use SDK-provided verification. The Azure confidential ledger SDKs handle TLS certificate verification automatically. Use the SDKs whenever possible to avoid implementing certificate pinning logic manually.

  • Implement SGX quote verification for high-security scenarios. For workloads with the strictest security requirements, implement SGX quote verification to confirm that nodes are running trusted code in genuine enclaves.

  • Verify the service endorsement chain. When verifying transaction receipts, check the serviceEndorsements field. If the service identity was renewed (for example, after a disaster recovery), this field contains the chain of previous service certificates needed to trace trust back to the current certificate.

  • Understand certificate rotation implications. Service identity certificates can change during disaster recovery or service updates. Your application should handle certificate rotation gracefully by re-fetching the service identity from the identity service endpoint when TLS connection failures occur.

Next steps