API PKCS#11 para el almacenamiento de certificados

Azure Cloud HSM proporciona una sólida compatibilidad con el almacenamiento de certificados mediante la API PKCS#11. En este artículo se explica cómo usar la API PKCS#11 para administrar certificados X.509, incluida la creación, copia, eliminación y recuperación de atributos de certificado. Para obtener información general detallada sobre la configuración del almacenamiento de certificados, incluidos los requisitos previos y la configuración, consulte Azure almacenamiento de certificados HSM en la nube.

Uso de la API PKCS#11 para el almacenamiento de certificados X.509

Se han ampliado las siguientes API existentes en PKCS#11 para Azure Cloud HSM para agregar compatibilidad con certificados de clave pública X.509.

  • C_CreateObject: crea un nuevo objeto de certificado.
  • C_DestroyObject: elimina un objeto de certificado existente.
  • C_CopyObject: copia un objeto de certificado existente.
  • C_GetAttributeValue: obtiene el valor de uno o varios atributos de un objeto de certificado.
  • C_SetAttributeValue: actualiza el valor de uno o varios atributos de un objeto de certificado.
  • C_FindObjectsInit: inicia una búsqueda de objetos de certificado.
  • C_FindObjects: continúa una búsqueda de objetos de certificado.
  • C_FindObjectsFinal: finaliza una búsqueda de objetos de certificado.

C_CrearObjeto

La API de C_CreateObject funciona de forma similar para las claves y los certificados. Se espera una matriz de atributos, el número de atributos y un puntero a un identificador de objeto donde se almacenará el identificador generado.

A continuación se muestra un ejemplo de cómo usar el C_CreateObject.

int create_cert(CK_SESSION_HANDLE session_rw, CK_OBJECT_HANDLE_PTR cert_handle)
{
    // Dummy certificate data
    CK_BYTE certData[] = { 0x30, 0x82, 0x03, 0x08, 0x30, 0x82, 0x02, 0xD0 }; // Sample DER-encoded cert
    CK_ULONG certSize = sizeof(certData);

    CK_OBJECT_CLASS objClass = CKO_CERTIFICATE;
    CK_CERTIFICATE_TYPE certType = CKC_X_509;
    CK_BBOOL trueValue = CK_TRUE;
    CK_BYTE id[] = {123};

    // Dummy DER-encoded Subject Name (adjust as needed)
    CK_BYTE subjectData[] = { 0x30, 0x1D, 0x31, 0x1B, 0x30, 0x19, 0x06, 0x03,
                              0x55, 0x04, 0x03, 0x0C, 0x12, 'M', 'y', 'C', 'e',
                              'r', 't', 'i', 'f', 'i', 'c', 'a', 't', 'e', '-', 'B', 'b', 'j' };
    CK_ULONG subjectSize = sizeof(subjectData);

    CK_ATTRIBUTE certTemplate[] = {
        { CKA_CLASS, &objClass, sizeof(objClass) },
        { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) },
        { CKA_TOKEN, &trueValue, sizeof(trueValue) },
        { CKA_LABEL, "MyCertificate", 13 },
        { CKA_SUBJECT, subjectData, subjectSize },
        { CKA_ID, id, sizeof(id) },
        { CKA_VALUE, certData, certSize }
    };

    int n_attr = sizeof(certTemplate) / sizeof(CK_ATTRIBUTE);

    if ((func_list->C_CreateObject)(session_rw, certTemplate,
                                    n_attr, cert_handle)) {
        return FAILED;
    }
#ifdef DEBUG
    printf("The cert handle created is : %lu \n", *cert_handle);
#endif

    return CKR_OK;
}

Los atributos siguientes representan el conjunto mínimo necesario para crear un certificado X.509 en PKCS#11.

Nivel Atributo Tipo de datos Descripción
Atributos comunes CKA_CLASS CK_OBJECT_CLASS Clase de objeto (tipo)
Objetos de certificado CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE Tipo de certificado, CKC_X_509 para certificados de clave pública X.509
Objetos de certificado de clave pública X.509 CKA_SUBJECT Matriz de bytes Codificación DER del nombre del firmante del certificado
Objetos de certificado de clave pública X.509 CKA_VALUE Matriz de bytes Codificación BER del certificado

Los atributos siguientes son aplicables a los certificados de clave pública X.509.

Nivel Atributo Tipo de datos Descripción
Atributos comunes CKA_CLASS CK_OBJECT_CLASS Clase de objeto (tipo)
Objetos de almacenamiento CKA_TOKEN CK_BBOOL CK_TRUE si el objeto es un objeto de token; CK_FALSE si el objeto es un objeto de sesión. El valor predeterminado es CK_FALSE.
Objetos de almacenamiento CKA_PRIVATE CK_BBOOL CK_TRUE si el objeto es un objeto privado; CK_FALSE si el objeto es un objeto público. El valor predeterminado es específico del token y puede depender de los valores de otros atributos del objeto.
Objetos de almacenamiento CKA_MODIFIABLE CK_BBOOL CK_TRUE si se puede modificar el objeto, default es CK_TRUE.
Objetos de almacenamiento CKA_LABEL cadena de RFC2279 Descripción del objeto (valor predeterminado vacío).
Objetos de almacenamiento CKA_COPYABLE CK_BBOOL CK_TRUE si el objeto se puede copiar mediante C_CopyObject. El valor predeterminado es CK_TRUE. No se puede establecer en TRUE una vez establecido en FALSE.
Objetos de almacenamiento CKA_DESTROYABLE CK_BBOOL CK_TRUE si el objeto se puede destruir mediante C_DestroyObject. El valor predeterminado es CK_TRUE.
Objetos de certificado CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE Tipo de certificado, CKC_X_509 para certificados de clave pública X.509
Objetos de certificado CKA_TRUSTED CK_BBOOL El certificado puede ser confiable para la aplicación para la que fue creado.
Objetos de certificado CKA_CERTIFICATE_CATEGORY CKA_CERTIFICATE_CATEGORY (valor predeterminado CK_CERTIFICATE_CATEGORY_UNSPECIFIED)
Objetos de certificado CKA_CHECK_VALUE Matriz de bytes Suma de comprobación
Objetos de certificado CKA_START_DATE CK_DATE Fecha de inicio del certificado (valor predeterminado vacío)
Objetos de certificado CKA_END_DATE CK_DATE Fecha de finalización del certificado (valor predeterminado vacío)
Objetos de certificado CKA_PUBLIC_KEY_INFO Matriz de bytes Codificación DER de SubjectPublicKeyInfo para la clave pública contenida en este certificado (valor predeterminado vacío)
Objetos de certificado de clave pública X.509 CKA_SUBJECT Matriz de bytes Codificación DER del nombre del firmante del certificado
Objetos de certificado de clave pública X.509 CKA_ID Matriz de bytes Identificador de clave para el par de claves pública o privada (valor predeterminado vacío)
Objetos de certificado de clave pública X.509 CKA_ISSUER Matriz de bytes Codificación DER del nombre del emisor del certificado (valor predeterminado vacío)
Objetos de certificado de clave pública X.509 CKA_SERIAL_NUMBER Matriz de bytes Codificación DER del número de serie del certificado (valor predeterminado vacío)
Objetos de certificado de clave pública X.509 CKA_VALUE Matriz de bytes Codificación BER del certificado
Objetos de certificado de clave pública X.509 CKA_URL cadena de RFC2279 Si no está vacío, este atributo proporciona la dirección URL donde se puede obtener el certificado completo (el valor predeterminado está vacío).
Objetos de certificado de clave pública X.509 CKA_HASH_OF_SUBJECT_PUBLIC_KEY Matriz de bytes Hash de la clave pública del sujeto (predeterminado vacío). El algoritmo de hash se define mediante CKA_NAME_HASH_ALGORITHM
Objetos de certificado de clave pública X.509 CKA_HASH_OF_ISSUER_PUBLIC_KEY Matriz de bytes Hash de la clave pública del emisor (valor predeterminado vacío). El algoritmo de hash se define mediante CKA_NAME_HASH_ALGORITHM
Objetos de certificado de clave pública X.509 CKA_JAVA_MIDP_SECURITY_DOMAIN CK_JAVA_MIDP_SECURITY_DOMAIN Dominio de seguridad MIDP de Java. (valor predeterminado CK_SECURITY_DOMAIN_UNSPECIFIED)
Objetos de certificado de clave pública X.509 CKA_NAME_HASH_ALGORITHM CK_MECHANISM_TYPE Define el mecanismo usado para calcular CKA_HASH_OF_SUBJECT_PUBLIC_KEY y CKA_HASH_OF_ISSUER_PUBLIC_KEY. Si el atributo no está presente, el tipo tiene como valor predeterminado SHA-1.

C_DestroyObject

La API de C_DestroyObject toma un identificador de sesión y el identificador de objeto asociado al certificado que desea eliminar. Al invocar esta función, se quita el certificado especificado de la cuenta de Azure Blob Storage mediante la eliminación del blob JWS correspondiente denominado pkcs11_certificate_<cert-handle>.

A continuación se muestra un fragmento de código que muestra cómo llamar a C_DestroyObject para los certificados (el mismo enfoque se aplica a las claves).

int delete_cert(CK_SESSION_HANDLE session_rw, CK_OBJECT_HANDLE cert_handle)
{
    CK_RV rv = 0;

    rv = (func_list->C_DestroyObject)(session_rw, cert_handle);

    if(rv != CKR_OK) {
        printf("Deleting Certificate failed \n");
        return rv;
    }

    return rv;
}

C_CopyObject

La API de C_CopyObject toma un identificador de sesión, el identificador del objeto que se va a copiar y un puntero para recibir el identificador del objeto recién creado. Para mantener la paridad con la implementación de C_CopyObject para objetos clave en Azure Cloud HSM, la implementación del certificado no admite la modificación de atributos durante la operación de copia.

A continuación se muestra un fragmento de código de ejemplo que muestra cómo usar C_CopyObject para almacenar certificados.

int copy_cert(CK_SESSION_HANDLE session_rw, CK_OBJECT_HANDLE cert_handle,
                   CK_OBJECT_HANDLE_PTR copied_cert_handle)
{
    CK_RV rv = 0;

    rv = (func_list->C_CopyObject)(session_rw, cert_handle, NULL, 0, copied_cert_handle);

    if(rv != CKR_OK) {
        printf("Copying Certificate failed \n");
        return rv;
    }

    return rv;
}

C_GetAttributeValue

La API de C_GetAttributeValue permite la recuperación de todos los atributos enumerados en la sección API de C_CreateObject. Normalmente, esta API se invoca dos veces. La primera llamada determina el tamaño de atributos con longitudes desconocidas, como CKA_SUBJECT, que contiene el sujeto del certificado codificado en DER.

A continuación se muestra un ejemplo de cómo llamar a C_GetAttributeValue para obtener los tamaños de los atributos especificados.

int get_cert_attribute(CK_SESSION_HANDLE session_rw, CK_OBJECT_HANDLE_PTR cert_handle)
{
    CK_RV rv = 0;

    CK_ULONG cka_class = 0;
    CK_CERTIFICATE_TYPE cka_cert_type = 0;
    CK_BBOOL cka_token = 0;
    char* cka_label = NULL;
    char* cka_subject = NULL;
    CK_BYTE* cka_id = NULL;
    CK_BYTE* cka_value = NULL;

    // Determine size needed for each attribute by calling C_GetAttributeValue with NULL pointers
    // and zero as the length.

    CK_ATTRIBUTE cert_template[] = {
        { CKA_CLASS, NULL, 0 },
        { CKA_CERTIFICATE_TYPE, NULL, 0 },
        { CKA_TOKEN, NULL, 0 },
        { CKA_LABEL, NULL, 0 },
        { CKA_ID, NULL, 0 },
    };

    int n_attr = sizeof(cert_template) / sizeof(CK_ATTRIBUTE);

    rv = (func_list->C_GetAttributeValue)(session_rw, *cert_handle, cert_template, n_attr);

    if (rv != CKR_OK) {
        printf("C_GetAttributeValue failed with %ld\n", rv);
        return FAILED;
    }

Once the attribute sizes are known, memory can be allocated accordingly. A second call to the C_GetAttributeValue API is then made to retrieve the attribute values and store them in the allocated memory.

The image below shows a code snippet demonstrating this process based on the previous example:

    cka_label = (char*)malloc(cert_template[3].ulValueLen);
    if (cka_label == NULL) {
        printf("Memory allocation failed for CKA_LABEL.\n");
        rv = FAILED;
        goto end_test_get_cert_attribute;
    }

    cert_template[3].pValue = cka_label;

    if (cert_template[4].ulValueLen <= 0) {
        printf("CKA_ID size must be > 0.\n");
        rv = FAILED;
        goto end_test_get_cert_attribute;
    }

    cka_id = (CK_BYTE*)malloc(cert_template[4].ulValueLen);
    if (cka_id == NULL) {
        printf("Memory allocation failed for CKA_ID.\n");
        rv = FAILED;
        goto end_test_get_cert_attribute;
    }

    cert_template[4].pValue = cka_id;

    rv = (func_list->C_GetAttributeValue)(session_rw, *cert_handle, cert_template, n_attr);
    if (rv != CKR_OK) {
        printf("C_GetAttributeValue failed with %ld\n", rv);
        rv = FAILED;
        goto end_test_get_cert_attribute;
    }

C_SetAttributeValue

La API de C_SetAttributeValue ahora admite la actualización de objetos de certificado. Requiere el identificador de sesión, el identificador del certificado que se va a actualizar, una matriz de atributos y sus nuevos valores, y el número de atributos que se van a actualizar. Solo se admiten los atributos enumerados en la tabla de uso de API de C_CreateObject para las actualizaciones; si intenta modificar atributos no admitidos, se producirá un error en la llamada API.

A continuación se muestra un fragmento de código que muestra cómo se puede usar C_SetAttributeValue con objetos de certificado.

int set_cert_attribute(CK_SESSION_HANDLE session_rw, CK_OBJECT_HANDLE_PTR cert_handle)
{
    CK_RV rv = CKR_OK;
    CK_BBOOL falseValue = CK_FALSE;
    CK_BYTE subjectData[] = { 0x40, 0x41, 0x42, 0x43, 0x44 };
    CK_BYTE id[] = {254};
    CK_BYTE certData[] = { 0x10, 0x20, 0x30, 0x40, 0x50, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };

    CK_ATTRIBUTE certTemplateValid1[] = {
        { CKA_TOKEN, &falseValue, sizeof(falseValue) },
        { CKA_LABEL, "This is a new label", strlen("This is a new label") },
        { CKA_SUBJECT, subjectData, sizeof(subjectData) },
        { CKA_ID, id, sizeof(id) },
        { CKA_VALUE, certData, sizeof(certData) }
    };

    int n_attr = sizeof(certTemplateValid1) / sizeof(CK_ATTRIBUTE);
    rv = (func_list->C_SetAttributeValue)(session_rw, *cert_handle, certTemplateValid1, n_attr);
    if (rv != CKR_OK) {
        printf("test_set_cert_attribute failed when updating attribute values.\n");
        return FAILED;
    }

    return rv;
}

C_FindObjectsInit

La API C_FindObjects* ahora admite la búsqueda de objetos de certificado además de objetos clave. Una operación de búsqueda puede devolver identificadores de clave y certificado si la plantilla de búsqueda incluye atributos comunes a ambos tipos de objeto. La API de C_FindObjectsInit se ha mejorado para admitir todos los atributos relacionados con certificados enumerados en la tabla uso de API de C_CreateObject.

A continuación se muestra un ejemplo de una llamada de C_FindObjectsInit que realiza una búsqueda de certificados mediante los atributos CKA_CLASS, CKA_CERTIFICATE_TYPE y CKA_LABEL para buscar todos los objetos de certificado coincidentes.

int find_cert(CK_SESSION_HANDLE session_rw, CK_OBJECT_HANDLE cert_handle)
{
    CK_RV rv;
    CK_OBJECT_CLASS objClass = CKO_CERTIFICATE;
    CK_CERTIFICATE_TYPE certType = CKC_X_509;

    CK_ATTRIBUTE certTemplate[] = {
        { CKA_CLASS, &objClass, sizeof(objClass) },
        { CKA_CERTIFICATE_TYPE, &certType, sizeof(certType) },
        { CKA_LABEL, "MyCertificate", 13 }
    };

    // Step 1: Initialize the search
    rv = (func_list->C_FindObjectsInit)(session_rw, certTemplate, sizeof(certTemplate) / sizeof(CK_ATTRIBUTE));
    if (rv != CKR_OK) {
        printf("C_FindObjectsInit failed: 0x%lX\n", rv);
        return rv;
    }

C_FindObjects

Después de inicializar los parámetros de búsqueda, la API de C_FindObjects se usa para recuperar los identificadores de objeto coincidentes. También devuelve el número de objetos encontrados. Esta API toma el identificador de sesión, una matriz para almacenar los identificadores de objeto resultantes, el número máximo de objetos que se van a recuperar y un parámetro de salida que indica cuántos objetos se encontraron.

El fragmento de código siguiente muestra una llamada a C_FindObjects siguiendo la configuración de la plantilla de búsqueda en el ejemplo de C_FindObjectsInit anterior.

    // Step 2: Call C_FindObjects
    CK_OBJECT_HANDLE_PTR foundObjects = NULL;
    CK_ULONG maxObjects = 50;

    foundObjects = (CK_OBJECT_HANDLE_PTR)malloc(sizeof(CK_OBJECT_HANDLE) * maxObjects);
    if (!foundObjects) {
        printf("Memory allocation failed\n");
        return CKR_HOST_MEMORY;
    }
    CK_ULONG foundCount = 0;

    rv = (func_list->C_FindObjects)(session_rw, foundObjects, maxObjects, &foundCount);
    if (rv != CKR_OK) {
        printf("C_FindObjects failed: 0x%lX\n", rv);
        (func_list->C_FindObjectsFinal)(session_rw); // Ensure cleanup
        free(foundObjects);
        return rv;
    }

C_FindObjectsFinal

La API de C_FindObjectsFinal se comporta igual para los objetos de clave y certificado. Toma el identificador de sesión actual como argumento y realiza la limpieza de todas las estructuras y memoria relacionadas con la búsqueda asignadas durante la llamada a C_FindObjectsInit.

A continuación se muestra un fragmento de código que muestra cómo llamar a C_FindObjectsFinal para completar y limpiar el proceso de búsqueda iniciado por las API de C_FindObjectsInit y C_FindObjects.

    // Step 3: Finalize the search
    rv = (func_list->C_FindObjectsFinal)(session_rw);
    if (rv != CKR_OK) {
        printf("C_FindObjectsFinal failed: 0x%lX\n", rv);
        free(foundObjects);
        return rv;
    }
}

Configuración y ejecución de la aplicación PKCS#11 con Azure Cloud HSM

Azure Cloud HSM incluye código de aplicación de ejemplo para ayudar a validar el almacenamiento de certificados, disponible en la Guía de integración de almacenamiento de certificados de Azure Cloud HSM dentro del SDK de Azure Cloud HSM en GitHub.

Estructura de certificados en el almacenamiento

Comprobación de certificados en el almacenamiento

Después de una llamada correcta a la API de C_CreateObject(), el objeto de certificado recién creado aparecerá en la cuenta de Azure Blob Storage, tal como se especifica en el archivo azcloudhsm_application.cfg. El blob se denominará con el formato pkcs11_certificate_<object-handle>, como se muestra a continuación. A los objetos de certificado se les asignan identificadores de objetos que van desde 0xFFF00000 hasta 0xFFFFFFFF (intervalo decimal: 4.293.918.720 a 4.294.967.295), lo que permite admitir hasta 1.048.575 certificados.

Desde Azure Portal, así como desde la máquina virtual de Azure, puede ver los certificados almacenados.

Comprobación desde Azure Portal

Captura de pantalla que muestra los blobs de certificados almacenados en Azure Portal para Azure Cloud HSM.

Comprobación de la máquina virtual de Azure con la CLI de AZ instalada

chsmVMAdmin@AdminVM:~$ az login --identity
[
  {
    "environmentName": "AzureCloud",
    "homeTenantId": "",
    "id": "",
    "isDefault": true,
    "managedByTenants": [],
    "name": "Test Subscription",
    "state": "Enabled",
    "tenantId": "",
    "user": {
      "assignedIdentityInfo": "MSI",
      "name": "systemAssignedIdentity",
      "type": "servicePrincipal"
    }
  }
]

chsmVMAdmin@AdminVM:~$ az storage blob list \
  --account-name chsmstorage \
  --container-name certificates \
  --auth-mode login \
  --output table

Name                                  Blob Type    Blob Tier    Length    Content Type              Last Modified
-----------------------------------  -----------  -----------  --------  ------------------------  -------------------------
pkcs11_certificate_4293918720        BlockBlob    Hot          1305      application/octet-stream  2025-05-16T22:43:31+00:00
pkcs11_certificate_4293918721        BlockBlob    Hot          1305      application/octet-stream  2025-05-16T22:47:25+00:00
pkcs11_certificate_4293918722        BlockBlob    Hot          1305      application/octet-stream  2025-05-16T22:47:25+00:00
pkcs11_certificate_4293918723        BlockBlob    Hot          3452      application/octet-stream  2025-05-16T22:56:28+00:00

Descargar el blob o verlo en Azure Portal e inspeccionar su contenido revelará que el certificado se almacena como un token JWS (JSON Web Signature). El token sigue la estructura JWS estándar, que se divide en el formato siguiente:

chsmVMAdmin@AdminVM:~$ az storage blob list \
  --account-name chsmstorage \
  --container-name certificates \
  --auth-mode login \
  --output table

Name                                  Blob Type    Blob Tier    Length    Content Type              Last Modified
-----------------------------------  -----------  -----------  --------  ------------------------  -------------------------
pkcs11_certificate_4293918720        BlockBlob    Hot          1305      application/octet-stream  2025-05-16T22:43:31+00:00
pkcs11_certificate_4293918721        BlockBlob    Hot          1305      application/octet-stream  2025-05-16T22:47:25+00:00
pkcs11_certificate_4293918722        BlockBlob    Hot          1305      application/octet-stream  2025-05-16T22:47:25+00:00
pkcs11_certificate_4293918723        BlockBlob    Hot          3452      application/octet-stream  2025-05-16T22:56:28+00:00

chsmVMAdmin@AdminVM:~$ az storage blob download \
  --account-name chsmstorage \
  --container-name certificates \
  --name pkcs11_certificate_4293918723 \
  --file pkcs11_certificate_4293918723.crt \
  --auth-mode login
Finished[########################################] 100.0000%
{
  "container": "certificates",
  "content": ""
}

chsmVMAdmin@AdminVM:~$ cat pkcs11_certificate_4293918723.crt
eyJhbgGciOiJSUzUxMiIsImp... (base64-encoded certificate continues)

Pasos siguientes