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

lib: app_jwt: add an application core jwt generator and add a sample for usage #19561

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
/doc/nrf/includes/ @nrfconnect/ncs-doc-leads
/doc/nrf/includes/boardname_tables/sample_boardnames.txt @nrfconnect/ncs-co-doc
/doc/nrf/installation/ @nrfconnect/ncs-doc-leads @nrfconnect/ncs-vestavind-doc @nrfconnect/ncs-wayland-doc
/doc/nrf/libraries/app_jwt/ @nrfconnect/ncs-modem-doc @ayla-nordicsemi
/doc/nrf/libraries/bin/ @nrfconnect/ncs-doc-leads
/doc/nrf/libraries/bin/lwm2m_carrier/ @nrfconnect/ncs-carrier-doc
/doc/nrf/libraries/bluetooth/ @nrfconnect/ncs-si-muffin-doc @nrfconnect/ncs-dragoon-doc
Expand Down Expand Up @@ -315,6 +316,7 @@
/ext/iperf3/ @nrfconnect/ncs-code-owners @jhirsi

# Include
/include/app_jwt.h @nrfconnect/ncs-modem @ayla-nordicsemi
/include/audio/ @nrfconnect/ncs-audio
/include/audio_module/ @nrfconnect/ncs-audio
/include/bluetooth/ @nrfconnect/ncs-dragoon
Expand Down Expand Up @@ -378,6 +380,7 @@

# Libraries
/lib/adp536x/ @nrfconnect/ncs-cia
/lib/app_jwt/ @nrfconnect/ncs-modem @ayla-nordicsemi
/lib/at_cmd_parser/ @nrfconnect/ncs-co-networking @nrfconnect/ncs-modem
/lib/at_cmd_custom/ @nrfconnect/ncs-modem
/lib/at_host/ @nrfconnect/ncs-co-networking @nrfconnect/ncs-modem
Expand Down Expand Up @@ -519,6 +522,7 @@
/samples/gazell/ @leewkb4567
/samples/hw_id/ @nrfconnect/ncs-cia
/samples/ipc/ipc_service/ @nrfconnect/ncs-si-muffin
/samples/jwt/ @nrfconnect/ncs-modem @ayla-nordicsemi
/samples/keys/ @nrfconnect/ncs-aegir
/samples/matter/ @nrfconnect/ncs-matter
/samples/mpsl/ @nrfconnect/ncs-dragoon
Expand Down Expand Up @@ -640,6 +644,7 @@
/samples/gazell/**/*.rst @nrfconnect/ncs-si-muffin-doc
/samples/hw_id/*.rst @nrfconnect/ncs-cia-doc
/samples/ipc/ipc_service/*.rst @nrfconnect/ncs-si-muffin-doc
/samples/jwt/*.rst @nrfconnect/ncs-modem-doc @ayla-nordicsemi
/samples/keys/**/*.rst @nrfconnect/ncs-aegir-doc
/samples/matter/**/*.rst @nrfconnect/ncs-matter-doc
/samples/mpsl/**/*.rst @nrfconnect/ncs-dragoon-doc
Expand Down
80 changes: 80 additions & 0 deletions doc/nrf/libraries/app_jwt/app_jwt.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
.. _app_jwt:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
.. _app_jwt:
.. _lib_app_jwt:

As per the guideline here: https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/templates/library_template_README.html


Application JWT
###################
Comment on lines +3 to +4
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Application JWT
###################
Application JWT
###############


Copy link
Contributor

Choose a reason for hiding this comment

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

.. contents::
:local:
:depth: 2

The Application JWT library provides access to the `JSON Web Token (JWT)`_ generation feature from application core using signing and identity services from secure core.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The Application JWT library provides access to the `JSON Web Token (JWT)`_ generation feature from application core using signing and identity services from secure core.
The Application JWT library provides access to the `JSON Web Token (JWT)`_ generation feature from application core using signing services and secure core using identity services.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Signing is done by Secure core,
Identification (generation of the UUID) is also done by secure core.

maybe rephrasing to the following to avoid any ambiguities :

Suggested change
The Application JWT library provides access to the `JSON Web Token (JWT)`_ generation feature from application core using signing and identity services from secure core.
The Application JWT library provides access to the `JSON Web Token (JWT)`_ generation feature from Application core using Secure core signing service and identity service.


Configuration
*************

To use the library to request a JWT, complete the following steps:

1. Set the following Kconfig options to enable the library:

* :kconfig:option:`CONFIG_APP_JWT`
* :kconfig:option:`CONFIG_APP_JWT_VERIFY_SIGNATURE`
* :kconfig:option:`CONFIG_APP_JWT_PRINT_EXPORTED_PUBKEY_DER`
* :kconfig:option:`CONFIG_NRF_SECURITY`
* :Kconfig:option:`CONFIG_SSF_PSA_CRYPTO_SERVICE_ENABLED`
* :Kconfig:option:`CONFIG_SSF_DEVICE_INFO_SERVICE_ENABLED`

#. Read the device UUID (:c:func:`app_jwt_get_uuid`)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#. Read the device UUID (:c:func:`app_jwt_get_uuid`)
#. Read the device UUID (:c:func:`app_jwt_get_uuid`).

#. Populate the :c:struct:`app_jwt_data` structure with your desired values.
See `Possible structure values`_ for more information.
#. Pass the structure to the function that generates JWT (:c:func:`app_jwt_generate`).

If the function executes successfully, :c:member:`app_jwt_data.jwt_buf` will contain the JSON Web Token.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
If the function executes successfully, :c:member:`app_jwt_data.jwt_buf` will contain the JSON Web Token.
If the function executes successfully, :c:member:`app_jwt_data.jwt_buf` will contain the JSON web token.


.. note::
The library doesn't check the validity of the time source, it is up to the caller to make sure that the system has access to a valid one, otherwise "iat" field will containt the time since boot in seconds.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The library doesn't check the validity of the time source, it is up to the caller to make sure that the system has access to a valid one, otherwise "iat" field will containt the time since boot in seconds.
The library does not check the validity of the time source.
You must make sure that the system has access to a valid one, otherwise ``iat`` field will contain the time since boot, in seconds.

Is caller the end user here? If so, need to use "You" here.


.. note::
If the time value in seconds is equal to ``0``, the claim ``iat`` will be left out of the JWT.

Possible structure values
=========================

You can configure the following values in the :c:struct:`app_jwt_data` structure:

* :c:member:`app_jwt_data.sec_tag` - Optional, the ``sec_tag`` must contain a valid signing key.
If set to 0, the library will use the IAK for signing.
* :c:member:`app_jwt_data.key_type` - Required if ``sec_tag`` is not zero.
Defines the type of key in the sec tag.
* :c:member:`app_jwt_data.alg` - Required, always use the value JWT_ALG_TYPE_ES256.
Defines the JWT signing algorithm. Currently, only ECDSA 256 is supported.
Comment on lines +44 to +45
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* :c:member:`app_jwt_data.alg` - Required, always use the value JWT_ALG_TYPE_ES256.
Defines the JWT signing algorithm. Currently, only ECDSA 256 is supported.
* :c:member:`app_jwt_data.alg` - Required, always use the value ``JWT_ALG_TYPE_ES256``.
Defines the JWT signing algorithm.
Currently, only ECDSA 256 is supported.

* :c:member:`app_jwt_data.add_keyid_to_header` - Optional.
Corresponds to ``keyid`` claim.
Use ``0`` if you want to leave out this field.
* :c:member:`app_jwt_data.json_token_id` - Optional.
Corresponds to ``jti`` claim.
Use ``0`` if you want to leave out this field.
* :c:member:`app_jwt_data.subject` - Optional.
Corresponds to ``sub`` claim.
Use ``0`` if you want to leave out this field.
* :c:member:`app_jwt_data.audience` - Optional.
Corresponds to ``aud`` claim.
Use ``0`` if you want to leave out this field.
* :c:member:`app_jwt_data.issuer` - Optional.
Corresponds to ``iss`` claim.
Use ``0`` if you want to leave out this field.
* :c:member:`app_jwt_data.add_timestamp` - Optional.
Corresponds to ``iat`` claim.
Use ``0`` if you want to leave out this field.
* :c:member:`app_jwt_data.validity_s` - Optional.
Defines the expiration date for the JWT.
If set to 0, the field ``exp`` will be omitted from the generated JWT.
* :c:member:`app_jwt_data.jwt_buf` - Required.
Buffer for the generated, null-terminated, JWT string.
Buffer size has to be al least 600 bytes, at most 900 bytes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Buffer size has to be al least 600 bytes, at most 900 bytes.
Buffer size has to be from 600 bytes to 900 bytes.

The user has to provide a valid buffer, library doesn't do any allocation.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The user has to provide a valid buffer, library doesn't do any allocation.
You must to provide a valid buffer, library does not do any allocation.

* :c:member:`app_jwt_data.jwt_sz` - Size of JWT buffer.
Required, has to be equal to the size of :c:member:`app_jwt_data.jwt_buf`.

API documentation
*****************

| Header file: :file:`include/app_jwt.h`
| Source file: :file:`lib/app_jwt/app_jwt.c`

.. doxygengroup:: app_jwt
11 changes: 11 additions & 0 deletions doc/nrf/libraries/app_jwt/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.. _lib_app_jwt:

Library Application JWT
Copy link
Contributor

Choose a reason for hiding this comment

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

Since Application JWT is a single library, it would be better to placed under the "nrf\doc\nrf\libraries\others" folder.

########################

.. toctree::
:maxdepth: 1
:glob:
:caption: Subpages:

*
155 changes: 155 additions & 0 deletions include/app_jwt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#ifndef _APP_JWT_H
#define _APP_JWT_H

#ifdef __cplusplus
extern "C" {
#endif

/**
* @file app_jwt.h
*
* @brief Generate a JWT with from application core.
* @defgroup app_jwt JWT generation
* @{
*
*/

#include <stdint.h>
#include <stdbool.h>
#include <strings.h>

/** @brief Maximum size of a JWT string, could be used to allocate JWT
* output buffer.
*/
#define APP_JWT_STR_MAX_LEN 900

/** @brief Maximum valid duration for JWTs generated by user application */
#define APP_JWT_VALID_TIME_S_MAX (7 * 24 * 60 * 60)

/** @brief Default valid duration for JWTs generated by user application */
#define APP_JWT_VALID_TIME_S_DEF (10 * 60)

/** @brief UUID size in bytes */
#define APP_JWT_UUID_BYTE_SZ 16

/** @brief UUID v4 format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + '\0' */
#define APP_JWT_UUID_V4_STR_LEN (((APP_JWT_UUID_BYTE_SZ * 2) + 4) + 1)

/** @brief Size in bytes of each JWT String field */
#define APP_JWT_CLAIM_MAX_SIZE 64

/** @brief The type of key to be used for signing the JWT. */
enum app_jwt_key_type {
JWT_KEY_TYPE_CLIENT_PRIV = 2,
JWT_KEY_TYPE_ENDORSEMENT = 8,
};

/** @brief JWT signing algorithm */
enum app_jwt_alg_type {
JWT_ALG_TYPE_ES256 = 0,
};

/** @brief JWT parameters required for JWT generation and pointer to generated JWT */
struct app_jwt_data {
/** Sec tag to use for JWT signing */
unsigned int sec_tag;
/** Key type in the specified sec tag */
enum app_jwt_key_type key_type;
/** JWT signing algorithm */
enum app_jwt_alg_type alg;

/**
* Indicates if a 'kid' claim is requiered or not, if set to 1, 'kid' claim
* will contain sha256 of the signing key.
*/
bool add_keyid_to_header;

/**
* NULL terminated 'jti' claim; Unique identifier; can be used to prevent the
* JWT from being replayed
*/
const char *json_token_id;
/** NULL terminated 'sub' claim; the principal that is the subject of the JWT */
const char *subject;
/** NULL terminated 'aud' claim; intended recipient of the JWT */
const char *audience;
/** NULL terminated 'iss' claim; Issuer of the JWT */
const char *issuer;

/**
* Indicates if an issue timestamp is requiered or not, if set to 1, 'exp' claim
* will be present.
*/
bool add_timestamp;

/**
* Corresponds to 'exp' claim; Defines how long the JWT will be valid.
* If application has a valid time source, and the 'iat' claim is present,
* the timestamp in seconds will be added to this value.
*/
uint32_t validity_s;

/**
* Buffer to which the NULL terminated JWT will be copied.
* It is the responsibility of the user to provide a valid buffer.
* The returned JWT could be as long as 900 bytes, use the
* defined size value APP_JWT_STR_MAX_LEN to create your supplied return buffer.
*/
char *jwt_buf;
/** Size of the user provided buffer. */
size_t jwt_sz;
};

/**
* @brief Generates a JWT using the supplied parameters. If successful,
* the JWT string will be stored in the supplied struct.
* The user is responsible for providing a valid pointer to store the JWT.
*
* Subject and audience fields may be NULL in which case those fields are left out
* from generated JWT token.
*
* The API doesn't verify the time source validity, it is up to the caller to make sure
* that the system has access to a valid time source, otherwise "iat" field will
* contain the time since boot in seconds.
*
* JWT is signed with the application identity attestation key, no matter what
* value is supplied in the sec_tag.
*
* @param[in,out] jwt Pointer to struct containing JWT parameters and result.
*
* @retval 0 If the operation was successful.
* Otherwise, a (negative) error code is returned, check header errno.h for a full list.
Comment on lines +126 to +127
Copy link
Contributor

Choose a reason for hiding this comment

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

2 @retvals one per line, apply comments throughout

Copy link
Contributor Author

Choose a reason for hiding this comment

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

do you mean it should be like this :

Suggested change
* @retval 0 If the operation was successful.
* Otherwise, a (negative) error code is returned, check header errno.h for a full list.
* @retval 0 If the operation was successful.
* @retval a (negative) error code is returned, check header errno.h for a full list.

Copy link
Contributor

Choose a reason for hiding this comment

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

* @retval -errno Negative errno for other failures.

*/
int app_jwt_generate(struct app_jwt_data *const jwt);

/**
* @brief Gets the device UUID from the secure domain
* and returns it as a NULL terminated string in the supplied buffer.
* The device UUID can be used as a device identifier for cloud services and
* for secure device management using the nRF Cloud Identity Service.
*
* UUID v4 defined by ITU-T X.667 | ISO/IEC 9834-8 has a length of 35 bytes, add
* 1 byte for the atring termination character. User is expected to provide a buffer
* of at least 36 bytes.
*
* @param[out] uuid_buffer Pointer to buffer where the device UUID string will be written to.
* @param[in] uuid_buffer_size Size of the provided buffer.
*
* @retval 0 If the operation was successful.
* Otherwise, a (negative) error code is returned, check header errno.h for a full list.
*/
int app_jwt_get_uuid(char *uuid_buffer, const size_t uuid_buffer_size);

/** @} */

#ifdef __cplusplus
}
#endif

#endif /* _APP_JWT_H */
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ add_subdirectory_ifdef(CONFIG_HW_ID_LIBRARY hw_id)
add_subdirectory_ifdef(CONFIG_EDGE_IMPULSE edge_impulse)
add_subdirectory_ifdef(CONFIG_WAVE_GEN_LIB wave_gen)
add_subdirectory_ifdef(CONFIG_HW_UNIQUE_KEY_SRC hw_unique_key)
add_subdirectory_ifdef(CONFIG_APP_JWT app_jwt)
add_subdirectory_ifdef(CONFIG_MODEM_JWT modem_jwt)
add_subdirectory_ifdef(CONFIG_MODEM_SLM modem_slm)
add_subdirectory_ifdef(CONFIG_MODEM_ATTEST_TOKEN modem_attest_token)
Expand Down
1 change: 1 addition & 0 deletions lib/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

menu "Libraries"

rsource "app_jwt/Kconfig"
rsource "bin/Kconfig"
rsource "nrf_modem_lib/Kconfig"
rsource "adp536x/Kconfig"
Expand Down
11 changes: 11 additions & 0 deletions lib/app_jwt/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

zephyr_library()

zephyr_library_sources(
app_jwt.c
)
30 changes: 30 additions & 0 deletions lib/app_jwt/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

menuconfig APP_JWT
bool "Application JWT Library"
depends on SSF_CLIENT && SSF_PSA_CRYPTO_SERVICE_ENABLED && SSF_DEVICE_INFO_SERVICE_ENABLED
select BASE64
# Needed for time and date
select POSIX_API
# Needed to print integer values in JSON
select CJSON_LIB
select CBPRINTF_FP_SUPPORT

if APP_JWT

config APP_JWT_VERIFY_SIGNATURE
bool "Verify signature after signing"
default y

config APP_JWT_PRINT_EXPORTED_PUBKEY_DER
bool "Print to terminal the DER formatted public key"

module=APP_JWT
module-str=User App JWT
source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config"

endif # APP_JWT
Loading