From 1f4ed74eb77f7801eb464933ff90e2d402ce13fa Mon Sep 17 00:00:00 2001 From: Hannes Tschofenig Date: Sun, 28 Jan 2024 12:56:35 +0100 Subject: [PATCH] New COSE-HPKE Implementation Attempt Focusing on in preparation for interoperability testing. --- Makefile.common | 48 +- Makefile.ossl | 4 +- Makefile.psa | 27 +- Makefile.test | 13 +- examples/encryption_examples.c | 242 +++- examples/encryption_examples.h | 4 + examples/examples_main.c | 10 + examples/hpke_test.c | 341 ++++++ inc/t_cose/t_cose_common.h | 9 + inc/t_cose/t_cose_parameters.h | 44 + inc/t_cose/t_cose_recipient_dec_hpke.h | 106 ++ inc/t_cose/t_cose_recipient_enc_hpke.h | 135 +++ inc/t_cose/t_cose_standard_constants.h | 17 +- src/hpke.c | 1458 ++++++++++++++++++++++++ src/hpke.h | 359 ++++++ src/t_cose_parameters.c | 20 + src/t_cose_recipient_dec_hpke.c | 203 ++++ src/t_cose_recipient_enc_hpke.c | 263 +++++ test/run_tests.c | 9 + test/t_cose_encrypt_decrypt_test.c | 263 ++++- test/t_cose_encrypt_decrypt_test.h | 4 + 21 files changed, 3550 insertions(+), 29 deletions(-) create mode 100644 examples/hpke_test.c create mode 100644 inc/t_cose/t_cose_recipient_dec_hpke.h create mode 100644 inc/t_cose/t_cose_recipient_enc_hpke.h create mode 100644 src/hpke.c create mode 100644 src/hpke.h create mode 100644 src/t_cose_recipient_dec_hpke.c create mode 100644 src/t_cose_recipient_enc_hpke.c diff --git a/Makefile.common b/Makefile.common index ca3d1955..b5039617 100644 --- a/Makefile.common +++ b/Makefile.common @@ -33,8 +33,11 @@ SRC_OBJ=src/t_cose_util.o \ src/t_cose_encrypt_dec.o \ src/t_cose_recipient_dec_keywrap.o \ src/t_cose_recipient_enc_keywrap.o \ + src/t_cose_recipient_dec_hpke.o \ + src/t_cose_recipient_enc_hpke.o \ src/t_cose_recipient_dec_esdh.o \ src/t_cose_recipient_enc_esdh.o \ + src/hpke.o \ src/t_cose_qcbor_gap.o @@ -58,6 +61,10 @@ EXAMPLE_OBJ=examples/examples_main.o \ examples/example_keys.o \ examples/print_buf.o +# ---- Object files for hpke_test ---- +HPKE_TEST_OBJ=examples/hpke_test.o \ + examples/example_keys.o \ + examples/print_buf.o # ---- public headers ----- PUBLIC_INTERFACE=inc/t_cose/q_useful_buf.h \ @@ -70,8 +77,10 @@ PUBLIC_INTERFACE=inc/t_cose/q_useful_buf.h \ inc/t_cose/t_cose_parameters.h \ inc/t_cose/t_cose_recipient_dec.h \ inc/t_cose/t_cose_recipient_dec_keywrap.h \ + inc/t_cose/t_cose_recipient_dec_hpke.h \ inc/t_cose/t_cose_recipient_enc.h \ inc/t_cose/t_cose_recipient_enc_keywrap.h \ + inc/t_cose/t_cose_recipient_enc_hpke.h \ inc/t_cose/t_cose_recipient_dec_esdh.h \ inc/t_cose/t_cose_recipient_enc.h \ inc/t_cose/t_cose_recipient_enc_keywrap.h \ @@ -193,7 +202,7 @@ src/t_cose_recipient_enc_keywrap.o: src/t_cose_recipient_enc_keywrap.c src/t_cose_recipient_dec_keywrap.o: src/t_cose_recipient_dec_keywrap.c -src/t_cose_recpient_enc_esdh.o: src/t_cose_recipient_enc_esdh.c \ +src/t_cose_recipient_enc_esdh.o: src/t_cose_recipient_enc_esdh.c \ inc/t_cose/t_cose_standard_constants.h \ inc/t_cose/t_cose_key.h \ inc/t_cose/t_cose_encrypt_enc.h \ @@ -203,9 +212,9 @@ src/t_cose_recpient_enc_esdh.o: src/t_cose_recipient_enc_esdh.c \ inc/t_cose/t_cose_recipient_enc_keywrap.h \ inc/t_cose/q_useful_buf.h \ src/t_cose_crypto.h \ - src/t_cose_util.h \ + src/t_cose_util.h -src/t_cose_recpient_dec_esdh.o: src/t_cose_recipient_dec_esdh.c \ +src/t_cose_recipient_dec_esdh.o: src/t_cose_recipient_dec_esdh.c \ inc/t_cose/t_cose_recipient_dec_esdh.h \ inc/t_cose/t_cose_parameters.h \ inc/t_cose/q_useful_buf.h \ @@ -218,6 +227,33 @@ src/t_cose_recpient_dec_esdh.o: src/t_cose_recipient_dec_esdh.c \ src/t_cose_crypto.h \ src/t_cose_util.h +src/t_cose_recipient_enc_hpke.o: src/t_cose_recipient_enc_hpke.c \ + src/hpke.h \ + inc/t_cose/t_cose_standard_constants.h \ + inc/t_cose/t_cose_key.h \ + inc/t_cose/t_cose_encrypt_enc.h \ + inc/t_cose/t_cose_recipient_enc.h \ + inc/t_cose/t_cose_common.h \ + inc/t_cose/t_cose_parameters.h \ + inc/t_cose/t_cose_recipient_enc_keywrap.h \ + inc/t_cose/q_useful_buf.h \ + src/t_cose_crypto.h \ + src/t_cose_util.h + +src/t_cose_recipient_dec_hpke.o: src/t_cose_recipient_dec_hpke.c \ + src/hpke.h \ + inc/t_cose/t_cose_recipient_dec_hpke.h \ + inc/t_cose/t_cose_parameters.h \ + inc/t_cose/q_useful_buf.h \ + inc/t_cose/t_cose_common.h \ + inc/t_cose/t_cose_standard_constants.h \ + inc/t_cose/t_cose_recipient_dec.h \ + inc/t_cose/t_cose_key.h \ + inc/t_cose/t_cose_encrypt_enc.h \ + inc/t_cose/t_cose_recipient_enc.h \ + src/t_cose_crypto.h \ + src/t_cose_util.h + # ---- test dependencies ----- test/t_cose_test.o: test/t_cose_test.h \ test/t_cose_make_test_messages.h \ @@ -257,6 +293,12 @@ examples/encryption_examples.o: examples/encryption_examples.h \ examples/init_keys.h \ examples/print_buf.h + +examples/hpke_test.o: examples/hpke_test.c \ + $(PUBLIC_INTERFACE) \ + examples/init_keys.h \ + examples/print_buf.h + examples/signing_examples.o: examples/signing_examples.h \ examples/signing_examples.c \ examples/init_keys.h \ diff --git a/Makefile.ossl b/Makefile.ossl index ec3e0487..710fd67b 100644 --- a/Makefile.ossl +++ b/Makefile.ossl @@ -59,8 +59,8 @@ CRYPTO_EXAMPLE_OBJ=examples/init_keys_ossl.o # ---- other configuration ---- # Use as needed. It has been used to disable features that aren't ready # or that aren't passing tests -OTHER_OPTS= - +# OTHER_OPTS= +OTHER_OPTS=-DT_COSE_DISABLE_HPKE # ---- compiler configuration ----- # This makefile uses a minimum of compiler flags so that it will diff --git a/Makefile.psa b/Makefile.psa index b20ea9ad..29730ecd 100644 --- a/Makefile.psa +++ b/Makefile.psa @@ -26,29 +26,28 @@ # This is for direct reference to QCBOR that is not installed in # /usr/local or some system location. The path may need to be adjusted # for your location of QCBOR. -#QCBOR_DIR=../../QCBOR/master -#QCBOR_INC=-I $(QCBOR_DIR)/inc -#QCBOR_LIB=$(QCBOR_DIR)/libqcbor.a +QCBOR_INC=-I ../QCBOR/inc +QCBOR_LIB=../QCBOR/build/libqcbor.a -lm # This is for reference to QCBOR that is installed in /usr/local or in # some system location. This will typically use dynamic linking if # there is a libqcbor.so -QCBOR_INC=-I /usr/local/include -QCBOR_LIB=-lqcbor - +#QCBOR_INC=-I /usr/local/include +#QCBOR_LIB=-lqcbor # ---- crypto configuration ----- # These two are for direct reference to Mbed TLS crypto that is not # installed in /usr/local or some system location. The path names may # need to be adjusted for your location of Mbed TLS -#CRYPTO_INC=-I ../../mbedtls/include/ +CRYPTO_INC=-I ../mbedtls/include/ #CRYPTO_LIB=../../mbedtls/library/libmbedcrypto.a +CRYPTO_LIB=../mbedtls/build/library/libmbedcrypto.a # These two are for reference to Mbed TLS that has been installed in # /usr/local or in some system location. -CRYPTO_LIB=-l mbedcrypto -CRYPTO_INC=-I /usr/local/include +#CRYPTO_LIB=-l mbedcrypto +#CRYPTO_INC=-I /usr/local/include CRYPTO_CONFIG_OPTS=-DT_COSE_USE_PSA_CRYPTO CRYPTO_OBJ=crypto_adapters/t_cose_psa_crypto.o @@ -59,7 +58,7 @@ CRYPTO_EXAMPLE_OBJ=examples/init_keys_psa.o # ---- other configuration ---- # Use as needed. It has been used to disable features that aren't ready # or that aren't passing tests -OTHER_OPTS= +OTHER_OPTS=-O0 -g # ---- compiler configuration ----- @@ -83,7 +82,7 @@ CFLAGS=$(CMD_LINE) $(ALL_INC) $(C_OPTS) $(CRYPTO_CONFIG_OPTS) $(OTHER_OPTS) # ---- The build targets ---- .PHONY: all install install_headers install_so uninstall clean warn -all: libt_cose.a t_cose_test t_cose_examples +all: libt_cose.a t_cose_test t_cose_examples hpke_test # run "make warn" as a handy way to compile with the warning flags # used in the QCBOR release process. See C_OPTS above. @@ -124,6 +123,10 @@ t_cose_examples: $(EXAMPLE_OBJ) $(CRYPTO_EXAMPLE_OBJ) libt_cose.a cc -o $@ $^ $(QCBOR_LIB) $(CRYPTO_LIB) +hpke_test: $(HPKE_TEST_OBJ) $(CRYPTO_EXAMPLE_OBJ) libt_cose.a + cc -o $@ $^ $(QCBOR_LIB) $(CRYPTO_LIB) + + # ---- Installation ---- ifeq ($(PREFIX),) PREFIX := /usr/local @@ -153,6 +156,6 @@ uninstall: libt_cose.a $(PUBLIC_INTERFACE) clean: rm -f $(SRC_OBJ) $(TEST_OBJ) $(CRYPTO_OBJ) $(CRYPTO_TEST_OBJ) $(EXAMPLE_OBJ) $(CRYPTO_EXAMPLE_OBJ) \ - t_cose_examples libt_cose.so \ + t_cose_examples hpke_test libt_cose.so \ main.o t_cose_test libt_cose.a libt_cose.so diff --git a/Makefile.test b/Makefile.test index fb22a0a6..72c6c1b1 100644 --- a/Makefile.test +++ b/Makefile.test @@ -26,15 +26,16 @@ # This is for direct reference to QCBOR that is not installed in # /usr/local or some system location. The path may need to be adjusted # for your location of QCBOR. -#QCBOR_DIR=../../QCBOR/master -#QCBOR_INC=-I $(QCBOR_DIR)/inc -#QCBOR_LIB=$(QCBOR_DIR)/libqcbor.a +QCBOR_DIR=../QCBOR +QCBOR_INC=-I $(QCBOR_DIR)/inc +QCBOR_LIB=$(QCBOR_DIR)/build/libqcbor.a + # This is for reference to QCBOR that is installed in /usr/local or in # some system location. This will typically use dynamic linking if # there is a libqcbor.so -QCBOR_INC=-I /usr/local/include -QCBOR_LIB=-lqcbor +#QCBOR_INC=-I /usr/local/include +#QCBOR_LIB=-lqcbor # ---- crypto configuration ----- @@ -50,7 +51,7 @@ CRYPTO_TEST_OBJ=examples/init_keys_test.o # ---- other configuration ---- # Use as needed. It has been used to disable features that aren't ready # or that aren't passing tests -OTHER_OPTS=-DT_COSE_ENABLE_HASH_FAIL_TEST -DT_COSE_DISABLE_SIGN_VERIFY_TESTS +OTHER_OPTS=-DT_COSE_ENABLE_HASH_FAIL_TEST -DT_COSE_DISABLE_SIGN_VERIFY_TESTS -DT_COSE_DISABLE_HPKE # ---- compiler configuration ----- diff --git a/examples/encryption_examples.c b/examples/encryption_examples.c index a6e405ea..1c605447 100644 --- a/examples/encryption_examples.c +++ b/examples/encryption_examples.c @@ -1,8 +1,9 @@ /* - * encryption_examples.c + * \file encryption_examples.c * * Copyright (c) 2022, Arm Limited. All rights reserved. * Copyright 2023, Laurence Lundblade + * Copyright (c) 2024, Hannes Tschofenig. All rights reserved. * * Created by Laurence Lundblade on 2/6/23 from previous files. * @@ -259,6 +260,244 @@ key_wrap_example(void) +#ifndef T_COSE_DISABLE_HPKE + +#include "t_cose/t_cose_recipient_enc_hpke.h" +#include "t_cose/t_cose_recipient_dec_hpke.h" + +int32_t +hpke_example(void) +{ + struct t_cose_encrypt_enc enc_ctx; + enum t_cose_err_t result; + struct t_cose_recipient_enc_hpke recipient; + struct q_useful_buf_c cose_encrypted_message; + struct q_useful_buf_c decrypted_plain_text; + struct t_cose_key skR; + struct t_cose_key pkR; + Q_USEFUL_BUF_MAKE_STACK_UB ( cose_encrypt_message_buffer, 200); + Q_USEFUL_BUF_MAKE_STACK_UB ( decrypted_plaintext_buffer, 50); + struct t_cose_recipient_dec_hpke dec_recipient; + struct t_cose_encrypt_dec_ctx dec_ctx; + + printf("\n---- START EXAMPLE HPKE ----\n"); + printf("Create COSE_Encrypt with included payload using HPKE with KEM: DHKEM(P-256, HKDF-SHA256), KDF: HKDF-SHA256, AEAD: AES-128-GCM\n"); + + /* Create a key pair. This is a fixed test key pair. The creation + * of this key pair is crypto-library dependent because t_cose_key + * is crypto-library dependent. See t_cose_key.h and the examples + * to understand key-pair creation better. */ + result = init_fixed_test_ec_encryption_key(T_COSE_ELLIPTIC_CURVE_P_256, + &pkR, /* out: public key to be used for encryption */ + &skR); /* out: corresponding private key for decryption */ + if(result != T_COSE_SUCCESS) { + goto Done; + } + + /* Initialize the encryption context telling it we want + * a COSE_Encrypt (not a COSE_Encrypt0) because we're doing HPKE with a + * COSE_Recpipient. Also tell it the AEAD algorithm for the + * body of the message. + */ + t_cose_encrypt_enc_init(&enc_ctx, + T_COSE_OPT_MESSAGE_TYPE_ENCRYPT, + T_COSE_ALGORITHM_A128GCM); + + /* Create the recipient object telling it the algorithm and the public key + * for the COSE_Recipient it's going to make. Then give that object + * to the main encryption context. (Only one recipient is set here, but + * there could be more) + */ + t_cose_recipient_enc_hpke_init(&recipient, + T_COSE_HPKE_Base_P256_SHA256_AES128GCM); + + t_cose_recipient_enc_hpke_set_key(&recipient, + pkR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + t_cose_encrypt_add_recipient(&enc_ctx, + (struct t_cose_recipient_enc *)&recipient); + + + /* Now do the actual encryption */ + result = t_cose_encrypt_enc(&enc_ctx, /* in: encryption context */ + Q_USEFUL_BUF_FROM_SZ_LITERAL(PAYLOAD), /* in: payload to encrypt */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + cose_encrypt_message_buffer, /* in: buffer for COSE_Encrypt */ + &cose_encrypted_message); /* out: COSE_Encrypt */ + + if (result != T_COSE_SUCCESS) { + printf("error encrypting (%d)\n", result); + goto Done; + } + + print_useful_buf("COSE_Encrypt: ", cose_encrypted_message); + + printf("\nHPKE encryption succeeded; starting decryption\n"); + + + /* Set up the decryption context, telling it what type of + * message to expect if there's no tag (that part isn't quite implemented right yet anyway). + */ + t_cose_encrypt_dec_init(&dec_ctx, T_COSE_OPT_MESSAGE_TYPE_ENCRYPT); + + + /* Set up the recipient object with the key material. We happen to know + * what the algorithm and key are in advance so we don't have to + * decode the parameters first to figure that out (not that this part is + * working yet). */ + t_cose_recipient_dec_hpke_init(&dec_recipient); + t_cose_recipient_dec_hpke_set_skr(&dec_recipient, + skR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + t_cose_encrypt_dec_add_recipient(&dec_ctx, (struct t_cose_recipient_dec *)&dec_recipient); + + result = t_cose_encrypt_dec(&dec_ctx, + cose_encrypted_message, /* in: the COSE_Encrypt message */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + decrypted_plaintext_buffer, + &decrypted_plain_text, + NULL); + + if (result != T_COSE_SUCCESS) { + printf("error decrypting (%d)\n", result); + goto Done; + } + + if(q_useful_buf_compare(decrypted_plain_text, Q_USEFUL_BUF_FROM_SZ_LITERAL(PAYLOAD))) { + printf("Decrypted content didn't match payload\n"); + result = 1; + goto Done; + } + + print_useful_buf("Decrypted plaintext: ", decrypted_plain_text); + +Done: + printf("---- %s EXAMPLE HPKE (%d) ----\n\n", + result ? "FAILED" : "COMPLETED", result); + return (int32_t)result; +} + + + + +int32_t +hpke_example_detached(void) +{ + struct t_cose_encrypt_enc enc_ctx; + enum t_cose_err_t result; + struct t_cose_recipient_enc_hpke recipient; + struct q_useful_buf_c cose_encrypted_message; + struct q_useful_buf_c encrypted_detached_payload; + struct q_useful_buf_c decrypted_plain_text; + struct t_cose_key skR; + struct t_cose_key pkR; + Q_USEFUL_BUF_MAKE_STACK_UB ( cose_encrypt_message_buffer, 200); + Q_USEFUL_BUF_MAKE_STACK_UB ( encrypted_detached_ciphertext_buffer, 50); + Q_USEFUL_BUF_MAKE_STACK_UB ( decrypted_plaintext_buffer, 50); + struct t_cose_recipient_dec_hpke dec_recipient; + struct t_cose_encrypt_dec_ctx dec_ctx; + + printf("\n---- START EXAMPLE HPKE ----\n"); + printf("Create COSE_Encrypt with detached payload using HPKE with KEM: DHKEM(P-256, HKDF-SHA256), KDF: HKDF-SHA256, AEAD: AES-128-GCM\n"); + + /* Create a key pair. This is a fixed test key pair. The creation + * of this key pair is crypto-library dependent because t_cose_key + * is crypto-library dependent. See t_cose_key.h and the examples + * to understand key-pair creation better. */ + result = init_fixed_test_ec_encryption_key(T_COSE_ELLIPTIC_CURVE_P_256, + &pkR, /* out: public key to be used for encryption */ + &skR); /* out: corresponding private key for decryption */ + if(result != T_COSE_SUCCESS) { + goto Done; + } + + /* Initialize the encryption context telling it we want + * a COSE_Encrypt (not a COSE_Encrypt0) because we're doing HPKE with a + * COSE_Recpipient. Also tell it the AEAD algorithm for the + * body of the message. + */ + t_cose_encrypt_enc_init(&enc_ctx, + T_COSE_OPT_MESSAGE_TYPE_ENCRYPT, + T_COSE_ALGORITHM_A128GCM); + + /* Create the recipient object telling it the algorithm and the public key + * for the COSE_Recipient it's going to make. Then give that object + * to the main encryption context. (Only one recipient is set here, but + * there could be more) + */ + t_cose_recipient_enc_hpke_init(&recipient, + T_COSE_HPKE_Base_P256_SHA256_AES128GCM); + + t_cose_recipient_enc_hpke_set_key(&recipient, + pkR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + + t_cose_encrypt_add_recipient(&enc_ctx, + (struct t_cose_recipient_enc *)&recipient); + + /* Now do the actual encryption */ + result = t_cose_encrypt_enc_detached(&enc_ctx, /* in: encryption context */ + Q_USEFUL_BUF_FROM_SZ_LITERAL(PAYLOAD), /* in: payload to encrypt */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + encrypted_detached_ciphertext_buffer, /* in: buffer for detached cipher text */ + cose_encrypt_message_buffer, /* in: buffer for COSE_Encrypt */ + &encrypted_detached_payload, /* out: encrypted detached */ + &cose_encrypted_message); /* out: COSE_Encrypt */ + + if (result != T_COSE_SUCCESS) { + printf("error encrypting (%d)\n", result); + goto Done; + } + + print_useful_buf("COSE_Encrypt: ", cose_encrypted_message); + print_useful_buf("Detached Ciphertext: ", encrypted_detached_payload); + + printf("\nHPKE encryption succeeded; starting decryption\n"); + + /* Set up the decryption context, telling it what type of + * message to expect if there's no tag (that part isn't quite implemented right yet anyway). + */ + t_cose_encrypt_dec_init(&dec_ctx, T_COSE_OPT_MESSAGE_TYPE_ENCRYPT); + + /* Set up the recipient object with the key material. We happen to know + * what the algorithm and key are in advance so we don't have to + * decode the parameters first to figure that out (not that this part is + * working yet). */ + t_cose_recipient_dec_hpke_init(&dec_recipient); + t_cose_recipient_dec_hpke_set_skr(&dec_recipient, + skR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + t_cose_encrypt_dec_add_recipient(&dec_ctx, (struct t_cose_recipient_dec *)&dec_recipient); + + result = t_cose_encrypt_dec_detached(&dec_ctx, + cose_encrypted_message, /* in: the COSE_Encrypt message */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + encrypted_detached_payload, /* in: detached encrypted ciphertext */ + decrypted_plaintext_buffer, + &decrypted_plain_text, + NULL); + + if (result != T_COSE_SUCCESS) { + printf("error decrypting (%d)\n", result); + goto Done; + } + + if(q_useful_buf_compare(decrypted_plain_text, Q_USEFUL_BUF_FROM_SZ_LITERAL(PAYLOAD))) { + printf("Decrypted content didn't match payload\n"); + result = 1; + goto Done; + } + + print_useful_buf("Decrypted plaintext: ", decrypted_plain_text); + +Done: + printf("---- %s EXAMPLE HPKE (%d) ----\n\n", + result ? "FAILED" : "COMPLETED", result); + return (int32_t)result; +} + +#endif /* !T_COSE_DISABLE_HPKE */ + #ifndef T_COSE_DISABLE_ESDH @@ -455,3 +694,4 @@ esdh_example_detached(void) return (int32_t)result; } #endif /* !T_COSE_DISABLE_ESDH */ + diff --git a/examples/encryption_examples.h b/examples/encryption_examples.h index 675daba3..68d0c4ab 100644 --- a/examples/encryption_examples.h +++ b/examples/encryption_examples.h @@ -24,4 +24,8 @@ int32_t esdh_example(void); int32_t esdh_example_detached(void); +int32_t hpke_example(void); + +int32_t hpke_example_detached(void); + #endif /* encryption_examples_h */ diff --git a/examples/examples_main.c b/examples/examples_main.c index d9edf796..b0451f74 100644 --- a/examples/examples_main.c +++ b/examples/examples_main.c @@ -17,6 +17,11 @@ #include "encryption_examples.h" +#ifndef T_COSE_DISABLE_HPKE +#include "encryption_examples.h" +#endif /* T_COSE_DISABLE_HPKE */ + + typedef int32_t (test_fun_t)(void); #define TEST_ENTRY(test_name) {#test_name, test_name, true} @@ -42,6 +47,11 @@ static test_entry s_tests[] = { TEST_ENTRY(esdh_example), TEST_ENTRY(esdh_example_detached), + +#ifndef T_COSE_DISABLE_HPKE + TEST_ENTRY(hpke_example), + TEST_ENTRY(hpke_example_detached), +#endif /* T_COSE_DISABLE_HPKE */ }; diff --git a/examples/hpke_test.c b/examples/hpke_test.c new file mode 100644 index 00000000..7de44fbc --- /dev/null +++ b/examples/hpke_test.c @@ -0,0 +1,341 @@ +/* + * \file hpke_test.c + * + * Copyright (c) 2024, Hannes Tschofenig. All rights reserved. + * + * Created by Hannes Tschofenig on 15/1/24 + * + * SPDX-License-Identifier: BSD-3-Clause + * + * See BSD-3-Clause license in README.md + */ + +#include "t_cose/t_cose_common.h" +#include "t_cose/t_cose_encrypt_enc.h" +#include "t_cose/t_cose_encrypt_dec.h" +#include "t_cose/t_cose_key.h" + +#include +#include "print_buf.h" +#include "init_keys.h" + +#define PAYLOAD "This is the payload" +#define TEST_SENDER_IDENTITY "sender" +#define TEST_RECIPIENT_IDENTITY "recipient" + + +#ifndef T_COSE_DISABLE_HPKE + +#include "t_cose/t_cose_recipient_enc_hpke.h" +#include "t_cose/t_cose_recipient_dec_hpke.h" + +int32_t hpke_encrypt(struct t_cose_key pkR, + struct q_useful_buf_c payload, + struct q_useful_buf_c *cose_encrypted_message, + struct q_useful_buf *cose_encrypt_message_buffer, + int32_t alg) +{ + struct t_cose_encrypt_enc enc_ctx; + enum t_cose_err_t result; + struct t_cose_recipient_enc_hpke recipient; + + /* Initialize the encryption context telling it we want + * a COSE_Encrypt (not a COSE_Encrypt0) because we're doing HPKE with a + * COSE_Recpipient. Also tell it the AEAD algorithm for the + * body of the message. + */ + t_cose_encrypt_enc_init(&enc_ctx, + T_COSE_OPT_MESSAGE_TYPE_ENCRYPT, + T_COSE_ALGORITHM_A128GCM); + + /* Create the recipient object telling it the algorithm and the public key + * for the COSE_Recipient it's going to make. Then give that object + * to the main encryption context. (Only one recipient is set here, but + * there could be more) + */ + t_cose_recipient_enc_hpke_init(&recipient, + alg); + + t_cose_recipient_enc_hpke_set_key(&recipient, + pkR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + + t_cose_encrypt_add_recipient(&enc_ctx, + (struct t_cose_recipient_enc *)&recipient); + + /* Now do the actual encryption */ + result = t_cose_encrypt_enc(&enc_ctx, /* in: encryption context */ + payload, /* in: payload to encrypt */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + *cose_encrypt_message_buffer, /* in: buffer for COSE_Encrypt */ + cose_encrypted_message); /* out: COSE_Encrypt */ + + return (int32_t)result; +} + + +int32_t hpke_decrypt(struct q_useful_buf_c cose_encrypted_message, + struct q_useful_buf_c *plaintext_message, + struct q_useful_buf *plaintext_buffer, + struct t_cose_key skR) +{ + struct t_cose_recipient_dec_hpke dec_recipient; + struct t_cose_encrypt_dec_ctx dec_ctx; + enum t_cose_err_t result; + + /* Set up the decryption context, telling it what type of + * message to expect if there's no tag (that part isn't quite implemented right yet anyway). + */ + t_cose_encrypt_dec_init(&dec_ctx, T_COSE_OPT_MESSAGE_TYPE_ENCRYPT); + + + /* Set up the recipient object with the key material. We happen to know + * what the algorithm and key are in advance so we don't have to + * decode the parameters first to figure that out (not that this part is + * working yet). */ + t_cose_recipient_dec_hpke_init(&dec_recipient); + t_cose_recipient_dec_hpke_set_skr(&dec_recipient, + skR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + + t_cose_encrypt_dec_add_recipient(&dec_ctx, (struct t_cose_recipient_dec *)&dec_recipient); + + result = t_cose_encrypt_dec(&dec_ctx, + cose_encrypted_message, /* in: the COSE_Encrypt message */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + *plaintext_buffer, + plaintext_message, + NULL); + + return (int32_t)result; +} + +int save_file(const char *path, unsigned char *buf, size_t n) +{ + FILE *f; + long size; + + if ((f = fopen(path, "w")) == NULL) { + return 1; + } + + size = fwrite(buf, 1, n, f); + + if (size != n) { + fclose(f); + return 1; + } + + return 0; +} + + + +int load_file(const char *path, unsigned char **buf, size_t *n) +{ + FILE *f; + long size; + + if ((f = fopen(path, "rb")) == NULL) { + return 1; + } + + fseek(f, 0, SEEK_END); + if ((size = ftell(f)) == -1) { + fclose(f); + return 1; + } + fseek(f, 0, SEEK_SET); + + *n = (size_t) size; + + if (*n + 1 == 0 || + (*buf = calloc(1, *n + 1)) == NULL) { + fclose(f); + return 1; + } + + if (fread(*buf, 1, *n, f) != *n) { + fclose(f); + + memset(*buf, 0, *n); + free(*buf); + + return 1; + } + + fclose(f); + + (*buf)[*n] = '\0'; + + return 0; +} + +#define USAGE \ + "\n usage: hpke_test param=<>...\n" \ + "\n acceptable parameters:\n" \ + " encrypt=%%d default: 0 (encryption)\n" \ + " input_file=%%s default: input.cbor\n" \ + " output_file=%%s default: output.cbor\n" \ + " alg=%%d default: 35 for T_COSE_HPKE_Base_P256_SHA256_AES128GCM \n" \ + "\n" + +/* + * global options + */ +struct options { + const char *input_file; /* input filename */ + const char *output_file; /* output filename */ + int encrypt; /* encryption (0) or decryption (1) */ + int alg; /* algorithm */ +} opt; + +#define DFL_INPUT_FILE "input.cbor" +#define DFL_OUTPUT_FILE "output.cbor" +#define DFL_ENCRYPTION 0 +#define DFL_ALGORITHM 35 + +int main(int argc, char *argv[]) +{ + int ret = 0; + int i; + char *p, *q; + struct t_cose_key skR; + struct t_cose_key pkR; + Q_USEFUL_BUF_MAKE_STACK_UB ( cose_encrypt_message_buffer, 200); + struct q_useful_buf_c cose_encrypted_message; + size_t n; + unsigned char *payload; + struct q_useful_buf_c plaintext_message; + Q_USEFUL_BUF_MAKE_STACK_UB ( plaintext_buffer, 100); + + + /* Default values */ + opt.encrypt = DFL_ENCRYPTION; + opt.input_file = DFL_INPUT_FILE; + opt.output_file = DFL_OUTPUT_FILE; + opt.alg = DFL_ALGORITHM; + + p = q = NULL; + if (argc < 2) { +usage: + if (p != NULL && q != NULL) { + printf("unrecognized value for '%s': '%s'\n", p, q); + } else if (p != NULL && q == NULL) { + printf("unrecognized param: '%s'\n", p); + } + + printf(USAGE); + + if (ret == 0) { + ret = 1; + } + goto exit; + } + + for (i = 1; i < argc; i++) { + p = argv[i]; + + if (strcmp(p, "help") == 0) { + printf(USAGE); + + ret = 0; + goto exit; + } + + + if ((q = strchr(p, '=')) == NULL) { + printf("param requires a value: '%s'\n", p); + p = NULL; // avoid "unrecnognized param" message + goto usage; + } + *q++ = '\0'; + + + if (strcmp(p, "encrypt") == 0) { + opt.encrypt = atoi(q); + if (opt.encrypt < 0 || opt.encrypt > 1) { + goto usage; + } + } else if (strcmp(p, "alg") == 0) { + opt.alg = atoi(q); + if (opt.encrypt < 0 || opt.encrypt > 1) { + goto usage; + } + } else if (strcmp(p, "input_file") == 0) { + opt.input_file = q; + } else if (strcmp(p, "output_file") == 0) { + opt.output_file = q; + } else { + /* This signals that the problem is with p not q */ + q = NULL; + goto usage; + } + } + /* This signals that any further errors are not with a single option */ + p = q = NULL; + + ret = init_fixed_test_ec_encryption_key(T_COSE_ELLIPTIC_CURVE_P_256, + &pkR, /* out: public key to be used for encryption */ + &skR); /* out: corresponding private key for decryption */ + if (ret != T_COSE_SUCCESS) { + goto exit; + } + + if (opt.encrypt == 0) + { + + if ((ret = load_file(opt.input_file, &payload, &n)) != 0) { + printf("Failure to load file!\n"); + goto usage; + } + + ret = hpke_encrypt(pkR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(payload), + &cose_encrypted_message, + &cose_encrypt_message_buffer, + opt.alg); + + if (ret != T_COSE_SUCCESS) { + goto exit; + } + + print_useful_buf("COSE_Encrypt: ", cose_encrypted_message); + + if ((ret = save_file(opt.output_file, (unsigned char *) cose_encrypted_message.ptr, cose_encrypted_message.len)) != 0) { + printf("Failure to save file!\n"); + goto usage; + } + + + } else { + + if ((ret = load_file(opt.input_file, &payload, &n)) != 0) { + printf("Failure to load file!\n"); + goto usage; + } + + ret = hpke_decrypt( (struct q_useful_buf_c) + {.ptr = payload, .len = n}, + &plaintext_message, + &plaintext_buffer, + skR); + + print_useful_buf("Plaintext: ", plaintext_message); + + } + +exit: + if (ret < 0) { + ret = 1; + } + + return (ret); +} + +#else +void main() +{ + printf("HPKE not available!\n"); +} +#endif /* !T_COSE_DISABLE_HPKE */ diff --git a/inc/t_cose/t_cose_common.h b/inc/t_cose/t_cose_common.h index 9187f109..2392ba10 100644 --- a/inc/t_cose/t_cose_common.h +++ b/inc/t_cose/t_cose_common.h @@ -249,6 +249,15 @@ enum t_cose_key_usage_flags { T_COSE_KEY_USAGE_FLAG_ENCRYPT = 2 }; +// TODO: this probably doesn't belong in common.h because it is HPKE-specific +/*! + * \brief HPKE ciphersuite + */ +struct t_cose_crypto_hpke_suite_t { + uint16_t kem_id; // Key Encryption Method id + uint16_t kdf_id; // Key Derivation Function id + uint16_t aead_id; // Authenticated Encryption with Associated Data id +}; /* Six: an alg id, a kid, an iv, a content type, one custom, crit list * or: 2 alg IDs, an IV, a kid, a supp_pub_info, one custom. If diff --git a/inc/t_cose/t_cose_parameters.h b/inc/t_cose/t_cose_parameters.h index 836786a9..8bee3671 100644 --- a/inc/t_cose/t_cose_parameters.h +++ b/inc/t_cose/t_cose_parameters.h @@ -650,6 +650,16 @@ static struct t_cose_parameter t_cose_param_make_partial_iv(struct q_useful_buf_c iv); +/** + * Make a struct t_cose_parameter for an encapsulated key. + * + * \param[in] enc Encapsulated key + * + * \return An initialized struct t_cose_parameter. + * + */ +static inline struct t_cose_parameter +t_cose_param_make_encapsulated_key(struct q_useful_buf_c enc); /** @@ -766,6 +776,22 @@ struct q_useful_buf_c t_cose_param_find_kid(const struct t_cose_parameter *parameter_list); +/** + * \brief Find the encapsulated key (enc) parameter in a linked list. + * + * \param[in] parameter_list The parameter list to search. + * + * \return The content type or \ref NULL_Q_USEFUL_BUF_C. + * + * This returns \ref NULL_Q_USEFUL_BUF_C on all errors including + * errors such as the parameter not being present, or the parameter not + * being a byte string. It doesn't matter if the parameter is + * protected or not. + */ +struct q_useful_buf_c +t_cose_param_find_enc(const struct t_cose_parameter *parameter_list); + + /** * \brief Find the initialization vector parameter in a linked list. * @@ -1026,6 +1052,24 @@ t_cose_param_make_iv(struct q_useful_buf_c iv) return parameter; } +static inline struct t_cose_parameter +t_cose_param_make_encapsulated_key(struct q_useful_buf_c enc) +{ + struct t_cose_parameter parameter; + + parameter.critical = false; + parameter.in_protected = false; + parameter.location.index = 0; + parameter.location.nesting = 0; + parameter.label = T_COSE_HEADER_ALG_PARAM_HPKE_ENCAPSULATED_KEY; + parameter.value_type = T_COSE_PARAMETER_TYPE_BYTE_STRING; + parameter.value.string = enc; + parameter.next = NULL; + + return parameter; +} + + static inline struct t_cose_parameter t_cose_param_make_partial_iv(struct q_useful_buf_c iv) { diff --git a/inc/t_cose/t_cose_recipient_dec_hpke.h b/inc/t_cose/t_cose_recipient_dec_hpke.h new file mode 100644 index 00000000..45fd03f7 --- /dev/null +++ b/inc/t_cose/t_cose_recipient_dec_hpke.h @@ -0,0 +1,106 @@ +/* + * \file t_cose_recipient_dec_hpke.h + * + * Copyright (c) 2022, Arm Limited. All rights reserved. + * Copyright (c) 2023, Laurence Lundblade. All rights reserved. + * Copyright (c) 2024, Hannes Tschofenig. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * See BSD-3-Clause license in README.md + */ + +#ifndef __T_COSE_RECIPIENT_DEC_HPKE_H__ +#define __T_COSE_RECIPIENT_DEC_HPKE_H__ + +#include +#include +#include "t_cose/t_cose_parameters.h" +#include "t_cose/t_cose_recipient_dec.h" +#include "t_cose/t_cose_key.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + + +/* This is the decoder for COSE_Recipients of type HPKE. +* To use this make an instance of it, initialize it and set +* the sKR, and add it as a t_cose_recipient_dec to t_cose_encrypt_dec. +* When t_cose_encrypt_dec() is called to process the cose message +* a callback will be made to this code to process COSE_Recpients +* it encounters that might be of type HPKE. +*/ +struct t_cose_recipient_dec_hpke { + /* Private data structure */ + + /* t_cose_recipient_dec must be the first item for the polymorphism to + * work. This structure, t_cose_recipient_enc_keywrap, will sometimes be + * uses as a t_cose_recipient_enc. + */ + struct t_cose_recipient_dec base; + + struct t_cose_key skr; + struct q_useful_buf_c kid; +}; + + +static void +t_cose_recipient_dec_hpke_init(struct t_cose_recipient_dec_hpke *context); + + + +/* Set the secret key of the receiver, the skR in RFC 9180, the one that will be used to + * by the DH key agreement in HPKE to decrypt the CEK. The kid + * is optional. */ + +// TODO: describe and implement the rules for kid matching + +static void +t_cose_recipient_dec_hpke_set_skr(struct t_cose_recipient_dec_hpke *context, + struct t_cose_key skr, + struct q_useful_buf_c kid); + + +/* ========================================================================= + BEGINNING OF PRIVATE INLINE IMPLEMENTATION + ========================================================================= */ + + +enum t_cose_err_t +t_cose_recipient_dec_hpke_cb_private(struct t_cose_recipient_dec *me_x, + const struct t_cose_header_location loc, + const struct t_cose_alg_and_bits ce_alg, + QCBORDecodeContext *cbor_decoder, + struct q_useful_buf cek_buffer, + struct t_cose_parameter_storage *p_storage, + struct t_cose_parameter **params, + struct q_useful_buf_c *cek); + + +static inline void +t_cose_recipient_dec_hpke_init(struct t_cose_recipient_dec_hpke *me) +{ + memset(me, 0, sizeof(*me)); + + me->base.decode_cb = t_cose_recipient_dec_hpke_cb_private; +} + + +static inline void +t_cose_recipient_dec_hpke_set_skr(struct t_cose_recipient_dec_hpke *me, + struct t_cose_key skr, + struct q_useful_buf_c kid) +{ + me->skr = skr; + me->kid = kid; +} + + +#ifdef __cplusplus +} +#endif + +#endif /* __T_COSE_RECIPIENT_DEC_HPKE_H__ */ diff --git a/inc/t_cose/t_cose_recipient_enc_hpke.h b/inc/t_cose/t_cose_recipient_enc_hpke.h new file mode 100644 index 00000000..e0da7844 --- /dev/null +++ b/inc/t_cose/t_cose_recipient_enc_hpke.h @@ -0,0 +1,135 @@ +/* + * t_cose_recipient_enc_hpke.h + * + * Copyright (c) 2022, Arm Limited. All rights reserved. + * Copyright (c) 2023, Laurence Lundblade. All rights reserved. + * Copyright (c) 2024, Hannes Tschofenig. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * See BSD-3-Clause license in README.md + */ + +#ifndef __T_COSE_RECIPIENT_ENC_HPKE_H__ +#define __T_COSE_RECIPIENT_ENC_HPKE_H__ + +#include +#include +#include "t_cose/t_cose_parameters.h" +#include "t_cose/t_cose_common.h" +#include "t_cose/t_cose_key.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +struct t_cose_recipient_enc_hpke { + /* Private data structure */ + + /* t_cose_recipient_enc must be the first item for the polymorphism to + * work. This structure, t_cose_recipient_enc_hpke, will sometimes be + * used as a t_cose_recipient_enc. + */ + struct t_cose_recipient_enc e; + + struct t_cose_key recipient_pub_key; + struct q_useful_buf_c kid; + int32_t cose_ec_curve_id; + int32_t cose_algorithm_id; + struct t_cose_parameter *added_params; + struct t_cose_crypto_hpke_suite_t hpke_suite; +}; + + + +/** + * @brief Initialize the creator COSE_Recipient for HPKE content key distribution. + + * @param[in] kem_id The key encapsulation mechanism ID. + * @param[in] kdf_id The key derivation funtion ID. + * @param[in] aead_id The AEAD ID. + * + * This must be called not only to set the kem, kdf, and the AEAD IDs, but also because + * this sets up the recipient callbacks. That is when all the real work of content key + * distribution gets done. + * + * If unknown algortihm IDs are passed, an error will occur when t_cose_encrypt_enc() is + * called and the error code will be returned there. + */ +static void +t_cose_recipient_enc_hpke_init(struct t_cose_recipient_enc_hpke *context, + int32_t cose_algorithm_id); + + +/** + * @brief Sets the recipient key, pkR + * + * The kid is optional and can be NULL + */ +static void +t_cose_recipient_enc_hpke_set_key(struct t_cose_recipient_enc_hpke *context, + struct t_cose_key recipient, + struct q_useful_buf_c kid); + + + +/* ========================================================================= + BEGINNING OF PRIVATE INLINE IMPLEMENTATION + ========================================================================= */ + +enum t_cose_err_t +t_cose_recipient_create_hpke_cb_private(struct t_cose_recipient_enc *me_x, + struct q_useful_buf_c cek, + struct t_cose_alg_and_bits ce, + QCBOREncodeContext *cbor_encoder); + + +static inline void +t_cose_recipient_enc_hpke_init(struct t_cose_recipient_enc_hpke *me, + int32_t cose_algorithm_id) +{ + uint16_t kem_id; + uint16_t kdf_id; + uint16_t aead_id; + int32_t cose_ec_curve_id; + + memset(me, 0, sizeof(*me)); + me->e.creat_cb = t_cose_recipient_create_hpke_cb_private; + + switch (cose_algorithm_id) { + case T_COSE_HPKE_Base_P256_SHA256_AES128GCM: + kem_id = T_COSE_HPKE_KEM_ID_P256; /* kem id */ + cose_ec_curve_id = T_COSE_ELLIPTIC_CURVE_P_256; /* curve */ + kdf_id = T_COSE_HPKE_KDF_ID_HKDF_SHA256; /* kdf id */ + aead_id = T_COSE_HPKE_AEAD_ID_AES_GCM_128; /* aead id */ + break; + default: + kem_id = 0; + kdf_id = 0; + aead_id = 0; + cose_ec_curve_id = 0; + } + + me->cose_algorithm_id = cose_algorithm_id; + me->cose_ec_curve_id = cose_ec_curve_id; + me->hpke_suite.kem_id = kem_id; + me->hpke_suite.kdf_id = kdf_id; + me->hpke_suite.aead_id = aead_id; +} + + +static inline void +t_cose_recipient_enc_hpke_set_key(struct t_cose_recipient_enc_hpke *me, + struct t_cose_key pkR, + struct q_useful_buf_c kid) +{ + me->recipient_pub_key = pkR; + me->kid = kid; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __T_COSE_RECIPIENT_ENC_HPKE_H__ */ diff --git a/inc/t_cose/t_cose_standard_constants.h b/inc/t_cose/t_cose_standard_constants.h index 789cf9a6..35cf0c21 100644 --- a/inc/t_cose/t_cose_standard_constants.h +++ b/inc/t_cose/t_cose_standard_constants.h @@ -136,14 +136,14 @@ */ /** - * \def T_COSE_HEADER_ALG_PARAM_HPKE_SENDER_INFO + * \def T_COSE_HEADER_ALG_PARAM_HPKE_ENCAPSULATED_KEY * * \brief CBOR label of header algorithm parameter containing - * the HPKE_sender_info structure. + * the HPKE encapsulated key. * * This implementation only supports a subset of the available algorithms. */ -#define T_COSE_HEADER_ALG_PARAM_HPKE_SENDER_INFO -4 +#define T_COSE_HEADER_ALG_PARAM_HPKE_ENCAPSULATED_KEY -4 /** @@ -719,6 +719,17 @@ #define T_COSE_HPKE_AEAD_ID_CHACHA_POLY1305 0x0003 ///< Chacha20-Poly1305 +/* ------- Constants from draft-ietf-cose-hpke --------- + */ + +/* Cipher suite for COSE-HPKE in Base Mode that uses the + * DHKEM(P-256, HKDF-SHA256) KEM, the HKDF-SHA256 KDF and the AES- + * 128-GCM AEAD. + * + * KEM = 0x10 | KDF = 0x1 | AEAD = 0x1 + */ +#define T_COSE_HPKE_Base_P256_SHA256_AES128GCM 35 + /* ------- Constants from RFC 8152 --------- */ diff --git a/src/hpke.c b/src/hpke.c new file mode 100644 index 00000000..73f5dda1 --- /dev/null +++ b/src/hpke.c @@ -0,0 +1,1458 @@ +/** + * \file + * MbedTLS-based HPKE implementation following draft-irtf-cfrg-hpke + */ + +/* + * Copyright The Mbed TLS Contributors + * 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. + * + * This code is based on https://github.com/sftcd/happykey + * Special thanks goes to Stephen Farrell for his support and the permission + * to re-use the code in Mbed TLS. Code has been subsquently been modified + * by Hannes Tschofenig. + * + */ + +#ifndef T_COSE_DISABLE_HPKE + +#include +#include +#include "mbedtls/error.h" + +#include "hpke.h" +#include "mbedtls/hkdf.h" + +#include "t_cose_crypto.h" /* The crypto adaptor layer */ + +#include "mbedtls/platform_util.h" +#include "mbedtls/error.h" + +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#include +#define mbedtls_calloc calloc +#define mbedtls_free free +#endif + + +#ifdef _MSC_VER +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +/*! + * \brief info about an AEAD + */ +typedef struct { + uint16_t aead_id; ///< code point for aead alg + size_t taglen; ///< aead tag len + size_t Nk; ///< size of a key for this aead + size_t Nn; ///< length of a nonce for this aead +} hpke_aead_info_t; + +/*! + * \brief table of AEADs + */ +static hpke_aead_info_t hpke_aead_tab[]={ + { 0, 0, 0, 0 }, // this is needed to keep indexing correct + { HPKE_AEAD_ID_AES_GCM_128, 16, 16, 12 }, + { HPKE_AEAD_ID_AES_GCM_256, 16, 32, 12 }, + { HPKE_AEAD_ID_CHACHA_POLY1305, 16, 32, 12 } +}; + +/*! + * \brief info about a KEM + */ +typedef struct { + uint16_t kem_id; ///< code point for key encipherment method + const char *keytype; ///< string form of algorithm type "EC"/"X25519"/"X448" + const char *groupname; ///< string form of EC group needed for NIST curves "P-256"/"p-384"/"p-521" + int groupid; ///< NID of KEM + size_t Nsecret; ///< size of secrets + size_t Nenc; ///< length of encapsulated key + size_t Npk; ///< length of public key + size_t Npriv; ///< length of raw private key +} hpke_kem_info_t; + +/*! + * \brief table of KEMs + * + * Ok we're wasting space here, but not much and it's ok + */ + +#define NID_X9_62_prime256v1 1 +#define NID_secp384r1 2 +#define NID_secp521r1 3 +#define EVP_PKEY_X25519 4 +#define EVP_PKEY_X448 5 + +hpke_kem_info_t hpke_kem_tab[]={ + { 0, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + { 1, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + { 2, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + { 3, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + { 4, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + { 5, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + { 6, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + { 7, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + { 8, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + { 9, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {10, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {11, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {12, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {13, NULL, NULL, 0, 0, 0, 0, 0}, // this is needed to keep indexing correct + {14, NULL, NULL, 0, 0, 0, 0, 0}, // this is needed to keep indexing correct + {15, NULL, NULL, 0, 0, 0, 0, 0}, // this is needed to keep indexing correct + { HPKE_KEM_ID_P256, "EC", "P-256", NID_X9_62_prime256v1, 32, 65, 65, 32 }, // maybe "prime256v1" instead of P-256? + { HPKE_KEM_ID_P384, "EC", "P-384", NID_secp384r1, 48, 97, 97, 48 }, + { HPKE_KEM_ID_P521, "EC", "P-521", NID_secp521r1, 64, 133, 133, 66 }, + {19, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {20, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {21, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {22, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {23, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {24, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {25, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {26, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {27, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {28, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {29, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {30, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {31, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct + {HPKE_KEM_ID_25519, "X25519", NULL, EVP_PKEY_X25519, 32, 32, 32, 32 }, + {HPKE_KEM_ID_448, "X448", NULL, EVP_PKEY_X448, 64, 56, 56, 56 }, + {34, NULL, NULL, 0, 0, 0, 0, 0 }, // this is needed to keep indexing correct +}; + + +/*! + * \brief info about a KDF + */ +typedef struct { + uint16_t kdf_id; // code point for KDF + size_t Nh; // length of hash/extract output +} hpke_kdf_info_t; + +/*! + * \brief table of KDFs + */ +static hpke_kdf_info_t hpke_kdf_tab[]={ + { 0, 0 }, // this is needed to keep indexing correct + { HPKE_KDF_ID_HKDF_SHA256, 32 }, + { HPKE_KDF_ID_HKDF_SHA384, 48 }, + { HPKE_KDF_ID_HKDF_SHA512, 64 } +}; + +/* + * \brief string matching for suites + */ +#define HPKE_MSMATCH(inp,known) (strlen(inp)==strlen(known) && !strcasecmp(inp,known)) + + +#define MBEDTLS_SSL_HPKE_LABEL( name, string ) \ + .name = string, + +struct mbedtls_ssl_hpke_labels_struct const mbedtls_ssl_hpke_labels = +{ + /* This seems to work in C, despite the string literal being one + * character too long due to the 0-termination. */ + MBEDTLS_SSL_HPKE_LABEL_LIST +}; + +#define MBEDTLS_SSL_HPKE_5869_MODE_KEM_MAX_LABEL_LEN \ + sizeof( mbedtls_ssl_hpke_labels.kem ) + \ + MBEDTLS_MD_MAX_SIZE + \ + sizeof( mbedtls_ssl_hpke_labels.version ) + \ + 2 // size of kem_id + +#define MBEDTLS_SSL_HPKE_5869_MODE_FULL_MAX_LABEL_LEN \ + MBEDTLS_SSL_HPKE_MAX_LABEL_LEN + \ + MBEDTLS_MD_MAX_SIZE + \ + sizeof( mbedtls_ssl_hpke_labels.version ) + \ + 6 // size of kem_id,kdf_id,aead_id + +#define MBEDTLS_SSL_HPKE_LABEL_LEN( label_len, info_len ) \ + ( MBEDTLS_SSL_HPKE_5869_MODE_FULL_MAX_LABEL_LEN + \ + + label_len \ + + info_len ) + +#define SSL_TLS1_3_KEY_SCHEDULE_MAX_HKDF_LABEL_LEN \ + SSL_TLS1_3_KEY_SCHEDULE_HKDF_LABEL_LEN( \ + sizeof(tls1_3_label_prefix) + \ + MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_LABEL_LEN, \ + MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_CONTEXT_LEN ) + +#undef MBEDTLS_SSL_HPKE_LABEL + + + +static int32_t +hpke_aead_id_2_cose(const hpke_suite_t suite) +{ + // TODO: this can probably be a look up table (smaller code size and inlined) + switch(suite.aead_id) { + case HPKE_AEAD_ID_AES_GCM_256: + return T_COSE_ALGORITHM_A256GCM; + + case HPKE_AEAD_ID_AES_GCM_128: + return T_COSE_ALGORITHM_A128GCM; +/* + case PSA_ALG_CHACHA20_POLY1305: + return T_COSE_ALG_CHA_CHA; */ + + default: + return T_COSE_ALGORITHM_NONE; + } +} + + + +int mbedtls_hpke_extract( + const hpke_suite_t suite, + const int mode5869, + const unsigned char *salt, const size_t saltlen, + const char *label, const size_t labellen, + const unsigned char *ikm, const size_t ikmlen, + unsigned char *secret, size_t *secretlen ) +{ + int ret; + const mbedtls_md_info_t *md; + mbedtls_md_type_t md_type; + unsigned char labeled_ikmbuf[ MBEDTLS_SSL_HPKE_5869_MODE_FULL_MAX_LABEL_LEN ]; + unsigned char *labeled_ikm = labeled_ikmbuf; + size_t labeled_ikmlen = 0; + size_t concat_offset = 0; + + if( ikmlen > MBEDTLS_MD_MAX_SIZE ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + concat_offset = 0; + + // Add version + memcpy( labeled_ikm, MBEDTLS_SSL_HPKE_LBL_WITH_LEN( version ) ); + concat_offset += sizeof( mbedtls_ssl_hpke_labels.version ); + + switch( mode5869 ) + { + case HPKE_5869_MODE_KEM: + + // Add kem label + memcpy( labeled_ikm + concat_offset, MBEDTLS_SSL_HPKE_LBL_WITH_LEN( kem ) ); + concat_offset += sizeof( mbedtls_ssl_hpke_labels.kem ); + + // Add kem_id + labeled_ikm[concat_offset] = (unsigned char)( suite.kem_id >> 8 ); + concat_offset += 1; + labeled_ikm[concat_offset] = (unsigned char)( suite.kem_id ); + concat_offset += 1; + break; + case HPKE_5869_MODE_FULL: + + // Add hpke label + memcpy( labeled_ikm + concat_offset, MBEDTLS_SSL_HPKE_LBL_WITH_LEN( hpke ) ); + concat_offset += sizeof( mbedtls_ssl_hpke_labels.hpke ); + + // Add kem_id + labeled_ikm[concat_offset] = (unsigned char)( suite.kem_id >> 8 ); + concat_offset += 1; + labeled_ikm[concat_offset] = (unsigned char)( suite.kem_id ); + concat_offset += 1; + + // Add kdf_id + labeled_ikm[concat_offset] = (unsigned char)( suite.kdf_id >> 8 ); + concat_offset += 1; + labeled_ikm[concat_offset] = (unsigned char)( suite.kdf_id ); + concat_offset += 1; + + // Add aead_id + labeled_ikm[concat_offset] = (unsigned char)( suite.aead_id >> 8 ); + concat_offset += 1; + labeled_ikm[concat_offset] = (unsigned char)( suite.aead_id ); + concat_offset += 1; + break; + default: + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Add label + memcpy( labeled_ikm + concat_offset, label, labellen ); + concat_offset += labellen; + + // Add ikm + if( ikmlen > 0 ) + { + memcpy( labeled_ikm + concat_offset, ikm, ikmlen ); + concat_offset += ikmlen; + } + + labeled_ikmlen = concat_offset; + + switch( suite.kdf_id ) + { + case HPKE_KDF_ID_HKDF_SHA256: + md_type = MBEDTLS_MD_SHA256; + break; + case HPKE_KDF_ID_HKDF_SHA384: + md_type = MBEDTLS_MD_SHA384; + break; + case HPKE_KDF_ID_HKDF_SHA512: + md_type = MBEDTLS_MD_SHA512; + break; + default: + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + md = mbedtls_md_info_from_type( md_type ); + + if( md == NULL ) + { + ret = MBEDTLS_ERR_HPKE_INTERNAL_ERROR; + goto exit; + } + + *secretlen = mbedtls_md_get_size( md ); + + /* HKDF-Extract takes a salt and input key material.*/ + ret = mbedtls_hkdf_extract( md, + salt, saltlen, + labeled_ikm, labeled_ikmlen, + secret ); + + if( ret != 0 ) + { + goto exit; + } + +exit: + mbedtls_platform_zeroize( labeled_ikmbuf, sizeof( labeled_ikmbuf ) ); + + return( ret ); +} + + + + +int mbedtls_hpke_expand( const hpke_suite_t suite, const int mode5869, + const unsigned char *prk, const size_t prklen, + const char *label, const size_t labellen, + const unsigned char *info, const size_t infolen, + const size_t L, + unsigned char *out, size_t *outlen) +{ + const mbedtls_md_info_t *md; + mbedtls_md_type_t md_type; + int ret; + unsigned char *p; // Pointer to temporary buffer + size_t concat_offset=0; + size_t loutlen; + + if( L > *outlen ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Allocate temporary buffer + p = mbedtls_calloc( 1, MBEDTLS_SSL_HPKE_LABEL_LEN( labellen, infolen ) ); + + if( p == NULL ) + { + return( MBEDTLS_ERR_HPKE_BUFFER_TOO_SMALL ); + } + + // Add expected output length + p[0] = (unsigned char)( L >> 8 ); + p[1] = (unsigned char)( L ); + concat_offset=2; + + // Add version label + memcpy( p + concat_offset, MBEDTLS_SSL_HPKE_LBL_WITH_LEN( version ) ); + concat_offset += sizeof( mbedtls_ssl_hpke_labels.version ); + + // Add suit_id label + if( mode5869 == HPKE_5869_MODE_KEM ) + { + memcpy( p + concat_offset, MBEDTLS_SSL_HPKE_LBL_WITH_LEN( kem ) ); + concat_offset += sizeof( mbedtls_ssl_hpke_labels.kem ); + } + else if( mode5869 == HPKE_5869_MODE_FULL ) + { + memcpy( p + concat_offset, MBEDTLS_SSL_HPKE_LBL_WITH_LEN( hpke ) ); + concat_offset += sizeof( mbedtls_ssl_hpke_labels.hpke ); + } + else + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Add kem_id + p[ concat_offset ] = (unsigned char)( suite.kem_id >> 8 ); + concat_offset += 1; + p[ concat_offset ] = (unsigned char)( suite.kem_id ); + concat_offset += 1; + + switch( mode5869 ) + { + case HPKE_5869_MODE_KEM: + memcpy( p + concat_offset, label, labellen ); + concat_offset += labellen; + + memcpy( p + concat_offset, info, infolen ); + concat_offset += infolen; + break; + + case HPKE_5869_MODE_FULL: + // Add kdf_id + p[ concat_offset] = (unsigned char)( suite.kdf_id >> 8 ); + concat_offset += 1; + p[ concat_offset] = (unsigned char)( suite.kdf_id ); + concat_offset += 1; + + // Add aead_id + p[ concat_offset ] = (unsigned char)( suite.aead_id >> 8 ); + concat_offset += 1; + p[ concat_offset ] = (unsigned char)( suite.aead_id ); + concat_offset += 1; + + // Add label + memcpy( p + concat_offset, label, labellen ); + concat_offset += labellen; + + // Add info + memcpy( p + concat_offset, info, infolen ); + concat_offset += infolen; + break; + + default: + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + switch( suite.kdf_id ) + { + case HPKE_KDF_ID_HKDF_SHA256: + md_type = MBEDTLS_MD_SHA256; + break; + case HPKE_KDF_ID_HKDF_SHA384: + md_type = MBEDTLS_MD_SHA384; + break; + case HPKE_KDF_ID_HKDF_SHA512: + md_type = MBEDTLS_MD_SHA512; + break; + default: + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + md = mbedtls_md_info_from_type( md_type ); + + if( md == NULL ) + { + ret = MBEDTLS_ERR_HPKE_INTERNAL_ERROR; + goto exit; + } + + loutlen = L; + + ret = mbedtls_hkdf_expand( md, + prk, prklen, + p, concat_offset, + out, loutlen ); + + if( ret != 0 ) + { + goto exit; + } + + *outlen = loutlen; + +exit: + mbedtls_free( p ); + return( ret ); +} + +int mbedtls_hpke_extract_and_expand( hpke_suite_t suite, + int mode5869, + unsigned char *shared_secret , size_t shared_secretlen, + unsigned char *context, size_t contextlen, + unsigned char *secret, size_t *secretlen ) +{ + int res; + unsigned char eae_prkbuf[MBEDTLS_MD_MAX_SIZE]; + size_t eae_prklen = MBEDTLS_MD_MAX_SIZE; + size_t lsecretlen; + + switch( suite.kem_id) + { + case HPKE_KEM_ID_P256: + lsecretlen = 32; + break; + case HPKE_KEM_ID_P384: + lsecretlen = 48; + break; + case HPKE_KEM_ID_P521: + lsecretlen = 64; + break; + default: + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + res = mbedtls_hpke_extract( suite, mode5869, + (unsigned char *) "", 0, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( eae_prk ), + shared_secret, shared_secretlen, + eae_prkbuf, &eae_prklen ); + + if( res != 0 ) + { + goto exit; + } + + res = mbedtls_hpke_expand( suite, mode5869, + eae_prkbuf, eae_prklen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( shared_secret ), + context, contextlen, + lsecretlen, + secret, &lsecretlen ); + + if( res != 0 ) + { + goto exit; + } + + *secretlen = lsecretlen; + +exit: + mbedtls_platform_zeroize( eae_prkbuf, MBEDTLS_MD_MAX_SIZE ); + + return( res ); +} + + + +/*! + * \brief Internal function for AEAD decryption + * + * \param suite is the ciphersuite + * \param key is the secret + * \param keylen is the length of the secret + * \param iv is the initialisation vector + * \param ivlen is the length of the iv + * \param aad is the additional authenticated data + * \param aadlen is the length of the aad + * \param cipher is obvious + * \param cipherlen is the ciphertext length + * \param plain is an output + * \param plainlen is an input/output, better be big enough on input, exact on output + * \return 0 for good otherwise bad + */ +static int hpke_aead_dec( + hpke_suite_t suite, + unsigned char *key, size_t keylen, + unsigned char *iv, size_t ivlen, + unsigned char *aad, size_t aadlen, + const unsigned char *cipher, size_t cipherlen, + unsigned char *plain, size_t *plainlen) +{ + psa_key_attributes_t attr_ciphertext = PSA_KEY_ATTRIBUTES_INIT; + psa_status_t status; + psa_algorithm_t mode; + size_t key_length; + psa_key_handle_t key_handle = 0; + psa_key_type_t key_type; + + /* Initialize the PSA */ + status = psa_crypto_init( ); + + if( status != PSA_SUCCESS ) + { + return( status ); + } + + /* Decrypt ciphertext using the Content Encryption Key (CEK). */ + if ( ( suite.aead_id == HPKE_AEAD_ID_AES_GCM_256 ) || + ( suite.aead_id == HPKE_AEAD_ID_AES_GCM_128 ) ) + { + mode = PSA_ALG_GCM; + key_type = PSA_KEY_TYPE_AES; + } + else if (suite.aead_id == HPKE_AEAD_ID_CHACHA_POLY1305 ) + { + mode = PSA_ALG_CHACHA20_POLY1305; + key_type = PSA_KEY_TYPE_CHACHA20; + } + else return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + + // key length are in bits + key_length = hpke_aead_tab[suite.aead_id].Nk * 8; + + if( key_length != keylen * 8 ) + { + return( MBEDTLS_ERR_HPKE_INTERNAL_ERROR ); + } + + psa_set_key_usage_flags( &attr_ciphertext, PSA_KEY_USAGE_DECRYPT ); + psa_set_key_algorithm( &attr_ciphertext, mode ); + psa_set_key_type( &attr_ciphertext, key_type ); + psa_set_key_bits( &attr_ciphertext, key_length ); + + status = psa_import_key( &attr_ciphertext, key, key_length / 8, &key_handle ); + + if ( status != PSA_SUCCESS ) + { + return( status ); + } + + status = psa_aead_decrypt( key_handle, // key + mode, // algorithm + iv, // iv + ivlen, // iv length + aad, // additional data + aadlen, // additional data length + cipher, // ciphertext + cipherlen, // ciphertext length + plain, *plainlen, // plaintext + plainlen ); // length of output + + if ( status != PSA_SUCCESS ) + { + return( status ); + } + + psa_destroy_key( key_handle ); + return( 0 ); +} + + + +/*! + * \brief run the KEM with two keys as per draft-05 + * + * \param encrypting is 1 if we're encrypting, 0 for decrypting + * \param suite is the ciphersuite + * key1 is the first key, for which we have the private value + * \param own_public_key_len is the length of the encoded form of key1 + * \param own_public_key is the encoded form of key1 + * key2 is the peer's key + * \param peer_public_key_len is the length of the encoded form of key1 + * \param peer_public_key is the encoded form of key1 + * akey is the authentication private key + * \param apublen is the length of the encoded the authentication public key + * \param apub is the encoded form of the authentication public key + * \param ss is (a pointer to) the buffer for the shared secret result + * \param sslen is the size of the buffer (octets-used on exit) + * \return 0 for good + */ + +static int hpke_do_kem( int encrypting, + hpke_suite_t suite, + psa_key_handle_t own_key_handle, + size_t own_public_key_len, const uint8_t *own_public_key, + size_t peer_public_key_len, const uint8_t *peer_public_key, + psa_key_handle_t apriv_handle, + size_t apublen, uint8_t *apub, + uint8_t **ss, size_t *sslen) +{ + int ret; + psa_status_t status; + + // Buffer for DH-derived key + size_t zzlen = 2 * PSA_RAW_KEY_AGREEMENT_OUTPUT_MAX_SIZE; + unsigned char zz[ 2 * PSA_RAW_KEY_AGREEMENT_OUTPUT_MAX_SIZE ]; + size_t zzlen2; + + // Buffer for context + size_t kem_contextlen = PSA_EXPORT_PUBLIC_KEY_MAX_SIZE * 3; + unsigned char kem_context[ PSA_EXPORT_PUBLIC_KEY_MAX_SIZE * 3 ]; + + /* Produce ECDH key */ + status = psa_raw_key_agreement( PSA_ALG_ECDH, // algorithm + own_key_handle, // private key + peer_public_key, // peer public key + peer_public_key_len, // length of peer public key + zz, sizeof( zz ), // buffer to store derived key + &zzlen ); // output size + + if( status != PSA_SUCCESS ) + { + return( status ); + } + + kem_contextlen = own_public_key_len + peer_public_key_len; + + if( kem_contextlen >= ( PSA_EXPORT_PUBLIC_KEY_MAX_SIZE * 3 ) ) + { + ret = MBEDTLS_ERR_HPKE_BUFFER_TOO_SMALL; + goto error; + } + + // Copy own public key and peer public key to context + if( encrypting ) + { + memcpy( kem_context, own_public_key, own_public_key_len ); + memcpy( kem_context + own_public_key_len, peer_public_key, peer_public_key_len ); + } + else + { + memcpy( kem_context, peer_public_key, peer_public_key_len ); + memcpy( kem_context + peer_public_key_len, own_public_key, own_public_key_len ); + } + + // Append the public auth key (mypub) to context + if( apublen != 0 ) + { + if( ( kem_contextlen + apublen ) >= ( PSA_EXPORT_PUBLIC_KEY_MAX_SIZE * 3 ) ) + { + ret = MBEDTLS_ERR_HPKE_BUFFER_TOO_SMALL; + goto error; + } + + memcpy( kem_context + kem_contextlen, apub, apublen ); + kem_contextlen += apublen; + } + + // Authentication part + if( apub != NULL ) + { + // Run to get 2nd half of zz + if( encrypting ) + { + status = psa_raw_key_agreement( + PSA_ALG_ECDH, // algorithm + apriv_handle, // auth private key + peer_public_key, // peer public key + peer_public_key_len, // length of peer public key + zz+zzlen, sizeof( zz ) - zzlen, // buffer to store derived key + &zzlen2 ); // output size + } + else + { + status = psa_raw_key_agreement( + PSA_ALG_ECDH, // algorithm + own_key_handle, // private key + apub, // peer public key + apublen, // length of peer public key + zz+zzlen, sizeof( zz ) - zzlen, // buffer to store derived key + &zzlen2 ); // output size + } + + if( status != PSA_SUCCESS ) + { + return( status ); + } + + zzlen+=zzlen2; + } + + ret = mbedtls_hpke_extract_and_expand( suite, + HPKE_5869_MODE_KEM, + zz, zzlen, + kem_context, kem_contextlen, + *ss, sslen ); + + if( ret != 0 ) + { + goto error; + } + +error: + mbedtls_platform_zeroize( zz, zzlen ); + + return( ret ); +} + +int mbedtls_hpke_decrypt( unsigned int mode, hpke_suite_t suite, + char *pskid, size_t psklen, unsigned char *psk, + size_t pkS_len, unsigned char *pkS, + psa_key_handle_t skR_handle, + size_t pkE_len, const unsigned char *pkE, + size_t cipherlen, const unsigned char *cipher, + size_t aadlen, unsigned char *aad, + size_t infolen, unsigned char *info, + size_t *clearlen, unsigned char *clear ) +{ + // Buffer for shared secret + uint8_t buffer[MBEDTLS_MD_MAX_SIZE]; + size_t shared_secretlen = MBEDTLS_MD_MAX_SIZE; + uint8_t *shared_secret = buffer; + + // Buffer for context + size_t ks_contextlen = MBEDTLS_MD_MAX_SIZE * 2 + 1; + uint8_t ks_context[ MBEDTLS_MD_MAX_SIZE * 2 + 1 ]= { 0 }; + + // Buffer for secret + size_t secretlen = MBEDTLS_MD_MAX_SIZE; + uint8_t secret[ MBEDTLS_MD_MAX_SIZE]; + + // Buffer for nonce + size_t noncelen = MBEDTLS_MD_MAX_SIZE; + uint8_t nonce[MBEDTLS_MD_MAX_SIZE]; + + // Buffer for PSK hash + size_t psk_hashlen = MBEDTLS_MD_MAX_SIZE; + uint8_t psk_hash[MBEDTLS_MD_MAX_SIZE]; + + // Buffer for key + size_t keylen = MBEDTLS_MD_MAX_SIZE; + uint8_t key[MBEDTLS_MD_MAX_SIZE]; + + // Buffer for exporter + size_t exporterlen = MBEDTLS_MD_MAX_SIZE; + uint8_t exporter[MBEDTLS_MD_MAX_SIZE]; + + // Buffer for pkR + size_t pkR_len; + uint8_t pkR[PSA_EXPORT_PUBLIC_KEY_MAX_SIZE]; + + int ret; + psa_status_t status; + size_t halflen; + size_t pskidlen; + + // Input check: mode + switch( mode ) + { + case HPKE_MODE_BASE: + case HPKE_MODE_PSK: + case HPKE_MODE_AUTH: + case HPKE_MODE_PSKAUTH: + break; + default: + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Input check: psk + if( (mode == HPKE_MODE_PSK || mode == HPKE_MODE_PSKAUTH ) + && ( pskid == NULL || psklen == 0 || psk == NULL ) ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Input check: suite + ret = hpke_suite_check( suite ); + + if( ret != 0 ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Input check: Is buffer for plaintext available + if( clearlen == 0 || clear == NULL ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Input check: Is ciphertext provided + if( cipher == NULL || cipherlen == 0 ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Input check: For AUTH mode is pkS provided + if( mode == HPKE_MODE_AUTH && + ( pkS == NULL || pkS_len == 0 ) ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Input check: For PSK mode is PSK provided + if( ( mode == HPKE_MODE_PSK || mode == HPKE_MODE_PSKAUTH ) && + ( psk == NULL || psklen == 0 || pskid == NULL ) ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + /* Export own public key */ + status = psa_export_public_key( skR_handle, + pkR, sizeof( pkR ), + &pkR_len ); + + if( status != PSA_SUCCESS ) + { + return( EXIT_FAILURE ); + } + + /* Run DH KEM to get shared secret */ + ret = hpke_do_kem( 0, // 0 means decrypting + suite, // ciphersuite + skR_handle, // skR handle + pkR_len, pkR, // public key recipient (pkR) + pkE_len, pkE, // is the peer's key + 0, // authentication private key + pkS_len, pkS, // authentication public key + &shared_secret, &shared_secretlen ); // shared secret + + if( ret != 0 ) + { + return( MBEDTLS_ERR_HPKE_INTERNAL_ERROR ); + } + + /* Create context buffer */ + ks_context[0] = ( unsigned char ) ( mode % 256 ); + ks_contextlen--; + halflen = ks_contextlen; + pskidlen = 0; + pskidlen = ( psk == NULL ? 0 : strlen( pskid ) ); + + ret = mbedtls_hpke_extract( suite, // ciphersuite + HPKE_5869_MODE_FULL, // mode + (unsigned char *) "", 0, // salt + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( psk_id_hash ), // label + (unsigned char *) pskid, pskidlen, // psk id + ks_context + 1, &halflen ); + + if( ret != 0 ) + { + goto error; + } + + ks_contextlen -= halflen; + + ret = mbedtls_hpke_extract( suite, + HPKE_5869_MODE_FULL, + (unsigned char *) "", 0, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( info_hash ), + info, infolen, + ks_context + 1 + halflen, &ks_contextlen ); + + if( ret != 0 ) + { + goto error; + } + + ks_contextlen += 1 + halflen; + + /* Extract secret */ + ret = mbedtls_hpke_extract( suite, + HPKE_5869_MODE_FULL, + (unsigned char *) "", 0, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( psk_hash ), + psk, psklen, + psk_hash, &psk_hashlen ); + + if( ret != 0) + { + goto error; + } + + secretlen = hpke_kdf_tab[suite.kdf_id].Nh; + + ret = mbedtls_hpke_extract( suite, + HPKE_5869_MODE_FULL, + shared_secret, shared_secretlen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( secret ), + psk, psklen, + secret, &secretlen ); + + if( ret != 0 ) + { + goto error; + } + + noncelen = hpke_aead_tab[suite.aead_id].Nn; + + ret = mbedtls_hpke_expand( suite, + HPKE_5869_MODE_FULL, + secret, secretlen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( base_nonce ), + ks_context, ks_contextlen, + noncelen, nonce, &noncelen ); + + if( ret != 0 ) + { + goto error; + } + + keylen = hpke_aead_tab[suite.aead_id].Nk; + + ret = mbedtls_hpke_expand( suite, + HPKE_5869_MODE_FULL, + secret, secretlen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( key ), + ks_context, ks_contextlen, + keylen, key, &keylen ); + + if( ret != 0 ) + { + goto error; + } + + exporterlen = hpke_kdf_tab[suite.kdf_id].Nh; + + ret = mbedtls_hpke_expand( suite, + HPKE_5869_MODE_FULL, + secret, secretlen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( exp ), + ks_context, ks_contextlen, + exporterlen, exporter, &exporterlen ); + + if( ret != 0 ) + { + goto error; + } + + noncelen = hpke_aead_tab[suite.aead_id].Nn; + + ret = mbedtls_hpke_expand( suite, + HPKE_5869_MODE_FULL, + secret, secretlen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( base_nonce ), + ks_context, ks_contextlen, + noncelen, nonce, &noncelen ); + + if( ret !=0 ) + { + goto error; + } + + /* Decrypt ciphertext */ + ret = hpke_aead_dec( suite, + key, keylen, + nonce, noncelen, + aad, aadlen, + cipher, cipherlen, + clear, clearlen ); + + if( ret != 0 ) + { + goto error; + } + +error: + return( ret ); +} + +/** + * \brief check if a suite is supported locally + * + * \param suite is the suite to check + * \return 1 for good/supported, not-1 otherwise + */ + +int hpke_suite_check( hpke_suite_t suite ) +{ + int nkems; + int nkdfs; + int naeads; + + int kem_ok = 0; + int kdf_ok = 0; + int aead_ok = 0; + + int ind = 0; + + // Check KEM + nkems= sizeof( hpke_kem_tab ) / sizeof( hpke_kem_info_t ); + + for( ind = 0; ind != nkems; ind++ ) + { + if( suite.kem_id == hpke_kem_tab[ ind ].kem_id ) + { + kem_ok = 1; + break; + } + } + + // Check KDF + nkdfs = sizeof( hpke_kdf_tab ) / sizeof( hpke_kdf_info_t ); + + for( ind = 0; ind != nkdfs; ind++ ) + { + if( suite.kdf_id == hpke_kdf_tab[ ind ].kdf_id ) + { + kdf_ok = 1; + break; + } + } + + // Check AEAD + naeads = sizeof( hpke_aead_tab ) / sizeof( hpke_aead_info_t ); + + for( ind = 0; ind != naeads; ind++ ) + { + if( suite.aead_id == hpke_aead_tab[ ind ].aead_id ) + { + aead_ok = 1; + break; + } + } + + if( kem_ok == 1 && kdf_ok == 1 && aead_ok ==1 ) + { + return( 0 ); + } + + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); +} + +/*! + * \brief Internal HPKE single-shot encryption function + * \param mode is the HPKE mode + * \param suite is the ciphersuite to use + * \param pskid is the pskid string fpr a PSK mode (can be NULL) + * \param psklen is the psk length + * \param psk is the psk + * \param pkR_len is the length of the recipient public key + * \param pkR is the encoded recipient public key + * \param clearlen is the length of the cleartext + * \param clear is the encoded cleartext + * \param aadlen is the lenght of the additional data (can be zero) + * \param aad is the encoded additional data (can be NULL) + * \param infolen is the lenght of the info data (can be zero) + * \param info is the encoded info data (can be NULL) + * \param pkE_len is the length of the input buffer for the sender's public key (length used on output) + * \param pkE is the input buffer for ciphertext + * \param cipherlen is the length of the input buffer for ciphertext (length used on output) + * \param cipher is the input buffer for ciphertext + * \return 1 for good (OpenSSL style), not-1 for error + */ +static int hpke_enc_int( unsigned int mode, hpke_suite_t suite, + char *pskid, size_t psklen, unsigned char *psk, + size_t pkR_len, const unsigned char *pkR, + psa_key_handle_t skS_handle, + size_t clearlen, const unsigned char *clear, + size_t aadlen, unsigned char *aad, + size_t infolen, unsigned char *info, + psa_key_handle_t ext_pkE_handle, + size_t *pkE_len, unsigned char *pkE, + size_t *cipherlen, unsigned char *cipher ) + +{ + // FOR TEST PURPOSES ONLY + size_t pkS_len = 0; + uint8_t *pkS = NULL; + + // Buffer for context + size_t ks_contextlen = MBEDTLS_MD_MAX_SIZE * 2 + 1; + uint8_t ks_context[ MBEDTLS_MD_MAX_SIZE * 2 + 1 ] = { 0 }; + + // Buffer for secret + size_t secretlen = MBEDTLS_MD_MAX_SIZE; + uint8_t secret[ MBEDTLS_MD_MAX_SIZE]; + + // Buffer for nonce + size_t noncelen = MBEDTLS_MD_MAX_SIZE; + uint8_t nonce[MBEDTLS_MD_MAX_SIZE]; + + // Buffer for PSK hash + size_t psk_hashlen = MBEDTLS_MD_MAX_SIZE; + uint8_t psk_hash[MBEDTLS_MD_MAX_SIZE]; + + // Buffer for key + size_t keylen = MBEDTLS_MD_MAX_SIZE; + uint8_t key[MBEDTLS_MD_MAX_SIZE]; + + // Buffer for exporter + size_t exporterlen = MBEDTLS_MD_MAX_SIZE; + uint8_t exporter[MBEDTLS_MD_MAX_SIZE]; + + // Buffer for pkE + uint8_t pkE_tmp[PSA_EXPORT_PUBLIC_KEY_MAX_SIZE] = { 0 }; + size_t pkE_tmp_len = PSA_EXPORT_PUBLIC_KEY_MAX_SIZE; + + // Buffer for secret + uint8_t buffer[MBEDTLS_MD_MAX_SIZE]; + size_t shared_secretlen = MBEDTLS_MD_MAX_SIZE; + uint8_t *shared_secret = buffer; + size_t halflen=0; + size_t pskidlen=0; + + psa_status_t status; + psa_key_attributes_t skE_attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_key_handle_t skE_handle = 0; + size_t key_len; + psa_key_type_t type; + int ret; + enum t_cose_err_t err; + + // Input check: mode + switch( mode ) + { + case HPKE_MODE_BASE: + case HPKE_MODE_PSK: + case HPKE_MODE_AUTH: + case HPKE_MODE_PSKAUTH: + break; + default: + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Input check: psk + if( (mode == HPKE_MODE_PSK || mode == HPKE_MODE_PSKAUTH ) + && ( pskid == NULL || psklen == 0 || psk == NULL ) ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + // Input check: suite + ret = hpke_suite_check( suite ); + + if( ret != 0 ) + { + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + switch( suite.kem_id ) + { + case HPKE_KEM_ID_P256: + key_len = 256; + break; + case HPKE_KEM_ID_P384: + key_len = 384; + break; + case HPKE_KEM_ID_P521: + key_len = 521; + break; + default: + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + switch( suite.kem_id ) + { + case HPKE_KEM_ID_P256: + case HPKE_KEM_ID_P384: + case HPKE_KEM_ID_P521: + type = PSA_KEY_TYPE_ECC_KEY_PAIR( PSA_ECC_FAMILY_SECP_R1 ); + break; + case HPKE_KEM_ID_25519: // not implemented yet + case HPKE_KEM_ID_448: // not implemented yet + default: + return( MBEDTLS_ERR_HPKE_BAD_INPUT_DATA ); + } + + if( ext_pkE_handle == 0 ) + { + /* generate key pair: skE, pkE */ + psa_set_key_usage_flags( &skE_attributes, PSA_KEY_USAGE_DERIVE | PSA_KEY_USAGE_EXPORT ); + psa_set_key_algorithm( &skE_attributes, PSA_ALG_ECDH ); + psa_set_key_type( &skE_attributes, type ); + psa_set_key_bits( &skE_attributes, key_len ); + + status = psa_generate_key( &skE_attributes, &skE_handle ); + + if( status != PSA_SUCCESS ) + { + return( EXIT_FAILURE ); + } + } + + status = psa_export_public_key( (ext_pkE_handle == 0) ? skE_handle : ext_pkE_handle, + (ext_pkE_handle == 0) ? pkE : pkE_tmp, + (ext_pkE_handle == 0) ? *pkE_len : pkE_tmp_len, + (ext_pkE_handle == 0) ? pkE_len : &pkE_tmp_len + ); + + if( status != PSA_SUCCESS ) + { + return( EXIT_FAILURE ); + } + + /* Run DH KEM to get shared secret */ + ret = hpke_do_kem( 1, // 1 means encryption + suite, // ciphersuite + (ext_pkE_handle == 0) ? skE_handle : ext_pkE_handle, // skE handle + (ext_pkE_handle == 0) ? *pkE_len : pkE_tmp_len, // pkE length + (ext_pkE_handle == 0) ? pkE : pkE_tmp, // pkE + pkR_len, pkR, // pkR + skS_handle, // skS handle + pkS_len, pkS, // pkS + &shared_secret, &shared_secretlen ); // shared secret + + if( ret != 0 ) + { + return( MBEDTLS_ERR_HPKE_INTERNAL_ERROR ); + } + + /* Create context buffer */ + ks_context[0] = (unsigned char) ( mode % 256 ); + ks_contextlen--; + halflen = ks_contextlen; + pskidlen = ( psk == NULL ? 0 : strlen( pskid ) ); + + ret = mbedtls_hpke_extract( suite, // ciphersuite + HPKE_5869_MODE_FULL, // mode + (unsigned char *) "", 0, // salt + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( psk_id_hash ), // label + (unsigned char*) pskid, pskidlen, // psk id + ks_context + 1, &halflen ); + + if( ret != 0 ) + { + goto error; + } + + ks_contextlen -= halflen; + + ret = mbedtls_hpke_extract( suite, + HPKE_5869_MODE_FULL, + (unsigned char *) "", 0, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( info_hash ), + info, infolen, + ks_context + 1 + halflen, &ks_contextlen ); + + if( ret != 0 ) + { + goto error; + } + + ks_contextlen += 1 + halflen; + + /* Extract secret */ + ret = mbedtls_hpke_extract( suite, + HPKE_5869_MODE_FULL, + (unsigned char *) "", 0, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( psk_hash ), + psk, psklen, + psk_hash, &psk_hashlen ); + + if( ret != 0) + { + goto error; + } + + secretlen = hpke_kdf_tab[suite.kdf_id].Nh; + + ret = mbedtls_hpke_extract( suite, + HPKE_5869_MODE_FULL, + shared_secret, shared_secretlen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( secret ), + psk, psklen, + secret, &secretlen ); + + if( ret != 0 ) + { + goto error; + } + + noncelen = hpke_aead_tab[suite.aead_id].Nn; + + ret = mbedtls_hpke_expand( suite, + HPKE_5869_MODE_FULL, + secret, secretlen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( base_nonce ), + ks_context, ks_contextlen, + noncelen, nonce, &noncelen ); + + if( ret != 0 ) + { + goto error; + } + + keylen = hpke_aead_tab[suite.aead_id].Nk; + + ret = mbedtls_hpke_expand( suite, + HPKE_5869_MODE_FULL, + secret, secretlen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( key ), + ks_context, ks_contextlen, + keylen, key, &keylen ); + + if( ret != 0 ) + { + goto error; + } + + exporterlen = hpke_kdf_tab[suite.kdf_id].Nh; + + ret = mbedtls_hpke_expand( suite, + HPKE_5869_MODE_FULL, + secret, secretlen, + MBEDTLS_SSL_HPKE_LBL_WITH_LEN( exp ), + ks_context, ks_contextlen, + exporterlen, exporter, &exporterlen ); + + if( ret != 0 ) + { + goto error; + } + + int32_t cose_aead_algorithm_id; + struct q_useful_buf_c ci; + struct t_cose_key cek_handle; + + /* Map the HPKE algorithm ID to a COSE algorithm ID */ + cose_aead_algorithm_id = hpke_aead_id_2_cose(suite); + + /* Turn the bytes of the CEK into a key handle for the crypto library. */ + err = t_cose_crypto_make_symmetric_key_handle(cose_aead_algorithm_id, + (struct q_useful_buf_c) {key, keylen}, + &cek_handle); + if( err != 0 ) + { + ret = (int)err; // TODO: make this function return enum t_cose_err_t + goto error; + } + + + // TODO: move this call out of hpke and into main t_cose_encrypt_enc so it is shared for all key distribution types + /* Call crypto layer to actually encrypt the payload */ + err = t_cose_crypto_aead_encrypt(cose_aead_algorithm_id, + cek_handle, + (struct q_useful_buf_c) {nonce, noncelen}, + (struct q_useful_buf_c) {aad, aadlen}, + (struct q_useful_buf_c) {clear, clearlen}, + (struct q_useful_buf) {cipher, *cipherlen}, + &ci); + + t_cose_crypto_free_symmetric_key(cek_handle); + + *cipherlen = ci.len; // TODO: when fully converted to useful, this will go away + + if( err != 0 ) + { + ret = (int)err; // TODO: make this function return enum t_cose_err_t + goto error; + } + +error: + return( ret ); +} + + +int mbedtls_hpke_encrypt( unsigned int mode, hpke_suite_t suite, + char *pskid, size_t psklen, uint8_t *psk, + size_t pkR_len, const uint8_t *pkR, + psa_key_handle_t skI_handle, + size_t clearlen, const uint8_t *clear, + size_t aadlen, uint8_t *aad, + size_t infolen, uint8_t *info, + psa_key_handle_t ext_skE_handle, + size_t *pkE_len, uint8_t *pkE, + size_t *cipherlen, uint8_t *cipher ) +{ + return hpke_enc_int( mode, // HPKE mode + suite, // ciphersuite + pskid, psklen, psk, // PSK for authentication + pkR_len, pkR, // pkR + skI_handle, // skI handle + clearlen, clear, // plaintext + aadlen, aad, // Additional data + infolen, info, // Info + ext_skE_handle, // skE handle + pkE_len, pkE, // pkE + cipherlen, cipher ); // ciphertext +} + +#else + +void hpke_placeholder(void) {} + + +#endif /* T_COSE_DISABLE_HPKE */ + + + diff --git a/src/hpke.h b/src/hpke.h new file mode 100644 index 00000000..e8d9f8c2 --- /dev/null +++ b/src/hpke.h @@ -0,0 +1,359 @@ +/* + * Copyright 2019-2021 Stephen Farrell. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * Copyright The Mbed TLS Contributors + * 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. + * + * This code is based on https://github.com/sftcd/happykey + * Special thanks goes to Stephen Farrell for his support and the permission + * to re-use the code in Mbed TLS. Code has been subsquently been modified + * by Hannes Tschofenig. + * + */ + +#ifndef HPKE_H_INCLUDED +#define HPKE_H_INCLUDED + + +#include "psa/crypto.h" +#include +#include + +/* + * The HPKE modes + */ +#define HPKE_MODE_BASE 0 ///< Base mode +#define HPKE_MODE_PSK 1 ///< Pre-shared key mode +#define HPKE_MODE_AUTH 2 ///< Authenticated mode +#define HPKE_MODE_PSKAUTH 3 ///< PSK+authenticated mode + +/* + * The (16bit) HPKE algorithm IDs + */ +#define HPKE_KEM_ID_RESERVED 0x0000 ///< not used +#define HPKE_KEM_ID_P256 0x0010 ///< NIST P-256 +#define HPKE_KEM_ID_P384 0x0011 ///< NIST P-256 +#define HPKE_KEM_ID_P521 0x0012 ///< NIST P-521 +#define HPKE_KEM_ID_25519 0x0020 ///< Curve25519 +#define HPKE_KEM_ID_448 0x0021 ///< Curve448 + +#define HPKE_KDF_ID_RESERVED 0x0000 ///< not used +#define HPKE_KDF_ID_HKDF_SHA256 0x0001 ///< HKDF-SHA256 +#define HPKE_KDF_ID_HKDF_SHA384 0x0002 ///< HKDF-SHA512 +#define HPKE_KDF_ID_HKDF_SHA512 0x0003 ///< HKDF-SHA512 + +#define HPKE_AEAD_ID_RESERVED 0x0000 ///< not used +#define HPKE_AEAD_ID_AES_GCM_128 0x0001 ///< AES-GCM-128 +#define HPKE_AEAD_ID_AES_GCM_256 0x0002 ///< AES-GCM-256 +#define HPKE_AEAD_ID_CHACHA_POLY1305 0x0003 ///< Chacha20-Poly1305 + +/* strings for modes */ +#define HPKE_MODESTR_BASE "base" ///< base mode (1), no sender auth +#define HPKE_MODESTR_PSK "psk" ///< psk mode (2) +#define HPKE_MODESTR_AUTH "auth" ///< auth (3), with a sender-key pair +#define HPKE_MODESTR_PSKAUTH "pskauth" ///< psk+sender-key pair (4) + +/* strings for suites */ +#define HPKE_KEMSTR_P256 "p256" ///< KEM id 0x10 +#define HPKE_KEMSTR_P384 "p384" ///< KEM id 0x11 +#define HPKE_KEMSTR_P521 "p521" ///< KEM id 0x12 +#define HPKE_KEMSTR_X25519 "x25519" ///< KEM id 0x20 +#define HPKE_KEMSTR_X448 "x448" ///< KEM id 0x21 +#define HPKE_KDFSTR_256 "hkdf-sha256" ///< KDF id 1 +#define HPKE_KDFSTR_384 "hkdf-sha384" ///< KDF id 2 +#define HPKE_KDFSTR_512 "hkdf-sha512" ///< KDF id 3 +#define HPKE_AEADSTR_AES128GCM "aes128gcm" ///< AEAD id 1 +#define HPKE_AEADSTR_AES256GCM "aes256gcm" ///< AEAD id 2 +#define HPKE_AEADSTR_CP "chachapoly1305" ///< AEAD id 3 + + + +/** + * \name HPKE Error codes + * \{ + */ +#define MBEDTLS_ERR_HPKE_BAD_INPUT_DATA -0x5F81 /**< Bad input parameters to function. */ +#define MBEDTLS_ERR_HPKE_INTERNAL_ERROR -0x5F82 /**< Internal error. */ +#define MBEDTLS_ERR_HPKE_BUFFER_TOO_SMALL -0x5F83 /**< Buffer too small. */ +/* \} name */ + + +/*! + * \brief ciphersuite combination + */ +typedef struct { + uint16_t kem_id; ///< Key Encryption Method id + uint16_t kdf_id; ///< Key Derivation Function id + uint16_t aead_id; ///< Authenticated Encryption with Associated Data id +} hpke_suite_t; + +/*! + * Two suite constants, use this like: + * + * hpke_suite_t myvar = HPKE_SUITE_DEFAULT; + */ +#define HPKE_SUITE_DEFAULT { HPKE_KEM_ID_P256, HPKE_KDF_ID_HKDF_SHA256, HPKE_AEAD_ID_AES_GCM_128 } +#define HPKE_SUITE_TURNITUPTO11 { HPKE_KEM_ID_448, HPKE_KDF_ID_HKDF_SHA512, HPKE_AEAD_ID_CHACHA_POLY1305 } + + +/*! + * \brief Map ascii to binary - utility macro used in >1 place + */ +#define HPKE_A2B(__c__) (__c__>='0'&&__c__<='9'?(__c__-'0'):\ + (__c__>='A'&&__c__<='F'?(__c__-'A'+10):\ + (__c__>='a'&&__c__<='f'?(__c__-'a'+10):0))) + +/* + * \brief HPKE single-shot encryption function + * + * \param mode is the HPKE mode + * \param suite is the ciphersuite to use + * \param pskid is the pskid string fpr a PSK mode (can be NULL) + * \param psklen is the psk length + * \param psk is the psk + * \param publen is the length of the public key + * \param pub is the encoded public key + * \param privlen is the length of the private (authentication) key + * \param priv is the encoded private (authentication) key + * \param clearlen is the length of the cleartext + * \param clear is the encoded cleartext + * \param aadlen is the lenght of the additional data + * \param aad is the encoded additional data + * \param infolen is the lenght of the info data (can be zero) + * \param info is the encoded info data (can be NULL) + * \param senderpublen is the length of the input buffer for the sender's public key (length used on output) + * \param senderpub is the input buffer for sender public key + * \param cipherlen is the length of the input buffer for ciphertext (length used on output) + * \param cipher is the input buffer for ciphertext + * \return 1 for good (OpenSSL style), not-1 for error + * + * Oddity: we're passing an hpke_suit_t directly, but 48 bits is actually + * smaller than a 64 bit pointer, so that's grand, if odd:-) + */ +int mbedtls_hpke_encrypt( unsigned int mode, hpke_suite_t suite, + char *pskid, size_t psklen, uint8_t *psk, + size_t pkR_len, const uint8_t *pkR, + psa_key_handle_t skI_handle, + size_t clearlen, const uint8_t *clear, + size_t aadlen, uint8_t *aad, + size_t infolen, uint8_t *info, + psa_key_handle_t ext_skE_handle, + size_t *pkE_len, uint8_t *pkE, + size_t *cipherlen, uint8_t *cipher ); + + +#if defined(MBEDTLS_SSL_DEBUG_ALL) +/*! + * \brief for odd/occasional debugging + * + * \param fout is a FILE * to use + * \param msg is prepended to print + * \param buf is the buffer to print + * \param blen is the length of the buffer + * \return 1 for success + */ + +int hpke_pbuf(FILE *fout, char *msg,unsigned char *buf,size_t blen); +#endif + + +/* + * \brief HPKE single-shot decryption function + * \param mode is the HPKE mode + * \param suite is the ciphersuite to use + * \param pskid is the pskid string fpr a PSK mode (can be NULL) + * \param psklen is the psk length + * \param psk is the psk + * \param publen is the length of the public (authentication) key + * \param pub is the encoded public (authentication) key + * \param privlen is the length of the private key + * \param priv is the encoded private key + * \param evppriv is a pointer to an internal form of private key + * \param enclen is the length of the peer's public value + * \param enc is the peer's public value + * \param cipherlen is the length of the ciphertext + * \param cipher is the ciphertext + * \param aadlen is the lenght of the additional data + * \param aad is the encoded additional data + * \param infolen is the lenght of the info data (can be zero) + * \param info is the encoded info data (can be NULL) + * \param clearlen is the length of the input buffer for cleartext (octets used on output) + * \param clear is the encoded cleartext + * \return 1 for good (OpenSSL style), not-1 for error + */ + +int mbedtls_hpke_decrypt( unsigned int mode, hpke_suite_t suite, + char *pskid, size_t psklen, unsigned char *psk, + size_t pkS_len, unsigned char *pkS, + psa_key_handle_t skR_handle, + size_t pkE_len, const unsigned char *pkE, + size_t cipherlen, const unsigned char *cipher, + size_t aadlen, unsigned char *aad, + size_t infolen, unsigned char *info, + size_t *clearlen, unsigned char *clear ); + + + +/** + * \brief check if a suite is supported locally + * + * \param suite is the suite to check + * \return 1 for good/supported, not-1 otherwise + */ +int hpke_suite_check(hpke_suite_t suite); + +/* + * These are temporary and only needed for esni-draft-09 + * where we gotta call 'em from outside + */ + + +/* + * 5869 modes for func below + */ +#define HPKE_5869_MODE_KEM 1 ///< Abide by HPKE section 4.1 +#define HPKE_5869_MODE_FULL 2 ///< Abide by HPKE section 5.1 + + + + +#define MBEDTLS_SSL_HPKE_LABEL_LIST \ + MBEDTLS_SSL_HPKE_LABEL( version , "HPKE-v1" ) \ + MBEDTLS_SSL_HPKE_LABEL( kem , "KEM" ) \ + MBEDTLS_SSL_HPKE_LABEL( hpke , "HPKE" ) \ + MBEDTLS_SSL_HPKE_LABEL( eae_prk , "eae_prk" ) \ + MBEDTLS_SSL_HPKE_LABEL( psk_id_hash , "psk_id_hash" ) \ + MBEDTLS_SSL_HPKE_LABEL( info_hash , "info_hash" ) \ + MBEDTLS_SSL_HPKE_LABEL( shared_secret , "shared_secret" ) \ + MBEDTLS_SSL_HPKE_LABEL( base_nonce , "base_nonce" ) \ + MBEDTLS_SSL_HPKE_LABEL( exp , "exp" ) \ + MBEDTLS_SSL_HPKE_LABEL( key , "key" ) \ + MBEDTLS_SSL_HPKE_LABEL( psk_hash , "psk_hash" ) \ + MBEDTLS_SSL_HPKE_LABEL( secret , "secret" ) + +#define MBEDTLS_SSL_HPKE_LABEL( name, string ) \ + const char name [ sizeof(string) - 1 ]; + +union mbedtls_ssl_hpke_labels_union +{ + MBEDTLS_SSL_HPKE_LABEL_LIST +}; +struct mbedtls_ssl_hpke_labels_struct +{ + MBEDTLS_SSL_HPKE_LABEL_LIST +}; +#undef MBEDTLS_SSL_HPKE_LABEL + +extern const struct mbedtls_ssl_hpke_labels_struct mbedtls_ssl_hpke_labels; + + +#define MBEDTLS_SSL_HPKE_LBL_WITH_LEN( LABEL ) \ + mbedtls_ssl_hpke_labels.LABEL, \ + sizeof(mbedtls_ssl_hpke_labels.LABEL) + +#define MBEDTLS_SSL_HPKE_MAX_LABEL_LEN \ + sizeof( union mbedtls_ssl_hpke_labels_union ) + +/** + * \brief HPKE Extract + * + * \param suite Ciphersuite + * \param mode5869 Controls labelling specifics + * \param salt Salt + * \param saltlen Length of above + * \param label Label for separation + * \param labellen Length of above + * \param ikm The initial key material (IKM) + * \param ikmlen Length of above + * \param secret The result of extraction + * \param secretlen Bufsize on input, used size on output + * + * \return 0 on success. + * \return #MBEDTLS_ERR_HKDF_BAD_INPUT_DATA when the parameters are invalid. + * \return An MBEDTLS_ERR_MD_* error for errors returned from the underlying + * MD layer. + * \return #MBEDTLS_ERR_HPKE_INTERNAL_ERROR for unexpected errors related to + * HPKE processin + * \return #MBEDTLS_ERR_HPKE_BAD_INPUT_DATA when the parameters to the HPKE + * layer are invalid. + * + * Note: Mode can be: + * + * - HPKE_5869_MODE_KEM meaning to follow section 4.1 + * where the suite_id is used as: + * concat("KEM", I2OSP(kem_id, 2)) + * + * - HPKE_5869_MODE_FULL meaning to follow section 5.1 + * where the suite_id is used as: + * concat("HPKE",I2OSP(kem_id, 2), + * I2OSP(kdf_id, 2), I2OSP(aead_id, 2)) + */ +int mbedtls_hpke_extract( const hpke_suite_t suite, + const int mode5869, + const unsigned char *salt, const size_t saltlen, + const char *label, const size_t labellen, + const unsigned char *ikm, const size_t ikmlen, + unsigned char *secret, size_t *secretlen ); + + +/*! + * \brief RFC5869 HKDF-Expand + * + * \param suite is the ciphersuite + * \param mode5869 - controls labelling specifics + * \param prk - the initial pseudo-random key material + * \param prklen - length of above + * \param label - label to prepend to info + * \param labellen - label to prepend to info + * \param info - the info + * \param infolen - length of above + * \param L - the length of the output desired + * \param out - the result of expansion (allocated by caller) + * \param outlen - buf size on input + * \return 1 for good otherwise bad + */ +int mbedtls_hpke_expand(const hpke_suite_t suite, const int mode5869, + const unsigned char *prk, const size_t prklen, + const char *label, const size_t labellen, + const unsigned char *info, const size_t infolen, + const size_t L, + unsigned char *out, size_t *outlen); + +/*! + * \brief ExtractAndExpand + * \param suite is the ciphersuite + * \param mode5869 - controls labelling specifics + * \param shared_secret - the initial DH shared secret + * \param shared_secretlen - length of above + * \param context - the info + * \param contextlen - length of above + * \param secret - the result of extract&expand + * \param secretlen - buf size on input + * \return 1 for good otherwise bad + */ +int mbedtls_hpke_extract_and_expand(hpke_suite_t suite, int mode5869, + unsigned char *shared_secret , size_t shared_secretlen, + unsigned char *context, size_t contextlen, + unsigned char *secret, size_t *secretlen ); + +#endif + diff --git a/src/t_cose_parameters.c b/src/t_cose_parameters.c index 684e147e..b4961010 100644 --- a/src/t_cose_parameters.c +++ b/src/t_cose_parameters.c @@ -824,6 +824,26 @@ t_cose_param_find_kid(const struct t_cose_parameter *parameter_list) } +/* + * Public function. See t_cose_parameters.h + */ +struct q_useful_buf_c +t_cose_param_find_enc(const struct t_cose_parameter *parameter_list) +{ + const struct t_cose_parameter *p_found; + + p_found = t_cose_param_find(parameter_list, T_COSE_HEADER_ALG_PARAM_HPKE_ENCAPSULATED_KEY); + if(p_found != NULL && + p_found->value_type == T_COSE_PARAMETER_TYPE_BYTE_STRING) { + return p_found->value.string; + } else { + return NULL_Q_USEFUL_BUF_C; + } +} + + + + /* * Public function. See t_cose_parameters.h */ diff --git a/src/t_cose_recipient_dec_hpke.c b/src/t_cose_recipient_dec_hpke.c new file mode 100644 index 00000000..d0eca66d --- /dev/null +++ b/src/t_cose_recipient_dec_hpke.c @@ -0,0 +1,203 @@ +/* + * \file t_cose_recipient_dec_hpke.c + * + * Copyright (c) 2022, Arm Limited. All rights reserved. + * Copyright (c) 2023, Laurence Lundblade. All rights reserved. + * Copyright (c) 2024, Hannes Tschofenig. All rights reserved. + + * SPDX-License-Identifier: BSD-3-Clause + * + * See BSD-3-Clause license in README.md + * + */ + +#ifndef T_COSE_DISABLE_HPKE + +#include +#include "qcbor/qcbor_spiffy_decode.h" +#include "t_cose/t_cose_recipient_dec_hpke.h" /* Interface implemented */ +#include "t_cose/t_cose_encrypt_enc.h" +#include "t_cose/t_cose_common.h" +#include "t_cose/t_cose_parameters.h" +#include "t_cose/q_useful_buf.h" +#include "t_cose/t_cose_standard_constants.h" +#include "t_cose_crypto.h" +#include "hpke.h" +#include "t_cose_util.h" + + +// TODO: maybe rearrange this to align with what happens in crypto adaptor layer +struct hpke_sender_info { + uint64_t kem_id; + uint64_t kdf_id; + uint64_t aead_id; + struct q_useful_buf_c enc; +}; + +/* This is an implementation of t_cose_recipient_dec_cb */ +enum t_cose_err_t +t_cose_recipient_dec_hpke_cb_private(struct t_cose_recipient_dec *me_x, + const struct t_cose_header_location loc, + const struct t_cose_alg_and_bits ce_alg, + QCBORDecodeContext *cbor_decoder, + struct q_useful_buf cek_buffer, + struct t_cose_parameter_storage *p_storage, + struct t_cose_parameter **params, + struct q_useful_buf_c *cek) +{ + struct t_cose_recipient_dec_hpke *me; + QCBORError result; + QCBORError cbor_error; + int64_t alg = 0; + UsefulBufC cek_encrypted; + struct q_useful_buf_c protected_params; + enum t_cose_err_t cose_result; + struct hpke_sender_info sender_info; + int psa_ret; + bool prot; + const struct t_cose_parameter *enc_param; +// const struct t_cose_parameter *kid_param; + UsefulBufC kid; + UsefulBufC enc; + // QCBORError uErr; + + MakeUsefulBufOnStack(enc_struct_buf, 50); // TODO: allow this to be + // supplied externally + struct q_useful_buf_c enc_struct; + + me = (struct t_cose_recipient_dec_hpke *)me_x; + + (void)ce_alg; /* TODO: Still up for debate whether COSE-HPKE does COSE_KDF_Context or not. */ + + /* One recipient */ + QCBORDecode_EnterArray(cbor_decoder, NULL); + cbor_error = QCBORDecode_GetError(cbor_decoder); + if(cbor_error != QCBOR_SUCCESS) { + goto Done; + } + + cose_result = t_cose_headers_decode(cbor_decoder, /* in: decoder to read from */ + loc, /* in: location in COSE message*/ + NULL,// hpke_encapsulated_key_decode_cb, /* in: callback for specials */ + NULL, // &sender_info, /* in: context for callback */ + p_storage, /* in: parameter storage */ + params, /* out: list of decoded params */ + &protected_params /* out: encoded prot params */ + ); + if(cose_result != T_COSE_SUCCESS) { + goto Done; + } + + + /* get CEK */ + QCBORDecode_GetByteString(cbor_decoder, &cek_encrypted); +// uErr = QCBORDecode_GetAndResetError(cbor_decoder); + + /* Close out decoding and error check */ + QCBORDecode_ExitArray(cbor_decoder); + cbor_error = QCBORDecode_GetError(cbor_decoder); + if(cbor_error != QCBOR_SUCCESS) { + cose_result = qcbor_decode_error_to_t_cose_error(cbor_error, + T_COSE_ERR_RECIPIENT_FORMAT); + goto Done; + } + + /* Fetch algorithm id */ + alg = t_cose_param_find_alg_id_prot(*params); + if (alg == T_COSE_HPKE_Base_P256_SHA256_AES128GCM) { + sender_info.kem_id = T_COSE_HPKE_KEM_ID_P256; /* kem id */ + //cose_ec_curve_id = T_COSE_ELLIPTIC_CURVE_P_256; /* curve */ + sender_info.kdf_id = T_COSE_HPKE_KDF_ID_HKDF_SHA256; /* kdf id */ + sender_info.aead_id = T_COSE_HPKE_AEAD_ID_AES_GCM_128; /* aead id */ + } else { + cose_result = T_COSE_ERR_UNSUPPORTED_CONTENT_KEY_DISTRIBUTION_ALG; + goto Done; + } + + if(t_cose_params_empty(protected_params)) { + cose_result = T_COSE_ERR_CBOR_MANDATORY_FIELD_MISSING; + goto Done; + } + + /* Fetch encapsulated key */ + enc = t_cose_param_find_enc(*params); + if(q_useful_buf_c_is_null(enc)) { + cose_result = T_COSE_ERR_FAIL; + goto Done; + } +// sender_info.enc = enc_param->value.string; + sender_info.enc = enc; + + /* Fetch kid, if present */ + kid = t_cose_param_find_kid(*params); + if(!q_useful_buf_c_is_null(me->kid)) { + if(q_useful_buf_c_is_null(kid)) { + cose_result = T_COSE_ERR_NO_KID; + goto Done; + } + if(q_useful_buf_compare(kid, me->kid)) { + cose_result = T_COSE_ERR_KID_UNMATCHED; + goto Done; + } + } + + + + /* --- Make the Enc_structure ---- */ + cose_result = create_enc_structure("Enc_Recipient", /* in: context string */ + protected_params, + NULL_Q_USEFUL_BUF_C, /* in: Externally supplied AAD */ + enc_struct_buf, + &enc_struct); + if(cose_result != T_COSE_SUCCESS) { + goto Done; + } + + // TODO: There is a big rearrangement necessary when the crypto adaptation + // layer calls for HPKE are sorted out. Lots of work to complete that... + hpke_suite_t suite; + size_t cek_len_in_out; + + // TODO: check that the sender_info decode happened correctly + // before proceeding + suite.aead_id = (uint16_t)sender_info.aead_id; + suite.kdf_id = (uint16_t)sender_info.kdf_id; + suite.kem_id = (uint16_t)sender_info.kem_id; + + cek_len_in_out = cek_buffer.len; + + psa_ret = mbedtls_hpke_decrypt( + HPKE_MODE_BASE, // HPKE mode + suite, // ciphersuite + NULL, 0, NULL, // PSK for authentication + 0, NULL, // pkS + (psa_key_handle_t)me->skr.key.handle, // skR handle + sender_info.enc.len, // pkE_len + sender_info.enc.ptr, // pkE + cek_encrypted.len, // Ciphertext length + cek_encrypted.ptr, // Ciphertext + // TODO: fix the const-ness all the way down so the cast can be removed + 0, NULL, // enc_struct.len, (uint8_t *)(uintptr_t)enc_struct.ptr, // AAD + 0, NULL, // Info + &cek_len_in_out, // Plaintext length + cek_buffer.ptr // Plaintext + ); + + if (psa_ret != 0) { + cose_result = T_COSE_ERR_HPKE_DECRYPT_FAIL; + goto Done; + } + + cek->ptr = cek_buffer.ptr; + cek->len = cek_len_in_out; + +Done: + return(cose_result); +} + +#else /* T_COSE_DISABLE_HPKE */ + +/* Place holder for compiler tools that don't like files with no functions */ +void t_cose_recipient_dec_hpke_placeholder(void) {} + +#endif /* T_COSE_DISABLE_HPKE */ diff --git a/src/t_cose_recipient_enc_hpke.c b/src/t_cose_recipient_enc_hpke.c new file mode 100644 index 00000000..a4eca14e --- /dev/null +++ b/src/t_cose_recipient_enc_hpke.c @@ -0,0 +1,263 @@ +/** + * \file t_cose_recipient_enc_hpke.c + * + * Copyright (c) 2022, Arm Limited. All rights reserved. + * Copyright (c) 2023, Laurence Lundblade. All rights reserved. + * Copyright (c) 2024, Hannes Tschofenig. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * See BSD-3-Clause license in README.md + * + */ + +#ifndef T_COSE_DISABLE_HPKE + +#include +#include "qcbor/qcbor_encode.h" +#include "t_cose/t_cose_recipient_enc.h" +#include "t_cose/t_cose_recipient_enc_hpke.h" /* Interface implemented */ +#include "t_cose/t_cose_encrypt_enc.h" +#include "t_cose/t_cose_common.h" +#include "t_cose/q_useful_buf.h" +#include "t_cose/t_cose_standard_constants.h" +#include "t_cose_crypto.h" +#include "hpke.h" +#include "t_cose_util.h" + + +/** + * \brief HPKE Encrypt Wrapper + * + * \param[in] suite HPKE ciphersuite + * \param[in] pkR pkR buffer + * \param[in] pkE pkE buffer + * \param[in] plaintext Plaintext buffer + * \param[in] ciphertext Ciphertext buffer + * \param[out] ciphertext_len Length of the produced ciphertext + * + * \retval T_COSE_SUCCESS + * HPKE encrypt operation was successful. + * \retval T_COSE_ERR_HPKE_ENCRYPT_FAIL + * Encrypt operation failed. + */ + +enum t_cose_err_t +t_cose_crypto_hpke_encrypt(struct t_cose_crypto_hpke_suite_t suite, + struct t_cose_key recipient_pub_key, + struct t_cose_key pkE, + struct q_useful_buf_c aad, + struct q_useful_buf_c plaintext, + struct q_useful_buf ciphertext, + size_t *ciphertext_len) +{ + int ret; + hpke_suite_t hpke_suite; + struct q_useful_buf_c pkR; + enum t_cose_err_t result; + int32_t cose_curve; + MakeUsefulBufOnStack( x_coord_buf, T_COSE_BITS_TO_BYTES(T_COSE_ECC_MAX_CURVE_BITS)); + MakeUsefulBufOnStack( y_coord_buf, T_COSE_BITS_TO_BYTES(T_COSE_ECC_MAX_CURVE_BITS)); + Q_USEFUL_BUF_MAKE_STACK_UB(x_y_coord_buf, 2*T_COSE_BITS_TO_BYTES(T_COSE_ECC_MAX_CURVE_BITS)+1); + + struct q_useful_buf_c x_coord; + struct q_useful_buf_c y_coord; + bool y_sign; + + hpke_suite.aead_id = suite.aead_id; + hpke_suite.kdf_id = suite.kdf_id; + hpke_suite.kem_id = suite.kem_id; + + result = t_cose_crypto_export_ec2_key(recipient_pub_key, + &cose_curve, + x_coord_buf, + &x_coord, + y_coord_buf, + &y_coord, + &y_sign); + if(result != T_COSE_SUCCESS) { + return result; + } + + memset(x_y_coord_buf.ptr, 0x04, x_coord.len + y_coord.len + 1); + memcpy(x_y_coord_buf.ptr + 1, x_coord.ptr, x_coord.len); + memcpy(x_y_coord_buf.ptr + 1 + x_coord.len, y_coord.ptr, y_coord.len); + + ret = mbedtls_hpke_encrypt( + HPKE_MODE_BASE, // HPKE mode + hpke_suite, // ciphersuite + NULL, 0, NULL, // PSK + x_coord.len + y_coord.len + 1, // pkR length + x_y_coord_buf.ptr, // pkR + 0, // skI + plaintext.len, // plaintext length + plaintext.ptr, // plaintext + //TODO: fix the const-ness all the way down so this cast can go away + 0, NULL, // aad.len, (uint8_t *)(uintptr_t)aad.ptr, // Additional data + 0, NULL, // Info + (psa_key_handle_t) + pkE.key.handle, // skE handle + 0, NULL, // pkE + ciphertext_len, // ciphertext length + (uint8_t *) ciphertext.ptr); // ciphertext + + if (ret != 0) { + return(T_COSE_ERR_HPKE_ENCRYPT_FAIL); + } + + return(T_COSE_SUCCESS); +} + +/* + * See documentation in t_cose_recipient_enc_hpke.h + */ +enum t_cose_err_t +t_cose_recipient_create_hpke_cb_private(struct t_cose_recipient_enc *me_x, + struct q_useful_buf_c cek, + struct t_cose_alg_and_bits ce_alg, + QCBOREncodeContext *cbor_encoder) +{ + struct q_useful_buf_c protected_params; + uint8_t encrypted_cek[T_COSE_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(T_COSE_MAX_SYMMETRIC_KEY_LENGTH)]; + size_t encrypted_cek_len = T_COSE_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(T_COSE_MAX_SYMMETRIC_KEY_LENGTH); + struct q_useful_buf_c cek_encrypted_cbor; + enum t_cose_err_t return_value; + struct t_cose_key ephemeral_key; + MakeUsefulBufOnStack( enc_struct_buf, 100); // TODO: allow this to be + // supplied externally + struct q_useful_buf_c enc_struct; + struct t_cose_recipient_enc_hpke *context; + struct q_useful_buf_c header; +// Q_USEFUL_BUF_MAKE_STACK_UB( enc_struct_buffer, T_COSE_ENCRYPT_STRUCT_DEFAULT_SIZE); + + struct t_cose_parameter params[3]; + struct t_cose_parameter *params2; + struct t_cose_parameter *params_tail; + + int32_t cose_curve; + MakeUsefulBufOnStack( x_coord_buf, T_COSE_BITS_TO_BYTES(T_COSE_ECC_MAX_CURVE_BITS)); + MakeUsefulBufOnStack( y_coord_buf, T_COSE_BITS_TO_BYTES(T_COSE_ECC_MAX_CURVE_BITS)); + Q_USEFUL_BUF_MAKE_STACK_UB(x_y_coord_buf, 2*T_COSE_BITS_TO_BYTES(T_COSE_ECC_MAX_CURVE_BITS)+1); + struct q_useful_buf_c x_coord; + struct q_useful_buf_c y_coord; + bool y_sign; + + context = (struct t_cose_recipient_enc_hpke *)me_x; + + (void)ce_alg; /* TODO: Still up for debate whether COSE-HPKE does COSE_KDF_Context or not. */ + + /* Create COSE_recipient array */ + QCBOREncode_OpenArray(cbor_encoder); + + /* Create ephemeral key */ + return_value = t_cose_crypto_generate_ec_key(context->cose_ec_curve_id, &ephemeral_key); + + if (return_value != T_COSE_SUCCESS) { + goto done; + } + + return_value = t_cose_crypto_export_ec2_key(ephemeral_key, + &cose_curve, + x_coord_buf, + &x_coord, + y_coord_buf, + &y_coord, + &y_sign); + if(return_value != T_COSE_SUCCESS) { + goto done_free_ec; + } + + memset(x_y_coord_buf.ptr, 0x04, 1); + memcpy(x_y_coord_buf.ptr + 1, x_coord.ptr, x_coord.len); + memcpy(x_y_coord_buf.ptr + 1 + x_coord.len, y_coord.ptr, y_coord.len); + x_y_coord_buf.len = 1 + x_coord.len + y_coord.len; + + /* ---- Make list of the header parameters and encode them ---- */ + + /* Alg ID param */ + params[0] = t_cose_param_make_alg_id(context->cose_algorithm_id); + params_tail = ¶ms[0]; + + /* Enc param */ + params[1] = t_cose_param_make_encapsulated_key( + (struct q_useful_buf_c) + {.ptr = x_y_coord_buf.ptr, .len = x_y_coord_buf.len}); + + params_tail->next = ¶ms[1]; + params_tail = params_tail->next; + + /* Optional kid param */ + if(!q_useful_buf_c_is_null(context->kid)) { + params[2] = t_cose_param_make_kid(context->kid); + params_tail->next = ¶ms[2]; + params_tail = params_tail->next; + } + + /* Custom params from caller */ + params2 = params; + t_cose_params_append(¶ms2, context->added_params); + + /* List complete, do the actual encode */ + return_value = t_cose_headers_encode(cbor_encoder, + params2, + &header); + + if(return_value) { + goto done_free_ec; + } + + /* --- Make the Enc_structure ---- */ + return_value = + create_enc_structure("Enc_Recipient",/* in: context string */ + header, /* in: CBOR encoded headers */ + NULL_Q_USEFUL_BUF_C, /* in: Externally supplied AAD */ + enc_struct_buf, /* in: output buffer */ + &enc_struct); /* out: encoded Enc_structure */ + + if (return_value != T_COSE_SUCCESS) { + goto done_free_ec; + } + + /* --- HPKE encryption of the CEK ---- */ + return_value = t_cose_crypto_hpke_encrypt( + context->hpke_suite, + context->recipient_pub_key, + ephemeral_key, + enc_struct, + cek, + (struct q_useful_buf) {.ptr = encrypted_cek, + .len = encrypted_cek_len}, + &encrypted_cek_len); + + if (return_value != T_COSE_SUCCESS) { + goto done_free_ec; + } + + /* Convert to UsefulBufC structure */ +// cek_encrypted_cbor.len = encrypted_cek_len; +// cek_encrypted_cbor.ptr = encrypted_cek; + + /* Add encrypted CEK */ + QCBOREncode_AddBytes(cbor_encoder, + (struct q_useful_buf_c) {.ptr = encrypted_cek, + .len = encrypted_cek_len}); + + /* Close recipient array */ + QCBOREncode_CloseArray(cbor_encoder); + + return_value = T_COSE_SUCCESS; + +done_free_ec: + t_cose_crypto_free_ec_key(ephemeral_key); + +done: + return(return_value); +} + + +#else + +/* Place holder for compiler tools that don't like files with no functions */ +void t_cose_recipient_enc_hpke_placeholder(void) {} + +#endif /* T_COSE_DISABLE_HPKE */ diff --git a/test/run_tests.c b/test/run_tests.c index 17b3b49f..f1ac1d28 100644 --- a/test/run_tests.c +++ b/test/run_tests.c @@ -61,6 +61,10 @@ static test_entry s_tests[] = { TEST_ENTRY(decrypt_known_bad), +#ifndef T_COSE_DISABLE_HPKE + TEST_ENTRY(hpke_enc_dec_test), +#endif /* T_COSE_DISABLE_HPKE */ + TEST_ENTRY(kdf_context_test), #endif /* T_COSE_USE_B_CON_SHA256 */ @@ -321,6 +325,11 @@ static void PrintSize(const char *szWhat, #include "t_cose/t_cose_recipient_enc_keywrap.h" #include "t_cose/t_cose_recipient_dec_keywrap.h" +#ifndef T_COSE_DISABLE_HPKE +#include "t_cose/t_cose_recipient_enc_hpke.h" +#include "t_cose/t_cose_recipient_dec_hpke.h" +#endif /* T_COSE_DISABLE_HPKE */ + #include "t_cose/t_cose_recipient_enc_esdh.h" #include "t_cose/t_cose_recipient_dec_esdh.h" diff --git a/test/t_cose_encrypt_decrypt_test.c b/test/t_cose_encrypt_decrypt_test.c index 5167c609..1019e95e 100644 --- a/test/t_cose_encrypt_decrypt_test.c +++ b/test/t_cose_encrypt_decrypt_test.c @@ -19,7 +19,16 @@ #include "t_cose_util.h" #include "data/test_messages.h" +#ifndef T_COSE_DISABLE_HPKE +#include "t_cose/t_cose_recipient_enc_hpke.h" +#include "t_cose/t_cose_recipient_dec_hpke.h" +#include "init_keys.h" + +#define PAYLOAD "This is the payload" +#define TEST_SENDER_IDENTITY "sender" +#define TEST_RECIPIENT_IDENTITY "recipient" +#endif #define PAYLOAD "This is the payload" #define TEST_KID "fixed_test_key_id" @@ -296,7 +305,101 @@ int32_t encrypt0_enc_dec(int32_t cose_algorithm_id) } +#ifndef T_COSE_DISABLE_HPKE +int32_t +hpke_encrypt_enc_dec(int32_t cose_algorithm_id, + int32_t hpke_alg, + struct q_useful_buf_c kid, + struct t_cose_key skR, + struct t_cose_key pkR, + struct q_useful_buf_c payload, + struct q_useful_buf_c aad) +{ + struct t_cose_encrypt_enc enc_ctx; + enum t_cose_err_t result; + int32_t return_value=0; + struct t_cose_recipient_enc_hpke recipient; + struct q_useful_buf_c cose_encrypted_message; + struct q_useful_buf_c decrypted_plain_text; + + Q_USEFUL_BUF_MAKE_STACK_UB ( cose_encrypt_message_buffer, 200); + Q_USEFUL_BUF_MAKE_STACK_UB ( decrypted_plaintext_buffer, 100); + struct t_cose_recipient_dec_hpke dec_recipient; + struct t_cose_encrypt_dec_ctx dec_ctx; + + /* Initialize the encryption context telling it we want + * a COSE_Encrypt (not a COSE_Encrypt0) because we're doing HPKE with a + * COSE_Recpipient. Also tell it the AEAD algorithm for the + * body of the message. + */ + t_cose_encrypt_enc_init(&enc_ctx, + T_COSE_OPT_MESSAGE_TYPE_ENCRYPT, + cose_algorithm_id); + + + /* Create the recipient object telling it the algorithm and the public key + * for the COSE_Recipient it's going to make. Then give that object + * to the main encryption context. (Only one recipient is set here, but + * there could be more) + */ + t_cose_recipient_enc_hpke_init(&recipient, hpke_alg); + + t_cose_recipient_enc_hpke_set_key(&recipient, + pkR, + kid); + t_cose_encrypt_add_recipient(&enc_ctx, + (struct t_cose_recipient_enc *)&recipient); + + + /* Now do the actual encryption */ + result = t_cose_encrypt_enc(&enc_ctx, /* in: encryption context */ + payload, /* in: payload to encrypt */ + aad, + cose_encrypt_message_buffer, /* in: buffer for COSE_Encrypt */ + &cose_encrypted_message); /* out: COSE_Encrypt */ + + if (result != T_COSE_SUCCESS) { + return_value = 2000 + (int32_t)result; + goto Done; + } + + + /* Set up the decryption context, telling it what type of + * message to expect if there's no tag (that part isn't quite implemented right yet anyway). + */ + t_cose_encrypt_dec_init(&dec_ctx, T_COSE_OPT_MESSAGE_TYPE_ENCRYPT); + + /* Set up the recipient object with the key material. We happen to know + * what the algorithm and key are in advance so we don't have to + * decode the parameters first to figure that out (not that this part is + * working yet). */ + t_cose_recipient_dec_hpke_init(&dec_recipient); + t_cose_recipient_dec_hpke_set_skr(&dec_recipient, + skR, + kid); + t_cose_encrypt_dec_add_recipient(&dec_ctx, (struct t_cose_recipient_dec *)&dec_recipient); + + result = t_cose_encrypt_dec(&dec_ctx, + cose_encrypted_message, /* in: the COSE_Encrypt message */ + aad, + decrypted_plaintext_buffer, + &decrypted_plain_text, + NULL); + + if (result != T_COSE_SUCCESS) { + return_value = 3000 + (int32_t)result; + goto Done; + } + + if(q_useful_buf_compare(decrypted_plain_text, payload)) { + return_value = 1; + goto Done; + } +Done: + return (int32_t)return_value; +} +#endif /* T_COSE_DISABLE_HPKE */ int32_t base_encrypt_decrypt_test(void) { @@ -429,7 +532,6 @@ int32_t decrypt_known_good_aeskw_non_aead_test(void) #endif /* !T_COSE_DISABLE_KEYWRAP */ - #include "init_keys.h" #ifndef T_COSE_USE_B_CON_SHA256 /* test crypto doesn't support ECDH */ @@ -534,6 +636,7 @@ esdh_enc_dec(int32_t curve, int32_t payload_cose_algorithm_id) } + int32_t esdh_enc_dec_test(void) { @@ -572,7 +675,6 @@ esdh_enc_dec_test(void) return 0; } - int32_t decrypt_known_good(void) { enum t_cose_err_t result; @@ -630,6 +732,163 @@ int32_t decrypt_known_good(void) +#ifndef T_COSE_DISABLE_HPKE + +#include "t_cose/t_cose_recipient_enc_hpke.h" +#include "t_cose/t_cose_recipient_dec_hpke.h" + +int32_t hpke_encrypt(struct t_cose_key pkR, + struct q_useful_buf_c payload, + struct q_useful_buf_c *cose_encrypted_message, + struct q_useful_buf *cose_encrypt_message_buffer, + int32_t alg) +{ + struct t_cose_encrypt_enc enc_ctx; + enum t_cose_err_t result; + struct t_cose_recipient_enc_hpke recipient; + + /* Initialize the encryption context telling it we want + * a COSE_Encrypt (not a COSE_Encrypt0) because we're doing HPKE with a + * COSE_Recpipient. Also tell it the AEAD algorithm for the + * body of the message. + */ + t_cose_encrypt_enc_init(&enc_ctx, + T_COSE_OPT_MESSAGE_TYPE_ENCRYPT, + T_COSE_ALGORITHM_A128GCM); + + /* Create the recipient object telling it the algorithm and the public key + * for the COSE_Recipient it's going to make. Then give that object + * to the main encryption context. (Only one recipient is set here, but + * there could be more) + */ + t_cose_recipient_enc_hpke_init(&recipient, + alg); + + t_cose_recipient_enc_hpke_set_key(&recipient, + pkR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + + t_cose_encrypt_add_recipient(&enc_ctx, + (struct t_cose_recipient_enc *)&recipient); + + /* Now do the actual encryption */ + result = t_cose_encrypt_enc(&enc_ctx, /* in: encryption context */ + payload, /* in: payload to encrypt */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + *cose_encrypt_message_buffer, /* in: buffer for COSE_Encrypt */ + cose_encrypted_message); /* out: COSE_Encrypt */ + + if(result != T_COSE_SUCCESS) { + return (int32_t)result + 2000; + } + + return 0; +} + +int32_t hpke_decrypt(struct q_useful_buf_c cose_encrypted_message, + struct q_useful_buf_c *plaintext_message, + struct q_useful_buf *plaintext_buffer, + struct t_cose_key skR) +{ + struct t_cose_recipient_dec_hpke dec_recipient; + struct t_cose_encrypt_dec_ctx dec_ctx; + enum t_cose_err_t result; + + /* Set up the decryption context, telling it what type of + * message to expect if there's no tag (that part isn't quite implemented right yet anyway). + */ + t_cose_encrypt_dec_init(&dec_ctx, T_COSE_OPT_MESSAGE_TYPE_ENCRYPT); + + + /* Set up the recipient object with the key material. We happen to know + * what the algorithm and key are in advance so we don't have to + * decode the parameters first to figure that out (not that this part is + * working yet). */ + t_cose_recipient_dec_hpke_init(&dec_recipient); + t_cose_recipient_dec_hpke_set_skr(&dec_recipient, + skR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + + t_cose_encrypt_dec_add_recipient(&dec_ctx, (struct t_cose_recipient_dec *)&dec_recipient); + + result = t_cose_encrypt_dec(&dec_ctx, + cose_encrypted_message, /* in: the COSE_Encrypt message */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + *plaintext_buffer, + plaintext_message, + NULL); + + if(result != T_COSE_SUCCESS) { + return (int32_t)result + 2000; + } + + return 0; +} + + +int32_t +hpke_enc_dec_test(void) +{ + int32_t ret; + /* Create a key pair. This is a fixed test key pair. The creation + * of this key pair is crypto-library dependent because t_cose_key + * is crypto-library dependent. See t_cose_key.h and the examples + * to understand key-pair creation better. */ + enum t_cose_err_t result; + struct t_cose_key skR; + struct t_cose_key pkR; + int alg = T_COSE_HPKE_Base_P256_SHA256_AES128GCM; + + Q_USEFUL_BUF_MAKE_STACK_UB ( cose_encrypt_message_buffer, 200); + struct q_useful_buf_c cose_encrypted_message; + size_t n; + struct q_useful_buf_c plaintext_message; + Q_USEFUL_BUF_MAKE_STACK_UB ( plaintext_buffer, 100); + + + /* Load public / private recipient key */ + result = init_fixed_test_ec_encryption_key(T_COSE_ELLIPTIC_CURVE_P_256, + &pkR, /* out: public key to be used for encryption */ + &skR); /* out: corresponding private key for decryption */ + if(result != T_COSE_SUCCESS) { + return -7; /* return some error value */ + } + + + ret = hpke_encrypt(pkR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(PAYLOAD), + &cose_encrypted_message, + &cose_encrypt_message_buffer, + alg); + + if (ret != 0) { + return (int32_t)result + 1000; + } + + ret = hpke_decrypt(cose_encrypted_message, + &plaintext_message, + &plaintext_buffer, + skR); + + if (ret != 0) { + return (int32_t)result + 2000; + } + + + if(q_useful_buf_compare(plaintext_message, Q_USEFUL_BUF_FROM_SZ_LITERAL(PAYLOAD))) { + return 3000; + } + + free_fixed_test_ec_encryption_key(skR); + free_fixed_test_ec_encryption_key(pkR); + + return 0; +} + +#endif /* T_COSE_DISABLE_HPKE */ + + + struct decrypt_test { const char *sz_description; struct q_useful_buf_c message; diff --git a/test/t_cose_encrypt_decrypt_test.h b/test/t_cose_encrypt_decrypt_test.h index 84df0d22..5ffb58ea 100644 --- a/test/t_cose_encrypt_decrypt_test.h +++ b/test/t_cose_encrypt_decrypt_test.h @@ -19,6 +19,10 @@ int32_t base_encrypt_decrypt_test(void); int32_t esdh_enc_dec_test(void); +#ifndef T_COSE_DISABLE_HPKE +int32_t hpke_enc_dec_test(void); +#endif /* T_COSE_DISABLE_HPKE */ + int32_t decrypt_known_good(void); int32_t decrypt_known_bad(void);