10.3. Enrollment over Secure Transport

10.3.1. General description

Enrollment over Secure Transport (EST, RFC 7030), to quote its specification, is a certificate management protocol targeting Public Key Infrastructure (PKI) clients that need to acquire client certificates and associated Certification Authority (CA) certificates. It also supports client-generated public/private key pairs as well as key pairs generated by the CA.

EST-coaps (draft-ietf-ace-coap-est-18, also referenced in the LwM2M Core TS) is a protocol that uses CoAP instead of HTTP for message exchanges, which allows constrained, low-resource devices to use existing EST functionality for provisioning certificates.

An implementation of the EST-coaps client is provided as a commercial feature in Anjay. This enhances security provided by the (D)TLS Certificate mode, by allowing the device to generate a key pair locally, with the private key never leaving the device, as well as allowing the server to update the device’s PKIX trust store without a full firmware update.

By using the EST-coaps protocol, you can:

  • Achieve the highest level of security available for the LwM2M protocol, especially when paired with Hardware Security Module support

    • The chain of trust for client and server certificates can be configured in a more robust way than with the standard Certificate mode

    • Generating client keys on the device means that they do not leak even if the server is compromised

  • Make key enrollment procedures in production environments streamlined, simpler and more scalable

    • Only the bootstrap credentials need to be provided at manufacture time, while keys for the main credentials can be provisioned remotely at first use

    • Thanks to standardized means of updating the trust store, including certificate revocation lists, there are more options for updating the security credentials and handling cases of compromised keys

10.3.2. Supported features

The following features are implemented:

  • Distribution of CA Certificates (/est/crts), supporting both a single raw X.509 certificate and PKCS#7 cert-only as response payload formats

  • Enrollment of Clients (/est/sen), supporting both raw X.509 and PKCS#7 cert-only as response payload formats

  • Re-enrollment of Clients (/est/sren) - the client certificate validity time is tracked and the /est/sren request is issued automatically when the certificate is nearing expiration, with the exact amount of time being configurable

  • The keys and certificates can be stored in local memory, processed in software and persisted to local non-volatile storage, or processed and stored by the Hardware Security Module (requires HSM integration code, some are available as additional commercial features)

It is assumed that the same CoAP endpoint is used as both the LwM2M Bootstrap Server and the EST-coaps server.

10.3.3. Technical documentation

10.3.3.1. Enabling EST support

If support for EST is available in your version of Anjay, it can be enabled at compile time by enabling the ANJAY_WITH_EST macro in the anjay_config.h file or, if using CMake, enabling the corresponding WITH_EST CMake option.

When enabled at compile time, EST support will be automatically available. However, for the EST requests to be made, the following conditions must be met:

  • LwM2M Bootstrap Server Account must exist; EST requests (other than /est/sren) are only performed after receiving the Bootstrap Finish request over the Bootstrap interface (see also: Bootstrap awareness)

  • At least one LwM2M Server Account (provisioned by the Bootstrap Server) must be configured with the Security Mode resource (/0/*/2) set to 4 (Certificate mode with EST).

Please note that the EST-coaps specification requires that the connection used for EST requests (which is the Bootstrap Server Account in case of LwM2M) is configured to use the certificate mode as well. Anjay does not enforce this requirement, but some LwM2M Bootstrap Servers (including Coiote DM from AVSystem) may refuse performing EST operations over a transport that does not use certificates. See DTLS connection using certificates for more information on configuring certificate-based security.

You may additionally set the Bootstrap Server Account to use the ANJAY_SECURITY_EST mode instead of ANJAY_SECURITY_CERTIFICATE, which will enable the use of EST-provisioned client certificate for subsequent connections to the bootstrap server (the certificate from the data model is used otherwise).

In this default configuration, the EST subsystem will use the following configuration:

  • /est/sren requests are issued 30 days before the certificate expiration date, or after 90% of the time between its provisioning and expiration dates has passed, whichever is later,

  • /est/crts requests are performed if there is at least one Server Account provisioned to use the EST mode; the resulting trust store is additionally used for subsequent connections with the Bootstrap Server as well,

  • keys and certificates provisioned using EST are stored in local memory and processed in software.

These settings can be changed when initializing the Anjay object, see the Configuring EST behavior section for details.

Important

For EST logic to work properly across restarts of the client application, persistence of EST state needs to be implemented. This is explained in the section below.

10.3.3.2. Persisting EST state

Note

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

The EST state needs to be kept in sync with the state of the Security object, so it is usually a good idea to store those two blocks of data in the same place. Thus, the persist and restore routines from the Persistence support tutorial can be extended as follows:

if (avs_is_err(anjay_security_object_persist(anjay, file_stream))) {
    avs_log(tutorial, ERROR, "Could not persist Security Object");
    goto finish;
}

if (avs_is_err(anjay_server_object_persist(anjay, file_stream))) {
    avs_log(tutorial, ERROR, "Could not persist Server Object");
    goto finish;
}

if (avs_is_err(anjay_attr_storage_persist(anjay, file_stream))) {
    avs_log(tutorial, ERROR, "Could not persist LwM2M attribute storage");
    goto finish;
}

if (avs_is_err(anjay_est_state_persist(anjay, file_stream))) {
    avs_log(tutorial, ERROR, "Could not persist EST state");
    goto finish;
}
if (avs_is_err(anjay_security_object_restore(anjay, file_stream))) {
    avs_log(tutorial, ERROR, "Could not restore Security Object");
    goto finish;
}

if (avs_is_err(anjay_server_object_restore(anjay, file_stream))) {
    avs_log(tutorial, ERROR, "Could not restore Server Object");
    goto finish;
}

if (avs_is_err(anjay_attr_storage_restore(anjay, file_stream))) {
    avs_log(tutorial, ERROR, "Could not restore LwM2M attribute storage");
    goto finish;
}

if (avs_is_err(anjay_est_state_restore(anjay, file_stream))) {
    avs_log(tutorial, ERROR, "Could not restore EST state");
    goto finish;
}

In the example, the state is persisted when exiting the application, but it might be a good idea to do it periodically or after each change. The anjay_est_state_is_ready_for_persistence() function can be useful for this purpose. It is intended to be used in a similar manner to functions such as anjay_security_object_is_modified(), however it will also return false if EST operation is in progress, because in contrast to data model objects, the EST state cannot be rolled back.

The simplest way to use these functions would be to skip persisting data if there is no new data ready to be persisted:

if ((!anjay_security_object_is_modified(anjay)
     && !anjay_server_object_is_modified(anjay)
     && !anjay_attr_storage_is_modified(anjay))
        || !anjay_est_state_is_ready_for_persistence(anjay)) {
    avs_log(tutorial, INFO,
            "Persistence not necessary - NOT persisting objects");
    return 0;
}

However, please take note of the following warning:

Important

If anjay_est_state_is_ready_for_persistence() returns false, the subsequent call to anjay_est_state_persist() will return an error. This error needs to be handled, and to make sure that some valid data is persisted, you should make sure that the original state of the persistence file (or flash memory block, etc.) is restored in that case.

In the example, this is done by never calling anjay_est_state_is_ready_for_persistence() in such a situation.

Important

Unless HSM-based security is used, the persistence stream will contain cryptographic credentials, including private keys. Please consider this when choosing the storage location for this data.

10.3.3.3. Configuring EST behavior

The EST example also contains a sample non-default configuration of the EST subsystem:

const anjay_configuration_t CONFIG = {
    .endpoint_name = argv[1],
    .in_buffer_size = 4000,
    .out_buffer_size = 4000,
    .msg_cache_size = 4000,

    .trust_store_certs = avs_crypto_certificate_chain_info_from_file(
            "/etc/ssl/certs/ca-certificates.crt"),
    .est_reenroll_config = &(const anjay_est_reenroll_config_t) {
        .enable = true,
        .nominal_usage = 0.8,
        .max_margin = avs_time_duration_from_scalar(7, AVS_TIME_DAY)
    },
    .est_cacerts_policy = ANJAY_EST_CACERTS_FOR_EST_SECURITY
};

g_anjay = anjay_new(&CONFIG);

Here’s a quick description of the settings used:

10.3.3.4. Using EST with hardware security modules

Note

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

When Anjay also has the hardware security module support (available as a separate commercial feature) compiled in, the EST module can easily be configured to use it for generating and storing the security credentials.

The variant of the example application that uses PKCS#11 hardware cryptography engine specifies additional fields in the anjay_configuration_t structure:

char EST_CACERTS_ADDRESS_BUF[256];

srand(time(NULL));

const anjay_configuration_t CONFIG = {
    .endpoint_name = argv[1],
    .in_buffer_size = 4000,
    .out_buffer_size = 4000,
    .msg_cache_size = 4000,

    .trust_store_certs = avs_crypto_certificate_chain_info_from_file(
            "/etc/ssl/certs/ca-certificates.crt"),
    .est_reenroll_config = &(const anjay_est_reenroll_config_t) {
        .enable = true,
        .nominal_usage = 0.8,
        .max_margin = avs_time_duration_from_scalar(7, AVS_TIME_DAY)
    },
    .est_cacerts_policy = ANJAY_EST_CACERTS_FOR_EST_SECURITY,

    .est_engine_key_address =
            "pkcs11:token=MyToken;object=EstClientKey;pin-value=1234",
    .est_engine_cert_address =
            "pkcs11:token=MyToken;object=EstClientCert;pin-value=1234",
    .est_engine_cacerts_address_gen_cb = est_crts_address_gen,
    .est_engine_cacerts_address_gen_cb_arg = EST_CACERTS_ADDRESS_BUF
};

Important

The “addresses” (also sometimes referred to as “query strings”) in this example are PKCS#11 URIs. However, the format of these strings is dependent on the hardware security engine used. Please refer to the documentation of the module you’re using (PKCS#11, PSA, IoT SAFE etc.), or its implementation if you have implemented one yourself.

Note

When the examples are compiled automatically (e.g. using make commercial_feature_examples), the Anjay library is configured to use the PKCS#11 backend.

To run it, the PKCS#11 module library needs to be specified via the PKCS11_MODULE_PATH environment variable, for example:

env PKCS11_MODULE_PATH=/usr/lib/softhsm/libsofthsm2.so ./anjay-est-pkcs11 {endpoint-name}

Here’s a quick description of the settings used:

  • est_engine_key_address specifies the address at which the client’s private key will be generated during the EST enrollment process

    • Note: in case of PKCS#11, two objects will be generated, separate for the private and public keys; these will share the same label

  • est_engine_cert_address specifies the address at which the client’s public certificate will be stored after having been provisioned by the EST server

  • est_engine_cacerts_address_gen_cb is set to a pointer to the function that will be used for generating addresses at which the trusted certificates provisioned via the /est/crts operation will be stored

    • This generation function is necessary because there might be multiple trusted certificates, so a single address is not sufficient

    • est_engine_cacerts_address_gen_cb_arg can be used to specify any opaque argument that will be passed to that function; in this example it points to a buffer that will be used to store the generated addresses, but you are free to use this argument in any way you wish

    • In this example application, the following function is used:

      static const char *est_crts_address_gen(void *arg,
                                              const void *x509_der_data,
                                              size_t x509_der_data_size) {
          (void) x509_der_data;
          (void) x509_der_data_size;
      
          char *buf = (char *) arg;
          sprintf(buf, "pkcs11:token=MyToken;object=CaCert%d;pin-value=1234", rand());
          return buf;
      }
      
    • The example function generates the addresses based on a random number; however, the actual DER-encoded certificate is passed to this function as well so that you may choose to generate the address based on the contents of the certificate

    • The returned string is copied by the library, so reusing the same buffer for multiple calls is OK. See anjay_est_engine_cacert_address_gen_t for details on this callback’s semantics.

Note

You may specify only some of the above arguments, and leave others as NULL. Software handling will be used for any credentials for which hardware engine addresses are not provided.

10.3.3.4.1. HSM-based EST and persistence

When using hardware security engine for EST, the persistence stream will only contain references (addresses) to the security credentials stored there.

The EST module manages the lifecycle of these credentials, so that old ones are automatically removed when e.g. a new certificate is re-enrolled.

Important

To ensure that the credentials stored on the HSM and the references in the persistence stream are kept in sync, all EST-related security credentials stored in the HSM will be removed from it when cleaning up the Anjay object, unless there have been no changes to them since the last call to anjay_est_state_persist() or anjay_est_state_restore().