Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: lwm2m: Add support for X509 certificates #59019

Merged
merged 2 commits into from Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
73 changes: 54 additions & 19 deletions doc/connectivity/networking/api/lwm2m.rst
Expand Up @@ -364,14 +364,44 @@ endpoint name. This is important as it needs to be unique per LwM2M server:
(void)memset(&client, 0x0, sizeof(client));
lwm2m_rd_client_start(&client, "unique-endpoint-name", 0, rd_client_event);

Using LwM2M library with DTLS
*****************************
.. _lwm2m_security:

The Zephyr LwM2M library can be used with DTLS transport for secure
communication by selecting :kconfig:option:`CONFIG_LWM2M_DTLS_SUPPORT`. In the client
initialization we need to create a PSK and identity. These need to match
the security information loaded onto the LwM2M server. Normally, the
endpoint name is used to lookup the related security information:
LwM2M security modes
********************

The Zephyr LwM2M library can be used either without security or use DTLS to secure the communication channel.
When using DTLS with the LwM2M engine, PSK (Pre-Shared Key) and X.509 certificates are the security modes that can be used to secure the communication.
The engine uses LwM2M Security object (Id 0) to read the stored credentials and feed keys from the security object into
the TLS credential subsystem, see :ref:`secure sockets documentation <secure_sockets_interface>`.
Enable the :kconfig:option:`CONFIG_LWM2M_DTLS_SUPPORT` Kconfig option to use the security.

Depending on the selected mode, the security object must contain following data:

PSK
Security Mode (Resource ID 2) set to zero (Pre-Shared Key mode).
Identity (Resource ID 3) contains PSK ID in binary form.
Secret key (Resource ID 5) contains the PSK key in binary form.
If the key or identity is provided as a hex string, it must be converted to binary before storing into the security object.

X509
When X509 certificates are used, set Security Mode (ID 2) to ``2`` (Certificate mode).
Identity (ID 3) is used to store the client certificate and Secret key (ID 5) must have a private key associated with the certificate.
Server Public Key resource (ID 4) must contain a server certificate or CA certificate used to sign the certificate chain.
If the :kconfig:option:`CONFIG_MBEDTLS_PEM_CERTIFICATE_FORMAT` Kconfig option is enabled, certificates and private key can be entered in PEM format.
Otherwise, they must be in binary DER format.

NoSec
When no security is used, set Security Mode (Resource ID 2) to ``3`` (NoSec).

In all modes, Server URI resource (ID 0) must contain the full URI for the target server.
When DNS names are used, the DNS resolver must be enabled.

LwM2M stack provides callbacks in the :c:struct:`lwm2m_ctx` structure.
They are used to feed keys from the LwM2M security object into the TLS credential subsystem.
By default, these callbacks can be left as NULL pointers, in which case default callbacks are used.
When an external TLS stack, or non-default socket options are required, you can overwrite the :c:func:`lwm2m_ctx.load_credentials` or :c:func:`lwm2m_ctx.set_socketoptions` callbacks.

An example of setting up the security object for PSK mode:

.. code-block:: c

Expand All @@ -383,21 +413,26 @@ endpoint name is used to lookup the related security information:

static const char client_identity[] = "Client_identity";

Next we alter the ``Security`` object resources to include DTLS security
information. The server URL should begin with ``coaps://`` to indicate security
is required. Assign a 0 value (Pre-shared Key mode) to the ``Security Mode``
resource. Lastly, set the client identity and PSK resources.
lwm2m_set_string(&LWM2M_OBJ(LWM2M_OBJECT_SECURITY_ID, 0, 0), "coaps://lwm2m.example.com");
lwm2m_set_u8(&LWM2M_OBJ(LWM2M_OBJECT_SECURITY_ID, 0, 2), LWM2M_SECURITY_PSK);
/* Set the client identity as a string, but this could be binary as well */
lwm2m_set_string(&LWM2M_OBJ(LWM2M_OBJECT_SECURITY_ID, 0, 3), client_identity);
/* Set the client pre-shared key (PSK) */
lwm2m_set_opaque(&LWM2M_OBJ(LWM2M_OBJECT_SECURITY_ID, 0, 5), client_psk, sizeof(client_psk));

An example of setting up the security object for X509 certificate mode:

.. code-block:: c

/* Use coaps:// for server URL protocol */
lwm2m_set_string(&LWM2M_OBJ(0, 0, 0), "coaps://5.39.83.206");
/* 0 = Pre-Shared Key mode */
lwm2m_set_u8(&LWM2M_OBJ(0, 0, 2), 0);
/* Set the client identity */
lwm2m_set_string(&LWM2M_OBJ(0, 0, 3), (char *)client_identity);
/* Set the client pre-shared key (PSK) */
lwm2m_set_opaque(&LWM2M_OBJ(0, 0, 5), (void *)client_psk, sizeof(client_psk));
static const char certificate[] = "-----BEGIN CERTIFICATE-----\nMIIB6jCCAY+gAw...";
static const char key[] = "-----BEGIN EC PRIVATE KEY-----\nMHcCAQ...";
static const char root_ca[] = "-----BEGIN CERTIFICATE-----\nMIIBaz...";

lwm2m_set_string(&LWM2M_OBJ(LWM2M_OBJECT_SECURITY_ID, 0, 0), "coaps://lwm2m.example.com");
lwm2m_set_u8(&LWM2M_OBJ(LWM2M_OBJECT_SECURITY_ID, 0, 2), LWM2M_SECURITY_CERT);
lwm2m_set_string(&LWM2M_OBJ(LWM2M_OBJECT_SECURITY_ID, 0, 3), certificate);
lwm2m_set_string(&LWM2M_OBJ(LWM2M_OBJECT_SECURITY_ID, 0, 5), key);
lwm2m_set_string(&LWM2M_OBJ(LWM2M_OBJECT_SECURITY_ID, 0, 5), root_ca);

Before calling :c:func:`lwm2m_rd_client_start` assign the tls_tag # where the
LwM2M library should store the DTLS information prior to connection (normally a
Expand Down
45 changes: 39 additions & 6 deletions include/zephyr/net/lwm2m.h
Expand Up @@ -145,7 +145,7 @@ struct lwm2m_ctx {
*/
void *processed_req;

#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
#if defined(CONFIG_LWM2M_DTLS_SUPPORT) || defined(__DOXYGEN__)
/** TLS tag is set by client as a reference used when the
* LwM2M engine calls tls_credential_(add|delete)
*/
Expand Down Expand Up @@ -182,7 +182,7 @@ struct lwm2m_ctx {
*/
bool connection_suspended;

#if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED)
#if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) || defined(__DOXYGEN__)
/**
* Flag to indicate that the client is buffering Notifications and Send messages.
* True value buffer Notifications and Send messages.
Expand Down Expand Up @@ -416,7 +416,7 @@ int lwm2m_device_add_err(uint8_t error_code);
#define RESULT_UPDATE_FAILED 8
#define RESULT_UNSUP_PROTO 9

#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT)
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) || defined(__DOXYGEN__)
/**
* @brief Set data callback for firmware block transfer.
*
Expand Down Expand Up @@ -489,7 +489,7 @@ void lwm2m_firmware_set_cancel_cb_inst(uint16_t obj_inst_id, lwm2m_engine_user_c
*/
lwm2m_engine_user_cb_t lwm2m_firmware_get_cancel_cb_inst(uint16_t obj_inst_id);

#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT)
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) || defined(__DOXYGEN__)
/**
* @brief Set data callback to handle firmware update execute events.
*
Expand Down Expand Up @@ -529,7 +529,7 @@ lwm2m_engine_execute_cb_t lwm2m_firmware_get_update_cb_inst(uint16_t obj_inst_id
#endif


#if defined(CONFIG_LWM2M_SWMGMT_OBJ_SUPPORT)
#if defined(CONFIG_LWM2M_SWMGMT_OBJ_SUPPORT) || defined(__DOXYGEN__)

/**
* @brief Set callback to handle software activation requests
Expand Down Expand Up @@ -623,7 +623,7 @@ int lwm2m_swmgmt_install_completed(uint16_t obj_inst_id, int error_code);

#endif

#if defined(CONFIG_LWM2M_EVENT_LOG_OBJ_SUPPORT)
#if defined(CONFIG_LWM2M_EVENT_LOG_OBJ_SUPPORT) || defined(__DOXYGEN__)

/**
* @brief Set callback to read log data
Expand Down Expand Up @@ -2239,5 +2239,38 @@ int lwm2m_engine_enable_cache(char const *resource_path, struct lwm2m_time_serie
int lwm2m_enable_cache(const struct lwm2m_obj_path *path, struct lwm2m_time_series_elem *data_cache,
size_t cache_len);

/**
* @brief Security modes as defined in LwM2M Security object.
*/
enum lwm2m_security_mode_e {
LWM2M_SECURITY_PSK = 0, /**< Pre-Shared Key mode */
LWM2M_SECURITY_RAW_PK = 1, /**< Raw Public Key mode */
LWM2M_SECURITY_CERT = 2, /**< Certificate mode */
LWM2M_SECURITY_NOSEC = 3, /**< NoSec mode */
LWM2M_SECURITY_CERT_EST = 4, /**< Certificate mode with EST */
};

/**
* @brief Read security mode from selected security object instance.
*
* This data is valid only if RD client is running.
*
* @param ctx Pointer to client context.
* @return int Positive values are @ref lwm2m_security_mode_e, negative error codes otherwise.
*/
int lwm2m_security_mode(struct lwm2m_ctx *ctx);

/**
* @brief Set default socket options for DTLS connections.
*
* The engine calls this when @ref lwm2m_ctx::set_socketoptions is not overwritten.
* You can call this from the overwritten callback to set extra options after or
* before defaults.
*
* @param ctx Client context
* @return 0 for success or negative in case of error.
*/
int lwm2m_set_default_sockopt(struct lwm2m_ctx *ctx);

#endif /* ZEPHYR_INCLUDE_NET_LWM2M_H_ */
/**@} */
38 changes: 38 additions & 0 deletions samples/net/lwm2m_client/overlay-dtls-cert.conf
@@ -0,0 +1,38 @@
CONFIG_LWM2M_DTLS_SUPPORT=y
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this overlay also enforce bootstrap? We're not feeding any certificates from the application.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary.. I was testing this manually by feeding the certificates from command line.

lwm2m write 0/0/3 -s "-----BEGIN CERTIFICATE-----\x0aMIIBazCCAR...
lwm2m write 0/0/5 -s "-----BEGIN EC PRIVATE KEY-----\x0aMHcCAQE...
lwm2m write 0/0/4 -s "-----BEGIN CERTIFICATE-----\x0aMIICBzCCAa...
lwm2m write 0/0/2 -u8 2
lwm2m start secure_client

I prefer to write separate documentation as there are now 3 different security modes supported. But I would like to do that on another commit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, an update in the documentation would be much appreciated. I'm ok with doing this separately.

CONFIG_LWM2M_PEER_PORT=5684

# I need room to store certificates
CONFIG_LWM2M_SECURITY_KEY_SIZE=2048

# Select Zephyr mbedtls
CONFIG_MBEDTLS=y
CONFIG_MBEDTLS_TLS_VERSION_1_2=y

# Special MbedTLS changes
CONFIG_MBEDTLS_ENABLE_HEAP=y
CONFIG_MBEDTLS_HEAP_SIZE=32768
CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=1500
CONFIG_MBEDTLS_CIPHER_CCM_ENABLED=y

# Disable RSA, use only ECC certificates
CONFIG_MBEDTLS_KEY_EXCHANGE_RSA_ENABLED=n
# Enable PSK and ECDHE_ECDSA
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED=y
# We only need prime256v1 curve
CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y
CONFIG_MBEDTLS_ECDH_C=y
CONFIG_MBEDTLS_ECDSA_C=y
CONFIG_MBEDTLS_ECP_C=y
CONFIG_MBEDTLS_CIPHER_CCM_ENABLED=y
CONFIG_MBEDTLS_CIPHER_GCM_ENABLED=y
# Optional: we could use just binary DER certificates
CONFIG_MBEDTLS_PEM_CERTIFICATE_FORMAT=y

CONFIG_NET_SOCKETS_SOCKOPT_TLS=y
CONFIG_NET_SOCKETS_TLS_MAX_CONTEXTS=4
CONFIG_NET_SOCKETS_ENABLE_DTLS=y

# MbedTLS needs a larger stack
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
1 change: 0 additions & 1 deletion subsys/net/lib/lwm2m/Kconfig
Expand Up @@ -316,7 +316,6 @@ config LWM2M_SECURITY_INSTANCE_COUNT
config LWM2M_SECURITY_KEY_SIZE
int "Buffer size of the security key resources"
default 16
range 16 256
help
This setting establishes the size of the key (pre-shared / public)
resources in the security object instances.
Expand Down