10.10. Bootstrapper (smart card bootstrap)

10.10.1. General description

The LwM2M specification defines Bootstrap from Smartcard, a mode of bootstrapping the device where the initial Bootstrap Information is stored on a smart card - typically the SIM card in case of devices that use cellular connectivity.

Standard file formats for this bootstrap information and related metadata are defined in Appendix G, of the LwM2M Technical Specification, and specifications for the secure channel between Smartcard and LwM2M Device Storage in Appendix H thereof.

While communicating with the smart card is considered outside the scope for the Anjay library, the “bootstrapper” feature, available commercially, implements a parser for the file format described in section G.3.4 of the Appendix G mentioned above.

Bootstrapping from smart card has a number of advantages, including:

  • Ability to store bootstrap information securely, increasing the device’s resilience against tampering

  • Possibility to remotely update bootstrap information using cellular infrastructure, without the need for a full firmware upgrade

  • For devices controlled by cellular carriers - ability to control the bootstrap information without contacting the device manufacturer

10.10.2. Technical documentation

10.10.2.1. Enabling the bootstrapper module

If the bootstrapper feature is available in your version of Anjay, it can be enabled at compile time by enabling the ANJAY_WITH_MODULE_BOOTSTRAPPER macro in the anjay_config.h file or, if using CMake, enabling the corresponding WITH_MODULE_bootstrapper CMake option.

When this feature is enabled, the anjay_bootstrapper() function can be used. The user will need to provide an implementation of avs_stream_t that allows the Anjay code to read the file contained on the smartcard. The avs_stream_simple_input_create() function from the avs_stream_simple_io.h header is likely to be the easiest way to provide such an implementation.

10.10.2.2. Bootstrap information generator tool

The generator.py application, located in the bootstrap directory of Anjay source package, allows generating binary files in the EF LwM2M_Bootstrap format that is supposed to be stored on smart cards, from a human-readable text file format.

The generator.py script, by default, processes the standard input and outputs to the standard output. However, an input file may be specified using the -c option, and the output file may be specified using the -o option.

Warning

The generator script is NOT intended to be safe to use with arbitrary input data. It is only intended for convenience when working with files created locally by trusted parties.

The input text files are evaluated as Python code, and as such, running the generator script with untrusted input may lead to arbitrary operations being performed on the computer.

The input file shall specify a dictionary according to Python syntax where:

  • On the top level, the keys shall be Object IDs, and the values shall be nested dictionaries describing the objects.

  • On the Object level, the keys shall be Instance IDs, and the values shall be nested dictionaries describing the instances.

  • On the Object Instance level, the keys shall be Resource IDs, and the values shall be either of:

    • Primitive types (numbers, booleans, strings or bytes objects) for single-instance resources

    • Lists of pairs (tuples of length 2) for Multiple-Instance Resources - in that case the first pair element shall be the Resource Instance ID, and the second one shall be the value of a primitive type

The constants from the OID and RID objects, as defined in the tests/integration/framework/test_utils.py file, may be used to make the keys more descriptive, as in the example input file (bootstrap/configs/basic):

{
    OID.Security: {
        1: {
            RID.Security.ServerURI          : 'coaps://eu.iot.avsystem.cloud:5684',
            RID.Security.Bootstrap          : False,
            RID.Security.Mode               : 0,  # PSK
            RID.Security.PKOrIdentity       : b'example-psk-identity',
            RID.Security.SecretKey          : b'3x@mpl3P5K53cr3tK3y',
            RID.Security.ShortServerID      : 1
        },
    },

    OID.Server: {
        1: {
            RID.Server.ShortServerID        : 1,
            RID.Server.Lifetime             : 86400,
            RID.Server.NotificationStoring  : False,
            RID.Server.Binding              : 'U'
        },
    }
}

The above example is equivalent to the following data written only using primitive values:

{
    0: {
        1: {
            0: 'coaps://eu.iot.avsystem.cloud:5684',
            1: False,
            2: 0,
            3: b'example-psk-identity',
            5: b'3x@mpl3P5K53cr3tK3y',
            10: 1
        }
    },
    1: {
        1: {
            0: 1,
            1: 86400,
            6: False,
            7: 'U'
        }
    }
}

The following example shell session illustrates the way of generating the binary bootstrap information file:

~/projects/anjay/bootstrap$ ./generator.py -c configs/basic -o basic_config.dat
~/projects/anjay/bootstrap$ hexdump -C basic_config.dat
00000000  00 02 00 7a 00 00 00 00  5e 08 01 5b c8 00 22 63  |...z....^..[.."c|
00000010  6f 61 70 73 3a 2f 2f 65  75 2e 69 6f 74 2e 61 76  |oaps://eu.iot.av|
00000020  73 79 73 74 65 6d 2e 63  6c 6f 75 64 3a 35 36 38  |system.cloud:568|
00000030  34 c1 01 00 c1 02 00 c8  03 14 65 78 61 6d 70 6c  |4.........exampl|
00000040  65 2d 70 73 6b 2d 69 64  65 6e 74 69 74 79 c8 05  |e-psk-identity..|
00000050  13 33 78 40 6d 70 6c 33  50 35 4b 35 33 63 72 33  |.3x@mpl3P5K53cr3|
00000060  74 4b 33 79 c1 0a 01 00  01 00 00 12 08 01 0f c1  |tK3y............|
00000070  00 01 c4 01 00 01 51 80  c1 06 00 c1 07 55        |......Q......U|
0000007e

10.10.2.3. Example code

Note

The full code for the following example can be found in the examples/commercial-features/CF-SmartCardBootstrap 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 bootstrapper feature.

The example is loosely based on the Installing mandatory Objects tutorial. However, since the bootstrap information will be loaded from a file, the setup_security_object() and setup_server_object() functions are no longer necessary, and the calls to them can be replaced with direct calls to anjay_security_object_install() and anjay_server_object_install():

int main(int argc, char *argv[]) {
    if (argc != 3) {
        avs_log(tutorial, ERROR, "usage: %s ENDPOINT_NAME BOOTSTRAP_INFO_FILE",
                argv[0]);
        return -1;
    }

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

    anjay_t *anjay = anjay_new(&CONFIG);
    if (!anjay) {
        avs_log(tutorial, ERROR, "Could not create Anjay object");
        return -1;
    }

    int result = 0;
    // Setup necessary objects
    if (anjay_security_object_install(anjay)
            || anjay_server_object_install(anjay)) {
        result = -1;
    }

    if (!result) {
        result = bootstrap_from_file(anjay, argv[2]);
    }

    if (!result) {
        result = anjay_event_loop_run(
                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));
    }

    anjay_delete(anjay);
    return result;
}

As you can see, the command line now expects a second argument with a name of the file containing the bootstrap information.

This file is loaded using the bootstrap_from_file() function, implemented as follows:

static int bootstrap_from_file(anjay_t *anjay, const char *filename) {
    avs_log(tutorial, INFO, "Attempting to bootstrap from file");

    avs_stream_t *file_stream =
            avs_stream_file_create(filename, AVS_STREAM_FILE_READ);

    if (!file_stream) {
        avs_log(tutorial, ERROR, "Could not open file");
        return -1;
    }

    int result = 0;
    if (avs_is_err(anjay_bootstrapper(anjay, file_stream))) {
        avs_log(tutorial, ERROR, "Could not bootstrap from file");
        result = -1;
    }

    avs_stream_cleanup(&file_stream);
    return result;
}

This shares similarities with the restore_objects_if_possible() function from the Persistence support tutorial.

As mentioned in the Enabling the bootstrapper module section above, to perform bootstrap using an actual smart card, the avs_stream_simple_input_create() function from the avs_stream_simple_io.h header could be used instead of the avs_stream_file_create() call that is used here to access regular file system.