Skip to content

Commit

Permalink
samples: crypto: Add Spake2+
Browse files Browse the repository at this point in the history
Adds sample demonstrating Spake2+ usage through PSA Crypto APIs.

Signed-off-by: Vidar Lillebø <vidar.lillebo@nordicsemi.no>
  • Loading branch information
vili-nordic authored and nordicjm committed Mar 21, 2024
1 parent eb0caf5 commit 88523f2
Show file tree
Hide file tree
Showing 6 changed files with 376 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ Cellular samples
Cryptography samples
--------------------

|no_changes_yet_note|
* Added :ref:`crypto_spake2p` sample.

Debug samples
-------------
Expand Down
6 changes: 6 additions & 0 deletions samples/crypto/spake2p/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(spake2p)

target_sources(app PRIVATE src/main.c)
41 changes: 41 additions & 0 deletions samples/crypto/spake2p/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.. _crypto_spake2p:

Crypto: Spake2+
###############

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

The Spake2+ sample demonstrates how to do a password-authenticated key exchange using the Spake2+ 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 Spake2+ on server and client sides.
#. Verifies that the derived keys are the same.

Building and running
********************

.. |sample path| replace:: :file:`samples/crypto/spake2p`


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.
17 changes: 17 additions & 0 deletions samples/crypto/spake2p/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Enable logging
CONFIG_LOG=y
CONFIG_CONSOLE=y

CONFIG_MAIN_STACK_SIZE=16000

# Enable nordic security backend and PSA APIs
CONFIG_PSA_WANT_GENERATE_RANDOM=y
CONFIG_NRF_SECURITY=y
CONFIG_MBEDTLS_ENABLE_HEAP=y
CONFIG_MBEDTLS_HEAP_SIZE=8192
CONFIG_PSA_WANT_ALG_SPAKE2P_HMAC=y
CONFIG_PSA_WANT_ALG_HKDF=y
CONFIG_PSA_WANT_ALG_HMAC=y
CONFIG_PSA_WANT_ALG_SHA_256=y
CONFIG_PSA_WANT_ECC_SECP_R1_256=y
CONFIG_PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT=y
24 changes: 24 additions & 0 deletions samples/crypto/spake2p/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
sample:
description: This app provides an example of Spake2+
name: Spake2+ example
tests:
sample.spake2p:
tags: introduction psa oberon
platform_allow: >
nrf5340dk_nrf5340_cpuapp
nrf9160dk_nrf9160 nrf52840dk_nrf52840
nrf9161dk_nrf9161
nrf54l15pdk_nrf54l15_cpuapp
nrf54h20pdk_nrf54h20_cpuapp
harness: console
harness_config:
type: multi_line
regex:
- ".*Example finished successfully!.*"
integration_platforms:
- nrf5340dk_nrf5340_cpuapp
- nrf9160dk_nrf9160
- nrf9161dk_nrf9161
- nrf52840dk_nrf52840
- nrf54l15pdk_nrf54l15_cpuapp
- nrf54h20pdk_nrf54h20_cpuapp
287 changes: 287 additions & 0 deletions samples/crypto/spake2p/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include <psa/crypto.h>
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>

LOG_MODULE_REGISTER(spake2p, 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!"

/* w0 || w1 */
static const uint8_t key_pair[] = {0x54, 0x8E, 0xC1, 0x42, 0xE2, 0x27, 0x90, 0x23, 0x7C, 0x67, 0xA8,
0x88, 0x49, 0xE8, 0x61, 0xD3, 0x77, 0x00, 0x5F, 0x0A, 0x5C, 0x33,
0x88, 0xF9, 0xAF, 0xA1, 0xC2, 0xFA, 0x58, 0xC7, 0xDA, 0x51, 0x35,
0x99, 0xF8, 0x67, 0x1D, 0xBB, 0x67, 0x04, 0xA2, 0xC6, 0x3A, 0x78,
0x4F, 0xC9, 0x5C, 0xD2, 0x8E, 0xBC, 0x55, 0x2E, 0xA4, 0x79, 0x98,
0xB9, 0x18, 0x69, 0x9A, 0xB9, 0x3F, 0x4F, 0x7A, 0xD7};

/* w0 || L */
static const uint8_t public_key[] = {
0x54, 0x8E, 0xC1, 0x42, 0xE2, 0x27, 0x90, 0x23, 0x7C, 0x67, 0xA8, 0x88, 0x49, 0xE8,
0x61, 0xD3, 0x77, 0x00, 0x5F, 0x0A, 0x5C, 0x33, 0x88, 0xF9, 0xAF, 0xA1, 0xC2, 0xFA,
0x58, 0xC7, 0xDA, 0x51, 0x04, 0x81, 0x43, 0x3D, 0xC5, 0x93, 0xC9, 0x46, 0xC9, 0x37,
0xD9, 0x90, 0x26, 0xDD, 0x42, 0x14, 0x40, 0xE1, 0xC8, 0x7D, 0x0E, 0xC4, 0x94, 0x8B,
0xFF, 0x59, 0xEA, 0xF4, 0x77, 0xE3, 0x35, 0xE5, 0x52, 0x49, 0x66, 0xB2, 0x03, 0x31,
0x37, 0xD8, 0x4C, 0x65, 0x56, 0xDE, 0x07, 0x31, 0x57, 0x5C, 0xD2, 0x95, 0xC9, 0x75,
0x12, 0x4F, 0x52, 0x13, 0x25, 0xF7, 0x80, 0x01, 0xEC, 0xBE, 0x67, 0xE8, 0xB7};

static psa_status_t send_message(psa_pake_operation_t *from, psa_pake_operation_t *to,
psa_pake_step_t step)
{
uint8_t data[1024];
size_t length;

psa_status_t status = psa_pake_output(from, step, data, sizeof(data), &length);

if (status) {
return status;
}
PRINT_HEX("send_message", data, length);
status = psa_pake_input(to, step, data, length);
return status;
}

static psa_status_t pake_setup(psa_pake_operation_t *op, psa_pake_role_t role, psa_key_id_t key,
psa_pake_cipher_suite_t *cipher_suite)
{
psa_status_t status;

status = psa_pake_setup(op, key, cipher_suite);
if (status != PSA_SUCCESS) {
LOG_INF("psa_pake_setup failed. (Error: %d)", status);
return status;
}

status = psa_pake_set_role(op, role);
if (status != PSA_SUCCESS) {
LOG_INF("psa_pake_set_role failed. (Error: %d)", status);
return status;
}

if (role == PSA_PAKE_ROLE_SERVER) {
status = psa_pake_set_user(op, "client", strlen("client"));
if (status != PSA_SUCCESS) {
LOG_INF("psa_pake_set_user failed. (Error: %d)", status);
return status;
}

status = psa_pake_set_peer(op, "server", strlen("server"));
if (status != PSA_SUCCESS) {
LOG_INF("psa_pake_set_peer failed. (Error: %d)", status);
return status;
}
} else {
status = psa_pake_set_user(op, "server", strlen("server"));
if (status != PSA_SUCCESS) {
LOG_INF("psa_pake_set_user failed. (Error: %d)", status);
return status;
}

status = psa_pake_set_peer(op, "client", strlen("client"));
if (status != PSA_SUCCESS) {
LOG_INF("psa_pake_set_peer failed. (Error: %d)", status);
return status;
}
}

return status;
}

int main(void)
{
/* For Matter-compatible Spake2+, this will be PSA_ALG_SPAKE2P_MATTER */
psa_algorithm_t psa_spake_alg = PSA_ALG_SPAKE2P_HMAC(PSA_ALG_SHA_256);
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_spake_alg);
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_key_confirmation(&cipher_suite, PSA_PAKE_CONFIRMED_KEY);

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_spake_alg);
psa_set_key_type(&key_attributes, PSA_KEY_TYPE_SPAKE2P_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1));
psa_set_key_bits(&key_attributes, 256);

psa_key_id_t key;

status = psa_import_key(&key_attributes, key_pair, sizeof(key_pair), &key);
if (status != PSA_SUCCESS) {
LOG_INF("psa_import_key failed. (Error: %d)", status);
goto error;
}

psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_DERIVE);
psa_set_key_algorithm(&key_attributes, psa_spake_alg);
psa_set_key_type(&key_attributes, PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1));
psa_set_key_bits(&key_attributes, 256);

psa_key_id_t key_server;

status = psa_import_key(&key_attributes, public_key, sizeof(public_key), &key_server);
if (status != PSA_SUCCESS) {
LOG_INF("psa_import_key failed. (Error: %d)", status);
goto error;
}

psa_reset_key_attributes(&key_attributes);

psa_pake_operation_t client_op = PSA_PAKE_OPERATION_INIT;

status = pake_setup(&client_op, PSA_PAKE_ROLE_CLIENT, key, &cipher_suite);
if (status != PSA_SUCCESS) {
goto error;
}

psa_pake_operation_t server_op = PSA_PAKE_OPERATION_INIT;

status = pake_setup(&server_op, PSA_PAKE_ROLE_SERVER, key, &cipher_suite);
if (status != PSA_SUCCESS) {
goto error;
}

/* Share P */
status = send_message(&client_op, &server_op, PSA_PAKE_STEP_KEY_SHARE);
if (status != PSA_SUCCESS) {
LOG_INF("send_message failed. (Error: %d, step: %d)", status,
PSA_PAKE_STEP_KEY_SHARE);
goto error;
}

/* Share V */
status = send_message(&server_op, &client_op, PSA_PAKE_STEP_KEY_SHARE);
if (status != PSA_SUCCESS) {
LOG_INF("send_message failed. (Error: %d, step: %d)", status,
PSA_PAKE_STEP_KEY_SHARE);
goto error;
}

/* Confirm P */
status = send_message(&server_op, &client_op, PSA_PAKE_STEP_CONFIRM);
if (status != PSA_SUCCESS) {
LOG_INF("send_message failed. (Error: %d, step: %d)", status,
PSA_PAKE_STEP_CONFIRM);
goto error;
}

/* Confirm V */
status = send_message(&client_op, &server_op, PSA_PAKE_STEP_CONFIRM);
if (status != PSA_SUCCESS) {
LOG_INF("send_message failed. (Error: %d, step: %d)", status,
PSA_PAKE_STEP_CONFIRM);
goto error;
}

psa_key_derivation_operation_t kdf = PSA_KEY_DERIVATION_OPERATION_INIT;
uint8_t client_secret[32] = {0};
uint8_t server_secret[32] = {0};

psa_key_attributes_t shared_key_attributes = PSA_KEY_ATTRIBUTES_INIT;

psa_set_key_type(&shared_key_attributes, PSA_KEY_TYPE_DERIVE);
psa_set_key_usage_flags(&shared_key_attributes, PSA_KEY_USAGE_DERIVE);
psa_set_key_algorithm(&shared_key_attributes, PSA_ALG_HKDF(PSA_ALG_SHA_256));

struct {
psa_pake_operation_t *op;
uint8_t *secret;
} client_server[] = {{&client_op, client_secret}, {&server_op, server_secret}};

for (size_t i = 0; i < ARRAY_SIZE(client_server); i++) {
psa_key_id_t key;

status = psa_pake_get_shared_key(client_server[i].op, &shared_key_attributes, &key);
if (status != PSA_SUCCESS) {
LOG_INF("psa_pake_get_shared_key failed. (Error: %d)", status);
goto error;
}

status = psa_key_derivation_setup(&kdf, PSA_ALG_HKDF(PSA_ALG_SHA_256));
if (status != PSA_SUCCESS) {
LOG_INF("psa_key_derivation_setup failed. (Error: %d)", status);
goto error;
}

status = psa_key_derivation_input_key(&kdf, PSA_KEY_DERIVATION_INPUT_SECRET, key);
if (status != PSA_SUCCESS) {
LOG_INF("psa_key_derivation_input_key failed. (Error: %d)", status);
goto error;
}

status = psa_key_derivation_input_bytes(&kdf, PSA_KEY_DERIVATION_INPUT_INFO, "Info",
4);
if (status != PSA_SUCCESS) {
LOG_INF("psa_key_derivation_input_bytes failed. (Error: %d)", status);
goto error;
}

status = psa_key_derivation_output_bytes(&kdf, client_server[i].secret,
sizeof(client_secret));
if (status != PSA_SUCCESS) {
LOG_INF("psa_key_derivation_output_bytes failed. (Error: %d)", status);
goto error;
}

status = psa_key_derivation_abort(&kdf);
if (status != PSA_SUCCESS) {
LOG_INF("psa_key_derivation_abort failed. (Error: %d)", status);
goto error;
}

status = psa_destroy_key(key);
if (status != PSA_SUCCESS) {
LOG_INF("psa_destroy_key failed. (Error: %d)", status);
goto error;
}
}

psa_reset_key_attributes(&shared_key_attributes);
PRINT_HEX("server_secret", client_secret, sizeof(client_secret));
PRINT_HEX("client_secret", server_secret, sizeof(server_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);
return APP_ERROR;
}

0 comments on commit 88523f2

Please sign in to comment.