10.5. Hardware Security Module

Hardware Security Module (HSM) is a piece of hardware designed to increase security of the device by keeping the vulnerable data safe (mainly private or secret keys, but also certificates) and performing operations like:

  • key generation,

  • signing/verification,

  • encryption/decryption,

while the used private and secret keys are not leaving their secure memory. Because such idea might have a lot of various implementations, some generic APIs were created. Commercial feature of Anjay, HSM, includes integrations with two of them: PKCS11 and PSA.

To increase the safety of the IoT client even more HSMs are often used to maintain credentials used in Enrollment over Security Transport (EST). To make this easier, when Anjay is used with both HSM and Enrollment over Secure Transport commercial features it includes also an additional integration between them, which allows to easily setup such a secure client (see CF-EST-PKCS11 example).

10.5.1. Supported features

The following features are implemented:

  • integration with PKCS11 API - working with both OpenSSL (using PKI) and mbed TLS (using PKI),

  • integration with Platform Security Architecure (PSA) API - working with mbed TLS (using PKI or PSK),

  • integration with the EST feature (see Enrollment over Secure Transport) which allows to keep the private keys and certificates used by EST operations.

10.5.2. Technical documentation

10.5.2.1. Enabling Hardware Security Module support

The integrations with the HSM APIs (i.e. PKCS11 and PSA) are available as separate commercial features and the first requirement to make it work is to use a version of Anjay containing them.

From Anjay’s perspective PKCS11 and PSA engines are used in quite similar way and are considered as backends for the APIs enabled by the corresponding macros:

  • AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE - for PKI support,

  • AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE - for PSK support.

To enable support for PKCS11 backend one has to enable (depending on which cryptographic library is used):

  • AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE - while using mbed TLS,

  • AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE - while using OpenSSL.

In the case of PSA only mbed TLS support is available, so the proper macro is AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE. Additionally, the used mbed TLS version must be compiled with MBEDTLS_USE_PSA_CRYPTO flag. If there is PSA Protcted Storage API available and you want Anjay to be able to use it, you need also AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE to be defined.

An alternative method to enable a certain integration is enabling the proper macro in the avs_commons_config.h file (where “proper” means that its name consists of AVS_COMMONS_ and corresponding CMake option).

There are also a few macros which can be defined for the support of the HSM-stored credentials in Anjay:

  • ANJAY_WITH_SECURITY_STRUCTURED - enable support for handling complex types of security credentials in the data model using structured <c>avs_crypto</c> types. In particular, it allows to keep credentials in the form of their HSM address.

  • ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT - enables the automatic moving to HSM the credentials stored in the built-in Anjay Security object.

  • ANJAY_WITH_EST_ENGINE_SUPPORT - when the EST commercial feature is available, it enables the support for storing on HSM the credentials used during EST operations.

As before, this macros might be defined directly in anjay_config.h file, or set using CMake.

10.5.2.2. Addressing Hardware Security Module objects

Objects in PSA and PKCS11 are addressed in a slightly different ways - in PSA an object is called key and to access it just its identifier is required, which is simply an integer. Thus, PSA query used by Anjay consists of a single parameter: kid=KEY_ID, where KEY_ID is the hex-encoded key identifier. When PSA Protected Storage API is available and enabled in Anjay (it needs the macro AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE to be defined), it may keep raw data addressed with some ID. In which case query will be quite similar: uid=ID.

Queries which are used to address the PKCS11 objects were defined in PKCS11 RFC as PKCS11 URI. They may contain a lot of various fields, but usually three of them are used in Anjay clients: token, pin and label (or id instead of label). In such case, the PKCS11 query looks like: pkcs11:token=TOKEN;object=LABEL;pin-value=PIN.

10.5.2.3. Using security objects already stored in HSM

Note

The full code for the following example can be found in the examples/commercial-features/CF-PSA-PSK, examples/commercial-features/CF-PSA-PKI and examples/commercial-features/CF-PKCS11 directories in Anjay sources. Note that to compile and run it, you need to have access to a commercial version of Anjay that includes HSM feature.

When using HSM to store the Security objects, they shouldn’t be stored in the application memory, so they can’t be kept in the buffers in the security object instances, so some other kind of structures is needed for them. For this purpose an additional set of fields, which are able to store them, was introduced in anjay_security_instance_t (to make them available ANJAY_WITH_SECURITY_STRUCTURED macro must be defined):

/** Resource: Public Key Or Identity;
* This is an alternative to the @p public_cert_or_psk_identity and
* @p psk_identity fields that may be used only if @p security_mode is
* either @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is
* also an error to specify non-empty values for more than one of these
* fields at the same time. */
avs_crypto_certificate_chain_info_t public_cert;
/** Resource: Secret Key;
* This is an alternative to the @p private_cert_or_psk_key and @ref psk_key
* fields that may be used only if @p security_mode is either
* @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is also an
* error to specify non-empty values for more than one of these fields at
* the same time. */
avs_crypto_private_key_info_t private_key;
/** Resource: Public Key Or Identity;
* This is an alternative to the @p public_cert_or_psk_identity and
* @ref public_cert fields that may be used only if @p security_mode is
* @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values
* for more than one of these fields at the same time. */
avs_crypto_psk_identity_info_t psk_identity;
/** Resource: Secret Key;
* This is an alternative to the @p private_cert_or_psk_key and
* @ref private_key fields that may be used only if @p security_mode is
* @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values
* for more than one of these fields at the same time. */
avs_crypto_psk_key_info_t psk_key;

There is also a set of functions which can turn an HSM query pointing to the required object stored on the HSM to the struct which can be used by the instance of the Security object:

  • avs_crypto_certificate_chain_info_from_engine() - creates certificate chain descriptor used later on to load a certificate from the engine,

  • avs_crypto_private_key_info_from_engine() - creates private key descriptor used later on to load private key from the engine,

  • avs_crypto_psk_key_info_from_engine() - creates pre-shared key descriptor used later on to load pre-shared key from the engine,

  • avs_crypto_psk_identity_info_from_engine() - creates pre-shared key identity descriptor used later on to load pre-shared key identity from the engine.

One may notice that the first two of them (as well as first two mentioned anjay_security_instance_t fields) are used when the connection is secured using PKI, while the latter are used with PSK. Let’s see how they work with a Security object instance in PKI mode in the PKCS11 example:

#define KEY_QUERY "pkcs11:token=MyToken;object=ClientKey;pin-value=1234"
#define CERTIFICATE_QUERY \
    "pkcs11:token=MyToken;object=ClientCert;pin-value=1234"

// ...

const anjay_security_instance_t security_instance = {
    .ssid = 1,
    .server_uri = "coaps://eu.iot.avsystem.cloud:5684",
    .security_mode = ANJAY_SECURITY_CERTIFICATE,
    .public_cert = avs_crypto_certificate_chain_info_from_engine(
            CERTIFICATE_QUERY),
    .private_key = avs_crypto_private_key_info_from_engine(KEY_QUERY)
};

The only thing that must be changed to use keys and certificates on HSM which uses PSA API are the queries for the key and the certificate:

#define KEY_QUERY "kid=0x00000001"
#define CERTIFICATE_QUERY "kid=0x00000002"

// ...

const anjay_security_instance_t security_instance = {
    .ssid = 1,
    .server_uri = "coaps://eu.iot.avsystem.cloud:5684",
    .security_mode = ANJAY_SECURITY_CERTIFICATE,
    .public_cert = avs_crypto_certificate_chain_info_from_engine(
            CERTIFICATE_QUERY),
    .private_key = avs_crypto_private_key_info_from_engine(KEY_QUERY),
};

And in the similar way, we can use PSA for keeping credentials for the PSK mode (CF-PSA-PSK example):

#define IDENTITY_QUERY "kid=0x00000001"
#define KEY_QUERY "kid=0x00000002"

// ...

const anjay_security_instance_t security_instance = {
    .ssid = 1,
    .server_uri = "coaps://eu.iot.avsystem.cloud:5684",
    .security_mode = ANJAY_SECURITY_PSK,
    .psk_identity =
            avs_crypto_psk_identity_info_from_engine(IDENTITY_QUERY),
    .psk_key = avs_crypto_psk_key_info_from_engine(KEY_QUERY),
};

10.5.2.4. Storing and removing objects from HSM

Note

The full code for the following example can be found in the examples/commercial-features/CF-PSA-management directory in Anjay sources. Note that to compile and run it, you need to have access to a commercial version of Anjay that includes HSM feature.

The avs_commons provides following functions for storing PKI private keys and certificates in the HSM:

  • avs_crypto_pki_engine_key_store(),

  • avs_crypto_pki_engine_certificate_store()

and corresponding functions for their removal:

  • avs_crypto_pki_engine_key_rm(),

  • avs_crypto_pki_engine_certificate_rm().

An example of how they can be used to manage PKI objects is shown in CF-PSA-management example:

if (!strcmp(argv[2], "pkey")) {
    if (avs_is_err(avs_crypto_pki_engine_key_rm(query))) {
        avs_log(tutorial, ERROR, "Private key removal failed");
        return -1;
    }
} else if (!strcmp(argv[2], "certificate")) {
    if (avs_is_err(avs_crypto_pki_engine_certificate_rm(query))) {
        avs_log(tutorial, ERROR, "Certificate removal failed");
        return -1;
    }
} else if (!strcmp(argv[2], "psk_key")) {

// ...

if (!strcmp(argv[2], "pkey")) {
    avs_crypto_private_key_info_t key_info =
            avs_crypto_private_key_info_from_file(argv[4], NULL);
    if (avs_is_err(avs_crypto_pki_engine_key_store(
                query, &key_info, NULL))) {
        avs_log(tutorial, ERROR, "Storing private key failed");
        return -1;
    }
} else if (!strcmp(argv[2], "certificate")) {
    avs_crypto_certificate_chain_info_t cert_info =
            avs_crypto_certificate_chain_info_from_file(argv[4]);
    if (avs_is_err(avs_crypto_pki_engine_certificate_store(
                query, &cert_info))) {
        avs_log(tutorial, ERROR, "Storing certificate failed");
        return -1;
    }
} else if (!strcmp(argv[2], "psk_key")) {

Analogous set of functions is also available for the PSK cryptography:

  • avs_crypto_psk_engine_key_store(),

  • avs_crypto_psk_engine_identity_store(),

  • avs_crypto_psk_engine_key_store() and

  • avs_crypto_psk_engine_identity_store().

There is also a similar example of their usage in the CF-PSA-management example.

In PKI engine API there are also functions for private key generation, avs_crypto_pki_engine_key_gen, but to use the generated key, we need to prepare a certificate for it. This is done typically during the EST enrollment. Please see the EST feature documentation for more information on this topic.

10.5.2.5. Use the HSM in the implicit way

Note

The full code for the following example can be found in the examples/commercial-features/CF-PSA-boostrap directory in Anjay sources. Note that to compile and run it, you need to have access to a commercial version of Anjay that includes HSM feature.

An alternative, and probably more elegant, approach to store and use credentials on HSM is to use the function anjay_security_object_install_with_hsm to install the Security object and then use it in the same way as the standard one - it will move the provided credentials to HSM memory. This function, comparing to default anjay_security_object_install, needs an additional argument - hsm_config which is basically a set of callbacks (and their arguments) required to generate the HSM adresses for new HSM objects:

/**
* Configuration of the callbacks for generating the query string addresses
* under which different kinds of security credentials will be stored on the
* hardware security engine.
*/
typedef struct {
    /**
    * Callback function that will be called whenever a public client
    * certificate needs to be stored in an external security engine.
    *
    * If NULL, public client certificates will be stored in main system memory
    * unless explicitly requested via either EST or the <c>public_cert</c>
    * field in @ref anjay_security_instance_t.
    */
    anjay_security_hsm_query_cb_t *public_cert_cb;

    /**
    * Opaque argument that will be passed to the function configured in the
    * <c>public_cert_cb</c> field.
    *
    * If <c>public_cert_cb</c> is NULL, this field is ignored.
    */
    void *public_cert_cb_arg;

    /**
    * Callback function that will be called whenever a client private key needs
    * to be stored in an external security engine.
    *
    * If NULL, client private keys will be stored in main system memory unless
    * explicitly requested via either EST or the <c>private_key</c> field in
    * @ref anjay_security_instance_t.
    */
    anjay_security_hsm_query_cb_t *private_key_cb;

    /**
    * Opaque argument that will be passed to the function configured in the
    * <c>private_key_cb</c> field.
    *
    * If <c>private_key_cb</c> is NULL, this field is ignored.
    */
    void *private_key_cb_arg;

    /**
    * Callback function that will be called whenever a PSK identity for use
    * with the main connection needs to be stored in an external security
    * engine.
    *
    * If NULL, PSK identities for use with the main connection will be stored
    * in main system memory unless explicitly requested via the
    * <c>psk_identity</c> field in @ref anjay_security_instance_t.
    */
    anjay_security_hsm_query_cb_t *psk_identity_cb;

    /**
    * Opaque argument that will be passed to the function configured in the
    * <c>psk_identity_cb</c> field.
    *
    * If <c>psk_identity_cb</c> is NULL, this field is ignored.
    */
    void *psk_identity_cb_arg;

    /**
    * Callback function that will be called whenever a PSK key for use with the
    * main connection needs to be stored in an external security engine.
    *
    * If NULL, PSK keys for use with the main connection will be stored in main
    * system memory unless explicitly requested via the <c>psk_key</c> field in
    * @ref anjay_security_instance_t.
    */
    anjay_security_hsm_query_cb_t *psk_key_cb;

    /**
    * Opaque argument that will be passed to the function configured in the
    * <c>psk_key_cb</c> field.
    *
    * If <c>psk_key_cb</c> is NULL, this field is ignored.
    */
    void *psk_key_cb_arg;
#    ifdef ANJAY_WITH_SMS
    /**
    * Callback function that will be called whenever a PSK identity for use
    * with SMS binding needs to be stored in an external security engine.
    *
    * If NULL, PSK identities for use with SMS binding will be stored in main
    * system memory unless explicitly requested via the <c>sms_psk_identity</c>
    * field in @ref anjay_security_instance_t.
    */
    anjay_security_hsm_query_cb_t *sms_psk_identity_cb;

    /**
    * Opaque argument that will be passed to the function configured in the
    * <c>sms_psk_identity_cb</c> field.
    *
    * If <c>sms_psk_identity_cb</c> is NULL, this field is ignored.
    */
    void *sms_psk_identity_cb_arg;

    /**
    * Callback function that will be called whenever a PSK key for use with SMS
    * binding needs to be stored in an external security engine.
    *
    * If NULL, PSK keys for use with SMS binding will be stored in main system
    * memory unless explicitly requested via the <c>sms_psk_key</c> field in
    * @ref anjay_security_instance_t.
    */
    anjay_security_hsm_query_cb_t *sms_psk_key_cb;

    /**
    * Opaque argument that will be passed to the function configured in the
    * <c>sms_psk_key_cb</c> field.
    *
    * If <c>sms_psk_key_cb</c> is NULL, this field is ignored.
    */
    void *sms_psk_key_cb_arg;
#    endif // ANJAY_WITH_SMS
} anjay_security_hsm_configuration_t;

This approach is particularly useful when using Bootstrap server - in this case Anjay will automatically move the credentials received from the Bootstrap server to the HSM memory. This is what we can see in action in CF-PSA-boostrap example. As you can see, the changes we need to make are quite subtle:

anjay_security_hsm_configuration_t HSM_CONFIG = {
    .psk_identity_cb = generate_hsm_address,
    .psk_key_cb = generate_hsm_address
};

// ...

if (anjay_security_object_install_with_hsm(anjay, &HSM_CONFIG)) {
    return -1;
}

Where generate_hsm_address is a function for PSA address generation, in this case pseudo-random:

static const char *generate_hsm_address(anjay_iid_t iid,
                                        anjay_ssid_t ssid,
                                        const void *data,
                                        size_t data_size,
                                        void *arg) {
    (void) iid;
    (void) ssid;
    (void) data;
    (void) data_size;
    (void) arg;

    static size_t offset = 0ul;
    static char buffer[1024];

    if (offset + sizeof(HSM_TEMPLATE) > sizeof(buffer)) {
        avs_log(tutorial, ERROR, "Wrong HSM address");
        return NULL;
    }

    static avs_rand_seed_t SEED;
    if (!SEED) {
        SEED = (avs_rand_seed_t) time(NULL);
    }

    char *result = buffer + offset;
    offset += sizeof(HSM_TEMPLATE);
    strcpy(result, HSM_TEMPLATE);

    for (int i = 0; result[i]; i++) {
        if (result[i] == '.') {
            result[i] = HSM_ALPHABET[(size_t) avs_rand_r(&SEED)
                                    % (sizeof(HSM_ALPHABET) - 1)];
        }
    }

    return result;
}