Certificate Chain Verification: The Notary Chain

Before reading this, understand Digital Signatures, Digital Certificates, and AWS Fleet Provisioning.

Certificate chain verification is like checking a chain of notary stamps — each certificate must be properly signed by the one above it, all the way to a trusted root.


The Notary Chain Analogy

Traditional Document Verification:

Certificate Chain Verification:


Certificate Chain Structure

graph TD
    A[Amazon Root CA 1<br/>Self-Signed<br/>Trusted by ESP32] --> B[AWS IoT CA<br/>Signed by Root CA<br/>Intermediate Certificate]
    B --> C[Device Certificate<br/>ESP32-LivingRoom-001<br/>Signed by IoT CA]
    B --> D[Device Certificate<br/>ESP32-Kitchen-002<br/>Signed by IoT CA]
    
    style A fill:#ff9999
    style B fill:#99ccff
    style C fill:#99ff99
    style D fill:#99ff99

Real Certificate Chain Example

Root CA Certificate (Pre-installed)

-----BEGIN CERTIFICATE-----
Subject: CN=Amazon Root CA 1, O=Amazon, C=US
Issuer: CN=Amazon Root CA 1, O=Amazon, C=US    ← Self-signed!
Valid From: 2015-05-26 00:00:00
Valid To:   2038-01-17 00:00:00                ← Long-lived
Public Key: RSA 2048 bits
    00:b0:28:cf:0c:a4:86:09:6f:85:43:51:83:c6:35:08:
    c9:37:3d:1e:68:b7:ab:5c:20:f8:4e:81:f7:7d:40:2a:
    ...
Signature: (Self-signed with own private key)
-----END CERTIFICATE-----

Intermediate CA Certificate (Downloaded)

-----BEGIN CERTIFICATE-----
Subject: CN=AWS IoT Device Management CA, O=Amazon.com Inc., C=US
Issuer: CN=Amazon Root CA 1, O=Amazon, C=US    ← Signed by Root!
Valid From: 2020-04-01 00:00:00  
Valid To:   2025-04-01 00:00:00                ← Shorter-lived
Public Key: RSA 2048 bits
    00:a1:2b:3c:4d:5e:6f:7a:8b:9c:ad:be:cf:d0:e1:f2:
    03:14:25:36:47:58:69:7a:8b:1c:2d:3e:4f:60:71:82:
    ...
Signature: (Signed by Amazon Root CA 1's private key)
-----END CERTIFICATE-----

Device Certificate (From Provisioning)

-----BEGIN CERTIFICATE-----
Subject: CN=ESP32-LivingRoom-001, O=CongruentTech, C=US
Issuer: CN=AWS IoT Device Management CA, O=Amazon.com Inc., C=US
Valid From: 2025-01-01 00:00:00
Valid To:   2026-01-01 00:00:00                ← Annual renewal
Public Key: ECC P-256
    04:1a:2b:3c:4d:5e:6f:7a:8b:9c:ad:be:cf:d0:e1:f2:
    03:14:25:36:47:58:69:7a:8b:1c:2d:3e:4f:60:71:82:
    ...
Signature: (Signed by AWS IoT CA's private key)
-----END CERTIFICATE-----

ESP32 Chain Verification Code

Store Root CA Certificate

// Pre-installed root CA (never changes)
static const char aws_root_ca_pem[] = 
"-----BEGIN CERTIFICATE-----\n"
"MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5\n"
"MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\n"
"Um9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkG\n"
"A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\n"
"Q0EgMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCLNxjm1QqFCGQWYG+YTxOVh\n"
"9YEKrfMT5rPBmTOgLo6BSs7cH0UHfCdNwFCnXl1cAWdB0KHBUxpZWJXzz6j5uq4j\n"
"RTBDMBsGCisGAQQBjyahkgIxAQYLKoZIhvdMBQICFA0wEgYDVR0RBAswCYIHZGRu\n"
"cy5vcmcwCgYIKoZIzj0EAwIDSQAwRgIhAMaC+GEGHrBwCVsRtF9GMJKiYdmCOVV3\n"
"K4kpRaJ0Qm3fAiEAjjD1xZdHhFn5D3VY6/z1J1Z3cOPSaAIEV2FQ3xtODrE=\n"
"-----END CERTIFICATE-----";

esp_err_t store_root_ca() {
    return pkcs11_store_certificate(aws_root_ca_pem, "Root CA Cert");
}

Verify Certificate Chain

esp_err_t verify_certificate_chain(const char* device_cert_pem,
                                   const char* intermediate_cert_pem) {
    mbedtls_x509_crt device_cert;
    mbedtls_x509_crt intermediate_cert;
    mbedtls_x509_crt root_ca_cert;
    
    // Parse all certificates
    mbedtls_x509_crt_parse(&device_cert, (uint8_t*)device_cert_pem, 
                          strlen(device_cert_pem) + 1);
    mbedtls_x509_crt_parse(&intermediate_cert, (uint8_t*)intermediate_cert_pem,
                          strlen(intermediate_cert_pem) + 1);
    mbedtls_x509_crt_parse(&root_ca_cert, (uint8_t*)aws_root_ca_pem,
                          strlen(aws_root_ca_pem) + 1);
    
    // Build certificate chain: Device ← Intermediate ← Root
    uint32_t flags;
    int ret = mbedtls_x509_crt_verify(&device_cert,     // Certificate to verify
                                     &intermediate_cert, // Chain of trust  
                                     &root_ca_cert,      // Trust anchors
                                     NULL,               // No CRL
                                     &flags,             // Verification flags
                                     NULL, NULL);        // No callback
    
    if (ret == 0) {
        ESP_LOGI(TAG, "✅ Certificate chain verification successful");
        return ESP_OK;
    } else {
        ESP_LOGE(TAG, "❌ Certificate chain verification failed: 0x%08X", flags);
        log_verification_errors(flags);
        return ESP_FAIL;
    }
}

Verification Error Analysis

void log_verification_errors(uint32_t flags) {
    if (flags & MBEDTLS_X509_BADCERT_EXPIRED) {
        ESP_LOGE(TAG, "Certificate has expired");
    }
    if (flags & MBEDTLS_X509_BADCERT_REVOKED) {
        ESP_LOGE(TAG, "Certificate has been revoked");
    }
    if (flags & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
        ESP_LOGE(TAG, "Common Name (CN) does not match");
    }
    if (flags & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
        ESP_LOGE(TAG, "Certificate is not signed by trusted CA");
    }
    if (flags & MBEDTLS_X509_BADCERT_FUTURE) {
        ESP_LOGE(TAG, "Certificate validity starts in the future");
    }
}

The Critical Public Key Verification

This is the security check that prevents certificate substitution attacks:

esp_err_t verify_public_key_match(const char* certificate_pem,
                                  CK_OBJECT_HANDLE device_public_key) {
    // Extract public key from received certificate
    mbedtls_x509_crt cert;
    mbedtls_x509_crt_parse(&cert, (uint8_t*)certificate_pem, 
                          strlen(certificate_pem) + 1);
    
    // Get certificate's public key
    uint8_t cert_pubkey[65];  // P-256 uncompressed format
    size_t cert_pubkey_len;
    mbedtls_pk_write_pubkey_raw(&cert.pk, cert_pubkey, 65);
    
    // Get device's public key from PKCS#11
    uint8_t device_pubkey[65];
    CK_ATTRIBUTE template[] = {
        {CKA_EC_POINT, device_pubkey, sizeof(device_pubkey)}
    };
    C_GetAttributeValue(session, device_public_key, template, 1);
    
    // Compare the two public keys
    if (memcmp(cert_pubkey, device_pubkey, 65) == 0) {
        ESP_LOGI(TAG, "✅ Certificate public key matches device key");
        return ESP_OK;
    } else {
        ESP_LOGE(TAG, "❌ Certificate public key mismatch - possible attack!");
        ESP_LOGE(TAG, "Certificate key: %s", hex_dump(cert_pubkey, 32));
        ESP_LOGE(TAG, "Device key:      %s", hex_dump(device_pubkey, 32));
        return ESP_FAIL;
    }
}

Mathematical Chain Verification

Signature Verification Process

For each certificate in chain:

1. Extract issuer's public key from parent certificate
2. Hash the certificate's content (TBSCertificate)  
3. Decrypt signature with issuer's public key
4. Compare decrypted hash with computed hash

Example (RSA-2048):
hash = SHA256(certificate_content)
decrypted = signature^e mod n  (using parent's public key)
valid = (hash == decrypted)

ECC Signature Verification (P-256)

esp_err_t verify_ecdsa_signature(const uint8_t* hash,
                                const ecdsa_signature_t* signature,
                                const ecc_point_t* public_key) {
    // ECDSA verification: 
    // u1 = hash * w mod n
    // u2 = r * w mod n  
    // (x, y) = u1*G + u2*Q
    // valid if x == r
    
    mbedtls_ecdsa_context ctx;
    mbedtls_ecdsa_init(&ctx);
    
    int ret = mbedtls_ecdsa_verify(&ctx.grp, hash, 32,
                                  &ctx.Q, signature->r, signature->s);
    
    mbedtls_ecdsa_free(&ctx);
    return (ret == 0) ? ESP_OK : ESP_FAIL;
}

Certificate Pinning for Extra Security

// Pin specific AWS IoT intermediate CA
static const char* pinned_aws_iot_ca_fingerprint = 
    "A1:B2:C3:D4:E5:F6:07:08:09:0A:1B:2C:3D:4E:5F:60:71:82:93:A4";

esp_err_t verify_certificate_pinning(mbedtls_x509_crt* cert) {
    uint8_t fingerprint[20];  // SHA-1 hash
    
    // Compute certificate fingerprint
    mbedtls_sha1_ret(cert->raw.p, cert->raw.len, fingerprint);
    
    // Convert to hex string
    char hex_fingerprint[60];
    for (int i = 0; i < 20; i++) {
        sprintf(&hex_fingerprint[i * 3], "%02X:", fingerprint[i]);
    }
    hex_fingerprint[59] = '\0';  // Remove trailing ':'
    
    // Compare with pinned fingerprint
    if (strcmp(hex_fingerprint, pinned_aws_iot_ca_fingerprint) == 0) {
        ESP_LOGI(TAG, "✅ Certificate pinning verification passed");
        return ESP_OK;
    } else {
        ESP_LOGE(TAG, "❌ Certificate pinning failed");
        ESP_LOGE(TAG, "Expected: %s", pinned_aws_iot_ca_fingerprint);
        ESP_LOGE(TAG, "Got:      %s", hex_fingerprint);
        return ESP_FAIL;
    }
}

Common Chain Verification Failures

Missing Intermediate Certificate

Error: MBEDTLS_X509_BADCERT_NOT_TRUSTED
Root Cause: Device cert → Root CA (missing intermediate)
Solution: Download and store AWS IoT intermediate CA

Certificate Order Wrong

// WRONG: Root → Device → Intermediate
mbedtls_x509_crt_verify(&device_cert, &root_ca, &intermediate, ...);

// CORRECT: Device → Intermediate → Root  
mbedtls_x509_crt_verify(&device_cert, &intermediate, &root_ca, ...);

Expired Intermediate Certificate

Error: MBEDTLS_X509_BADCERT_EXPIRED
Root Cause: Intermediate CA certificate expired
Solution: Update to newer intermediate CA certificate

Clock Synchronization Issues

// Device thinks it's 2023, certificate valid from 2025
time_t now = time(NULL);
if (now < cert->valid_from) {
    ESP_LOGE(TAG, "Certificate not yet valid - sync device clock");
    sntp_sync_time();  // NTP time synchronization
}

Performance Optimization

Certificate Caching

typedef struct {
    char* cert_pem;
    mbedtls_x509_crt parsed_cert;
    time_t cache_timestamp;
    bool is_valid;
} cert_cache_entry_t;

static cert_cache_entry_t cert_cache[MAX_CACHED_CERTS];

mbedtls_x509_crt* get_cached_certificate(const char* subject_name) {
    for (int i = 0; i < MAX_CACHED_CERTS; i++) {
        if (cert_cache[i].is_valid && 
            strcmp(cert_cache[i].parsed_cert.subject.val.p, subject_name) == 0) {
            return &cert_cache[i].parsed_cert;
        }
    }
    return NULL;  // Cache miss
}

Hardware Acceleration

// Use ESP32's hardware crypto acceleration
esp_err_t init_hardware_crypto() {
    // Enable AES, SHA, RSA, ECC hardware acceleration
    esp_aes_acquire_hardware();
    esp_sha_acquire_hardware();
    
    // Configure mbedTLS to use hardware
    mbedtls_hardware_poll(NULL, NULL, 0, NULL);
    
    return ESP_OK;
}

Real-World Timing

// Certificate verification timing on ESP32
typedef struct {
    uint32_t parse_cert_ms;        // 10-50ms per certificate
    uint32_t rsa_verify_ms;        // 200-800ms (RSA-2048)
    uint32_t ecc_verify_ms;        // 50-200ms (P-256)
    uint32_t chain_build_ms;       // 5-20ms
    uint32_t total_verify_ms;      // 265-1070ms total
} verification_timing_t;

// Optimization: Cache parsed certificates, use ECC when possible

Complete Verification Function

esp_err_t verify_device_certificate_complete(const char* cert_pem) {
    esp_err_t ret;
    
    // Step 1: Basic certificate parsing
    ret = parse_and_validate_certificate(cert_pem);
    if (ret != ESP_OK) return ret;
    
    // Step 2: Verify certificate chain (AWS signature validation)
    ret = verify_certificate_chain(cert_pem, aws_intermediate_ca_pem);
    if (ret != ESP_OK) return ret;
    
    // Step 3: Verify public key matches device's key (prevents attacks)
    CK_OBJECT_HANDLE device_pubkey = find_device_public_key();
    ret = verify_public_key_match(cert_pem, device_pubkey);
    if (ret != ESP_OK) return ret;
    
    // Step 4: Optional certificate pinning
    #ifdef CONFIG_ENABLE_CERT_PINNING
    ret = verify_certificate_pinning(&parsed_cert);
    if (ret != ESP_OK) return ret;
    #endif
    
    // Step 5: Check certificate extensions and usage
    ret = verify_certificate_usage(&parsed_cert);
    if (ret != ESP_OK) return ret;
    
    ESP_LOGI(TAG, "🔒 Certificate verification completed successfully");
    return ESP_OK;
}

Next Steps

Certificate chain verification is your device’s security checkpoint — it ensures every certificate in the chain is legitimate, properly signed, and that you received the right certificate for your device!