Solving PKCS#11 Key Import Issues in AWS IoT Fleet Provisioning

Working on ESP32-C6 firmware for a smart locker controller, we ran into a stubborn CKR_ATTRIBUTE_VALUE_INVALID error when trying to import AWS claim certificates into PKCS#11 secure storage. After multiple failed attempts at parameter formatting, the solution came from understanding a fundamental design principle.


The Problem: Import vs. Generation

AWS IoT Fleet Provisioning uses a two-tier authentication system:

Our ESP32 firmware needed to:

  1. Store AWS-provided claim certificates in PKCS#11
  2. Generate device keys internally using PKCS#11
  3. Use claim certs to request permanent device certificates from AWS

Simple enough, right?


The Persistent Error

E (4053) device_provisioning: ❌ PKCS#11 error: 0x00000012 (CKR_ATTRIBUTE_VALUE_INVALID)
E (4063) device_provisioning: πŸ’‘ Failed to store RSA private key in secure storage

This CKR_ATTRIBUTE_VALUE_INVALID error appeared whenever we tried to import the external RSA claim keys into PKCS#11 storage.


Our Failed Attempts

1. RSA Parameter Formatting

We suspected the 2048-bit RSA parameters weren’t formatted correctly:

// Tried fixing public exponent alignment
unsigned char compactExponent[] = {0x01, 0x00, 0x01};
pubExpTemplate.pValue = compactExponent;
pubExpTemplate.ulValueLen = sizeof(compactExponent);

Result: Same error.

2. Minimal Templates

Maybe we were providing too many RSA parameters:

// Stripped down to only essential attributes
CK_ATTRIBUTE privateKeyTemplate[] = {
    {CKA_CLASS, &privateKeyClass, sizeof(privateKeyClass)},
    {CKA_MODULUS, modulus, modulusLen},
    {CKA_PRIVATE_EXPONENT, privateExponent, privateExponentLen}
};

Result: Same error.

3. Parameter Padding Investigation

We even examined the raw modulus and private exponent for alignment issues.

Result: Parameters looked correct, but same error persisted.


The Breakthrough Insight

The user made a crucial observation: β€œis that we can not push an object in PKCS#11 and the keys should be created inside of them?”

This hit the nail on the head. PKCS#11 is fundamentally designed for:

It’s not designed for importing external keys, especially complex ones like RSA private keys with multiple parameters.


The Elegant Solution: Hybrid Approach

Instead of forcing external keys into PKCS#11, we implemented a hybrid approach:

Device Keys β†’ PKCS#11 (Internal Generation)

// Generate ECDSA P-256 device keys internally in PKCS#11
CK_MECHANISM mechanism = {CKM_EC_KEY_PAIR_GEN, NULL, 0};
CK_OBJECT_HANDLE privateKey, publicKey;

result = C_GenerateKeyPair(session, &mechanism, publicKeyTemplate, 
                          publicTemplateCount, privateKeyTemplate, 
                          privateTemplateCount, &publicKey, &privateKey);

Claim Keys β†’ MbedTLS Direct (External Import)

// Load claim credentials directly with MbedTLS
typedef struct {
    mbedtls_x509_crt claim_cert;
    mbedtls_pk_context claim_key;
    bool initialized;
} claim_credentials_t;

// Parse directly without PKCS#11
mbedtls_x509_crt_parse(&credentials->claim_cert, cert_buffer, cert_len + 1);
mbedtls_pk_parse_key(&credentials->claim_key, key_buffer, key_len + 1, 
                     NULL, 0, NULL, NULL);

Why This Works Better

1. Follows Design Intent

2. Better Security Model

3. Cleaner Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Claim Keys    β”‚    β”‚  Device Keys    β”‚
β”‚   (External)    β”‚    β”‚  (Internal)     β”‚
β”‚                 β”‚    β”‚                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚    β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   MbedTLS   β”‚β”‚    β”‚ β”‚   PKCS#11   β”‚ β”‚
β”‚  β”‚   Direct    β”‚β”‚    β”‚ β”‚  Secure     β”‚ β”‚
β”‚  β”‚   Loading   β”‚β”‚    β”‚ β”‚  Storage    β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚    β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      ↓                        ↓
  AWS Fleet            Device Certificate
  Provisioning         Signing & Storage

The Fix in Action

Before: Persistent Errors

E (4053) device_provisioning: ❌ PKCS#11 error: 0x00000012
E (4063) device_provisioning: Failed RSA parameter formatting attempts
E (4073) device_provisioning: CRT parameter issues
E (4083) device_provisioning: Multiple template variations failed

After: Clean Success

I (7050) device_provisioning: βœ… Claim certificate parsed successfully
I (7130) device_provisioning: βœ… Claim private key parsed successfully
I (7140) device_provisioning: πŸ”‘ Key type: RSA (correct for AWS claim credentials)
I (7160) device_provisioning: πŸŽ‰ Claim credentials loaded successfully with MbedTLS!

Lessons Learned

1. Understand the Tool’s Purpose

PKCS#11 isn’t a general-purpose key storage system. It’s a cryptographic token interface designed for hardware security modules and internal key generation.

2. Don’t Force Square Pegs into Round Holes

When a tool repeatedly resists your approach, step back and question whether you’re using it correctly.

3. Hybrid Approaches Are Often Better

Using the right tool for each job (PKCS#11 for secure generation, MbedTLS for external parsing) created a more robust solution than trying to make one tool do everything.

4. Security Models Matter

The hybrid approach actually improved our security posture:


Final Architecture

Our final AWS IoT Fleet Provisioning flow:

  1. WiFi Connection β†’ Basic connectivity established
  2. PKCS#11 Init β†’ Secure storage system ready
  3. Device Key Generation β†’ ECDSA P-256 keys in secure storage
  4. Claim Credentials Loading β†’ MbedTLS direct parsing
  5. MQTT Provisioning β†’ Uses hybrid credential approach
  6. Certificate Exchange β†’ Secure device certificate obtained

The key insight: Use each tool for what it’s designed to do, rather than trying to make one tool handle everything.


Impact

This solution eliminated the persistent CKR_ATTRIBUTE_VALUE_INVALID errors and created a more maintainable, secure architecture for ESP32 AWS IoT integration. The hybrid approach is now part of our standard IoT device provisioning pattern.