diff --git a/.mbedignore b/.mbedignore index 7f81d54..cbe5036 100644 --- a/.mbedignore +++ b/.mbedignore @@ -2,3 +2,5 @@ __*/* pal-platform/* storage-selector/littlefs/* delta-tool/* +venv/* +utils/* diff --git a/CHANGELOG.md b/CHANGELOG.md index ece62b8..d3a6188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ # Changelog for Pelion Device Management Client example application +## Release 4.12.0 (01.03.2022) + +- Updated to Mbed OS 6.15.1. +- [Linux] Add a new `define_lwm2m_compliant.txt` that enables communication with a LwM2M compliant service / interoperability (IoP) testing. +- [Mbed OS] Add a new `mbed_app_lwm2m_compliant.json` that enables communication with a LwM2M compliant service / interoperability (IOP) testing with FRDM K64F device. + ## Release 4.11.2 (01.12.2021) -Updated to Mbed OS 6.15.0. +- Updated to Mbed OS 6.15.0. ## Release 4.11.1 (11.10.2021) diff --git a/README.md b/README.md index 4c7b19f..5f7b4cb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,179 @@ -The full documentation for this example is [available on our documentation site](https://www.pelion.com/docs/device-management/current/connecting/device-management-client-tutorials.html). +# Device Management Client reference application for connectivity -You can report concerns about the documentation or this SW as issues to [this GitHub repository](https://github.com/PelionIoT/mbed-cloud-client-example/issues). +The [`mbed-cloud-client-example`](https://github.com/PelionIoT/mbed-cloud-client-example) is a reference application that uses [Pelion Device Management Client library](https://github.com/PelionIoT/mbed-cloud-client) and demonstrates how to build a connectivity application. + +## Device Management Client connection tutorial + +This tutorial builds and flashes a Device Management Client application using either Linux (running on a PC) or Mbed OS. +The application can then connect to a standard OMA Lightweight M2M server. +The application uses developer mode that relies on a developer certificate, which you add to your software binary to allow test devices to connect to the server. +In the production, you should use the factory flow. + +### Linux + +#### Requirements + +This requires a Linux PC (64-bit Ubuntu/XUbuntu OS desktop environment). +See also the [Mbed CLI instructions](https://os.mbed.com/docs/mbed-os/latest/tools/developing-mbed-cli.html). + +#### Connecting the device + +1. Open a terminal, and clone the example repository to a convenient location on your development environment: + + ``` + git clone https://github.com/PelionIoT/mbed-cloud-client-example + cd mbed-cloud-client-example + ``` + + **Note:** If you want to speed up `mbed deploy`, you can remove components that are unnecessary for Linux, such as `mbed-os.lib` and the `drivers/` folder. + +2. Deploy the example repository: + + ``` + mbed deploy + ``` + +3. [Create a developer certificate](#create-developer-cert). + +4. Copy the `mbed_cloud_dev_credentials.c` file to the root folder of the example. + +5. Deploy Linux dependencies: + + ``` + python pal-platform/pal-platform.py deploy --target=x86_x64_NativeLinux_mbedtls generate + cd __x86_x64_NativeLinux_mbedtls + ``` + **Note: python2 is needed for the above command** + +6. Generate `cmake` files based on your configuration and build profile (**Release** or **Debug**): + + - For the **Release** profile: + ``` + cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=./../pal-platform/Toolchain/GCC/GCC.cmake -DEXTERNAL_DEFINE_FILE=./../define_lwm2m_compliant.txt + ``` + + - For the **Debug** profile: + ``` + cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=./../pal-platform/Toolchain/GCC/GCC.cmake -DEXTERNAL_DEFINE_FILE=./../define_lwm2m_compliant.txt + ``` + - If you want your application to bypass the Bootstrap server and work directly with LwM2M server, please add `DISABLE_BOOTSTRAP` cmake flag: + ``` + cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DDISABLE_BOOTSTRAP=ON -DCMAKE_TOOLCHAIN_FILE=./../pal-platform/Toolchain/GCC/GCC.cmake -DEXTERNAL_DEFINE_FILE=./../define_lwm2m_compliant.txt + ``` + +7. Compile the application: + + ``` + make mbedCloudClientExample.elf + ``` + +8. The build creates binaries under `mbed-cloud-client-example/__x86_x64_NativeLinux_mbedtls`. In both cases, there are subdirectories `Debug` and `Release` respectively created for the two profiles. + +9. Run the application (at the respective path, see above): + + ``` + ./mbedCloudClientExample.elf + ``` + + You should see a message when the device connects to LwM2M server: + + ``` + Client registered + Endpoint Name: + Device Id: + ``` +10. If you want to run the application with a clean storage, you can remove the `pal` folder that is created in the location where you run your application. + + +### Mbed OS + +#### Prerequisites + +To work with the Device Management Client example application, you need: + +* A supported board with a network connection and an SD card attached. Currently FRDM K64F and NUCLEO F429ZI boards are supported. +* [Serial connection](https://os.mbed.com/docs/latest/tutorials/serial-comm.html) to your device with open terminal connection (baud rate 115200, 8N1). +* [Arm Mbed CLI](https://os.mbed.com/docs/mbed-os/latest/tools/index.html) installed. See [installation instructions](https://os.mbed.com/docs/latest/tools/installation-and-setup.html). + * Make sure that all the Python components are in par with the `pip` package [requirements.txt](https://github.com/PelionIoT/mbed-os/blob/master/requirements.txt) list from Mbed OS. +* Updated [DAPLink](https://github.com/ARMmbed/DAPLink/releases) software (version 250 or later), if your board uses DAPLink. + +#### Connecting the device + +1. Clone the embedded application's GitHub repository to your local computer and navigate to the new folder: + + ``` + mbed import https://github.com/PelionIoT/mbed-cloud-client-example + cd mbed-cloud-client-example + ``` + +2. Configure Mbed CLI to use your board: + + ``` + mbed target + mbed toolchain GCC_ARM + ``` + +3. [Create a developer certificate](#create-developer-cert). + +4. Copy the `mbed_cloud_dev_credentials.c` file to the root folder of the example application. + +5. Configure the example application: + 1. If you want your application to bypass the Bootstrap server and work directly with LwM2M server, + please set the `disable-bootstrap-feature` feature to `true` in [mbed_app_lwm2m_compliant.json](https://github.com/PelionIoT/mbed-cloud-client-example/blob/master/mbed_app_lwm2m_compliant.json#L21). + + ``` + mbed-client.disable-bootstrap-feature: true + ``` + + 2. Currently, the application will always start with a clean storage. + If you want to avoid this, remove the `RESET_STORAGE` from the `"target.macros_add"` line in the `mbed_app_lwm2m_compliant.json`: + + ``` + "target.macros_add" : ["LWM2M_COMPLIANT","DISABLE_SERVER_CERT_VERIFY"], + ``` + +7. Compile the example application: + + ``` + mbed compile --app-config mbed_app_lwm2m_compliant.json + ``` + For more information about Mbed CLI parameters, please see the [Mbed OS documentation site](https://os.mbed.com/docs/mbed-os/latest/build-tools/mbed-cli-1.html). + +8. Flash the binary to the device + 1. Connect the device to your computer over USB. It's listed as a mass storage device. + 2. Drag and drop `mbed-cloud-client-example.bin` to the device, or alternatively add the `-f` flag to the build command (if your device is connected to the build machine). This flashes the binary to the device. You should see the LED blink rapidly; wait for it to stop. + +9. Press the **Reset** button to restart the device and reset the terminal. +10. When the client has successfully connected, the terminal shows: + + ``` + Client registered + Endpoint Name: + Device ID: + ``` + +

Create a developer certificate

+ +1. Download the server CA certificate from the LwM2M service you want to connect to and copy it to the scripts' folder: + + ``` + cp utils/server_ca_cert.der + ``` + +2. Run the python script `cert_convert.py` to generate a `mbed_cloud_dev_credentials.c` file. + + ``` + cd utils + python cert_convert.py --endpoint --uri --use-ca + ``` + + The script will do the following steps: + 1. Generate a root CA key and certificate on the first time the script is running. All CA outputs are stored in `CA` folder. + 1. Generate a private key and a certificate signed by this CA. + 1. Convert the private key, certificate and the server certificate to a C file. + 1. All non CA outputs are stored in a folder named `YOUR_ENDPOINT_NAME`. + + + + diff --git a/define_lwm2m_compliant.txt b/define_lwm2m_compliant.txt new file mode 100644 index 0000000..3e5d7ea --- /dev/null +++ b/define_lwm2m_compliant.txt @@ -0,0 +1,70 @@ +cmake_policy(SET CMP0005 OLD) # definitions escaped explicitly (default) +cmake_policy(SET CMP0011 OLD) # policy flow old way (default) + +if (NOT (${OS_BRAND} MATCHES "Linux")) + message(FATAL_ERROR "define.txt to be used only with Linux builds.") +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) + +add_definitions(-DMBED_CLOUD_CLIENT_USER_CONFIG_FILE="\\"mbed_cloud_client_user_config.h"\\") +add_definitions(-DPAL_USER_DEFINED_CONFIGURATION="\\"sotp_fs_config_linux_lwm2m_compliant.h"\\") +add_definitions(-DTARGET_LIKE_POSIX) +add_definitions(-DPAL_DTLS_PEER_MIN_TIMEOUT=5000) + +# More ciphers +add_definitions(-DPAL_MAX_ALLOWED_CIPHER_SUITES=4) + +# enable fota +add_definitions(-DMBED_CLOUD_CLIENT_FOTA_ENABLE=1) +add_definitions(-DMBED_CLOUD_CLIENT_FOTA_LINUX_SINGLE_MAIN_FILE=1) + +# Set to 1 to enable tracing +add_definitions(-DMBED_CONF_MBED_TRACE_ENABLE=0) + +add_definitions(-DMBED_CONF_APP_DEVELOPER_MODE=1) + +add_definitions(-DPLATFORM_ENABLE_BUTTON=1) +add_definitions(-DPLATFORM_ENABLE_LED=1) + +add_definitions(-DMBED_CONF_MBED_CLOUD_CLIENT_DISABLE_CERTIFICATE_ENROLLMENT) +add_definitions(-DLWM2M_COMPLIANT) +add_definitions(-DDISABLE_SERVER_CERT_VERIFY) + +add_definitions(-DFOTA_DEFAULT_APP_IFS=1) +add_definitions(-DTARGET_LIKE_LINUX=1) +add_definitions(-DFOTA_CUSTOM_PLATFORM=1) +add_definitions(-DMBED_CLOUD_CLIENT_FOTA_SUB_COMPONENT_SUPPORT=1) + +if(RESET_STORAGE) + add_definitions(-DRESET_STORAGE) +endif(RESET_STORAGE) + +SET(PAL_TLS_BSP_DIR ${NEW_CMAKE_SOURCE_DIR}/mbed-cloud-client/mbed-client-pal/Configs/${TLS_LIBRARY}) + +if (${TLS_LIBRARY} MATCHES mbedTLS) + add_definitions(-DMBEDTLS_CONFIG_FILE="\\"${PAL_TLS_BSP_DIR}/mbedTLSConfig_Linux_LWM2M_Compliant.h"\\") +endif() + +if(PAL_SIMULATOR_FILE_SYSTEM_OVER_RAM) + message(WARNING "You are using simulation of File System over RAM") + add_definitions(-DPAL_SIMULATOR_FILE_SYSTEM_OVER_RAM=${PAL_SIMULATOR_FILE_SYSTEM_OVER_RAM}) +endif(PAL_SIMULATOR_FILE_SYSTEM_OVER_RAM) + +# This definition controls application automatic reboot if network errors exceed certain limit. +# Disabled when set to 0. +add_definitions(-DMAX_ERROR_COUNT=0) + +if(ENABLE_DEVICE_SENTRY) + add_definitions(-DMBED_CONF_MBED_CLOUD_CLIENT_ENABLE_DEVICE_SENTRY) + message("Enable Device Sentry feature") + add_definitions(-DMBED_CONF_APP_ENABLE_DS_CUSTOM_METRICS_EXAMPLE) + message("Enable Device Sentry example application") +endif(ENABLE_DEVICE_SENTRY) + +# Enable FOTA Update +option(FOTA_ENABLE "Enable FOTA client module" ON) + +if(DISABLE_BOOTSTRAP) + add_definitions(-DMBED_CONF_MBED_CLIENT_DISABLE_BOOTSTRAP_FEATURE) +endif() diff --git a/mbed-cloud-client.lib b/mbed-cloud-client.lib index 4868938..777d48f 100644 --- a/mbed-cloud-client.lib +++ b/mbed-cloud-client.lib @@ -1 +1 @@ -https://github.com/PelionIoT/mbed-cloud-client/#08d12fb1878c795a7a05770beee485e054bf23b0 +https://github.com/PelionIoT/mbed-cloud-client/#54282bc27ed9c404524e15a0de93dc9a59516f79 diff --git a/mbed-os.lib b/mbed-os.lib index 5d6d1e1..7655422 100644 --- a/mbed-os.lib +++ b/mbed-os.lib @@ -1 +1 @@ -https://github.com/ARMmbed/mbed-os/#4cfbea43cabe86bc3ed7a5287cd464be7a218938 +https://github.com/ARMmbed/mbed-os/#2eb06e76208588afc6cb7580a8dd64c5429a10ce diff --git a/mbed_app_lwm2m_compliant.json b/mbed_app_lwm2m_compliant.json new file mode 100644 index 0000000..aeadccf --- /dev/null +++ b/mbed_app_lwm2m_compliant.json @@ -0,0 +1,93 @@ +{ + "target_overrides": { + "*": { + "target.features_add" : ["BOOTLOADER", "STORAGE"], + "target.c_lib" : "std", + "platform.stdio-baud-rate" : 115200, + "platform.stdio-convert-newlines" : true, + "platform.stdio-buffered-serial" : true, + "platform.stdio-flush-at-exit" : true, + "rtos.main-thread-stack-size" : 5120, + "events.shared-stacksize" : 2048, + "events.shared-eventsize" : 2048, + "update-client.storage-locations" : 1, + "mbed-trace.enable" : 0, + "nsapi.default-wifi-security" : "WPA_WPA2", + "nsapi.default-wifi-ssid" : "\"SSID\"", + "nsapi.default-wifi-password" : "\"Password\"", + "target.macros_add" : ["LWM2M_COMPLIANT","DISABLE_SERVER_CERT_VERIFY","RESET_STORAGE"], + "client_app.pal-user-defined-configuration" : "\"pal_config_MbedOS_LWM2M_Compliant.h\"", + "lwip.mem-size" : 18432, + "mbed-client.disable-bootstrap-feature" : null, + "mbed-client.max-certificate-size" : 2048 + }, + "K64F": { + "target.network-default-interface-type" : "ETHERNET", + "target.bootloader_img" : "tools/mbed-bootloader-k64f-block_device-kvstore-v4.1.0.bin", + "target.header_offset" : "0xa000", + "target.app_offset" : "0xa400", + "target.components_add" : ["SD"], + "update-client.bootloader-details" : "0x00007188", + "update-client.application-details" : "(40*1024)", + "update-client.storage-address" : "(1024*1024*64)", + "update-client.storage-size" : "((MBED_ROM_START + MBED_ROM_SIZE - APPLICATION_ADDR) * MBED_CONF_UPDATE_CLIENT_STORAGE_LOCATIONS)", + "mbed-cloud-client.update-storage" : "ARM_UCP_FLASHIAP_BLOCKDEVICE", + "storage_filesystem.internal_base_address" : "(32*1024)", + "storage_filesystem.rbp_internal_size" : "(8*1024)", + "storage_filesystem.external_base_address" : "(0x0)", + "storage_filesystem.external_size" : "(1024*1024*64)", + "storage.storage_type" : "FILESYSTEM", + "storage_filesystem.filesystem" : "LITTLE", + "storage_filesystem.blockdevice" : "SD" + }, + "NUCLEO_F429ZI": { + "target.network-default-interface-type" : "ETHERNET", + "target.bootloader_img" : "tools/mbed-bootloader-nucleo_f429zi-internal_flash-no_rot-v4.1.0.bin", + "target.header_offset" : "0x8000", + "target.app_offset" : "0x8400", + "target.restrict_size" : "0xF7C00", + "update-client.bootloader-details" : "0x080078CC", + "update-client.application-details" : "(MBED_ROM_START + MBED_BOOTLOADER_SIZE)", + "update-client.storage-address" : "(MBED_CONF_STORAGE_TDB_INTERNAL_INTERNAL_BASE_ADDRESS+MBED_CONF_STORAGE_TDB_INTERNAL_INTERNAL_SIZE)", + "update-client.storage-size" : "(1024*1024-MBED_CONF_STORAGE_TDB_INTERNAL_INTERNAL_SIZE)", + "update-client.storage-page" : 1, + "mbed-cloud-client.update-storage" : "ARM_UCP_FLASHIAP", + "storage_tdb_internal.internal_base_address": "(MBED_ROM_START+1024*1024)", + "storage_tdb_internal.internal_size" : "(128*1024)", + "storage.storage_type" : "TDB_INTERNAL" + } + }, + "config": { + "developer-mode": { + "help" : "Enable Developer mode to skip Factory enrollment", + "options" : [null, 1], + "value" : 1 + }, + "button-pinname": { + "help" : "PinName for button.", + "value" : "BUTTON1" + }, + "led-pinname" : { + "help" : "PinName for led, which is attached to led blink resource.", + "value" : "LED1" + }, + "bootloader-size": { + "help" : "Helper macro to enable calculation of rom regions. target.header_offset and target.app_offset still needs to be calculated manually, though.", + "value" : "(32*1024)", + "macro_name": "MBED_BOOTLOADER_SIZE" + }, + "mbed-trace-max-level": { + "help" : "Max trace level. Must be one of the following: [TRACE_LEVEL_DEBUG, TRACE_LEVEL_INFO, TRACE_LEVEL_WARN, TRACE_LEVEL_ERROR, TRACE_LEVEL_CMD]", + "macro_name": "MBED_TRACE_MAX_LEVEL", + "value" : "TRACE_LEVEL_DEBUG" + }, + "mbed-cloud-client-update-buffer-size": { + "value" : null + }, + "enable-ds-custom-metrics-example": { + "help" : "Enable Device Sentry custom metrics example applicaton", + "options" : [null, 1], + "value" : null + } + } +} diff --git a/mbed_cloud_client_user_config.h b/mbed_cloud_client_user_config.h index e72f367..152d898 100644 --- a/mbed_cloud_client_user_config.h +++ b/mbed_cloud_client_user_config.h @@ -24,7 +24,12 @@ #define MBED_CLOUD_CLIENT_ENDPOINT_TYPE "default" #endif -#define MBED_CLOUD_CLIENT_LIFETIME 86400 +#define MBED_CLOUD_CLIENT_LIFETIME (3*60) + +#ifdef LWM2M_COMPLIANT +#define MBED_CLOUD_CLIENT_TRANSPORT_MODE_UDP +#define MBED_CONF_MBED_CLIENT_MAX_CERTIFICATE_SIZE 2048 +#endif #if !defined(MBED_CLOUD_CLIENT_TRANSPORT_MODE_UDP) && !defined(MBED_CLOUD_CLIENT_TRANSPORT_MODE_TCP) && !defined(MBED_CLOUD_CLIENT_TRANSPORT_MODE_UDP_QUEUE) #define MBED_CLOUD_CLIENT_TRANSPORT_MODE_TCP diff --git a/pal_config_MbedOS_LWM2M_Compliant.h b/pal_config_MbedOS_LWM2M_Compliant.h new file mode 100644 index 0000000..b6d324e --- /dev/null +++ b/pal_config_MbedOS_LWM2M_Compliant.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2022 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PAL_CONFIG_MBEDOS +#define PAL_CONFIG_MBEDOS + +#define PAL_USE_HW_ROT 0 +#define PAL_USE_HW_RTC 0 +#define PAL_USE_HW_TRNG 1 +#define PAL_SIMULATOR_FLASH_OVER_FILE_SYSTEM 0 +#define PAL_USE_INTERNAL_FLASH 0 +#define PAL_USE_SECURE_TIME 0 +#define PAL_USE_SSL_SESSION_RESUME 1 +#define PAL_MAX_FRAG_LEN 4 + + +#include "mbedOS_SST.h" + + +#endif //PAL_CONFIG_MBEDOS diff --git a/sotp_fs_config_linux_lwm2m_compliant.h b/sotp_fs_config_linux_lwm2m_compliant.h new file mode 100644 index 0000000..9e12672 --- /dev/null +++ b/sotp_fs_config_linux_lwm2m_compliant.h @@ -0,0 +1,12 @@ +#ifndef PAL_HEADER_SOTP_FS_LINUX_COIOTE +#define PAL_HEADER_SOTP_FS_LINUX_COIOTE + +#define PAL_USE_HW_ROT 0 +#define PAL_USE_HW_RTC 0 +#define PAL_USE_HW_TRNG 1 +#define PAL_SIMULATOR_FLASH_OVER_FILE_SYSTEM 1 +#define PAL_USE_SECURE_TIME 0 + +#include "Linux_default.h" + +#endif //PAL_HEADER_SOTP_FS_LINUX_COIOTE diff --git a/utils/cert_convert.py b/utils/cert_convert.py new file mode 100755 index 0000000..0fde4d2 --- /dev/null +++ b/utils/cert_convert.py @@ -0,0 +1,545 @@ +#!/usr/bin/env python3 + +# ---------------------------------------------------------------------------- +# Copyright 2022 Pelion +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +"""Script to convert DER files into C arrays.""" + +import argparse +import binascii +import pathlib +import sys + +from helpers import ExecuteHelper +from helpers import _str_to_resolved_path + +dummy_accountID = "123456" +bs_public_cert_file = "bs_cert.der" +bs_private_key_file = "bs_key.der" +csr_file = "csr.pem" +keys_file = "keys.pem" +lwm2m_private_key_file = None +lwm2m_public_cert_file = None +output_file = "mbed_cloud_dev_credentials.c" +private_key_file = "cprik.der" +public_cert_file = "cert.der" +root_cert_pem_file = "CA/root_cert.pem" +root_cert_der_file = "CA/root_cert.der" +root_keys_file = "CA/root_keys.pem" +server_cert_file = "server_ca_cert.der" + + +def _parse_args(): + # Parse command line + parser = argparse.ArgumentParser( + description="Convert DER into c file", add_help=False + ) + + required = parser.add_argument_group("required arguments") + optional = parser.add_argument_group("optional arguments") + + required.add_argument( + "--endpoint", + help="endpoint name", + required=True, + ) + required.add_argument( + "--uri", + help="The URI of the bootstrap or device managment service", + required=True, + ) + optional.add_argument( + "--server-cert", + help="Server CA cert", + default=server_cert_file, + ) + optional.add_argument( + "--use-bs", + action="store_true", + help="Use BS certificate", + default=False, + required=False, + ) + optional.add_argument( + "--use-ca", + action="store_true", + help="Use CA certificate", + default=False, + required=False, + ) + optional.add_argument( + "-v", + "--verbose", + action="store_true", + help="Print verbose output", + default=False, + required=False, + ) + optional.add_argument( + "-h", "--help", action="help", help="Show this help message and exit." + ) + args = parser.parse_args() + + return args + + +def _sanity_check_files(args): + # Sanity check files exist or not. + status = True + + # Expect the server_certificate to exist. + if not pathlib.Path(server_cert_file).is_file(): + print("ERROR - file {} not found.".format(args.server_cert)) + status = False + + return status + + +def _generate_ca_keys(endpoint, use_bs, verbose): + + helper = ExecuteHelper(verbose) + + status = True + if ( + pathlib.Path(root_cert_pem_file).exists() + and pathlib.Path(root_keys_file).exists() + ): + print("Using the existing CA instead of generating a new one.") + else: + status = status and helper.execute_command( + [ + "openssl", + "ecparam", + "-out", + root_keys_file, + "-name", + "prime256v1", + "-genkey", + ] + )[0] + status = status and helper.execute_command( + [ + "openssl", + "req", + "-x509", + "-new", + "-key", + root_keys_file, + "-sha256", + "-days", + "36500", + "-subj", + "/CN=ROOT_CA", + "-outform", + "PEM", + "-out", + root_cert_pem_file, + ] + )[0] + status = status and helper.execute_command( + [ + "openssl", + "x509", + "-inform", + "PEM", + "-in", + root_cert_pem_file, + "-outform", + "DER", + "-out", + root_cert_der_file, + ] + )[0] + + # Now the CA is in place, create the certificates using it. + status = status and helper.execute_command( + [ + "openssl", + "ecparam", + "-out", + keys_file, + "-name", + "prime256v1", + "-genkey", + ] + )[0] + status = status and helper.execute_command( + [ + "openssl", + "pkcs8", + "-topk8", + "-inform", + "PEM", + "-outform", + "DER", + "-in", + keys_file, + "-out", + private_key_file, + "-nocrypt", + ] + )[0] + status = status and helper.execute_command( + [ + "openssl", + "req", + "-new", + "-key", + keys_file, + "-out", + csr_file, + "-subj", + "/CN={}".format(endpoint), + ] + )[0] + status = status and helper.execute_command( + [ + "openssl", + "x509", + "-req", + "-in", + csr_file, + "-CA", + root_cert_pem_file, + "-CAkey", + root_keys_file, + "-CAcreateserial", + "-days", + "36500", + "-outform", + "DER", + "-out", + public_cert_file, + ] + )[0] + + # If the use_bs flag is set the generate a set of LwM2M server files. + if use_bs: + status = status and helper.execute_command( + [ + "openssl", + "ecparam", + "-out", + keys_file, + "-name", + "prime256v1", + "-genkey", + ] + )[0] + status = status and helper.execute_command( + [ + "openssl", + "pkcs8", + "-topk8", + "-inform", + "PEM", + "-outform", + "DER", + "-in", + keys_file, + "-out", + lwm2m_private_key_file, + "-nocrypt", + ] + )[0] + status = status and helper.execute_command( + [ + "openssl", + "req", + "-new", + "-key", + keys_file, + "-out", + csr_file, + "-subj", + "/CN={}".format(endpoint), + ] + )[0] + status = status and helper.execute_command( + [ + "openssl", + "x509", + "-req", + "-in", + csr_file, + "-CA", + root_cert_pem_file, + "-CAkey", + root_keys_file, + "-CAcreateserial", + "-days", + "36500", + "-outform", + "DER", + "-out", + lwm2m_public_cert_file, + ] + )[0] + return status + + +def _generate_non_ca_keys(endpoint, verbose): + + helper = ExecuteHelper(verbose) + + status = True + + status = status and helper.execute_command( + [ + "openssl", + "ecparam", + "-out", + keys_file, + "-name", + "prime256v1", + "-genkey", + ] + )[0] + + status = status and helper.execute_command( + [ + "openssl", + "pkcs8", + "-topk8", + "-inform", + "PEM", + "-outform", + "DER", + "-in", + keys_file, + "-out", + private_key_file, + "-nocrypt", + ] + )[0] + status = status and helper.execute_command( + [ + "openssl", + "req", + "-x509", + "-new", + "-key", + keys_file, + "-sha256", + "-days", + "36500", + "-subj", + "/CN={}".format(endpoint), + "-outform", + "DER", + "-out", + public_cert_file, + ] + )[0] + + return status + + +def _process_data(infile, outfile, name, verbose): + # Process data + if verbose: + print("\tAdding {} into the credentials file.".format(infile)) + outfile.write("const uint8_t {}[] =\n".format(name)) + outfile.write("{\n") + with open(infile, "rb") as dataFile: + while True: + hexdata = dataFile.read(16).hex() + if len(hexdata) == 0: + break + hexlist = map("".join, zip(hexdata[::2], hexdata[1::2])) + outfile.write(" ") + for item in hexlist: + outfile.write("0x{}, ".format(item)) + outfile.write("\n") + outfile.write("};\n") + + outfile.write("const uint32_t {0}_SIZE = sizeof({0});\n\n".format(name)) + + +def _create_output_folders(endpoint): + status = True + + # Create the CA folder if we don't already have one. + try: + pathlib.Path("CA").mkdir(parents=True, exist_ok=True) + except FileExistsError: + print("ERROR - could not create folder {}.".format("CA")) + status = False + + try: + pathlib.Path(endpoint).mkdir(parents=True, exist_ok=True) + except FileExistsError: + print("ERROR - could not create folder {}.".format(endpoint)) + status = False + return status + + +def _resolve_file_paths(args): + + # Convert all the file names to full paths. + global csr_file + global keys_file + global output_file + global private_key_file + global public_cert_file + global root_cert_der_file + global root_cert_pem_file + global root_keys_file + global server_cert_file + + global lwm2m_private_key_file + global lwm2m_public_cert_file + + csr_file = _str_to_resolved_path( + pathlib.Path(args.endpoint).joinpath(csr_file) + ) + keys_file = _str_to_resolved_path( + pathlib.Path(args.endpoint).joinpath(keys_file) + ) + output_file = _str_to_resolved_path( + pathlib.Path(args.endpoint).joinpath(output_file) + ) + + root_cert_der_file = _str_to_resolved_path(root_cert_der_file) + root_cert_pem_file = _str_to_resolved_path(root_cert_pem_file) + root_keys_file = _str_to_resolved_path(root_keys_file) + server_cert_file = _str_to_resolved_path(args.server_cert) + + # If the use_bs flag is set then we need to generate 2 sets of files. + # Set the filenames to reflect this. + + if args.use_ca and args.use_bs: + lwm2m_private_key_file = _str_to_resolved_path( + pathlib.Path(args.endpoint).joinpath(private_key_file) + ) + lwm2m_public_cert_file = _str_to_resolved_path( + pathlib.Path(args.endpoint).joinpath(public_cert_file) + ) + private_key_file = _str_to_resolved_path( + pathlib.Path(args.endpoint).joinpath(bs_private_key_file) + ) + public_cert_file = _str_to_resolved_path( + pathlib.Path(args.endpoint).joinpath(bs_public_cert_file) + ) + else: + private_key_file = _str_to_resolved_path( + pathlib.Path(args.endpoint).joinpath(private_key_file) + ) + public_cert_file = _str_to_resolved_path( + pathlib.Path(args.endpoint).joinpath(public_cert_file) + ) + + +def main(): + """Perform the main execution.""" + args = _parse_args() + + if not _create_output_folders(args.endpoint): + sys.exit(2) + + _resolve_file_paths(args) + + if _sanity_check_files(args) is False: + sys.exit(2) + + if args.use_ca: + status = _generate_ca_keys(args.endpoint, args.use_bs, args.verbose) + else: + status = _generate_non_ca_keys(args.endpoint, args.verbose) + + if status is False: + print( + "ERROR - certificate generation failed. " + "Run with --verbose for more details." + ) + sys.exit(1) + + # All the variables etc in the output file have the same prefix. Doing it + # like this keeps the line lengths below 80 characters. + prefix = "MBED_CLOUD_DEV_" + + if args.verbose: + print("\nGenerating {}.".format(output_file)) + + with open(output_file, "w") as output_data: + output_data.write("#ifndef __{}CREDENTIALS_H__\n".format(prefix)) + output_data.write("#define __{}CREDENTIALS_H__\n\n".format(prefix)) + output_data.write("#include \n\n") + + output_data.write( + 'const char {}BOOTSTRAP_ENDPOINT_NAME[] = "{}";\n'.format( + prefix, args.endpoint + ) + ) + output_data.write( + 'const char {}ACCOUNT_ID[] = "{}";\n'.format( + prefix, dummy_accountID + ) + ) + output_data.write( + 'const char {}BOOTSTRAP_SERVER_URI[] = "{}";\n\n'.format( + prefix, args.uri + ) + ) + + _process_data( + private_key_file, + output_data, + "{}BOOTSTRAP_DEVICE_PRIVATE_KEY".format(prefix), + args.verbose, + ) + _process_data( + public_cert_file, + output_data, + "{}BOOTSTRAP_DEVICE_CERTIFICATE".format(prefix), + args.verbose, + ) + _process_data( + server_cert_file, + output_data, + "{}BOOTSTRAP_SERVER_ROOT_CA_CERTIFICATE".format(prefix), + args.verbose, + ) + + output_data.write( + 'const char {}MANUFACTURER[] = "dev_manufacturer";\n'.format( + prefix + ) + ) + output_data.write( + 'const char {}MODEL_NUMBER[] = "dev_model_num";\n'.format(prefix) + ) + output_data.write( + 'const char {}SERIAL_NUMBER[] = "0";\n'.format(prefix) + ) + output_data.write( + 'const char {}DEVICE_TYPE[] = "dev_device_type";\n'.format(prefix) + ) + output_data.write( + 'const char {}HARDWARE_VERSION[] = "' + 'dev_hardware_version";\n'.format(prefix) + ) + output_data.write( + "const uint32_t {}MEMORY_TOTAL_KB = 0;\n\n".format(prefix) + ) + + output_data.write("#endif //__{}CREDENTIALS_H__\n".format(prefix)) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/utils/helpers.py b/utils/helpers.py new file mode 100644 index 0000000..e375df3 --- /dev/null +++ b/utils/helpers.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 Pelion Limited and Contributors. All rights reserved. +# +# +# SPDX-License-Identifier: Apache-2.0 +# + +""" +Helpers module. + +This module contains helper functions and classes +""" + +import pathlib +import subprocess + + +def _str_to_resolved_path(path_str): + """ + Convert a string to a resolved Path object. + + Args: + * path_str (str): string to convert to a Path object. + + """ + return pathlib.Path(path_str).resolve(strict=False) + + +class ExecuteHelper: + """Class to provide a wrapper for executing commands as a subprocess.""" + + verbose = False + + def __init__(self, verbose=False): + """Initialise the class.""" + ExecuteHelper.verbose = verbose + + @staticmethod + def _print(data): + if ExecuteHelper.verbose: + print(data) + + @staticmethod + def _print_command(data): + if ExecuteHelper.verbose: + for item in data: + print("{} ".format(item), end="") + print("") + + @staticmethod + def execute_command(command, timeout=None): + """Execute the provided command list. + + Executes the command and returns the error code, stdout and stderr. + """ + ExecuteHelper._print_command(command) + p = subprocess.Popen( + command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=-1, + universal_newlines=True, + ) + try: + output, error = p.communicate(timeout=timeout) + except subprocess.TimeoutExpired: + ExecuteHelper._print("Timed out after {}s".format(timeout)) + p.kill() + output, error = p.communicate() + + if p.returncode: + ExecuteHelper._print("error:") + ExecuteHelper._print(error) + ExecuteHelper._print("returnCode:") + ExecuteHelper._print(p.returncode) + + return (p.returncode == 0), output, error diff --git a/west.yml b/west.yml index 287cc3d..0db8bff 100644 --- a/west.yml +++ b/west.yml @@ -10,7 +10,7 @@ manifest: - name: pelion-dm repo-path: mbed-cloud-client remote: PelionIoT - revision: 08d12fb1878c795a7a05770beee485e054bf23b0 + revision: 54282bc27ed9c404524e15a0de93dc9a59516f79 path: modules/lib/pelion-dm - name: zephyr remote: zephyrproject-rtos