From dfc74b9847e4fe39e6b98732ebab69c93b779beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vidar=20Lilleb=C3=B8?= Date: Thu, 11 May 2023 16:49:45 +0200 Subject: [PATCH] samples: crypto: Add sample for EC J-PAKE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds sample demonstrating use of EC J-PAKE. Signed-off-by: Vidar Lillebø --- doc/nrf/releases/release-notes-changelog.rst | 5 + samples/crypto/ecjpake/CMakeLists.txt | 6 + samples/crypto/ecjpake/README.rst | 43 ++++ samples/crypto/ecjpake/prj.conf | 9 + samples/crypto/ecjpake/sample.yaml | 18 ++ samples/crypto/ecjpake/src/main.c | 248 +++++++++++++++++++ 6 files changed, 329 insertions(+) create mode 100644 samples/crypto/ecjpake/CMakeLists.txt create mode 100644 samples/crypto/ecjpake/README.rst create mode 100644 samples/crypto/ecjpake/prj.conf create mode 100644 samples/crypto/ecjpake/sample.yaml create mode 100644 samples/crypto/ecjpake/src/main.c diff --git a/doc/nrf/releases/release-notes-changelog.rst b/doc/nrf/releases/release-notes-changelog.rst index 085ff336b76..c29dd3c2e77 100644 --- a/doc/nrf/releases/release-notes-changelog.rst +++ b/doc/nrf/releases/release-notes-changelog.rst @@ -206,6 +206,11 @@ Bluetooth mesh samples * Fixed an issue with the sample not fitting into RAM size on the nrf52dk_nrf52832 board. +Cryptography samples +-------------------- + +* Added the :ref:`crypto_ecjpake` sample demonstrating usage of EC J-PAKE. + nRF9160 samples --------------- diff --git a/samples/crypto/ecjpake/CMakeLists.txt b/samples/crypto/ecjpake/CMakeLists.txt new file mode 100644 index 00000000000..cf9f8f1e0a7 --- /dev/null +++ b/samples/crypto/ecjpake/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ecjpake) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/crypto/ecjpake/README.rst b/samples/crypto/ecjpake/README.rst new file mode 100644 index 00000000000..a823a579b94 --- /dev/null +++ b/samples/crypto/ecjpake/README.rst @@ -0,0 +1,43 @@ +.. _crypto_ecjpake: + +Crypto: EC J-PAKE +################# + +.. contents:: + :local: + :depth: 2 + +The EC J-PAKE sample demonstrates how to do password-authenticated key exchange using +the elliptic curve (EC) version of the password-authenticated key exchange by +juggling (J-PAKE) protocol. + +Requirements +************ + +The sample supports the following development kits: + +.. table-from-sample-yaml:: + +Overview +******** + +The sample performs the following operations: + +1. Initializes the Platform Security Architecture (PSA) API. +#. Goes through the steps for J-PAKE on server and client sides. +#. Verifies that the derived keys are the same. + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/crypto/ecjpake` + + +Testing +======= + +After programming the sample to your development kit, complete the following steps to test it: + +1. |connect_terminal| +#. Compile and program the application. +#. Observe the logs from the application using a terminal emulator. diff --git a/samples/crypto/ecjpake/prj.conf b/samples/crypto/ecjpake/prj.conf new file mode 100644 index 00000000000..4b2a4cd4653 --- /dev/null +++ b/samples/crypto/ecjpake/prj.conf @@ -0,0 +1,9 @@ +CONFIG_LOG=y +CONFIG_CONSOLE=y +CONFIG_PSA_CRYPTO_DRIVER_OBERON=y +CONFIG_PSA_WANT_ALG_TLS12_ECJPAKE_TO_PMS=y +CONFIG_NRF_SECURITY=y + +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=8192 +CONFIG_MAIN_STACK_SIZE=8192 diff --git a/samples/crypto/ecjpake/sample.yaml b/samples/crypto/ecjpake/sample.yaml new file mode 100644 index 00000000000..c737d598866 --- /dev/null +++ b/samples/crypto/ecjpake/sample.yaml @@ -0,0 +1,18 @@ +sample: + description: This app provides an example of EC J-PAKE + name: EC J-PAKE example +tests: + sample.ecjpake: + tags: introduction psa + platform_allow: > + nrf5340dk_nrf5340_cpuapp + nrf9160dk_nrf9160 nrf52840dk_nrf52840 + harness: console + harness_config: + type: multi_line + regex: + - ".*Example finished successfully!.*" + integration_platforms: + - nrf5340dk_nrf5340_cpuapp + - nrf9160dk_nrf9160 + - nrf52840dk_nrf52840 diff --git a/samples/crypto/ecjpake/src/main.c b/samples/crypto/ecjpake/src/main.c new file mode 100644 index 00000000000..932212bf50b --- /dev/null +++ b/samples/crypto/ecjpake/src/main.c @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ecjpake, LOG_LEVEL_DBG); + +#define PRINT_HEX(p_label, p_text, len) \ + ({ \ + LOG_INF("---- %s (len: %u): ----", p_label, len); \ + LOG_HEXDUMP_INF(p_text, len, "Content:"); \ + LOG_INF("---- %s end ----", p_label); \ + }) + +#define APP_SUCCESS (0) +#define APP_ERROR (-1) +#define APP_SUCCESS_MESSAGE "Example finished successfully!" +#define APP_ERROR_MESSAGE "Example exited with error!" + +psa_status_t do_rounds(psa_pake_operation_t *server, psa_pake_operation_t *client) +{ + psa_pake_step_t round1_steps[] = {PSA_PAKE_STEP_KEY_SHARE, PSA_PAKE_STEP_ZK_PUBLIC, + PSA_PAKE_STEP_ZK_PROOF, PSA_PAKE_STEP_KEY_SHARE, + PSA_PAKE_STEP_ZK_PUBLIC, PSA_PAKE_STEP_ZK_PROOF}; + uint8_t buffer[128]; + size_t outlen; + psa_status_t status; + + /* Server provides data to client */ + for (uint32_t i = 0; i < ARRAY_SIZE(round1_steps); i++) { + status = psa_pake_output(server, round1_steps[i], buffer, sizeof(buffer), &outlen); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_output failed. (Error: %d)", status); + return status; + } + + status = psa_pake_input(client, round1_steps[i], buffer, outlen); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_input failed. (Error: %d)", status); + return status; + } + } + + /* Client provides data to server. */ + for (uint32_t i = 0; i < ARRAY_SIZE(round1_steps); i++) { + status = psa_pake_output(client, round1_steps[i], buffer, sizeof(buffer), &outlen); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_output failed. (Error: %d)", status); + return status; + } + + status = psa_pake_input(server, round1_steps[i], buffer, outlen); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_input failed. (Error: %d)", status); + return status; + } + } + + psa_pake_step_t round2_steps[] = {PSA_PAKE_STEP_KEY_SHARE, PSA_PAKE_STEP_ZK_PUBLIC, + PSA_PAKE_STEP_ZK_PROOF}; + + /* Server provides data to client */ + for (uint32_t i = 0; i < ARRAY_SIZE(round2_steps); i++) { + status = psa_pake_output(server, round2_steps[i], buffer, sizeof(buffer), &outlen); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_output failed. (Error: %d)", status); + return status; + } + + status = psa_pake_input(client, round2_steps[i], buffer, outlen); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_input failed. (Error: %d)", status); + return status; + } + } + + /* Client provides data to server. */ + for (uint32_t i = 0; i < ARRAY_SIZE(round2_steps); i++) { + status = psa_pake_output(client, round2_steps[i], buffer, sizeof(buffer), &outlen); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_output failed. (Error: %d)", status); + return status; + } + + status = psa_pake_input(server, round2_steps[i], buffer, outlen); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_input failed. (Error: %d)", status); + return status; + } + } + + return status; +} + +psa_status_t pake_setup(psa_pake_operation_t *op, psa_pake_cipher_suite_t *cs, const char *user, + const char *peer, psa_key_id_t *password) +{ + psa_status_t status = psa_pake_setup(op, cs); + + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_setup failed. (Error: %d)", status); + return status; + } + + status = psa_pake_set_user(op, user, strlen(user)); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_set_user failed. (Error: %d)", status); + return status; + } + + status = psa_pake_set_peer(op, peer, strlen(peer)); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_set_peer failed. (Error: %d)", status); + return status; + } + + status = psa_pake_set_password_key(op, *password); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_set_password_key failed. (Error: %d)", status); + return status; + } + + return PSA_SUCCESS; +} + +psa_status_t do_key_derivation(psa_pake_operation_t *op, uint8_t *key_buffer, + size_t key_buffer_size) +{ + psa_key_derivation_operation_t kdf = PSA_KEY_DERIVATION_OPERATION_INIT; + + psa_status_t status = psa_key_derivation_setup(&kdf, PSA_ALG_TLS12_ECJPAKE_TO_PMS); + + if (status != PSA_SUCCESS) { + LOG_INF("psa_key_derivation_setup failed. (Error: %d)", status); + return status; + } + + status = psa_pake_get_implicit_key(op, &kdf); + if (status != PSA_SUCCESS) { + LOG_INF("psa_pake_get_implicit_key failed. (Error: %d)", status); + psa_key_derivation_abort(&kdf); + return status; + } + + status = psa_key_derivation_output_bytes(&kdf, key_buffer, key_buffer_size); + if (status != PSA_SUCCESS) { + LOG_INF("psa_key_derivation_output_bytes failed. (Error: %d)", status); + psa_key_derivation_abort(&kdf); + return status; + } + + return PSA_SUCCESS; +} + +int main(void) +{ + psa_status_t status = psa_crypto_init(); + + if (status != PSA_SUCCESS) { + LOG_INF("psa_crypto_init failed. (Error: %d)", status); + } + + psa_pake_cipher_suite_t cipher_suite = PSA_PAKE_CIPHER_SUITE_INIT; + + psa_pake_cs_set_algorithm(&cipher_suite, PSA_ALG_JPAKE); + psa_pake_cs_set_primitive(&cipher_suite, PSA_PAKE_PRIMITIVE(PSA_PAKE_PRIMITIVE_TYPE_ECC, + PSA_ECC_FAMILY_SECP_R1, 256)); + psa_pake_cs_set_hash(&cipher_suite, PSA_ALG_SHA_256); + + psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(&key_attributes, PSA_ALG_JPAKE); + psa_set_key_type(&key_attributes, PSA_KEY_TYPE_PASSWORD); + + psa_key_id_t key; + + status = psa_import_key(&key_attributes, "password", 8, &key); + if (status != PSA_SUCCESS) { + LOG_INF("psa_import_key failed. (Error: %d)", status); + goto error; + } + + /* Initialize PAKE operation object for the client.*/ + psa_pake_operation_t client = PSA_PAKE_OPERATION_INIT; + + status = pake_setup(&client, &cipher_suite, "client", "server", &key); + if (status != PSA_SUCCESS) { + goto error; + } + + /* Initialize PAKE operation object for the server. */ + psa_pake_operation_t server = PSA_PAKE_OPERATION_INIT; + + status = pake_setup(&server, &cipher_suite, "server", "client", &key); + if (status != PSA_SUCCESS) { + goto error; + } + + /* Perform the data exchange rounds */ + status = do_rounds(&server, &client); + if (status != PSA_SUCCESS) { + LOG_INF("EC J-PAKE rounds failed. (Error: %d)", status); + goto error; + } + + /* Retrieve keys from J-PAKE results. */ + uint8_t server_secret[32]; + uint8_t client_secret[32]; + + do_key_derivation(&server, server_secret, sizeof(server_secret)); + do_key_derivation(&client, client_secret, sizeof(client_secret)); + + PRINT_HEX("server_secret", server_secret, sizeof(server_secret)); + PRINT_HEX("client_secret", client_secret, sizeof(client_secret)); + + bool compare_eq = true; + + for (size_t i = 0; i < sizeof(server_secret); i++) { + if (server_secret[i] != client_secret[i]) { + compare_eq = false; + } + } + + if (!compare_eq) { + LOG_ERR("Derived keys for server and client are not equal."); + goto error; + } + + LOG_INF(APP_SUCCESS_MESSAGE); + return APP_SUCCESS; + +error: + LOG_INF(APP_ERROR_MESSAGE); + psa_destroy_key(key); + psa_pake_abort(&client); + psa_pake_abort(&server); + + return APP_ERROR; +}