From 3712e9367c3d7be419300cdd941f534b2454378b Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Mon, 21 Oct 2024 08:52:40 +0000 Subject: [PATCH 01/19] cose edit start --- CMakeLists.txt | 6 ++++ cmake/crypto.cmake | 1 + include/ccf/crypto/cose.h | 10 +++++++ src/crypto/cose.cpp | 32 ++++++++++++++++++++++ tests/perf-system/submitter/CMakeLists.txt | 2 ++ 5 files changed, 51 insertions(+) create mode 100644 include/ccf/crypto/cose.h create mode 100644 src/crypto/cose.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 806d9102833a..6b81e005ceb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -864,6 +864,12 @@ if(BUILD_TESTS) ) target_link_libraries(base64_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) + add_unit_test( + cose_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/cose.cpp + ) + target_link_libraries(cose_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(cose_test PRIVATE qcbor.host) + add_unit_test(pem_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/pem.cpp) target_link_libraries(pem_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) diff --git a/cmake/crypto.cmake b/cmake/crypto.cmake index c5de9c2fc2d0..ef63e162f998 100644 --- a/cmake/crypto.cmake +++ b/cmake/crypto.cmake @@ -15,6 +15,7 @@ set(CCFCRYPTO_SRC ${CCF_DIR}/src/crypto/hmac.cpp ${CCF_DIR}/src/crypto/pem.cpp ${CCF_DIR}/src/crypto/ecdsa.cpp + ${CCF_DIR}/src/crypto/cose.cpp ${CCF_DIR}/src/crypto/openssl/symmetric_key.cpp ${CCF_DIR}/src/crypto/openssl/public_key.cpp ${CCF_DIR}/src/crypto/openssl/key_pair.cpp diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h new file mode 100644 index 000000000000..e9a12dcc1d4f --- /dev/null +++ b/include/ccf/crypto/cose.h @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. +#pragma once + +#include + +namespace ccf::cose::edit +{ + static void insert_receipt_in_uhdr(const std::span& buf_); +} \ No newline at end of file diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp new file mode 100644 index 000000000000..4a70c4e113f2 --- /dev/null +++ b/src/crypto/cose.cpp @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. + +#include + +#include +#include +#include + +#include "ccf/crypto/cose.h" + +namespace ccf::cose::edit +{ + // TODO: split COSE + // TODO: append to map, if key is not found + // TODO: extend array value in map, when key is found, or inject array otherwise + void insert_receipt_in_uhdr(const std::span& buf_) { + UsefulBufC buf{buf_.data(), buf_.size()}; + + QCBORError rc; + + QCBORDecodeContext ctx; + QCBORDecode_Init(&ctx, buf, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_EnterArray(&ctx, nullptr); + rc = QCBORDecode_GetError(&ctx); + if (rc != QCBOR_SUCCESS) + { + throw std::logic_error("Failed to parse COSE_Sign1 outer array"); + } + }; +} \ No newline at end of file diff --git a/tests/perf-system/submitter/CMakeLists.txt b/tests/perf-system/submitter/CMakeLists.txt index 45e9f9c45d45..eea0e855f602 100644 --- a/tests/perf-system/submitter/CMakeLists.txt +++ b/tests/perf-system/submitter/CMakeLists.txt @@ -19,6 +19,7 @@ set(CCFCRYPTO_SRC ${CCF_DIR}/src/crypto/key_wrap.cpp ${CCF_DIR}/src/crypto/hmac.cpp ${CCF_DIR}/src/crypto/pem.cpp + ${CCF_DIR}/src/crypto/cose.cpp ${CCF_DIR}/src/crypto/openssl/symmetric_key.cpp ${CCF_DIR}/src/crypto/openssl/public_key.cpp ${CCF_DIR}/src/crypto/openssl/key_pair.cpp @@ -32,6 +33,7 @@ set(CCFCRYPTO_SRC add_library(stdcxxccfcrypto.host STATIC "${CCFCRYPTO_SRC}") target_link_libraries(stdcxxccfcrypto.host PUBLIC crypto) target_link_libraries(stdcxxccfcrypto.host PUBLIC ssl) +target_link_libraries(stdcxxccfcrypto.host PUBLIC qcbor.host) target_link_libraries( submit PRIVATE stdcxxhttp_parser.host stdcxxccfcrypto.host arrow parquet From 55fa57e35b4a0ac62a87e8ca1145d719b2d21b41 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Mon, 21 Oct 2024 16:30:59 +0000 Subject: [PATCH 02/19] / --- CMakeLists.txt | 1 + include/ccf/crypto/cose.h | 6 ++- src/crypto/cose.cpp | 89 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b81e005ceb2..74a16af676b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -868,6 +868,7 @@ if(BUILD_TESTS) cose_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/cose.cpp ) target_link_libraries(cose_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(cose_test PRIVATE ccfcrypto.host) target_link_libraries(cose_test PRIVATE qcbor.host) add_unit_test(pem_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/pem.cpp) diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h index e9a12dcc1d4f..49d546390d9f 100644 --- a/include/ccf/crypto/cose.h +++ b/include/ccf/crypto/cose.h @@ -3,8 +3,12 @@ #pragma once #include +#include namespace ccf::cose::edit { - static void insert_receipt_in_uhdr(const std::span& buf_); + std::vector insert_map_in_uhdr( + const std::span& buf_, + size_t key, + const std::vector value); } \ No newline at end of file diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp index 4a70c4e113f2..c0fdfde7e441 100644 --- a/src/crypto/cose.cpp +++ b/src/crypto/cose.cpp @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 License. #include +#include #include #include @@ -11,14 +12,13 @@ namespace ccf::cose::edit { - // TODO: split COSE - // TODO: append to map, if key is not found - // TODO: extend array value in map, when key is found, or inject array otherwise - void insert_receipt_in_uhdr(const std::span& buf_) { + std::vector insert_map_in_uhdr( + const std::span& buf_, + size_t key, + const std::vector value) { UsefulBufC buf{buf_.data(), buf_.size()}; QCBORError rc; - QCBORDecodeContext ctx; QCBORDecode_Init(&ctx, buf, QCBOR_DECODE_MODE_NORMAL); @@ -28,5 +28,84 @@ namespace ccf::cose::edit { throw std::logic_error("Failed to parse COSE_Sign1 outer array"); } + + auto tag = QCBORDecode_GetNthTagOfLast(&ctx, 0); + if (tag != CBOR_TAG_COSE_SIGN1) + { + throw std::logic_error("Failed to parse COSE_Sign1 tag"); + } + + QCBORItem item; + auto err = QCBORDecode_GetNext(&ctx, &item); + if (err != QCBOR_SUCCESS || item.uDataType != QCBOR_TYPE_BYTE_STRING) + { + throw std::logic_error("Failed to parse COSE_Sign1 protected header as bstr"); + } + std::span phdr = {(uint8_t *) item.val.string.ptr, item.val.string.len}; + + // Skip unprotected header + QCBORDecode_VGetNextConsume(&ctx, &item); + + std::optional> payload = std::nullopt; + // Payload + err = QCBORDecode_GetNext(&ctx, &item); + if (err != QCBOR_SUCCESS) + { + throw std::logic_error("Failed to parse COSE_Sign1 payload"); + } + if (item.uDataType == QCBOR_TYPE_BYTE_STRING) + { + payload = {(uint8_t *) item.val.string.ptr, item.val.string.len}; + } + else if (item.uDataType == QCBOR_TYPE_NULL) + { + // No payload + } + else + { + throw std::logic_error("Invalid COSE_Sign1 payload"); + } + // TODO: QCBORDecode_PartialFinish() before and after should allow constructing a span + // of the encoded payload, which can perhaps then be passed to QCBOREncode_AddEncoded + + // Signature + err = QCBORDecode_GetNext(&ctx, &item); + if (err != QCBOR_SUCCESS && item.uDataType != QCBOR_TYPE_BYTE_STRING) + { + throw std::logic_error("Failed to parse COSE_Sign1 signature"); + } + std::span signature = {(uint8_t *) item.val.string.ptr, item.val.string.len}; + + QCBORDecode_ExitArray(&ctx); + err = QCBORDecode_Finish(&ctx); + if (err != QCBOR_SUCCESS) + { + throw std::logic_error("Failed to parse COSE_Sign1"); + } + + std::vector output(4096); // TBD: work out size + UsefulBuf output_buf{output.data(), output.size()}; + + QCBOREncodeContext ectx; + QCBOREncode_Init(&ectx, output_buf); + QCBOREncode_AddTag(&ectx, CBOR_TAG_COSE_SIGN1); + QCBOREncode_OpenArray(&ectx); + // protected headers + QCBOREncode_AddBytes(&ectx, {phdr.data(), phdr.size()}); + // unprotected headers + QCBOREncode_OpenMap(&ectx); + QCBOREncode_AddBytesToMapN(&ectx, key, {value.data(), value.size()}); + QCBOREncode_CloseMap(&ectx); + QCBOREncode_CloseArray(&ectx); + + UsefulBufC cose; + err = QCBOREncode_Finish(&ectx, &cose); + if (err != QCBOR_SUCCESS) + { + throw std::logic_error("Failed to encode COSE_Sign1"); + } + output.resize(cose.len); + output.shrink_to_fit(); + return output; }; } \ No newline at end of file From 629220804ad57f6f702d9b83448d729ffa5f220c Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Tue, 22 Oct 2024 09:22:32 +0000 Subject: [PATCH 03/19] Insert single key at map --- include/ccf/crypto/cose.h | 8 +- src/crypto/cose.cpp | 192 ++++++++++++++++++++------------------ 2 files changed, 105 insertions(+), 95 deletions(-) diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h index 49d546390d9f..7fd7293736fd 100644 --- a/include/ccf/crypto/cose.h +++ b/include/ccf/crypto/cose.h @@ -7,8 +7,8 @@ namespace ccf::cose::edit { - std::vector insert_map_in_uhdr( - const std::span& buf_, - size_t key, - const std::vector value); + std::vector insert_at_key_in_uhdr( + const std::span& buf_, + size_t key, + const std::vector value); } \ No newline at end of file diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp index c0fdfde7e441..2497f3b3b903 100644 --- a/src/crypto/cose.cpp +++ b/src/crypto/cose.cpp @@ -1,111 +1,121 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the Apache 2.0 License. -#include -#include +#include "ccf/crypto/cose.h" -#include +#include #include +#include #include - -#include "ccf/crypto/cose.h" +#include namespace ccf::cose::edit { - std::vector insert_map_in_uhdr( - const std::span& buf_, - size_t key, - const std::vector value) { - UsefulBufC buf{buf_.data(), buf_.size()}; + std::vector insert_at_key_in_uhdr( + const std::span& buf_, + size_t key, + const std::vector value) + { + UsefulBufC buf{buf_.data(), buf_.size()}; + + QCBORError rc; + QCBORDecodeContext ctx; + QCBORDecode_Init(&ctx, buf, QCBOR_DECODE_MODE_NORMAL); - QCBORError rc; - QCBORDecodeContext ctx; - QCBORDecode_Init(&ctx, buf, QCBOR_DECODE_MODE_NORMAL); + QCBORDecode_EnterArray(&ctx, nullptr); + rc = QCBORDecode_GetError(&ctx); + if (rc != QCBOR_SUCCESS) + { + throw std::logic_error("Failed to parse COSE_Sign1 outer array"); + } - QCBORDecode_EnterArray(&ctx, nullptr); - rc = QCBORDecode_GetError(&ctx); - if (rc != QCBOR_SUCCESS) - { - throw std::logic_error("Failed to parse COSE_Sign1 outer array"); - } + auto tag = QCBORDecode_GetNthTagOfLast(&ctx, 0); + if (tag != CBOR_TAG_COSE_SIGN1) + { + throw std::logic_error("Failed to parse COSE_Sign1 tag"); + } - auto tag = QCBORDecode_GetNthTagOfLast(&ctx, 0); - if (tag != CBOR_TAG_COSE_SIGN1) - { - throw std::logic_error("Failed to parse COSE_Sign1 tag"); - } + QCBORItem item; + auto err = QCBORDecode_GetNext(&ctx, &item); + if (err != QCBOR_SUCCESS || item.uDataType != QCBOR_TYPE_BYTE_STRING) + { + throw std::logic_error( + "Failed to parse COSE_Sign1 protected header as bstr"); + } + std::span phdr = { + (uint8_t*)item.val.string.ptr, item.val.string.len}; - QCBORItem item; - auto err = QCBORDecode_GetNext(&ctx, &item); - if (err != QCBOR_SUCCESS || item.uDataType != QCBOR_TYPE_BYTE_STRING) - { - throw std::logic_error("Failed to parse COSE_Sign1 protected header as bstr"); - } - std::span phdr = {(uint8_t *) item.val.string.ptr, item.val.string.len}; + // Skip unprotected header for now + QCBORDecode_VGetNextConsume(&ctx, &item); - // Skip unprotected header - QCBORDecode_VGetNextConsume(&ctx, &item); + std::optional> payload = std::nullopt; + err = QCBORDecode_GetNext(&ctx, &item); + if (err != QCBOR_SUCCESS) + { + throw std::logic_error("Failed to parse COSE_Sign1 payload"); + } + if (item.uDataType == QCBOR_TYPE_BYTE_STRING) + { + payload = {(uint8_t*)item.val.string.ptr, item.val.string.len}; + } + else if (item.uDataType == QCBOR_TYPE_NULL) + { + // No payload + } + else + { + throw std::logic_error("Invalid COSE_Sign1 payload"); + } + // QCBORDecode_PartialFinish() before and after should allow constructing a + // span of the encoded payload, which can perhaps then be passed to + // QCBOREncode_AddEncoded and would allow blindly copying the payload + // without parsing it. - std::optional> payload = std::nullopt; - // Payload - err = QCBORDecode_GetNext(&ctx, &item); - if (err != QCBOR_SUCCESS) - { - throw std::logic_error("Failed to parse COSE_Sign1 payload"); - } - if (item.uDataType == QCBOR_TYPE_BYTE_STRING) - { - payload = {(uint8_t *) item.val.string.ptr, item.val.string.len}; - } - else if (item.uDataType == QCBOR_TYPE_NULL) - { - // No payload - } - else - { - throw std::logic_error("Invalid COSE_Sign1 payload"); - } - // TODO: QCBORDecode_PartialFinish() before and after should allow constructing a span - // of the encoded payload, which can perhaps then be passed to QCBOREncode_AddEncoded + err = QCBORDecode_GetNext(&ctx, &item); + if (err != QCBOR_SUCCESS && item.uDataType != QCBOR_TYPE_BYTE_STRING) + { + throw std::logic_error("Failed to parse COSE_Sign1 signature"); + } + std::span signature = { + (uint8_t*)item.val.string.ptr, item.val.string.len}; - // Signature - err = QCBORDecode_GetNext(&ctx, &item); - if (err != QCBOR_SUCCESS && item.uDataType != QCBOR_TYPE_BYTE_STRING) - { - throw std::logic_error("Failed to parse COSE_Sign1 signature"); - } - std::span signature = {(uint8_t *) item.val.string.ptr, item.val.string.len}; + QCBORDecode_ExitArray(&ctx); + err = QCBORDecode_Finish(&ctx); + if (err != QCBOR_SUCCESS) + { + throw std::logic_error("Failed to parse COSE_Sign1"); + } - QCBORDecode_ExitArray(&ctx); - err = QCBORDecode_Finish(&ctx); - if (err != QCBOR_SUCCESS) - { - throw std::logic_error("Failed to parse COSE_Sign1"); - } - - std::vector output(4096); // TBD: work out size - UsefulBuf output_buf{output.data(), output.size()}; + std::vector output(buf_.size() + value.size() + 1024 /* too much, should be encoded key size + potential varint bump in sizes */); + UsefulBuf output_buf{output.data(), output.size()}; - QCBOREncodeContext ectx; - QCBOREncode_Init(&ectx, output_buf); - QCBOREncode_AddTag(&ectx, CBOR_TAG_COSE_SIGN1); - QCBOREncode_OpenArray(&ectx); - // protected headers - QCBOREncode_AddBytes(&ectx, {phdr.data(), phdr.size()}); - // unprotected headers - QCBOREncode_OpenMap(&ectx); - QCBOREncode_AddBytesToMapN(&ectx, key, {value.data(), value.size()}); - QCBOREncode_CloseMap(&ectx); - QCBOREncode_CloseArray(&ectx); + QCBOREncodeContext ectx; + QCBOREncode_Init(&ectx, output_buf); + QCBOREncode_AddTag(&ectx, CBOR_TAG_COSE_SIGN1); + QCBOREncode_OpenArray(&ectx); + QCBOREncode_AddBytes(&ectx, {phdr.data(), phdr.size()}); + QCBOREncode_OpenMap(&ectx); + QCBOREncode_AddBytesToMapN(&ectx, key, {value.data(), value.size()}); + QCBOREncode_CloseMap(&ectx); + if (payload.has_value()) + { + QCBOREncode_AddBytes(&ectx, {payload->data(), payload->size()}); + } + else + { + QCBOREncode_AddSimple(&ectx, CBOR_SIMPLEV_NULL); + } + QCBOREncode_AddBytes(&ectx, {signature.data(), signature.size()}); + QCBOREncode_CloseArray(&ectx); - UsefulBufC cose; - err = QCBOREncode_Finish(&ectx, &cose); - if (err != QCBOR_SUCCESS) - { - throw std::logic_error("Failed to encode COSE_Sign1"); - } - output.resize(cose.len); - output.shrink_to_fit(); - return output; - }; + UsefulBufC cose; + err = QCBOREncode_Finish(&ectx, &cose); + if (err != QCBOR_SUCCESS) + { + throw std::logic_error("Failed to encode COSE_Sign1"); + } + output.resize(cose.len); + output.shrink_to_fit(); + return output; + }; } \ No newline at end of file From a428e4deff4e29f7f9ba5fee48c971bdb03b12db Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Tue, 22 Oct 2024 09:50:11 +0000 Subject: [PATCH 04/19] test --- include/ccf/crypto/cose.h | 1 + src/crypto/test/cose.cpp | 73 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/crypto/test/cose.cpp diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h index 7fd7293736fd..4f1d9ccd5b3a 100644 --- a/include/ccf/crypto/cose.h +++ b/include/ccf/crypto/cose.h @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 License. #pragma once +#include #include #include diff --git a/src/crypto/test/cose.cpp b/src/crypto/test/cose.cpp new file mode 100644 index 000000000000..9c399a1f794a --- /dev/null +++ b/src/crypto/test/cose.cpp @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "ccf/crypto/cose.h" + +#include "crypto/openssl/cose_verifier.h" + +#include +#include +#include +#include +#include + +static const ccf::crypto::Pem cose_sign1_sample0_cert = ccf::crypto::Pem( + "-----BEGIN PUBLIC KEY-----\n" + "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEqv7c4eTwwUiRZ8F6b1QrcNiiSZrNc7Kj\n" + "UM4ZZO8VzwMwQYN6kcJ9lv5rqemlr/ViQ4pZ3/XfocrnEiQQX1dJ26c7aaLnDioi\n" + "0jRn/N6gVUxWzLXupEQDg7XNrb116oCj\n" + "-----END PUBLIC KEY-----\n"); + +static const std::vector cose_sign1_sample0 = { + 210, 132, 88, 125, 164, 1, 56, 34, 4, 88, 64, 97, 102, 57, 51, + 56, 98, 54, 57, 52, 101, 100, 102, 56, 51, 53, 54, 54, 57, 51, + 51, 100, 99, 50, 102, 54, 99, 100, 50, 56, 56, 56, 97, 51, 54, + 50, 98, 53, 53, 52, 49, 56, 102, 53, 100, 50, 101, 102, 100, 102, + 98, 49, 97, 57, 56, 51, 99, 56, 98, 51, 98, 54, 56, 49, 53, + 112, 99, 99, 102, 46, 103, 111, 118, 46, 109, 115, 103, 46, 116, 121, + 112, 101, 104, 112, 114, 111, 112, 111, 115, 97, 108, 118, 99, 99, 102, + 46, 103, 111, 118, 46, 109, 115, 103, 46, 99, 114, 101, 97, 116, 101, + 100, 95, 97, 116, 26, 103, 23, 114, 91, 160, 71, 112, 97, 121, 108, + 111, 97, 100, 88, 96, 181, 64, 150, 14, 237, 176, 247, 51, 37, 225, + 53, 220, 166, 180, 86, 57, 75, 24, 61, 133, 55, 59, 122, 30, 23, + 181, 189, 58, 8, 42, 162, 165, 69, 232, 145, 219, 29, 120, 107, 241, + 214, 144, 78, 125, 192, 179, 246, 102, 52, 30, 98, 127, 64, 83, 0, + 71, 61, 219, 170, 226, 134, 51, 140, 28, 36, 223, 249, 61, 113, 7, + 181, 126, 27, 133, 84, 7, 158, 114, 113, 115, 171, 215, 57, 233, 166, + 198, 159, 243, 140, 255, 152, 255, 2, 3, 126, 18}; +// Function to dump bytes to a file +void dump_bytes_to_file( + const std::vector& bytes, const std::string& filename) +{ + std::ofstream file(filename, std::ios::binary); + if (!file) + { + throw std::ios_base::failure("Failed to open file for writing"); + } + file.write(reinterpret_cast(bytes.data()), bytes.size()); + file.close(); +} + +TEST_CASE( + "Check insertion at key in unprotected header does not affect verification") +{ + auto verifier = + ccf::crypto::make_cose_verifier_from_key(cose_sign1_sample0_cert); + std::span payload; + + // Verify the original COSE_Sign1 + REQUIRE(verifier->verify(cose_sign1_sample0, payload)); + REQUIRE(std::string(payload.begin(), payload.end()) == "payload"); + + size_t key = 42; + std::vector value = {1, 2, 3, 4}; + auto enriched_cose_sign1 = + ccf::cose::edit::insert_at_key_in_uhdr(cose_sign1_sample0, key, value); + + // Debug + dump_bytes_to_file(enriched_cose_sign1, "enriched.bin"); + + REQUIRE(verifier->verify(enriched_cose_sign1, payload)); + REQUIRE( + enriched_cose_sign1.size() > cose_sign1_sample0.size() + value.size()); +} \ No newline at end of file From 58ffeb0822b11ba0ff0b12f2658126293168d217 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Tue, 22 Oct 2024 16:51:21 +0000 Subject: [PATCH 05/19] receipts --- cddl/ccf-receipt.cddl | 50 +++++++++++++++++++++++ include/ccf/crypto/cose.h | 1 + samples/apps/logging/logging.cpp | 45 +++++++++++++++++++++ src/crypto/cose.cpp | 9 ++++- src/crypto/test/cose.cpp | 5 ++- src/node/historical_queries_adapter.cpp | 7 ++-- src/node/test/historical_queries.cpp | 2 +- tests/e2e_logging.py | 53 +++++++++++++++++++++++++ 8 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 cddl/ccf-receipt.cddl diff --git a/cddl/ccf-receipt.cddl b/cddl/ccf-receipt.cddl new file mode 100644 index 000000000000..c943a09dfec8 --- /dev/null +++ b/cddl/ccf-receipt.cddl @@ -0,0 +1,50 @@ +ccf-cose-root-signature-tagged = #6.18(ccf-cose-root-signature) + +ccf-cose-root-signature = [ + phdr : bstr .cbor protected-headers, ; bstr-wrapped protected headers + uhdr : unprotected-headers, ; unwrappeed (plain map) unprotected headers + payload : nil, ; signed Merkle tree root hash, *detached* payload + signature : bstr ; COSE-signature +] + +unprotected-headers = { + &(vdp: 396) => verifiable-proofs +} + +inclusion-proofs = [ + bstr .cbor ccf-inclusion-proof ] + +verifiable-proofs = { + &(inclusion-proof: -1) => inclusion-proofs +} + +protected-headers = { + &(alg: 1) => int, ; signing algoritm ID, as per RFC8152 + &(kid: 4) => bstr, ; signing key hash + &(cwt: 15) => cwt-map, ; CWT claims, as per RFC8392 + &(vds: 395) => int, ; verifiable data structure, as per COSE Receipts (draft) RFC (https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/) + "ccf.v1" => ccf-map ; a set of CCF-specific parameters +} + +cwt-map = { + &(iat: 6) => int ; "issued at", number of seconds since the epoch +} + +ccf-map = { + &(last-signed-txid: "txid") => tstr ; last committed transaction ID this COSE-signature signs +} + +ccf-inclusion-proof = { + &(leaf: 1) => ccf-leaf + &(path: 2) => [+ ccf-proof-element] +} + +ccf-leaf = [ + internal-transaction-hash: bstr .size 32 ; a string of HASH_SIZE(32) bytes + internal-evidence: tstr .size (1..1024) ; a string of at most 1024 bytes + data-hash: bstr .size 32 ; a string of HASH_SIZE(32) bytes +] + +ccf-proof-element = [ + left: bool ; position of the element + hash: bstr .size 32 ; hash of the proof element (string of HASH_SIZE(32) bytes) +] diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h index 4f1d9ccd5b3a..0ee380c559fe 100644 --- a/include/ccf/crypto/cose.h +++ b/include/ccf/crypto/cose.h @@ -11,5 +11,6 @@ namespace ccf::cose::edit std::vector insert_at_key_in_uhdr( const std::span& buf_, size_t key, + size_t subkey, const std::vector value); } \ No newline at end of file diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index f6bb2ca40423..4d9ced52e2b6 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -7,6 +7,7 @@ // CCF #include "ccf/app_interface.h" #include "ccf/common_auth_policies.h" +#include "ccf/crypto/cose.h" #include "ccf/crypto/verifier.h" #include "ccf/ds/hash.h" #include "ccf/endpoints/authentication/all_of_auth.h" @@ -2038,6 +2039,50 @@ namespace loggingapp .set_auto_schema() .set_forwarding_required(ccf::endpoints::ForwardingRequired::Never) .install(); + + auto get_cose_receipt = [this]( + ccf::endpoints::ReadOnlyEndpointContext& ctx, + ccf::historical::StatePtr historical_state) { + auto historical_tx = historical_state->store->create_read_only_tx(); + + assert(historical_state->receipt); + auto signature = describe_cose_signature_v1(*historical_state->receipt); + if (!signature.has_value()) + { + ctx.rpc_ctx->set_error( + HTTP_STATUS_NOT_FOUND, + ccf::errors::ResourceNotFound, + "No COSE signature available for this transaction"); + return; + } + auto proof = describe_merkle_proof_v1(*historical_state->receipt); + if (!proof.has_value()) + { + ctx.rpc_ctx->set_error( + HTTP_STATUS_NOT_FOUND, + ccf::errors::ResourceNotFound, + "No merkle proof available for this transaction"); + return; + } + + auto cose_receipt = + ccf::cose::edit::insert_at_key_in_uhdr(*signature, 396, -1, *proof); + + ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK); + ctx.rpc_ctx->set_response_header( + ccf::http::headers::CONTENT_TYPE, + ccf::http::headervalues::contenttype::COSE); + ctx.rpc_ctx->set_response_body(cose_receipt); + }; + make_read_only_endpoint( + "/log/public/cose_receipt", + HTTP_GET, + ccf::historical::read_only_adapter_v4( + get_cose_receipt, context, is_tx_committed), + auth_policies) + .set_auto_schema() + .set_forwarding_required(ccf::endpoints::ForwardingRequired::Never) + .install(); } }; } diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp index 2497f3b3b903..18eef8adc887 100644 --- a/src/crypto/cose.cpp +++ b/src/crypto/cose.cpp @@ -14,6 +14,7 @@ namespace ccf::cose::edit std::vector insert_at_key_in_uhdr( const std::span& buf_, size_t key, + size_t subkey, const std::vector value) { UsefulBufC buf{buf_.data(), buf_.size()}; @@ -95,7 +96,13 @@ namespace ccf::cose::edit QCBOREncode_OpenArray(&ectx); QCBOREncode_AddBytes(&ectx, {phdr.data(), phdr.size()}); QCBOREncode_OpenMap(&ectx); - QCBOREncode_AddBytesToMapN(&ectx, key, {value.data(), value.size()}); + QCBOREncode_OpenMapInMapN(&ectx, key); + + QCBOREncode_OpenArrayInMapN(&ectx, subkey); + QCBOREncode_AddBytes(&ectx, {value.data(), value.size()}); + QCBOREncode_CloseArray(&ectx); + QCBOREncode_CloseMap(&ectx); + QCBOREncode_CloseMap(&ectx); if (payload.has_value()) { diff --git a/src/crypto/test/cose.cpp b/src/crypto/test/cose.cpp index 9c399a1f794a..67068bb54630 100644 --- a/src/crypto/test/cose.cpp +++ b/src/crypto/test/cose.cpp @@ -60,9 +60,10 @@ TEST_CASE( REQUIRE(std::string(payload.begin(), payload.end()) == "payload"); size_t key = 42; + size_t subkey = 43; std::vector value = {1, 2, 3, 4}; - auto enriched_cose_sign1 = - ccf::cose::edit::insert_at_key_in_uhdr(cose_sign1_sample0, key, value); + auto enriched_cose_sign1 = ccf::cose::edit::insert_at_key_in_uhdr( + cose_sign1_sample0, key, subkey, value); // Debug dump_bytes_to_file(enriched_cose_sign1, "enriched.bin"); diff --git a/src/node/historical_queries_adapter.cpp b/src/node/historical_queries_adapter.cpp index e0dda512c2f8..3a932bde61af 100644 --- a/src/node/historical_queries_adapter.cpp +++ b/src/node/historical_queries_adapter.cpp @@ -202,9 +202,10 @@ namespace ccf std::optional> describe_merkle_proof_v1( const TxReceiptImpl& receipt) { - constexpr size_t buf_size = 2048; + constexpr size_t buf_size = 2048; // TBD: calculate why this is enough std::vector underlying_buffer(buf_size); - q_useful_buf buffer{underlying_buffer.data(), buf_size}; + UsefulBuf buffer{underlying_buffer.data(), underlying_buffer.size()}; + assert(buffer.len == buf_size); QCBOREncodeContext ctx; QCBOREncode_Init(&ctx, buffer); @@ -232,7 +233,7 @@ namespace ccf QCBOREncode_CloseMap(&ctx); - struct q_useful_buf_c result; + UsefulBufC result; auto qerr = QCBOREncode_Finish(&ctx, &result); if (qerr) { diff --git a/src/node/test/historical_queries.cpp b/src/node/test/historical_queries.cpp index 5be0c1e14725..ffe1d825cc9f 100644 --- a/src/node/test/historical_queries.cpp +++ b/src/node/test/historical_queries.cpp @@ -1978,7 +1978,7 @@ TEST_CASE("Valid merkle proof from receipts") REQUIRE_EQ( ccf::ds::to_hex(decoded.claims_digest), historical_state->receipt->claims_digest.value() - .hex_str()); // HEX as workaround emmpy claims (set flag). + .hex_str()); // HEX as workaround empty claims (set flag). auto it = decoded.path.begin(); for (const auto& node : *historical_state->receipt->path) diff --git a/tests/e2e_logging.py b/tests/e2e_logging.py index a5918066c6f0..ce9918e38ad6 100644 --- a/tests/e2e_logging.py +++ b/tests/e2e_logging.py @@ -1011,6 +1011,58 @@ def test_cose_signature_schema(network, args): return network +@reqs.description("Check COSE receipt CDDL schema") +def test_cose_receipt_schema(network, args): + primary, _ = network.find_nodes() + + with primary.client("user0") as client: + r = client.get("/commit") + assert r.status_code == http.HTTPStatus.OK + last_txid = TxID.from_str(r.body.json()["transaction_id"]) + + for seqno in range(last_txid.seqno, last_txid.seqno - 10, -1): + txid = f"{last_txid.view}.{seqno}" + LOG.debug(f"Trying to get COSE receipt for txid {txid}") + max_retries = 10 + found_proof = False + for _ in range(max_retries): + r = client.get( + "/log/public/cose_receipt", + headers={infra.clients.CCF_TX_ID_HEADER: txid}, + log_capture=[], # Do not emit raw binary to stdout + ) + if r.status_code == http.HTTPStatus.OK: + cbor_proof = r.body.data() + cbor_proof_filename = os.path.join( + network.common_dir, f"receipt_{txid}.cose" + ) + with open(cbor_proof_filename, "wb") as f: + f.write(cbor_proof) + subprocess.run( + ["cddl", "../cddl/ccf-receipt.cddl", "v", cbor_proof_filename], + check=True, + ) + found_proof = True + LOG.debug(f"Checked COSE receipt for txid {txid}") + break + elif r.status_code == http.HTTPStatus.ACCEPTED: + LOG.debug(f"Transaction {txid} accepted, retrying") + time.sleep(0.1) + elif r.status_code == http.HTTPStatus.NOT_FOUND: + LOG.debug(f"Transaction {txid} is a signature") + break + else: + assert ( + False + ), f"Failed to get receipt for txid {txid} after {max_retries} retries" + if found_proof: + break + else: + assert False, "Failed to find a non-signature in the last 10 transactions" + + return network + + @reqs.description("Read range of historical state") @reqs.supports_methods("/app/log/public", "/app/log/public/historical/range") def test_historical_query_range(network, args): @@ -2194,6 +2246,7 @@ def run_main_tests(network, args): if args.package == "samples/apps/logging/liblogging": test_cbor_merkle_proof(network, args) test_cose_signature_schema(network, args) + test_cose_receipt_schema(network, args) # HTTP2 doesn't support forwarding if not args.http2: From 65492b89f374579b340c23766c4a089955583d0c Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Tue, 22 Oct 2024 17:42:01 +0000 Subject: [PATCH 06/19] schema --- doc/schemas/app_openapi.json | 26 +++++++++++++++++++++++++- samples/apps/logging/logging.cpp | 2 +- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/doc/schemas/app_openapi.json b/doc/schemas/app_openapi.json index b225066f1fc0..0b6561c9e9c0 100644 --- a/doc/schemas/app_openapi.json +++ b/doc/schemas/app_openapi.json @@ -295,7 +295,7 @@ "info": { "description": "This CCF sample app implements a simple logging application, securely recording messages at client-specified IDs. It demonstrates most of the features available to CCF apps.", "title": "CCF Sample Logging App", - "version": "2.4.3" + "version": "2.5.0" }, "openapi": "3.0.0", "paths": { @@ -1273,6 +1273,30 @@ } } }, + "/app/log/public/cose_receipt": { + "get": { + "operationId": "GetAppLogPublicCoseReceipt", + "responses": { + "204": { + "description": "Default response description" + }, + "default": { + "$ref": "#/components/responses/default" + } + }, + "security": [ + { + "jwt": [] + }, + { + "user_cose_sign1": [] + } + ], + "x-ccf-forwarding": { + "$ref": "#/components/x-ccf-forwarding/never" + } + } + }, "/app/log/public/cose_signature": { "get": { "operationId": "GetAppLogPublicCoseSignature", diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index 4d9ced52e2b6..1413ca2f61d5 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -459,7 +459,7 @@ namespace loggingapp "recording messages at client-specified IDs. It demonstrates most of " "the features available to CCF apps."; - openapi_info.document_version = "2.4.3"; + openapi_info.document_version = "2.5.0"; index_per_public_key = std::make_shared( PUBLIC_RECORDS, context, 10000, 20); From 30cf22002bf54cf585dce77edccc154d4496a8b5 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Wed, 23 Oct 2024 09:06:51 +0000 Subject: [PATCH 07/19] Add array editing for signed statements --- include/ccf/crypto/cose.h | 20 +++++++++++++++++--- samples/apps/logging/logging.cpp | 7 +++++-- src/crypto/cose.cpp | 30 ++++++++++++++++++++++-------- src/crypto/test/cose.cpp | 6 +++--- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h index 0ee380c559fe..e52d0e95b713 100644 --- a/include/ccf/crypto/cose.h +++ b/include/ccf/crypto/cose.h @@ -4,13 +4,27 @@ #include #include +#include #include namespace ccf::cose::edit { - std::vector insert_at_key_in_uhdr( + namespace op + { + struct Append + {}; + + struct SetAtKey + { + ssize_t key; + }; + + using Type = std::variant; + } + + std::vector insert_in_uhdr( const std::span& buf_, - size_t key, - size_t subkey, + ssize_t key, + op::Type op, const std::vector value); } \ No newline at end of file diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index 1413ca2f61d5..72d2ceb809a0 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -2065,8 +2065,11 @@ namespace loggingapp return; } - auto cose_receipt = - ccf::cose::edit::insert_at_key_in_uhdr(*signature, 396, -1, *proof); + size_t vdp = 396; + auto inclusion_proof = ccf::cose::edit::op::SetAtKey{-1}; + + auto cose_receipt = ccf::cose::edit::insert_in_uhdr( + *signature, vdp, inclusion_proof, *proof); ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK); ctx.rpc_ctx->set_response_header( diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp index 18eef8adc887..05bcd0495c12 100644 --- a/src/crypto/cose.cpp +++ b/src/crypto/cose.cpp @@ -11,10 +11,10 @@ namespace ccf::cose::edit { - std::vector insert_at_key_in_uhdr( + std::vector insert_in_uhdr( const std::span& buf_, - size_t key, - size_t subkey, + ssize_t key, + op::Type op, const std::vector value) { UsefulBufC buf{buf_.data(), buf_.size()}; @@ -96,12 +96,26 @@ namespace ccf::cose::edit QCBOREncode_OpenArray(&ectx); QCBOREncode_AddBytes(&ectx, {phdr.data(), phdr.size()}); QCBOREncode_OpenMap(&ectx); - QCBOREncode_OpenMapInMapN(&ectx, key); - QCBOREncode_OpenArrayInMapN(&ectx, subkey); - QCBOREncode_AddBytes(&ectx, {value.data(), value.size()}); - QCBOREncode_CloseArray(&ectx); - QCBOREncode_CloseMap(&ectx); + if (std::holds_alternative(op)) + { + QCBOREncode_OpenArrayInMapN(&ectx, key); + QCBOREncode_AddBytes(&ectx, {value.data(), value.size()}); + QCBOREncode_CloseArray(&ectx); + } + else if (std::holds_alternative(op)) + { + QCBOREncode_OpenMapInMapN(&ectx, key); + auto subkey = std::get(op).key; + QCBOREncode_OpenArrayInMapN(&ectx, subkey); + QCBOREncode_AddBytes(&ectx, {value.data(), value.size()}); + QCBOREncode_CloseArray(&ectx); + QCBOREncode_CloseMap(&ectx); + } + else + { + throw std::logic_error("Invalid COSE_Sign1 edit operation"); + } QCBOREncode_CloseMap(&ectx); if (payload.has_value()) diff --git a/src/crypto/test/cose.cpp b/src/crypto/test/cose.cpp index 67068bb54630..5c8a23fa341a 100644 --- a/src/crypto/test/cose.cpp +++ b/src/crypto/test/cose.cpp @@ -60,10 +60,10 @@ TEST_CASE( REQUIRE(std::string(payload.begin(), payload.end()) == "payload"); size_t key = 42; - size_t subkey = 43; + auto subkey = ccf::cose::edit::op::SetAtKey{43}; std::vector value = {1, 2, 3, 4}; - auto enriched_cose_sign1 = ccf::cose::edit::insert_at_key_in_uhdr( - cose_sign1_sample0, key, subkey, value); + auto enriched_cose_sign1 = + ccf::cose::edit::insert_in_uhdr(cose_sign1_sample0, key, subkey, value); // Debug dump_bytes_to_file(enriched_cose_sign1, "enriched.bin"); From e99a1ac9479ebda61ee1ad2a9c16436351d52b65 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 08:39:13 +0000 Subject: [PATCH 08/19] more generic value handling --- include/ccf/crypto/cose.h | 21 ++++++++--- samples/apps/logging/logging.cpp | 4 +- src/crypto/cose.cpp | 65 +++++++++++++------------------- src/crypto/test/cose.cpp | 6 +-- 4 files changed, 47 insertions(+), 49 deletions(-) diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h index e52d0e95b713..023e27b053a6 100644 --- a/include/ccf/crypto/cose.h +++ b/include/ccf/crypto/cose.h @@ -9,22 +9,31 @@ namespace ccf::cose::edit { - namespace op + namespace pos { - struct Append + struct InArray {}; - struct SetAtKey + struct AtKey { ssize_t key; }; - using Type = std::variant; + using Type = std::variant; } - std::vector insert_in_uhdr( + /** + * Set the unprotected header of a COSE_Sign1 message, to a map containing + * \key and depending on the value of \position, either an array containing + * \value, or a map with key \subkey and value \value. + * + * Useful to add a proof to a signature to turn it into a receipt, or to + * add a receipt to a signed statement to turn it into a transparent + * statement. + */ + std::vector set_unprotected_header( const std::span& buf_, ssize_t key, - op::Type op, + pos::Type position, const std::vector value); } \ No newline at end of file diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index 72d2ceb809a0..8a3fe63dcc09 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -2066,9 +2066,9 @@ namespace loggingapp } size_t vdp = 396; - auto inclusion_proof = ccf::cose::edit::op::SetAtKey{-1}; + auto inclusion_proof = ccf::cose::edit::pos::AtKey{-1}; - auto cose_receipt = ccf::cose::edit::insert_in_uhdr( + auto cose_receipt = ccf::cose::edit::set_unprotected_header( *signature, vdp, inclusion_proof, *proof); ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK); diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp index 05bcd0495c12..22264dac6ccd 100644 --- a/src/crypto/cose.cpp +++ b/src/crypto/cose.cpp @@ -11,21 +11,24 @@ namespace ccf::cose::edit { - std::vector insert_in_uhdr( + std::vector set_unprotected_header( const std::span& buf_, ssize_t key, - op::Type op, + pos::Type pos, const std::vector value) { UsefulBufC buf{buf_.data(), buf_.size()}; - QCBORError rc; + QCBORError err; QCBORDecodeContext ctx; QCBORDecode_Init(&ctx, buf, QCBOR_DECODE_MODE_NORMAL); + size_t pos_start = 0; + size_t pos_end = 0; + QCBORDecode_EnterArray(&ctx, nullptr); - rc = QCBORDecode_GetError(&ctx); - if (rc != QCBOR_SUCCESS) + err = QCBORDecode_GetError(&ctx); + if (err != QCBOR_SUCCESS) { throw std::logic_error("Failed to parse COSE_Sign1 outer array"); } @@ -37,36 +40,30 @@ namespace ccf::cose::edit } QCBORItem item; - auto err = QCBORDecode_GetNext(&ctx, &item); + err = QCBORDecode_GetNext(&ctx, &item); if (err != QCBOR_SUCCESS || item.uDataType != QCBOR_TYPE_BYTE_STRING) { throw std::logic_error( "Failed to parse COSE_Sign1 protected header as bstr"); } - std::span phdr = { - (uint8_t*)item.val.string.ptr, item.val.string.len}; + UsefulBufC phdr = {item.val.string.ptr, item.val.string.len}; - // Skip unprotected header for now + // Skip unprotected header QCBORDecode_VGetNextConsume(&ctx, &item); - std::optional> payload = std::nullopt; - err = QCBORDecode_GetNext(&ctx, &item); - if (err != QCBOR_SUCCESS) - { - throw std::logic_error("Failed to parse COSE_Sign1 payload"); - } - if (item.uDataType == QCBOR_TYPE_BYTE_STRING) - { - payload = {(uint8_t*)item.val.string.ptr, item.val.string.len}; - } - else if (item.uDataType == QCBOR_TYPE_NULL) + err = QCBORDecode_PartialFinish(&ctx, &pos_start); + if (err != QCBOR_ERR_ARRAY_OR_MAP_UNCONSUMED) { - // No payload + throw std::logic_error("Failed to find start of payload"); } - else + QCBORDecode_VGetNextConsume(&ctx, &item); + err = QCBORDecode_PartialFinish(&ctx, &pos_end); + if (err != QCBOR_ERR_ARRAY_OR_MAP_UNCONSUMED) { - throw std::logic_error("Invalid COSE_Sign1 payload"); + throw std::logic_error("Failed to find end of payload"); } + UsefulBufC payload = {buf_.data() + pos_start, pos_end - pos_start}; + // QCBORDecode_PartialFinish() before and after should allow constructing a // span of the encoded payload, which can perhaps then be passed to // QCBOREncode_AddEncoded and would allow blindly copying the payload @@ -77,8 +74,7 @@ namespace ccf::cose::edit { throw std::logic_error("Failed to parse COSE_Sign1 signature"); } - std::span signature = { - (uint8_t*)item.val.string.ptr, item.val.string.len}; + UsefulBufC signature = {item.val.string.ptr, item.val.string.len}; QCBORDecode_ExitArray(&ctx); err = QCBORDecode_Finish(&ctx); @@ -94,19 +90,19 @@ namespace ccf::cose::edit QCBOREncode_Init(&ectx, output_buf); QCBOREncode_AddTag(&ectx, CBOR_TAG_COSE_SIGN1); QCBOREncode_OpenArray(&ectx); - QCBOREncode_AddBytes(&ectx, {phdr.data(), phdr.size()}); + QCBOREncode_AddBytes(&ectx, phdr); QCBOREncode_OpenMap(&ectx); - if (std::holds_alternative(op)) + if (std::holds_alternative(pos)) { QCBOREncode_OpenArrayInMapN(&ectx, key); QCBOREncode_AddBytes(&ectx, {value.data(), value.size()}); QCBOREncode_CloseArray(&ectx); } - else if (std::holds_alternative(op)) + else if (std::holds_alternative(pos)) { QCBOREncode_OpenMapInMapN(&ectx, key); - auto subkey = std::get(op).key; + auto subkey = std::get(pos).key; QCBOREncode_OpenArrayInMapN(&ectx, subkey); QCBOREncode_AddBytes(&ectx, {value.data(), value.size()}); QCBOREncode_CloseArray(&ectx); @@ -118,15 +114,8 @@ namespace ccf::cose::edit } QCBOREncode_CloseMap(&ectx); - if (payload.has_value()) - { - QCBOREncode_AddBytes(&ectx, {payload->data(), payload->size()}); - } - else - { - QCBOREncode_AddSimple(&ectx, CBOR_SIMPLEV_NULL); - } - QCBOREncode_AddBytes(&ectx, {signature.data(), signature.size()}); + QCBOREncode_AddEncoded(&ectx, payload); + QCBOREncode_AddBytes(&ectx, signature); QCBOREncode_CloseArray(&ectx); UsefulBufC cose; diff --git a/src/crypto/test/cose.cpp b/src/crypto/test/cose.cpp index 5c8a23fa341a..eae154481b9d 100644 --- a/src/crypto/test/cose.cpp +++ b/src/crypto/test/cose.cpp @@ -60,10 +60,10 @@ TEST_CASE( REQUIRE(std::string(payload.begin(), payload.end()) == "payload"); size_t key = 42; - auto subkey = ccf::cose::edit::op::SetAtKey{43}; + auto subkey = ccf::cose::edit::pos::AtKey{43}; std::vector value = {1, 2, 3, 4}; - auto enriched_cose_sign1 = - ccf::cose::edit::insert_in_uhdr(cose_sign1_sample0, key, subkey, value); + auto enriched_cose_sign1 = ccf::cose::edit::set_unprotected_header( + cose_sign1_sample0, key, subkey, value); // Debug dump_bytes_to_file(enriched_cose_sign1, "enriched.bin"); From 2d0305490b6984ad0650a9dff33e0e120ef5c6c3 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 09:27:21 +0000 Subject: [PATCH 09/19] size --- src/crypto/cose.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp index 22264dac6ccd..f7605b0a1b0b 100644 --- a/src/crypto/cose.cpp +++ b/src/crypto/cose.cpp @@ -83,7 +83,22 @@ namespace ccf::cose::edit throw std::logic_error("Failed to parse COSE_Sign1"); } - std::vector output(buf_.size() + value.size() + 1024 /* too much, should be encoded key size + potential varint bump in sizes */); + // Maximum expected size of the additional map, sub-map is the + // worst-case scenario + size_t additional_map_size = QCBOR_HEAD_BUFFER_SIZE + // map + QCBOR_HEAD_BUFFER_SIZE + // key + sizeof(key) + // key + QCBOR_HEAD_BUFFER_SIZE + // submap + QCBOR_HEAD_BUFFER_SIZE + // subkey + sizeof(pos::AtKey::key) + // subkey + QCBOR_HEAD_BUFFER_SIZE + // value + value.size(); // value + + // We add one extra QCBOR_HEAD_BUFFER_SIZE, because we parse and re-encode + // the protected header bstr, which involves variable integer encoding, just + // in case the library does not pick the most compact encoding. + std::vector output( + buf_.size() + additional_map_size + QCBOR_HEAD_BUFFER_SIZE); UsefulBuf output_buf{output.data(), output.size()}; QCBOREncodeContext ectx; From d1ccb882548054895cd85a22a190c2c3eed48d96 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 09:53:44 +0000 Subject: [PATCH 10/19] a bit more tests --- src/crypto/test/cose.cpp | 59 +++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/crypto/test/cose.cpp b/src/crypto/test/cose.cpp index eae154481b9d..78b385e74926 100644 --- a/src/crypto/test/cose.cpp +++ b/src/crypto/test/cose.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,18 @@ static const std::vector cose_sign1_sample0 = { 71, 61, 219, 170, 226, 134, 51, 140, 28, 36, 223, 249, 61, 113, 7, 181, 126, 27, 133, 84, 7, 158, 114, 113, 115, 171, 215, 57, 233, 166, 198, 159, 243, 140, 255, 152, 255, 2, 3, 126, 18}; + +static const std::vector keys = { + 42, std::numeric_limits::min(), std::numeric_limits::max()}; + +static const std::vector positions = { + ccf::cose::edit::pos::AtKey{42}, + ccf::cose::edit::pos::AtKey{std::numeric_limits::min()}, + ccf::cose::edit::pos::AtKey{std::numeric_limits::max()}, + ccf::cose::edit::pos::InArray{}}; + +const std::vector value = {1, 2, 3, 4}; + // Function to dump bytes to a file void dump_bytes_to_file( const std::vector& bytes, const std::string& filename) @@ -48,27 +61,47 @@ void dump_bytes_to_file( file.close(); } -TEST_CASE( - "Check insertion at key in unprotected header does not affect verification") +TEST_CASE("Check setting does not affect verification") { auto verifier = ccf::crypto::make_cose_verifier_from_key(cose_sign1_sample0_cert); std::span payload; - // Verify the original COSE_Sign1 REQUIRE(verifier->verify(cose_sign1_sample0, payload)); REQUIRE(std::string(payload.begin(), payload.end()) == "payload"); - size_t key = 42; - auto subkey = ccf::cose::edit::pos::AtKey{43}; - std::vector value = {1, 2, 3, 4}; - auto enriched_cose_sign1 = ccf::cose::edit::set_unprotected_header( - cose_sign1_sample0, key, subkey, value); + for (const auto& key : keys) + { + for (const auto& position : positions) + { + auto enriched_cose_sign1 = ccf::cose::edit::set_unprotected_header( + cose_sign1_sample0, key, position, value); - // Debug - dump_bytes_to_file(enriched_cose_sign1, "enriched.bin"); + // Debug + dump_bytes_to_file(enriched_cose_sign1, "enriched.bin"); - REQUIRE(verifier->verify(enriched_cose_sign1, payload)); - REQUIRE( - enriched_cose_sign1.size() > cose_sign1_sample0.size() + value.size()); + REQUIRE(verifier->verify(enriched_cose_sign1, payload)); + REQUIRE( + enriched_cose_sign1.size() > cose_sign1_sample0.size() + value.size()); + } + } +} + +TEST_CASE("Check repeated setting is idempotent") +{ + for (const auto& key : keys) + { + for (const auto& position : positions) + { + auto enriched_cose_sign1 = ccf::cose::edit::set_unprotected_header( + cose_sign1_sample0, key, position, value); + + auto enriched_cose_sign1_again = ccf::cose::edit::set_unprotected_header( + cose_sign1_sample0, key, position, value); + REQUIRE(enriched_cose_sign1 == enriched_cose_sign1_again); + + // Debug + dump_bytes_to_file(enriched_cose_sign1, "enriched.bin"); + } + } } \ No newline at end of file From 1b14b3d02d1d1e9ba33d00832fa43d3b2abafc15 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 11:05:44 +0000 Subject: [PATCH 11/19] a bit more tests --- src/crypto/test/cose.cpp | 159 ++++++++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 62 deletions(-) diff --git a/src/crypto/test/cose.cpp b/src/crypto/test/cose.cpp index 78b385e74926..c48c57bb718d 100644 --- a/src/crypto/test/cose.cpp +++ b/src/crypto/test/cose.cpp @@ -3,6 +3,7 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include "ccf/crypto/cose.h" +#include "crypto/openssl/cose_sign.h" #include "crypto/openssl/cose_verifier.h" #include @@ -12,31 +13,6 @@ #include #include -static const ccf::crypto::Pem cose_sign1_sample0_cert = ccf::crypto::Pem( - "-----BEGIN PUBLIC KEY-----\n" - "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEqv7c4eTwwUiRZ8F6b1QrcNiiSZrNc7Kj\n" - "UM4ZZO8VzwMwQYN6kcJ9lv5rqemlr/ViQ4pZ3/XfocrnEiQQX1dJ26c7aaLnDioi\n" - "0jRn/N6gVUxWzLXupEQDg7XNrb116oCj\n" - "-----END PUBLIC KEY-----\n"); - -static const std::vector cose_sign1_sample0 = { - 210, 132, 88, 125, 164, 1, 56, 34, 4, 88, 64, 97, 102, 57, 51, - 56, 98, 54, 57, 52, 101, 100, 102, 56, 51, 53, 54, 54, 57, 51, - 51, 100, 99, 50, 102, 54, 99, 100, 50, 56, 56, 56, 97, 51, 54, - 50, 98, 53, 53, 52, 49, 56, 102, 53, 100, 50, 101, 102, 100, 102, - 98, 49, 97, 57, 56, 51, 99, 56, 98, 51, 98, 54, 56, 49, 53, - 112, 99, 99, 102, 46, 103, 111, 118, 46, 109, 115, 103, 46, 116, 121, - 112, 101, 104, 112, 114, 111, 112, 111, 115, 97, 108, 118, 99, 99, 102, - 46, 103, 111, 118, 46, 109, 115, 103, 46, 99, 114, 101, 97, 116, 101, - 100, 95, 97, 116, 26, 103, 23, 114, 91, 160, 71, 112, 97, 121, 108, - 111, 97, 100, 88, 96, 181, 64, 150, 14, 237, 176, 247, 51, 37, 225, - 53, 220, 166, 180, 86, 57, 75, 24, 61, 133, 55, 59, 122, 30, 23, - 181, 189, 58, 8, 42, 162, 165, 69, 232, 145, 219, 29, 120, 107, 241, - 214, 144, 78, 125, 192, 179, 246, 102, 52, 30, 98, 127, 64, 83, 0, - 71, 61, 219, 170, 226, 134, 51, 140, 28, 36, 223, 249, 61, 113, 7, - 181, 126, 27, 133, 84, 7, 158, 114, 113, 115, 171, 215, 57, 233, 166, - 198, 159, 243, 140, 255, 152, 255, 2, 3, 126, 18}; - static const std::vector keys = { 42, std::numeric_limits::min(), std::numeric_limits::max()}; @@ -48,60 +24,119 @@ static const std::vector positions = { const std::vector value = {1, 2, 3, 4}; -// Function to dump bytes to a file -void dump_bytes_to_file( - const std::vector& bytes, const std::string& filename) +enum class PayloadType +{ + Detached, + Flat, + NestedCBOR // Useful to test the payload transfer +}; + +struct Signer { - std::ofstream file(filename, std::ios::binary); - if (!file) + ccf::crypto::KeyPair_OpenSSL kp; + std::vector payload; + bool detached_payload = false; + + Signer(PayloadType type) : kp(ccf::crypto::CurveID::SECP384R1) { - throw std::ios_base::failure("Failed to open file for writing"); + switch (type) + { + case PayloadType::Detached: + detached_payload = true; + payload = {'p', 'a', 'y', 'l', 'o', 'a', 'd'}; + break; + case PayloadType::Flat: + payload = {'p', 'a', 'y', 'l', 'o', 'a', 'd'}; + break; + case PayloadType::NestedCBOR: + { + payload.resize(1024); + QCBOREncodeContext ctx; + QCBOREncode_Init(&ctx, {payload.data(), payload.size()}); + QCBOREncode_OpenArray(&ctx); + QCBOREncode_AddInt64(&ctx, 1); + QCBOREncode_OpenArray(&ctx); + QCBOREncode_AddInt64(&ctx, 2); + QCBOREncode_AddInt64(&ctx, 3); + QCBOREncode_CloseArray(&ctx); + QCBOREncode_CloseArray(&ctx); + UsefulBufC result; + QCBOREncode_Finish(&ctx, &result); + payload.resize(result.len); + payload.shrink_to_fit(); + } + break; + } } - file.write(reinterpret_cast(bytes.data()), bytes.size()); - file.close(); -} -TEST_CASE("Check setting does not affect verification") -{ - auto verifier = - ccf::crypto::make_cose_verifier_from_key(cose_sign1_sample0_cert); - std::span payload; + std::vector make_cose_sign1() + { + const auto pheaders = { + ccf::crypto::cose_params_int_bytes(300, value), + ccf::crypto::cose_params_int_int(301, 34)}; - REQUIRE(verifier->verify(cose_sign1_sample0, payload)); - REQUIRE(std::string(payload.begin(), payload.end()) == "payload"); + return ccf::crypto::cose_sign1(kp, pheaders, payload, false); + }; - for (const auto& key : keys) + void verify(const std::vector& cose_sign1) { - for (const auto& position : positions) + auto verifier = + ccf::crypto::make_cose_verifier_from_key(kp.public_key_pem()); + if (detached_payload) { - auto enriched_cose_sign1 = ccf::cose::edit::set_unprotected_header( - cose_sign1_sample0, key, position, value); + verifier->verify_detached(cose_sign1, payload); + } + else + { + std::span payload_; + REQUIRE(verifier->verify(cose_sign1, payload_)); + std::vector payload_copy(payload_.begin(), payload_.end()); + REQUIRE(payload == payload_copy); + } + }; +}; - // Debug - dump_bytes_to_file(enriched_cose_sign1, "enriched.bin"); +TEST_CASE("Verification and payload invariant") +{ + for (auto type : + {PayloadType::Detached, PayloadType::Flat, PayloadType::NestedCBOR}) + { + Signer signer(type); + auto csp = signer.make_cose_sign1(); + signer.verify(csp); - REQUIRE(verifier->verify(enriched_cose_sign1, payload)); - REQUIRE( - enriched_cose_sign1.size() > cose_sign1_sample0.size() + value.size()); + for (const auto& key : keys) + { + for (const auto& position : positions) + { + auto csp_set = + ccf::cose::edit::set_unprotected_header(csp, key, position, value); + + signer.verify(csp_set); + } } } } -TEST_CASE("Check repeated setting is idempotent") +TEST_CASE("Idempotence") { - for (const auto& key : keys) + for (auto type : + {PayloadType::Detached, PayloadType::Flat, PayloadType::NestedCBOR}) { - for (const auto& position : positions) - { - auto enriched_cose_sign1 = ccf::cose::edit::set_unprotected_header( - cose_sign1_sample0, key, position, value); + Signer signer(type); + auto csp = signer.make_cose_sign1(); - auto enriched_cose_sign1_again = ccf::cose::edit::set_unprotected_header( - cose_sign1_sample0, key, position, value); - REQUIRE(enriched_cose_sign1 == enriched_cose_sign1_again); - - // Debug - dump_bytes_to_file(enriched_cose_sign1, "enriched.bin"); + for (const auto& key : keys) + { + for (const auto& position : positions) + { + auto csp_set_once = + ccf::cose::edit::set_unprotected_header(csp, key, position, value); + + auto csp_set_twice = ccf::cose::edit::set_unprotected_header( + csp_set_once, key, position, value); + REQUIRE(csp_set_once == csp_set_twice); + } } } } \ No newline at end of file From 2ce181cdfc9b78accf3afd27e12fb1408c37b99a Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 12:06:19 +0000 Subject: [PATCH 12/19] fix --- include/ccf/crypto/cose.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h index 023e27b053a6..b9412ca13d96 100644 --- a/include/ccf/crypto/cose.h +++ b/include/ccf/crypto/cose.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include From 8b433739ef7d24254a04417cf7e5b98eab0985d4 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 12:15:55 +0000 Subject: [PATCH 13/19] doc --- CHANGELOG.md | 1 + doc/build_apps/api.rst | 6 ++++++ include/ccf/crypto/cose.h | 16 ++++++++++++---- src/crypto/cose.cpp | 14 +++++++------- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d666ea52a2d..19fcc9a4babf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Set VMPL value when creating SNP attestations, and check VMPL value is in guest range when verifiying attestation, since recent [updates allow host-initiated attestations](https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/programmer-references/56860.pdf) (#6583). +- Added ccf::cose::edit::set_unprotected_header() API, to allow easy injection of proofs in signatures, and of receipts in signed statements (#6586). ## [6.0.0-dev2] diff --git a/doc/build_apps/api.rst b/doc/build_apps/api.rst index 0bd0ea9004eb..0354d9f7cca4 100644 --- a/doc/build_apps/api.rst +++ b/doc/build_apps/api.rst @@ -158,3 +158,9 @@ HTTP Entity Tags Matching .. doxygenclass:: ccf::http::Matcher :project: CCF :members: + +COSE +---- + +.. doxygenfunction:: ccf::cose::edit::set_unprotected_header + :project: CCF \ No newline at end of file diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h index b9412ca13d96..3dc6d9164faa 100644 --- a/include/ccf/crypto/cose.h +++ b/include/ccf/crypto/cose.h @@ -2,8 +2,8 @@ // Licensed under the Apache 2.0 License. #pragma once -#include #include +#include #include #include #include @@ -25,15 +25,23 @@ namespace ccf::cose::edit /** * Set the unprotected header of a COSE_Sign1 message, to a map containing - * \key and depending on the value of \position, either an array containing - * \value, or a map with key \subkey and value \value. + * @p key and depending on the value of @p position, either an array + * containing + * @p value, or a map with key @p subkey and value @p value. * * Useful to add a proof to a signature to turn it into a receipt, or to * add a receipt to a signed statement to turn it into a transparent * statement. + * + * @param cose_input The COSE_Sign1 message to edit. + * @param key The key at which to insert either an array or a map. + * @param position Either InArray or AtKey, to determine whether to insert an + * array or a map. + * + * @return The COSE_Sign1 message with the new unprotected header. */ std::vector set_unprotected_header( - const std::span& buf_, + const std::span& cose_input, ssize_t key, pos::Type position, const std::vector value); diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp index f7605b0a1b0b..32d1eba857e6 100644 --- a/src/crypto/cose.cpp +++ b/src/crypto/cose.cpp @@ -12,12 +12,12 @@ namespace ccf::cose::edit { std::vector set_unprotected_header( - const std::span& buf_, + const std::span& cose_input, ssize_t key, pos::Type pos, const std::vector value) { - UsefulBufC buf{buf_.data(), buf_.size()}; + UsefulBufC buf{cose_input.data(), cose_input.size()}; QCBORError err; QCBORDecodeContext ctx; @@ -62,7 +62,7 @@ namespace ccf::cose::edit { throw std::logic_error("Failed to find end of payload"); } - UsefulBufC payload = {buf_.data() + pos_start, pos_end - pos_start}; + UsefulBufC payload = {cose_input.data() + pos_start, pos_end - pos_start}; // QCBORDecode_PartialFinish() before and after should allow constructing a // span of the encoded payload, which can perhaps then be passed to @@ -98,7 +98,7 @@ namespace ccf::cose::edit // the protected header bstr, which involves variable integer encoding, just // in case the library does not pick the most compact encoding. std::vector output( - buf_.size() + additional_map_size + QCBOR_HEAD_BUFFER_SIZE); + cose_input.size() + additional_map_size + QCBOR_HEAD_BUFFER_SIZE); UsefulBuf output_buf{output.data(), output.size()}; QCBOREncodeContext ectx; @@ -133,13 +133,13 @@ namespace ccf::cose::edit QCBOREncode_AddBytes(&ectx, signature); QCBOREncode_CloseArray(&ectx); - UsefulBufC cose; - err = QCBOREncode_Finish(&ectx, &cose); + UsefulBufC cose_output; + err = QCBOREncode_Finish(&ectx, &cose_output); if (err != QCBOR_SUCCESS) { throw std::logic_error("Failed to encode COSE_Sign1"); } - output.resize(cose.len); + output.resize(cose_output.len); output.shrink_to_fit(); return output; }; From eb038defa59d8782c7e44c1a7e783576311e1b3a Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 12:33:17 +0000 Subject: [PATCH 14/19] last test --- src/crypto/test/cose.cpp | 77 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/crypto/test/cose.cpp b/src/crypto/test/cose.cpp index c48c57bb718d..7e896332d56d 100644 --- a/src/crypto/test/cose.cpp +++ b/src/crypto/test/cose.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include @@ -139,4 +141,79 @@ TEST_CASE("Idempotence") } } } +} + +TEST_CASE("Check unprotected header") +{ + for (auto type : + {PayloadType::Detached, PayloadType::Flat, PayloadType::NestedCBOR}) + { + Signer signer(type); + auto csp = signer.make_cose_sign1(); + + for (const auto& key : keys) + { + for (const auto& position : positions) + { + auto csp_set = + ccf::cose::edit::set_unprotected_header(csp, key, position, value); + + std::vector ref(1024); + { + // Create expected reference value for the unprotected header + UsefulBuf ref_buf{ref.data(), ref.size()}; + QCBOREncodeContext ctx; + QCBOREncode_Init(&ctx, ref_buf); + QCBOREncode_OpenMap(&ctx); + + if (std::holds_alternative(position)) + { + QCBOREncode_OpenArrayInMapN(&ctx, key); + QCBOREncode_AddBytes(&ctx, {value.data(), value.size()}); + QCBOREncode_CloseArray(&ctx); + } + else if (std::holds_alternative( + position)) + { + QCBOREncode_OpenMapInMapN(&ctx, key); + auto subkey = std::get(position).key; + QCBOREncode_OpenArrayInMapN(&ctx, subkey); + QCBOREncode_AddBytes(&ctx, {value.data(), value.size()}); + QCBOREncode_CloseArray(&ctx); + QCBOREncode_CloseMap(&ctx); + } + QCBOREncode_CloseMap(&ctx); + UsefulBufC ref_buf_c; + QCBOREncode_Finish(&ctx, &ref_buf_c); + ref.resize(ref_buf_c.len); + ref.shrink_to_fit(); + } + + size_t uhdr_start, uhdr_end; + QCBORError err; + QCBORItem item; + QCBORDecodeContext ctx; + UsefulBufC buf{csp_set.data(), csp_set.size()}; + QCBORDecode_Init(&ctx, buf, QCBOR_DECODE_MODE_NORMAL); + QCBORDecode_EnterArray(&ctx, nullptr); + QCBORDecode_GetNthTagOfLast(&ctx, 0); + // Protected header + QCBORDecode_VGetNextConsume(&ctx, &item); + // Unprotected header + QCBORDecode_PartialFinish(&ctx, &uhdr_start); + QCBORDecode_VGetNextConsume(&ctx, &item); + QCBORDecode_PartialFinish(&ctx, &uhdr_end); + std::vector uhdr{ + csp_set.data() + uhdr_start, csp_set.data() + uhdr_end}; + REQUIRE(uhdr == ref); + // Payload + QCBORDecode_VGetNextConsume(&ctx, &item); + // Signature + QCBORDecode_VGetNextConsume(&ctx, &item); + QCBORDecode_ExitArray(&ctx); + err = QCBORDecode_Finish(&ctx); + REQUIRE(err == QCBOR_SUCCESS); + } + } + } } \ No newline at end of file From 5121400edf5f0f3b6ce64372c184d8294bd2411b Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 13:00:52 +0000 Subject: [PATCH 15/19] ssize_t is not standard --- include/ccf/crypto/cose.h | 5 ++--- src/crypto/cose.cpp | 2 +- src/crypto/test/cose.cpp | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/ccf/crypto/cose.h b/include/ccf/crypto/cose.h index 3dc6d9164faa..b75e5f9db5ee 100644 --- a/include/ccf/crypto/cose.h +++ b/include/ccf/crypto/cose.h @@ -2,7 +2,6 @@ // Licensed under the Apache 2.0 License. #pragma once -#include #include #include #include @@ -17,7 +16,7 @@ namespace ccf::cose::edit struct AtKey { - ssize_t key; + int64_t key; }; using Type = std::variant; @@ -42,7 +41,7 @@ namespace ccf::cose::edit */ std::vector set_unprotected_header( const std::span& cose_input, - ssize_t key, + int64_t key, pos::Type position, const std::vector value); } \ No newline at end of file diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp index 32d1eba857e6..b1217e6377c0 100644 --- a/src/crypto/cose.cpp +++ b/src/crypto/cose.cpp @@ -13,7 +13,7 @@ namespace ccf::cose::edit { std::vector set_unprotected_header( const std::span& cose_input, - ssize_t key, + int64_t key, pos::Type pos, const std::vector value) { diff --git a/src/crypto/test/cose.cpp b/src/crypto/test/cose.cpp index 7e896332d56d..1719c1fea1b2 100644 --- a/src/crypto/test/cose.cpp +++ b/src/crypto/test/cose.cpp @@ -15,13 +15,13 @@ #include #include -static const std::vector keys = { - 42, std::numeric_limits::min(), std::numeric_limits::max()}; +static const std::vector keys = { + 42, std::numeric_limits::min(), std::numeric_limits::max()}; static const std::vector positions = { ccf::cose::edit::pos::AtKey{42}, - ccf::cose::edit::pos::AtKey{std::numeric_limits::min()}, - ccf::cose::edit::pos::AtKey{std::numeric_limits::max()}, + ccf::cose::edit::pos::AtKey{std::numeric_limits::min()}, + ccf::cose::edit::pos::AtKey{std::numeric_limits::max()}, ccf::cose::edit::pos::InArray{}}; const std::vector value = {1, 2, 3, 4}; From 19f095c98760bd9704d971e3f0c7ef2a765c6e03 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 13:05:25 +0000 Subject: [PATCH 16/19] one call --- CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 74a16af676b2..dff2ce26bc89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -867,9 +867,7 @@ if(BUILD_TESTS) add_unit_test( cose_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/cose.cpp ) - target_link_libraries(cose_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) - target_link_libraries(cose_test PRIVATE ccfcrypto.host) - target_link_libraries(cose_test PRIVATE qcbor.host) + target_link_libraries(cose_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} ccfcrypto.host qcbor.host) add_unit_test(pem_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/pem.cpp) target_link_libraries(pem_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) From 66d579cc30bec634c1cddb80806bdb6332ed63ce Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 13:10:07 +0000 Subject: [PATCH 17/19] . --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dff2ce26bc89..65992691c8b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -867,7 +867,9 @@ if(BUILD_TESTS) add_unit_test( cose_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/cose.cpp ) - target_link_libraries(cose_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} ccfcrypto.host qcbor.host) + target_link_libraries( + cose_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} ccfcrypto.host qcbor.host + ) add_unit_test(pem_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/pem.cpp) target_link_libraries(pem_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) From c5b3b3490bb11df53f63d0d32ba8fae19e5bdf03 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 14:42:30 +0100 Subject: [PATCH 18/19] Update src/crypto/cose.cpp Co-authored-by: Max --- src/crypto/cose.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/cose.cpp b/src/crypto/cose.cpp index b1217e6377c0..f4ad8c67aded 100644 --- a/src/crypto/cose.cpp +++ b/src/crypto/cose.cpp @@ -85,7 +85,7 @@ namespace ccf::cose::edit // Maximum expected size of the additional map, sub-map is the // worst-case scenario - size_t additional_map_size = QCBOR_HEAD_BUFFER_SIZE + // map + const size_t additional_map_size = QCBOR_HEAD_BUFFER_SIZE + // map QCBOR_HEAD_BUFFER_SIZE + // key sizeof(key) + // key QCBOR_HEAD_BUFFER_SIZE + // submap From 6fbdb9f97297c7eb019bbacf8d27dba6f76db8aa Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Thu, 24 Oct 2024 14:34:27 +0000 Subject: [PATCH 19/19] oops --- src/crypto/test/cose.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/test/cose.cpp b/src/crypto/test/cose.cpp index 1719c1fea1b2..c3304d0cb7b7 100644 --- a/src/crypto/test/cose.cpp +++ b/src/crypto/test/cose.cpp @@ -77,7 +77,7 @@ struct Signer ccf::crypto::cose_params_int_bytes(300, value), ccf::crypto::cose_params_int_int(301, 34)}; - return ccf::crypto::cose_sign1(kp, pheaders, payload, false); + return ccf::crypto::cose_sign1(kp, pheaders, payload, detached_payload); }; void verify(const std::vector& cose_sign1)