From 23d24d153bc75fd891ddd5678c237c366753f7a3 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Sat, 12 Oct 2024 13:13:07 +0000 Subject: [PATCH 1/6] IAT claim in cose sign --- src/crypto/openssl/cose_sign.h | 2 ++ src/node/history.h | 6 +++++- src/service/internal_tables_access.h | 3 +++ tests/recovery.py | 2 ++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index 06eb68483584..7ba356c964f1 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -18,6 +18,8 @@ namespace ccf::crypto static constexpr int64_t COSE_PHEADER_KEY_ID = 4; // Standardised: verifiable data structure. static constexpr int64_t COSE_PHEADER_KEY_VDS = 395; + // Standardised: issued at CWT claim. + static const std::string COSE_PHEADER_IAT = "iat"; // CCF-specific: last signed TxID. static const std::string COSE_PHEADER_KEY_TXID = "ccf.txid"; // CCF-specific: first TX in the range. diff --git a/src/node/history.h b/src/node/history.h index 988f3fc84108..b6387d17052b 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -381,7 +381,11 @@ namespace ccf ccf::crypto::COSE_PHEADER_KEY_VDS, vds_merkle_tree), // TxID ccf::crypto::cose_params_string_string( - ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str())}; + ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str()), + // iat + ccf::crypto::cose_params_string_int( + ccf::crypto::COSE_PHEADER_IAT, static_cast(time(nullptr)))}; + auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash); signatures->put(sig_value); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index e14c39c7d153..e60d62125c2c 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -451,6 +451,9 @@ namespace ccf endorsement.endorsement_epoch_end->to_str())); } + pheaders.push_back(ccf::crypto::cose_params_string_int( + ccf::crypto::COSE_PHEADER_IAT, static_cast(time(nullptr)))); + try { endorsement.endorsement = cose_sign1( diff --git a/tests/recovery.py b/tests/recovery.py index 1f696af8d7c1..25e3ff7e6adb 100644 --- a/tests/recovery.py +++ b/tests/recovery.py @@ -77,6 +77,8 @@ def verify_endorsements_chain(primary, endorsements, pubkey): root_from_headers = cose_msg.phdr["ccf.merkle.root"] assert root_from_receipt == root_from_headers + assert "iat" in cose_msg.phdr + next_key_bytes = cose_msg.payload pubkey = serialization.load_der_public_key(next_key_bytes, default_backend()) From a947824e3f9fa08691d5f5e7b884b3065deae4f2 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 14 Oct 2024 12:31:58 +0000 Subject: [PATCH 2/6] Use enclave time instead of stdtime --- CMakeLists.txt | 3 +++ src/node/history.h | 8 +++++++- src/service/internal_tables_access.h | 7 ++++++- tests/recovery.py | 4 +++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf6c7eb0a36e..8d4ff526fa65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -787,6 +787,7 @@ if(BUILD_TESTS) add_unit_test( history_test ${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/history.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/enclave/enclave_time.cpp ) target_link_libraries( history_test PRIVATE ccfcrypto.host http_parser.host ccf_kv.host @@ -841,6 +842,7 @@ if(BUILD_TESTS) add_unit_test( snapshot_test ${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/snapshot.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/enclave/enclave_time.cpp ) target_link_libraries(snapshot_test PRIVATE ccf_kv.host) @@ -1008,6 +1010,7 @@ if(BUILD_TESTS) add_picobench( history_bench SRCS src/node/test/history_bench.cpp src/enclave/thread_local.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/enclave/enclave_time.cpp LINK_LIBS ccf_kv.host ) diff --git a/src/node/history.h b/src/node/history.h index b6387d17052b..dda7e563bd9a 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -11,6 +11,7 @@ #include "crypto/openssl/hash.h" #include "crypto/openssl/key_pair.h" #include "ds/thread_messaging.h" +#include "enclave/enclave_time.h" #include "endian.h" #include "kv/kv_types.h" #include "kv/store.h" @@ -372,6 +373,11 @@ namespace ccf std::vector kid(SHA256_DIGEST_LENGTH); SHA256(service_key_der.data(), service_key_der.size(), kid.data()); + const auto time_since_epoch = + std::chrono::duration_cast( + ccf::get_enclave_time()) + .count(); + const auto pheaders = { // Key digest ccf::crypto::cose_params_int_bytes( @@ -384,7 +390,7 @@ namespace ccf ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str()), // iat ccf::crypto::cose_params_string_int( - ccf::crypto::COSE_PHEADER_IAT, static_cast(time(nullptr)))}; + ccf::crypto::COSE_PHEADER_IAT, time_since_epoch)}; auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index e60d62125c2c..1554661f2f48 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -13,6 +13,7 @@ #include "ccf/tx.h" #include "consensus/aft/raft_types.h" #include "crypto/openssl/cose_sign.h" +#include "enclave/enclave_time.h" #include "node/ledger_secrets.h" #include "node/uvm_endorsements.h" #include "service/tables/governance_history.h" @@ -451,8 +452,12 @@ namespace ccf endorsement.endorsement_epoch_end->to_str())); } + const auto time_since_epoch = + std::chrono::duration_cast( + ccf::get_enclave_time()) + .count(); pheaders.push_back(ccf::crypto::cose_params_string_int( - ccf::crypto::COSE_PHEADER_IAT, static_cast(time(nullptr)))); + ccf::crypto::COSE_PHEADER_IAT, time_since_epoch)); try { diff --git a/tests/recovery.py b/tests/recovery.py index 25e3ff7e6adb..bd8a505817e3 100644 --- a/tests/recovery.py +++ b/tests/recovery.py @@ -77,7 +77,9 @@ def verify_endorsements_chain(primary, endorsements, pubkey): root_from_headers = cose_msg.phdr["ccf.merkle.root"] assert root_from_receipt == root_from_headers - assert "iat" in cose_msg.phdr + assert "iat" in cose_msg.phdr, cose_msg.phdr + last_five_minutes = 5 * 60 + assert time.time() - cose_msg.phdr["iat"] < last_five_minutes, cose_msg.phdr next_key_bytes = cose_msg.payload pubkey = serialization.load_der_public_key(next_key_bytes, default_backend()) From 6331df07acf680279a8bfb13a5e343cbfa8d201b Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 14 Oct 2024 17:20:52 +0000 Subject: [PATCH 3/6] IAT as nested CWT --- src/crypto/openssl/cose_sign.cpp | 22 ++++++++++++++++++++++ src/crypto/openssl/cose_sign.h | 6 ++++++ src/node/history.h | 4 ++-- src/service/internal_tables_access.h | 4 ++-- tests/recovery.py | 11 +++++++++-- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 3f4254aae112..1ae0fd08817d 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -94,6 +94,28 @@ namespace ccf::crypto } } + COSEParametersFactory cose_params_cwt_map(const CWTMap& m) + { + size_t args_size = extra_size_for_seq_tag; + for (const auto& [key, value] : m) + { + args_size += key.size() + sizeof(value) + extra_size_for_seq_tag + + extra_size_for_int_tag; + } + + return COSEParametersFactory( + [=](QCBOREncodeContext* ctx) { + QCBOREncode_OpenMapInMapN(ctx, COSE_PHEADER_KEY_CWT); + for (const auto& [key, value] : m) + { + QCBOREncode_AddSZString(ctx, key.c_str()); + QCBOREncode_AddInt64(ctx, value); + } + QCBOREncode_CloseMap(ctx); + }, + args_size); + } + COSEParametersFactory cose_params_int_int(int64_t key, int64_t value) { const size_t args_size = sizeof(key) + sizeof(value) + diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index 7ba356c964f1..fdecb3f68793 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -16,6 +16,8 @@ namespace ccf::crypto static constexpr int64_t COSE_PHEADER_KEY_ALG = 1; // Standardised: hash of the signing key. static constexpr int64_t COSE_PHEADER_KEY_ID = 4; + // Standardised: CWT claims map. + static constexpr int64_t COSE_PHEADER_KEY_CWT = 15; // Standardised: verifiable data structure. static constexpr int64_t COSE_PHEADER_KEY_VDS = 395; // Standardised: issued at CWT claim. @@ -29,6 +31,8 @@ namespace ccf::crypto // CCF-specific: Merkle root hash. static const std::string COSE_PHEADER_KEY_MERKLE_ROOT = "ccf.merkle.root"; + using CWTMap = std::unordered_map; + class COSEParametersFactory { public: @@ -53,6 +57,8 @@ namespace ccf::crypto size_t args_size{}; }; + COSEParametersFactory cose_params_cwt_map(const CWTMap& m); + COSEParametersFactory cose_params_int_int(int64_t key, int64_t value); COSEParametersFactory cose_params_int_string( diff --git a/src/node/history.h b/src/node/history.h index dda7e563bd9a..83a1febea593 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -389,8 +389,8 @@ namespace ccf ccf::crypto::cose_params_string_string( ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str()), // iat - ccf::crypto::cose_params_string_int( - ccf::crypto::COSE_PHEADER_IAT, time_since_epoch)}; + ccf::crypto::cose_params_cwt_map(ccf::crypto::CWTMap{ + {ccf::crypto::COSE_PHEADER_IAT, time_since_epoch}})}; auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index aa1deef9dc6c..034cf51533d2 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -460,8 +460,8 @@ namespace ccf std::chrono::duration_cast( ccf::get_enclave_time()) .count(); - pheaders.push_back(ccf::crypto::cose_params_string_int( - ccf::crypto::COSE_PHEADER_IAT, time_since_epoch)); + pheaders.push_back(ccf::crypto::cose_params_cwt_map(ccf::crypto::CWTMap{ + {ccf::crypto::COSE_PHEADER_IAT, time_since_epoch}})); try { diff --git a/tests/recovery.py b/tests/recovery.py index bd8a505817e3..c9ce4e280e49 100644 --- a/tests/recovery.py +++ b/tests/recovery.py @@ -77,9 +77,16 @@ def verify_endorsements_chain(primary, endorsements, pubkey): root_from_headers = cose_msg.phdr["ccf.merkle.root"] assert root_from_receipt == root_from_headers - assert "iat" in cose_msg.phdr, cose_msg.phdr + CWT_KEY = 15 + IAT_CWT_LABEL = "iat" + assert ( + CWT_KEY in cose_msg.phdr and IAT_CWT_LABEL in cose_msg.phdr[CWT_KEY] + ), cose_msg.phdr + last_five_minutes = 5 * 60 - assert time.time() - cose_msg.phdr["iat"] < last_five_minutes, cose_msg.phdr + assert ( + time.time() - cose_msg.phdr[CWT_KEY][IAT_CWT_LABEL] < last_five_minutes + ), cose_msg.phdr next_key_bytes = cose_msg.payload pubkey = serialization.load_der_public_key(next_key_bytes, default_backend()) From 32ec9259436f5dccb31a38b934fc23cd3ae3c60f Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 14 Oct 2024 17:22:06 +0000 Subject: [PATCH 4/6] Key name --- src/crypto/openssl/cose_sign.h | 2 +- src/node/history.h | 2 +- src/service/internal_tables_access.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index fdecb3f68793..02a048b142ff 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -21,7 +21,7 @@ namespace ccf::crypto // Standardised: verifiable data structure. static constexpr int64_t COSE_PHEADER_KEY_VDS = 395; // Standardised: issued at CWT claim. - static const std::string COSE_PHEADER_IAT = "iat"; + static const std::string COSE_PHEADER_KEY_IAT = "iat"; // CCF-specific: last signed TxID. static const std::string COSE_PHEADER_KEY_TXID = "ccf.txid"; // CCF-specific: first TX in the range. diff --git a/src/node/history.h b/src/node/history.h index 83a1febea593..b7398748a7d4 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -390,7 +390,7 @@ namespace ccf ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str()), // iat ccf::crypto::cose_params_cwt_map(ccf::crypto::CWTMap{ - {ccf::crypto::COSE_PHEADER_IAT, time_since_epoch}})}; + {ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch}})}; auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 034cf51533d2..1f56f5f2ab82 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -461,7 +461,7 @@ namespace ccf ccf::get_enclave_time()) .count(); pheaders.push_back(ccf::crypto::cose_params_cwt_map(ccf::crypto::CWTMap{ - {ccf::crypto::COSE_PHEADER_IAT, time_since_epoch}})); + {ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch}})); try { From bafae1c7f149cebbd49f3fd7c8e67de8b9a838f6 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 15 Oct 2024 10:06:26 +0000 Subject: [PATCH 5/6] iat -> 6 as per rfc --- src/crypto/openssl/cose_sign.cpp | 4 ++-- src/crypto/openssl/cose_sign.h | 14 +++++++++++--- tests/recovery.py | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 1ae0fd08817d..e7677678b260 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -99,7 +99,7 @@ namespace ccf::crypto size_t args_size = extra_size_for_seq_tag; for (const auto& [key, value] : m) { - args_size += key.size() + sizeof(value) + extra_size_for_seq_tag + + args_size += sizeof(key) + sizeof(value) + extra_size_for_int_tag + extra_size_for_int_tag; } @@ -108,7 +108,7 @@ namespace ccf::crypto QCBOREncode_OpenMapInMapN(ctx, COSE_PHEADER_KEY_CWT); for (const auto& [key, value] : m) { - QCBOREncode_AddSZString(ctx, key.c_str()); + QCBOREncode_AddInt64(ctx, key); QCBOREncode_AddInt64(ctx, value); } QCBOREncode_CloseMap(ctx); diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index 02a048b142ff..ffb499df0026 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -20,8 +20,16 @@ namespace ccf::crypto static constexpr int64_t COSE_PHEADER_KEY_CWT = 15; // Standardised: verifiable data structure. static constexpr int64_t COSE_PHEADER_KEY_VDS = 395; - // Standardised: issued at CWT claim. - static const std::string COSE_PHEADER_KEY_IAT = "iat"; + // Standardised: issued at CWT claim. Value is **PLAIN INTEGER**, as per + // https://www.rfc-editor.org/rfc/rfc8392#section-2. Quote: + /* The "NumericDate" term in this specification has the same meaning and + * processing rules as the JWT "NumericDate" term defined in Section 2 of + * [RFC7519], except that it is represented as a CBOR numericdate (from + * Section 2.4.1 of [RFC7049]) instead of a JSON number. The encoding is + * modified so that the leading tag 1 (epoch-based date/time) MUST be + * omitted. + */ + static constexpr int64_t COSE_PHEADER_KEY_IAT = 6; // CCF-specific: last signed TxID. static const std::string COSE_PHEADER_KEY_TXID = "ccf.txid"; // CCF-specific: first TX in the range. @@ -31,7 +39,7 @@ namespace ccf::crypto // CCF-specific: Merkle root hash. static const std::string COSE_PHEADER_KEY_MERKLE_ROOT = "ccf.merkle.root"; - using CWTMap = std::unordered_map; + using CWTMap = std::unordered_map; class COSEParametersFactory { diff --git a/tests/recovery.py b/tests/recovery.py index c9ce4e280e49..e366add20a77 100644 --- a/tests/recovery.py +++ b/tests/recovery.py @@ -78,7 +78,7 @@ def verify_endorsements_chain(primary, endorsements, pubkey): assert root_from_receipt == root_from_headers CWT_KEY = 15 - IAT_CWT_LABEL = "iat" + IAT_CWT_LABEL = 6 assert ( CWT_KEY in cose_msg.phdr and IAT_CWT_LABEL in cose_msg.phdr[CWT_KEY] ), cose_msg.phdr From 006de47a1cd4bd9e5e6cc70e33335208e02bd984 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 15 Oct 2024 10:21:47 +0000 Subject: [PATCH 6/6] Rename map --- src/crypto/openssl/cose_sign.cpp | 2 +- src/crypto/openssl/cose_sign.h | 2 +- src/node/history.h | 2 +- src/service/internal_tables_access.h | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index e7677678b260..fe678e771d90 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -94,7 +94,7 @@ namespace ccf::crypto } } - COSEParametersFactory cose_params_cwt_map(const CWTMap& m) + COSEParametersFactory cose_params_cwt_map_int_int(const CWTMap& m) { size_t args_size = extra_size_for_seq_tag; for (const auto& [key, value] : m) diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index ffb499df0026..582a27e2a820 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -65,7 +65,7 @@ namespace ccf::crypto size_t args_size{}; }; - COSEParametersFactory cose_params_cwt_map(const CWTMap& m); + COSEParametersFactory cose_params_cwt_map_int_int(const CWTMap& m); COSEParametersFactory cose_params_int_int(int64_t key, int64_t value); diff --git a/src/node/history.h b/src/node/history.h index b7398748a7d4..b57efe44b79b 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -389,7 +389,7 @@ namespace ccf ccf::crypto::cose_params_string_string( ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str()), // iat - ccf::crypto::cose_params_cwt_map(ccf::crypto::CWTMap{ + ccf::crypto::cose_params_cwt_map_int_int(ccf::crypto::CWTMap{ {ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch}})}; auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 1f56f5f2ab82..7c1efb3b82cc 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -460,8 +460,9 @@ namespace ccf std::chrono::duration_cast( ccf::get_enclave_time()) .count(); - pheaders.push_back(ccf::crypto::cose_params_cwt_map(ccf::crypto::CWTMap{ - {ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch}})); + pheaders.push_back( + ccf::crypto::cose_params_cwt_map_int_int(ccf::crypto::CWTMap{ + {ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch}})); try {