From df37cd69a486bbf73ff7685b8ec6cc9fb03ad8d6 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Wed, 19 Jul 2023 18:23:04 -0400 Subject: [PATCH 01/36] Minimal changes to un-break CI (#350) * Suppress some more lints * Remove destructuring in for loop * Fix typo in .clang-tidy --- .clang-tidy | 2 ++ lib/mls_vectors/src/mls_vectors.cpp | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index 2c9859e6..ab2a8131 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -5,6 +5,7 @@ Checks: '*, -bugprone-easily-swappable-parameters, -bugprone-exception-escape, -cert-err58-cpp, + -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-non-private-member-variables-in-classes, @@ -20,6 +21,7 @@ Checks: '*, -llvmlibc-implementation-in-namespace, -llvmlibc-restrict-system-libc-headers, -misc-non-private-member-variables-in-classes, + -misc-use-anonymous-namespace, -modernize-use-nodiscard, -modernize-use-trailing-return-type, -readability-function-cognitive-complexity, diff --git a/lib/mls_vectors/src/mls_vectors.cpp b/lib/mls_vectors/src/mls_vectors.cpp index ec569064..c813e78c 100644 --- a/lib/mls_vectors/src/mls_vectors.cpp +++ b/lib/mls_vectors/src/mls_vectors.cpp @@ -1229,7 +1229,13 @@ struct TreeTestCase auto path = pub.encap(sender_priv, context, joiner); // Process the UpdatePath at all the members - for (auto& [leaf, priv_state] : privs) { + for (auto& pair : privs) { + // XXX(RLB): It might seem like this could be done with a simple + // destructuring assignment, either here or in the `for` clause above. + // However, either of these options cause clang-tidy to segfault when + // evaulating the "bugprone-unchecked-optional-access" lint. + const auto& leaf = pair.first; + auto& priv_state = pair.second; if (leaf == from) { priv_state = PrivateState{ priv_state.sig_priv, sender_priv, { from } }; From 53bb7eacc3332e7fa1b2c1096ff8dbd81fa3bdcb Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 31 Jul 2023 14:25:08 -0400 Subject: [PATCH 02/36] Add tree hash to tree operations test vectors (#352) * Add tree hash to test vector structs * Reflect changes in JSON serialization * Set ciphersuite and initialize tree --- cmd/interop/src/json_details.h | 5 ++++- .../include/mls_vectors/mls_vectors.h | 7 ++++++- lib/mls_vectors/src/mls_vectors.cpp | 20 ++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/cmd/interop/src/json_details.h b/cmd/interop/src/json_details.h index 95c47941..60d5e1ea 100644 --- a/cmd/interop/src/json_details.h +++ b/cmd/interop/src/json_details.h @@ -290,10 +290,13 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(WelcomeTestVector, welcome) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(TreeOperationsTestVector, + cipher_suite, tree_before, + tree_hash_before, proposal, proposal_sender, - tree_after) + tree_after, + tree_hash_after) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(TreeKEMTestVector::PathSecret, node, diff --git a/lib/mls_vectors/include/mls_vectors/mls_vectors.h b/lib/mls_vectors/include/mls_vectors/mls_vectors.h index 6eef5966..b0fc5fd7 100644 --- a/lib/mls_vectors/include/mls_vectors/mls_vectors.h +++ b/lib/mls_vectors/include/mls_vectors/mls_vectors.h @@ -444,15 +444,20 @@ struct TreeOperationsTestVector : PseudoRandom static const std::vector all_scenarios; + mls::CipherSuite cipher_suite; + mls::TreeKEMPublicKey tree_before; + bytes tree_hash_before; + mls::Proposal proposal; mls::LeafIndex proposal_sender; mls::TreeKEMPublicKey tree_after; + bytes tree_hash_after; TreeOperationsTestVector() = default; TreeOperationsTestVector(mls::CipherSuite suite, Scenario scenario); - std::optional verify() const; + std::optional verify(); }; struct TreeKEMTestVector : PseudoRandom diff --git a/lib/mls_vectors/src/mls_vectors.cpp b/lib/mls_vectors/src/mls_vectors.cpp index c813e78c..208d3f51 100644 --- a/lib/mls_vectors/src/mls_vectors.cpp +++ b/lib/mls_vectors/src/mls_vectors.cpp @@ -1451,6 +1451,7 @@ const std::vector TreeOperationsTestVector::TreeOperationsTestVector(mls::CipherSuite suite, Scenario scenario) : PseudoRandom(suite, "tree-operations") + , cipher_suite(suite) , proposal_sender(0) { auto init_priv = prg.hpke_key("init_key"); @@ -1480,6 +1481,7 @@ TreeOperationsTestVector::TreeOperationsTestVector(mls::CipherSuite suite, proposal = Proposal{ Add{ key_package } }; tree_before = tc.pub; + tree_hash_before = tree_before.root_hash(); tree_after = tree_before; tree_after.add_leaf(key_package.leaf_node); @@ -1493,6 +1495,8 @@ TreeOperationsTestVector::TreeOperationsTestVector(mls::CipherSuite suite, tree_before = tc.pub; tree_before.blank_path(LeafIndex{ 4 }); + tree_before.set_hash_all(); + tree_hash_before = tree_before.root_hash(); tree_after = tree_before; tree_after.add_leaf(key_package.leaf_node); @@ -1506,6 +1510,7 @@ TreeOperationsTestVector::TreeOperationsTestVector(mls::CipherSuite suite, proposal = Proposal{ Update{ key_package.leaf_node } }; tree_before = tc.pub; + tree_hash_before = tree_before.root_hash(); tree_after = tree_before; tree_after.update_leaf(proposal_sender, key_package.leaf_node); @@ -1519,6 +1524,7 @@ TreeOperationsTestVector::TreeOperationsTestVector(mls::CipherSuite suite, proposal = Proposal{ Remove{ removed } }; tree_before = tc.pub; + tree_hash_before = tree_before.root_hash(); tree_after = tree_before; tree_after.blank_path(removed); @@ -1533,6 +1539,7 @@ TreeOperationsTestVector::TreeOperationsTestVector(mls::CipherSuite suite, proposal = Proposal{ Remove{ removed } }; tree_before = tc.pub; + tree_hash_before = tree_before.root_hash(); tree_after = tree_before; tree_after.blank_path(removed); @@ -1540,12 +1547,20 @@ TreeOperationsTestVector::TreeOperationsTestVector(mls::CipherSuite suite, break; } } + + tree_after.set_hash_all(); + tree_hash_after = tree_after.root_hash(); } std::optional -TreeOperationsTestVector::verify() const +TreeOperationsTestVector::verify() { + tree_before.suite = cipher_suite; + tree_before.set_hash_all(); + auto tree = tree_before; + VERIFY_EQUAL("tree hash before", tree.root_hash(), tree_hash_before); + auto apply = overloaded{ [&](const Add& add) { tree.add_leaf(add.key_package.leaf_node); }, @@ -1566,6 +1581,9 @@ TreeOperationsTestVector::verify() const var::visit(apply, proposal.content); VERIFY_EQUAL("tree after", tree, tree_after); + tree.set_hash_all(); + VERIFY_EQUAL("tree hash after", tree.root_hash(), tree_hash_after); + return std::nullopt; } From 06a3df6f2c021281c678b91a049c9e1797b42290 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Tue, 1 Aug 2023 16:20:37 -0400 Subject: [PATCH 03/36] Uncomment messages interop test (#354) * Uncomment messages interop test * Add testing of the random-commits test vector --- cmd/interop/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/interop/Makefile b/cmd/interop/Makefile index 78d6d642..3c505c86 100644 --- a/cmd/interop/Makefile +++ b/cmd/interop/Makefile @@ -34,10 +34,10 @@ interop-test: ${BUILD_DIR}/${APP_NAME} ./${BUILD_DIR}/${APP_NAME} -ver 9 <${TEST_VECTOR_DIR}/welcome.json ./${BUILD_DIR}/${APP_NAME} -ver 10 <${TEST_VECTOR_DIR}/tree-operations.json ./${BUILD_DIR}/${APP_NAME} -ver 11 <${TEST_VECTOR_DIR}/treekem.json - # TODO(RLB) Uncomment once the messages test vectors are fixed - #./${BUILD_DIR}/${APP_NAME} -ver 12 <${TEST_VECTOR_DIR}/messages.json + ./${BUILD_DIR}/${APP_NAME} -ver 12 <${TEST_VECTOR_DIR}/messages.json ./${BUILD_DIR}/${APP_NAME} -ver 13 <${TEST_VECTOR_DIR}/passive-client-welcome.json ./${BUILD_DIR}/${APP_NAME} -ver 13 <${TEST_VECTOR_DIR}/passive-client-handling-commit.json + ./${BUILD_DIR}/${APP_NAME} -ver 13 <${TEST_VECTOR_DIR}/passive-client-random.json format: clang-format -i -style=Mozilla src/*.cpp src/*.h From e872de37570b64a04de92de663ca964d7179c286 Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Fri, 4 Aug 2023 15:03:16 -0500 Subject: [PATCH 04/36] configure install target for cmake (#351) * adding install target to cmake configuration * removed version number from the include path --- CMakeLists.txt | 39 ++++++++++++++++++++++++++++++++-- cmake/config.cmake.in | 4 ++++ lib/bytes/CMakeLists.txt | 16 +++++++++++++- lib/hpke/CMakeLists.txt | 16 +++++++++++++- lib/mls_vectors/CMakeLists.txt | 16 +++++++++++++- lib/tls_syntax/CMakeLists.txt | 16 +++++++++++++- third_party/CMakeLists.txt | 13 +++++++++++- 7 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 cmake/config.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bf7f0de..2e4327f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,8 @@ option(SANITIZERS "Enable sanitizers" OFF) set_property(GLOBAL PROPERTY USE_FOLDERS ON) include(CheckCXXCompilerFlag) +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -107,7 +109,7 @@ target_link_libraries(${LIB_NAME} bytes tls_syntax hpke) target_include_directories(${LIB_NAME} PUBLIC $ - $ + $ PRIVATE ${OPENSSL_INCLUDE_DIR} ) @@ -123,5 +125,38 @@ endif() ### Exports ### set(CMAKE_EXPORT_PACKAGE_REGISTRY ON) -export(TARGETS mlspp tls_syntax hpke bytes mls_vectors third_party NAMESPACE MLSPP:: FILE MLSPPConfig.cmake) +export(EXPORT mlspp-targets + NAMESPACE MLSPP:: + FILE ${CMAKE_CURRENT_BINARY_DIR}/mlspp-targets.cmake) export(PACKAGE MLSPP) + +configure_package_config_file(cmake/config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_DATADIR}/mlspp + NO_SET_AND_CHECK_MACRO) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config-version.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +### +### Install +### + +install(TARGETS ${LIB_NAME} EXPORT mlspp-targets) + +install( + DIRECTORY + include/ + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config-version.cmake + ${CMAKE_CURRENT_BINARY_DIR}/mlspp-targets.cmake + DESTINATION + ${CMAKE_INSTALL_DATADIR}/mlspp) + diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in new file mode 100644 index 00000000..39e39cbe --- /dev/null +++ b/cmake/config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/mlspp-targets.cmake) +check_required_components(mlspp) diff --git a/lib/bytes/CMakeLists.txt b/lib/bytes/CMakeLists.txt index 41d9c115..f5ab674e 100644 --- a/lib/bytes/CMakeLists.txt +++ b/lib/bytes/CMakeLists.txt @@ -11,7 +11,21 @@ add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES}) add_dependencies(${CURRENT_LIB_NAME} tls_syntax) target_link_libraries(${CURRENT_LIB_NAME} tls_syntax) target_include_directories(${CURRENT_LIB_NAME} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + PUBLIC + $ + $ +) + +### +### Install +### + +install(TARGETS ${CURRENT_LIB_NAME} EXPORT mlspp-targets) +install( + DIRECTORY + include/ + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} ) ### diff --git a/lib/hpke/CMakeLists.txt b/lib/hpke/CMakeLists.txt index 3a1bb340..30cf5aa9 100644 --- a/lib/hpke/CMakeLists.txt +++ b/lib/hpke/CMakeLists.txt @@ -16,7 +16,21 @@ add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES}) add_dependencies(${CURRENT_LIB_NAME} bytes tls_syntax) target_link_libraries(${CURRENT_LIB_NAME} PRIVATE bytes tls_syntax OpenSSL::Crypto) target_include_directories(${CURRENT_LIB_NAME} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + PUBLIC + $ + $ +) + +### +### Install +### + +install(TARGETS ${CURRENT_LIB_NAME} EXPORT mlspp-targets) +install( + DIRECTORY + include/ + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} ) ### diff --git a/lib/mls_vectors/CMakeLists.txt b/lib/mls_vectors/CMakeLists.txt index 66aef3e8..6816d7f8 100644 --- a/lib/mls_vectors/CMakeLists.txt +++ b/lib/mls_vectors/CMakeLists.txt @@ -11,7 +11,21 @@ add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES}) add_dependencies(${CURRENT_LIB_NAME} mlspp) target_link_libraries(${CURRENT_LIB_NAME} mlspp bytes tls_syntax) target_include_directories(${CURRENT_LIB_NAME} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + PUBLIC + $ + $ +) + +### +### Install +### + +install(TARGETS ${CURRENT_LIB_NAME} EXPORT mlspp-targets) +install( + DIRECTORY + include/ + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} ) ### diff --git a/lib/tls_syntax/CMakeLists.txt b/lib/tls_syntax/CMakeLists.txt index 27f9148d..43c75c20 100644 --- a/lib/tls_syntax/CMakeLists.txt +++ b/lib/tls_syntax/CMakeLists.txt @@ -11,7 +11,21 @@ add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES}) add_dependencies(${CURRENT_LIB_NAME} third_party) target_link_libraries(${CURRENT_LIB_NAME} third_party) target_include_directories(${CURRENT_LIB_NAME} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + PUBLIC + $ + $ +) + +### +### Install +### + +install(TARGETS ${CURRENT_LIB_NAME} EXPORT mlspp-targets) +install( + DIRECTORY + include/ + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} ) ### diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index b991cb64..c1612f59 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -2,5 +2,16 @@ set(CURRENT_LIB_NAME third_party) add_library(${CURRENT_LIB_NAME} INTERFACE) target_include_directories(${CURRENT_LIB_NAME} - INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} + INTERFACE + $ + $ ) + +### +### Install +### + +install(TARGETS ${CURRENT_LIB_NAME} EXPORT mlspp-targets) +install(FILES variant.hpp + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) From a415b28b33b385a45cdbfea7132a011fa1a3aa14 Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Tue, 13 Jun 2023 17:56:26 -0500 Subject: [PATCH 05/36] integrating userinfo_vc --- include/mls/credential.h | 23 ++++++++++++++++++++--- lib/hpke/src/group.cpp | 7 +++---- src/core_types.cpp | 3 ++- src/credential.cpp | 10 ++++++++++ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/include/mls/credential.h b/include/mls/credential.h index c7e251df..2592f0c9 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -11,9 +11,9 @@ namespace mls { // } BasicCredential; struct BasicCredential { - BasicCredential() {} + BasicCredential() = default; - BasicCredential(bytes identity_in) + explicit BasicCredential(bytes identity_in) : identity(std::move(identity_in)) { } @@ -47,6 +47,20 @@ struct X509Credential SignatureScheme _signature_scheme; }; +struct UserInfoVCCredential +{ + UserInfoVCCredential() = default; + + explicit UserInfoVCCredential(bytes userinfo_vc_jwt) + : userinfo_vc_jwt(std::move(userinfo_vc_jwt)) + { + } + + bytes userinfo_vc_jwt; + + TLS_SERIALIZABLE(userinfo_vc_jwt) +}; + tls::ostream& operator<<(tls::ostream& str, const X509Credential& obj); @@ -61,6 +75,7 @@ enum struct CredentialType : uint16_t reserved = 0, basic = 1, x509 = 2, + userinfo_vc = 3, // GREASE values, included here mainly so that debugger output looks nice GREASE_0 = 0x0A0A, @@ -103,6 +118,7 @@ class Credential static Credential basic(const bytes& identity); static Credential x509(const std::vector& der_chain); + static Credential userinfo_vc(const bytes& userinfo_vc_jwt); bool valid_for(const SignaturePublicKey& pub) const; @@ -110,7 +126,7 @@ class Credential TLS_TRAITS(tls::variant) private: - var::variant _cred; + var::variant _cred; }; } // namespace mls @@ -119,5 +135,6 @@ namespace tls { TLS_VARIANT_MAP(mls::CredentialType, mls::BasicCredential, basic) TLS_VARIANT_MAP(mls::CredentialType, mls::X509Credential, x509) +TLS_VARIANT_MAP(mls::CredentialType, mls::UserInfoVCCredential, userinfo_vc) } // namespace TLS diff --git a/lib/hpke/src/group.cpp b/lib/hpke/src/group.cpp index 5afcd025..e148141d 100644 --- a/lib/hpke/src/group.cpp +++ b/lib/hpke/src/group.cpp @@ -530,10 +530,9 @@ struct ECKeyGroup : public EVPGroup int curve_nid; #if !defined(WITH_OPENSSL3) - EC_KEY* new_ec_key() const - { - return EC_KEY_new_by_curve_name(curve_nid); - } + // clang-format off + EC_KEY* new_ec_key() const { return EC_KEY_new_by_curve_name(curve_nid); } + // clang-format on static EVP_PKEY* to_pkey(EC_KEY* eckey) { diff --git a/src/core_types.cpp b/src/core_types.cpp index 6bfdc08f..d7a89218 100644 --- a/src/core_types.cpp +++ b/src/core_types.cpp @@ -45,9 +45,10 @@ const std::array all_supported_ciphersuites = { CipherSuite::ID::X448_CHACHA20POLY1305_SHA512_Ed448, }; -const std::array all_supported_credentials = { +const std::array all_supported_credentials = { CredentialType::basic, CredentialType::x509, + CredentialType::userinfo_vc }; Capabilities diff --git a/src/credential.cpp b/src/credential.cpp index 88737f32..a378f99e 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -135,6 +135,14 @@ Credential::x509(const std::vector& der_chain) return cred; } +Credential +Credential::userinfo_vc(const bytes& userinfo_vc_jwt) +{ + Credential cred; + cred._cred = UserInfoVCCredential{ userinfo_vc_jwt }; + return cred; +} + bool Credential::valid_for(const SignaturePublicKey& pub) const { @@ -142,6 +150,8 @@ Credential::valid_for(const SignaturePublicKey& pub) const [&](const X509Credential& x509) { return x509.valid_for(pub); }, [](const BasicCredential& /* basic */) { return true; }, + + [](const UserInfoVCCredential&) { return true; }, }; return var::visit(pub_key_match, _cred); From 16693e95bb2222f1d12cebc01a5213e0a49c972d Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 3 Jul 2023 15:10:39 -0400 Subject: [PATCH 06/36] Implement multi-credentials --- include/mls/credential.h | 73 ++++++++++++++++++++++++++++-- include/mls/crypto.h | 1 + src/credential.cpp | 92 ++++++++++++++++++++++++++++++++++---- src/crypto.cpp | 1 + test/credential.cpp | 95 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 250 insertions(+), 12 deletions(-) diff --git a/include/mls/credential.h b/include/mls/credential.h index 2592f0c9..b02f5114 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -76,6 +76,7 @@ enum struct CredentialType : uint16_t basic = 1, x509 = 2, userinfo_vc = 3, + multi = 4, // GREASE values, included here mainly so that debugger output looks nice GREASE_0 = 0x0A0A, @@ -95,6 +96,31 @@ enum struct CredentialType : uint16_t GREASE_E = 0xEAEA, }; +// struct { +// Credential credential; +// SignaturePublicKey credential_key; +// opaque signature; +// } CredentialBinding +// +// struct { +// CredentialBinding bindings; +// } MultiCredential; +struct CredentialBinding; +struct CredentialBindingInput; + +struct MultiCredential +{ + MultiCredential() = default; + MultiCredential(const std::vector& binding_inputs, + const SignaturePublicKey& signature_key); + + std::vector bindings; + + bool valid_for(const SignaturePublicKey& pub) const; + + TLS_SERIALIZABLE(bindings) +}; + // struct { // CredentialType credential_type; // select (credential_type) { @@ -105,9 +131,10 @@ enum struct CredentialType : uint16_t // opaque cert_data<1..2^24-1>; // }; // } Credential; -class Credential +struct Credential { -public: + Credential() = default; + CredentialType type() const; template @@ -119,6 +146,9 @@ class Credential static Credential basic(const bytes& identity); static Credential x509(const std::vector& der_chain); static Credential userinfo_vc(const bytes& userinfo_vc_jwt); + static Credential multi( + const std::vector& binding_inputs, + const SignaturePublicKey& signature_key); bool valid_for(const SignaturePublicKey& pub) const; @@ -126,7 +156,41 @@ class Credential TLS_TRAITS(tls::variant) private: - var::variant _cred; + using SpecificCredential = + var::variant; + + Credential(SpecificCredential _cred); + SpecificCredential _cred; +}; + +// XXX(RLB): This struct needs to appear below Credential so that all types are +// concrete at the appropriate points. +struct CredentialBindingInput +{ + CipherSuite cipher_suite; + Credential credential; + const SignaturePrivateKey& credential_priv; +}; + +struct CredentialBinding +{ + CipherSuite cipher_suite; + Credential credential; + SignaturePublicKey credential_key; + bytes signature; + + CredentialBinding() = default; + CredentialBinding(CipherSuite suite_in, + Credential credential_in, + const SignaturePrivateKey& credential_priv, + const SignaturePublicKey& signature_key); + + bool valid_for(const SignaturePublicKey& pub) const; + + TLS_SERIALIZABLE(credential, credential_key, signature) + +private: + bytes to_be_signed(const SignaturePublicKey& signature_key) const; }; } // namespace mls @@ -136,5 +200,6 @@ namespace tls { TLS_VARIANT_MAP(mls::CredentialType, mls::BasicCredential, basic) TLS_VARIANT_MAP(mls::CredentialType, mls::X509Credential, x509) TLS_VARIANT_MAP(mls::CredentialType, mls::UserInfoVCCredential, userinfo_vc) +TLS_VARIANT_MAP(mls::CredentialType, mls::MultiCredential, multi) -} // namespace TLS +} // namespace tls diff --git a/include/mls/crypto.h b/include/mls/crypto.h index ab507ebb..42b89f67 100644 --- a/include/mls/crypto.h +++ b/include/mls/crypto.h @@ -205,6 +205,7 @@ extern const std::string mls_content; extern const std::string leaf_node; extern const std::string key_package; extern const std::string group_info; +extern const std::string multi_credential; } // namespace sign_label struct SignaturePublicKey diff --git a/src/credential.cpp b/src/credential.cpp index a378f99e..4fbb689f 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -109,6 +109,75 @@ operator==(const X509Credential& lhs, const X509Credential& rhs) return lhs.der_chain == rhs.der_chain; } +/// +/// CredentialBinding and MultiCredential +/// + +struct CredentialBindingTBS +{ + const CipherSuite& cipher_suite; + const Credential& credential; + const SignaturePublicKey& credential_key; + const SignaturePublicKey& signature_key; + + TLS_SERIALIZABLE(cipher_suite, credential, credential_key, signature_key) +}; + +CredentialBinding::CredentialBinding(CipherSuite cipher_suite_in, + Credential credential_in, + const SignaturePrivateKey& credential_priv, + const SignaturePublicKey& signature_key) + : cipher_suite(cipher_suite_in) + , credential(std::move(credential_in)) + , credential_key(credential_priv.public_key) +{ + if (!credential.valid_for(credential_key)) { + throw InvalidParameterError("Credential key does not match credential"); + } + + signature = credential_priv.sign( + cipher_suite, sign_label::multi_credential, to_be_signed(signature_key)); +} + +bytes +CredentialBinding::to_be_signed(const SignaturePublicKey& signature_key) const +{ + return tls::marshal(CredentialBindingTBS{ + cipher_suite, credential, credential_key, signature_key }); +} + +bool +CredentialBinding::valid_for(const SignaturePublicKey& signature_key) const +{ + auto valid_self = credential.valid_for(credential_key); + auto valid_other = credential_key.verify(cipher_suite, + sign_label::multi_credential, + to_be_signed(signature_key), + signature); + + return valid_self && valid_other; +} + +MultiCredential::MultiCredential( + const std::vector& binding_inputs, + const SignaturePublicKey& signature_key) +{ + bindings = + stdx::transform(binding_inputs, [&](auto&& input) { + return CredentialBinding(input.cipher_suite, + input.credential, + input.credential_priv, + signature_key); + }); +} + +bool +MultiCredential::valid_for(const SignaturePublicKey& pub) const +{ + return stdx::all_of( + bindings, [&](const auto& binding) { return binding.valid_for(pub); }); +} + /// /// Credential /// @@ -122,17 +191,20 @@ Credential::type() const Credential Credential::basic(const bytes& identity) { - Credential cred; - cred._cred = BasicCredential{ identity }; - return cred; + return Credential(BasicCredential{ identity }); } Credential Credential::x509(const std::vector& der_chain) { - Credential cred; - cred._cred = X509Credential{ der_chain }; - return cred; + return Credential(X509Credential{ der_chain }); +} + +Credential +Credential::multi(const std::vector& binding_inputs, + const SignaturePublicKey& signature_key) +{ + return Credential(MultiCredential{ binding_inputs, signature_key }); } Credential @@ -148,13 +220,17 @@ Credential::valid_for(const SignaturePublicKey& pub) const { const auto pub_key_match = overloaded{ [&](const X509Credential& x509) { return x509.valid_for(pub); }, - [](const BasicCredential& /* basic */) { return true; }, - [](const UserInfoVCCredential&) { return true; }, + [&](const MultiCredential& multi) { return multi.valid_for(pub); }, }; return var::visit(pub_key_match, _cred); } +Credential::Credential(SpecificCredential specific) + : _cred(std::move(specific)) +{ +} + } // namespace mls diff --git a/src/crypto.cpp b/src/crypto.cpp index 3236e026..9611fbd5 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -362,6 +362,7 @@ const std::string mls_content = "FramedContentTBS"; const std::string leaf_node = "LeafNodeTBS"; const std::string key_package = "KeyPackageTBS"; const std::string group_info = "GroupInfoTBS"; +const std::string multi_credential = "MultiCredential"; } // namespace sign_label struct SignContent diff --git a/test/credential.cpp b/test/credential.cpp index 34ba3b1c..5c1a342e 100644 --- a/test/credential.cpp +++ b/test/credential.cpp @@ -18,6 +18,101 @@ TEST_CASE("Basic Credential") REQUIRE(basic.identity == user_id); } +TEST_CASE("X.509 Credential") +{ + auto suite = CipherSuite{ CipherSuite::ID::P256_AES128GCM_SHA256_P256 }; + + const auto priv_data = from_hex( + "afdee46291cd304277fdd2599f125c4cc0aa1e539df7fd7c8032f632c5d0d3a4"); + + const auto leaf_der = + from_hex("308201363081dda003020102020100300a06082a8648ce3d0403023014311230" + "100603550403130946616b6520434120303020170d3233303730313138353334" + "385a180f32313232303630393138353334385a30123110300e06035504031307" + "5375626a6563743059301306072a8648ce3d020106082a8648ce3d0301070342" + "00041dd062efebc93e62c8c5a4984e6a1df6192ae87eaea0666e4fc5ff8d610c" + "2e465d077fafd72e249ce5ba602188df4203b78422380239bbcb9ab88b6940ba" + "f6a8a320301e300e0603551d0f0101ff0404030202a4300c0603551d130101ff" + "04023000300a06082a8648ce3d0403020348003045022041d8341e498806ec4b" + "7aa097deaeffe6b752ecc49e5093ad0d2ffba7ca629eee0221009f24accf35a9" + "05dcd8720ad6dfa881e7614f3b6abd9f49ebf21e2439060402eb"); + + auto priv = SignaturePrivateKey::parse(suite, priv_data); + auto pub = priv.public_key; + + auto cred = Credential::x509({ leaf_der }); + REQUIRE(cred.valid_for(pub)); + + const auto& x509 = cred.get(); + REQUIRE(x509.der_chain.size() == 1); + REQUIRE(x509.der_chain.front().data == leaf_der); +} + +TEST_CASE("Multi Credential") +{ + auto suite = CipherSuite{ CipherSuite::ID::P256_AES128GCM_SHA256_P256 }; + + // First X.509 Credential + const auto leaf_der_1 = + from_hex("308201363081dda003020102020100300a06082a8648ce3d0403023014311230" + "100603550403130946616b6520434120303020170d3233303730313138353334" + "385a180f32313232303630393138353334385a30123110300e06035504031307" + "5375626a6563743059301306072a8648ce3d020106082a8648ce3d0301070342" + "00041dd062efebc93e62c8c5a4984e6a1df6192ae87eaea0666e4fc5ff8d610c" + "2e465d077fafd72e249ce5ba602188df4203b78422380239bbcb9ab88b6940ba" + "f6a8a320301e300e0603551d0f0101ff0404030202a4300c0603551d130101ff" + "04023000300a06082a8648ce3d0403020348003045022041d8341e498806ec4b" + "7aa097deaeffe6b752ecc49e5093ad0d2ffba7ca629eee0221009f24accf35a9" + "05dcd8720ad6dfa881e7614f3b6abd9f49ebf21e2439060402eb"); + const auto cred_priv_data_1 = from_hex( + "afdee46291cd304277fdd2599f125c4cc0aa1e539df7fd7c8032f632c5d0d3a4"); + + auto cred_priv_1 = SignaturePrivateKey::parse(suite, cred_priv_data_1); + auto cred_pub_1 = cred_priv_1.public_key; + + auto cred_1 = Credential::x509({ leaf_der_1 }); + REQUIRE(cred_1.valid_for(cred_pub_1)); + + // Second X.509 Credential + const auto leaf_der_2 = + from_hex("308201353081dda003020102020100300a06082a8648ce3d0403023014311230" + "100603550403130946616b6520434120303020170d3233303730313139303234" + "305a180f32313232303630393139303234305a30123110300e06035504031307" + "5375626a6563743059301306072a8648ce3d020106082a8648ce3d0301070342" + "0004e7f7987f024d0d1b420018a585929e690f95b6fe7b23ec1ff6532b1c55c4" + "75ef36b826e4b54bd60b8823f3fc222c28369771a9ed0a644df351e16ad495dc" + "fb54a320301e300e0603551d0f0101ff0404030202a4300c0603551d130101ff" + "04023000300a06082a8648ce3d040302034700304402200d7e0e5362cfe4d551" + "cbb6a5b2b64541e30e86e10e734e84c1e24b46d1e098bc022037fc32a59b4062" + "c14b3323a20a0c7a5e05bbd3f27e22dc225ddd69ca771b90fc"); + const auto cred_priv_data_2 = from_hex( + "8915f49863cb24d6553fab0036da18a0fec431ae0cc94255010f6ed35555631e"); + + auto cred_priv_2 = SignaturePrivateKey::parse(suite, cred_priv_data_2); + auto cred_pub_2 = cred_priv_2.public_key; + + auto cred_2 = Credential::x509({ leaf_der_2 }); + REQUIRE(cred_2.valid_for(cred_pub_2)); + + // Multi-Credential + auto priv = SignaturePrivateKey::generate(suite); + auto pub = priv.public_key; + + auto cred = Credential::multi( + { { suite, cred_1, cred_priv_1 }, { suite, cred_2, cred_priv_2 } }, pub); + REQUIRE(cred.valid_for(pub)); + + auto multi = cred.get(); + const auto& bindings = multi.bindings; + REQUIRE(bindings.size() == 2); + REQUIRE(bindings[0].credential == cred_1); + REQUIRE(bindings[0].credential_key == cred_pub_1); + REQUIRE(bindings[0].credential.valid_for(bindings[0].credential_key)); + REQUIRE(bindings[1].credential == cred_2); + REQUIRE(bindings[1].credential_key == cred_pub_2); + REQUIRE(bindings[1].credential.valid_for(bindings[1].credential_key)); +} + TEST_CASE("X509 Credential Depth 2") { // Chain is of depth 2 From a9472e4a832ab742bd1679744ae96c003fa8db04 Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Wed, 19 Jul 2023 17:09:38 -0500 Subject: [PATCH 07/36] clang-format adjustments --- include/mls/credential.h | 6 ++++-- lib/hpke/src/group.cpp | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/mls/credential.h b/include/mls/credential.h index b02f5114..e086e845 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -156,8 +156,10 @@ struct Credential TLS_TRAITS(tls::variant) private: - using SpecificCredential = - var::variant; + using SpecificCredential = var::variant; Credential(SpecificCredential _cred); SpecificCredential _cred; diff --git a/lib/hpke/src/group.cpp b/lib/hpke/src/group.cpp index e148141d..c6b42b3b 100644 --- a/lib/hpke/src/group.cpp +++ b/lib/hpke/src/group.cpp @@ -530,9 +530,7 @@ struct ECKeyGroup : public EVPGroup int curve_nid; #if !defined(WITH_OPENSSL3) - // clang-format off EC_KEY* new_ec_key() const { return EC_KEY_new_by_curve_name(curve_nid); } - // clang-format on static EVP_PKEY* to_pkey(EC_KEY* eckey) { From 2e2fd74dceb6f2c1451e3b1ee81dfea058a6506a Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Mon, 24 Jul 2023 15:26:10 -0500 Subject: [PATCH 08/36] adding import and export jwk keys into signatures --- CMakeLists.txt | 19 ++- alternatives/openssl_3/vcpkg.json | 3 +- include/mls/credential.h | 4 +- include/mls/crypto.h | 8 ++ lib/bytes/CMakeLists.txt | 6 +- lib/bytes/include/bytes/bytes.h | 12 ++ lib/bytes/src/bytes.cpp | 124 +++++++++++++++- lib/bytes/test/bytes.cpp | 28 ++++ lib/hpke/CMakeLists.txt | 7 +- lib/hpke/include/hpke/signature.h | 7 + lib/hpke/src/group.cpp | 228 +++++++++++++++++++++++++++++- lib/hpke/src/group.h | 9 ++ lib/hpke/src/signature.cpp | 121 ++++++++++++++++ lib/tls_syntax/CMakeLists.txt | 2 +- src/crypto.cpp | 30 ++++ test/crypto.cpp | 135 +++++++++++++++++- vcpkg.json | 3 +- 17 files changed, 730 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e4327f7..bf9d8798 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,8 @@ target_include_directories(${LIB_NAME} ${OPENSSL_INCLUDE_DIR} ) +install(TARGETS ${LIB_NAME} EXPORT mlspp-targets) + ### ### Tests ### @@ -125,9 +127,7 @@ endif() ### Exports ### set(CMAKE_EXPORT_PACKAGE_REGISTRY ON) -export(EXPORT mlspp-targets - NAMESPACE MLSPP:: - FILE ${CMAKE_CURRENT_BINARY_DIR}/mlspp-targets.cmake) +export(EXPORT mlspp-targets NAMESPACE MLSPP:: FILE mlspp-targets.cmake) export(PACKAGE MLSPP) configure_package_config_file(cmake/config.cmake.in @@ -144,8 +144,6 @@ write_basic_package_version_file( ### Install ### -install(TARGETS ${LIB_NAME} EXPORT mlspp-targets) - install( DIRECTORY include/ @@ -156,7 +154,16 @@ install( FILES ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config-version.cmake - ${CMAKE_CURRENT_BINARY_DIR}/mlspp-targets.cmake + DESTINATION + ${CMAKE_INSTALL_DATADIR}/mlspp) + +install( + EXPORT + mlspp-targets + NAMESPACE + MLSPP:: + FILE + mlspp-targets.cmake DESTINATION ${CMAKE_INSTALL_DATADIR}/mlspp) diff --git a/alternatives/openssl_3/vcpkg.json b/alternatives/openssl_3/vcpkg.json index 4b4d7657..97c9cc3e 100644 --- a/alternatives/openssl_3/vcpkg.json +++ b/alternatives/openssl_3/vcpkg.json @@ -7,7 +7,8 @@ "name": "openssl", "version>=": "3.0.7" }, - "doctest" + "doctest", + "nlohmann-json" ], "builtin-baseline": "5908d702d61cea1429b223a0b7a10ab86bad4c78", "overrides": [ diff --git a/include/mls/credential.h b/include/mls/credential.h index e086e845..981610cb 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -11,9 +11,9 @@ namespace mls { // } BasicCredential; struct BasicCredential { - BasicCredential() = default; + BasicCredential() {} - explicit BasicCredential(bytes identity_in) + BasicCredential(bytes identity_in) : identity(std::move(identity_in)) { } diff --git a/include/mls/crypto.h b/include/mls/crypto.h index 42b89f67..d25653bc 100644 --- a/include/mls/crypto.h +++ b/include/mls/crypto.h @@ -210,6 +210,9 @@ extern const std::string multi_credential; struct SignaturePublicKey { + static SignaturePublicKey from_jwk(CipherSuite suite, + const std::string& json_str); + bytes data; bool verify(const CipherSuite& suite, @@ -217,6 +220,8 @@ struct SignaturePublicKey const bytes& message, const bytes& signature) const; + std::string to_jwk(CipherSuite suite) const; + TLS_SERIALIZABLE(data) }; @@ -225,6 +230,8 @@ struct SignaturePrivateKey static SignaturePrivateKey generate(CipherSuite suite); static SignaturePrivateKey parse(CipherSuite suite, const bytes& data); static SignaturePrivateKey derive(CipherSuite suite, const bytes& secret); + static SignaturePrivateKey from_jwk(CipherSuite suite, + const std::string& json_str); SignaturePrivateKey() = default; @@ -236,6 +243,7 @@ struct SignaturePrivateKey const bytes& message) const; void set_public_key(CipherSuite suite); + std::string to_jwk(CipherSuite suite) const; TLS_SERIALIZABLE(data) diff --git a/lib/bytes/CMakeLists.txt b/lib/bytes/CMakeLists.txt index f5ab674e..21a78744 100644 --- a/lib/bytes/CMakeLists.txt +++ b/lib/bytes/CMakeLists.txt @@ -9,7 +9,11 @@ file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES}) add_dependencies(${CURRENT_LIB_NAME} tls_syntax) -target_link_libraries(${CURRENT_LIB_NAME} tls_syntax) +target_link_libraries(${CURRENT_LIB_NAME} + PUBLIC + tls_syntax + PRIVATE + OpenSSL::Crypto) target_include_directories(${CURRENT_LIB_NAME} PUBLIC $ diff --git a/lib/bytes/include/bytes/bytes.h b/lib/bytes/include/bytes/bytes.h index ddb5dacf..49d7ead7 100644 --- a/lib/bytes/include/bytes/bytes.h +++ b/lib/bytes/include/bytes/bytes.h @@ -115,4 +115,16 @@ to_hex(const bytes& data); bytes from_hex(const std::string& hex); +std::string +to_base64(const bytes& data); + +std::string +to_base64url(const bytes& data); + +bytes +from_base64(const std::string& enc); + +bytes +from_base64url(const std::string& enc); + } // namespace bytes_ns diff --git a/lib/bytes/src/bytes.cpp b/lib/bytes/src/bytes.cpp index 509e2532..b63798cf 100644 --- a/lib/bytes/src/bytes.cpp +++ b/lib/bytes/src/bytes.cpp @@ -1,7 +1,10 @@ #include +#include #include -#include +#include +#include +#include #include #include @@ -137,4 +140,123 @@ operator!=(const std::vector& lhs, const bytes_ns::bytes& rhs) return rhs != lhs; } +std::string +to_base64(const bytes& data) +{ + bool done = false; + int result = 0; + + if (data.empty()) { + return ""; + } + + BIO* b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + BIO* out = BIO_new(BIO_s_mem()); + BIO_push(b64, out); + + while (!done) { + result = BIO_write(b64, data.data(), static_cast(data.size())); + + if (result <= 0) { + if (BIO_should_retry(b64)) { + continue; + } + throw std::runtime_error("base64 encode failed"); + } + done = true; + } + BIO_flush(b64); + char* string_ptr = nullptr; + // long string_len = BIO_get_mem_data(out, &string_ptr); + // BIO_get_mem_data failed clang-tidy + long string_len = BIO_ctrl(out, BIO_CTRL_INFO, 0, &string_ptr); + auto return_value = std::string(string_ptr, string_len); + + BIO_set_close(out, BIO_NOCLOSE); + BIO_free(b64); + BIO_free(out); + return return_value; +} + +std::string +to_base64url(const bytes& data) +{ + if (data.empty()) { + return ""; + } + + std::string return_value = to_base64(data); + + // remove the end padding + auto sz = return_value.find_first_of('='); + + if (sz != std::string::npos) { + return_value = return_value.substr(0, sz); + } + + // replace plus with hyphen + std::replace(return_value.begin(), return_value.end(), '+', '-'); + + // replace slash with underscore + std::replace(return_value.begin(), return_value.end(), '/', '_'); + return return_value; +} + +bytes +from_base64(const std::string& enc) +{ + if (enc.length() == 0) { + return {}; + } + + if (enc.length() % 4 != 0) { + throw std::runtime_error("Base64 length is not divisible by 4"); + } + bytes input = from_ascii(enc); + bytes output(input.size() / 4 * 3); + int output_buffer_length = static_cast(output.size()); + EVP_ENCODE_CTX* ctx = EVP_ENCODE_CTX_new(); + EVP_DecodeInit(ctx); + + int result = EVP_DecodeUpdate(ctx, + output.data(), + &output_buffer_length, + input.data(), + static_cast(input.size())); + + if (result == -1) { + auto code = ERR_get_error(); + throw std::runtime_error(ERR_error_string(code, nullptr)); + } + + if (result == 0 && enc.substr(enc.length() - 2, enc.length()) == "==") { + output = output.slice(0, output.size() - 2); + } else if (result == 0 && enc.substr(enc.length() - 1, enc.length()) == "=") { + output = output.slice(0, output.size() - 1); + } else if (result == 0) { + throw std::runtime_error("Base64 padding was malformed."); + } + EVP_DecodeFinal(ctx, output.data(), &output_buffer_length); + EVP_ENCODE_CTX_free(ctx); + return output; +} + +bytes +from_base64url(const std::string& enc) +{ + if (enc.empty()) { + return {}; + } + std::string enc_copy = enc; // copy + std::replace(enc_copy.begin(), enc_copy.end(), '-', '+'); + std::replace(enc_copy.begin(), enc_copy.end(), '_', '/'); + + while (enc_copy.length() % 4 != 0) { + enc_copy += "="; + } + bytes return_value = from_base64(enc_copy); + return return_value; +} + } // namespace bytes_ns diff --git a/lib/bytes/test/bytes.cpp b/lib/bytes/test/bytes.cpp index a28dbf9f..69a764df 100644 --- a/lib/bytes/test/bytes.cpp +++ b/lib/bytes/test/bytes.cpp @@ -2,6 +2,7 @@ #include #include #include +#include using namespace bytes_ns; using namespace std::literals::string_literals; @@ -40,6 +41,33 @@ TEST_CASE("To/from hex/ASCII") REQUIRE(from_ascii(str) == ascii); } +TEST_CASE("To Base64 / To Base64Url") +{ + struct KnownAnswerTest + { + bytes data; + std::string base64; + std::string base64u; + }; + + const std::vector cases{ + { from_ascii("hello there"), "aGVsbG8gdGhlcmU=", "aGVsbG8gdGhlcmU" }, + { from_ascii("A B C D E F "), "QSBCIEMgRCBFIEYg", "QSBCIEMgRCBFIEYg" }, + { from_ascii("hello\xfethere"), "aGVsbG/+dGhlcmU=", "aGVsbG_-dGhlcmU" }, + { from_ascii("\xfe"), "/g==", "_g" }, + { from_ascii("\x01\x02"), "AQI=", "AQI" }, + { from_ascii("\x01"), "AQ==", "AQ" }, + { from_ascii(""), "", "" }, + }; + + for (const auto& tc : cases) { + REQUIRE(to_base64(tc.data) == tc.base64); + REQUIRE(to_base64url(tc.data) == tc.base64u); + REQUIRE(from_base64(tc.base64) == tc.data); + REQUIRE(from_base64url(tc.base64u) == tc.data); + } +} + TEST_CASE("Operators") { const auto lhs = from_hex("00010203"); diff --git a/lib/hpke/CMakeLists.txt b/lib/hpke/CMakeLists.txt index 30cf5aa9..afbc32f1 100644 --- a/lib/hpke/CMakeLists.txt +++ b/lib/hpke/CMakeLists.txt @@ -3,6 +3,7 @@ set(CURRENT_LIB_NAME hpke) ### ### Dependencies ### +find_package(nlohmann_json REQUIRED) find_package(OpenSSL 1.1 REQUIRED) ### @@ -14,7 +15,11 @@ file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES}) add_dependencies(${CURRENT_LIB_NAME} bytes tls_syntax) -target_link_libraries(${CURRENT_LIB_NAME} PRIVATE bytes tls_syntax OpenSSL::Crypto) +target_link_libraries(${CURRENT_LIB_NAME} + PRIVATE + nlohmann_json::nlohmann_json OpenSSL::Crypto + PUBLIC + bytes tls_syntax) target_include_directories(${CURRENT_LIB_NAME} PUBLIC $ diff --git a/lib/hpke/include/hpke/signature.h b/lib/hpke/include/hpke/signature.h index 8ee0b39b..378f22a5 100644 --- a/lib/hpke/include/hpke/signature.h +++ b/lib/hpke/include/hpke/signature.h @@ -50,6 +50,13 @@ struct Signature virtual std::unique_ptr deserialize_private( const bytes& skm) const; + virtual std::unique_ptr import_jwk_private( + const std::string& json_str) const; + virtual std::unique_ptr import_jwk( + const std::string& json_str) const; + virtual std::string export_jwk_private(const bytes& env) const; + virtual std::string export_jwk(const bytes& env) const; + virtual bytes sign(const bytes& data, const PrivateKey& sk) const = 0; virtual bool verify(const bytes& data, const bytes& sig, diff --git a/lib/hpke/src/group.cpp b/lib/hpke/src/group.cpp index c6b42b3b..47dc441e 100644 --- a/lib/hpke/src/group.cpp +++ b/lib/hpke/src/group.cpp @@ -526,11 +526,170 @@ struct ECKeyGroup : public EVPGroup #endif } + // EC Key + void get_coordinates(const Group::PublicKey& pk, + bytes& x, + bytes& y) const override + { + auto bnX = make_typed_unique(BN_new()); + auto bnY = make_typed_unique(BN_new()); + const auto& rpk = dynamic_cast(pk); + +#if defined(WITH_OPENSSL3) + OSSL_PARAM* param = nullptr; + + if (1 != EVP_PKEY_todata(rpk.pkey.get(), EVP_PKEY_PUBLIC_KEY, ¶m)) { + throw openssl_error(); + } + auto param_ptr = make_typed_unique(param); + const OSSL_PARAM* pk_param = + OSSL_PARAM_locate_const(param_ptr.get(), OSSL_PKEY_PARAM_PUB_KEY); + + if (pk_param == nullptr) { + throw std::runtime_error("Failed to locate OSSL_PKEY_PARAM_PUB_KEY"); + } + size_t len = 0; + + if (1 != OSSL_PARAM_get_octet_string(pk_param, nullptr, 0, &len)) { + throw std::runtime_error("Failed to get OSSL_PKEY_PARAM_PUB_KEY len"); + } + bytes buf(len); + void* data_ptr = buf.data(); + + if (1 != OSSL_PARAM_get_octet_string(pk_param, &data_ptr, len, nullptr)) { + throw std::runtime_error("Failed to get OSSL_PKEY_PARAM_PUB_KEY data"); + } + auto group = make_typed_unique( + EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid)); + + if (group == nullptr) { + throw openssl_error(); + } + auto point = make_typed_unique(EC_POINT_new(group.get())); + + if (point == nullptr) { + throw openssl_error(); + } + const auto* oct_ptr = static_cast(data_ptr); + + if (1 != + EC_POINT_oct2point(group.get(), point.get(), oct_ptr, len, nullptr)) { + throw openssl_error(); + } + + if (1 != EC_POINT_get_affine_coordinates( + group.get(), point.get(), bnX.get(), bnY.get(), nullptr)) { + throw openssl_error(); + } +#else + auto* pub = EVP_PKEY_get0_EC_KEY(rpk.pkey.get()); + const auto* point = EC_KEY_get0_public_key(pub); + const auto* group = EC_KEY_get0_group(pub); + + if (1 != EC_POINT_get_affine_coordinates_GFp( + group, point, bnX.get(), bnY.get(), nullptr)) { + throw openssl_error(); + } +#endif + auto outX = bytes(BN_num_bytes(bnX.get())); + auto outY = bytes(BN_num_bytes(bnY.get())); + + if (BN_bn2bin(bnX.get(), outX.data()) != int(outX.size())) { + throw openssl_error(); + } + + if (BN_bn2bin(bnY.get(), outY.data()) != int(outY.size())) { + throw openssl_error(); + } + const auto zeros_neededX = dh_size - outX.size(); + const auto zeros_neededY = dh_size - outY.size(); + auto leading_zerosX = bytes(zeros_neededX, 0); + auto leading_zerosY = bytes(zeros_neededY, 0); + x = leading_zerosX + outX; + y = leading_zerosY + outY; + } + + // EC Key + std::unique_ptr set_coordinates( + const bytes& x, + const bytes& y) const override + { + auto bnX = make_typed_unique( + BN_bin2bn(x.data(), static_cast(x.size()), nullptr)); + auto bnY = make_typed_unique( + BN_bin2bn(y.data(), static_cast(y.size()), nullptr)); + + if (bnX == nullptr || bnY == nullptr) { + throw std::runtime_error("Failed to convert bnX or bnY"); + } + +#if defined(WITH_OPENSSL3) + auto* group = EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid); + auto group_ptr = make_typed_unique(group); + + auto* point = EC_POINT_new(group); + auto point_ptr = make_typed_unique(point); + + if (point == nullptr || group == nullptr) { + throw std::runtime_error("Failed to create EC_POINT or EC_GROUP"); + } + + if (1 != EC_POINT_set_affine_coordinates( + group, point, bnX.get(), bnY.get(), nullptr)) { + throw openssl_error(); + } + + const auto point_size = EC_POINT_point2oct( + group, point, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); + + if (0 == point_size) { + throw openssl_error(); + } + bytes pub(point_size); + + if (EC_POINT_point2oct(group, + point, + POINT_CONVERSION_UNCOMPRESSED, + pub.data(), + point_size, + nullptr) != point_size) { + throw openssl_error(); + } + auto key = public_evp_key(pub); + return std::make_unique(key.release()); +#else + auto eckey = make_typed_unique(new_ec_key()); + + if (eckey == nullptr) { + throw std::runtime_error("Failed to create EC_KEY"); + } + + const auto* group = EC_KEY_get0_group(eckey.get()); + auto* point = EC_POINT_new(group); + auto point_ptr = make_typed_unique(point); + + if (1 != EC_POINT_set_affine_coordinates_GFp( + group, point, bnX.get(), bnY.get(), nullptr)) { + throw openssl_error(); + } + + if (1 != EC_KEY_set_public_key(eckey.get(), point)) { + throw openssl_error(); + } + return std::make_unique(to_pkey(eckey.release())); +#endif + } + private: int curve_nid; #if !defined(WITH_OPENSSL3) - EC_KEY* new_ec_key() const { return EC_KEY_new_by_curve_name(curve_nid); } + // clang-format off + EC_KEY* new_ec_key() const + { + return EC_KEY_new_by_curve_name(curve_nid); + } + // clang-format on static EVP_PKEY* to_pkey(EC_KEY* eckey) { @@ -648,6 +807,30 @@ struct RawKeyGroup : public EVPGroup return std::make_unique(pkey); } + // Raw Key + void get_coordinates(const Group::PublicKey& pk, + bytes& x, + bytes& /*unused*/) const override + { + const auto& rpk = dynamic_cast(pk); + auto raw = bytes(pk_size); + auto* data_ptr = raw.data(); + auto data_len = raw.size(); + + if (1 != EVP_PKEY_get_raw_public_key(rpk.pkey.get(), data_ptr, &data_len)) { + throw openssl_error(); + } + x = raw; + } + + // Raw Key + std::unique_ptr set_coordinates( + const bytes& x, + const bytes& /*unused*/) const override + { + return deserialize(x); + } + private: const int evp_type; @@ -809,11 +992,54 @@ group_sk_size(Group::ID group_id) } } +static inline std::string +group_jwt_curve_name(Group::ID group_id) +{ + switch (group_id) { + case Group::ID::P256: + return "P-256"; + case Group::ID::P384: + return "P-384"; + case Group::ID::P521: + return "P-521"; + case Group::ID::Ed25519: + return "Ed25519"; + case Group::ID::Ed448: + return "Ed448"; + case Group::ID::X25519: + return "X25519"; + case Group::ID::X448: + return "X448"; + default: + throw std::runtime_error("Unknown group"); + } +} + +static inline std::string +group_jwt_key_type(Group::ID group_id) +{ + switch (group_id) { + case Group::ID::P256: + case Group::ID::P384: + case Group::ID::P521: + return "EC"; + case Group::ID::Ed25519: + case Group::ID::Ed448: + case Group::ID::X25519: + case Group::ID::X448: + return "OKP"; + default: + throw std::runtime_error("Unknown group"); + } +} + Group::Group(ID group_id_in, const KDF& kdf_in) : id(group_id_in) , dh_size(group_dh_size(group_id_in)) , pk_size(group_pk_size(group_id_in)) , sk_size(group_sk_size(group_id_in)) + , jwt_key_type(group_jwt_key_type(group_id_in)) + , jwt_curve_name(group_jwt_curve_name(group_id_in)) , kdf(kdf_in) { } diff --git a/lib/hpke/src/group.h b/lib/hpke/src/group.h index ace8d7a9..efb33245 100644 --- a/lib/hpke/src/group.h +++ b/lib/hpke/src/group.h @@ -43,6 +43,8 @@ struct Group const size_t dh_size; const size_t pk_size; const size_t sk_size; + const std::string jwt_key_type; + const std::string jwt_curve_name; virtual std::unique_ptr generate_key_pair() const = 0; virtual std::unique_ptr derive_key_pair( @@ -63,6 +65,13 @@ struct Group const bytes& sig, const PublicKey& pk) const = 0; + virtual void get_coordinates(const Group::PublicKey& pk, + bytes& x, + bytes& y) const = 0; + virtual std::unique_ptr set_coordinates( + const bytes& x, + const bytes& y) const = 0; + protected: const KDF& kdf; diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index a79cafea..78b7d580 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -1,14 +1,21 @@ #include #include +#include #include "dhkem.h" #include "common.h" #include "group.h" #include "rsa.h" +#include +#include +#include +#include #include #include +using namespace nlohmann; + namespace hpke { struct GroupSignature : public Signature @@ -103,6 +110,96 @@ struct GroupSignature : public Signature return group.verify(data, sig, rpk); } + std::unique_ptr import_jwk_private( + const std::string& json_str) const override + { + // TODO(ghewett): handle failed parse + json jwk_json = json::parse(json_str); + + // TODO(ghewett): jwk_json should patch cipher suite + + // TODO(ghewett): handle the absense of 'd' + bytes d = from_base64url(jwk_json["d"]); + + return std::make_unique(group.deserialize_private(d).release()); + } + + std::unique_ptr import_jwk( + const std::string& json_str) const override + { + bytes x = bytes({}, 0); + bytes y = bytes({}, 0); + json jwk_json = json::parse(json_str); + + if (jwk_json.empty() || !jwk_json.contains("kty") || + !jwk_json.contains("crv") || !jwk_json.contains("x")) { + throw std::runtime_error("import_jwk: malformed json input"); + } + + if (jwk_json["kty"] != group.jwt_key_type) { + throw std::runtime_error("import_jwk: group keytype does not match json"); + } + + if (jwk_json["crv"] != group.jwt_curve_name) { + throw std::runtime_error("import_jwk: group curve does not match json"); + } + x = from_base64url(jwk_json["x"]); + + if (jwk_json.contains("y")) { + y = from_base64url(jwk_json["y"]); + } + return group.set_coordinates(x, y); + } + + std::string export_jwk(const bytes& enc) const override + { + bytes x; + bytes y; + json json_jwk; + json_jwk["crv"] = group.jwt_curve_name; + json_jwk["kty"] = group.jwt_key_type; + + std::unique_ptr pk = deserialize(enc); + const auto& rpk = + dynamic_cast(*(pk.release())); + group.get_coordinates(rpk, x, y); + + if (!x.empty()) { + json_jwk["x"] = to_base64url(x); + } + + if (!y.empty()) { + json_jwk["y"] = to_base64url(y); + } + return json_jwk.dump(); + } + + std::string export_jwk_private(const bytes& enc) const override + { + bytes x; + bytes y; + json json_jwk; + json_jwk["crv"] = group.jwt_curve_name; + json_jwk["kty"] = group.jwt_key_type; + + // encode the private key + json_jwk["d"] = to_base64url(enc); + + const auto priv = group.deserialize_private(enc); + const auto& rpk = + dynamic_cast(*(priv->public_key().release())); + group.get_coordinates(rpk, x, y); + + if (!x.empty()) { + json_jwk["x"] = to_base64url(x); + } + + if (!y.empty()) { + json_jwk["y"] = to_base64url(y); + } + return json_jwk.dump(); + } + private: const Group& group; }; @@ -182,6 +279,30 @@ Signature::serialize_private(const PrivateKey& /* unused */) const throw std::runtime_error("Not implemented"); } +std::unique_ptr +Signature::import_jwk(const std::string& /* unused */) const +{ + throw std::runtime_error("Not implemented."); +} + +std::unique_ptr +Signature::import_jwk_private(const std::string& /* unused */) const +{ + throw std::runtime_error("Not implemented."); +} + +std::string +Signature::export_jwk(const bytes& /* unused */) const +{ + throw std::runtime_error("Not implemented."); +} + +std::string +Signature::export_jwk_private(const bytes& /* unused */) const +{ + throw std::runtime_error("Not implemented."); +} + std::unique_ptr Signature::deserialize_private(const bytes& /* unused */) const { diff --git a/lib/tls_syntax/CMakeLists.txt b/lib/tls_syntax/CMakeLists.txt index 43c75c20..44ba14fd 100644 --- a/lib/tls_syntax/CMakeLists.txt +++ b/lib/tls_syntax/CMakeLists.txt @@ -9,7 +9,7 @@ file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES}) add_dependencies(${CURRENT_LIB_NAME} third_party) -target_link_libraries(${CURRENT_LIB_NAME} third_party) +target_link_libraries(${CURRENT_LIB_NAME} PUBLIC third_party) target_include_directories(${CURRENT_LIB_NAME} PUBLIC $ diff --git a/src/crypto.cpp b/src/crypto.cpp index 9611fbd5..1c161f53 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -384,6 +384,20 @@ SignaturePublicKey::verify(const CipherSuite& suite, return suite.sig().verify(content, signature, *pub); } +SignaturePublicKey +SignaturePublicKey::from_jwk(CipherSuite suite, const std::string& json_str) +{ + auto pub = suite.sig().import_jwk(json_str); + auto pub_data = suite.sig().serialize(*pub); + return SignaturePublicKey{ pub_data }; +} + +std::string +SignaturePublicKey::to_jwk(CipherSuite suite) const +{ + return suite.sig().export_jwk(data); +} + SignaturePrivateKey SignaturePrivateKey::generate(CipherSuite suite) { @@ -438,4 +452,20 @@ SignaturePrivateKey::set_public_key(CipherSuite suite) public_key.data = suite.sig().serialize(*pub); } +SignaturePrivateKey +SignaturePrivateKey::from_jwk(CipherSuite suite, const std::string& json_str) +{ + auto priv = suite.sig().import_jwk_private(json_str); + auto priv_data = suite.sig().serialize_private(*priv); + auto pub = priv->public_key(); + auto pub_data = suite.sig().serialize(*pub); + return { priv_data, pub_data }; +} + +std::string +SignaturePrivateKey::to_jwk(CipherSuite suite) const +{ + return suite.sig().export_jwk_private(data); +} + } // namespace mls diff --git a/test/crypto.cpp b/test/crypto.cpp index 8dd930c2..acbffa09 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -1,11 +1,12 @@ #include #include #include - +#include #include using namespace mls; using namespace mls_vectors; +using namespace nlohmann; TEST_CASE("Basic HPKE") { @@ -91,6 +92,138 @@ TEST_CASE("Signature Key Serializion") } } +TEST_CASE("Signature Key Serializion To JWK") +{ + + struct KnownAnswerTest + { + CipherSuite suite; + bool supported; + bytes pk; + std::string kty; + std::string crv; + std::string d; + std::string x; + std::string y; + }; + + std::vector cases{ + { CipherSuite::ID::P256_AES128GCM_SHA256_P256, + true, + from_hex( + "cae90bad54df6973c64f7e4116ee78409045ed43e9668d0d474948a510f38acf"), + "EC", + "P-256", + "yukLrVTfaXPGT35BFu54QJBF7UPpZo0NR0lIpRDzis8", + "nUV1xGxWcUobNQrV0DsSN_z7P8hwVivmUji8EIJnrGg", + "2TGu_-lIxa7fn8PW-3gMNod-CjwwoAiLIhkbcsHtSdw" }, + { CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519, + true, + from_hex( + "9f959eeebab856bede41bfcd985077f5eaae702dde01c76b48952c35c9a97618"), + "OKP", + "Ed25519", + "n5We7rq4Vr7eQb_NmFB39equcC3eAcdrSJUsNcmpdhg", + "NmQinNknsQjwPFpujKmLa09alb4kagXy1YJenH3Zs-I", + "" }, + { CipherSuite::ID::X25519_CHACHA20POLY1305_SHA256_Ed25519, + true, + from_hex( + "f6d9dfcfc3e7f2016df7894b959e3f922d01035292732da12158f0c08b6251ae"), + "OKP", + "Ed25519", + "9tnfz8Pn8gFt94lLlZ4_ki0BA1KScy2hIVjwwItiUa4", + "kcnJ4z9eHBgiuFSDGlsF8PyibD2seAMncB4iKamamSU", + "" }, + { CipherSuite::ID::X448_AES256GCM_SHA512_Ed448, + true, + from_hex("e8dfd869ebe67fe696f0a0a12e04111cf1e4744e1a045fa73b2285a0168f319" + "e66522c9ddec741a8dd8011d0fc4b72303053901540c36f1e89"), + "OKP", + "Ed448", + "6N_Yaevmf-aW8KChLgQRHPHkdE4aBF-" + "nOyKFoBaPMZ5mUiyd3sdBqN2AEdD8S3IwMFOQFUDDbx6J", + "5uf09bDIVeecX74gv2ljKmvf3eLUXYiB6Jbycwww8ijcbnM04rfJr1agpFC2TuVSm5d0iDCj" + "EDIA", + "" }, + { CipherSuite::ID::P521_AES256GCM_SHA512_P521, + true, + from_hex( + "01c58ae6621000da12b682f45248f88b4cef278743a4fa325fc234f8770648d440cab3" + "367e90a49293c02778732776bd3eb985415c5f9df77a212e2097f0026298b8"), + "EC", + "P-521", + "AcWK5mIQANoStoL0Ukj4i0zvJ4dDpPoyX8I0-" + "HcGSNRAyrM2fpCkkpPAJ3hzJ3a9PrmFQVxfnfd6IS4gl_ACYpi4", + "AFLfr4vhftq9G6axgJ8g6xdukrUFn2cD5HDIxp8uzSbYW_" + "QIjKdUV1pF2vzzcz7Vj185LE6kl1SqTX6Z551W38mC", + "AbPIkuJkgfBZCidxSFrJALD1_e8-tKE0Ygy1dF2PZXJMGcHQRPbnytg-" + "4iVVGbjVdcakGIuUq3aAO09NqLi8j81d" }, + { CipherSuite::ID::X448_CHACHA20POLY1305_SHA512_Ed448, + true, + from_hex("5535d624e127fed3bc20d24a51269ce842e1ce36d6a62002b7f59696fcd3d9e" + "7d865da15e8e690caf22c34bf04bd34bd761be1eacb26fec193"), + "OKP", + "Ed448", + "VTXWJOEn_tO8INJKUSac6ELhzjbWpiACt_" + "WWlvzT2efYZdoV6OaQyvIsNL8EvTS9dhvh6ssm_sGT", + "jfbh2FAWZ57XmEEgrlGLAk6Am-qZ1IibFy2qip1uU3zOfWJ-TXmq4Ty-" + "yssJdZ5c0niU3SNO7JkA", + "" }, + { CipherSuite::ID::P384_AES256GCM_SHA384_P384, + true, + from_hex("33500ad0e749f53707e1f5ebef7d80758f95923c5b02acd89c21ffb2eb9f4f0" + "ccc5db144cd92e1577963dfb1b4e3fa68"), + "EC", + "P-384", + "M1AK0OdJ9TcH4fXr732AdY-VkjxbAqzYnCH_suufTwzMXbFEzZLhV3lj37G04_po", + "FyXCw9vukrBkLD_Lu7HvZw6cr-gwvpldN4aqZgtjAuM1rRSL74Lfi3CBBD8LpB0A", + "UUd8Qs3VdkOTFJlP62TKaVBp0JZlD74b7TU2gNlkDX3o8EIfl4POCooLs920bCJf" } + }; + + for (const auto& tc : cases) { + const CipherSuite suite{ tc.suite }; + + if (!tc.supported) { + auto private_key = SignaturePrivateKey::generate(suite); + CHECK_THROWS_WITH(private_key.to_jwk(suite), "Unsupported group"); + continue; + } + + // Export Private Key + auto private_key = SignaturePrivateKey::parse(suite, tc.pk); + auto jwk_str = private_key.to_jwk(tc.suite); + auto jwk_json = json::parse(jwk_str); + REQUIRE(jwk_json["kty"] == tc.kty); + REQUIRE(jwk_json["crv"] == tc.crv); + REQUIRE(jwk_json["d"] == tc.d); + REQUIRE(jwk_json["x"] == tc.x); + + if (!tc.y.empty()) { + REQUIRE(jwk_json["y"] == tc.y); + } + + // Export Public Key + auto jwk_pk_str = private_key.public_key.to_jwk(tc.suite); + auto jwk_pk_json = json::parse(jwk_pk_str); + REQUIRE(jwk_pk_json["kty"] == tc.kty); + REQUIRE(jwk_pk_json["crv"] == tc.crv); + REQUIRE(jwk_pk_json["x"] == tc.x); + + if (!tc.y.empty()) { + REQUIRE(jwk_pk_json["y"] == tc.y); + } + + // Import Private Key + auto import_jwk_sk = SignaturePrivateKey::from_jwk(tc.suite, jwk_str); + REQUIRE(tc.pk == import_jwk_sk.data); + + // Import Public Key + auto import_jwk_pk = SignaturePublicKey::from_jwk(tc.suite, jwk_pk_str); + REQUIRE(private_key.public_key.data == import_jwk_pk.data); + } +} + TEST_CASE("Crypto Interop") { for (auto suite : all_supported_suites) { diff --git a/vcpkg.json b/vcpkg.json index f5870561..0ffd459e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,7 +7,8 @@ "name": "openssl", "version>=": "1.1.1n" }, - "doctest" + "doctest", + "nlohmann-json" ], "builtin-baseline": "3b3bd424827a1f7f4813216f6b32b6c61e386b2e", "overrides": [ From 0b8addf6e303bdab158c1d60a6d329ffe665346a Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Mon, 31 Jul 2023 17:09:19 -0500 Subject: [PATCH 09/36] updated function to something more meaningful --- lib/hpke/src/group.cpp | 16 ++++++++-------- lib/hpke/src/group.h | 8 ++++---- lib/hpke/src/signature.cpp | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/hpke/src/group.cpp b/lib/hpke/src/group.cpp index 47dc441e..ff1802d0 100644 --- a/lib/hpke/src/group.cpp +++ b/lib/hpke/src/group.cpp @@ -527,9 +527,9 @@ struct ECKeyGroup : public EVPGroup } // EC Key - void get_coordinates(const Group::PublicKey& pk, - bytes& x, - bytes& y) const override + void get_coordinates_from_public_key(const Group::PublicKey& pk, + bytes& x, + bytes& y) const override { auto bnX = make_typed_unique(BN_new()); auto bnY = make_typed_unique(BN_new()); @@ -610,7 +610,7 @@ struct ECKeyGroup : public EVPGroup } // EC Key - std::unique_ptr set_coordinates( + std::unique_ptr get_public_key_from_coordinates( const bytes& x, const bytes& y) const override { @@ -808,9 +808,9 @@ struct RawKeyGroup : public EVPGroup } // Raw Key - void get_coordinates(const Group::PublicKey& pk, - bytes& x, - bytes& /*unused*/) const override + void get_coordinates_from_public_key(const Group::PublicKey& pk, + bytes& x, + bytes& /*unused*/) const override { const auto& rpk = dynamic_cast(pk); auto raw = bytes(pk_size); @@ -824,7 +824,7 @@ struct RawKeyGroup : public EVPGroup } // Raw Key - std::unique_ptr set_coordinates( + std::unique_ptr get_public_key_from_coordinates( const bytes& x, const bytes& /*unused*/) const override { diff --git a/lib/hpke/src/group.h b/lib/hpke/src/group.h index efb33245..0e6016bb 100644 --- a/lib/hpke/src/group.h +++ b/lib/hpke/src/group.h @@ -65,10 +65,10 @@ struct Group const bytes& sig, const PublicKey& pk) const = 0; - virtual void get_coordinates(const Group::PublicKey& pk, - bytes& x, - bytes& y) const = 0; - virtual std::unique_ptr set_coordinates( + virtual void get_coordinates_from_public_key(const Group::PublicKey& pk, + bytes& x, + bytes& y) const = 0; + virtual std::unique_ptr get_public_key_from_coordinates( const bytes& x, const bytes& y) const = 0; diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 78b7d580..3fa993f8 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -148,7 +148,7 @@ struct GroupSignature : public Signature if (jwk_json.contains("y")) { y = from_base64url(jwk_json["y"]); } - return group.set_coordinates(x, y); + return group.get_public_key_from_coordinates(x, y); } std::string export_jwk(const bytes& enc) const override @@ -162,7 +162,7 @@ struct GroupSignature : public Signature std::unique_ptr pk = deserialize(enc); const auto& rpk = dynamic_cast(*(pk.release())); - group.get_coordinates(rpk, x, y); + group.get_coordinates_from_public_key(rpk, x, y); if (!x.empty()) { json_jwk["x"] = to_base64url(x); @@ -188,7 +188,7 @@ struct GroupSignature : public Signature const auto priv = group.deserialize_private(enc); const auto& rpk = dynamic_cast(*(priv->public_key().release())); - group.get_coordinates(rpk, x, y); + group.get_coordinates_from_public_key(rpk, x, y); if (!x.empty()) { json_jwk["x"] = to_base64url(x); From d7b701fb3f268f16b80d44209f41d37f99aa8865 Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Tue, 8 Aug 2023 13:45:06 -0500 Subject: [PATCH 10/36] fixing credential types for validation. --- src/core_types.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core_types.cpp b/src/core_types.cpp index d7a89218..f66b864a 100644 --- a/src/core_types.cpp +++ b/src/core_types.cpp @@ -45,10 +45,11 @@ const std::array all_supported_ciphersuites = { CipherSuite::ID::X448_CHACHA20POLY1305_SHA512_Ed448, }; -const std::array all_supported_credentials = { +const std::array all_supported_credentials = { CredentialType::basic, CredentialType::x509, - CredentialType::userinfo_vc + CredentialType::userinfo_vc, + CredentialType::multi }; Capabilities From 85deba92f82332c128f64b745a516f323863b3c0 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 25 Aug 2023 13:27:19 -0400 Subject: [PATCH 11/36] Fix clang-tidy errors --- include/mls/credential.h | 4 ++-- lib/bytes/src/bytes.cpp | 12 ++++++------ lib/hpke/src/signature.cpp | 2 +- src/credential.cpp | 10 ++++------ 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/include/mls/credential.h b/include/mls/credential.h index 981610cb..bdc63aa0 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -161,7 +161,7 @@ struct Credential UserInfoVCCredential, MultiCredential>; - Credential(SpecificCredential _cred); + Credential(SpecificCredential specific); SpecificCredential _cred; }; @@ -187,7 +187,7 @@ struct CredentialBinding const SignaturePrivateKey& credential_priv, const SignaturePublicKey& signature_key); - bool valid_for(const SignaturePublicKey& pub) const; + bool valid_for(const SignaturePublicKey& signature_key) const; TLS_SERIALIZABLE(credential, credential_key, signature) diff --git a/lib/bytes/src/bytes.cpp b/lib/bytes/src/bytes.cpp index b63798cf..df6e897e 100644 --- a/lib/bytes/src/bytes.cpp +++ b/lib/bytes/src/bytes.cpp @@ -170,7 +170,7 @@ to_base64(const bytes& data) char* string_ptr = nullptr; // long string_len = BIO_get_mem_data(out, &string_ptr); // BIO_get_mem_data failed clang-tidy - long string_len = BIO_ctrl(out, BIO_CTRL_INFO, 0, &string_ptr); + const auto string_len = BIO_ctrl(out, BIO_CTRL_INFO, 0, &string_ptr); auto return_value = std::string(string_ptr, string_len); BIO_set_close(out, BIO_NOCLOSE); @@ -219,11 +219,11 @@ from_base64(const std::string& enc) EVP_ENCODE_CTX* ctx = EVP_ENCODE_CTX_new(); EVP_DecodeInit(ctx); - int result = EVP_DecodeUpdate(ctx, - output.data(), - &output_buffer_length, - input.data(), - static_cast(input.size())); + auto result = EVP_DecodeUpdate(ctx, + output.data(), + &output_buffer_length, + input.data(), + static_cast(input.size())); if (result == -1) { auto code = ERR_get_error(); diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 3fa993f8..f1f652e0 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -119,7 +119,7 @@ struct GroupSignature : public Signature // TODO(ghewett): jwk_json should patch cipher suite // TODO(ghewett): handle the absense of 'd' - bytes d = from_base64url(jwk_json["d"]); + const bytes d = from_base64url(jwk_json["d"]); return std::make_unique(group.deserialize_private(d).release()); } diff --git a/src/credential.cpp b/src/credential.cpp index 4fbb689f..a77fa08e 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -191,28 +191,26 @@ Credential::type() const Credential Credential::basic(const bytes& identity) { - return Credential(BasicCredential{ identity }); + return { BasicCredential{ identity } }; } Credential Credential::x509(const std::vector& der_chain) { - return Credential(X509Credential{ der_chain }); + return { X509Credential{ der_chain } }; } Credential Credential::multi(const std::vector& binding_inputs, const SignaturePublicKey& signature_key) { - return Credential(MultiCredential{ binding_inputs, signature_key }); + return { MultiCredential{ binding_inputs, signature_key } }; } Credential Credential::userinfo_vc(const bytes& userinfo_vc_jwt) { - Credential cred; - cred._cred = UserInfoVCCredential{ userinfo_vc_jwt }; - return cred; + return { UserInfoVCCredential{ userinfo_vc_jwt } }; } bool From 05c80bf9f3732f72bb1b9dcd7f0ad17a2f9f1c04 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 25 Aug 2023 13:43:03 -0400 Subject: [PATCH 12/36] Move base64 functionality to 'hpke' module --- lib/bytes/CMakeLists.txt | 6 +- lib/bytes/include/bytes/bytes.h | 12 --- lib/bytes/src/bytes.cpp | 124 +----------------------------- lib/bytes/test/bytes.cpp | 28 ------- lib/hpke/include/hpke/base64.h | 20 +++++ lib/hpke/src/base64.cpp | 131 ++++++++++++++++++++++++++++++++ lib/hpke/src/signature.cpp | 1 + lib/hpke/test/base64.cpp | 32 ++++++++ 8 files changed, 186 insertions(+), 168 deletions(-) create mode 100644 lib/hpke/include/hpke/base64.h create mode 100644 lib/hpke/src/base64.cpp create mode 100644 lib/hpke/test/base64.cpp diff --git a/lib/bytes/CMakeLists.txt b/lib/bytes/CMakeLists.txt index 21a78744..f5ab674e 100644 --- a/lib/bytes/CMakeLists.txt +++ b/lib/bytes/CMakeLists.txt @@ -9,11 +9,7 @@ file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES}) add_dependencies(${CURRENT_LIB_NAME} tls_syntax) -target_link_libraries(${CURRENT_LIB_NAME} - PUBLIC - tls_syntax - PRIVATE - OpenSSL::Crypto) +target_link_libraries(${CURRENT_LIB_NAME} tls_syntax) target_include_directories(${CURRENT_LIB_NAME} PUBLIC $ diff --git a/lib/bytes/include/bytes/bytes.h b/lib/bytes/include/bytes/bytes.h index 49d7ead7..ddb5dacf 100644 --- a/lib/bytes/include/bytes/bytes.h +++ b/lib/bytes/include/bytes/bytes.h @@ -115,16 +115,4 @@ to_hex(const bytes& data); bytes from_hex(const std::string& hex); -std::string -to_base64(const bytes& data); - -std::string -to_base64url(const bytes& data); - -bytes -from_base64(const std::string& enc); - -bytes -from_base64url(const std::string& enc); - } // namespace bytes_ns diff --git a/lib/bytes/src/bytes.cpp b/lib/bytes/src/bytes.cpp index df6e897e..509e2532 100644 --- a/lib/bytes/src/bytes.cpp +++ b/lib/bytes/src/bytes.cpp @@ -1,10 +1,7 @@ #include -#include #include -#include -#include -#include +#include #include #include @@ -140,123 +137,4 @@ operator!=(const std::vector& lhs, const bytes_ns::bytes& rhs) return rhs != lhs; } -std::string -to_base64(const bytes& data) -{ - bool done = false; - int result = 0; - - if (data.empty()) { - return ""; - } - - BIO* b64 = BIO_new(BIO_f_base64()); - BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); - BIO* out = BIO_new(BIO_s_mem()); - BIO_push(b64, out); - - while (!done) { - result = BIO_write(b64, data.data(), static_cast(data.size())); - - if (result <= 0) { - if (BIO_should_retry(b64)) { - continue; - } - throw std::runtime_error("base64 encode failed"); - } - done = true; - } - BIO_flush(b64); - char* string_ptr = nullptr; - // long string_len = BIO_get_mem_data(out, &string_ptr); - // BIO_get_mem_data failed clang-tidy - const auto string_len = BIO_ctrl(out, BIO_CTRL_INFO, 0, &string_ptr); - auto return_value = std::string(string_ptr, string_len); - - BIO_set_close(out, BIO_NOCLOSE); - BIO_free(b64); - BIO_free(out); - return return_value; -} - -std::string -to_base64url(const bytes& data) -{ - if (data.empty()) { - return ""; - } - - std::string return_value = to_base64(data); - - // remove the end padding - auto sz = return_value.find_first_of('='); - - if (sz != std::string::npos) { - return_value = return_value.substr(0, sz); - } - - // replace plus with hyphen - std::replace(return_value.begin(), return_value.end(), '+', '-'); - - // replace slash with underscore - std::replace(return_value.begin(), return_value.end(), '/', '_'); - return return_value; -} - -bytes -from_base64(const std::string& enc) -{ - if (enc.length() == 0) { - return {}; - } - - if (enc.length() % 4 != 0) { - throw std::runtime_error("Base64 length is not divisible by 4"); - } - bytes input = from_ascii(enc); - bytes output(input.size() / 4 * 3); - int output_buffer_length = static_cast(output.size()); - EVP_ENCODE_CTX* ctx = EVP_ENCODE_CTX_new(); - EVP_DecodeInit(ctx); - - auto result = EVP_DecodeUpdate(ctx, - output.data(), - &output_buffer_length, - input.data(), - static_cast(input.size())); - - if (result == -1) { - auto code = ERR_get_error(); - throw std::runtime_error(ERR_error_string(code, nullptr)); - } - - if (result == 0 && enc.substr(enc.length() - 2, enc.length()) == "==") { - output = output.slice(0, output.size() - 2); - } else if (result == 0 && enc.substr(enc.length() - 1, enc.length()) == "=") { - output = output.slice(0, output.size() - 1); - } else if (result == 0) { - throw std::runtime_error("Base64 padding was malformed."); - } - EVP_DecodeFinal(ctx, output.data(), &output_buffer_length); - EVP_ENCODE_CTX_free(ctx); - return output; -} - -bytes -from_base64url(const std::string& enc) -{ - if (enc.empty()) { - return {}; - } - std::string enc_copy = enc; // copy - std::replace(enc_copy.begin(), enc_copy.end(), '-', '+'); - std::replace(enc_copy.begin(), enc_copy.end(), '_', '/'); - - while (enc_copy.length() % 4 != 0) { - enc_copy += "="; - } - bytes return_value = from_base64(enc_copy); - return return_value; -} - } // namespace bytes_ns diff --git a/lib/bytes/test/bytes.cpp b/lib/bytes/test/bytes.cpp index 69a764df..a28dbf9f 100644 --- a/lib/bytes/test/bytes.cpp +++ b/lib/bytes/test/bytes.cpp @@ -2,7 +2,6 @@ #include #include #include -#include using namespace bytes_ns; using namespace std::literals::string_literals; @@ -41,33 +40,6 @@ TEST_CASE("To/from hex/ASCII") REQUIRE(from_ascii(str) == ascii); } -TEST_CASE("To Base64 / To Base64Url") -{ - struct KnownAnswerTest - { - bytes data; - std::string base64; - std::string base64u; - }; - - const std::vector cases{ - { from_ascii("hello there"), "aGVsbG8gdGhlcmU=", "aGVsbG8gdGhlcmU" }, - { from_ascii("A B C D E F "), "QSBCIEMgRCBFIEYg", "QSBCIEMgRCBFIEYg" }, - { from_ascii("hello\xfethere"), "aGVsbG/+dGhlcmU=", "aGVsbG_-dGhlcmU" }, - { from_ascii("\xfe"), "/g==", "_g" }, - { from_ascii("\x01\x02"), "AQI=", "AQI" }, - { from_ascii("\x01"), "AQ==", "AQ" }, - { from_ascii(""), "", "" }, - }; - - for (const auto& tc : cases) { - REQUIRE(to_base64(tc.data) == tc.base64); - REQUIRE(to_base64url(tc.data) == tc.base64u); - REQUIRE(from_base64(tc.base64) == tc.data); - REQUIRE(from_base64url(tc.base64u) == tc.data); - } -} - TEST_CASE("Operators") { const auto lhs = from_hex("00010203"); diff --git a/lib/hpke/include/hpke/base64.h b/lib/hpke/include/hpke/base64.h new file mode 100644 index 00000000..7f809729 --- /dev/null +++ b/lib/hpke/include/hpke/base64.h @@ -0,0 +1,20 @@ +#pragma once + +#include +using namespace bytes_ns; + +namespace hpke { + +std::string +to_base64(const bytes& data); + +std::string +to_base64url(const bytes& data); + +bytes +from_base64(const std::string& enc); + +bytes +from_base64url(const std::string& enc); + +} // namespace hpke diff --git a/lib/hpke/src/base64.cpp b/lib/hpke/src/base64.cpp new file mode 100644 index 00000000..f0642f7e --- /dev/null +++ b/lib/hpke/src/base64.cpp @@ -0,0 +1,131 @@ +#include + +#include "openssl_common.h" + +#include +#include +#include + +namespace hpke { + +std::string +to_base64(const bytes& data) +{ + bool done = false; + int result = 0; + + if (data.empty()) { + return ""; + } + + BIO* b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + BIO* out = BIO_new(BIO_s_mem()); + BIO_push(b64, out); + + while (!done) { + result = BIO_write(b64, data.data(), static_cast(data.size())); + + if (result <= 0) { + if (BIO_should_retry(b64)) { + continue; + } + throw std::runtime_error("base64 encode failed"); + } + done = true; + } + BIO_flush(b64); + char* string_ptr = nullptr; + // long string_len = BIO_get_mem_data(out, &string_ptr); + // BIO_get_mem_data failed clang-tidy + const auto string_len = BIO_ctrl(out, BIO_CTRL_INFO, 0, &string_ptr); + auto return_value = std::string(string_ptr, string_len); + + BIO_set_close(out, BIO_NOCLOSE); + BIO_free(b64); + BIO_free(out); + return return_value; +} + +std::string +to_base64url(const bytes& data) +{ + if (data.empty()) { + return ""; + } + + std::string return_value = to_base64(data); + + // remove the end padding + auto sz = return_value.find_first_of('='); + + if (sz != std::string::npos) { + return_value = return_value.substr(0, sz); + } + + // replace plus with hyphen + std::replace(return_value.begin(), return_value.end(), '+', '-'); + + // replace slash with underscore + std::replace(return_value.begin(), return_value.end(), '/', '_'); + return return_value; +} + +bytes +from_base64(const std::string& enc) +{ + if (enc.length() == 0) { + return {}; + } + + if (enc.length() % 4 != 0) { + throw std::runtime_error("Base64 length is not divisible by 4"); + } + bytes input = from_ascii(enc); + bytes output(input.size() / 4 * 3); + int output_buffer_length = static_cast(output.size()); + EVP_ENCODE_CTX* ctx = EVP_ENCODE_CTX_new(); + EVP_DecodeInit(ctx); + + auto result = EVP_DecodeUpdate(ctx, + output.data(), + &output_buffer_length, + input.data(), + static_cast(input.size())); + + if (result == -1) { + auto code = ERR_get_error(); + throw std::runtime_error(ERR_error_string(code, nullptr)); + } + + if (result == 0 && enc.substr(enc.length() - 2, enc.length()) == "==") { + output = output.slice(0, output.size() - 2); + } else if (result == 0 && enc.substr(enc.length() - 1, enc.length()) == "=") { + output = output.slice(0, output.size() - 1); + } else if (result == 0) { + throw std::runtime_error("Base64 padding was malformed."); + } + EVP_DecodeFinal(ctx, output.data(), &output_buffer_length); + EVP_ENCODE_CTX_free(ctx); + return output; +} + +bytes +from_base64url(const std::string& enc) +{ + if (enc.empty()) { + return {}; + } + std::string enc_copy = enc; // copy + std::replace(enc_copy.begin(), enc_copy.end(), '-', '+'); + std::replace(enc_copy.begin(), enc_copy.end(), '_', '/'); + + while (enc_copy.length() % 4 != 0) { + enc_copy += "="; + } + bytes return_value = from_base64(enc_copy); + return return_value; +} + + +} // namespace hpke diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index f1f652e0..393ec369 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "dhkem.h" diff --git a/lib/hpke/test/base64.cpp b/lib/hpke/test/base64.cpp new file mode 100644 index 00000000..b912ae0f --- /dev/null +++ b/lib/hpke/test/base64.cpp @@ -0,0 +1,32 @@ +#include +#include + +using namespace hpke; +using namespace bytes_ns; + +TEST_CASE("To Base64 / To Base64Url") +{ + struct KnownAnswerTest + { + bytes data; + std::string base64; + std::string base64u; + }; + + const std::vector cases{ + { from_ascii("hello there"), "aGVsbG8gdGhlcmU=", "aGVsbG8gdGhlcmU" }, + { from_ascii("A B C D E F "), "QSBCIEMgRCBFIEYg", "QSBCIEMgRCBFIEYg" }, + { from_ascii("hello\xfethere"), "aGVsbG/+dGhlcmU=", "aGVsbG_-dGhlcmU" }, + { from_ascii("\xfe"), "/g==", "_g" }, + { from_ascii("\x01\x02"), "AQI=", "AQI" }, + { from_ascii("\x01"), "AQ==", "AQ" }, + { from_ascii(""), "", "" }, + }; + + for (const auto& tc : cases) { + REQUIRE(to_base64(tc.data) == tc.base64); + REQUIRE(to_base64url(tc.data) == tc.base64u); + REQUIRE(from_base64(tc.base64) == tc.data); + REQUIRE(from_base64url(tc.base64u) == tc.data); + } +} From 2d490ba940ea84656bd96ac027b4f403299d2620 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 25 Aug 2023 14:32:53 -0400 Subject: [PATCH 13/36] Review+refactor base64 code --- lib/bytes/include/bytes/bytes.h | 3 + lib/bytes/src/bytes.cpp | 6 ++ lib/hpke/src/base64.cpp | 103 +++++++++++--------------------- lib/hpke/src/signature.cpp | 2 +- lib/hpke/test/base64.cpp | 2 +- 5 files changed, 46 insertions(+), 70 deletions(-) diff --git a/lib/bytes/include/bytes/bytes.h b/lib/bytes/include/bytes/bytes.h index ddb5dacf..713a3c64 100644 --- a/lib/bytes/include/bytes/bytes.h +++ b/lib/bytes/include/bytes/bytes.h @@ -106,6 +106,9 @@ struct bytes std::vector _data; }; +std::string +to_ascii(const bytes& data); + bytes from_ascii(const std::string& ascii); diff --git a/lib/bytes/src/bytes.cpp b/lib/bytes/src/bytes.cpp index 509e2532..6eb0fe34 100644 --- a/lib/bytes/src/bytes.cpp +++ b/lib/bytes/src/bytes.cpp @@ -79,6 +79,12 @@ bytes::operator^(const bytes& rhs) const return out; } +std::string +to_ascii(const bytes& data) +{ + return { data.begin(), data.end() }; +} + bytes from_ascii(const std::string& ascii) { diff --git a/lib/hpke/src/base64.cpp b/lib/hpke/src/base64.cpp index f0642f7e..e114b4a1 100644 --- a/lib/hpke/src/base64.cpp +++ b/lib/hpke/src/base64.cpp @@ -11,40 +11,21 @@ namespace hpke { std::string to_base64(const bytes& data) { - bool done = false; - int result = 0; - if (data.empty()) { return ""; } - BIO* b64 = BIO_new(BIO_f_base64()); - BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); - BIO* out = BIO_new(BIO_s_mem()); - BIO_push(b64, out); - - while (!done) { - result = BIO_write(b64, data.data(), static_cast(data.size())); - - if (result <= 0) { - if (BIO_should_retry(b64)) { - continue; - } - throw std::runtime_error("base64 encode failed"); - } - done = true; + const auto data_size = static_cast(data.size()); + const auto out_size = (data_size + 2) / 3 * 4; + auto out = bytes(out_size + 1); // NUL terminator + + const auto result = EVP_EncodeBlock(out.data(), data.data(), data_size); + if (result != out_size) { + throw openssl_error(); } - BIO_flush(b64); - char* string_ptr = nullptr; - // long string_len = BIO_get_mem_data(out, &string_ptr); - // BIO_get_mem_data failed clang-tidy - const auto string_len = BIO_ctrl(out, BIO_CTRL_INFO, 0, &string_ptr); - auto return_value = std::string(string_ptr, string_len); - - BIO_set_close(out, BIO_NOCLOSE); - BIO_free(b64); - BIO_free(out); - return return_value; + + out.resize(out.size() - 1); // strip NUL terminator + return to_ascii(out); } std::string @@ -54,21 +35,17 @@ to_base64url(const bytes& data) return ""; } - std::string return_value = to_base64(data); + auto encoded = to_base64(data); - // remove the end padding - auto sz = return_value.find_first_of('='); - - if (sz != std::string::npos) { - return_value = return_value.substr(0, sz); + auto pad_start = encoded.find_first_of('='); + if (pad_start != std::string::npos) { + encoded = encoded.substr(0, pad_start); } - // replace plus with hyphen - std::replace(return_value.begin(), return_value.end(), '+', '-'); + std::replace(encoded.begin(), encoded.end(), '+', '-'); + std::replace(encoded.begin(), encoded.end(), '/', '_'); - // replace slash with underscore - std::replace(return_value.begin(), return_value.end(), '/', '_'); - return return_value; + return encoded; } bytes @@ -81,33 +58,23 @@ from_base64(const std::string& enc) if (enc.length() % 4 != 0) { throw std::runtime_error("Base64 length is not divisible by 4"); } - bytes input = from_ascii(enc); - bytes output(input.size() / 4 * 3); - int output_buffer_length = static_cast(output.size()); - EVP_ENCODE_CTX* ctx = EVP_ENCODE_CTX_new(); - EVP_DecodeInit(ctx); - - auto result = EVP_DecodeUpdate(ctx, - output.data(), - &output_buffer_length, - input.data(), - static_cast(input.size())); - - if (result == -1) { - auto code = ERR_get_error(); - throw std::runtime_error(ERR_error_string(code, nullptr)); + + const auto in = from_ascii(enc); + const auto in_size = static_cast(in.size()); + auto out = bytes(in.size() / 4 * 3); + + const auto result = EVP_DecodeBlock(out.data(), in.data(), in_size); + if (result != 0) { + throw openssl_error(); } - if (result == 0 && enc.substr(enc.length() - 2, enc.length()) == "==") { - output = output.slice(0, output.size() - 2); - } else if (result == 0 && enc.substr(enc.length() - 1, enc.length()) == "=") { - output = output.slice(0, output.size() - 1); - } else if (result == 0) { - throw std::runtime_error("Base64 padding was malformed."); + if (enc.substr(enc.length() - 2, enc.length()) == "==") { + out.resize(out.size() - 2); + } else if (enc.substr(enc.length() - 1, enc.length()) == "=") { + out.resize(out.size() - 1); } - EVP_DecodeFinal(ctx, output.data(), &output_buffer_length); - EVP_ENCODE_CTX_free(ctx); - return output; + + return out; } bytes @@ -116,16 +83,16 @@ from_base64url(const std::string& enc) if (enc.empty()) { return {}; } - std::string enc_copy = enc; // copy + + auto enc_copy = enc; std::replace(enc_copy.begin(), enc_copy.end(), '-', '+'); std::replace(enc_copy.begin(), enc_copy.end(), '_', '/'); while (enc_copy.length() % 4 != 0) { enc_copy += "="; } - bytes return_value = from_base64(enc_copy); - return return_value; -} + return from_base64(enc_copy); +} } // namespace hpke diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 393ec369..773017fb 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -1,6 +1,6 @@ +#include #include #include -#include #include #include "dhkem.h" diff --git a/lib/hpke/test/base64.cpp b/lib/hpke/test/base64.cpp index b912ae0f..25f1c007 100644 --- a/lib/hpke/test/base64.cpp +++ b/lib/hpke/test/base64.cpp @@ -1,5 +1,5 @@ -#include #include +#include using namespace hpke; using namespace bytes_ns; From 5fa909cfe3ef2dd0790b9697ebca9da4d51f2a9b Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 25 Aug 2023 16:34:36 -0400 Subject: [PATCH 14/36] Review and refactor JWK import/export --- lib/hpke/include/hpke/signature.h | 12 +-- lib/hpke/src/group.cpp | 171 ++++++++++++++++-------------- lib/hpke/src/group.h | 10 +- lib/hpke/src/rsa.cpp | 26 +++++ lib/hpke/src/rsa.h | 7 ++ lib/hpke/src/signature.cpp | 108 ++++++------------- src/crypto.cpp | 6 +- 7 files changed, 171 insertions(+), 169 deletions(-) diff --git a/lib/hpke/include/hpke/signature.h b/lib/hpke/include/hpke/signature.h index 378f22a5..39dae06f 100644 --- a/lib/hpke/include/hpke/signature.h +++ b/lib/hpke/include/hpke/signature.h @@ -46,16 +46,16 @@ struct Signature virtual bytes serialize(const PublicKey& pk) const = 0; virtual std::unique_ptr deserialize(const bytes& enc) const = 0; - virtual bytes serialize_private(const PrivateKey& sk) const; + virtual bytes serialize_private(const PrivateKey& sk) const = 0; virtual std::unique_ptr deserialize_private( - const bytes& skm) const; + const bytes& skm) const = 0; virtual std::unique_ptr import_jwk_private( - const std::string& json_str) const; + const std::string& json_str) const = 0; virtual std::unique_ptr import_jwk( - const std::string& json_str) const; - virtual std::string export_jwk_private(const bytes& env) const; - virtual std::string export_jwk(const bytes& env) const; + const std::string& json_str) const = 0; + virtual std::string export_jwk_private(const PrivateKey& env) const = 0; + virtual std::string export_jwk(const PublicKey& env) const = 0; virtual bytes sign(const bytes& data, const PrivateKey& sk) const = 0; virtual bool verify(const bytes& data, diff --git a/lib/hpke/src/group.cpp b/lib/hpke/src/group.cpp index ff1802d0..3e9c2d19 100644 --- a/lib/hpke/src/group.cpp +++ b/lib/hpke/src/group.cpp @@ -321,7 +321,7 @@ struct ECKeyGroup : public EVPGroup auto* group = EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid); auto group_ptr = make_typed_unique(group); #else - auto eckey = make_typed_unique(new_ec_key()); + auto eckey = new_ec_key(); const auto* group = EC_KEY_get0_group(eckey.get()); #endif @@ -358,7 +358,8 @@ struct ECKeyGroup : public EVPGroup EC_KEY_set_private_key(eckey.get(), sk.get()); EC_KEY_set_public_key(eckey.get(), pt.get()); - return std::make_unique(to_pkey(eckey.release())); + auto pkey = to_pkey(eckey.release()); + return std::make_unique(pkey.release()); #endif } @@ -449,7 +450,7 @@ struct ECKeyGroup : public EVPGroup } return std::make_unique(key.release()); #else - auto eckey = make_typed_unique(new_ec_key()); + auto eckey = new_ec_key(); auto* eckey_ptr = eckey.get(); const auto* data_ptr = enc.data(); if (nullptr == @@ -460,7 +461,8 @@ struct ECKeyGroup : public EVPGroup throw openssl_error(); } - return std::make_unique(to_pkey(eckey.release())); + auto pkey = to_pkey(eckey.release()); + return std::make_unique(pkey.release()); #endif } @@ -512,7 +514,7 @@ struct ECKeyGroup : public EVPGroup auto key = keypair_evp_key(priv); return std::make_unique(key.release()); #else - auto eckey = make_typed_unique(new_ec_key()); + auto eckey = new_ec_key(); const auto* group = EC_KEY_get0_group(eckey.get()); const auto d = make_typed_unique( BN_bin2bn(skm.data(), static_cast(skm.size()), nullptr)); @@ -522,131 +524,141 @@ struct ECKeyGroup : public EVPGroup EC_KEY_set_private_key(eckey.get(), d.get()); EC_KEY_set_public_key(eckey.get(), pt.get()); - return std::make_unique(to_pkey(eckey.release())); + auto pkey = to_pkey(eckey.release()); + return std::make_unique(pkey.release()); #endif } // EC Key - void get_coordinates_from_public_key(const Group::PublicKey& pk, - bytes& x, - bytes& y) const override + std::tuple coordinates( + const Group::PublicKey& pk) const override { - auto bnX = make_typed_unique(BN_new()); - auto bnY = make_typed_unique(BN_new()); + auto bn_x = make_typed_unique(BN_new()); + auto bn_y = make_typed_unique(BN_new()); const auto& rpk = dynamic_cast(pk); #if defined(WITH_OPENSSL3) - OSSL_PARAM* param = nullptr; - - if (1 != EVP_PKEY_todata(rpk.pkey.get(), EVP_PKEY_PUBLIC_KEY, ¶m)) { + // Raw pointer OK here because it becomes managed as soon as possible + OSSL_PARAM* param_ptr = nullptr; + if (1 != + EVP_PKE_y_todata(rpk.pkey.get(), EVP_PKE_y_PUBLIC_KE_y, ¶m_ptr)) { throw openssl_error(); } - auto param_ptr = make_typed_unique(param); - const OSSL_PARAM* pk_param = - OSSL_PARAM_locate_const(param_ptr.get(), OSSL_PKEY_PARAM_PUB_KEY); + auto param = make_typed_unique(param); + + // Raw pointer OK here because it is non-owning + const auto* pk_param = + OSSL_PARAM_locate_const(param.get(), OSSL_PKE_y_PARAM_PUB_KE_y); if (pk_param == nullptr) { - throw std::runtime_error("Failed to locate OSSL_PKEY_PARAM_PUB_KEY"); + throw std::runtime_error("Failed to locate OSSL_PKE_y_PARAM_PUB_KE_y"); } - size_t len = 0; + // Copy the octet string representation of the key into a buffer + auto len = size_t(0); if (1 != OSSL_PARAM_get_octet_string(pk_param, nullptr, 0, &len)) { - throw std::runtime_error("Failed to get OSSL_PKEY_PARAM_PUB_KEY len"); + throw std::runtime_error("Failed to get OSSL_PKE_y_PARAM_PUB_KE_y len"); } - bytes buf(len); - void* data_ptr = buf.data(); - if (1 != OSSL_PARAM_get_octet_string(pk_param, &data_ptr, len, nullptr)) { - throw std::runtime_error("Failed to get OSSL_PKEY_PARAM_PUB_KEY data"); + auto buf = bytes(len); + auto* buf_ptr = buf.data(); + if (1 != OSSL_PARAM_get_octet_string(pk_param, &buf_ptr, len, nullptr)) { + throw std::runtime_error("Failed to get OSSL_PKE_y_PARAM_PUB_KE_y data"); } + + // Parse the octet string representation into an EC_POINT auto group = make_typed_unique( EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid)); - if (group == nullptr) { throw openssl_error(); } - auto point = make_typed_unique(EC_POINT_new(group.get())); + auto point = make_typed_unique(EC_POINT_new(group.get())); if (point == nullptr) { throw openssl_error(); } - const auto* oct_ptr = static_cast(data_ptr); if (1 != - EC_POINT_oct2point(group.get(), point.get(), oct_ptr, len, nullptr)) { + EC_POINT_oct2point(group.get(), point.get(), buf_ptr, len, nullptr)) { throw openssl_error(); } + // Retrieve the affine coordinates of the point if (1 != EC_POINT_get_affine_coordinates( - group.get(), point.get(), bnX.get(), bnY.get(), nullptr)) { + group.get(), point.get(), bn_x.get(), bn_y.get(), nullptr)) { throw openssl_error(); } #else + // Raw pointers are non-owning auto* pub = EVP_PKEY_get0_EC_KEY(rpk.pkey.get()); const auto* point = EC_KEY_get0_public_key(pub); const auto* group = EC_KEY_get0_group(pub); if (1 != EC_POINT_get_affine_coordinates_GFp( - group, point, bnX.get(), bnY.get(), nullptr)) { + group, point, bn_x.get(), bn_y.get(), nullptr)) { throw openssl_error(); } #endif - auto outX = bytes(BN_num_bytes(bnX.get())); - auto outY = bytes(BN_num_bytes(bnY.get())); - - if (BN_bn2bin(bnX.get(), outX.data()) != int(outX.size())) { + const auto x_size = BN_num_bytes(bn_x.get()); + auto x = bytes(x_size); + if (BN_bn2bin(bn_x.get(), x.data()) != x_size) { throw openssl_error(); } - if (BN_bn2bin(bnY.get(), outY.data()) != int(outY.size())) { + const auto y_size = BN_num_bytes(bn_y.get()); + auto y = bytes(y_size); + if (BN_bn2bin(bn_y.get(), y.data()) != y_size) { throw openssl_error(); } - const auto zeros_neededX = dh_size - outX.size(); - const auto zeros_neededY = dh_size - outY.size(); - auto leading_zerosX = bytes(zeros_neededX, 0); - auto leading_zerosY = bytes(zeros_neededY, 0); - x = leading_zerosX + outX; - y = leading_zerosY + outY; + + const auto zeros_needed_x = dh_size - x.size(); + const auto zeros_needed_y = dh_size - y.size(); + auto leading_zeros_x = bytes(zeros_needed_x, 0); + auto leading_zeros_y = bytes(zeros_needed_y, 0); + + return { leading_zeros_x + x, leading_zeros_y + y }; } // EC Key - std::unique_ptr get_public_key_from_coordinates( + std::unique_ptr public_key_from_coordinates( const bytes& x, const bytes& y) const override { - auto bnX = make_typed_unique( + auto bn_x = make_typed_unique( BN_bin2bn(x.data(), static_cast(x.size()), nullptr)); - auto bnY = make_typed_unique( + auto bn_y = make_typed_unique( BN_bin2bn(y.data(), static_cast(y.size()), nullptr)); - if (bnX == nullptr || bnY == nullptr) { - throw std::runtime_error("Failed to convert bnX or bnY"); + if (bn_x == nullptr || bn_y == nullptr) { + throw std::runtime_error("Failed to convert bn_x or bn_y"); } #if defined(WITH_OPENSSL3) - auto* group = EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid); - auto group_ptr = make_typed_unique(group); - - auto* point = EC_POINT_new(group); - auto point_ptr = make_typed_unique(point); + const auto group = make_typed_unique( + EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid)); + if (group == nullptr) { + throw std::runtime_error("Failed to create EC_GROUP"); + } - if (point == nullptr || group == nullptr) { - throw std::runtime_error("Failed to create EC_POINT or EC_GROUP"); + // Construct a point with the given coordinates + auto point = make_typed_unique(EC_POINT_new(group)); + if (group == nullptr) { + throw std::runtime_error("Failed to create EC_POINT"); } if (1 != EC_POINT_set_affine_coordinates( - group, point, bnX.get(), bnY.get(), nullptr)) { + group.get(), point.get(), bn_x.get(), bn_y.get(), nullptr)) { throw openssl_error(); } + // Serialize the point const auto point_size = EC_POINT_point2oct( group, point, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); - if (0 == point_size) { throw openssl_error(); } - bytes pub(point_size); + auto pub = bytes(point_size); if (EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, @@ -655,28 +667,31 @@ struct ECKeyGroup : public EVPGroup nullptr) != point_size) { throw openssl_error(); } + + // Initialize a public key from the serialized point auto key = public_evp_key(pub); return std::make_unique(key.release()); #else - auto eckey = make_typed_unique(new_ec_key()); - + auto eckey = new_ec_key(); if (eckey == nullptr) { throw std::runtime_error("Failed to create EC_KEY"); } + // Group pointer is non-owning const auto* group = EC_KEY_get0_group(eckey.get()); - auto* point = EC_POINT_new(group); - auto point_ptr = make_typed_unique(point); + auto point = make_typed_unique(EC_POINT_new(group)); if (1 != EC_POINT_set_affine_coordinates_GFp( - group, point, bnX.get(), bnY.get(), nullptr)) { + group, point.get(), bn_x.get(), bn_y.get(), nullptr)) { throw openssl_error(); } - if (1 != EC_KEY_set_public_key(eckey.get(), point)) { + if (1 != EC_KEY_set_public_key(eckey.get(), point.get())) { throw openssl_error(); } - return std::make_unique(to_pkey(eckey.release())); + + auto pkey = to_pkey(eckey.release()); + return std::make_unique(pkey.release()); #endif } @@ -684,19 +699,17 @@ struct ECKeyGroup : public EVPGroup int curve_nid; #if !defined(WITH_OPENSSL3) - // clang-format off - EC_KEY* new_ec_key() const + typed_unique_ptr new_ec_key() const { - return EC_KEY_new_by_curve_name(curve_nid); + return make_typed_unique(EC_KEY_new_by_curve_name(curve_nid)); } - // clang-format on - static EVP_PKEY* to_pkey(EC_KEY* eckey) + static typed_unique_ptr to_pkey(EC_KEY* eckey) { auto* pkey = EVP_PKEY_new(); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) EVP_PKEY_assign_EC_KEY(pkey, eckey); - return pkey; + return make_typed_unique(pkey); } #endif @@ -808,9 +821,8 @@ struct RawKeyGroup : public EVPGroup } // Raw Key - void get_coordinates_from_public_key(const Group::PublicKey& pk, - bytes& x, - bytes& /*unused*/) const override + std::tuple coordinates( + const Group::PublicKey& pk) const override { const auto& rpk = dynamic_cast(pk); auto raw = bytes(pk_size); @@ -820,13 +832,14 @@ struct RawKeyGroup : public EVPGroup if (1 != EVP_PKEY_get_raw_public_key(rpk.pkey.get(), data_ptr, &data_len)) { throw openssl_error(); } - x = raw; + + return { raw, {} }; } // Raw Key - std::unique_ptr get_public_key_from_coordinates( + std::unique_ptr public_key_from_coordinates( const bytes& x, - const bytes& /*unused*/) const override + const bytes& /* y */) const override { return deserialize(x); } @@ -993,7 +1006,7 @@ group_sk_size(Group::ID group_id) } static inline std::string -group_jwt_curve_name(Group::ID group_id) +group_jwk_curve_name(Group::ID group_id) { switch (group_id) { case Group::ID::P256: @@ -1016,7 +1029,7 @@ group_jwt_curve_name(Group::ID group_id) } static inline std::string -group_jwt_key_type(Group::ID group_id) +group_jwk_key_type(Group::ID group_id) { switch (group_id) { case Group::ID::P256: @@ -1038,8 +1051,8 @@ Group::Group(ID group_id_in, const KDF& kdf_in) , dh_size(group_dh_size(group_id_in)) , pk_size(group_pk_size(group_id_in)) , sk_size(group_sk_size(group_id_in)) - , jwt_key_type(group_jwt_key_type(group_id_in)) - , jwt_curve_name(group_jwt_curve_name(group_id_in)) + , jwk_key_type(group_jwk_key_type(group_id_in)) + , jwk_curve_name(group_jwk_curve_name(group_id_in)) , kdf(kdf_in) { } diff --git a/lib/hpke/src/group.h b/lib/hpke/src/group.h index 0e6016bb..f6b19f9a 100644 --- a/lib/hpke/src/group.h +++ b/lib/hpke/src/group.h @@ -43,8 +43,8 @@ struct Group const size_t dh_size; const size_t pk_size; const size_t sk_size; - const std::string jwt_key_type; - const std::string jwt_curve_name; + const std::string jwk_key_type; + const std::string jwk_curve_name; virtual std::unique_ptr generate_key_pair() const = 0; virtual std::unique_ptr derive_key_pair( @@ -65,10 +65,8 @@ struct Group const bytes& sig, const PublicKey& pk) const = 0; - virtual void get_coordinates_from_public_key(const Group::PublicKey& pk, - bytes& x, - bytes& y) const = 0; - virtual std::unique_ptr get_public_key_from_coordinates( + virtual std::tuple coordinates(const PublicKey& pk) const = 0; + virtual std::unique_ptr public_key_from_coordinates( const bytes& x, const bytes& y) const = 0; diff --git a/lib/hpke/src/rsa.cpp b/lib/hpke/src/rsa.cpp index 5bb6b521..45ba444d 100644 --- a/lib/hpke/src/rsa.cpp +++ b/lib/hpke/src/rsa.cpp @@ -146,6 +146,31 @@ RSASignature::verify(const bytes& data, return rv == 1; } +// TODO(RLB) Implement these methods +std::unique_ptr +RSASignature::import_jwk_private(const std::string& /* json_str */) const +{ + throw std::runtime_error("not implemented"); +} + +std::unique_ptr +RSASignature::import_jwk(const std::string& /* json_str */) const +{ + throw std::runtime_error("not implemented"); +} + +std::string +RSASignature::export_jwk_private(const Signature::PrivateKey& /* sk */) const +{ + throw std::runtime_error("not implemented"); +} + +std::string +RSASignature::export_jwk(const Signature::PublicKey& /* pk */) const +{ + throw std::runtime_error("not implemented"); +} + const EVP_MD* RSASignature::digest_to_md(Digest::ID digest) { @@ -177,4 +202,5 @@ RSASignature::digest_to_sig(Digest::ID digest) throw std::runtime_error("Unsupported digest"); } } + } // namespace hpke diff --git a/lib/hpke/src/rsa.h b/lib/hpke/src/rsa.h index 684435ae..c88d90a4 100644 --- a/lib/hpke/src/rsa.h +++ b/lib/hpke/src/rsa.h @@ -78,6 +78,13 @@ struct RSASignature : public Signature const bytes& sig, const Signature::PublicKey& pk) const override; + std::unique_ptr import_jwk_private( + const std::string& json_str) const override; + std::unique_ptr import_jwk( + const std::string& json_str) const override; + std::string export_jwk_private(const Signature::PrivateKey& sk) const override; + std::string export_jwk(const Signature::PublicKey& pk) const override; + private: const EVP_MD* md; diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 773017fb..cc6bb20d 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -137,11 +137,11 @@ struct GroupSignature : public Signature throw std::runtime_error("import_jwk: malformed json input"); } - if (jwk_json["kty"] != group.jwt_key_type) { + if (jwk_json["kty"] != group.jwk_key_type) { throw std::runtime_error("import_jwk: group keytype does not match json"); } - if (jwk_json["crv"] != group.jwt_curve_name) { + if (jwk_json["crv"] != group.jwk_curve_name) { throw std::runtime_error("import_jwk: group curve does not match json"); } x = from_base64url(jwk_json["x"]); @@ -149,60 +149,52 @@ struct GroupSignature : public Signature if (jwk_json.contains("y")) { y = from_base64url(jwk_json["y"]); } - return group.get_public_key_from_coordinates(x, y); + return group.public_key_from_coordinates(x, y); } - std::string export_jwk(const bytes& enc) const override + std::string export_jwk(const Signature::PublicKey& pk) const override { - bytes x; - bytes y; - json json_jwk; - json_jwk["crv"] = group.jwt_curve_name; - json_jwk["kty"] = group.jwt_key_type; - - std::unique_ptr pk = deserialize(enc); - const auto& rpk = - dynamic_cast(*(pk.release())); - group.get_coordinates_from_public_key(rpk, x, y); - - if (!x.empty()) { - json_jwk["x"] = to_base64url(x); - } - - if (!y.empty()) { - json_jwk["y"] = to_base64url(y); - } + const auto& gpk = dynamic_cast(pk); + const auto json_jwk = export_jwk_json(gpk); return json_jwk.dump(); } - std::string export_jwk_private(const bytes& enc) const override + std::string export_jwk_private(const Signature::PrivateKey& sk) const override { - bytes x; - bytes y; - json json_jwk; - json_jwk["crv"] = group.jwt_curve_name; - json_jwk["kty"] = group.jwt_key_type; + const auto& gsk = dynamic_cast(sk); + const auto gpk = gsk.public_key(); + + auto json_jwk = export_jwk_json(*gpk); // encode the private key + const auto enc = serialize_private(sk); json_jwk["d"] = to_base64url(enc); - const auto priv = group.deserialize_private(enc); - const auto& rpk = - dynamic_cast(*(priv->public_key().release())); - group.get_coordinates_from_public_key(rpk, x, y); - - if (!x.empty()) { - json_jwk["x"] = to_base64url(x); - } - - if (!y.empty()) { - json_jwk["y"] = to_base64url(y); - } return json_jwk.dump(); } private: const Group& group; + + json export_jwk_json(const Group::PublicKey& pk) const + { + const auto [x, y] = group.coordinates(pk); + + json json_jwk; + json_jwk["crv"] = group.jwk_curve_name; + json_jwk["kty"] = group.jwk_key_type; + + if (group.jwk_key_type == "EC") { + json_jwk["x"] = to_base64url(x); + json_jwk["y"] = to_base64url(y); + } else if (group.jwk_key_type == "OKP") { + json_jwk["x"] = to_base64url(x); + } else { + throw std::runtime_error("unknown key type"); + } + + return json_jwk; + } }; template<> @@ -274,42 +266,6 @@ Signature::Signature(Signature::ID id_in) { } -bytes -Signature::serialize_private(const PrivateKey& /* unused */) const -{ - throw std::runtime_error("Not implemented"); -} - -std::unique_ptr -Signature::import_jwk(const std::string& /* unused */) const -{ - throw std::runtime_error("Not implemented."); -} - -std::unique_ptr -Signature::import_jwk_private(const std::string& /* unused */) const -{ - throw std::runtime_error("Not implemented."); -} - -std::string -Signature::export_jwk(const bytes& /* unused */) const -{ - throw std::runtime_error("Not implemented."); -} - -std::string -Signature::export_jwk_private(const bytes& /* unused */) const -{ - throw std::runtime_error("Not implemented."); -} - -std::unique_ptr -Signature::deserialize_private(const bytes& /* unused */) const -{ - throw std::runtime_error("Not implemented"); -} - std::unique_ptr Signature::generate_rsa(size_t bits) { diff --git a/src/crypto.cpp b/src/crypto.cpp index 1c161f53..ecedadfc 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -395,7 +395,8 @@ SignaturePublicKey::from_jwk(CipherSuite suite, const std::string& json_str) std::string SignaturePublicKey::to_jwk(CipherSuite suite) const { - return suite.sig().export_jwk(data); + auto pub = suite.sig().deserialize(data); + return suite.sig().export_jwk(*pub); } SignaturePrivateKey @@ -465,7 +466,8 @@ SignaturePrivateKey::from_jwk(CipherSuite suite, const std::string& json_str) std::string SignaturePrivateKey::to_jwk(CipherSuite suite) const { - return suite.sig().export_jwk_private(data); + const auto priv = suite.sig().deserialize_private(data); + return suite.sig().export_jwk_private(*priv); } } // namespace mls From c58ea16cfeecdde8de1ab3592cef481502793375 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 25 Aug 2023 17:27:06 -0400 Subject: [PATCH 15/36] Add unit tests for JWK import/export --- lib/hpke/src/base64.cpp | 5 +- lib/hpke/src/signature.cpp | 5 +- lib/hpke/test/signature.cpp | 138 +++++++++++++++++++++++++++++++++++- test/crypto.cpp | 2 +- 4 files changed, 144 insertions(+), 6 deletions(-) diff --git a/lib/hpke/src/base64.cpp b/lib/hpke/src/base64.cpp index e114b4a1..15cec158 100644 --- a/lib/hpke/src/base64.cpp +++ b/lib/hpke/src/base64.cpp @@ -61,10 +61,11 @@ from_base64(const std::string& enc) const auto in = from_ascii(enc); const auto in_size = static_cast(in.size()); - auto out = bytes(in.size() / 4 * 3); + const auto out_size = in_size / 4 * 3; + auto out = bytes(out_size); const auto result = EVP_DecodeBlock(out.data(), in.data(), in_size); - if (result != 0) { + if (result != out_size) { throw openssl_error(); } diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index cc6bb20d..2356c723 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -161,8 +161,9 @@ struct GroupSignature : public Signature std::string export_jwk_private(const Signature::PrivateKey& sk) const override { - const auto& gsk = dynamic_cast(sk); - const auto gpk = gsk.public_key(); + const auto& gssk = dynamic_cast(sk); + const auto& gsk = gssk.group_priv; + const auto gpk = gsk->public_key(); auto json_jwk = export_jwk_json(*gpk); diff --git a/lib/hpke/test/signature.cpp b/lib/hpke/test/signature.cpp index 44c4d5f0..1268bcfb 100644 --- a/lib/hpke/test/signature.cpp +++ b/lib/hpke/test/signature.cpp @@ -1,9 +1,13 @@ #include #include +#include + +#include #include "common.h" -#include +using nlohmann::json; + TEST_CASE("Signature Known-Answer") { ensure_fips_if_required(); @@ -237,3 +241,135 @@ TEST_CASE("Signature Round-Trip") CHECK(sig.verify(data, signature2, *pub3)); } } + +TEST_CASE("Signature Key JWK Round-Trip") +{ + ensure_fips_if_required(); + const std::vector ids{ + Signature::ID::P256_SHA256, Signature::ID::P384_SHA384, + Signature::ID::P521_SHA512, Signature::ID::Ed25519, + Signature::ID::Ed448, + }; + + + for (const auto& id : ids) { + if (fips() && fips_disable(id)) { + continue; + } + + const auto& sig = select_signature(id); + + const auto priv = sig.generate_key_pair(); + const auto encoded_priv = sig.export_jwk_private(*priv); + const auto decoded_priv = sig.import_jwk_private(encoded_priv); + CHECK(sig.serialize_private(*priv) == sig.serialize_private(*decoded_priv)); + + const auto pub = priv->public_key(); + const auto encoded_pub = sig.export_jwk(*pub); + const auto decoded_pub = sig.import_jwk(encoded_pub); + CHECK(sig.serialize(*pub) == sig.serialize(*decoded_pub)); + } +} + +TEST_CASE("Signature Key JWK Known-Answer") +{ + ensure_fips_if_required(); + + struct KnownAnswerTest + { + Signature::ID id; + std::string jwk_priv; + std::string jwk_pub; + }; + + const std::vector cases{ + { + Signature::ID::P256_SHA256, + R"({ + "crv": "P-256", + "kty": "EC", + "d": "1J2hVVHFHYyeyfPmcXMG0uHLBn3i742jbRyUBGe2Jmg", + "x": "Bo8xjILpecBFkEwDa0H8p0_xCv7UPE2XMj8KcEe_LGE", + "y":"LbiO9VbkY1eRrR6UgdL7W4jhqej6Sfu6n6HvTRm5ySs" + })", + R"({ + "crv": "P-256", + "kty": "EC", + "x": "Bo8xjILpecBFkEwDa0H8p0_xCv7UPE2XMj8KcEe_LGE", + "y":"LbiO9VbkY1eRrR6UgdL7W4jhqej6Sfu6n6HvTRm5ySs" + })", + }, + { + Signature::ID::P384_SHA384, + R"({ + "crv": "P-384", + "kty": "EC", + "d": "uLHpTwv2Pucmo4ZVgO-0IYJQEyKQlJdukO1M6vZp0EYyvgCJhzoYWsBTbIUEOG8e", + "x": "apvkQ2jdEkTr5MkLePjAt2vW-Sk_djA7WjcPN_Waw_MS327r3WFCrchYEFvrMLru", + "y":"In9wbO3tnH9CQn0NMJm3DvgLtBF3OmcfOnW9sAy1I1-yHVs_N8Bph2Z46YbJGbdS" + })", + R"({ + "crv": "P-384", + "kty": "EC", + "x": "apvkQ2jdEkTr5MkLePjAt2vW-Sk_djA7WjcPN_Waw_MS327r3WFCrchYEFvrMLru", + "y":"In9wbO3tnH9CQn0NMJm3DvgLtBF3OmcfOnW9sAy1I1-yHVs_N8Bph2Z46YbJGbdS" + })", + }, + { + Signature::ID::P521_SHA512, + R"({ + "crv": "P-521", + "kty": "EC", + "d": "AckbSdZaMoA4ylPYStWyAUU20Wo5Yu8hsiocCmhqNniJ42ZXYzZ4EzQElB_sU6RGbPgwQmiKWTZ0jbi5NmZTC6mv", + "x": "AH9tp56TOvXzN_JWFGEAmk0HdkpDSPZBZOwX-xuT3RV0JjFVq3VWzMtMrf_x_Okt5QVNLBi-41no47VDDTK5UQiM", + "y":"AJtFy8ogaNhBxLx6CtjoOG5ptR3-z7CEz9I9ioIOJ9Q2a0vtixuNGa4ILQUY8vz-5_AuqHEWkIaK3sqrryYGPqdH" + })", + R"({ + "crv": "P-521", + "kty": "EC", + "x": "AH9tp56TOvXzN_JWFGEAmk0HdkpDSPZBZOwX-xuT3RV0JjFVq3VWzMtMrf_x_Okt5QVNLBi-41no47VDDTK5UQiM", + "y":"AJtFy8ogaNhBxLx6CtjoOG5ptR3-z7CEz9I9ioIOJ9Q2a0vtixuNGa4ILQUY8vz-5_AuqHEWkIaK3sqrryYGPqdH" + })", + }, + { + Signature::ID::Ed25519, + R"({ + "crv": "Ed25519", + "kty": "OKP", + "d": "kH7Q5NRfGgX1GSTTSz7ofQuQZNnLE5jWKP_RKT76Um8", + "x":"Rv-rXr1oUaa29TdaXzIhJEOV3eJYzMqy_luvV0T80no" + })", + R"({ + "crv": "Ed25519", + "kty": "OKP", + "x": "Rv-rXr1oUaa29TdaXzIhJEOV3eJYzMqy_luvV0T80no" + })", + }, + { + Signature::ID::Ed448, + R"({ + "crv": "Ed448", + "kty": "OKP", + "d": "NSnqhxrVgCDdREhKNkRsegr5o4WGIMUIdG9enGCBb413B4KgJ8URiZVPaVn0-AslPgHdkF--oFGV", + "x":"0P_035gQbBr8mHe_4uLu8wUI22JiSBYWq9Yzb3Tr3C4ksfv85Xo5OIRrWzE-L0QFRez78nrNA4wA" + })", + R"({ + "crv": "Ed448", + "kty": "OKP", + "x":"0P_035gQbBr8mHe_4uLu8wUI22JiSBYWq9Yzb3Tr3C4ksfv85Xo5OIRrWzE-L0QFRez78nrNA4wA" + })", + } + }; + + for (const auto& tc : cases) { + const auto& sig = select_signature(tc.id); + + auto imported_priv = sig.import_jwk_private(tc.jwk_priv); + auto exported_priv = sig.export_jwk_private(*imported_priv); + CHECK(json::parse(tc.jwk_priv) == json::parse(exported_priv)); + + auto imported_pub = sig.import_jwk(tc.jwk_pub); + auto exported_pub = sig.export_jwk(*imported_pub); + CHECK(json::parse(tc.jwk_pub) == json::parse(exported_pub)); + } +} diff --git a/test/crypto.cpp b/test/crypto.cpp index acbffa09..0eb023c7 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -107,7 +107,7 @@ TEST_CASE("Signature Key Serializion To JWK") std::string y; }; - std::vector cases{ + const auto cases = std::vector{ { CipherSuite::ID::P256_AES128GCM_SHA256_P256, true, from_hex( From 3d015beb5bcfd991a307d4909d141afe340d94cc Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sat, 26 Aug 2023 11:37:33 -0400 Subject: [PATCH 16/36] Add _draft_00 suffix --- include/mls/credential.h | 17 ++++++++--------- src/core_types.cpp | 4 ++-- src/credential.cpp | 17 ++++++++++++++++- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/include/mls/credential.h b/include/mls/credential.h index bdc63aa0..c36156df 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -50,14 +50,12 @@ struct X509Credential struct UserInfoVCCredential { UserInfoVCCredential() = default; - - explicit UserInfoVCCredential(bytes userinfo_vc_jwt) - : userinfo_vc_jwt(std::move(userinfo_vc_jwt)) - { - } + explicit UserInfoVCCredential(bytes userinfo_vc_jwt); bytes userinfo_vc_jwt; + bool valid_for(const SignaturePublicKey& pub) const; + TLS_SERIALIZABLE(userinfo_vc_jwt) }; @@ -75,8 +73,9 @@ enum struct CredentialType : uint16_t reserved = 0, basic = 1, x509 = 2, - userinfo_vc = 3, - multi = 4, + + userinfo_vc_draft_00 = 0xFE00, + multi_draft_00 = 0xFF00, // GREASE values, included here mainly so that debugger output looks nice GREASE_0 = 0x0A0A, @@ -201,7 +200,7 @@ namespace tls { TLS_VARIANT_MAP(mls::CredentialType, mls::BasicCredential, basic) TLS_VARIANT_MAP(mls::CredentialType, mls::X509Credential, x509) -TLS_VARIANT_MAP(mls::CredentialType, mls::UserInfoVCCredential, userinfo_vc) -TLS_VARIANT_MAP(mls::CredentialType, mls::MultiCredential, multi) +TLS_VARIANT_MAP(mls::CredentialType, mls::UserInfoVCCredential, userinfo_vc_draft_00) +TLS_VARIANT_MAP(mls::CredentialType, mls::MultiCredential, multi_draft_00) } // namespace tls diff --git a/src/core_types.cpp b/src/core_types.cpp index f66b864a..e8f27901 100644 --- a/src/core_types.cpp +++ b/src/core_types.cpp @@ -48,8 +48,8 @@ const std::array all_supported_ciphersuites = { const std::array all_supported_credentials = { CredentialType::basic, CredentialType::x509, - CredentialType::userinfo_vc, - CredentialType::multi + CredentialType::userinfo_vc_draft_00, + CredentialType::multi_draft_00 }; Capabilities diff --git a/src/credential.cpp b/src/credential.cpp index a77fa08e..b3aba283 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -109,6 +109,21 @@ operator==(const X509Credential& lhs, const X509Credential& rhs) return lhs.der_chain == rhs.der_chain; } +/// +/// UserInfoVCCredential +/// +UserInfoVCCredential::UserInfoVCCredential(bytes userinfo_vc_jwt) + : userinfo_vc_jwt(std::move(userinfo_vc_jwt)) +{} + +bool +UserInfoVCCredential::valid_for(const SignaturePublicKey& /* pub */) const +{ + // TODO Extract payload + // TODO Extract did:jwk + throw NotImplementedError(); +} + /// /// CredentialBinding and MultiCredential /// @@ -219,7 +234,7 @@ Credential::valid_for(const SignaturePublicKey& pub) const const auto pub_key_match = overloaded{ [&](const X509Credential& x509) { return x509.valid_for(pub); }, [](const BasicCredential& /* basic */) { return true; }, - [](const UserInfoVCCredential&) { return true; }, + [&](const UserInfoVCCredential& vc) { return vc.valid_for(pub); }, [&](const MultiCredential& multi) { return multi.valid_for(pub); }, }; From 7044910d21c12b90092b727628d809db80f1fcc3 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sat, 26 Aug 2023 11:38:16 -0400 Subject: [PATCH 17/36] Finish implementing JWK private key import --- lib/hpke/src/rsa.cpp | 3 +- lib/hpke/src/signature.cpp | 77 ++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/lib/hpke/src/rsa.cpp b/lib/hpke/src/rsa.cpp index 45ba444d..236de856 100644 --- a/lib/hpke/src/rsa.cpp +++ b/lib/hpke/src/rsa.cpp @@ -146,7 +146,8 @@ RSASignature::verify(const bytes& data, return rv == 1; } -// TODO(RLB) Implement these methods +// TODO(RLB) Implement these methods. No concrete need, but might be nice for +// completeness. std::unique_ptr RSASignature::import_jwk_private(const std::string& /* json_str */) const { diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 2356c723..45456a6c 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -114,49 +114,33 @@ struct GroupSignature : public Signature std::unique_ptr import_jwk_private( const std::string& json_str) const override { - // TODO(ghewett): handle failed parse - json jwk_json = json::parse(json_str); - - // TODO(ghewett): jwk_json should patch cipher suite + const auto jwk_json = validate_jwk_json(json_str, true); - // TODO(ghewett): handle the absense of 'd' - const bytes d = from_base64url(jwk_json["d"]); + const auto d = from_base64url(jwk_json["d"]); + auto gsk = group.deserialize_private(d); - return std::make_unique(group.deserialize_private(d).release()); + return std::make_unique(gsk.release()); } std::unique_ptr import_jwk( const std::string& json_str) const override { - bytes x = bytes({}, 0); - bytes y = bytes({}, 0); - json jwk_json = json::parse(json_str); - - if (jwk_json.empty() || !jwk_json.contains("kty") || - !jwk_json.contains("crv") || !jwk_json.contains("x")) { - throw std::runtime_error("import_jwk: malformed json input"); - } - - if (jwk_json["kty"] != group.jwk_key_type) { - throw std::runtime_error("import_jwk: group keytype does not match json"); - } - - if (jwk_json["crv"] != group.jwk_curve_name) { - throw std::runtime_error("import_jwk: group curve does not match json"); - } - x = from_base64url(jwk_json["x"]); + const auto jwk_json = validate_jwk_json(json_str, false); + const auto x = from_base64url(jwk_json["x"]); + auto y = bytes{}; if (jwk_json.contains("y")) { y = from_base64url(jwk_json["y"]); } + return group.public_key_from_coordinates(x, y); } std::string export_jwk(const Signature::PublicKey& pk) const override { const auto& gpk = dynamic_cast(pk); - const auto json_jwk = export_jwk_json(gpk); - return json_jwk.dump(); + const auto jwk_json = export_jwk_json(gpk); + return jwk_json.dump(); } std::string export_jwk_private(const Signature::PrivateKey& sk) const override @@ -165,36 +149,57 @@ struct GroupSignature : public Signature const auto& gsk = gssk.group_priv; const auto gpk = gsk->public_key(); - auto json_jwk = export_jwk_json(*gpk); + auto jwk_json = export_jwk_json(*gpk); // encode the private key const auto enc = serialize_private(sk); - json_jwk["d"] = to_base64url(enc); + jwk_json["d"] = to_base64url(enc); - return json_jwk.dump(); + return jwk_json.dump(); } private: const Group& group; + json validate_jwk_json(const std::string json_str, bool private_key) const + { + json jwk_json = json::parse(json_str); + + if (jwk_json.empty() || !jwk_json.contains("kty") || + !jwk_json.contains("crv") || !jwk_json.contains("x") || + (private_key && !jwk_json.contains("d"))) { + throw std::runtime_error("malformed JWK"); + } + + if (jwk_json["kty"] != group.jwk_key_type) { + throw std::runtime_error("invalid JWK key type"); + } + + if (jwk_json["crv"] != group.jwk_curve_name) { + throw std::runtime_error("invalid JWK curve"); + } + + return jwk_json; + } + json export_jwk_json(const Group::PublicKey& pk) const { const auto [x, y] = group.coordinates(pk); - json json_jwk; - json_jwk["crv"] = group.jwk_curve_name; - json_jwk["kty"] = group.jwk_key_type; + json jwk_json; + jwk_json["crv"] = group.jwk_curve_name; + jwk_json["kty"] = group.jwk_key_type; if (group.jwk_key_type == "EC") { - json_jwk["x"] = to_base64url(x); - json_jwk["y"] = to_base64url(y); + jwk_json["x"] = to_base64url(x); + jwk_json["y"] = to_base64url(y); } else if (group.jwk_key_type == "OKP") { - json_jwk["x"] = to_base64url(x); + jwk_json["x"] = to_base64url(x); } else { throw std::runtime_error("unknown key type"); } - return json_jwk; + return jwk_json; } }; From a449211db9e87b7bdb80f36a433797e124a09dfc Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sat, 26 Aug 2023 11:45:46 -0400 Subject: [PATCH 18/36] Only do round-trip JWK tests at the mlspp level --- lib/hpke/src/signature.cpp | 2 +- test/crypto.cpp | 142 ++++--------------------------------- 2 files changed, 14 insertions(+), 130 deletions(-) diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 45456a6c..81ef954a 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -15,7 +15,7 @@ #include #include -using namespace nlohmann; +using nlohmann::json; namespace hpke { diff --git a/test/crypto.cpp b/test/crypto.cpp index 0eb023c7..e2ad394c 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -1,12 +1,10 @@ #include #include #include -#include #include using namespace mls; using namespace mls_vectors; -using namespace nlohmann; TEST_CASE("Basic HPKE") { @@ -92,135 +90,21 @@ TEST_CASE("Signature Key Serializion") } } -TEST_CASE("Signature Key Serializion To JWK") +TEST_CASE("Signature Key JWK Import/Export") { + for (auto suite_id : all_supported_suites) { + const auto suite = CipherSuite{ suite_id }; + const auto priv = SignaturePrivateKey::generate(suite); + const auto pub = priv.public_key; + + const auto encoded_priv = priv.to_jwk(suite); + const auto decoded_priv = SignaturePrivateKey::from_jwk(suite, encoded_priv); + REQUIRE(decoded_priv == priv); + - struct KnownAnswerTest - { - CipherSuite suite; - bool supported; - bytes pk; - std::string kty; - std::string crv; - std::string d; - std::string x; - std::string y; - }; - - const auto cases = std::vector{ - { CipherSuite::ID::P256_AES128GCM_SHA256_P256, - true, - from_hex( - "cae90bad54df6973c64f7e4116ee78409045ed43e9668d0d474948a510f38acf"), - "EC", - "P-256", - "yukLrVTfaXPGT35BFu54QJBF7UPpZo0NR0lIpRDzis8", - "nUV1xGxWcUobNQrV0DsSN_z7P8hwVivmUji8EIJnrGg", - "2TGu_-lIxa7fn8PW-3gMNod-CjwwoAiLIhkbcsHtSdw" }, - { CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519, - true, - from_hex( - "9f959eeebab856bede41bfcd985077f5eaae702dde01c76b48952c35c9a97618"), - "OKP", - "Ed25519", - "n5We7rq4Vr7eQb_NmFB39equcC3eAcdrSJUsNcmpdhg", - "NmQinNknsQjwPFpujKmLa09alb4kagXy1YJenH3Zs-I", - "" }, - { CipherSuite::ID::X25519_CHACHA20POLY1305_SHA256_Ed25519, - true, - from_hex( - "f6d9dfcfc3e7f2016df7894b959e3f922d01035292732da12158f0c08b6251ae"), - "OKP", - "Ed25519", - "9tnfz8Pn8gFt94lLlZ4_ki0BA1KScy2hIVjwwItiUa4", - "kcnJ4z9eHBgiuFSDGlsF8PyibD2seAMncB4iKamamSU", - "" }, - { CipherSuite::ID::X448_AES256GCM_SHA512_Ed448, - true, - from_hex("e8dfd869ebe67fe696f0a0a12e04111cf1e4744e1a045fa73b2285a0168f319" - "e66522c9ddec741a8dd8011d0fc4b72303053901540c36f1e89"), - "OKP", - "Ed448", - "6N_Yaevmf-aW8KChLgQRHPHkdE4aBF-" - "nOyKFoBaPMZ5mUiyd3sdBqN2AEdD8S3IwMFOQFUDDbx6J", - "5uf09bDIVeecX74gv2ljKmvf3eLUXYiB6Jbycwww8ijcbnM04rfJr1agpFC2TuVSm5d0iDCj" - "EDIA", - "" }, - { CipherSuite::ID::P521_AES256GCM_SHA512_P521, - true, - from_hex( - "01c58ae6621000da12b682f45248f88b4cef278743a4fa325fc234f8770648d440cab3" - "367e90a49293c02778732776bd3eb985415c5f9df77a212e2097f0026298b8"), - "EC", - "P-521", - "AcWK5mIQANoStoL0Ukj4i0zvJ4dDpPoyX8I0-" - "HcGSNRAyrM2fpCkkpPAJ3hzJ3a9PrmFQVxfnfd6IS4gl_ACYpi4", - "AFLfr4vhftq9G6axgJ8g6xdukrUFn2cD5HDIxp8uzSbYW_" - "QIjKdUV1pF2vzzcz7Vj185LE6kl1SqTX6Z551W38mC", - "AbPIkuJkgfBZCidxSFrJALD1_e8-tKE0Ygy1dF2PZXJMGcHQRPbnytg-" - "4iVVGbjVdcakGIuUq3aAO09NqLi8j81d" }, - { CipherSuite::ID::X448_CHACHA20POLY1305_SHA512_Ed448, - true, - from_hex("5535d624e127fed3bc20d24a51269ce842e1ce36d6a62002b7f59696fcd3d9e" - "7d865da15e8e690caf22c34bf04bd34bd761be1eacb26fec193"), - "OKP", - "Ed448", - "VTXWJOEn_tO8INJKUSac6ELhzjbWpiACt_" - "WWlvzT2efYZdoV6OaQyvIsNL8EvTS9dhvh6ssm_sGT", - "jfbh2FAWZ57XmEEgrlGLAk6Am-qZ1IibFy2qip1uU3zOfWJ-TXmq4Ty-" - "yssJdZ5c0niU3SNO7JkA", - "" }, - { CipherSuite::ID::P384_AES256GCM_SHA384_P384, - true, - from_hex("33500ad0e749f53707e1f5ebef7d80758f95923c5b02acd89c21ffb2eb9f4f0" - "ccc5db144cd92e1577963dfb1b4e3fa68"), - "EC", - "P-384", - "M1AK0OdJ9TcH4fXr732AdY-VkjxbAqzYnCH_suufTwzMXbFEzZLhV3lj37G04_po", - "FyXCw9vukrBkLD_Lu7HvZw6cr-gwvpldN4aqZgtjAuM1rRSL74Lfi3CBBD8LpB0A", - "UUd8Qs3VdkOTFJlP62TKaVBp0JZlD74b7TU2gNlkDX3o8EIfl4POCooLs920bCJf" } - }; - - for (const auto& tc : cases) { - const CipherSuite suite{ tc.suite }; - - if (!tc.supported) { - auto private_key = SignaturePrivateKey::generate(suite); - CHECK_THROWS_WITH(private_key.to_jwk(suite), "Unsupported group"); - continue; - } - - // Export Private Key - auto private_key = SignaturePrivateKey::parse(suite, tc.pk); - auto jwk_str = private_key.to_jwk(tc.suite); - auto jwk_json = json::parse(jwk_str); - REQUIRE(jwk_json["kty"] == tc.kty); - REQUIRE(jwk_json["crv"] == tc.crv); - REQUIRE(jwk_json["d"] == tc.d); - REQUIRE(jwk_json["x"] == tc.x); - - if (!tc.y.empty()) { - REQUIRE(jwk_json["y"] == tc.y); - } - - // Export Public Key - auto jwk_pk_str = private_key.public_key.to_jwk(tc.suite); - auto jwk_pk_json = json::parse(jwk_pk_str); - REQUIRE(jwk_pk_json["kty"] == tc.kty); - REQUIRE(jwk_pk_json["crv"] == tc.crv); - REQUIRE(jwk_pk_json["x"] == tc.x); - - if (!tc.y.empty()) { - REQUIRE(jwk_pk_json["y"] == tc.y); - } - - // Import Private Key - auto import_jwk_sk = SignaturePrivateKey::from_jwk(tc.suite, jwk_str); - REQUIRE(tc.pk == import_jwk_sk.data); - - // Import Public Key - auto import_jwk_pk = SignaturePublicKey::from_jwk(tc.suite, jwk_pk_str); - REQUIRE(private_key.public_key.data == import_jwk_pk.data); + const auto encoded_pub = pub.to_jwk(suite); + const auto decoded_pub = SignaturePublicKey::from_jwk(suite, encoded_pub); + REQUIRE(decoded_pub == pub); } } From 840a39fe273fa3a1a07f21b5d1b413b4c7914749 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sat, 26 Aug 2023 11:50:02 -0400 Subject: [PATCH 19/36] clang-format --- include/mls/credential.h | 4 +++- lib/hpke/src/rsa.h | 3 ++- lib/hpke/src/signature.cpp | 2 +- lib/hpke/test/signature.cpp | 6 ++++-- src/credential.cpp | 5 +++-- test/crypto.cpp | 4 ++-- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/include/mls/credential.h b/include/mls/credential.h index c36156df..1ce4d2c7 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -200,7 +200,9 @@ namespace tls { TLS_VARIANT_MAP(mls::CredentialType, mls::BasicCredential, basic) TLS_VARIANT_MAP(mls::CredentialType, mls::X509Credential, x509) -TLS_VARIANT_MAP(mls::CredentialType, mls::UserInfoVCCredential, userinfo_vc_draft_00) +TLS_VARIANT_MAP(mls::CredentialType, + mls::UserInfoVCCredential, + userinfo_vc_draft_00) TLS_VARIANT_MAP(mls::CredentialType, mls::MultiCredential, multi_draft_00) } // namespace tls diff --git a/lib/hpke/src/rsa.h b/lib/hpke/src/rsa.h index c88d90a4..8457252f 100644 --- a/lib/hpke/src/rsa.h +++ b/lib/hpke/src/rsa.h @@ -82,7 +82,8 @@ struct RSASignature : public Signature const std::string& json_str) const override; std::unique_ptr import_jwk( const std::string& json_str) const override; - std::string export_jwk_private(const Signature::PrivateKey& sk) const override; + std::string export_jwk_private( + const Signature::PrivateKey& sk) const override; std::string export_jwk(const Signature::PublicKey& pk) const override; private: diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 81ef954a..d1aa6ed1 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -117,7 +117,7 @@ struct GroupSignature : public Signature const auto jwk_json = validate_jwk_json(json_str, true); const auto d = from_base64url(jwk_json["d"]); - auto gsk = group.deserialize_private(d); + auto gsk = group.deserialize_private(d); return std::make_unique(gsk.release()); } diff --git a/lib/hpke/test/signature.cpp b/lib/hpke/test/signature.cpp index 1268bcfb..c8f8a149 100644 --- a/lib/hpke/test/signature.cpp +++ b/lib/hpke/test/signature.cpp @@ -251,7 +251,6 @@ TEST_CASE("Signature Key JWK Round-Trip") Signature::ID::Ed448, }; - for (const auto& id : ids) { if (fips() && fips_disable(id)) { continue; @@ -282,6 +281,8 @@ TEST_CASE("Signature Key JWK Known-Answer") std::string jwk_pub; }; + // XXX(RLB) clang-format wants to indent this really far to the right. + // clang-format off const std::vector cases{ { Signature::ID::P256_SHA256, @@ -358,8 +359,9 @@ TEST_CASE("Signature Key JWK Known-Answer") "kty": "OKP", "x":"0P_035gQbBr8mHe_4uLu8wUI22JiSBYWq9Yzb3Tr3C4ksfv85Xo5OIRrWzE-L0QFRez78nrNA4wA" })", - } + }, }; + // clang-format on for (const auto& tc : cases) { const auto& sig = select_signature(tc.id); diff --git a/src/credential.cpp b/src/credential.cpp index b3aba283..dcd33662 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -113,8 +113,9 @@ operator==(const X509Credential& lhs, const X509Credential& rhs) /// UserInfoVCCredential /// UserInfoVCCredential::UserInfoVCCredential(bytes userinfo_vc_jwt) - : userinfo_vc_jwt(std::move(userinfo_vc_jwt)) -{} + : userinfo_vc_jwt(std::move(userinfo_vc_jwt)) +{ +} bool UserInfoVCCredential::valid_for(const SignaturePublicKey& /* pub */) const diff --git a/test/crypto.cpp b/test/crypto.cpp index e2ad394c..a21b517e 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -98,10 +98,10 @@ TEST_CASE("Signature Key JWK Import/Export") const auto pub = priv.public_key; const auto encoded_priv = priv.to_jwk(suite); - const auto decoded_priv = SignaturePrivateKey::from_jwk(suite, encoded_priv); + const auto decoded_priv = + SignaturePrivateKey::from_jwk(suite, encoded_priv); REQUIRE(decoded_priv == priv); - const auto encoded_pub = pub.to_jwk(suite); const auto decoded_pub = SignaturePublicKey::from_jwk(suite, encoded_pub); REQUIRE(decoded_pub == pub); From 2249d88aaf693f50fbe9e4d6be1d34d838d9f3ea Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sat, 26 Aug 2023 12:54:55 -0400 Subject: [PATCH 20/36] clang-tidy --- lib/hpke/src/signature.cpp | 2 +- src/credential.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index d1aa6ed1..0269d107 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -161,7 +161,7 @@ struct GroupSignature : public Signature private: const Group& group; - json validate_jwk_json(const std::string json_str, bool private_key) const + json validate_jwk_json(const std::string& json_str, bool private_key) const { json jwk_json = json::parse(json_str); diff --git a/src/credential.cpp b/src/credential.cpp index dcd33662..f9d8016a 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -118,10 +118,10 @@ UserInfoVCCredential::UserInfoVCCredential(bytes userinfo_vc_jwt) } bool +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) UserInfoVCCredential::valid_for(const SignaturePublicKey& /* pub */) const { - // TODO Extract payload - // TODO Extract did:jwk + // TODO(RLB) Extract payload -> did:jwk, compare throw NotImplementedError(); } From ea43cbfeffe396c945036cb57e08ebfe2bd0659b Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sun, 27 Aug 2023 13:36:14 -0400 Subject: [PATCH 21/36] Copy/paste errors --- Makefile | 14 ++++++++++++-- lib/hpke/src/group.cpp | 24 +++++++++++++----------- lib/hpke/src/signature.cpp | 25 +++++++++++++------------ 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 29745db0..7626b1e1 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,9 @@ BUILD_DIR=build TEST_DIR=build/test CLANG_FORMAT=clang-format -i CLANG_TIDY=OFF +OPENSSL3_MANIFEST=alternatives/openssl_3 -.PHONY: all dev test ctest dtest dbtest libs test-libs test-all everything ci clean cclean format +.PHONY: all dev dev3 test ctest dtest dbtest libs test-libs test-all everything ci ci3 clean cclean format all: ${BUILD_DIR} cmake --build ${BUILD_DIR} --target mlspp @@ -22,6 +23,10 @@ dev: # too slow, and we can run them in CI cmake -B${BUILD_DIR} -DTESTING=ON -DCMAKE_BUILD_TYPE=Debug . +dev3: + # Like `dev`, but using OpenSSL 3 + cmake -B${BUILD_DIR} -DTESTING=ON -DCMAKE_BUILD_TYPE=Debug -DVCPKG_MANIFEST_DIR=${OPENSSL3_MANIFEST} . + test: ${BUILD_DIR} test/* cmake --build ${BUILD_DIR} --target mlspp_test @@ -53,7 +58,12 @@ everything: ${BUILD_DIR} ci: cmake -B ${BUILD_DIR} -DTESTING=ON -DCLANG_TIDY=ON -DSANITIZERS=ON \ - -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="${VCPKG_TOOLCHAIN_FILE}" . + -DCMAKE_BUILD_TYPE=Debug . + +ci3: + # Like `ci`, but using OpenSSL 3 + cmake -B ${BUILD_DIR} -DTESTING=ON -DCLANG_TIDY=ON -DSANITIZERS=ON \ + -DCMAKE_BUILD_TYPE=Debug -DVCPKG_MANIFEST_DIR=${OPENSSL3_MANIFEST} . clean: cmake --build ${BUILD_DIR} --target clean diff --git a/lib/hpke/src/group.cpp b/lib/hpke/src/group.cpp index 3e9c2d19..a5b50079 100644 --- a/lib/hpke/src/group.cpp +++ b/lib/hpke/src/group.cpp @@ -541,29 +541,31 @@ struct ECKeyGroup : public EVPGroup // Raw pointer OK here because it becomes managed as soon as possible OSSL_PARAM* param_ptr = nullptr; if (1 != - EVP_PKE_y_todata(rpk.pkey.get(), EVP_PKE_y_PUBLIC_KE_y, ¶m_ptr)) { + EVP_PKEY_todata(rpk.pkey.get(), EVP_PKEY_PUBLIC_KEY, ¶m_ptr)) { throw openssl_error(); } - auto param = make_typed_unique(param); + auto param = make_typed_unique(param_ptr); // Raw pointer OK here because it is non-owning const auto* pk_param = - OSSL_PARAM_locate_const(param.get(), OSSL_PKE_y_PARAM_PUB_KE_y); + OSSL_PARAM_locate_const(param.get(), OSSL_PKEY_PARAM_PUB_KEY); if (pk_param == nullptr) { - throw std::runtime_error("Failed to locate OSSL_PKE_y_PARAM_PUB_KE_y"); + throw std::runtime_error("Failed to locate OSSL_PKEY_PARAM_PUB_KEY"); } // Copy the octet string representation of the key into a buffer auto len = size_t(0); if (1 != OSSL_PARAM_get_octet_string(pk_param, nullptr, 0, &len)) { - throw std::runtime_error("Failed to get OSSL_PKE_y_PARAM_PUB_KE_y len"); + throw std::runtime_error("Failed to get OSSL_PKEY_PARAM_PUB_KEY len"); } auto buf = bytes(len); auto* buf_ptr = buf.data(); - if (1 != OSSL_PARAM_get_octet_string(pk_param, &buf_ptr, len, nullptr)) { - throw std::runtime_error("Failed to get OSSL_PKE_y_PARAM_PUB_KE_y data"); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto* buf_ptr_void = reinterpret_cast(buf_ptr); + if (1 != OSSL_PARAM_get_octet_string(pk_param, &buf_ptr_void, len, nullptr)) { + throw std::runtime_error("Failed to get OSSL_PKEY_PARAM_PUB_KEY data"); } // Parse the octet string representation into an EC_POINT @@ -641,7 +643,7 @@ struct ECKeyGroup : public EVPGroup } // Construct a point with the given coordinates - auto point = make_typed_unique(EC_POINT_new(group)); + auto point = make_typed_unique(EC_POINT_new(group.get())); if (group == nullptr) { throw std::runtime_error("Failed to create EC_POINT"); } @@ -653,14 +655,14 @@ struct ECKeyGroup : public EVPGroup // Serialize the point const auto point_size = EC_POINT_point2oct( - group, point, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); + group.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); if (0 == point_size) { throw openssl_error(); } auto pub = bytes(point_size); - if (EC_POINT_point2oct(group, - point, + if (EC_POINT_point2oct(group.get(), + point.get(), POINT_CONVERSION_UNCOMPRESSED, pub.data(), point_size, diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 0269d107..890257a8 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -116,7 +116,7 @@ struct GroupSignature : public Signature { const auto jwk_json = validate_jwk_json(json_str, true); - const auto d = from_base64url(jwk_json["d"]); + const auto d = from_base64url(jwk_json.at("d")); auto gsk = group.deserialize_private(d); return std::make_unique(gsk.release()); @@ -127,10 +127,10 @@ struct GroupSignature : public Signature { const auto jwk_json = validate_jwk_json(json_str, false); - const auto x = from_base64url(jwk_json["x"]); + const auto x = from_base64url(jwk_json.at("x")); auto y = bytes{}; if (jwk_json.contains("y")) { - y = from_base64url(jwk_json["y"]); + y = from_base64url(jwk_json.at("y")); } return group.public_key_from_coordinates(x, y); @@ -153,7 +153,7 @@ struct GroupSignature : public Signature // encode the private key const auto enc = serialize_private(sk); - jwk_json["d"] = to_base64url(enc); + jwk_json.emplace("d", to_base64url(enc)); return jwk_json.dump(); } @@ -171,11 +171,11 @@ struct GroupSignature : public Signature throw std::runtime_error("malformed JWK"); } - if (jwk_json["kty"] != group.jwk_key_type) { + if (jwk_json.at("kty") != group.jwk_key_type) { throw std::runtime_error("invalid JWK key type"); } - if (jwk_json["crv"] != group.jwk_curve_name) { + if (jwk_json.at("crv") != group.jwk_curve_name) { throw std::runtime_error("invalid JWK curve"); } @@ -186,15 +186,16 @@ struct GroupSignature : public Signature { const auto [x, y] = group.coordinates(pk); - json jwk_json; - jwk_json["crv"] = group.jwk_curve_name; - jwk_json["kty"] = group.jwk_key_type; + json jwk_json = json::object({ + { "crv", group.jwk_curve_name }, + { "kty", group.jwk_key_type }, + }); if (group.jwk_key_type == "EC") { - jwk_json["x"] = to_base64url(x); - jwk_json["y"] = to_base64url(y); + jwk_json.emplace("x", to_base64url(x)); + jwk_json.emplace("y", to_base64url(y)); } else if (group.jwk_key_type == "OKP") { - jwk_json["x"] = to_base64url(x); + jwk_json.emplace("x", to_base64url(x)); } else { throw std::runtime_error("unknown key type"); } From 209e0d61a8640459ee81ce41b580ee37cc29f55a Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sun, 27 Aug 2023 13:38:58 -0400 Subject: [PATCH 22/36] clang-format --- lib/hpke/src/group.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/hpke/src/group.cpp b/lib/hpke/src/group.cpp index a5b50079..754fb277 100644 --- a/lib/hpke/src/group.cpp +++ b/lib/hpke/src/group.cpp @@ -540,8 +540,7 @@ struct ECKeyGroup : public EVPGroup #if defined(WITH_OPENSSL3) // Raw pointer OK here because it becomes managed as soon as possible OSSL_PARAM* param_ptr = nullptr; - if (1 != - EVP_PKEY_todata(rpk.pkey.get(), EVP_PKEY_PUBLIC_KEY, ¶m_ptr)) { + if (1 != EVP_PKEY_todata(rpk.pkey.get(), EVP_PKEY_PUBLIC_KEY, ¶m_ptr)) { throw openssl_error(); } @@ -564,7 +563,8 @@ struct ECKeyGroup : public EVPGroup auto* buf_ptr = buf.data(); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) auto* buf_ptr_void = reinterpret_cast(buf_ptr); - if (1 != OSSL_PARAM_get_octet_string(pk_param, &buf_ptr_void, len, nullptr)) { + if (1 != + OSSL_PARAM_get_octet_string(pk_param, &buf_ptr_void, len, nullptr)) { throw std::runtime_error("Failed to get OSSL_PKEY_PARAM_PUB_KEY data"); } @@ -654,8 +654,12 @@ struct ECKeyGroup : public EVPGroup } // Serialize the point - const auto point_size = EC_POINT_point2oct( - group.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); + const auto point_size = EC_POINT_point2oct(group.get(), + point.get(), + POINT_CONVERSION_UNCOMPRESSED, + nullptr, + 0, + nullptr); if (0 == point_size) { throw openssl_error(); } From 0ecf80b931b5eb707f62607634359dbb133cb6f5 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sun, 27 Aug 2023 14:04:09 -0400 Subject: [PATCH 23/36] Allow recursion; forbid multi-credential nesting --- .clang-tidy | 1 + src/credential.cpp | 4 ++++ src/treekem.cpp | 5 ++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index ab2a8131..36586010 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -22,6 +22,7 @@ Checks: '*, -llvmlibc-restrict-system-libc-headers, -misc-non-private-member-variables-in-classes, -misc-use-anonymous-namespace, + -misc-no-recursion, -modernize-use-nodiscard, -modernize-use-trailing-return-type, -readability-function-cognitive-complexity, diff --git a/src/credential.cpp b/src/credential.cpp index f9d8016a..bb6ba3ae 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -147,6 +147,10 @@ CredentialBinding::CredentialBinding(CipherSuite cipher_suite_in, , credential(std::move(credential_in)) , credential_key(credential_priv.public_key) { + if (credential.type() == CredentialType::multi_draft_00) { + throw InvalidParameterError("Multi-credentials cannot be nested"); + } + if (!credential.valid_for(credential_key)) { throw InvalidParameterError("Credential key does not match credential"); } diff --git a/src/treekem.cpp b/src/treekem.cpp index 211c2f8a..92254104 100644 --- a/src/treekem.cpp +++ b/src/treekem.cpp @@ -531,7 +531,7 @@ TreeKEMPublicKey::parent_hash_valid() const } std::vector -TreeKEMPublicKey::resolve(NodeIndex index) const // NOLINT(misc-no-recursion) +TreeKEMPublicKey::resolve(NodeIndex index) const { auto at_leaf = (index.level() == 0); if (!node_at(index).blank()) { @@ -797,7 +797,7 @@ struct TreeHashInput }; const bytes& -TreeKEMPublicKey::get_hash(NodeIndex index) // NOLINT(misc-no-recursion) +TreeKEMPublicKey::get_hash(NodeIndex index) { if (hashes.count(index) > 0) { return hashes.at(index); @@ -905,7 +905,6 @@ TreeKEMPublicKey::parent_hashes( } const bytes& -// NOLINTNEXTLINE(misc-no-recursion) TreeKEMPublicKey::original_tree_hash(TreeHashCache& cache, NodeIndex index, std::vector parent_except) const From 64178801f5a987b8a854baa41f4d56d3338aed3e Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Mon, 28 Aug 2023 12:45:31 -0500 Subject: [PATCH 24/36] responding to @suhasHere comments. Thank you! --- include/mls/credential.h | 2 +- lib/hpke/src/base64.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/mls/credential.h b/include/mls/credential.h index 1ce4d2c7..e1616119 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -188,7 +188,7 @@ struct CredentialBinding bool valid_for(const SignaturePublicKey& signature_key) const; - TLS_SERIALIZABLE(credential, credential_key, signature) + TLS_SERIALIZABLE(cipher_suite, credential, credential_key, signature) private: bytes to_be_signed(const SignaturePublicKey& signature_key) const; diff --git a/lib/hpke/src/base64.cpp b/lib/hpke/src/base64.cpp index 15cec158..7ff46eac 100644 --- a/lib/hpke/src/base64.cpp +++ b/lib/hpke/src/base64.cpp @@ -16,6 +16,8 @@ to_base64(const bytes& data) } const auto data_size = static_cast(data.size()); + + // base64 encoding produces 4 characters for every 3 input bytes (rounded up) const auto out_size = (data_size + 2) / 3 * 4; auto out = bytes(out_size + 1); // NUL terminator From 0e6ce4117e310fbc7c25102290580c38a32deb73 Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Thu, 31 Aug 2023 10:28:20 -0500 Subject: [PATCH 25/36] Allowing for dynamic top-level namespacing (#359) * Allowing dynamic top-level namespace * changing namespace from mls to MLS_NAMESPACE * changing namespace hpke with MLS_NAMESPACE::hpke * changing bytes_ns with MLS_NAMESPACE::bytes_ns * changing tls with MLS_NAMESPACE::tls * Reduced to one cmake option * adding underscore to CXX namespace --- .github/workflows/style.yml | 7 +- .gitignore | 1 + CMakeLists.txt | 62 ++- Makefile | 1 + cmake/config.cmake.in | 2 +- cmake/namespace.h.in | 4 + cmd/interop/src/json_details.h | 69 ++-- cmd/interop/src/main.cpp | 21 +- cmd/interop/src/mls_client_impl.cpp | 390 ++++++++++-------- cmd/interop/src/mls_client_impl.h | 45 +- include/mls/common.h | 11 +- include/mls/core_types.h | 19 +- include/mls/credential.h | 25 +- include/mls/crypto.h | 8 +- include/mls/key_schedule.h | 5 +- include/mls/messages.h | 95 +++-- include/mls/session.h | 5 +- include/mls/state.h | 5 +- include/mls/tree_math.h | 5 +- include/mls/treekem.h | 19 +- lib/bytes/CMakeLists.txt | 1 + lib/bytes/include/bytes/bytes.h | 5 +- lib/bytes/src/bytes.cpp | 5 +- lib/bytes/test/bytes.cpp | 2 +- lib/hpke/CMakeLists.txt | 1 + lib/hpke/include/hpke/base64.h | 6 +- lib/hpke/include/hpke/certificate.h | 7 +- lib/hpke/include/hpke/digest.h | 8 +- lib/hpke/include/hpke/hpke.h | 7 +- lib/hpke/include/hpke/random.h | 7 +- lib/hpke/include/hpke/signature.h | 7 +- lib/hpke/src/aead_cipher.cpp | 5 +- lib/hpke/src/aead_cipher.h | 5 +- lib/hpke/src/base64.cpp | 4 +- lib/hpke/src/certificate.cpp | 5 +- lib/hpke/src/common.cpp | 5 +- lib/hpke/src/common.h | 5 +- lib/hpke/src/dhkem.cpp | 5 +- lib/hpke/src/dhkem.h | 5 +- lib/hpke/src/digest.cpp | 5 +- lib/hpke/src/group.cpp | 5 +- lib/hpke/src/group.h | 5 +- lib/hpke/src/hkdf.cpp | 5 +- lib/hpke/src/hkdf.h | 5 +- lib/hpke/src/hpke.cpp | 5 +- lib/hpke/src/openssl_common.cpp | 5 +- lib/hpke/src/openssl_common.h | 5 +- lib/hpke/src/random.cpp | 5 +- lib/hpke/src/rsa.cpp | 5 +- lib/hpke/src/rsa.h | 5 +- lib/hpke/src/signature.cpp | 9 +- lib/hpke/test/base64.cpp | 4 +- lib/hpke/test/certificate.cpp | 2 +- lib/hpke/test/common.h | 4 +- lib/hpke/test/hpke.cpp | 14 +- lib/hpke/test/random.cpp | 2 +- lib/mls_vectors/CMakeLists.txt | 1 + .../include/mls_vectors/mls_vectors.h | 204 ++++----- lib/mls_vectors/src/mls_vectors.cpp | 26 +- lib/mls_vectors/test/mls_vectors.cpp | 16 +- lib/tls_syntax/CMakeLists.txt | 1 + lib/tls_syntax/include/tls/compat.h | 5 +- lib/tls_syntax/include/tls/tls_syntax.h | 5 +- lib/tls_syntax/src/tls_syntax.cpp | 5 +- lib/tls_syntax/test/tls_syntax.cpp | 47 +-- src/common.cpp | 5 +- src/core_types.cpp | 5 +- src/credential.cpp | 9 +- src/crypto.cpp | 17 +- src/grease.cpp | 5 +- src/grease.h | 5 +- src/key_schedule.cpp | 5 +- src/messages.cpp | 5 +- src/session.cpp | 8 +- src/state.cpp | 5 +- src/tree_math.cpp | 5 +- src/treekem.cpp | 5 +- test/credential.cpp | 2 +- test/crypto.cpp | 2 +- test/key_schedule.cpp | 2 +- test/messages.cpp | 2 +- test/session.cpp | 19 +- test/state.cpp | 2 +- test/treekem.cpp | 2 +- 84 files changed, 796 insertions(+), 608 deletions(-) create mode 100644 cmake/namespace.h.in diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index c4280cfe..5a9a40a8 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -13,9 +13,10 @@ jobs: - 'cmd' - 'lib' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Run clang-format style check for C/C++ programs. - uses: jidicula/clang-format-action@v4.6.2 + uses: jidicula/clang-format-action@v4.11.0 with: - clang-format-version: '14' + clang-format-version: '16' check-path: ${{ matrix.path }} + fallback-style: 'Mozilla' diff --git a/.gitignore b/.gitignore index 4d0232c0..d42f84a0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ out *.swp .vs/** .vscode/** +include/namespace.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bf9d8798..28e8efd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,12 +8,30 @@ project(mlspp option(TESTING "Build tests" OFF) option(CLANG_TIDY "Perform linting with clang-tidy" OFF) option(SANITIZERS "Enable sanitizers" OFF) +option(MLS_NAMESPACE_SUFFIX "Namespace Suffix for CXX and CMake Export") + +if(MLS_NAMESPACE_SUFFIX) + set(MLS_CXX_NAMESPACE "mls_${MLS_NAMESPACE_SUFFIX}" CACHE STRING "Top-level Namespace for CXX") + set(MLS_EXPORT_NAMESPACE "MLSPP${MLS_NAMESPACE_SUFFIX}" CACHE STRING "Namespace for CMake Export") +else() + set(MLS_CXX_NAMESPACE "mls" CACHE STRING "Top-level Namespace for CXX") + set(MLS_EXPORT_NAMESPACE "MLSPP" CACHE STRING "Namespace for CMake Export") +endif() +message(STATUS "CXX Namespace: ${MLS_CXX_NAMESPACE}") +message(STATUS "CMake Export Namespace: ${MLS_EXPORT_NAMESPACE}") + ### ### Global Config ### set_property(GLOBAL PROPERTY USE_FOLDERS ON) +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/namespace.h.in" + "${CMAKE_CURRENT_SOURCE_DIR}/include/namespace.h" + @ONLY +) + include(CheckCXXCompilerFlag) include(CMakePackageConfigHelpers) include(GNUInstallDirs) @@ -127,16 +145,22 @@ endif() ### Exports ### set(CMAKE_EXPORT_PACKAGE_REGISTRY ON) -export(EXPORT mlspp-targets NAMESPACE MLSPP:: FILE mlspp-targets.cmake) -export(PACKAGE MLSPP) +export( + EXPORT + mlspp-targets + NAMESPACE + ${MLS_EXPORT_NAMESPACE}:: + FILE + ${MLS_EXPORT_NAMESPACE}Targets.cmake) +export(PACKAGE ${MLS_EXPORT_NAMESPACE}) configure_package_config_file(cmake/config.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_DATADIR}/mlspp + ${CMAKE_CURRENT_BINARY_DIR}/${MLS_EXPORT_NAMESPACE}Config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_DATADIR}/${MLS_EXPORT_NAMESPACE} NO_SET_AND_CHECK_MACRO) write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config-version.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${MLS_EXPORT_NAMESPACE}ConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) @@ -144,6 +168,16 @@ write_basic_package_version_file( ### Install ### +install( + EXPORT + mlspp-targets + NAMESPACE + ${MLS_EXPORT_NAMESPACE}:: + FILE + ${MLS_EXPORT_NAMESPACE}Targets.cmake + DESTINATION + ${CMAKE_INSTALL_DATADIR}/${MLS_EXPORT_NAMESPACE}) + install( DIRECTORY include/ @@ -152,18 +186,16 @@ install( install( FILES - ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config.cmake - ${CMAKE_CURRENT_BINARY_DIR}/mlspp-config-version.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${MLS_EXPORT_NAMESPACE}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${MLS_EXPORT_NAMESPACE}ConfigVersion.cmake DESTINATION - ${CMAKE_INSTALL_DATADIR}/mlspp) + ${CMAKE_INSTALL_DATADIR}/${MLS_EXPORT_NAMESPACE}) install( - EXPORT - mlspp-targets - NAMESPACE - MLSPP:: - FILE - mlspp-targets.cmake + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE DESTINATION - ${CMAKE_INSTALL_DATADIR}/mlspp) + ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME} + RENAME + copyright) diff --git a/Makefile b/Makefile index 7626b1e1..206d326e 100644 --- a/Makefile +++ b/Makefile @@ -75,4 +75,5 @@ format: find include -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} find src -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} find test -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} + find cmd -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} find lib -iname "*.h" -or -iname "*.cpp" | grep -v "test_vectors.cpp" | xargs ${CLANG_FORMAT} diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in index 39e39cbe..82145af7 100644 --- a/cmake/config.cmake.in +++ b/cmake/config.cmake.in @@ -1,4 +1,4 @@ @PACKAGE_INIT@ -include(${CMAKE_CURRENT_LIST_DIR}/mlspp-targets.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/@MLS_EXPORT_NAMESPACE@Targets.cmake) check_required_components(mlspp) diff --git a/cmake/namespace.h.in b/cmake/namespace.h.in new file mode 100644 index 00000000..79dfad2c --- /dev/null +++ b/cmake/namespace.h.in @@ -0,0 +1,4 @@ +#pragma once + +// Configurable top-level MLS namespace +#define MLS_NAMESPACE @MLS_CXX_NAMESPACE@ diff --git a/cmd/interop/src/json_details.h b/cmd/interop/src/json_details.h index 60d5e1ea..bac888b9 100644 --- a/cmd/interop/src/json_details.h +++ b/cmd/interop/src/json_details.h @@ -43,7 +43,8 @@ struct adl_serializer> }; // LeafCount, NodeCount, etc. -// XXX(RLB): For some reason, just defining this for mls::Uint32 didn't work. +// XXX(RLB): For some reason, just defining this for MLS_NAMESPACE::Uint32 +// didn't work. template struct uint_serializer { @@ -54,26 +55,25 @@ struct uint_serializer #define UINT_SERIALIZER(T) \ template<> \ struct adl_serializer : uint_serializer \ - { \ - }; + {}; -UINT_SERIALIZER(mls::LeafCount) -UINT_SERIALIZER(mls::NodeCount) -UINT_SERIALIZER(mls::LeafIndex) -UINT_SERIALIZER(mls::NodeIndex) +UINT_SERIALIZER(MLS_NAMESPACE::LeafCount) +UINT_SERIALIZER(MLS_NAMESPACE::NodeCount) +UINT_SERIALIZER(MLS_NAMESPACE::LeafIndex) +UINT_SERIALIZER(MLS_NAMESPACE::NodeIndex) -// mls::Ciphersuite +// MLS_NAMESPACE::Ciphersuite template<> -struct adl_serializer +struct adl_serializer { - static void to_json(json& j, const mls::CipherSuite& v) + static void to_json(json& j, const MLS_NAMESPACE::CipherSuite& v) { j = v.cipher_suite(); } - static void from_json(const json& j, mls::CipherSuite& v) + static void from_json(const json& j, MLS_NAMESPACE::CipherSuite& v) { - v = mls::CipherSuite(j.get()); + v = MLS_NAMESPACE::CipherSuite(j.get()); } }; @@ -94,43 +94,44 @@ struct asymmetric_key_serializer #define ASYMM_KEY_SERIALIZER(T) \ template<> \ struct adl_serializer : asymmetric_key_serializer \ - { \ - }; + {}; -ASYMM_KEY_SERIALIZER(mls::HPKEPublicKey) -ASYMM_KEY_SERIALIZER(mls::HPKEPrivateKey) -ASYMM_KEY_SERIALIZER(mls::SignaturePublicKey) -ASYMM_KEY_SERIALIZER(mls::SignaturePrivateKey) +ASYMM_KEY_SERIALIZER(MLS_NAMESPACE::HPKEPublicKey) +ASYMM_KEY_SERIALIZER(MLS_NAMESPACE::HPKEPrivateKey) +ASYMM_KEY_SERIALIZER(MLS_NAMESPACE::SignaturePublicKey) +ASYMM_KEY_SERIALIZER(MLS_NAMESPACE::SignaturePrivateKey) // Other TLS-serializable things template struct tls_serializer { - static void to_json(json& j, const T& v) { j = bytes(tls::marshal(v)); } + static void to_json(json& j, const T& v) + { + j = bytes(MLS_NAMESPACE::tls::marshal(v)); + } static void from_json(const json& j, T& v) { - v = tls::get(j.get()); + v = MLS_NAMESPACE::tls::get(j.get()); } }; #define TLS_SERIALIZER(T) \ template<> \ struct adl_serializer : tls_serializer \ - { \ - }; - -TLS_SERIALIZER(mls::TreeKEMPublicKey) -TLS_SERIALIZER(mls::AuthenticatedContent) -TLS_SERIALIZER(mls::Credential) -TLS_SERIALIZER(mls::Proposal) -TLS_SERIALIZER(mls::Commit) -TLS_SERIALIZER(mls::ApplicationData) -TLS_SERIALIZER(mls::MLSMessage) -TLS_SERIALIZER(mls::LeafNode) -TLS_SERIALIZER(mls::UpdatePath) -TLS_SERIALIZER(mls::KeyPackage) -TLS_SERIALIZER(mls::Welcome) + {}; + +TLS_SERIALIZER(MLS_NAMESPACE::TreeKEMPublicKey) +TLS_SERIALIZER(MLS_NAMESPACE::AuthenticatedContent) +TLS_SERIALIZER(MLS_NAMESPACE::Credential) +TLS_SERIALIZER(MLS_NAMESPACE::Proposal) +TLS_SERIALIZER(MLS_NAMESPACE::Commit) +TLS_SERIALIZER(MLS_NAMESPACE::ApplicationData) +TLS_SERIALIZER(MLS_NAMESPACE::MLSMessage) +TLS_SERIALIZER(MLS_NAMESPACE::LeafNode) +TLS_SERIALIZER(MLS_NAMESPACE::UpdatePath) +TLS_SERIALIZER(MLS_NAMESPACE::KeyPackage) +TLS_SERIALIZER(MLS_NAMESPACE::Welcome) } // namespace nlohmann diff --git a/cmd/interop/src/main.cpp b/cmd/interop/src/main.cpp index 0ef89714..4864c982 100644 --- a/cmd/interop/src/main.cpp +++ b/cmd/interop/src/main.cpp @@ -48,7 +48,7 @@ make_test_vector(uint64_t type) case TestVectorClass::crypto_basics: { auto cases = std::vector(); - for (const auto& suite : mls::all_supported_suites) { + for (const auto& suite : MLS_NAMESPACE::all_supported_suites) { cases.emplace_back(suite); } @@ -59,7 +59,7 @@ make_test_vector(uint64_t type) auto cases = std::vector(); auto generations = std::vector{ 1, 15 }; - for (const auto& suite : mls::all_supported_suites) { + for (const auto& suite : MLS_NAMESPACE::all_supported_suites) { cases.emplace_back(suite, 15, generations); } @@ -69,7 +69,7 @@ make_test_vector(uint64_t type) case TestVectorClass::message_protection: { auto cases = std::vector(); - for (const auto& suite : mls::all_supported_suites) { + for (const auto& suite : MLS_NAMESPACE::all_supported_suites) { cases.emplace_back(suite); } @@ -79,7 +79,7 @@ make_test_vector(uint64_t type) case TestVectorClass::key_schedule: { auto cases = std::vector(); - for (const auto& suite : mls::all_supported_suites) { + for (const auto& suite : MLS_NAMESPACE::all_supported_suites) { cases.emplace_back(suite, n); } @@ -89,7 +89,7 @@ make_test_vector(uint64_t type) case TestVectorClass::pre_shared_keys: { auto cases = std::vector(); - for (const auto& suite : mls::all_supported_suites) { + for (const auto& suite : MLS_NAMESPACE::all_supported_suites) { cases.emplace_back(suite, 5); } @@ -99,7 +99,7 @@ make_test_vector(uint64_t type) case TestVectorClass::tree_validation: { auto cases = std::vector(); - for (const auto& suite : mls::all_supported_suites) { + for (const auto& suite : MLS_NAMESPACE::all_supported_suites) { for (const auto& tree_structure : all_tree_structures) { cases.emplace_back(suite, tree_structure); } @@ -111,7 +111,7 @@ make_test_vector(uint64_t type) case TestVectorClass::transcript_hash: { auto cases = std::vector(); - for (const auto& suite : mls::all_supported_suites) { + for (const auto& suite : MLS_NAMESPACE::all_supported_suites) { cases.emplace_back(suite); } @@ -121,7 +121,7 @@ make_test_vector(uint64_t type) case TestVectorClass::welcome: { auto cases = std::vector(); - for (const auto& suite : mls::all_supported_suites) { + for (const auto& suite : MLS_NAMESPACE::all_supported_suites) { cases.emplace_back(suite); } @@ -131,7 +131,8 @@ make_test_vector(uint64_t type) case TestVectorClass::tree_modifications: { auto cases = std::vector(); - auto suite = mls::CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519; + auto suite = + MLS_NAMESPACE::CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519; for (auto scenario : TreeOperationsTestVector::all_scenarios) { cases.emplace_back(suite, scenario); } @@ -142,7 +143,7 @@ make_test_vector(uint64_t type) case TestVectorClass::treekem: { auto cases = std::vector(); - for (const auto& suite : mls::all_supported_suites) { + for (const auto& suite : MLS_NAMESPACE::all_supported_suites) { for (const auto& tree_structure : treekem_test_tree_structures) { cases.emplace_back(suite, tree_structure); } diff --git a/cmd/interop/src/mls_client_impl.cpp b/cmd/interop/src/mls_client_impl.cpp index ffaf45be..8fcaaa2b 100644 --- a/cmd/interop/src/mls_client_impl.cpp +++ b/cmd/interop/src/mls_client_impl.cpp @@ -4,7 +4,7 @@ using grpc::StatusCode; using nlohmann::json; -using namespace bytes_ns; +using namespace MLS_NAMESPACE::bytes_ns; static inline std::string bytes_to_string(const std::vector& data) @@ -19,9 +19,9 @@ string_to_bytes(const std::string& str) } static inline std::string -marshal_message(mls::MLSMessage&& msg) +marshal_message(MLS_NAMESPACE::MLSMessage&& msg) { - return bytes_to_string(tls::marshal(msg)); + return bytes_to_string(MLS_NAMESPACE::tls::marshal(msg)); } template @@ -29,14 +29,14 @@ T unmarshal_message(const std::string& str) { auto data = string_to_bytes(str); - auto msg = tls::get(data); + auto msg = MLS_NAMESPACE::tls::get(data); return var::get(msg.message); } -static inline mls::CipherSuite +static inline MLS_NAMESPACE::CipherSuite mls_suite(uint32_t suite_id) { - return static_cast(suite_id); + return static_cast(suite_id); } // Map C++ exceptions to gRPC errors @@ -84,7 +84,7 @@ MLSClientImpl::SupportedCiphersuites( SupportedCiphersuitesResponse* reply) { reply->clear_ciphersuites(); - for (const auto suite : mls::all_supported_suites) { + for (const auto suite : MLS_NAMESPACE::all_supported_suites) { reply->add_ciphersuites(static_cast(suite)); } return Status::OK; @@ -382,28 +382,30 @@ MLSClientImpl::HandleReInitWelcome(ServerContext* /* context */, // Factory for key packages MLSClientImpl::KeyPackageWithSecrets -MLSClientImpl::new_key_package(mls::CipherSuite cipher_suite, +MLSClientImpl::new_key_package(MLS_NAMESPACE::CipherSuite cipher_suite, const bytes& identity) { - auto init_priv = mls::HPKEPrivateKey::generate(cipher_suite); - auto encryption_priv = mls::HPKEPrivateKey::generate(cipher_suite); - auto signature_priv = mls::SignaturePrivateKey::generate(cipher_suite); - auto cred = mls::Credential::basic(identity); - - auto key_package = mls::KeyPackage(cipher_suite, - init_priv.public_key, - { - cipher_suite, - encryption_priv.public_key, - signature_priv.public_key, - cred, - mls::Capabilities::create_default(), - mls::Lifetime::create_default(), - {}, - signature_priv, - }, - {}, - signature_priv); + auto init_priv = MLS_NAMESPACE::HPKEPrivateKey::generate(cipher_suite); + auto encryption_priv = MLS_NAMESPACE::HPKEPrivateKey::generate(cipher_suite); + auto signature_priv = + MLS_NAMESPACE::SignaturePrivateKey::generate(cipher_suite); + auto cred = MLS_NAMESPACE::Credential::basic(identity); + + auto key_package = + MLS_NAMESPACE::KeyPackage(cipher_suite, + init_priv.public_key, + { + cipher_suite, + encryption_priv.public_key, + signature_priv.public_key, + cred, + MLS_NAMESPACE::Capabilities::create_default(), + MLS_NAMESPACE::Lifetime::create_default(), + {}, + signature_priv, + }, + {}, + signature_priv); return { init_priv, encryption_priv, signature_priv, key_package }; } @@ -445,7 +447,7 @@ MLSClientImpl::Free(ServerContext* /* context */, uint32_t MLSClientImpl::store_join(KeyPackageWithSecrets&& kp_priv) { - auto join_id = tls::get(kp_priv.key_package.ref()); + auto join_id = MLS_NAMESPACE::tls::get(kp_priv.key_package.ref()); auto entry = CachedJoin{ std::move(kp_priv), {} }; join_cache.emplace(std::make_pair(join_id, std::move(entry))); return join_id; @@ -461,7 +463,7 @@ MLSClientImpl::load_join(uint32_t join_id) } // Cached group state -mls::MessageOpts +MLS_NAMESPACE::MessageOpts MLSClientImpl::CachedState::message_opts() const { return { encrypt_handshake, {}, 0 }; @@ -475,21 +477,23 @@ MLSClientImpl::CachedState::reset_pending() } std::string -MLSClientImpl::CachedState::marshal(const mls::MLSMessage& msg) +MLSClientImpl::CachedState::marshal(const MLS_NAMESPACE::MLSMessage& msg) { - return bytes_to_string(tls::marshal(msg)); + return bytes_to_string(MLS_NAMESPACE::tls::marshal(msg)); } -mls::MLSMessage +MLS_NAMESPACE::MLSMessage MLSClientImpl::CachedState::unmarshal(const std::string& wire) { - return tls::get(string_to_bytes(wire)); + return MLS_NAMESPACE::tls::get( + string_to_bytes(wire)); } uint32_t -MLSClientImpl::store_state(mls::State&& state, bool encrypt_handshake) +MLSClientImpl::store_state(MLS_NAMESPACE::State&& state, bool encrypt_handshake) { - auto state_id = tls::get(state.epoch_authenticator()); + auto state_id = + MLS_NAMESPACE::tls::get(state.epoch_authenticator()); state_id += state.index().val; auto entry = CachedState{ std::move(state), encrypt_handshake, {}, {} }; @@ -507,9 +511,9 @@ MLSClientImpl::load_state(uint32_t state_id) } uint32_t -MLSClientImpl::store_signer(mls::SignaturePrivateKey&& priv) +MLSClientImpl::store_signer(MLS_NAMESPACE::SignaturePrivateKey&& priv) { - auto signer_id = tls::get(priv.public_key.data); + auto signer_id = MLS_NAMESPACE::tls::get(priv.public_key.data); auto entry = CachedSigner{ std::move(priv) }; signer_cache.emplace(std::make_pair(signer_id, std::move(entry))); return signer_id; @@ -525,7 +529,8 @@ MLSClientImpl::load_signer(uint32_t signer_id) } MLSClientImpl::CachedState* -MLSClientImpl::find_state(const bytes& group_id, const mls::epoch_t epoch) +MLSClientImpl::find_state(const bytes& group_id, + const MLS_NAMESPACE::epoch_t epoch) { auto entry = std::find_if( state_cache.begin(), state_cache.end(), [&](const auto& entry) { @@ -549,10 +554,10 @@ MLSClientImpl::remove_state(uint32_t state_id) uint32_t MLSClientImpl::store_reinit(KeyPackageWithSecrets&& kp_priv, - mls::State::Tombstone&& tombstone, + MLS_NAMESPACE::State::Tombstone&& tombstone, bool encrypt_handshake) { - auto reinit_id = tls::get(kp_priv.key_package.ref()); + auto reinit_id = MLS_NAMESPACE::tls::get(kp_priv.key_package.ref()); auto entry = CachedReInit{ std::move(kp_priv), std::move(tombstone), encrypt_handshake }; reinit_cache.emplace(std::make_pair(reinit_id, std::move(entry))); @@ -580,10 +585,11 @@ MLSClientImpl::group_context_extensions_proposal( const GroupContextExtensionsProposalRequest* request, ProposalResponse* response) { - auto ext_list = mls::ExtensionList{}; + auto ext_list = MLS_NAMESPACE::ExtensionList{}; for (int i = 0; i < request->extensions_size(); i++) { auto ext = request->extensions(i); - auto ext_type = static_cast(ext.extension_type()); + auto ext_type = + static_cast(ext.extension_type()); auto ext_data = string_to_bytes(ext.extension_data()); ext_list.add(ext_type, ext_data); } @@ -595,12 +601,12 @@ MLSClientImpl::group_context_extensions_proposal( return Status::OK; } -mls::LeafIndex -MLSClientImpl::find_member(const mls::TreeKEMPublicKey& tree, +MLS_NAMESPACE::LeafIndex +MLSClientImpl::find_member(const MLS_NAMESPACE::TreeKEMPublicKey& tree, const std::string& identity) { const auto id = string_to_bytes(identity); - auto index = mls::LeafIndex{ 0 }; + auto index = MLS_NAMESPACE::LeafIndex{ 0 }; for (; index < tree.size; index.val++) { const auto maybe_leaf = tree.leaf_node(index); if (!maybe_leaf) { @@ -608,7 +614,7 @@ MLSClientImpl::find_member(const mls::TreeKEMPublicKey& tree, } const auto& leaf = opt::get(maybe_leaf); - const auto& basic = leaf.credential.get(); + const auto& basic = leaf.credential.get(); if (basic.identity == id) { break; } @@ -621,60 +627,63 @@ MLSClientImpl::find_member(const mls::TreeKEMPublicKey& tree, return index; } -mls::Proposal -MLSClientImpl::proposal_from_description(mls::CipherSuite suite, - const bytes& group_id, - const mls::TreeKEMPublicKey& tree, - const ProposalDescription& desc) +MLS_NAMESPACE::Proposal +MLSClientImpl::proposal_from_description( + MLS_NAMESPACE::CipherSuite suite, + const bytes& group_id, + const MLS_NAMESPACE::TreeKEMPublicKey& tree, + const ProposalDescription& desc) { if (desc.proposal_type() == "add") { const auto kp_msg_data = string_to_bytes(desc.key_package()); - const auto kp_msg = tls::get(kp_msg_data); - const auto kp = var::get(kp_msg.message); - return { mls::Add{ kp } }; + const auto kp_msg = + MLS_NAMESPACE::tls::get(kp_msg_data); + const auto kp = var::get(kp_msg.message); + return { MLS_NAMESPACE::Add{ kp } }; } if (desc.proposal_type() == "remove") { const auto removed_index = find_member(tree, desc.removed_id()); - return { mls::Remove{ removed_index } }; + return { MLS_NAMESPACE::Remove{ removed_index } }; } if (desc.proposal_type() == "externalPSK") { const auto external_psk_id = string_to_bytes(desc.psk_id()); - const auto psk_id = mls::PreSharedKeyID{ - { mls::ExternalPSK{ external_psk_id } }, - mls::random_bytes(suite.secret_size()), + const auto psk_id = MLS_NAMESPACE::PreSharedKeyID{ + { MLS_NAMESPACE::ExternalPSK{ external_psk_id } }, + MLS_NAMESPACE::random_bytes(suite.secret_size()), }; - return { mls::PreSharedKey{ psk_id } }; + return { MLS_NAMESPACE::PreSharedKey{ psk_id } }; } if (desc.proposal_type() == "resumptionPSK") { const auto epoch = desc.epoch_id(); - const auto psk_id = mls::PreSharedKeyID{ - { mls::ResumptionPSK{ - mls::ResumptionPSKUsage::application, group_id, epoch } }, - mls::random_bytes(suite.secret_size()), + const auto psk_id = MLS_NAMESPACE::PreSharedKeyID{ + { MLS_NAMESPACE::ResumptionPSK{ + MLS_NAMESPACE::ResumptionPSKUsage::application, group_id, epoch } }, + MLS_NAMESPACE::random_bytes(suite.secret_size()), }; - return { mls::PreSharedKey{ psk_id } }; + return { MLS_NAMESPACE::PreSharedKey{ psk_id } }; } - auto ext_list = mls::ExtensionList{}; + auto ext_list = MLS_NAMESPACE::ExtensionList{}; for (int i = 0; i < desc.extensions_size(); i++) { auto ext = desc.extensions(i); - auto ext_type = static_cast(ext.extension_type()); + auto ext_type = + static_cast(ext.extension_type()); auto ext_data = string_to_bytes(ext.extension_data()); ext_list.add(ext_type, ext_data); } if (desc.proposal_type() == "groupContextExtensions") { - return { mls::GroupContextExtensions{ ext_list } }; + return { MLS_NAMESPACE::GroupContextExtensions{ ext_list } }; } if (desc.proposal_type() == "reinit") { - return { mls::ReInit{ + return { MLS_NAMESPACE::ReInit{ string_to_bytes(desc.group_id()), - mls::ProtocolVersion::mls10, - static_cast(desc.cipher_suite()), + MLS_NAMESPACE::ProtocolVersion::mls10, + static_cast(desc.cipher_suite()), ext_list } }; } @@ -690,23 +699,23 @@ MLSClientImpl::create_group(const CreateGroupRequest* request, auto cipher_suite = mls_suite(request->cipher_suite()); auto identity = string_to_bytes(request->identity()); - auto leaf_priv = mls::HPKEPrivateKey::generate(cipher_suite); - auto sig_priv = mls::SignaturePrivateKey::generate(cipher_suite); - auto cred = mls::Credential::basic(identity); + auto leaf_priv = MLS_NAMESPACE::HPKEPrivateKey::generate(cipher_suite); + auto sig_priv = MLS_NAMESPACE::SignaturePrivateKey::generate(cipher_suite); + auto cred = MLS_NAMESPACE::Credential::basic(identity); - auto leaf_node = mls::LeafNode{ + auto leaf_node = MLS_NAMESPACE::LeafNode{ cipher_suite, leaf_priv.public_key, sig_priv.public_key, cred, - mls::Capabilities::create_default(), - mls::Lifetime::create_default(), + MLS_NAMESPACE::Capabilities::create_default(), + MLS_NAMESPACE::Lifetime::create_default(), {}, sig_priv, }; - auto state = - mls::State(group_id, cipher_suite, leaf_priv, sig_priv, leaf_node, {}); + auto state = MLS_NAMESPACE::State( + group_id, cipher_suite, leaf_priv, sig_priv, leaf_node, {}); auto state_id = store_state(std::move(state), request->encrypt_handshake()); response->set_state_id(state_id); @@ -726,7 +735,8 @@ MLSClientImpl::create_key_package(const CreateKeyPackageRequest* request, response->set_encryption_priv(bytes_to_string(kp_priv.encryption_priv.data)); response->set_signature_priv(bytes_to_string(kp_priv.signature_priv.data)); - auto key_package = tls::marshal(mls::MLSMessage{ kp_priv.key_package }); + auto key_package = MLS_NAMESPACE::tls::marshal( + MLS_NAMESPACE::MLSMessage{ kp_priv.key_package }); response->set_key_package(bytes_to_string(key_package)); auto join_id = store_join(std::move(kp_priv)); @@ -744,20 +754,21 @@ MLSClientImpl::join_group(const JoinGroupRequest* request, return Status(StatusCode::INVALID_ARGUMENT, "Unknown transaction ID"); } - auto welcome = unmarshal_message(request->welcome()); - auto ratchet_tree = std::optional{}; + auto welcome = unmarshal_message(request->welcome()); + auto ratchet_tree = std::optional{}; auto ratchet_tree_data = string_to_bytes(request->ratchet_tree()); if (!ratchet_tree_data.empty()) { - ratchet_tree = tls::get(ratchet_tree_data); + ratchet_tree = MLS_NAMESPACE::tls::get( + ratchet_tree_data); } - auto state = mls::State(join->kp_priv.init_priv, - std::move(join->kp_priv.encryption_priv), - std::move(join->kp_priv.signature_priv), - join->kp_priv.key_package, - welcome, - ratchet_tree, - join->external_psks); + auto state = MLS_NAMESPACE::State(join->kp_priv.init_priv, + std::move(join->kp_priv.encryption_priv), + std::move(join->kp_priv.signature_priv), + join->kp_priv.key_package, + welcome, + ratchet_tree, + join->external_psks); auto epoch_authenticator = state.epoch_authenticator(); auto state_id = store_state(std::move(state), request->encrypt_handshake()); @@ -772,45 +783,47 @@ MLSClientImpl::external_join(const ExternalJoinRequest* request, ExternalJoinResponse* response) { const auto group_info_msg = - unmarshal_message(request->group_info()); + unmarshal_message(request->group_info()); const auto suite = group_info_msg.group_context.cipher_suite; - auto init_priv = mls::HPKEPrivateKey::generate(suite); - auto leaf_priv = mls::HPKEPrivateKey::generate(suite); - auto sig_priv = mls::SignaturePrivateKey::generate(suite); + auto init_priv = MLS_NAMESPACE::HPKEPrivateKey::generate(suite); + auto leaf_priv = MLS_NAMESPACE::HPKEPrivateKey::generate(suite); + auto sig_priv = MLS_NAMESPACE::SignaturePrivateKey::generate(suite); auto identity = string_to_bytes(request->identity()); - auto cred = mls::Credential::basic(identity); + auto cred = MLS_NAMESPACE::Credential::basic(identity); - auto leaf = mls::LeafNode{ + auto leaf = MLS_NAMESPACE::LeafNode{ suite, leaf_priv.public_key, sig_priv.public_key, cred, - mls::Capabilities::create_default(), - mls::Lifetime::create_default(), + MLS_NAMESPACE::Capabilities::create_default(), + MLS_NAMESPACE::Lifetime::create_default(), {}, sig_priv, }; - auto kp = mls::KeyPackage(suite, init_priv.public_key, leaf, {}, sig_priv); + auto kp = + MLS_NAMESPACE::KeyPackage(suite, init_priv.public_key, leaf, {}, sig_priv); // Import an external tree if present - auto ratchet_tree = std::optional{}; + auto ratchet_tree = std::optional{}; auto ratchet_tree_data = string_to_bytes(request->ratchet_tree()); if (!ratchet_tree_data.empty()) { - ratchet_tree = tls::get(ratchet_tree_data); + ratchet_tree = MLS_NAMESPACE::tls::get( + ratchet_tree_data); } // If required, find our prior appearance and remove it - auto remove_prior = std::optional{}; + auto remove_prior = std::optional{}; if (request->remove_prior()) { // Find the tree we're going to look at // XXX(RLB): This replicates logic in State::import_tree, but we need to // do it out here since this is where the knowledge of which leaf to // remove resides. - auto tree = mls::TreeKEMPublicKey(suite); + auto tree = MLS_NAMESPACE::TreeKEMPublicKey(suite); auto maybe_tree_extn = - group_info_msg.extensions.find(); + group_info_msg.extensions.find(); if (ratchet_tree) { tree = opt::get(ratchet_tree); } else if (maybe_tree_extn) { @@ -820,14 +833,14 @@ MLSClientImpl::external_join(const ExternalJoinRequest* request, } // Scan through to find a matching identity - for (auto i = mls::LeafIndex{ 0 }; i < tree.size; i.val++) { + for (auto i = MLS_NAMESPACE::LeafIndex{ 0 }; i < tree.size; i.val++) { const auto maybe_leaf = tree.leaf_node(i); if (!maybe_leaf) { continue; } const auto& leaf = opt::get(maybe_leaf); - const auto& cred = leaf.credential.get(); + const auto& cred = leaf.credential.get(); if (cred.identity != identity) { continue; } @@ -850,15 +863,15 @@ MLSClientImpl::external_join(const ExternalJoinRequest* request, } auto encrypt = request->encrypt_handshake(); - auto leaf_secret = mls::random_bytes(suite.secret_size()); - auto [commit, state] = mls::State::external_join(leaf_secret, - sig_priv, - kp, - group_info_msg, - ratchet_tree, - { {}, encrypt, 0 }, - remove_prior, - psks); + auto leaf_secret = MLS_NAMESPACE::random_bytes(suite.secret_size()); + auto [commit, state] = MLS_NAMESPACE::State::external_join(leaf_secret, + sig_priv, + kp, + group_info_msg, + ratchet_tree, + { {}, encrypt, 0 }, + remove_prior, + psks); auto epoch_authenticator = state.epoch_authenticator(); auto state_id = store_state(std::move(state), encrypt); @@ -880,7 +893,8 @@ MLSClientImpl::group_info(CachedState& entry, response->set_group_info(marshal_message(group_info)); if (!inline_tree) { - auto ratchet_tree = bytes_to_string(tls::marshal(entry.state.tree())); + auto ratchet_tree = + bytes_to_string(MLS_NAMESPACE::tls::marshal(entry.state.tree())); response->set_ratchet_tree(ratchet_tree); } @@ -928,11 +942,12 @@ MLSClientImpl::unprotect(CachedState& entry, UnprotectResponse* response) { auto ct_data = string_to_bytes(request->ciphertext()); - auto ct = tls::get(ct_data); + auto ct = MLS_NAMESPACE::tls::get(ct_data); // Locate the right epoch to decrypt with const auto group_id = entry.state.group_id(); - const auto epoch = var::get(ct.message).get_epoch(); + const auto epoch = + var::get(ct.message).get_epoch(); auto* state = &entry.state; if (entry.state.epoch() != epoch) { @@ -958,7 +973,8 @@ MLSClientImpl::add_proposal(CachedState& entry, const AddProposalRequest* request, ProposalResponse* response) { - auto key_package = unmarshal_message(request->key_package()); + auto key_package = + unmarshal_message(request->key_package()); auto message = entry.state.add(key_package, entry.message_opts()); response->set_proposal(entry.marshal(message)); @@ -970,7 +986,8 @@ MLSClientImpl::update_proposal(CachedState& entry, const UpdateProposalRequest* /* request */, ProposalResponse* response) { - auto leaf_priv = mls::HPKEPrivateKey::generate(entry.state.cipher_suite()); + auto leaf_priv = + MLS_NAMESPACE::HPKEPrivateKey::generate(entry.state.cipher_suite()); auto message = entry.state.update(leaf_priv, {}, entry.message_opts()); response->set_proposal(entry.marshal(message)); @@ -1035,7 +1052,7 @@ MLSClientImpl::commit(CachedState& entry, } // Create by-value proposals - auto by_value = std::vector(); + auto by_value = std::vector(); for (int i = 0; i < request->by_value_size(); i++) { const auto desc = request->by_value(i); const auto proposal = proposal_from_description(entry.state.cipher_suite(), @@ -1049,14 +1066,15 @@ MLSClientImpl::commit(CachedState& entry, auto inline_tree = !request->external_tree(); auto leaf_secret = - mls::random_bytes(entry.state.cipher_suite().secret_size()); - auto [commit, welcome, next] = - entry.state.commit(leaf_secret, - mls::CommitOpts{ by_value, inline_tree, force_path, {} }, - entry.message_opts()); + MLS_NAMESPACE::random_bytes(entry.state.cipher_suite().secret_size()); + auto [commit, welcome, next] = entry.state.commit( + leaf_secret, + MLS_NAMESPACE::CommitOpts{ by_value, inline_tree, force_path, {} }, + entry.message_opts()); if (!inline_tree) { - auto ratchet_tree = bytes_to_string(tls::marshal(next.tree())); + auto ratchet_tree = + bytes_to_string(MLS_NAMESPACE::tls::marshal(next.tree())); response->set_ratchet_tree(ratchet_tree); } @@ -1143,8 +1161,9 @@ MLSClientImpl::new_member_add_proposal( NewMemberAddProposalResponse* response) { auto group_info_msg_data = string_to_bytes(request->group_info()); - auto group_info_msg = tls::get(group_info_msg_data); - auto group_info = var::get(group_info_msg.message); + auto group_info_msg = + MLS_NAMESPACE::tls::get(group_info_msg_data); + auto group_info = var::get(group_info_msg.message); auto cipher_suite = group_info.group_context.cipher_suite; auto group_id = group_info.group_context.group_id; @@ -1158,7 +1177,7 @@ MLSClientImpl::new_member_add_proposal( response->set_encryption_priv(bytes_to_string(kp_priv.encryption_priv.data)); response->set_signature_priv(bytes_to_string(kp_priv.signature_priv.data)); - auto proposal = mls::State::new_member_add( + auto proposal = MLS_NAMESPACE::State::new_member_add( group_id, epoch, kp_priv.key_package, kp_priv.signature_priv); response->set_proposal(marshal_message(std::move(proposal))); @@ -1176,12 +1195,14 @@ MLSClientImpl::create_external_signer( const auto cipher_suite = mls_suite(request->cipher_suite()); const auto identity = string_to_bytes(request->identity()); - auto signature_priv = mls::SignaturePrivateKey::generate(cipher_suite); - const auto cred = mls::Credential::basic(identity); + auto signature_priv = + MLS_NAMESPACE::SignaturePrivateKey::generate(cipher_suite); + const auto cred = MLS_NAMESPACE::Credential::basic(identity); const auto external_sender = - mls::ExternalSender{ signature_priv.public_key, cred }; - response->set_external_sender(bytes_to_string(tls::marshal(external_sender))); + MLS_NAMESPACE::ExternalSender{ signature_priv.public_key, cred }; + response->set_external_sender( + bytes_to_string(MLS_NAMESPACE::tls::marshal(external_sender))); const auto signer_id = store_signer(std::move(signature_priv)); response->set_signer_id(signer_id); @@ -1195,11 +1216,13 @@ MLSClientImpl::add_external_signer(CachedState& entry, ProposalResponse* response) { auto external_sender_data = string_to_bytes(request->external_sender()); - auto external_sender = tls::get(external_sender_data); + auto external_sender = MLS_NAMESPACE::tls::get( + external_sender_data); auto ext_list = entry.state.extensions(); - auto ext_senders = mls::ExternalSendersExtension{}; - auto curr_ext_senders = ext_list.find(); + auto ext_senders = MLS_NAMESPACE::ExternalSendersExtension{}; + auto curr_ext_senders = + ext_list.find(); if (curr_ext_senders) { ext_senders = opt::get(curr_ext_senders); } @@ -1219,15 +1242,17 @@ MLSClientImpl::external_signer_proposal( ProposalResponse* response) { auto group_info_msg_data = string_to_bytes(request->group_info()); - auto group_info_msg = tls::get(group_info_msg_data); - auto group_info = var::get(group_info_msg.message); + auto group_info_msg = + MLS_NAMESPACE::tls::get(group_info_msg_data); + auto group_info = var::get(group_info_msg.message); auto cipher_suite = group_info.group_context.cipher_suite; auto group_id = group_info.group_context.group_id; auto epoch = group_info.group_context.epoch; auto ratchet_tree_data = string_to_bytes(request->ratchet_tree()); - auto ratchet_tree = tls::get(ratchet_tree_data); + auto ratchet_tree = + MLS_NAMESPACE::tls::get(ratchet_tree_data); // Look up the signer auto* signer = load_signer(request->signer_id()); @@ -1237,7 +1262,8 @@ MLSClientImpl::external_signer_proposal( // Look up the signer index of this signer const auto maybe_ext_senders = - group_info.group_context.extensions.find(); + group_info.group_context.extensions + .find(); if (!maybe_ext_senders) { throw std::runtime_error("No external senders allowed"); } @@ -1256,12 +1282,13 @@ MLSClientImpl::external_signer_proposal( // Sign the proposal const auto proposal = proposal_from_description( cipher_suite, group_id, ratchet_tree, request->description()); - auto signed_proposal = mls::external_proposal(cipher_suite, - group_id, - epoch, - proposal, - signer_index, - signer->signature_priv); + auto signed_proposal = + MLS_NAMESPACE::external_proposal(cipher_suite, + group_id, + epoch, + proposal, + signer_index, + signer->signature_priv); response->set_proposal(marshal_message(std::move(signed_proposal))); return Status::OK; @@ -1273,13 +1300,14 @@ MLSClientImpl::reinit_proposal(CachedState& entry, ProposalResponse* response) { auto group_id = string_to_bytes(request->group_id()); - auto version = mls::ProtocolVersion::mls10; + auto version = MLS_NAMESPACE::ProtocolVersion::mls10; auto cipher_suite = mls_suite(request->cipher_suite()); - auto ext_list = mls::ExtensionList{}; + auto ext_list = MLS_NAMESPACE::ExtensionList{}; for (int i = 0; i < request->extensions_size(); i++) { auto ext = request->extensions(i); - auto ext_type = static_cast(ext.extension_type()); + auto ext_type = + static_cast(ext.extension_type()); auto ext_data = string_to_bytes(ext.extension_data()); ext_list.add(ext_type, ext_data); } @@ -1310,15 +1338,17 @@ MLSClientImpl::reinit_commit(CachedState& entry, } const auto leaf_secret = - mls::random_bytes(entry.state.cipher_suite().secret_size()); - const auto commit_opts = mls::CommitOpts{ {}, inline_tree, force_path, {} }; + MLS_NAMESPACE::random_bytes(entry.state.cipher_suite().secret_size()); + const auto commit_opts = + MLS_NAMESPACE::CommitOpts{ {}, inline_tree, force_path, {} }; auto [tombstone, commit] = entry.state.reinit_commit(leaf_secret, commit_opts, entry.message_opts()); // Cache the reinit const auto my_leaf = opt::get(entry.state.tree().leaf_node(entry.state.index())); - const auto identity = my_leaf.credential.get().identity; + const auto identity = + my_leaf.credential.get().identity; auto kp_priv = new_key_package(tombstone.reinit.cipher_suite, identity); const auto reinit_id = store_reinit( @@ -1380,7 +1410,8 @@ MLSClientImpl::handle_reinit_commit(CachedState& entry, // Cache the reinit const auto my_leaf = opt::get(entry.state.tree().leaf_node(entry.state.index())); - const auto identity = my_leaf.credential.get().identity; + const auto identity = + my_leaf.credential.get().identity; auto kp_priv = new_key_package(tombstone.reinit.cipher_suite, identity); const auto key_package = entry.marshal(kp_priv.key_package); @@ -1407,20 +1438,21 @@ MLSClientImpl::reinit_welcome(const ReInitWelcomeRequest* request, } // Import the KeyPackages - auto key_packages = std::vector{}; + auto key_packages = std::vector{}; for (int i = 0; i < request->key_package_size(); i++) { const auto key_package_msg_data = string_to_bytes(request->key_package(i)); const auto key_package_msg = - tls::get(key_package_msg_data); + MLS_NAMESPACE::tls::get(key_package_msg_data); key_packages.emplace_back( - var::get(key_package_msg.message)); + var::get(key_package_msg.message)); } // Create the Welcome const auto inline_tree = !request->external_tree(); const auto force_path = request->force_path(); const auto cipher_suite = reinit->tombstone.reinit.cipher_suite; - const auto leaf_secret = mls::random_bytes(cipher_suite.secret_size()); + const auto leaf_secret = + MLS_NAMESPACE::random_bytes(cipher_suite.secret_size()); auto [state, welcome] = reinit->tombstone.create_welcome(reinit->kp_priv.encryption_priv, reinit->kp_priv.signature_priv, @@ -1429,7 +1461,8 @@ MLSClientImpl::reinit_welcome(const ReInitWelcomeRequest* request, leaf_secret, { {}, inline_tree, force_path, {} }); - const auto welcome_data = tls::marshal(mls::MLSMessage{ welcome }); + const auto welcome_data = + MLS_NAMESPACE::tls::marshal(MLS_NAMESPACE::MLSMessage{ welcome }); // Store the resulting state auto epoch_authenticator = state.epoch_authenticator(); @@ -1440,7 +1473,8 @@ MLSClientImpl::reinit_welcome(const ReInitWelcomeRequest* request, response->set_welcome(bytes_to_string(welcome_data)); response->set_epoch_authenticator(bytes_to_string(epoch_authenticator)); if (!inline_tree) { - response->set_ratchet_tree(bytes_to_string(tls::marshal(tree))); + response->set_ratchet_tree( + bytes_to_string(MLS_NAMESPACE::tls::marshal(tree))); } return Status::OK; @@ -1458,13 +1492,15 @@ MLSClientImpl::handle_reinit_welcome(const HandleReInitWelcomeRequest* request, // Process the welcome const auto welcome_msg_data = string_to_bytes(request->welcome()); - const auto welcome_msg = tls::get(welcome_msg_data); - const auto welcome = var::get(welcome_msg.message); + const auto welcome_msg = + MLS_NAMESPACE::tls::get(welcome_msg_data); + const auto welcome = var::get(welcome_msg.message); - auto ratchet_tree = std::optional{}; + auto ratchet_tree = std::optional{}; auto ratchet_tree_data = string_to_bytes(request->ratchet_tree()); if (!ratchet_tree_data.empty()) { - ratchet_tree = tls::get(ratchet_tree_data); + ratchet_tree = MLS_NAMESPACE::tls::get( + ratchet_tree_data); } auto state = reinit->tombstone.handle_welcome(reinit->kp_priv.init_priv, @@ -1489,19 +1525,21 @@ MLSClientImpl::create_branch(CachedState& entry, CreateSubgroupResponse* response) { // Import KeyPackages - auto key_packages = std::vector{}; + auto key_packages = std::vector{}; for (int i = 0; i < request->key_packages_size(); i++) { const auto kp_msg_data = string_to_bytes(request->key_packages(i)); - const auto kp_msg = tls::get(kp_msg_data); - key_packages.emplace_back(var::get(kp_msg.message)); + const auto kp_msg = + MLS_NAMESPACE::tls::get(kp_msg_data); + key_packages.emplace_back( + var::get(kp_msg.message)); } // Import extensions - auto ext_list = mls::ExtensionList{}; + auto ext_list = MLS_NAMESPACE::ExtensionList{}; for (int i = 0; i < request->extensions_size(); i++) { const auto ext = request->extensions(i); const auto ext_type = - static_cast(ext.extension_type()); + static_cast(ext.extension_type()); const auto ext_data = string_to_bytes(ext.extension_data()); ext_list.add(ext_type, ext_data); } @@ -1509,14 +1547,16 @@ MLSClientImpl::create_branch(CachedState& entry, // Create the branch const auto my_leaf = opt::get(entry.state.tree().leaf_node(entry.state.index())); - const auto identity = my_leaf.credential.get().identity; + const auto identity = + my_leaf.credential.get().identity; const auto inline_tree = !request->external_tree(); const auto force_path = request->force_path(); const auto group_id = string_to_bytes(request->group_id()); const auto cipher_suite = entry.state.cipher_suite(); const auto kp_priv = new_key_package(cipher_suite, identity); - const auto leaf_secret = mls::random_bytes(cipher_suite.secret_size()); + const auto leaf_secret = + MLS_NAMESPACE::random_bytes(cipher_suite.secret_size()); auto [next, welcome] = entry.state.create_branch(group_id, kp_priv.encryption_priv, @@ -1536,7 +1576,8 @@ MLSClientImpl::create_branch(CachedState& entry, response->set_epoch_authenticator(bytes_to_string(epoch_authenticator)); if (!inline_tree) { - auto ratchet_tree = bytes_to_string(tls::marshal(next.tree())); + auto ratchet_tree = + bytes_to_string(MLS_NAMESPACE::tls::marshal(next.tree())); response->set_ratchet_tree(ratchet_tree); } @@ -1553,11 +1594,12 @@ MLSClientImpl::handle_branch(CachedState& entry, return Status(StatusCode::INVALID_ARGUMENT, "Unknown transaction ID"); } - auto welcome = unmarshal_message(request->welcome()); - auto ratchet_tree = std::optional{}; + auto welcome = unmarshal_message(request->welcome()); + auto ratchet_tree = std::optional{}; auto ratchet_tree_data = string_to_bytes(request->ratchet_tree()); if (!ratchet_tree_data.empty()) { - ratchet_tree = tls::get(ratchet_tree_data); + ratchet_tree = MLS_NAMESPACE::tls::get( + ratchet_tree_data); } auto state = entry.state.handle_branch(join->kp_priv.init_priv, diff --git a/cmd/interop/src/mls_client_impl.h b/cmd/interop/src/mls_client_impl.h index 30e37318..685195dc 100644 --- a/cmd/interop/src/mls_client_impl.h +++ b/cmd/interop/src/mls_client_impl.h @@ -141,13 +141,13 @@ class MLSClientImpl final : public MLSClient::Service struct KeyPackageWithSecrets { - mls::HPKEPrivateKey init_priv; - mls::HPKEPrivateKey encryption_priv; - mls::SignaturePrivateKey signature_priv; - mls::KeyPackage key_package; + MLS_NAMESPACE::HPKEPrivateKey init_priv; + MLS_NAMESPACE::HPKEPrivateKey encryption_priv; + MLS_NAMESPACE::SignaturePrivateKey signature_priv; + MLS_NAMESPACE::KeyPackage key_package; }; - KeyPackageWithSecrets new_key_package(mls::CipherSuite cipher_suite, + KeyPackageWithSecrets new_key_package(MLS_NAMESPACE::CipherSuite cipher_suite, const bytes& identity); // Cached join transactions @@ -165,56 +165,59 @@ class MLSClientImpl final : public MLSClient::Service // Cached group state struct CachedState { - mls::State state; + MLS_NAMESPACE::State state; bool encrypt_handshake; - mls::MessageOpts message_opts() const; + MLS_NAMESPACE::MessageOpts message_opts() const; std::optional pending_commit; std::optional pending_state_id; void reset_pending(); // Marshal/unmarshal with encryption as required - std::string marshal(const mls::MLSMessage& msg); - mls::MLSMessage unmarshal(const std::string& wire); + std::string marshal(const MLS_NAMESPACE::MLSMessage& msg); + MLS_NAMESPACE::MLSMessage unmarshal(const std::string& wire); }; std::map state_cache; - uint32_t store_state(mls::State&& state, bool encrypt_handshake); + uint32_t store_state(MLS_NAMESPACE::State&& state, bool encrypt_handshake); CachedState* load_state(uint32_t state_id); - CachedState* find_state(const bytes& group_id, const mls::epoch_t epoch); + CachedState* find_state(const bytes& group_id, + const MLS_NAMESPACE::epoch_t epoch); void remove_state(uint32_t state_id); - mls::LeafIndex find_member(const mls::TreeKEMPublicKey& tree, - const std::string& identity); - mls::Proposal proposal_from_description(mls::CipherSuite suite, - const bytes& group_id, - const mls::TreeKEMPublicKey& tree, - const ProposalDescription& desc); + MLS_NAMESPACE::LeafIndex find_member( + const MLS_NAMESPACE::TreeKEMPublicKey& tree, + const std::string& identity); + MLS_NAMESPACE::Proposal proposal_from_description( + MLS_NAMESPACE::CipherSuite suite, + const bytes& group_id, + const MLS_NAMESPACE::TreeKEMPublicKey& tree, + const ProposalDescription& desc); // Cached external signers struct CachedSigner { - mls::SignaturePrivateKey signature_priv; + MLS_NAMESPACE::SignaturePrivateKey signature_priv; }; std::map signer_cache; - uint32_t store_signer(mls::SignaturePrivateKey&& signature_priv); + uint32_t store_signer(MLS_NAMESPACE::SignaturePrivateKey&& signature_priv); CachedSigner* load_signer(uint32_t signer_id); // Cached ReInit struct CachedReInit { KeyPackageWithSecrets kp_priv; - mls::State::Tombstone tombstone; + MLS_NAMESPACE::State::Tombstone tombstone; bool encrypt_handshake; }; std::map reinit_cache; uint32_t store_reinit(KeyPackageWithSecrets&& kp_priv, - mls::State::Tombstone&& tombstone, + MLS_NAMESPACE::State::Tombstone&& tombstone, bool encrypt_handshake); CachedReInit* load_reinit(uint32_t reinit_id); void remove_reinit(uint32_t reinit_id); diff --git a/include/mls/common.h b/include/mls/common.h index c89a6f3d..ac9ea516 100644 --- a/include/mls/common.h +++ b/include/mls/common.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -11,14 +12,14 @@ using namespace std::literals::string_literals; // Expose the bytes library globally #include -using namespace bytes_ns; +using namespace MLS_NAMESPACE::bytes_ns; // Expose the compatibility library globally #include -namespace var = tls::var; -namespace opt = tls::opt; +namespace var = MLS_NAMESPACE::tls::var; +namespace opt = MLS_NAMESPACE::tls::opt; -namespace mls { +namespace MLS_NAMESPACE { // Make variant equality work in the same way as optional equality, with // automatic unwrapping. In other words @@ -262,4 +263,4 @@ upper_bound(const Container& c, const Value& val) } // namespace stdx -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/include/mls/core_types.h b/include/mls/core_types.h index fda9f71f..cc895fa0 100644 --- a/include/mls/core_types.h +++ b/include/mls/core_types.h @@ -3,8 +3,9 @@ #include "mls/credential.h" #include "mls/crypto.h" #include "mls/tree_math.h" +#include -namespace mls { +namespace MLS_NAMESPACE { // enum { // reserved(0), @@ -357,12 +358,16 @@ struct UpdatePath TLS_SERIALIZABLE(leaf_node, nodes) }; -} // namespace mls +} // namespace MLS_NAMESPACE -namespace tls { +namespace MLS_NAMESPACE::tls { -TLS_VARIANT_MAP(mls::LeafNodeSource, mls::Lifetime, key_package) -TLS_VARIANT_MAP(mls::LeafNodeSource, mls::Empty, update) -TLS_VARIANT_MAP(mls::LeafNodeSource, mls::ParentHash, commit) +TLS_VARIANT_MAP(MLS_NAMESPACE::LeafNodeSource, + MLS_NAMESPACE::Lifetime, + key_package) +TLS_VARIANT_MAP(MLS_NAMESPACE::LeafNodeSource, MLS_NAMESPACE::Empty, update) +TLS_VARIANT_MAP(MLS_NAMESPACE::LeafNodeSource, + MLS_NAMESPACE::ParentHash, + commit) -} // namespace tls +} // namespace MLS_NAMESPACE::tls diff --git a/include/mls/credential.h b/include/mls/credential.h index e1616119..27e6f042 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -2,8 +2,9 @@ #include #include +#include -namespace mls { +namespace MLS_NAMESPACE { // struct { // opaque identity<0..2^16-1>; @@ -194,15 +195,21 @@ struct CredentialBinding bytes to_be_signed(const SignaturePublicKey& signature_key) const; }; -} // namespace mls +} // namespace MLS_NAMESPACE -namespace tls { +namespace MLS_NAMESPACE::tls { -TLS_VARIANT_MAP(mls::CredentialType, mls::BasicCredential, basic) -TLS_VARIANT_MAP(mls::CredentialType, mls::X509Credential, x509) -TLS_VARIANT_MAP(mls::CredentialType, - mls::UserInfoVCCredential, +TLS_VARIANT_MAP(MLS_NAMESPACE::CredentialType, + MLS_NAMESPACE::BasicCredential, + basic) +TLS_VARIANT_MAP(MLS_NAMESPACE::CredentialType, + MLS_NAMESPACE::X509Credential, + x509) +TLS_VARIANT_MAP(MLS_NAMESPACE::CredentialType, + MLS_NAMESPACE::UserInfoVCCredential, userinfo_vc_draft_00) -TLS_VARIANT_MAP(mls::CredentialType, mls::MultiCredential, multi_draft_00) +TLS_VARIANT_MAP(MLS_NAMESPACE::CredentialType, + MLS_NAMESPACE::MultiCredential, + multi_draft_00) -} // namespace tls +} // namespace MLS_NAMESPACE::tls diff --git a/include/mls/crypto.h b/include/mls/crypto.h index d25653bc..00d513d6 100644 --- a/include/mls/crypto.h +++ b/include/mls/crypto.h @@ -5,11 +5,11 @@ #include #include #include +#include #include - #include -namespace mls { +namespace MLS_NAMESPACE { /// Signature Code points, borrowed from RFC 8446 enum struct SignatureScheme : uint16_t @@ -136,7 +136,7 @@ struct CipherSuite extern const std::array all_supported_suites; // Utilities -using hpke::random_bytes; +using MLS_NAMESPACE::hpke::random_bytes; // HPKE Keys namespace encrypt_label { @@ -251,4 +251,4 @@ struct SignaturePrivateKey SignaturePrivateKey(bytes priv_data, bytes pub_data); }; -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/include/mls/key_schedule.h b/include/mls/key_schedule.h index e1014de7..6cf5be2e 100644 --- a/include/mls/key_schedule.h +++ b/include/mls/key_schedule.h @@ -5,8 +5,9 @@ #include #include #include +#include -namespace mls { +namespace MLS_NAMESPACE { struct HashRatchet { @@ -202,4 +203,4 @@ struct TranscriptHash bool operator==(const TranscriptHash& lhs, const TranscriptHash& rhs); -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/include/mls/messages.h b/include/mls/messages.h index e53e2764..54477cbf 100644 --- a/include/mls/messages.h +++ b/include/mls/messages.h @@ -5,10 +5,11 @@ #include "mls/credential.h" #include "mls/crypto.h" #include "mls/treekem.h" +#include #include #include -namespace mls { +namespace MLS_NAMESPACE { struct ExternalPubExtension { @@ -668,41 +669,63 @@ external_proposal(CipherSuite suite, uint32_t signer_index, const SignaturePrivateKey& sig_priv); -} // namespace mls - -namespace tls { - -TLS_VARIANT_MAP(mls::PSKType, mls::ExternalPSK, external) -TLS_VARIANT_MAP(mls::PSKType, mls::ResumptionPSK, resumption) - -TLS_VARIANT_MAP(mls::ProposalOrRefType, mls::Proposal, value) -TLS_VARIANT_MAP(mls::ProposalOrRefType, mls::ProposalRef, reference) - -TLS_VARIANT_MAP(mls::ProposalType, mls::Add, add) -TLS_VARIANT_MAP(mls::ProposalType, mls::Update, update) -TLS_VARIANT_MAP(mls::ProposalType, mls::Remove, remove) -TLS_VARIANT_MAP(mls::ProposalType, mls::PreSharedKey, psk) -TLS_VARIANT_MAP(mls::ProposalType, mls::ReInit, reinit) -TLS_VARIANT_MAP(mls::ProposalType, mls::ExternalInit, external_init) -TLS_VARIANT_MAP(mls::ProposalType, - mls::GroupContextExtensions, +} // namespace MLS_NAMESPACE + +namespace MLS_NAMESPACE::tls { + +TLS_VARIANT_MAP(MLS_NAMESPACE::PSKType, MLS_NAMESPACE::ExternalPSK, external) +TLS_VARIANT_MAP(MLS_NAMESPACE::PSKType, + MLS_NAMESPACE::ResumptionPSK, + resumption) + +TLS_VARIANT_MAP(MLS_NAMESPACE::ProposalOrRefType, + MLS_NAMESPACE::Proposal, + value) +TLS_VARIANT_MAP(MLS_NAMESPACE::ProposalOrRefType, + MLS_NAMESPACE::ProposalRef, + reference) + +TLS_VARIANT_MAP(MLS_NAMESPACE::ProposalType, MLS_NAMESPACE::Add, add) +TLS_VARIANT_MAP(MLS_NAMESPACE::ProposalType, MLS_NAMESPACE::Update, update) +TLS_VARIANT_MAP(MLS_NAMESPACE::ProposalType, MLS_NAMESPACE::Remove, remove) +TLS_VARIANT_MAP(MLS_NAMESPACE::ProposalType, MLS_NAMESPACE::PreSharedKey, psk) +TLS_VARIANT_MAP(MLS_NAMESPACE::ProposalType, MLS_NAMESPACE::ReInit, reinit) +TLS_VARIANT_MAP(MLS_NAMESPACE::ProposalType, + MLS_NAMESPACE::ExternalInit, + external_init) +TLS_VARIANT_MAP(MLS_NAMESPACE::ProposalType, + MLS_NAMESPACE::GroupContextExtensions, group_context_extensions) -TLS_VARIANT_MAP(mls::ContentType, mls::ApplicationData, application) -TLS_VARIANT_MAP(mls::ContentType, mls::Proposal, proposal) -TLS_VARIANT_MAP(mls::ContentType, mls::Commit, commit) - -TLS_VARIANT_MAP(mls::SenderType, mls::MemberSender, member) -TLS_VARIANT_MAP(mls::SenderType, mls::ExternalSenderIndex, external) -TLS_VARIANT_MAP(mls::SenderType, - mls::NewMemberProposalSender, +TLS_VARIANT_MAP(MLS_NAMESPACE::ContentType, + MLS_NAMESPACE::ApplicationData, + application) +TLS_VARIANT_MAP(MLS_NAMESPACE::ContentType, MLS_NAMESPACE::Proposal, proposal) +TLS_VARIANT_MAP(MLS_NAMESPACE::ContentType, MLS_NAMESPACE::Commit, commit) + +TLS_VARIANT_MAP(MLS_NAMESPACE::SenderType, MLS_NAMESPACE::MemberSender, member) +TLS_VARIANT_MAP(MLS_NAMESPACE::SenderType, + MLS_NAMESPACE::ExternalSenderIndex, + external) +TLS_VARIANT_MAP(MLS_NAMESPACE::SenderType, + MLS_NAMESPACE::NewMemberProposalSender, new_member_proposal) -TLS_VARIANT_MAP(mls::SenderType, mls::NewMemberCommitSender, new_member_commit) - -TLS_VARIANT_MAP(mls::WireFormat, mls::PublicMessage, mls_plaintext) -TLS_VARIANT_MAP(mls::WireFormat, mls::PrivateMessage, mls_ciphertext) -TLS_VARIANT_MAP(mls::WireFormat, mls::Welcome, mls_welcome) -TLS_VARIANT_MAP(mls::WireFormat, mls::GroupInfo, mls_group_info) -TLS_VARIANT_MAP(mls::WireFormat, mls::KeyPackage, mls_key_package) - -} // namespace tls +TLS_VARIANT_MAP(MLS_NAMESPACE::SenderType, + MLS_NAMESPACE::NewMemberCommitSender, + new_member_commit) + +TLS_VARIANT_MAP(MLS_NAMESPACE::WireFormat, + MLS_NAMESPACE::PublicMessage, + mls_plaintext) +TLS_VARIANT_MAP(MLS_NAMESPACE::WireFormat, + MLS_NAMESPACE::PrivateMessage, + mls_ciphertext) +TLS_VARIANT_MAP(MLS_NAMESPACE::WireFormat, MLS_NAMESPACE::Welcome, mls_welcome) +TLS_VARIANT_MAP(MLS_NAMESPACE::WireFormat, + MLS_NAMESPACE::GroupInfo, + mls_group_info) +TLS_VARIANT_MAP(MLS_NAMESPACE::WireFormat, + MLS_NAMESPACE::KeyPackage, + mls_key_package) + +} // namespace MLS_NAMESPACE::tls diff --git a/include/mls/session.h b/include/mls/session.h index eb9d0b65..987b54df 100644 --- a/include/mls/session.h +++ b/include/mls/session.h @@ -5,8 +5,9 @@ #include #include #include +#include -namespace mls { +namespace MLS_NAMESPACE { class PendingJoin; class Session; @@ -95,4 +96,4 @@ class Session friend bool operator!=(const Session& lhs, const Session& rhs); }; -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/include/mls/state.h b/include/mls/state.h index 2d7b448f..b1ef0d93 100644 --- a/include/mls/state.h +++ b/include/mls/state.h @@ -5,10 +5,11 @@ #include "mls/messages.h" #include "mls/treekem.h" #include +#include #include #include -namespace mls { +namespace MLS_NAMESPACE { // Index into the session roster struct RosterIndex : public UInt32 @@ -417,4 +418,4 @@ class State State successor() const; }; -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/include/mls/tree_math.h b/include/mls/tree_math.h index ffcade83..19e12d7b 100644 --- a/include/mls/tree_math.h +++ b/include/mls/tree_math.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -27,7 +28,7 @@ // // 01x = <00x, 10x> -namespace mls { +namespace MLS_NAMESPACE { // Index types go in the overall namespace // XXX(rlb@ipv.sx): Seems like this stuff can probably get @@ -104,4 +105,4 @@ struct NodeIndex : public UInt32 uint32_t level() const; }; -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/include/mls/treekem.h b/include/mls/treekem.h index b5c91b29..d58f3c7a 100644 --- a/include/mls/treekem.h +++ b/include/mls/treekem.h @@ -4,11 +4,12 @@ #include "mls/core_types.h" #include "mls/crypto.h" #include "mls/tree_math.h" +#include #include #define ENABLE_TREE_DUMP 1 -namespace mls { +namespace MLS_NAMESPACE { enum struct NodeType : uint8_t { @@ -200,14 +201,16 @@ operator>>(tls::istream& str, TreeKEMPublicKey& obj); struct LeafNodeHashInput; struct ParentNodeHashInput; -} // namespace mls +} // namespace MLS_NAMESPACE -namespace tls { +namespace MLS_NAMESPACE::tls { -TLS_VARIANT_MAP(mls::NodeType, mls::LeafNodeHashInput, leaf) -TLS_VARIANT_MAP(mls::NodeType, mls::ParentNodeHashInput, parent) +TLS_VARIANT_MAP(MLS_NAMESPACE::NodeType, MLS_NAMESPACE::LeafNodeHashInput, leaf) +TLS_VARIANT_MAP(MLS_NAMESPACE::NodeType, + MLS_NAMESPACE::ParentNodeHashInput, + parent) -TLS_VARIANT_MAP(mls::NodeType, mls::LeafNode, leaf) -TLS_VARIANT_MAP(mls::NodeType, mls::ParentNode, parent) +TLS_VARIANT_MAP(MLS_NAMESPACE::NodeType, MLS_NAMESPACE::LeafNode, leaf) +TLS_VARIANT_MAP(MLS_NAMESPACE::NodeType, MLS_NAMESPACE::ParentNode, parent) -} // namespace tls +} // namespace MLS_NAMESPACE::tls diff --git a/lib/bytes/CMakeLists.txt b/lib/bytes/CMakeLists.txt index f5ab674e..7cfb6e31 100644 --- a/lib/bytes/CMakeLists.txt +++ b/lib/bytes/CMakeLists.txt @@ -12,6 +12,7 @@ add_dependencies(${CURRENT_LIB_NAME} tls_syntax) target_link_libraries(${CURRENT_LIB_NAME} tls_syntax) target_include_directories(${CURRENT_LIB_NAME} PUBLIC + $ $ $ ) diff --git a/lib/bytes/include/bytes/bytes.h b/lib/bytes/include/bytes/bytes.h index 713a3c64..4789bdbf 100644 --- a/lib/bytes/include/bytes/bytes.h +++ b/lib/bytes/include/bytes/bytes.h @@ -1,10 +1,11 @@ #pragma once +#include #include #include #include -namespace bytes_ns { +namespace MLS_NAMESPACE::bytes_ns { struct bytes { @@ -118,4 +119,4 @@ to_hex(const bytes& data); bytes from_hex(const std::string& hex); -} // namespace bytes_ns +} // namespace MLS_NAMESPACE::bytes_ns diff --git a/lib/bytes/src/bytes.cpp b/lib/bytes/src/bytes.cpp index 6eb0fe34..2bb2e93e 100644 --- a/lib/bytes/src/bytes.cpp +++ b/lib/bytes/src/bytes.cpp @@ -2,10 +2,11 @@ #include #include +#include #include #include -namespace bytes_ns { +namespace MLS_NAMESPACE::bytes_ns { bool bytes::operator==(const bytes& other) const @@ -143,4 +144,4 @@ operator!=(const std::vector& lhs, const bytes_ns::bytes& rhs) return rhs != lhs; } -} // namespace bytes_ns +} // namespace MLS_NAMESPACE::bytes_ns diff --git a/lib/bytes/test/bytes.cpp b/lib/bytes/test/bytes.cpp index a28dbf9f..3241f1ec 100644 --- a/lib/bytes/test/bytes.cpp +++ b/lib/bytes/test/bytes.cpp @@ -3,7 +3,7 @@ #include #include -using namespace bytes_ns; +using namespace MLS_NAMESPACE::bytes_ns; using namespace std::literals::string_literals; // To check that memory is safely zeroized on destroy, we have to deliberately diff --git a/lib/hpke/CMakeLists.txt b/lib/hpke/CMakeLists.txt index afbc32f1..d57afed0 100644 --- a/lib/hpke/CMakeLists.txt +++ b/lib/hpke/CMakeLists.txt @@ -23,6 +23,7 @@ target_link_libraries(${CURRENT_LIB_NAME} target_include_directories(${CURRENT_LIB_NAME} PUBLIC $ + $ $ ) diff --git a/lib/hpke/include/hpke/base64.h b/lib/hpke/include/hpke/base64.h index 7f809729..0be0d405 100644 --- a/lib/hpke/include/hpke/base64.h +++ b/lib/hpke/include/hpke/base64.h @@ -1,9 +1,9 @@ #pragma once #include -using namespace bytes_ns; +using namespace MLS_NAMESPACE::bytes_ns; -namespace hpke { +namespace MLS_NAMESPACE::hpke { std::string to_base64(const bytes& data); @@ -17,4 +17,4 @@ from_base64(const std::string& enc); bytes from_base64url(const std::string& enc); -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/include/hpke/certificate.h b/lib/hpke/include/hpke/certificate.h index 3267f134..785594cc 100644 --- a/lib/hpke/include/hpke/certificate.h +++ b/lib/hpke/include/hpke/certificate.h @@ -6,10 +6,11 @@ #include #include #include +#include -using namespace bytes_ns; +using namespace MLS_NAMESPACE::bytes_ns; -namespace hpke { +namespace MLS_NAMESPACE::hpke { struct Certificate { @@ -72,4 +73,4 @@ struct Certificate bool operator==(const Certificate& lhs, const Certificate& rhs); -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/include/hpke/digest.h b/lib/hpke/include/hpke/digest.h index 9523a6f1..022bc942 100644 --- a/lib/hpke/include/hpke/digest.h +++ b/lib/hpke/include/hpke/digest.h @@ -3,9 +3,11 @@ #include #include -using namespace bytes_ns; +#include -namespace hpke { +using namespace MLS_NAMESPACE::bytes_ns; + +namespace MLS_NAMESPACE::hpke { struct Digest { @@ -33,4 +35,4 @@ struct Digest friend struct HKDF; }; -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/include/hpke/hpke.h b/lib/hpke/include/hpke/hpke.h index 302a2598..74ff9384 100644 --- a/lib/hpke/include/hpke/hpke.h +++ b/lib/hpke/include/hpke/hpke.h @@ -4,9 +4,10 @@ #include #include -using namespace bytes_ns; +#include +using namespace MLS_NAMESPACE::bytes_ns; -namespace hpke { +namespace MLS_NAMESPACE::hpke { struct KEM { @@ -248,4 +249,4 @@ struct HPKE const bytes& psk_id) const; }; -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/include/hpke/random.h b/lib/hpke/include/hpke/random.h index 8245efac..b43503a5 100644 --- a/lib/hpke/include/hpke/random.h +++ b/lib/hpke/include/hpke/random.h @@ -1,11 +1,12 @@ #pragma once #include -using namespace bytes_ns; +#include +using namespace MLS_NAMESPACE::bytes_ns; -namespace hpke { +namespace MLS_NAMESPACE::hpke { bytes random_bytes(size_t size); -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/include/hpke/signature.h b/lib/hpke/include/hpke/signature.h index 39dae06f..65e67020 100644 --- a/lib/hpke/include/hpke/signature.h +++ b/lib/hpke/include/hpke/signature.h @@ -3,9 +3,10 @@ #include #include -using namespace bytes_ns; +#include +using namespace MLS_NAMESPACE::bytes_ns; -namespace hpke { +namespace MLS_NAMESPACE::hpke { struct Signature { @@ -68,4 +69,4 @@ struct Signature Signature(ID id_in); }; -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/aead_cipher.cpp b/lib/hpke/src/aead_cipher.cpp index 35024190..43a4f073 100644 --- a/lib/hpke/src/aead_cipher.cpp +++ b/lib/hpke/src/aead_cipher.cpp @@ -1,9 +1,10 @@ #include "aead_cipher.h" #include "openssl_common.h" +#include #include -namespace hpke { +namespace MLS_NAMESPACE::hpke { /// /// ExportOnlyCipher @@ -244,4 +245,4 @@ AEADCipher::open(const bytes& key, return pt; } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/aead_cipher.h b/lib/hpke/src/aead_cipher.h index aef28245..a1461cc3 100644 --- a/lib/hpke/src/aead_cipher.h +++ b/lib/hpke/src/aead_cipher.h @@ -1,8 +1,9 @@ #pragma once #include +#include -namespace hpke { +namespace MLS_NAMESPACE::hpke { struct ExportOnlyCipher : public AEAD { @@ -42,4 +43,4 @@ struct AEADCipher : public AEAD friend AEADCipher make_aead(AEAD::ID cipher_in); }; -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/base64.cpp b/lib/hpke/src/base64.cpp index 7ff46eac..476ea997 100644 --- a/lib/hpke/src/base64.cpp +++ b/lib/hpke/src/base64.cpp @@ -6,7 +6,7 @@ #include #include -namespace hpke { +namespace MLS_NAMESPACE::hpke { std::string to_base64(const bytes& data) @@ -98,4 +98,4 @@ from_base64url(const std::string& enc) return from_base64(enc_copy); } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/certificate.cpp b/lib/hpke/src/certificate.cpp index b7ec7d02..1ce97e64 100644 --- a/lib/hpke/src/certificate.cpp +++ b/lib/hpke/src/certificate.cpp @@ -4,13 +4,14 @@ #include #include #include +#include #include #include #include #include #include -namespace hpke { +namespace MLS_NAMESPACE::hpke { /// /// Utility functions /// @@ -518,4 +519,4 @@ operator==(const Certificate& lhs, const Certificate& rhs) return lhs.raw == rhs.raw; } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/common.cpp b/lib/hpke/src/common.cpp index 1666e71b..2848768c 100644 --- a/lib/hpke/src/common.cpp +++ b/lib/hpke/src/common.cpp @@ -1,6 +1,7 @@ #include "common.h" +#include -namespace hpke { +namespace MLS_NAMESPACE::hpke { bytes i2osp(uint64_t val, size_t size) @@ -17,4 +18,4 @@ i2osp(uint64_t val, size_t size) return out; } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/common.h b/lib/hpke/src/common.h index 4f122f1d..c58ab073 100644 --- a/lib/hpke/src/common.h +++ b/lib/hpke/src/common.h @@ -1,10 +1,11 @@ #pragma once #include +#include -namespace hpke { +namespace MLS_NAMESPACE::hpke { bytes i2osp(uint64_t val, size_t size); -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/dhkem.cpp b/lib/hpke/src/dhkem.cpp index 1897d548..d145be50 100644 --- a/lib/hpke/src/dhkem.cpp +++ b/lib/hpke/src/dhkem.cpp @@ -1,8 +1,9 @@ #include "dhkem.h" #include "common.h" +#include -namespace hpke { +namespace MLS_NAMESPACE::hpke { DHKEM::PrivateKey::PrivateKey(Group::PrivateKey* group_priv_in) : group_priv(group_priv_in) @@ -211,4 +212,4 @@ DHKEM::extract_and_expand(const bytes& dh, const bytes& kem_context) const suite_id, eae_prk, label_shared_secret, kem_context, secret_size); } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/dhkem.h b/lib/hpke/src/dhkem.h index c6c4fa48..fc540065 100644 --- a/lib/hpke/src/dhkem.h +++ b/lib/hpke/src/dhkem.h @@ -1,10 +1,11 @@ #pragma once #include +#include #include "group.h" -namespace hpke { +namespace MLS_NAMESPACE::hpke { struct DHKEM : public KEM { @@ -54,4 +55,4 @@ struct DHKEM : public KEM const KDF& kdf_in); }; -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/digest.cpp b/lib/hpke/src/digest.cpp index d2627ca7..2284f771 100644 --- a/lib/hpke/src/digest.cpp +++ b/lib/hpke/src/digest.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -8,7 +9,7 @@ #include "openssl_common.h" -namespace hpke { +namespace MLS_NAMESPACE::hpke { static const EVP_MD* openssl_digest_type(Digest::ID digest) @@ -184,4 +185,4 @@ Digest::hmac_for_hkdf_extract(const bytes& key, const bytes& data) const return md; } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/group.cpp b/lib/hpke/src/group.cpp index 754fb277..cf02b1e0 100644 --- a/lib/hpke/src/group.cpp +++ b/lib/hpke/src/group.cpp @@ -1,6 +1,7 @@ #include "group.h" #include +#include #include "common.h" #include "openssl_common.h" @@ -13,7 +14,7 @@ #include "openssl/param_build.h" #endif -namespace hpke { +namespace MLS_NAMESPACE::hpke { static inline size_t group_dh_size(Group::ID group_id); @@ -1063,4 +1064,4 @@ Group::Group(ID group_id_in, const KDF& kdf_in) { } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/group.h b/lib/hpke/src/group.h index f6b19f9a..d871e1ed 100644 --- a/lib/hpke/src/group.h +++ b/lib/hpke/src/group.h @@ -2,11 +2,12 @@ #include #include +#include #include "openssl_common.h" #include -namespace hpke { +namespace MLS_NAMESPACE::hpke { struct Group { @@ -113,4 +114,4 @@ struct EVPGroup : public Group const Group::PublicKey& pk) const override; }; -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/hkdf.cpp b/lib/hpke/src/hkdf.cpp index 382d5bc1..2961e63e 100644 --- a/lib/hpke/src/hkdf.cpp +++ b/lib/hpke/src/hkdf.cpp @@ -1,11 +1,12 @@ #include "hkdf.h" #include "openssl_common.h" +#include #include #include #include -namespace hpke { +namespace MLS_NAMESPACE::hpke { template<> const HKDF& @@ -76,4 +77,4 @@ HKDF::expand(const bytes& prk, const bytes& info, size_t size) const return okm; } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/hkdf.h b/lib/hpke/src/hkdf.h index c89a0069..004b55c3 100644 --- a/lib/hpke/src/hkdf.h +++ b/lib/hpke/src/hkdf.h @@ -2,8 +2,9 @@ #include #include +#include -namespace hpke { +namespace MLS_NAMESPACE::hpke { struct HKDF : public KDF { @@ -21,4 +22,4 @@ struct HKDF : public KDF explicit HKDF(const Digest& digest_in); }; -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/hpke.cpp b/lib/hpke/src/hpke.cpp index 99c9fc07..19a37fa2 100644 --- a/lib/hpke/src/hpke.cpp +++ b/lib/hpke/src/hpke.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "aead_cipher.h" #include "common.h" @@ -10,7 +11,7 @@ #include #include -namespace hpke { +namespace MLS_NAMESPACE::hpke { /// /// Helper functions and constants @@ -533,4 +534,4 @@ HPKE::key_schedule(Mode mode, return { suite, key, nonce, exporter_secret, kdf, aead }; } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/openssl_common.cpp b/lib/hpke/src/openssl_common.cpp index 34d5ddb3..e1714d81 100644 --- a/lib/hpke/src/openssl_common.cpp +++ b/lib/hpke/src/openssl_common.cpp @@ -1,4 +1,5 @@ #include "openssl_common.h" +#include #include #include @@ -10,7 +11,7 @@ #include #endif -namespace hpke { +namespace MLS_NAMESPACE::hpke { template<> void @@ -148,4 +149,4 @@ openssl_error() return std::runtime_error(ERR_error_string(code, nullptr)); } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/openssl_common.h b/lib/hpke/src/openssl_common.h index a9413544..60d75e03 100644 --- a/lib/hpke/src/openssl_common.h +++ b/lib/hpke/src/openssl_common.h @@ -2,9 +2,10 @@ #include #include +#include #include -namespace hpke { +namespace MLS_NAMESPACE::hpke { template void @@ -23,4 +24,4 @@ make_typed_unique(T* ptr) std::runtime_error openssl_error(); -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/random.cpp b/lib/hpke/src/random.cpp index 36365359..d88be35a 100644 --- a/lib/hpke/src/random.cpp +++ b/lib/hpke/src/random.cpp @@ -1,10 +1,11 @@ #include +#include #include "openssl_common.h" #include -namespace hpke { +namespace MLS_NAMESPACE::hpke { bytes random_bytes(size_t size) @@ -16,4 +17,4 @@ random_bytes(size_t size) return rand; } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/rsa.cpp b/lib/hpke/src/rsa.cpp index 236de856..2eea958d 100644 --- a/lib/hpke/src/rsa.cpp +++ b/lib/hpke/src/rsa.cpp @@ -3,8 +3,9 @@ #include "common.h" #include "openssl/rsa.h" #include "openssl_common.h" +#include -namespace hpke { +namespace MLS_NAMESPACE::hpke { std::unique_ptr RSASignature::generate_key_pair() const @@ -204,4 +205,4 @@ RSASignature::digest_to_sig(Digest::ID digest) } } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/rsa.h b/lib/hpke/src/rsa.h index 8457252f..44eaf382 100644 --- a/lib/hpke/src/rsa.h +++ b/lib/hpke/src/rsa.h @@ -3,12 +3,13 @@ #include #include #include +#include #include "openssl_common.h" #include #include -namespace hpke { +namespace MLS_NAMESPACE::hpke { // XXX(RLB): There is a lot of code in RSASignature that is duplicated in // EVPGroup. I have allowed this duplication rather than factoring it out @@ -94,4 +95,4 @@ struct RSASignature : public Signature static Signature::ID digest_to_sig(Digest::ID digest); }; -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 890257a8..8a6ff093 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -1,13 +1,12 @@ #include #include #include +#include #include #include "dhkem.h" - -#include "common.h" -#include "group.h" #include "rsa.h" + #include #include #include @@ -17,7 +16,7 @@ using nlohmann::json; -namespace hpke { +namespace MLS_NAMESPACE::hpke { struct GroupSignature : public Signature { @@ -279,4 +278,4 @@ Signature::generate_rsa(size_t bits) return RSASignature::generate_key_pair(bits); } -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/test/base64.cpp b/lib/hpke/test/base64.cpp index 25f1c007..6ac06a70 100644 --- a/lib/hpke/test/base64.cpp +++ b/lib/hpke/test/base64.cpp @@ -1,8 +1,8 @@ #include #include -using namespace hpke; -using namespace bytes_ns; +using namespace MLS_NAMESPACE::hpke; +using namespace MLS_NAMESPACE::bytes_ns; TEST_CASE("To Base64 / To Base64Url") { diff --git a/lib/hpke/test/certificate.cpp b/lib/hpke/test/certificate.cpp index 5df2bd8c..d3ab3977 100644 --- a/lib/hpke/test/certificate.cpp +++ b/lib/hpke/test/certificate.cpp @@ -8,7 +8,7 @@ #include #include -namespace opt = tls::opt; +namespace opt = MLS_NAMESPACE::tls::opt; TEST_CASE("Certificate Known-Answer depth 2") { diff --git a/lib/hpke/test/common.h b/lib/hpke/test/common.h index b029ba73..0ef60053 100644 --- a/lib/hpke/test/common.h +++ b/lib/hpke/test/common.h @@ -1,9 +1,9 @@ #include #include -using namespace hpke; +using namespace MLS_NAMESPACE::hpke; #include -using namespace bytes_ns; +using namespace MLS_NAMESPACE::bytes_ns; void ensure_fips_if_required(); diff --git a/lib/hpke/test/hpke.cpp b/lib/hpke/test/hpke.cpp index e9841cd8..78cd5ab2 100644 --- a/lib/hpke/test/hpke.cpp +++ b/lib/hpke/test/hpke.cpp @@ -1,10 +1,11 @@ #include #include +#include #include "common.h" #include "test_vectors.h" -namespace hpke { +namespace MLS_NAMESPACE::hpke { struct HPKETest { @@ -16,18 +17,18 @@ struct HPKETest } }; -} // namespace hpke +} // namespace MLS_NAMESPACE::hpke static void test_context(ReceiverContext& ctxR, const HPKETestVector& tv) { - auto key = hpke::HPKETest::key(ctxR); + auto key = MLS_NAMESPACE::hpke::HPKETest::key(ctxR); REQUIRE(key == tv.key); - auto nonce = hpke::HPKETest::nonce(ctxR); + auto nonce = MLS_NAMESPACE::hpke::HPKETest::nonce(ctxR); REQUIRE(nonce == tv.nonce); - auto exporter_secret = hpke::HPKETest::exporter_secret(ctxR); + auto exporter_secret = MLS_NAMESPACE::hpke::HPKETest::exporter_secret(ctxR); REQUIRE(exporter_secret == tv.exporter_secret); for (const auto& enc : tv.encryptions) { @@ -135,7 +136,8 @@ TEST_CASE("HPKE Test Vectors") ensure_fips_if_required(); auto test_vector_bytes = bytes(test_vector_data); - auto test_vectors = tls::get(test_vector_bytes); + auto test_vectors = + MLS_NAMESPACE::tls::get(test_vector_bytes); for (const auto& tv : test_vectors.vectors) { if (fips() && fips_disable(tv.aead_id)) { diff --git a/lib/hpke/test/random.cpp b/lib/hpke/test/random.cpp index d7cd0b42..48b869e1 100644 --- a/lib/hpke/test/random.cpp +++ b/lib/hpke/test/random.cpp @@ -8,6 +8,6 @@ TEST_CASE("Random bytes") ensure_fips_if_required(); auto size = size_t(128); - auto test_val = hpke::random_bytes(size); + auto test_val = MLS_NAMESPACE::hpke::random_bytes(size); CHECK(test_val.size() == size); } diff --git a/lib/mls_vectors/CMakeLists.txt b/lib/mls_vectors/CMakeLists.txt index 6816d7f8..83b30daa 100644 --- a/lib/mls_vectors/CMakeLists.txt +++ b/lib/mls_vectors/CMakeLists.txt @@ -13,6 +13,7 @@ target_link_libraries(${CURRENT_LIB_NAME} mlspp bytes tls_syntax) target_include_directories(${CURRENT_LIB_NAME} PUBLIC $ + $ $ ) diff --git a/lib/mls_vectors/include/mls_vectors/mls_vectors.h b/lib/mls_vectors/include/mls_vectors/mls_vectors.h index b0fc5fd7..e808083a 100644 --- a/lib/mls_vectors/include/mls_vectors/mls_vectors.h +++ b/lib/mls_vectors/include/mls_vectors/mls_vectors.h @@ -16,7 +16,7 @@ struct PseudoRandom struct Generator { Generator() = default; - Generator(mls::CipherSuite suite_in, const std::string& label); + Generator(MLS_NAMESPACE::CipherSuite suite_in, const std::string& label); Generator sub(const std::string& label) const; bytes secret(const std::string& label) const; @@ -26,38 +26,40 @@ struct PseudoRandom uint32_t uint32(const std::string& label) const; uint64_t uint64(const std::string& label) const; - mls::SignaturePrivateKey signature_key(const std::string& label) const; - mls::HPKEPrivateKey hpke_key(const std::string& label) const; + MLS_NAMESPACE::SignaturePrivateKey signature_key( + const std::string& label) const; + MLS_NAMESPACE::HPKEPrivateKey hpke_key(const std::string& label) const; size_t output_length() const; private: - mls::CipherSuite suite; + MLS_NAMESPACE::CipherSuite suite; bytes seed; - Generator(mls::CipherSuite suite_in, bytes&& seed_in); + Generator(MLS_NAMESPACE::CipherSuite suite_in, bytes&& seed_in); }; PseudoRandom() = default; - PseudoRandom(mls::CipherSuite suite, const std::string& label); + PseudoRandom(MLS_NAMESPACE::CipherSuite suite, const std::string& label); Generator prg; }; struct TreeMathTestVector { - using OptionalNode = std::optional; + using OptionalNode = std::optional; - mls::LeafCount n_leaves; - mls::NodeCount n_nodes; - mls::NodeIndex root; + MLS_NAMESPACE::LeafCount n_leaves; + MLS_NAMESPACE::NodeCount n_nodes; + MLS_NAMESPACE::NodeIndex root; std::vector left; std::vector right; std::vector parent; std::vector sibling; - std::optional null_if_invalid(mls::NodeIndex input, - mls::NodeIndex answer) const; + std::optional null_if_invalid( + MLS_NAMESPACE::NodeIndex input, + MLS_NAMESPACE::NodeIndex answer) const; TreeMathTestVector() = default; TreeMathTestVector(uint32_t n_leaves); @@ -73,8 +75,8 @@ struct CryptoBasicsTestVector : PseudoRandom bytes out; RefHash() = default; - RefHash(mls::CipherSuite suite, PseudoRandom::Generator&& prg); - std::optional verify(mls::CipherSuite suite) const; + RefHash(MLS_NAMESPACE::CipherSuite suite, PseudoRandom::Generator&& prg); + std::optional verify(MLS_NAMESPACE::CipherSuite suite) const; }; struct ExpandWithLabel @@ -86,8 +88,9 @@ struct CryptoBasicsTestVector : PseudoRandom bytes out; ExpandWithLabel() = default; - ExpandWithLabel(mls::CipherSuite suite, PseudoRandom::Generator&& prg); - std::optional verify(mls::CipherSuite suite) const; + ExpandWithLabel(MLS_NAMESPACE::CipherSuite suite, + PseudoRandom::Generator&& prg); + std::optional verify(MLS_NAMESPACE::CipherSuite suite) const; }; struct DeriveSecret @@ -97,8 +100,9 @@ struct CryptoBasicsTestVector : PseudoRandom bytes out; DeriveSecret() = default; - DeriveSecret(mls::CipherSuite suite, PseudoRandom::Generator&& prg); - std::optional verify(mls::CipherSuite suite) const; + DeriveSecret(MLS_NAMESPACE::CipherSuite suite, + PseudoRandom::Generator&& prg); + std::optional verify(MLS_NAMESPACE::CipherSuite suite) const; }; struct DeriveTreeSecret @@ -110,27 +114,29 @@ struct CryptoBasicsTestVector : PseudoRandom bytes out; DeriveTreeSecret() = default; - DeriveTreeSecret(mls::CipherSuite suite, PseudoRandom::Generator&& prg); - std::optional verify(mls::CipherSuite suite) const; + DeriveTreeSecret(MLS_NAMESPACE::CipherSuite suite, + PseudoRandom::Generator&& prg); + std::optional verify(MLS_NAMESPACE::CipherSuite suite) const; }; struct SignWithLabel { - mls::SignaturePrivateKey priv; - mls::SignaturePublicKey pub; + MLS_NAMESPACE::SignaturePrivateKey priv; + MLS_NAMESPACE::SignaturePublicKey pub; bytes content; std::string label; bytes signature; SignWithLabel() = default; - SignWithLabel(mls::CipherSuite suite, PseudoRandom::Generator&& prg); - std::optional verify(mls::CipherSuite suite) const; + SignWithLabel(MLS_NAMESPACE::CipherSuite suite, + PseudoRandom::Generator&& prg); + std::optional verify(MLS_NAMESPACE::CipherSuite suite) const; }; struct EncryptWithLabel { - mls::HPKEPrivateKey priv; - mls::HPKEPublicKey pub; + MLS_NAMESPACE::HPKEPrivateKey priv; + MLS_NAMESPACE::HPKEPublicKey pub; std::string label; bytes context; bytes plaintext; @@ -138,11 +144,12 @@ struct CryptoBasicsTestVector : PseudoRandom bytes ciphertext; EncryptWithLabel() = default; - EncryptWithLabel(mls::CipherSuite suite, PseudoRandom::Generator&& prg); - std::optional verify(mls::CipherSuite suite) const; + EncryptWithLabel(MLS_NAMESPACE::CipherSuite suite, + PseudoRandom::Generator&& prg); + std::optional verify(MLS_NAMESPACE::CipherSuite suite) const; }; - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; RefHash ref_hash; ExpandWithLabel expand_with_label; @@ -152,7 +159,7 @@ struct CryptoBasicsTestVector : PseudoRandom EncryptWithLabel encrypt_with_label; CryptoBasicsTestVector() = default; - CryptoBasicsTestVector(mls::CipherSuite suite); + CryptoBasicsTestVector(MLS_NAMESPACE::CipherSuite suite); std::optional verify() const; }; @@ -166,8 +173,8 @@ struct SecretTreeTestVector : PseudoRandom bytes nonce; SenderData() = default; - SenderData(mls::CipherSuite suite, PseudoRandom::Generator&& prg); - std::optional verify(mls::CipherSuite suite) const; + SenderData(MLS_NAMESPACE::CipherSuite suite, PseudoRandom::Generator&& prg); + std::optional verify(MLS_NAMESPACE::CipherSuite suite) const; }; struct RatchetStep @@ -179,7 +186,7 @@ struct SecretTreeTestVector : PseudoRandom bytes application_nonce; }; - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; SenderData sender_data; @@ -187,7 +194,7 @@ struct SecretTreeTestVector : PseudoRandom std::vector> leaves; SecretTreeTestVector() = default; - SecretTreeTestVector(mls::CipherSuite suite, + SecretTreeTestVector(MLS_NAMESPACE::CipherSuite suite, uint32_t n_leaves, const std::vector& generations); std::optional verify() const; @@ -227,11 +234,11 @@ struct KeyScheduleTestVector : PseudoRandom bytes membership_key; bytes resumption_psk; - mls::HPKEPublicKey external_pub; + MLS_NAMESPACE::HPKEPublicKey external_pub; Export exporter; }; - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; bytes group_id; bytes initial_init_secret; @@ -239,50 +246,51 @@ struct KeyScheduleTestVector : PseudoRandom std::vector epochs; KeyScheduleTestVector() = default; - KeyScheduleTestVector(mls::CipherSuite suite, uint32_t n_epochs); + KeyScheduleTestVector(MLS_NAMESPACE::CipherSuite suite, uint32_t n_epochs); std::optional verify() const; }; struct MessageProtectionTestVector : PseudoRandom { - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; bytes group_id; - mls::epoch_t epoch; + MLS_NAMESPACE::epoch_t epoch; bytes tree_hash; bytes confirmed_transcript_hash; - mls::SignaturePrivateKey signature_priv; - mls::SignaturePublicKey signature_pub; + MLS_NAMESPACE::SignaturePrivateKey signature_priv; + MLS_NAMESPACE::SignaturePublicKey signature_pub; bytes encryption_secret; bytes sender_data_secret; bytes membership_key; - mls::Proposal proposal; - mls::MLSMessage proposal_pub; - mls::MLSMessage proposal_priv; + MLS_NAMESPACE::Proposal proposal; + MLS_NAMESPACE::MLSMessage proposal_pub; + MLS_NAMESPACE::MLSMessage proposal_priv; - mls::Commit commit; - mls::MLSMessage commit_pub; - mls::MLSMessage commit_priv; + MLS_NAMESPACE::Commit commit; + MLS_NAMESPACE::MLSMessage commit_pub; + MLS_NAMESPACE::MLSMessage commit_priv; bytes application; - mls::MLSMessage application_priv; + MLS_NAMESPACE::MLSMessage application_priv; MessageProtectionTestVector() = default; - MessageProtectionTestVector(mls::CipherSuite suite); + MessageProtectionTestVector(MLS_NAMESPACE::CipherSuite suite); std::optional verify(); private: - mls::GroupKeySource group_keys() const; - mls::GroupContext group_context() const; - - mls::MLSMessage protect_pub( - const mls::GroupContent::RawContent& raw_content) const; - mls::MLSMessage protect_priv( - const mls::GroupContent::RawContent& raw_content); - std::optional unprotect(const mls::MLSMessage& message); + MLS_NAMESPACE::GroupKeySource group_keys() const; + MLS_NAMESPACE::GroupContext group_context() const; + + MLS_NAMESPACE::MLSMessage protect_pub( + const MLS_NAMESPACE::GroupContent::RawContent& raw_content) const; + MLS_NAMESPACE::MLSMessage protect_priv( + const MLS_NAMESPACE::GroupContent::RawContent& raw_content); + std::optional unprotect( + const MLS_NAMESPACE::MLSMessage& message); }; struct PSKSecretTestVector : PseudoRandom @@ -294,44 +302,44 @@ struct PSKSecretTestVector : PseudoRandom bytes psk; }; - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; std::vector psks; bytes psk_secret; PSKSecretTestVector() = default; - PSKSecretTestVector(mls::CipherSuite suite, size_t n_psks); + PSKSecretTestVector(MLS_NAMESPACE::CipherSuite suite, size_t n_psks); std::optional verify() const; }; struct TranscriptTestVector : PseudoRandom { - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; bytes confirmation_key; bytes interim_transcript_hash_before; - mls::AuthenticatedContent authenticated_content; + MLS_NAMESPACE::AuthenticatedContent authenticated_content; bytes confirmed_transcript_hash_after; bytes interim_transcript_hash_after; TranscriptTestVector() = default; - TranscriptTestVector(mls::CipherSuite suite); + TranscriptTestVector(MLS_NAMESPACE::CipherSuite suite); std::optional verify() const; }; struct WelcomeTestVector : PseudoRandom { - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; - mls::HPKEPrivateKey init_priv; - mls::SignaturePublicKey signer_pub; + MLS_NAMESPACE::HPKEPrivateKey init_priv; + MLS_NAMESPACE::SignaturePublicKey signer_pub; - mls::MLSMessage key_package; - mls::MLSMessage welcome; + MLS_NAMESPACE::MLSMessage key_package; + MLS_NAMESPACE::MLSMessage welcome; WelcomeTestVector() = default; - WelcomeTestVector(mls::CipherSuite suite); + WelcomeTestVector(MLS_NAMESPACE::CipherSuite suite); std::optional verify() const; }; @@ -419,15 +427,16 @@ extern std::array treekem_test_tree_structures; struct TreeHashTestVector : PseudoRandom { - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; bytes group_id; - mls::TreeKEMPublicKey tree; + MLS_NAMESPACE::TreeKEMPublicKey tree; std::vector tree_hashes; - std::vector> resolutions; + std::vector> resolutions; TreeHashTestVector() = default; - TreeHashTestVector(mls::CipherSuite suite, TreeStructure tree_structure); + TreeHashTestVector(MLS_NAMESPACE::CipherSuite suite, + TreeStructure tree_structure); std::optional verify(); }; @@ -444,19 +453,19 @@ struct TreeOperationsTestVector : PseudoRandom static const std::vector all_scenarios; - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; - mls::TreeKEMPublicKey tree_before; + MLS_NAMESPACE::TreeKEMPublicKey tree_before; bytes tree_hash_before; - mls::Proposal proposal; - mls::LeafIndex proposal_sender; + MLS_NAMESPACE::Proposal proposal; + MLS_NAMESPACE::LeafIndex proposal_sender; - mls::TreeKEMPublicKey tree_after; + MLS_NAMESPACE::TreeKEMPublicKey tree_after; bytes tree_hash_after; TreeOperationsTestVector() = default; - TreeOperationsTestVector(mls::CipherSuite suite, Scenario scenario); + TreeOperationsTestVector(MLS_NAMESPACE::CipherSuite suite, Scenario scenario); std::optional verify(); }; @@ -464,40 +473,41 @@ struct TreeKEMTestVector : PseudoRandom { struct PathSecret { - mls::NodeIndex node; + MLS_NAMESPACE::NodeIndex node; bytes path_secret; }; struct LeafPrivateInfo { - mls::LeafIndex index; - mls::HPKEPrivateKey encryption_priv; - mls::SignaturePrivateKey signature_priv; + MLS_NAMESPACE::LeafIndex index; + MLS_NAMESPACE::HPKEPrivateKey encryption_priv; + MLS_NAMESPACE::SignaturePrivateKey signature_priv; std::vector path_secrets; }; struct UpdatePathInfo { - mls::LeafIndex sender; - mls::UpdatePath update_path; + MLS_NAMESPACE::LeafIndex sender; + MLS_NAMESPACE::UpdatePath update_path; std::vector> path_secrets; bytes commit_secret; bytes tree_hash_after; }; - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; bytes group_id; - mls::epoch_t epoch; + MLS_NAMESPACE::epoch_t epoch; bytes confirmed_transcript_hash; - mls::TreeKEMPublicKey ratchet_tree; + MLS_NAMESPACE::TreeKEMPublicKey ratchet_tree; std::vector leaves_private; std::vector update_paths; TreeKEMTestVector() = default; - TreeKEMTestVector(mls::CipherSuite suite, TreeStructure tree_structure); + TreeKEMTestVector(MLS_NAMESPACE::CipherSuite suite, + TreeStructure tree_structure); std::optional verify(); }; @@ -538,22 +548,22 @@ struct PassiveClientTestVector : PseudoRandom struct Epoch { - std::vector proposals; - mls::MLSMessage commit; + std::vector proposals; + MLS_NAMESPACE::MLSMessage commit; bytes epoch_authenticator; }; - mls::CipherSuite cipher_suite; + MLS_NAMESPACE::CipherSuite cipher_suite; - mls::MLSMessage key_package; - mls::SignaturePrivateKey signature_priv; - mls::HPKEPrivateKey encryption_priv; - mls::HPKEPrivateKey init_priv; + MLS_NAMESPACE::MLSMessage key_package; + MLS_NAMESPACE::SignaturePrivateKey signature_priv; + MLS_NAMESPACE::HPKEPrivateKey encryption_priv; + MLS_NAMESPACE::HPKEPrivateKey init_priv; std::vector external_psks; - mls::MLSMessage welcome; - std::optional ratchet_tree; + MLS_NAMESPACE::MLSMessage welcome; + std::optional ratchet_tree; bytes initial_epoch_authenticator; std::vector epochs; diff --git a/lib/mls_vectors/src/mls_vectors.cpp b/lib/mls_vectors/src/mls_vectors.cpp index 208d3f51..64936b37 100644 --- a/lib/mls_vectors/src/mls_vectors.cpp +++ b/lib/mls_vectors/src/mls_vectors.cpp @@ -7,7 +7,7 @@ namespace mls_vectors { -using namespace mls; +using namespace MLS_NAMESPACE; /// /// Assertions for verifying test vectors @@ -248,7 +248,7 @@ PseudoRandom::PseudoRandom(CipherSuite suite, const std::string& label) // XXX(RLB): This is a hack to get the tests working in the right format. In // reality, the tree math functions should be updated to be fallible. -std::optional +std::optional TreeMathTestVector::null_if_invalid(NodeIndex input, NodeIndex answer) const { // For some invalid cases (e.g., leaf.left()), we currently return the node @@ -476,7 +476,7 @@ CryptoBasicsTestVector::verify() const /// SecretTreeTestVector /// -SecretTreeTestVector::SenderData::SenderData(mls::CipherSuite suite, +SecretTreeTestVector::SenderData::SenderData(MLS_NAMESPACE::CipherSuite suite, PseudoRandom::Generator&& prg) : sender_data_secret(prg.secret("sender_data_secret")) , ciphertext(prg.secret("ciphertext")) @@ -488,7 +488,7 @@ SecretTreeTestVector::SenderData::SenderData(mls::CipherSuite suite, } std::optional -SecretTreeTestVector::SenderData::verify(mls::CipherSuite suite) const +SecretTreeTestVector::SenderData::verify(MLS_NAMESPACE::CipherSuite suite) const { auto key_and_nonce = KeyScheduleEpoch::sender_data_keys(suite, sender_data_secret, ciphertext); @@ -498,7 +498,7 @@ SecretTreeTestVector::SenderData::verify(mls::CipherSuite suite) const } SecretTreeTestVector::SecretTreeTestVector( - mls::CipherSuite suite, + MLS_NAMESPACE::CipherSuite suite, uint32_t n_leaves, const std::vector& generations) : PseudoRandom(suite, "secret-tree") @@ -820,7 +820,7 @@ MessageProtectionTestVector::group_context() const MLSMessage MessageProtectionTestVector::protect_pub( - const mls::GroupContent::RawContent& raw_content) const + const MLS_NAMESPACE::GroupContent::RawContent& raw_content) const { auto sender = Sender{ MemberSender{ LeafIndex{ 1 } } }; auto authenticated_data = bytes{}; @@ -844,7 +844,7 @@ MessageProtectionTestVector::protect_pub( MLSMessage MessageProtectionTestVector::protect_priv( - const mls::GroupContent::RawContent& raw_content) + const MLS_NAMESPACE::GroupContent::RawContent& raw_content) { auto sender = Sender{ MemberSender{ LeafIndex{ 1 } } }; auto authenticated_data = bytes{}; @@ -914,7 +914,8 @@ to_psk_w_secret(const std::vector& psks) return pskws; } -PSKSecretTestVector::PSKSecretTestVector(mls::CipherSuite suite, size_t n_psks) +PSKSecretTestVector::PSKSecretTestVector(MLS_NAMESPACE::CipherSuite suite, + size_t n_psks) : PseudoRandom(suite, "psk_secret") , cipher_suite(suite) , psks(n_psks) @@ -1385,7 +1386,7 @@ struct TreeTestCase /// /// TreeHashTestVector /// -TreeHashTestVector::TreeHashTestVector(mls::CipherSuite suite, +TreeHashTestVector::TreeHashTestVector(MLS_NAMESPACE::CipherSuite suite, TreeStructure tree_structure) : PseudoRandom(suite, "tree-hashes") , cipher_suite(suite) @@ -1448,8 +1449,9 @@ const std::vector Scenario::remove_right_edge, Scenario::remove_internal, }; -TreeOperationsTestVector::TreeOperationsTestVector(mls::CipherSuite suite, - Scenario scenario) +TreeOperationsTestVector::TreeOperationsTestVector( + MLS_NAMESPACE::CipherSuite suite, + Scenario scenario) : PseudoRandom(suite, "tree-operations") , cipher_suite(suite) , proposal_sender(0) @@ -1591,7 +1593,7 @@ TreeOperationsTestVector::verify() /// TreeKEMTestVector /// -TreeKEMTestVector::TreeKEMTestVector(mls::CipherSuite suite, +TreeKEMTestVector::TreeKEMTestVector(MLS_NAMESPACE::CipherSuite suite, TreeStructure tree_structure) : PseudoRandom(suite, "treekem") , cipher_suite(suite) diff --git a/lib/mls_vectors/test/mls_vectors.cpp b/lib/mls_vectors/test/mls_vectors.cpp index 592b3f7a..f50f96b0 100644 --- a/lib/mls_vectors/test/mls_vectors.cpp +++ b/lib/mls_vectors/test/mls_vectors.cpp @@ -4,13 +4,13 @@ using namespace mls_vectors; -static const std::vector supported_suites{ - { mls::CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519 }, - { mls::CipherSuite::ID::P256_AES128GCM_SHA256_P256 }, - { mls::CipherSuite::ID::X25519_CHACHA20POLY1305_SHA256_Ed25519 }, - { mls::CipherSuite::ID::X448_AES256GCM_SHA512_Ed448 }, - { mls::CipherSuite::ID::P521_AES256GCM_SHA512_P521 }, - { mls::CipherSuite::ID::X448_CHACHA20POLY1305_SHA512_Ed448 }, +static const std::vector supported_suites{ + { MLS_NAMESPACE::CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519 }, + { MLS_NAMESPACE::CipherSuite::ID::P256_AES128GCM_SHA256_P256 }, + { MLS_NAMESPACE::CipherSuite::ID::X25519_CHACHA20POLY1305_SHA256_Ed25519 }, + { MLS_NAMESPACE::CipherSuite::ID::X448_AES256GCM_SHA512_Ed448 }, + { MLS_NAMESPACE::CipherSuite::ID::P521_AES256GCM_SHA512_P521 }, + { MLS_NAMESPACE::CipherSuite::ID::X448_CHACHA20POLY1305_SHA512_Ed448 }, }; TEST_CASE("Tree Math") @@ -87,7 +87,7 @@ TEST_CASE("Tree Hashes") TEST_CASE("Tree Operations") { - auto suite = mls::CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519; + auto suite = MLS_NAMESPACE::CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519; for (auto scenario : TreeOperationsTestVector::all_scenarios) { auto tv = TreeOperationsTestVector{ suite, scenario }; REQUIRE(tv.verify() == std::nullopt); diff --git a/lib/tls_syntax/CMakeLists.txt b/lib/tls_syntax/CMakeLists.txt index 44ba14fd..4d98912e 100644 --- a/lib/tls_syntax/CMakeLists.txt +++ b/lib/tls_syntax/CMakeLists.txt @@ -13,6 +13,7 @@ target_link_libraries(${CURRENT_LIB_NAME} PUBLIC third_party) target_include_directories(${CURRENT_LIB_NAME} PUBLIC $ + $ $ ) diff --git a/lib/tls_syntax/include/tls/compat.h b/lib/tls_syntax/include/tls/compat.h index be8c20d9..6c78683a 100644 --- a/lib/tls_syntax/include/tls/compat.h +++ b/lib/tls_syntax/include/tls/compat.h @@ -8,8 +8,9 @@ #else #include #endif // VARIANT_COMPAT +#include -namespace tls { +namespace MLS_NAMESPACE::tls { // To balance backward-compatibility with macOS 10.11 with forward-compatibility // with future versions of C++, we use `mpark::variant` or `std::variant` as @@ -65,4 +66,4 @@ get(const std::optional&& opt) } } // namespace opt -} // namespace tls +} // namespace MLS_NAMESPACE::tls diff --git a/lib/tls_syntax/include/tls/tls_syntax.h b/lib/tls_syntax/include/tls/tls_syntax.h index 19806f0c..28427aa2 100644 --- a/lib/tls_syntax/include/tls/tls_syntax.h +++ b/lib/tls_syntax/include/tls/tls_syntax.h @@ -4,13 +4,14 @@ #include #include #include +#include #include #include #include #include -namespace tls { +namespace MLS_NAMESPACE::tls { // For indicating no min or max in vector definitions const size_t none = std::numeric_limits::max(); @@ -565,4 +566,4 @@ inline return str; } -} // namespace tls +} // namespace MLS_NAMESPACE::tls diff --git a/lib/tls_syntax/src/tls_syntax.cpp b/lib/tls_syntax/src/tls_syntax.cpp index 717b6b1f..dccfe16f 100644 --- a/lib/tls_syntax/src/tls_syntax.cpp +++ b/lib/tls_syntax/src/tls_syntax.cpp @@ -1,7 +1,8 @@ +#include #include // NOLINTNEXTLINE(llvmlibc-implementation-in-namespace) -namespace tls { +namespace MLS_NAMESPACE::tls { void ostream::write_raw(const std::vector& bytes) @@ -175,4 +176,4 @@ varint::decode(istream& str, uint64_t& val) return str; } -} // namespace tls +} // namespace MLS_NAMESPACE::tls diff --git a/lib/tls_syntax/test/tls_syntax.cpp b/lib/tls_syntax/test/tls_syntax.cpp index 8fdbd270..3a6e68d6 100644 --- a/lib/tls_syntax/test/tls_syntax.cpp +++ b/lib/tls_syntax/test/tls_syntax.cpp @@ -1,8 +1,9 @@ #include #include +#include #include -using namespace bytes_ns; +using namespace MLS_NAMESPACE::bytes_ns; // An enum to test enum encoding, and as a type for variants enum struct IntType : uint16_t @@ -11,12 +12,12 @@ enum struct IntType : uint16_t uint16 = 0xBBBB, }; -namespace tls { +namespace MLS_NAMESPACE::tls { TLS_VARIANT_MAP(IntType, uint8_t, uint8) TLS_VARIANT_MAP(IntType, uint16_t, uint16) -} // namespace tls +} // namespace MLS_NAMESPACE::tls // A struct to test struct encoding and traits struct ExampleStruct @@ -25,20 +26,20 @@ struct ExampleStruct std::array b{ 0, 0, 0, 0 }; std::optional c; std::vector d; - tls::var::variant e; + MLS_NAMESPACE::tls::var::variant e; uint64_t f{ 0 }; uint64_t g{ 0 }; uint64_t h{ 0 }; TLS_SERIALIZABLE(a, b, c, d, e, f, g, h) - TLS_TRAITS(tls::pass, - tls::pass, - tls::pass, - tls::pass, - tls::variant, - tls::varint, - tls::varint, - tls::varint) + TLS_TRAITS(MLS_NAMESPACE::tls::pass, + MLS_NAMESPACE::tls::pass, + MLS_NAMESPACE::tls::pass, + MLS_NAMESPACE::tls::pass, + MLS_NAMESPACE::tls::variant, + MLS_NAMESPACE::tls::varint, + MLS_NAMESPACE::tls::varint, + MLS_NAMESPACE::tls::varint) }; static bool @@ -99,7 +100,7 @@ template void ostream_test(T val, const std::vector& enc) { - tls::ostream w; // NOLINT(misc-const-correctness) + MLS_NAMESPACE::tls::ostream w; // NOLINT(misc-const-correctness) w << val; REQUIRE(w.bytes() == enc); REQUIRE(w.size() == enc.size()); @@ -108,7 +109,7 @@ ostream_test(T val, const std::vector& enc) TEST_CASE_FIXTURE(TLSSyntaxTest, "TLS ostream") { bytes answer{ 1, 2, 3, 4 }; - tls::ostream w; + MLS_NAMESPACE::tls::ostream w; w.write_raw(answer); REQUIRE(w.bytes() == answer); @@ -129,7 +130,7 @@ template void istream_test(T val, T& data, const std::vector& enc) { - tls::istream r(enc); // NOLINT(misc-const-correctness) + MLS_NAMESPACE::tls::istream r(enc); // NOLINT(misc-const-correctness) r >> data; REQUIRE(data == val); REQUIRE(r.empty()); @@ -175,26 +176,26 @@ TEST_CASE_FIXTURE(TLSSyntaxTest, "TLS abbreviations") { ExampleStruct val_in = val_struct; - tls::ostream w; + MLS_NAMESPACE::tls::ostream w; w << val_struct; auto streamed = w.bytes(); - auto marshaled = tls::marshal(val_struct); + auto marshaled = MLS_NAMESPACE::tls::marshal(val_struct); REQUIRE(streamed == marshaled); ExampleStruct val_out1; - tls::unmarshal(marshaled, val_out1); + MLS_NAMESPACE::tls::unmarshal(marshaled, val_out1); REQUIRE(val_in == val_out1); - auto val_out2 = tls::get(marshaled); + auto val_out2 = MLS_NAMESPACE::tls::get(marshaled); REQUIRE(val_in == val_out2); } TEST_CASE("TLS varint failure cases") { // Encoding a value that is to large - tls::ostream w; + MLS_NAMESPACE::tls::ostream w; // NOLINTNEXTLINE(llvm-else-after-return, readability-else-after-return) - REQUIRE_THROWS(tls::varint::encode(w, uint64_t(0xffffffff))); + REQUIRE_THROWS(MLS_NAMESPACE::tls::varint::encode(w, uint64_t(0xffffffff))); // Too large and non-minimal values auto decode_failure_cases = std::vector{ @@ -204,9 +205,9 @@ TEST_CASE("TLS varint failure cases") }; for (const auto& enc : decode_failure_cases) { auto val = uint64_t(0); - auto r = tls::istream(enc); + auto r = MLS_NAMESPACE::tls::istream(enc); // NOLINTNEXTLINE(llvm-else-after-return, readability-else-after-return) - REQUIRE_THROWS(tls::varint::decode(r, val)); + REQUIRE_THROWS(MLS_NAMESPACE::tls::varint::decode(r, val)); } } diff --git a/src/common.cpp b/src/common.cpp index 1091d4c6..c0185aa5 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -1,6 +1,7 @@ #include "mls/common.h" +#include -namespace mls { +namespace MLS_NAMESPACE { uint64_t seconds_since_epoch() @@ -10,4 +11,4 @@ seconds_since_epoch() return std::time(nullptr); } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/core_types.cpp b/src/core_types.cpp index e8f27901..dc763b23 100644 --- a/src/core_types.cpp +++ b/src/core_types.cpp @@ -1,11 +1,12 @@ #include "mls/core_types.h" #include "mls/messages.h" +#include #include "grease.h" #include -namespace mls { +namespace MLS_NAMESPACE { /// /// Extensions @@ -437,4 +438,4 @@ KeyPackage::to_be_signed() const return out.bytes(); } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/credential.cpp b/src/credential.cpp index bb6ba3ae..16294ab0 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -1,15 +1,16 @@ #include "mls/credential.h" #include "hpke/certificate.h" +#include #include -namespace mls { +namespace MLS_NAMESPACE { /// /// X509Credential /// -using hpke::Certificate; // NOLINT(misc-unused-using-decls) -using hpke::Signature; // NOLINT(misc-unused-using-decls) +using MLS_NAMESPACE::hpke::Certificate; // NOLINT(misc-unused-using-decls) +using MLS_NAMESPACE::hpke::Signature; // NOLINT(misc-unused-using-decls) static const Signature& find_signature(Signature::ID id) @@ -251,4 +252,4 @@ Credential::Credential(SpecificCredential specific) { } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/crypto.cpp b/src/crypto.cpp index ecedadfc..315ededd 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -1,17 +1,18 @@ #include #include #include +#include #include -using hpke::AEAD; // NOLINT(misc-unused-using-decls) -using hpke::Digest; // NOLINT(misc-unused-using-decls) -using hpke::HPKE; // NOLINT(misc-unused-using-decls) -using hpke::KDF; // NOLINT(misc-unused-using-decls) -using hpke::KEM; // NOLINT(misc-unused-using-decls) -using hpke::Signature; // NOLINT(misc-unused-using-decls) +using MLS_NAMESPACE::hpke::AEAD; // NOLINT(misc-unused-using-decls) +using MLS_NAMESPACE::hpke::Digest; // NOLINT(misc-unused-using-decls) +using MLS_NAMESPACE::hpke::HPKE; // NOLINT(misc-unused-using-decls) +using MLS_NAMESPACE::hpke::KDF; // NOLINT(misc-unused-using-decls) +using MLS_NAMESPACE::hpke::KEM; // NOLINT(misc-unused-using-decls) +using MLS_NAMESPACE::hpke::Signature; // NOLINT(misc-unused-using-decls) -namespace mls { +namespace MLS_NAMESPACE { SignatureScheme tls_signature_scheme(Signature::ID id) @@ -470,4 +471,4 @@ SignaturePrivateKey::to_jwk(CipherSuite suite) const return suite.sig().export_jwk_private(*priv); } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/grease.cpp b/src/grease.cpp index e0de4c62..fc210648 100644 --- a/src/grease.cpp +++ b/src/grease.cpp @@ -1,9 +1,10 @@ #include "grease.h" +#include #include #include -namespace mls { +namespace MLS_NAMESPACE { // Randomness parmeters: // * Given a list of N items, insert max(1, rand(p_grease * N)) GREASE values @@ -117,4 +118,4 @@ grease(ExtensionList&& extensions) return { ext }; } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/grease.h b/src/grease.h index 1a59f108..c57f10a6 100644 --- a/src/grease.h +++ b/src/grease.h @@ -1,8 +1,9 @@ #pragma once #include "mls/core_types.h" +#include -namespace mls { +namespace MLS_NAMESPACE { Capabilities grease(Capabilities&& capabilities, const ExtensionList& extensions); @@ -10,4 +11,4 @@ grease(Capabilities&& capabilities, const ExtensionList& extensions); ExtensionList grease(ExtensionList&& extensions); -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/key_schedule.cpp b/src/key_schedule.cpp index 3882955a..a5587ca2 100644 --- a/src/key_schedule.cpp +++ b/src/key_schedule.cpp @@ -1,6 +1,7 @@ #include +#include -namespace mls { +namespace MLS_NAMESPACE { /// /// Key Derivation Functions @@ -576,4 +577,4 @@ operator==(const TranscriptHash& lhs, const TranscriptHash& rhs) return confirmed && interim; } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/messages.cpp b/src/messages.cpp index 17590b3d..7510fb16 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -2,10 +2,11 @@ #include #include #include +#include #include "grease.h" -namespace mls { +namespace MLS_NAMESPACE { // Extensions @@ -897,4 +898,4 @@ external_proposal(CipherSuite suite, return PublicMessage::protect(std::move(content_auth), suite, {}, {}); } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/session.cpp b/src/session.cpp index b9ea49f3..6d42cdce 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -1,10 +1,10 @@ -#include - #include +#include +#include #include -namespace mls { +namespace MLS_NAMESPACE { /// /// Inner struct declarations for PendingJoin and Session @@ -435,4 +435,4 @@ operator!=(const Session& lhs, const Session& rhs) return !(lhs == rhs); } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/state.cpp b/src/state.cpp index 248ed7fc..c786f2d9 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -1,7 +1,8 @@ #include +#include #include -namespace mls { +namespace MLS_NAMESPACE { /// /// Constructors @@ -2143,4 +2144,4 @@ State::successor() const return next; } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/tree_math.cpp b/src/tree_math.cpp index d67f4371..f1a8be95 100644 --- a/src/tree_math.cpp +++ b/src/tree_math.cpp @@ -1,5 +1,6 @@ #include "mls/tree_math.h" #include "mls/common.h" +#include #include @@ -19,7 +20,7 @@ log2(uint32_t x) return k - 1; } -namespace mls { +namespace MLS_NAMESPACE { LeafCount::LeafCount(const NodeCount w) { @@ -220,4 +221,4 @@ NodeIndex::level() const return k; } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/src/treekem.cpp b/src/treekem.cpp index 92254104..176dc856 100644 --- a/src/treekem.cpp +++ b/src/treekem.cpp @@ -1,10 +1,11 @@ #include +#include #if ENABLE_TREE_DUMP #include #endif -namespace mls { +namespace MLS_NAMESPACE { // Utility method used for removing leaves from a resolution static void @@ -1079,4 +1080,4 @@ operator>>(tls::istream& str, TreeKEMPublicKey& obj) return str; } -} // namespace mls +} // namespace MLS_NAMESPACE diff --git a/test/credential.cpp b/test/credential.cpp index 5c1a342e..d8939747 100644 --- a/test/credential.cpp +++ b/test/credential.cpp @@ -1,7 +1,7 @@ #include #include -using namespace mls; +using namespace MLS_NAMESPACE; TEST_CASE("Basic Credential") { diff --git a/test/crypto.cpp b/test/crypto.cpp index a21b517e..a738f88f 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -3,7 +3,7 @@ #include #include -using namespace mls; +using namespace MLS_NAMESPACE; using namespace mls_vectors; TEST_CASE("Basic HPKE") diff --git a/test/key_schedule.cpp b/test/key_schedule.cpp index 7ccc8d27..81880c15 100644 --- a/test/key_schedule.cpp +++ b/test/key_schedule.cpp @@ -2,7 +2,7 @@ #include #include -using namespace mls; +using namespace MLS_NAMESPACE; using namespace mls_vectors; TEST_CASE("Secret Tree Interop") diff --git a/test/messages.cpp b/test/messages.cpp index ababc97e..31621e1e 100644 --- a/test/messages.cpp +++ b/test/messages.cpp @@ -4,7 +4,7 @@ #include #include -using namespace mls; +using namespace MLS_NAMESPACE; using namespace mls_vectors; TEST_CASE("Extensions") diff --git a/test/session.cpp b/test/session.cpp index 89c75de9..894f2654 100644 --- a/test/session.cpp +++ b/test/session.cpp @@ -2,7 +2,7 @@ #include #include -using namespace mls; +using namespace MLS_NAMESPACE; class SessionTest { @@ -268,23 +268,24 @@ TEST_CASE("Session with X509 Credential") "d9865c93f3952abc7e671e625b8479214c1c9b62a7cc6a51a84a3610f4"); const std::vector der_chain{ leaf_der, issuing_der }; - const mls::CipherSuite suite{ - mls::CipherSuite::ID::P256_AES128GCM_SHA256_P256 + const MLS_NAMESPACE::CipherSuite suite{ + MLS_NAMESPACE::CipherSuite::ID::P256_AES128GCM_SHA256_P256 }; auto alice_id = from_ascii("alice"); - auto alice_sig_priv = mls::SignaturePrivateKey::parse(suite, key_raw); - auto alice_cred = mls::Credential::x509(der_chain); - auto alice_client = mls::Client(suite, alice_sig_priv, alice_cred); + auto alice_sig_priv = + MLS_NAMESPACE::SignaturePrivateKey::parse(suite, key_raw); + auto alice_cred = MLS_NAMESPACE::Credential::x509(der_chain); + auto alice_client = MLS_NAMESPACE::Client(suite, alice_sig_priv, alice_cred); auto group_id = bytes{ 0, 1, 2, 3 }; auto alice_session = alice_client.begin_session(group_id); auto bob_id = from_ascii("bob"); - auto bob_sig_priv = mls::SignaturePrivateKey::generate(suite); - auto bob_cred = mls::Credential::basic(bob_id); + auto bob_sig_priv = MLS_NAMESPACE::SignaturePrivateKey::generate(suite); + auto bob_cred = MLS_NAMESPACE::Credential::basic(bob_id); - auto bob_client = mls::Client(suite, bob_sig_priv, bob_cred); + auto bob_client = MLS_NAMESPACE::Client(suite, bob_sig_priv, bob_cred); auto bob_pending_join = bob_client.start_join(); diff --git a/test/state.cpp b/test/state.cpp index 9d93220b..f2619528 100644 --- a/test/state.cpp +++ b/test/state.cpp @@ -3,7 +3,7 @@ #include #include -using namespace mls; +using namespace MLS_NAMESPACE; struct CustomExtension { diff --git a/test/treekem.cpp b/test/treekem.cpp index c5025106..579fe511 100644 --- a/test/treekem.cpp +++ b/test/treekem.cpp @@ -4,7 +4,7 @@ #include #include -using namespace mls; +using namespace MLS_NAMESPACE; using namespace mls_vectors; class TreeKEMTest From 7bfa991e2e99a51899d8ab56b9b53387c9dbf67c Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Thu, 31 Aug 2023 15:12:32 -0500 Subject: [PATCH 26/36] Don't run slow/expensive CI jobs if faster jobs fail (#361) * bold updates to ci * installing linux headers on ubuntu * installing ubuntu headers * let's try one file * turned off on events * let's try it * fixing errors * split CI into three jobs that depend on each other * updating clang-format-action * consolidating to one file * fixing quick linux check * fixing interop harness * Fixing names and adding macos compatibility * do not run long tests when PR in in draft * removing tests from macos compatability * adding badge --- .github/workflows/build.yml | 65 --------- .github/workflows/build_openssl3.yml | 68 ---------- .github/workflows/compat.yml | 42 ------ .github/workflows/interop.yml | 54 -------- .github/workflows/main_ci.yml | 189 +++++++++++++++++++++++++++ .github/workflows/style.yml | 22 ---- README.md | 2 + 7 files changed, 191 insertions(+), 251 deletions(-) delete mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/build_openssl3.yml delete mode 100644 .github/workflows/compat.yml delete mode 100644 .github/workflows/interop.yml create mode 100644 .github/workflows/main_ci.yml delete mode 100644 .github/workflows/style.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 5ff051c7..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Build and Test - -on: - push: - branches: - - main - pull_request: - branches: - - main - schedule: - - cron: '30 2 * * 1' - -env: - CTEST_OUTPUT_ON_FAILURE: 1 - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - include: - - os: windows-latest - vcpkg-cmake-file: "$env:VCPKG_INSTALLATION_ROOT\\scripts\\buildsystems\\vcpkg.cmake" - ctest-target: RUN_TESTS - - os: ubuntu-latest - vcpkg-cmake-file: "$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" - ctest-target: test - - os: macos-latest - vcpkg-cmake-file: "$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" - ctest-target: test - - env: - CMAKE_BUILD_DIR: ${{ github.workspace }}/build - CMAKE_TEST_DIR: ${{ github.workspace }}/build/test - - steps: - - uses: actions/checkout@v2 - - - name: dependencies (macos) - if: ${{ matrix.os == 'macos-latest' }} - run: | - brew install llvm pkg-config - ln -s "/usr/local/opt/llvm/bin/clang-format" "/usr/local/bin/clang-format" - ln -s "/usr/local/opt/llvm/bin/clang-tidy" "/usr/local/bin/clang-tidy" - - - name: Restore cache - uses: actions/cache@v2 - with: - path: | - ${{ env.CMAKE_BUILD_DIR }}/vcpkg_installed - key: ${{ runner.os }}-${{ hashFiles( '**/vcpkg.json' ) }} - - - name: Configure to use clang-tidy and sanitizers - run: | - cmake -B "${{ env.CMAKE_BUILD_DIR }}" -DTESTING=ON -DCLANG_TIDY=ON -DSANITIZERS=ON -DCMAKE_TOOLCHAIN_FILE="${{ matrix.vcpkg-cmake-file}}" . - - - name: Build - run: | - cmake --build "${{ env.CMAKE_BUILD_DIR }}" - - - name: Unit tests - run: | - cmake --build "${{ env.CMAKE_BUILD_DIR }}" --target "${{ matrix.ctest-target}}" diff --git a/.github/workflows/build_openssl3.yml b/.github/workflows/build_openssl3.yml deleted file mode 100644 index 9899ec44..00000000 --- a/.github/workflows/build_openssl3.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Build and Test (OpenSSL 3) - -on: - push: - branches: - - main - pull_request: - branches: - - main - schedule: - - cron: '30 3 * * 1' - -env: - CTEST_OUTPUT_ON_FAILURE: 1 - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - include: - - os: windows-latest - vcpkg-cmake-file: "$env:VCPKG_INSTALLATION_ROOT\\scripts\\buildsystems\\vcpkg.cmake" - ossl3-vcpkg-dir: "alternatives\\openssl_3" - ctest-target: RUN_TESTS - - os: ubuntu-latest - vcpkg-cmake-file: "$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" - ossl3-vcpkg-dir: "alternatives/openssl_3" - ctest-target: test - - os: macos-latest - vcpkg-cmake-file: "$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" - ossl3-vcpkg-dir: "alternatives/openssl_3" - ctest-target: test - - env: - CMAKE_BUILD_DIR: ${{ github.workspace }}/build - CMAKE_TEST_DIR: ${{ github.workspace }}/build/test - - steps: - - uses: actions/checkout@v2 - - - name: dependencies (macos) - if: ${{ matrix.os == 'macos-latest' }} - run: | - brew install llvm pkg-config - ln -s "/usr/local/opt/llvm/bin/clang-format" "/usr/local/bin/clang-format" - ln -s "/usr/local/opt/llvm/bin/clang-tidy" "/usr/local/bin/clang-tidy" - - - name: Restore cache - uses: actions/cache@v2 - with: - path: | - ${{ env.CMAKE_BUILD_DIR }}/vcpkg_installed - key: ${{ runner.os }}-${{ hashFiles( '**/vcpkg.json' ) }} - - - name: Configure to use clang-tidy and sanitizers - run: | - cmake -B "${{ env.CMAKE_BUILD_DIR }}" -DTESTING=ON -DCLANG_TIDY=ON -DSANITIZERS=ON -DVCPKG_MANIFEST_DIR="${{ matrix.ossl3-vcpkg-dir }}" -DCMAKE_TOOLCHAIN_FILE="${{ matrix.vcpkg-cmake-file}}" . - - - name: Build - run: | - cmake --build "${{ env.CMAKE_BUILD_DIR }}" - - - name: Unit tests - run: | - cmake --build "${{ env.CMAKE_BUILD_DIR }}" --target "${{ matrix.ctest-target}}" diff --git a/.github/workflows/compat.yml b/.github/workflows/compat.yml deleted file mode 100644 index b4525d2c..00000000 --- a/.github/workflows/compat.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Build for older MacOS - -on: - push: - branches: - - main - pull_request: - branches: - - main - -env: - CTEST_OUTPUT_ON_FAILURE: 1 - MACOSX_DEPLOYMENT_TARGET: 10.11 - -jobs: - build: - runs-on: macos-latest - - env: - CMAKE_BUILD_DIR: ${{ github.workspace }}/build - TOOLCHAIN_FILE: $VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake - - steps: - - uses: actions/checkout@v2 - - - name: dependencies - run: | - brew install llvm pkg-config - ln -s "/usr/local/opt/llvm/bin/clang-format" "/usr/local/bin/clang-format" - ln -s "/usr/local/opt/llvm/bin/clang-tidy" "/usr/local/bin/clang-tidy" - - - name: Restore cache - uses: actions/cache@v2 - with: - path: | - ${{ env.CMAKE_BUILD_DIR }}/vcpkg_installed - key: ${{ runner.os }}-${{ hashFiles( '**/vcpkg.json' ) }} - - - name: build the library - run: | - cmake -B "${{ env.CMAKE_BUILD_DIR }}" -DCMAKE_TOOLCHAIN_FILE="${{ env.TOOLCHAIN_FILE }}" . - cmake --build "${{ env.CMAKE_BUILD_DIR }}" --target mlspp diff --git a/.github/workflows/interop.yml b/.github/workflows/interop.yml deleted file mode 100644 index deecc25b..00000000 --- a/.github/workflows/interop.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Test interoperability - -on: - push: - branches: - - main - pull_request: - branches: - - main - -env: - CTEST_OUTPUT_ON_FAILURE: 1 - -jobs: - build: - runs-on: ubuntu-latest - - env: - CMAKE_BUILD_DIR: ${{ github.workspace }}/build - TOOLCHAIN_FILE: $VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake - - steps: - - uses: actions/checkout@v2 - - - name: Restore cache - uses: actions/cache@v2 - with: - path: | - ${{ env.CMAKE_BUILD_DIR }}/vcpkg_installed - key: ${{ runner.os }}-${{ hashFiles( '**/vcpkg.json' ) }} - - - name: Build the library - run: | - cmake -B build -DCMAKE_TOOLCHAIN_FILE="${{ env.TOOLCHAIN_FILE }}" . - cmake --build build - - - name: Build interop harness - run: | - cd cmd/interop - cmake -B build -DSANITIZERS=ON -DCMAKE_TOOLCHAIN_FILE="${{ env.TOOLCHAIN_FILE }}" . - cmake --build build - - - name: Test self-interop - run: | - make -C cmd/interop self-test - - - name: Test interop on test vectors - run: | - make -C cmd/interop interop-test - - - name: Test gRPC live interop with self - run: | - cd cmd/interop - ./grpc-self-test.sh diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml new file mode 100644 index 00000000..8f6c3168 --- /dev/null +++ b/.github/workflows/main_ci.yml @@ -0,0 +1,189 @@ +name: MLSPP CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +env: + CTEST_OUTPUT_ON_FAILURE: 1 + CMAKE_BUILD_DIR: ${{ github.workspace }}/build + CMAKE_BUILD_OPENSSL3_DIR: ${{ github.workspace }}/build_openssl3 + CMAKE_TEST_OPENSSL3_DIR: ${{ github.workspace }}/build_openssl3/test + CMAKE_TEST_DIR: ${{ github.workspace }}/build/test + VCPKG_BINARY_SOURCES: files,${{ github.workspace }}/build/cache,readwrite + +jobs: + formatting-check: + name: Formatting Check + runs-on: ubuntu-latest + strategy: + matrix: + path: + - 'include' + - 'src' + - 'test' + - 'cmd' + - 'lib' + steps: + - uses: actions/checkout@v3 + + - name: Run clang-format style check for C/C++ programs + uses: jidicula/clang-format-action@v4.11.0 + with: + clang-format-version: '16' + check-path: ${{ matrix.path }} + fallback-style: 'Mozilla' + + quick-linux-interop-check: + needs: formatting-check + name: Quick Linux Check and Interop + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Dependencies (Ubuntu) + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + sudo apt-get install -y linux-headers-$(uname -r) + + - name: Restore cache + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/build/cache + key: VCPKG-BinaryCache-${{ runner.os }} + + - name: Build (OpenSSL 1.1) + run: | + cmake -B "${{ env.CMAKE_BUILD_DIR }}" -DTESTING=ON -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" + cmake --build "${{ env.CMAKE_BUILD_DIR }}" --target all --parallel 2 + + - name: Unit Test (OpenSSL 1.1) + run: | + cmake --build "${{ env.CMAKE_BUILD_DIR }}" --target test + + - name: Build (Interop Harness) + run: | + cd cmd/interop + cmake -B build -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" + cmake --build build + + - name: Test self-interop + run: | + make -C cmd/interop self-test + + - name: Test interop on test vectors + run: | + make -C cmd/interop interop-test + + - name: Test gRPC live interop with self + run: | + cd cmd/interop + ./grpc-self-test.sh + + - name: Build (OpenSSL 3) + run: | + cmake -B "${{ env.CMAKE_BUILD_OPENSSL3_DIR }}" -DTESTING=ON -DVCPKG_MANIFEST_DIR="alternatives/openssl_3" -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" + cmake --build "${{ env.CMAKE_BUILD_OPENSSL3_DIR }}" + + - name: Unit Test (OpenSSL 3) + run: | + cmake --build "${{ env.CMAKE_BUILD_OPENSSL3_DIR }}" --target test + + platform-sanitizer-tests: + if: github.event.pull_request.draft == false + needs: quick-linux-interop-check + name: Build and test platforms using sanitizers and clang-tidy + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + include: + - os: windows-latest + vcpkg-cmake-file: "$env:VCPKG_INSTALLATION_ROOT\\scripts\\buildsystems\\vcpkg.cmake" + ossl3-vcpkg-dir: "alternatives\\openssl_3" + ctest-target: RUN_TESTS + - os: ubuntu-latest + vcpkg-cmake-file: "$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" + ossl3-vcpkg-dir: "alternatives/openssl_3" + ctest-target: test + - os: macos-latest + vcpkg-cmake-file: "$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" + ossl3-vcpkg-dir: "alternatives/openssl_3" + ctest-target: test + + steps: + - uses: actions/checkout@v3 + + - name: Dependencies (macOs) + if: ${{ matrix.os == 'macos-latest' }} + run: | + brew install llvm pkg-config + ln -s "/usr/local/opt/llvm/bin/clang-format" "/usr/local/bin/clang-format" + ln -s "/usr/local/opt/llvm/bin/clang-tidy" "/usr/local/bin/clang-tidy" + + - name: Dependencies (Ubuntu) + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + sudo apt-get install -y linux-headers-$(uname -r) + + - name: Restore cache + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/build/cache + key: VCPKG-BinaryCache-${{ runner.os }} + + - name: Build (OpenSSL1.1) + run: | + cmake -B "${{ env.CMAKE_BUILD_DIR }}" -DTESTING=ON -DCLANG_TIDY=ON -DSANITIZERS=ON -DCMAKE_TOOLCHAIN_FILE="${{ matrix.vcpkg-cmake-file}}" + cmake --build "${{ env.CMAKE_BUILD_DIR }}" --parallel 2 + + - name: Unit Test (OpenSSL1.1) + run: | + cmake --build "${{ env.CMAKE_BUILD_DIR }}" --target "${{ matrix.ctest-target}}" + + - name: Build (OpenSSL3) + run: | + cmake -B "${{ env.CMAKE_BUILD_OPENSSL3_DIR }}" -DTESTING=ON -DCLANG_TIDY=ON -DSANITIZERS=ON -DVCPKG_MANIFEST_DIR="${{ matrix.ossl3-vcpkg-dir }}" -DCMAKE_TOOLCHAIN_FILE="${{ matrix.vcpkg-cmake-file}}" + cmake --build "${{ env.CMAKE_BUILD_OPENSSL3_DIR }}" + + - name: Unit Test (OpenSSL3) + run: | + cmake --build "${{ env.CMAKE_BUILD_OPENSSL3_DIR }}" --target "${{ matrix.ctest-target}}" + + old-macos-compatibility: + if: github.event.pull_request.draft == false + needs: quick-linux-interop-check + name: Build for older MacOS + runs-on: macos-latest + + env: + CMAKE_BUILD_DIR: ${{ github.workspace }}/build + TOOLCHAIN_FILE: $VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake + VCPKG_BINARY_SOURCES: files,${{ github.workspace }}/build/cache,readwrite + MACOSX_DEPLOYMENT_TARGET: 10.11 + + steps: + - uses: actions/checkout@v3 + + - name: dependencies + run: | + brew install llvm pkg-config + ln -s "/usr/local/opt/llvm/bin/clang-format" "/usr/local/bin/clang-format" + ln -s "/usr/local/opt/llvm/bin/clang-tidy" "/usr/local/bin/clang-tidy" + + - name: Restore cache + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/build/cache + key: VCPKG-BinaryCache-${{ runner.os }} + + - name: Build + run: | + cmake -B "${{ env.CMAKE_BUILD_DIR }}" -DCMAKE_TOOLCHAIN_FILE="${{ env.TOOLCHAIN_FILE }}" + cmake --build "${{ env.CMAKE_BUILD_DIR }}" --target mlspp --parallel 2 + diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml deleted file mode 100644 index 5a9a40a8..00000000 --- a/.github/workflows/style.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Formatting Check -on: [push] -jobs: - formatting-check: - name: Formatting Check - runs-on: ubuntu-latest - strategy: - matrix: - path: - - 'include' - - 'src' - - 'test' - - 'cmd' - - 'lib' - steps: - - uses: actions/checkout@v3 - - name: Run clang-format style check for C/C++ programs. - uses: jidicula/clang-format-action@v4.11.0 - with: - clang-format-version: '16' - check-path: ${{ matrix.path }} - fallback-style: 'Mozilla' diff --git a/README.md b/README.md index 144f3df8..c3089d27 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![MLSPP CI](https://github.com/cisco/mlspp/actions/workflows/main_ci.yml/badge.svg)](https://github.com/cisco/mlspp/actions/workflows/main_ci.yml) + MLS++ ===== From 949c447c93a067f02f733a8144908abb13f3dab0 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Tue, 5 Sep 2023 15:03:05 -0400 Subject: [PATCH 27/36] UserInfo VC details (#360) * Add UserInfoVC struct * Add methods that deduce signature algorithm from JWK parameters * Add public key parsing * Add VC public-key checking * Checkpoint * Now passing a basic test * Fix errors after merge * Address review comments * clang-format * Re-add 'id' to UserInfo VC test * clang-tidy * clang-format * clang-format * clang-tidy --- .clang-tidy | 1 + include/mls/credential.h | 18 +- lib/hpke/include/hpke/signature.h | 20 +- lib/hpke/include/hpke/userinfo_vc.h | 43 ++++ lib/hpke/src/signature.cpp | 99 ++++++++-- lib/hpke/src/userinfo_vc.cpp | 296 ++++++++++++++++++++++++++++ lib/hpke/test/userinfo_vc.cpp | 132 +++++++++++++ src/credential.cpp | 21 +- 8 files changed, 593 insertions(+), 37 deletions(-) create mode 100644 lib/hpke/include/hpke/userinfo_vc.h create mode 100644 lib/hpke/src/userinfo_vc.cpp create mode 100644 lib/hpke/test/userinfo_vc.cpp diff --git a/.clang-tidy b/.clang-tidy index 36586010..a885be83 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,6 +2,7 @@ Checks: '*, -altera-*, -fuchsia-*, + -abseil-string-find-startswith, -bugprone-easily-swappable-parameters, -bugprone-exception-escape, -cert-err58-cpp, diff --git a/include/mls/credential.h b/include/mls/credential.h index 27e6f042..a8bbd77f 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -48,23 +48,27 @@ struct X509Credential SignatureScheme _signature_scheme; }; +tls::ostream& +operator<<(tls::ostream& str, const X509Credential& obj); + +tls::istream& +operator>>(tls::istream& str, X509Credential& obj); + struct UserInfoVCCredential { UserInfoVCCredential() = default; - explicit UserInfoVCCredential(bytes userinfo_vc_jwt); + explicit UserInfoVCCredential(bytes userinfo_vc_jwt_in); bytes userinfo_vc_jwt; bool valid_for(const SignaturePublicKey& pub) const; TLS_SERIALIZABLE(userinfo_vc_jwt) -}; - -tls::ostream& -operator<<(tls::ostream& str, const X509Credential& obj); -tls::istream& -operator>>(tls::istream& str, X509Credential& obj); +private: + SignaturePublicKey _public_key; + SignatureScheme _signature_scheme; +}; bool operator==(const X509Credential& lhs, const X509Credential& rhs); diff --git a/lib/hpke/include/hpke/signature.h b/lib/hpke/include/hpke/signature.h index 65e67020..311baa18 100644 --- a/lib/hpke/include/hpke/signature.h +++ b/lib/hpke/include/hpke/signature.h @@ -51,10 +51,26 @@ struct Signature virtual std::unique_ptr deserialize_private( const bytes& skm) const = 0; + struct PrivateJWK + { + const Signature& sig; + std::optional key_id; + std::unique_ptr key; + }; + static PrivateJWK parse_jwk_private(const std::string& jwk_json); + + struct PublicJWK + { + const Signature& sig; + std::optional key_id; + std::unique_ptr key; + }; + static PublicJWK parse_jwk(const std::string& jwk_json); + virtual std::unique_ptr import_jwk_private( - const std::string& json_str) const = 0; + const std::string& jwk_json) const = 0; virtual std::unique_ptr import_jwk( - const std::string& json_str) const = 0; + const std::string& jwk_json) const = 0; virtual std::string export_jwk_private(const PrivateKey& env) const = 0; virtual std::string export_jwk(const PublicKey& env) const = 0; diff --git a/lib/hpke/include/hpke/userinfo_vc.h b/lib/hpke/include/hpke/userinfo_vc.h new file mode 100644 index 00000000..908451f5 --- /dev/null +++ b/lib/hpke/include/hpke/userinfo_vc.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include + +#include +#include +#include +#include + +using namespace MLS_NAMESPACE::bytes_ns; + +namespace MLS_NAMESPACE::hpke { + +struct UserInfoVC +{ +private: + struct ParsedCredential; + std::shared_ptr parsed_cred; + +public: + explicit UserInfoVC(std::string jwt); + UserInfoVC() = default; + UserInfoVC(const UserInfoVC& other) = default; + ~UserInfoVC() = default; + UserInfoVC& operator=(const UserInfoVC& other) = default; + UserInfoVC& operator=(UserInfoVC&& other) = default; + + std::string issuer() const; + std::string key_id() const; + std::chrono::system_clock::time_point not_before() const; + std::chrono::system_clock::time_point not_after() const; + std::map subject() const; + const Signature::PublicJWK& public_key() const; + + bool valid_from(const Signature::PublicKey& issuer_key) const; + + std::string raw; +}; + +bool +operator==(const UserInfoVC& lhs, const UserInfoVC& rhs); + +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/signature.cpp b/lib/hpke/src/signature.cpp index 8a6ff093..16cfe117 100644 --- a/lib/hpke/src/signature.cpp +++ b/lib/hpke/src/signature.cpp @@ -111,25 +111,25 @@ struct GroupSignature : public Signature } std::unique_ptr import_jwk_private( - const std::string& json_str) const override + const std::string& jwk_json) const override { - const auto jwk_json = validate_jwk_json(json_str, true); + const auto jwk = validate_jwk_json(jwk_json, true); - const auto d = from_base64url(jwk_json.at("d")); + const auto d = from_base64url(jwk.at("d")); auto gsk = group.deserialize_private(d); return std::make_unique(gsk.release()); } std::unique_ptr import_jwk( - const std::string& json_str) const override + const std::string& jwk_json) const override { - const auto jwk_json = validate_jwk_json(json_str, false); + const auto jwk = validate_jwk_json(jwk_json, false); - const auto x = from_base64url(jwk_json.at("x")); + const auto x = from_base64url(jwk.at("x")); auto y = bytes{}; - if (jwk_json.contains("y")) { - y = from_base64url(jwk_json.at("y")); + if (jwk.contains("y")) { + y = from_base64url(jwk.at("y")); } return group.public_key_from_coordinates(x, y); @@ -160,46 +160,45 @@ struct GroupSignature : public Signature private: const Group& group; - json validate_jwk_json(const std::string& json_str, bool private_key) const + json validate_jwk_json(const std::string& jwk_json, bool private_key) const { - json jwk_json = json::parse(json_str); + json jwk = json::parse(jwk_json); - if (jwk_json.empty() || !jwk_json.contains("kty") || - !jwk_json.contains("crv") || !jwk_json.contains("x") || - (private_key && !jwk_json.contains("d"))) { + if (jwk.empty() || !jwk.contains("kty") || !jwk.contains("crv") || + !jwk.contains("x") || (private_key && !jwk.contains("d"))) { throw std::runtime_error("malformed JWK"); } - if (jwk_json.at("kty") != group.jwk_key_type) { + if (jwk.at("kty") != group.jwk_key_type) { throw std::runtime_error("invalid JWK key type"); } - if (jwk_json.at("crv") != group.jwk_curve_name) { + if (jwk.at("crv") != group.jwk_curve_name) { throw std::runtime_error("invalid JWK curve"); } - return jwk_json; + return jwk; } json export_jwk_json(const Group::PublicKey& pk) const { const auto [x, y] = group.coordinates(pk); - json jwk_json = json::object({ + json jwk = json::object({ { "crv", group.jwk_curve_name }, { "kty", group.jwk_key_type }, }); if (group.jwk_key_type == "EC") { - jwk_json.emplace("x", to_base64url(x)); - jwk_json.emplace("y", to_base64url(y)); + jwk.emplace("x", to_base64url(x)); + jwk.emplace("y", to_base64url(y)); } else if (group.jwk_key_type == "OKP") { - jwk_json.emplace("x", to_base64url(x)); + jwk.emplace("x", to_base64url(x)); } else { throw std::runtime_error("unknown key type"); } - return jwk_json; + return jwk; } }; @@ -278,4 +277,62 @@ Signature::generate_rsa(size_t bits) return RSASignature::generate_key_pair(bits); } +static const Signature& +sig_from_jwk(const std::string& jwk_json) +{ + using KeyTypeAndCurve = std::tuple; + static const auto alg_sig_map = std::map{ + { { "EC", "P-256" }, Signature::get() }, + { { "EC", "P-384" }, Signature::get() }, + { { "EC", "P-512" }, Signature::get() }, + { { "OKP", "Ed25519" }, Signature::get() }, + { { "OKP", "Ed448" }, Signature::get() }, + // TODO(RLB): RSA + }; + + const auto jwk = json::parse(jwk_json); + const auto& kty = jwk.at("kty"); + + auto crv = std::string(""); + if (jwk.contains("crv")) { + crv = jwk.at("crv"); + } + + const auto key = KeyTypeAndCurve{ kty, crv }; + return alg_sig_map.at(key); +} + +Signature::PrivateJWK +Signature::parse_jwk_private(const std::string& jwk_json) +{ + // XXX(RLB): This JSON-parses the JWK twice. I'm assuming that this is a less + // bad cost than changing the import_jwk method signature to take `json`. + const auto& sig = sig_from_jwk(jwk_json); + const auto jwk = json::parse(jwk_json); + auto priv = sig.import_jwk_private(jwk_json); + + auto kid = std::optional{}; + if (jwk.contains("kid")) { + kid = jwk.at("kid").get(); + } + + return { sig, kid, std::move(priv) }; +} + +Signature::PublicJWK +Signature::parse_jwk(const std::string& jwk_json) +{ + // XXX(RLB): Same double-parsing comment as with `parse_jwk_private` + const auto& sig = sig_from_jwk(jwk_json); + const auto jwk = json::parse(jwk_json); + auto pub = sig.import_jwk(jwk_json); + + auto kid = std::optional{}; + if (jwk.contains("kid")) { + kid = jwk.at("kid").get(); + } + + return { sig, kid, std::move(pub) }; +} + } // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/src/userinfo_vc.cpp b/lib/hpke/src/userinfo_vc.cpp new file mode 100644 index 00000000..fcbfb5b8 --- /dev/null +++ b/lib/hpke/src/userinfo_vc.cpp @@ -0,0 +1,296 @@ +#include +#include +#include +#include +#include + +using nlohmann::json; + +namespace MLS_NAMESPACE::hpke { + +/// +/// ParsedCredential +/// +static const Signature& +signature_from_alg(const std::string& alg) +{ + static const auto alg_sig_map = std::map{ + { "ES256", Signature::get() }, + { "ES384", Signature::get() }, + { "ES512", Signature::get() }, + { "Ed25519", Signature::get() }, + { "Ed448", Signature::get() }, + { "RS256", Signature::get() }, + { "RS384", Signature::get() }, + { "RS512", Signature::get() }, + }; + + return alg_sig_map.at(alg); +} + +static std::chrono::system_clock::time_point +epoch_time(int64_t seconds_since_epoch) +{ + const auto delta = std::chrono::seconds(seconds_since_epoch); + return std::chrono::system_clock::time_point(delta); +} + +static bool +is_ecdsa(const Signature& sig) +{ + return sig.id == Signature::ID::P256_SHA256 || + sig.id == Signature::ID::P384_SHA384 || + sig.id == Signature::ID::P521_SHA512; +} + +// OpenSSL expects ECDSA signatures to be in DER form. JWS provides the +// signature in raw R||S form. So we need to do some manual DER encoding. +static bytes +jws_to_der_sig(const bytes& jws_sig) +{ + // Inputs that are too large will result in invalid DER encodings with this + // code. At this size, the combination of the DER integer headers and the + // integer data will overflow the one-byte DER struct length. + static const auto max_sig_size = size_t(250); + if (jws_sig.size() > max_sig_size) { + throw std::runtime_error("JWS signature too large"); + } + + if (jws_sig.size() % 2 != 0) { + throw std::runtime_error("Malformed JWS signature"); + } + + const auto int_size = jws_sig.size() / 2; + const auto jws_sig_cut = + jws_sig.begin() + static_cast(int_size); + + // Compute the encoded size of R and S integer data, adding a zero byte if + // needed to clear the sign bit + const auto r_big = (jws_sig.at(0) >= 0x80); + const auto s_big = (jws_sig.at(int_size) >= 0x80); + + const auto r_size = int_size + (r_big ? 1 : 0); + const auto s_size = int_size + (s_big ? 1 : 0); + + // Compute the size of the DER-encoded signature + static const auto int_header_size = 2; + const auto r_int_size = int_header_size + r_size; + const auto s_int_size = int_header_size + s_size; + + const auto content_size = r_int_size + s_int_size; + const auto content_big = (content_size > 0x80); + + auto der_header_size = 2 + (content_big ? 1 : 0); + const auto der_size = der_header_size + content_size; + + // Allocate the DER buffer + auto der = bytes(der_size, 0); + + // Write the header + der.at(0) = 0x30; + if (content_big) { + der.at(1) = 0x81; + der.at(2) = static_cast(content_size); + } else { + der.at(1) = static_cast(content_size); + } + + // Write R, virtually padding with a zero byte if needed + const auto r_start = der_header_size; + const auto r_data_start = r_start + int_header_size + (r_big ? 1 : 0); + const auto r_data_begin = + der.begin() + static_cast(r_data_start); + + der.at(r_start) = 0x02; + der.at(r_start + 1) = static_cast(r_size); + std::copy(jws_sig.begin(), jws_sig_cut, r_data_begin); + + // Write S, virtually padding with a zero byte if needed + const auto s_start = der_header_size + r_int_size; + const auto s_data_start = s_start + int_header_size + (s_big ? 1 : 0); + const auto s_data_begin = + der.begin() + static_cast(s_data_start); + + der.at(s_start) = 0x02; + der.at(s_start + 1) = static_cast(s_size); + std::copy(jws_sig_cut, jws_sig.end(), s_data_begin); + + return der; +} + +struct UserInfoVC::ParsedCredential +{ + // Header fields + const Signature& signature_algorithm; // `alg` + std::string key_id; // `kid` + + // Top-level Payload fields + std::string issuer; // `iss` + std::chrono::system_clock::time_point not_before; // `nbf` + std::chrono::system_clock::time_point not_after; // `exp` + + // Credential subject fields + std::map credential_subject; + Signature::PublicJWK public_key; + + // Signature verification information + bytes to_be_signed; + bytes signature; + + ParsedCredential(const Signature& signature_algorithm_in, + std::string key_id_in, + std::string issuer_in, + std::chrono::system_clock::time_point not_before_in, + std::chrono::system_clock::time_point not_after_in, + std::map credential_subject_in, + Signature::PublicJWK&& public_key_in, + bytes to_be_signed_in, + bytes signature_in) + : signature_algorithm(signature_algorithm_in) + , key_id(std::move(key_id_in)) + , issuer(std::move(issuer_in)) + , not_before(not_before_in) + , not_after(not_after_in) + , credential_subject(std::move(credential_subject_in)) + , public_key(std::move(public_key_in)) + , to_be_signed(std::move(to_be_signed_in)) + , signature(std::move(signature_in)) + { + } + + static std::shared_ptr parse(const std::string& jwt) + { + // Split the JWT into its header, payload, and signature + const auto first_dot = jwt.find_first_of('.'); + const auto last_dot = jwt.find_last_of('.'); + if (first_dot == std::string::npos || last_dot == std::string::npos || + first_dot == last_dot || last_dot > jwt.length() - 2) { + throw std::runtime_error("malformed JWT; not enough '.' characters"); + } + + const auto header_b64 = jwt.substr(0, first_dot); + const auto payload_b64 = + jwt.substr(first_dot + 1, last_dot - first_dot - 1); + const auto signature_b64 = jwt.substr(last_dot + 1); + + // Parse the components + const auto header = json::parse(from_base64url(header_b64)); + const auto payload = json::parse(from_base64url(payload_b64)); + + // Prepare the validation inputs + const auto& sig = signature_from_alg(header.at("alg")); + const auto to_be_signed = from_ascii(header_b64 + "." + payload_b64); + auto signature = from_base64url(signature_b64); + if (is_ecdsa(sig)) { + signature = jws_to_der_sig(signature); + } + + // Verify the VC parts + const auto& vc = payload.at("vc"); + + static const auto context = + std::vector{ { "https://www.w3.org/2018/credentials/v1" } }; + const auto vc_context = vc.at("@context").get>(); + if (vc_context != context) { + throw std::runtime_error("malformed VC: incorrect context value"); + } + + static const auto type = std::vector{ + "VerifiableCredential", + "UserInfoCredential", + }; + if (vc.at("type") != type) { + throw std::runtime_error("malformed VC: incorrect type value"); + } + + // Parse the subject public key + static const std::string did_jwk_prefix = "did:jwk:"; + const auto id = vc.at("credentialSubject").at("id").get(); + if (id.find(did_jwk_prefix) != 0) { + throw std::runtime_error("malformed UserInfo VC: ID is not did:jwk"); + } + + const auto jwk = to_ascii(from_base64url(id.substr(did_jwk_prefix.size()))); + auto public_key = Signature::parse_jwk(jwk); + + // Extract the salient parts + return std::make_shared( + sig, + header.at("kid"), + + payload.at("iss"), + epoch_time(payload.at("nbf").get()), + epoch_time(payload.at("exp").get()), + + vc.at("credentialSubject"), + std::move(public_key), + + to_be_signed, + signature); + } + + bool verify(const Signature::PublicKey& issuer_key) + { + return signature_algorithm.verify(to_be_signed, signature, issuer_key); + } +}; + +/// +/// UserInfoVC +/// + +UserInfoVC::UserInfoVC(std::string jwt) + : parsed_cred(ParsedCredential::parse(jwt)) + , raw(std::move(jwt)) +{ +} + +std::string +UserInfoVC::issuer() const +{ + return parsed_cred->issuer; +} + +std::string +UserInfoVC::key_id() const +{ + return parsed_cred->key_id; +} + +bool +UserInfoVC::valid_from(const Signature::PublicKey& issuer_key) const +{ + return parsed_cred->verify(issuer_key); +} + +std::map +UserInfoVC::subject() const +{ + return parsed_cred->credential_subject; +} + +std::chrono::system_clock::time_point +UserInfoVC::not_before() const +{ + return parsed_cred->not_before; +} + +std::chrono::system_clock::time_point +UserInfoVC::not_after() const +{ + return parsed_cred->not_after; +} + +const Signature::PublicJWK& +UserInfoVC::public_key() const +{ + return parsed_cred->public_key; +} + +bool +operator==(const UserInfoVC& lhs, const UserInfoVC& rhs) +{ + return lhs.raw == rhs.raw; +} + +} // namespace MLS_NAMESPACE::hpke diff --git a/lib/hpke/test/userinfo_vc.cpp b/lib/hpke/test/userinfo_vc.cpp new file mode 100644 index 00000000..cdde3322 --- /dev/null +++ b/lib/hpke/test/userinfo_vc.cpp @@ -0,0 +1,132 @@ +#include +#include + +#include +namespace opt = MLS_NAMESPACE::tls::opt; + +using namespace MLS_NAMESPACE::hpke; +using namespace std::string_literals; + +static bool +operator==(const Signature::PublicJWK& lhs, const Signature::PublicJWK& rhs) +{ + const auto sig = (&lhs.sig == &rhs.sig); + const auto kid = ((!lhs.key_id && !rhs.key_id) || lhs.key_id == rhs.key_id); + + const auto pkL = lhs.sig.serialize(*lhs.key); + const auto pkR = rhs.sig.serialize(*rhs.key); + const auto key = (pkL == pkR); + + return sig && kid && key; +} + +TEST_CASE("UserInfoVC Parsing and Validation") +{ + // Parsed contents: + // + // Protected header: + // { + // "alg": "ES256", + // "typ": "JWT", + // "kid": "gyAKXvQA8X-m9JxDBgv9rULPxlU7fjB9O7D_gmIrDXs" + // } + // + // Payload: + // { + // "vc": { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "type": [ + // "VerifiableCredential", + // "UserInfoCredential" + // ], + // "credentialSubject": { + // "id": + // "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6InAxOXJZemVDYnZ5" + // "VHpyWGtqTGIyVkRGYllEc20yVFpxSURselQyQnEzQUEiLCJ5IjoiVVVnRmdwWjZ3" + // "WndHZkstWE4tVWtJSlVnTHlwZ3o2MW5xVWY4M1Nza2poRSJ9", + // "sub": "248289761001", + // "name": "Jane Doe", + // "given_name": "Jane", + // "family_name": "Doe", + // "preferred_username": "j.doe", + // "email": "janedoe@example.com", + // "picture": "http://example.com/janedoe/me.jpg" + // } + // }, + // "nbf": 1693420220, + // "exp": 1694025020, + // "iss": "https://localhost:3000", + // "aud": "client_id", + // "iat": 1693506620 + // } + const auto userinfo_vc_raw = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imd5QUtYdlFBOFgtbTlK" + "eERCZ3Y5clVMUHhsVTdmakI5TzdEX2dtSXJEWHMifQ" + "." + "eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVk" + "ZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVXNl" + "ckluZm9DcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlk" + "Omp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2llQ0k2SW5B" + "eE9YSlplbVZEWW5aNVZIcHlXR3RxVEdJeVZrUkdZbGxFYzIweVZGcHhTVVJzZWxR" + "eVFuRXpRVUVpTENKNUlqb2lWVlZuUm1kd1dqWjNXbmRIWmtzdFdFNHRWV3RKU2xW" + "blRIbHdaM28yTVc1eFZXWTRNMU56YTJwb1JTSjkiLCJzdWIiOiIyNDgyODk3NjEw" + "MDEiLCJuYW1lIjoiSmFuZSBEb2UiLCJnaXZlbl9uYW1lIjoiSmFuZSIsImZhbWls" + "eV9uYW1lIjoiRG9lIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiai5kb2UiLCJlbWFp" + "bCI6ImphbmVkb2VAZXhhbXBsZS5jb20iLCJwaWN0dXJlIjoiaHR0cDovL2V4YW1w" + "bGUuY29tL2phbmVkb2UvbWUuanBnIn19LCJuYmYiOjE2OTM0MjAyMjAsImV4cCI6" + "MTY5NDAyNTAyMCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6MzAwMCIsImF1ZCI6" + "ImNsaWVudF9pZCIsImlhdCI6MTY5MzUwNjYyMH0" + "." + "lSU3pbjPCcBpQID6w1WeAYO_ZyYRDZ3rsJiPD1uWPOILWzeUIHTwjjyqaL9sko9k" + "FV0Xch-16qwdOlpTgzaHrw"s; + + const auto known_issuer = "https://localhost:3000"s; + const auto known_key_id = "gyAKXvQA8X-m9JxDBgv9rULPxlU7fjB9O7D_gmIrDXs"s; + const auto known_not_before = std::chrono::seconds(1693420220); + const auto known_not_after = std::chrono::seconds(1694025020); + const auto known_subject = std::map{ + { { "id", + "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6InAxOXJZemVDYnZ5" + "VHpyWGtqTGIyVkRGYllEc20yVFpxSURselQyQnEzQUEiLCJ5IjoiVVVnRmdwWjZ3" + "WndHZkstWE4tVWtJSlVnTHlwZ3o2MW5xVWY4M1Nza2poRSJ9" }, + { "sub", "248289761001" }, + { "name", "Jane Doe" }, + { "given_name", "Jane" }, + { "family_name", "Doe" }, + { "preferred_username", "j.doe" }, + { "email", "janedoe@example.com" }, + { "picture", "http://example.com/janedoe/me.jpg" } } + }; + const auto known_subject_jwk_raw = R"({ + "kty": "EC", + "crv": "P-256", + "x": "p19rYzeCbvyTzrXkjLb2VDFbYDsm2TZqIDlzT2Bq3AA", + "y": "UUgFgpZ6wZwGfK-XN-UkIJUgLypgz61nqUf83SskjhE" + })"s; + + const auto issuer_jwk_raw = R"({ + "kty": "EC", + "use": "sig", + "kid": "gyAKXvQA8X-m9JxDBgv9rULPxlU7fjB9O7D_gmIrDXs", + "alg": "ES256", + "crv": "P-256", + "x": "2-MG_vi7KtZNzbwrbT2JX4kJTw7iJcnVXj7ucBZHUCg", + "y":"ZwQq_CgT-1vfeE77uoWGM9Pm-8DyH7p-SIi1RKHEB8E" + })"s; + + const auto vc = UserInfoVC(userinfo_vc_raw); + const auto known_subject_jwk = Signature::parse_jwk(known_subject_jwk_raw); + const auto issuer_jwk = Signature::parse_jwk(issuer_jwk_raw); + + CHECK(vc.valid_from(*issuer_jwk.key)); + + CHECK(vc.issuer() == known_issuer); + CHECK(vc.key_id() == known_key_id); + CHECK(vc.key_id() == opt::get(issuer_jwk.key_id)); + CHECK(vc.not_before().time_since_epoch() == known_not_before); + CHECK(vc.not_after().time_since_epoch() == known_not_after); + CHECK(vc.subject() == known_subject); + CHECK(vc.public_key() == known_subject_jwk); +} diff --git a/src/credential.cpp b/src/credential.cpp index 16294ab0..730562b9 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -1,5 +1,6 @@ -#include "mls/credential.h" -#include "hpke/certificate.h" +#include +#include +#include #include #include @@ -11,6 +12,7 @@ namespace MLS_NAMESPACE { using MLS_NAMESPACE::hpke::Certificate; // NOLINT(misc-unused-using-decls) using MLS_NAMESPACE::hpke::Signature; // NOLINT(misc-unused-using-decls) +using MLS_NAMESPACE::hpke::UserInfoVC; // NOLINT(misc-unused-using-decls) static const Signature& find_signature(Signature::ID id) @@ -113,17 +115,22 @@ operator==(const X509Credential& lhs, const X509Credential& rhs) /// /// UserInfoVCCredential /// -UserInfoVCCredential::UserInfoVCCredential(bytes userinfo_vc_jwt) - : userinfo_vc_jwt(std::move(userinfo_vc_jwt)) +UserInfoVCCredential::UserInfoVCCredential(bytes userinfo_vc_jwt_in) + : userinfo_vc_jwt(std::move(userinfo_vc_jwt_in)) { + const auto vc = UserInfoVC(to_ascii(userinfo_vc_jwt)); + + const auto& pub = vc.public_key(); + const auto pub_data = pub.sig.serialize(*pub.key); + _signature_scheme = tls_signature_scheme(pub.sig.id); + _public_key = SignaturePublicKey{ pub_data }; } bool // NOLINTNEXTLINE(readability-convert-member-functions-to-static) -UserInfoVCCredential::valid_for(const SignaturePublicKey& /* pub */) const +UserInfoVCCredential::valid_for(const SignaturePublicKey& pub) const { - // TODO(RLB) Extract payload -> did:jwk, compare - throw NotImplementedError(); + return pub == _public_key; } /// From 572be18750eb1e11b728a0cb654f7f8b66620c30 Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Fri, 8 Sep 2023 14:39:38 -0500 Subject: [PATCH 28/36] fixes for recent integration (#364) --- include/mls/messages.h | 2 ++ lib/bytes/include/bytes/bytes.h | 2 ++ src/messages.cpp | 13 +++++++++++++ 3 files changed, 17 insertions(+) diff --git a/include/mls/messages.h b/include/mls/messages.h index 54477cbf..c7368688 100644 --- a/include/mls/messages.h +++ b/include/mls/messages.h @@ -592,6 +592,8 @@ struct PublicMessage friend tls::ostream& operator<<(tls::ostream& str, const PublicMessage& obj); friend tls::istream& operator>>(tls::istream& str, PublicMessage& obj); + friend bool operator==(const PublicMessage& lhs, const PublicMessage& rhs); + friend bool operator!=(const PublicMessage& lhs, const PublicMessage& rhs); private: GroupContent content; diff --git a/lib/bytes/include/bytes/bytes.h b/lib/bytes/include/bytes/bytes.h index 4789bdbf..582701a2 100644 --- a/lib/bytes/include/bytes/bytes.h +++ b/lib/bytes/include/bytes/bytes.h @@ -79,6 +79,8 @@ struct bytes auto& at(size_t pos) { return _data.at(pos); } void resize(size_t count) { _data.resize(count); } + void reserve(size_t len) { _data.reserve(len); } + void push_back(uint8_t byte) { _data.push_back(byte); } // Equality operators bool operator==(const bytes& other) const; diff --git a/src/messages.cpp b/src/messages.cpp index 7510fb16..a7090e8d 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -630,6 +630,19 @@ operator>>(tls::istream& str, PublicMessage& obj) return str; } +bool +operator==(const PublicMessage& lhs, const PublicMessage& rhs) +{ + return lhs.content == rhs.content && lhs.auth == rhs.auth && + lhs.membership_tag == rhs.membership_tag; +} + +bool +operator!=(const PublicMessage& lhs, const PublicMessage& rhs) +{ + return !(lhs == rhs); +} + static bytes marshal_ciphertext_content(const GroupContent& content, const GroupContentAuthData& auth, From a684f2b31ef350d1b230b68d0718b26e12ab1517 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sat, 9 Sep 2023 11:54:36 -0400 Subject: [PATCH 29/36] More integration fixes (#365) * Properly handle one-member trees without the one member in leaf 0 * Move uniqueness checking to tree operations * Rename WireFormat enum values to match RFC * clang-format * clang-tidy --------- Co-authored-by: Richard Barnes --- include/mls/messages.h | 12 ++--- include/mls/treekem.h | 40 ++++++++++++++++ lib/mls_vectors/src/mls_vectors.cpp | 41 ++++++++++------ src/messages.cpp | 24 +++++----- src/session.cpp | 4 +- src/state.cpp | 58 ++++++++--------------- src/treekem.cpp | 72 +++++++++++++++++++++++------ test/messages.cpp | 31 +++++++++---- test/state.cpp | 54 ++++++++++++++++++++++ test/treekem.cpp | 21 +++++---- 10 files changed, 254 insertions(+), 103 deletions(-) diff --git a/include/mls/messages.h b/include/mls/messages.h index c7368688..a660aeb0 100644 --- a/include/mls/messages.h +++ b/include/mls/messages.h @@ -419,8 +419,8 @@ struct GroupContext; enum struct WireFormat : uint16_t { reserved = 0, - mls_plaintext = 1, - mls_ciphertext = 2, + mls_public_message = 1, + mls_private_message = 2, mls_welcome = 3, mls_group_info = 4, mls_key_package = 5, @@ -653,8 +653,8 @@ struct MLSMessage WireFormat wire_format() const; MLSMessage() = default; - MLSMessage(PublicMessage mls_plaintext); - MLSMessage(PrivateMessage mls_ciphertext); + MLSMessage(PublicMessage public_message); + MLSMessage(PrivateMessage private_message); MLSMessage(Welcome welcome); MLSMessage(GroupInfo group_info); MLSMessage(KeyPackage key_package); @@ -718,10 +718,10 @@ TLS_VARIANT_MAP(MLS_NAMESPACE::SenderType, TLS_VARIANT_MAP(MLS_NAMESPACE::WireFormat, MLS_NAMESPACE::PublicMessage, - mls_plaintext) + mls_public_message) TLS_VARIANT_MAP(MLS_NAMESPACE::WireFormat, MLS_NAMESPACE::PrivateMessage, - mls_ciphertext) + mls_private_message) TLS_VARIANT_MAP(MLS_NAMESPACE::WireFormat, MLS_NAMESPACE::Welcome, mls_welcome) TLS_VARIANT_MAP(MLS_NAMESPACE::WireFormat, MLS_NAMESPACE::GroupInfo, diff --git a/include/mls/treekem.h b/include/mls/treekem.h index d58f3c7a..75a9550f 100644 --- a/include/mls/treekem.h +++ b/include/mls/treekem.h @@ -123,6 +123,7 @@ struct TreeKEMPublicKey TreeKEMPublicKey& operator=(const TreeKEMPublicKey& other) = default; TreeKEMPublicKey& operator=(TreeKEMPublicKey&& other) = default; + LeafIndex allocate_leaf(); LeafIndex add_leaf(const LeafNode& leaf); void update_leaf(LeafIndex index, const LeafNode& leaf); void blank_path(LeafIndex index); @@ -149,6 +150,40 @@ struct TreeKEMPublicKey std::optional leaf_node(LeafIndex index) const; std::vector resolve(NodeIndex index) const; + template + bool all_leaves(const UnaryPredicate& pred) const + { + for (LeafIndex i{ 0 }; i < size; i.val++) { + const auto& node = node_at(i); + if (node.blank()) { + continue; + } + + if (!pred(i, node.leaf_node())) { + return false; + } + } + + return true; + } + + template + bool any_leaf(const UnaryPredicate& pred) const + { + for (LeafIndex i{ 0 }; i < size; i.val++) { + const auto& node = node_at(i); + if (node.blank()) { + continue; + } + + if (pred(i, node.leaf_node())) { + return true; + } + } + + return false; + } + using FilteredDirectPath = std::vector>>; FilteredDirectPath filtered_direct_path(NodeIndex index) const; @@ -188,6 +223,11 @@ struct TreeKEMPublicKey NodeIndex parent, NodeIndex sibling) const; + bool exists_in_tree(const HPKEPublicKey& key, + std::optional except) const; + bool exists_in_tree(const SignaturePublicKey& key, + std::optional except) const; + OptionalNode blank_node; friend struct TreeKEMPrivateKey; diff --git a/lib/mls_vectors/src/mls_vectors.cpp b/lib/mls_vectors/src/mls_vectors.cpp index 64936b37..11bd8c8d 100644 --- a/lib/mls_vectors/src/mls_vectors.cpp +++ b/lib/mls_vectors/src/mls_vectors.cpp @@ -828,7 +828,7 @@ MessageProtectionTestVector::protect_pub( auto content = GroupContent{ group_id, epoch, sender, authenticated_data, raw_content }; - auto auth_content = AuthenticatedContent::sign(WireFormat::mls_plaintext, + auto auth_content = AuthenticatedContent::sign(WireFormat::mls_public_message, content, cipher_suite, signature_priv, @@ -853,11 +853,12 @@ MessageProtectionTestVector::protect_priv( auto content = GroupContent{ group_id, epoch, sender, authenticated_data, raw_content }; - auto auth_content = AuthenticatedContent::sign(WireFormat::mls_ciphertext, - content, - cipher_suite, - signature_priv, - group_context()); + auto auth_content = + AuthenticatedContent::sign(WireFormat::mls_private_message, + content, + cipher_suite, + signature_priv, + group_context()); if (content.content_type() == ContentType::commit) { auto confirmation_tag = prg.secret("confirmation_tag"); auth_content.set_confirmation_tag(confirmation_tag); @@ -973,7 +974,7 @@ TranscriptTestVector::TranscriptTestVector(CipherSuite suite) auto leaf_index = LeafIndex{ 0 }; authenticated_content = AuthenticatedContent::sign( - WireFormat::mls_plaintext, + WireFormat::mls_public_message, GroupContent{ group_id, epoch, { MemberSender{ leaf_index } }, {}, Commit{} }, suite, @@ -1812,11 +1813,15 @@ MessagesTestVector::MessagesTestVector() auto version = ProtocolVersion::mls10; auto hpke_priv = prg.hpke_key("hpke_priv"); + auto hpke_priv_2 = prg.hpke_key("hpke_priv_2"); auto hpke_pub = hpke_priv.public_key; + auto hpke_pub_2 = hpke_priv_2.public_key; auto hpke_ct = HPKECiphertext{ prg.secret("kem_output"), prg.secret("ciphertext") }; auto sig_priv = prg.signature_key("signature_priv"); + auto sig_priv_2 = prg.signature_key("signature_priv_2"); auto sig_pub = sig_priv.public_key; + auto sig_pub_2 = sig_priv_2.public_key; // KeyPackage and extensions auto cred = Credential::basic(user_id); @@ -1828,6 +1833,14 @@ MessagesTestVector::MessagesTestVector() Lifetime::create_default(), ext_list, sig_priv }; + auto leaf_node_2 = LeafNode{ suite, + hpke_pub_2, + sig_pub_2, + cred, + Capabilities::create_default(), + Lifetime::create_default(), + ext_list, + sig_priv_2 }; auto key_package_obj = KeyPackage{ suite, hpke_pub, leaf_node, {}, sig_priv }; auto leaf_node_update = @@ -1839,7 +1852,7 @@ MessagesTestVector::MessagesTestVector() auto tree = TreeKEMPublicKey{ suite }; tree.add_leaf(leaf_node); - tree.add_leaf(leaf_node); + tree.add_leaf(leaf_node_2); auto ratchet_tree_obj = RatchetTreeExtension{ tree }; // Welcome and its substituents @@ -1886,7 +1899,7 @@ MessagesTestVector::MessagesTestVector() auto membership_key = prg.secret("membership_key"); auto content_auth_proposal = AuthenticatedContent::sign( - WireFormat::mls_plaintext, + WireFormat::mls_public_message, { group_id, epoch, sender, {}, Proposal{ remove } }, suite, sig_priv, @@ -1895,7 +1908,7 @@ MessagesTestVector::MessagesTestVector() content_auth_proposal, suite, membership_key, group_context); auto content_auth_commit = - AuthenticatedContent::sign(WireFormat::mls_plaintext, + AuthenticatedContent::sign(WireFormat::mls_public_message, { group_id, epoch, sender, {}, commit_obj }, suite, sig_priv, @@ -1906,7 +1919,7 @@ MessagesTestVector::MessagesTestVector() // PrivateMessage auto content_auth_application_obj = AuthenticatedContent::sign( - WireFormat::mls_ciphertext, + WireFormat::mls_private_message, { group_id, epoch, sender, {}, ApplicationData{} }, suite, sig_priv, @@ -1982,15 +1995,15 @@ MessagesTestVector::verify() const VERIFY_TLS_RTT_VAL("Public(Proposal)", MLSMessage, public_message_proposal, - require_format(WireFormat::mls_plaintext)); + require_format(WireFormat::mls_public_message)); VERIFY_TLS_RTT_VAL("Public(Commit)", MLSMessage, public_message_commit, - require_format(WireFormat::mls_plaintext)); + require_format(WireFormat::mls_public_message)); VERIFY_TLS_RTT_VAL("PrivateMessage", MLSMessage, private_message, - require_format(WireFormat::mls_ciphertext)); + require_format(WireFormat::mls_private_message)); return std::nullopt; } diff --git a/src/messages.cpp b/src/messages.cpp index a7090e8d..8daa652d 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -351,7 +351,7 @@ AuthenticatedContent::sign(WireFormat wire_format, const SignaturePrivateKey& sig_priv, const std::optional& context) { - if (wire_format == WireFormat::mls_plaintext && + if (wire_format == WireFormat::mls_public_message && content.content_type() == ContentType::application) { throw InvalidParameterError( "Application data cannot be sent as PublicMessage"); @@ -369,7 +369,7 @@ AuthenticatedContent::verify(CipherSuite suite, const SignaturePublicKey& sig_pub, const std::optional& context) const { - if (wire_format == WireFormat::mls_plaintext && + if (wire_format == WireFormat::mls_public_message && content.content_type() == ContentType::application) { return false; } @@ -545,7 +545,7 @@ PublicMessage::unprotect(CipherSuite suite, } return AuthenticatedContent{ - WireFormat::mls_plaintext, + WireFormat::mls_public_message, content, auth, }; @@ -561,7 +561,7 @@ AuthenticatedContent PublicMessage::authenticated_content() const { auto auth_content = AuthenticatedContent{}; - auth_content.wire_format = WireFormat::mls_plaintext; + auth_content.wire_format = WireFormat::mls_public_message; auth_content.content = content; auth_content.auth = auth; return auth_content; @@ -571,7 +571,7 @@ PublicMessage::PublicMessage(AuthenticatedContent content_auth) : content(std::move(content_auth.content)) , auth(std::move(content_auth.auth)) { - if (content_auth.wire_format != WireFormat::mls_plaintext) { + if (content_auth.wire_format != WireFormat::mls_public_message) { throw InvalidParameterError("Wire format mismatch (not mls_plaintext)"); } } @@ -590,7 +590,7 @@ PublicMessage::membership_mac(CipherSuite suite, const std::optional& context) const { auto tbm = tls::marshal(GroupContentTBM{ - { WireFormat::mls_plaintext, content, context }, + { WireFormat::mls_public_message, content, context }, auth, }); @@ -813,7 +813,7 @@ PrivateMessage::unprotect(CipherSuite suite, unmarshal_ciphertext_content(opt::get(content_pt), content, auth); return AuthenticatedContent{ - WireFormat::mls_ciphertext, + WireFormat::mls_private_message, std::move(content), std::move(auth), }; @@ -851,13 +851,13 @@ MLSMessage::wire_format() const return tls::variant::type(message); } -MLSMessage::MLSMessage(PublicMessage mls_plaintext) - : message(std::move(mls_plaintext)) +MLSMessage::MLSMessage(PublicMessage public_message) + : message(std::move(public_message)) { } -MLSMessage::MLSMessage(PrivateMessage mls_ciphertext) - : message(std::move(mls_ciphertext)) +MLSMessage::MLSMessage(PrivateMessage private_message) + : message(std::move(private_message)) { } @@ -906,7 +906,7 @@ external_proposal(CipherSuite suite, { /* no authenticated data */ }, { proposal } }; auto content_auth = AuthenticatedContent::sign( - WireFormat::mls_plaintext, std::move(content), suite, sig_priv, {}); + WireFormat::mls_public_message, std::move(content), suite, sig_priv, {}); return PublicMessage::protect(std::move(content_auth), suite, {}, {}); } diff --git a/src/session.cpp b/src/session.cpp index 6d42cdce..e1b615cb 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -200,14 +200,14 @@ Session::Inner::import_handshake(const bytes& encoded) const auto msg = tls::get(encoded); switch (msg.wire_format()) { - case WireFormat::mls_plaintext: + case WireFormat::mls_public_message: if (encrypt_handshake) { throw ProtocolError("Handshake not encrypted as required"); } return msg; - case WireFormat::mls_ciphertext: { + case WireFormat::mls_private_message: { if (!encrypt_handshake) { throw ProtocolError("Unexpected handshake encryption"); } diff --git a/src/state.cpp b/src/state.cpp index c786f2d9..9a01eea1 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -269,7 +269,7 @@ State::new_member_add(const bytes& group_id, { /* no authenticated data */ }, { std::move(proposal) } }; auto content_auth = AuthenticatedContent::sign( - WireFormat::mls_plaintext, std::move(content), suite, sig_priv, {}); + WireFormat::mls_public_message, std::move(content), suite, sig_priv, {}); return PublicMessage::protect(std::move(content_auth), suite, {}, {}); } @@ -299,8 +299,8 @@ State::sign(const Sender& sender, _group_id, _epoch, sender, authenticated_data, { inner_content } }; - auto wire_format = - (encrypt) ? WireFormat::mls_ciphertext : WireFormat::mls_plaintext; + auto wire_format = (encrypt) ? WireFormat::mls_private_message + : WireFormat::mls_public_message; auto content_auth = AuthenticatedContent::sign( wire_format, std::move(content), _suite, _identity_priv, group_context()); @@ -312,13 +312,13 @@ MLSMessage State::protect(AuthenticatedContent&& content_auth, size_t padding_size) { switch (content_auth.wire_format) { - case WireFormat::mls_plaintext: + case WireFormat::mls_public_message: return PublicMessage::protect(std::move(content_auth), _suite, _key_schedule.membership_key, group_context()); - case WireFormat::mls_ciphertext: + case WireFormat::mls_private_message: return PrivateMessage::protect(std::move(content_auth), _suite, _keys, @@ -813,9 +813,8 @@ State::handle(const AuthenticatedContent& content_auth, if (!external_commit) { sender_location = opt::get(sender); } else { - // Add the joiner - const auto& path = opt::get(commit.path); - sender_location = next._tree.add_leaf(path.leaf_node); + // Find where the joiner will be added + sender_location = next._tree.allocate_leaf(); // Extract the forced init secret auto kem_output = commit.valid_external(); @@ -1361,7 +1360,7 @@ State::unprotect(const MLSMessage& ct) throw ProtocolError("Unprotect of handshake message"); } - if (content_auth.wire_format != WireFormat::mls_ciphertext) { + if (content_auth.wire_format != WireFormat::mls_private_message) { throw ProtocolError("Application data not sent as PrivateMessage"); } @@ -1417,44 +1416,27 @@ State::valid(const LeafNode& leaf_node, // credential types currently in use by other members. // // Verify that the following fields are unique among the members of the group: - // signature_key - // encryption_key - const auto& signature_key = leaf_node.signature_key; - const auto& encryption_key = leaf_node.encryption_key; - auto unique_signature_key = true; - auto unique_encryption_key = true; - auto mutual_credential_support = true; - for (auto i = LeafIndex{ 0 }; i < _tree.size; i.val++) { - const auto maybe_leaf = _tree.leaf_node(i); - if (!maybe_leaf) { - continue; - } - - const auto& leaf = opt::get(maybe_leaf); - - // Signature keys are allowed to repeat within a leaf - unique_signature_key = - unique_signature_key && - ((i == index) || (signature_key != leaf.signature_key)); - unique_encryption_key = - unique_encryption_key && (encryption_key != leaf.encryption_key); - mutual_credential_support = - mutual_credential_support && - leaf.capabilities.credential_supported(leaf_node.credential) && - leaf_node.capabilities.credential_supported(leaf.credential); - } + // signature_key + // encryption_key + // + // Note: Uniqueness of signature and encryption keys is assured by the + // tree operations (add/update), so we do not need to verify those here. + const auto mutual_credential_support = + _tree.all_leaves([&](auto /* i */, const auto& leaf) { + return leaf.capabilities.credential_supported(leaf_node.credential) && + leaf_node.capabilities.credential_supported(leaf.credential); + }); // Verify that the extensions in the LeafNode are supported by checking that // the ID for each extension in the extensions field is listed in the // capabilities.extensions field of the LeafNode. - auto all_extensions_supported = + auto supports_own_extensions = stdx::all_of(leaf_node.extensions.extensions, [&](const auto& ext) { return stdx::contains(leaf_node.capabilities.extensions, ext.type); }); return (signature_valid && supports_group_extensions && correct_source && - mutual_credential_support && unique_signature_key && - unique_encryption_key && all_extensions_supported); + mutual_credential_support && supports_own_extensions); } bool diff --git a/src/treekem.cpp b/src/treekem.cpp index 176dc856..c8c56688 100644 --- a/src/treekem.cpp +++ b/src/treekem.cpp @@ -382,7 +382,7 @@ TreeKEMPublicKey::TreeKEMPublicKey(CipherSuite suite_in) } LeafIndex -TreeKEMPublicKey::add_leaf(const LeafNode& leaf) +TreeKEMPublicKey::allocate_leaf() { // Find the leftmost blank leaf node auto index = LeafIndex(0); @@ -391,7 +391,6 @@ TreeKEMPublicKey::add_leaf(const LeafNode& leaf) } // Extend the tree if necessary - auto ni = NodeIndex(index); if (index.val >= size.val) { if (size.val == 0) { size.val = 1; @@ -402,11 +401,29 @@ TreeKEMPublicKey::add_leaf(const LeafNode& leaf) } } + return index; +} + +LeafIndex +TreeKEMPublicKey::add_leaf(const LeafNode& leaf) +{ + // Check that the leaf node's keys are not already present in the tree + if (exists_in_tree(leaf.encryption_key, std::nullopt)) { + throw InvalidParameterError("Duplicate encryption key"); + } + + if (exists_in_tree(leaf.signature_key, std::nullopt)) { + throw InvalidParameterError("Duplicate signature key"); + } + + // Allocate a blank leaf for this node + const auto index = allocate_leaf(); + // Set the leaf node_at(index).node = Node{ leaf }; // Update the unmerged list - for (auto& n : ni.dirpath(size)) { + for (auto& n : NodeIndex(index).dirpath(size)) { if (!node_at(n).node) { continue; } @@ -425,6 +442,16 @@ TreeKEMPublicKey::add_leaf(const LeafNode& leaf) void TreeKEMPublicKey::update_leaf(LeafIndex index, const LeafNode& leaf) { + // Check that the leaf node's keys are not already present in the tree, except + // for the signature key, which is allowed to repeat. + if (exists_in_tree(leaf.encryption_key, std::nullopt)) { + throw InvalidParameterError("Duplicate encryption key"); + } + + if (exists_in_tree(leaf.signature_key, index)) { + throw InvalidParameterError("Duplicate signature key"); + } + blank_path(index); node_at(NodeIndex(index)).node = Node{ leaf }; clear_hash_path(index); @@ -449,7 +476,7 @@ TreeKEMPublicKey::blank_path(LeafIndex index) void TreeKEMPublicKey::merge(LeafIndex from, const UpdatePath& path) { - node_at(from).node = Node{ path.leaf_node }; + update_leaf(from, path.leaf_node); auto dp = filtered_direct_path(NodeIndex(from)); if (dp.size() != path.nodes.size()) { @@ -469,7 +496,6 @@ TreeKEMPublicKey::merge(LeafIndex from, const UpdatePath& path) path.nodes[i].public_key, parent_hash, {} } }; } - clear_hash_path(from); set_hash_all(); } @@ -869,19 +895,19 @@ TreeKEMPublicKey::parent_hashes( const FilteredDirectPath& fdp, const std::vector& path_nodes) const { + // An empty filtered direct path indicates a one-member tree, since there's + // nobody else there to encrypt with. In this special case, there's no + // parent hashing to be done. + if (fdp.empty()) { + return {}; + } + // The list of nodes for whom parent hashes are computed, namely: Direct path // excluding root, including leaf auto from_node = NodeIndex(from); auto dp = fdp; - if (!dp.empty()) { - // pop_back() on an empty list is undefined behavior - dp.pop_back(); - } - - if (from_node != NodeIndex::root(size)) { - // Handle the special case of a one-leaf tree - dp.insert(dp.begin(), { from_node, {} }); - } + dp.pop_back(); + dp.insert(dp.begin(), { from_node, {} }); if (dp.size() != path_nodes.size()) { throw ProtocolError("Malformed UpdatePath"); @@ -1011,6 +1037,24 @@ TreeKEMPublicKey::parent_hash_valid(LeafIndex from, return leaf_ph && opt::get(leaf_ph) == hash_chain[0]; } +bool +TreeKEMPublicKey::exists_in_tree(const HPKEPublicKey& key, + std::optional except) const +{ + return any_leaf([&](auto i, const auto& node) { + return i != except && node.encryption_key == key; + }); +} + +bool +TreeKEMPublicKey::exists_in_tree(const SignaturePublicKey& key, + std::optional except) const +{ + return any_leaf([&](auto i, const auto& node) { + return i != except && node.signature_key == key; + }); +} + tls::ostream& operator<<(tls::ostream& str, const TreeKEMPublicKey& obj) { diff --git a/test/messages.cpp b/test/messages.cpp index 31621e1e..033c9fac 100644 --- a/test/messages.cpp +++ b/test/messages.cpp @@ -98,23 +98,34 @@ class MLSMessageTest TEST_CASE_FIXTURE(MLSMessageTest, "AuthenticatedContent Sign/Verify") { // Verify that a sign / verify round-trip works - auto content_auth = AuthenticatedContent::sign( - WireFormat::mls_ciphertext, application_content, suite, sig_priv, context); + auto content_auth = + AuthenticatedContent::sign(WireFormat::mls_private_message, + application_content, + suite, + sig_priv, + context); REQUIRE(content_auth.verify(suite, sig_priv.public_key, context)); REQUIRE(content_auth.content == application_content); // Verify that `mls_plaintext` is forbidden for ApplicationData // NOLINTNEXTLINE(llvm-else-after-return, readability-else-after-return) - REQUIRE_THROWS(AuthenticatedContent::sign( - WireFormat::mls_plaintext, application_content, suite, sig_priv, context)); + REQUIRE_THROWS(AuthenticatedContent::sign(WireFormat::mls_public_message, + application_content, + suite, + sig_priv, + context)); } TEST_CASE_FIXTURE(MLSMessageTest, "PublicMessage Protect/Unprotect") { auto content = proposal_content; - auto content_auth_original = AuthenticatedContent::sign( - WireFormat::mls_plaintext, std::move(content), suite, sig_priv, context); + auto content_auth_original = + AuthenticatedContent::sign(WireFormat::mls_public_message, + std::move(content), + suite, + sig_priv, + context); auto pt = PublicMessage::protect( content_auth_original, suite, membership_key, context); @@ -125,8 +136,12 @@ TEST_CASE_FIXTURE(MLSMessageTest, "PublicMessage Protect/Unprotect") TEST_CASE_FIXTURE(MLSMessageTest, "PrivateMessage Protect/Unprotect") { auto content = proposal_content; - auto content_auth_original = AuthenticatedContent::sign( - WireFormat::mls_ciphertext, std::move(content), suite, sig_priv, context); + auto content_auth_original = + AuthenticatedContent::sign(WireFormat::mls_private_message, + std::move(content), + suite, + sig_priv, + context); auto ct = PrivateMessage::protect( content_auth_original, suite, keys, sender_data_secret, padding_size); diff --git a/test/state.cpp b/test/state.cpp index f2619528..4daf5cf9 100644 --- a/test/state.cpp +++ b/test/state.cpp @@ -365,6 +365,60 @@ TEST_CASE_FIXTURE(StateTest, "Two Person with PSK") REQUIRE(first1 == second0); } +TEST_CASE_FIXTURE(StateTest, "Two Person with Replacement") +{ + // Initialize the creator's state + auto first0 = State{ group_id, + suite, + leaf_privs[0], + identity_privs[0], + key_packages[0].leaf_node, + {} }; + + // Handle the Add proposal and create a Commit + const auto add1 = first0.add_proposal(key_packages[1]); + const auto [commit1, welcome1, first1_] = + first0.commit(fresh_secret(), CommitOpts{ { add1 }, true, false, {} }, {}); + silence_unused(commit1); + auto first1 = first1_; + + // Initialize the second participant from the Welcome + auto second1 = State{ init_privs[1], + leaf_privs[1], + identity_privs[1], + key_packages[1], + welcome1, + std::nullopt, + {} }; + REQUIRE(first1 == second1); + + // Create a new appearance of the first member + const auto [init_priv, leaf_priv, _identity_priv, key_package_] = + make_client(); + const auto identity_priv = identity_privs[0]; + auto key_package = key_package_; + key_package.leaf_node.signature_key = identity_priv.public_key; + key_package.leaf_node.sign(suite, identity_priv, std::nullopt); + key_package.sign(identity_priv); + + // Create a commit replacing the first member + const auto remove2 = second1.remove_proposal(LeafIndex{ 0 }); + const auto add2 = second1.add_proposal(key_package); + const auto [commit2, welcome2, second2_] = second1.commit( + fresh_secret(), CommitOpts{ { add2, remove2 }, true, false, {} }, {}); + auto second2 = second2_; + silence_unused(commit2); + + // Initialize the new first member from the Welcome + const auto first2 = + State{ init_priv, leaf_priv, identity_priv, key_package, welcome2, + std::nullopt, {} }; + REQUIRE(first2 == second2); + + auto group = std::vector{ first2, second2 }; + verify_group_functionality(group); +} + TEST_CASE_FIXTURE(StateTest, "External Join") { // Initialize the creator's state diff --git a/test/treekem.cpp b/test/treekem.cpp index 579fe511..be2c8d3e 100644 --- a/test/treekem.cpp +++ b/test/treekem.cpp @@ -69,9 +69,9 @@ TEST_CASE_FIXTURE(TreeKEMTest, "TreeKEM Private Key") const auto hash_size = suite.digest().hash_size; // Create a tree with N blank leaves - auto [_leaf_priv, _sig_priv, leaf_node] = new_leaf_node(); auto pub = TreeKEMPublicKey(suite); for (auto i = uint32_t(0); i < size.val; i++) { + auto [_leaf_priv, _sig_priv, leaf_node] = new_leaf_node(); pub.add_leaf(leaf_node); } @@ -144,9 +144,10 @@ TEST_CASE_FIXTURE(TreeKEMTest, "TreeKEM Public Key") auto pub = TreeKEMPublicKey{ suite }; for (uint32_t i = 0; i < size.val; i++) { // Add a leaf - auto [init_priv, sig_priv, leaf_before_] = new_leaf_node(); + auto [_init_priv_before, _sig_priv_before, leaf_before_] = new_leaf_node(); auto leaf_before = leaf_before_; - silence_unused(init_priv); + silence_unused(_init_priv_before); + silence_unused(_sig_priv_before); auto index = LeafIndex(i); @@ -162,20 +163,23 @@ TEST_CASE_FIXTURE(TreeKEMTest, "TreeKEM Public Key") REQUIRE(found_leaf == leaf_before); // Manually construct a direct path to populate nodes above the new leaf - auto path = UpdatePath{ leaf_before, {} }; + const auto [_init_priv_after, sig_priv_after, leaf_after_] = + new_leaf_node(); + const auto leaf_after = leaf_after_; + auto path = UpdatePath{ leaf_after, {} }; auto dp = pub.filtered_direct_path(NodeIndex(index)); while (path.nodes.size() < dp.size()) { auto node_pub = HPKEPrivateKey::generate(suite).public_key; path.nodes.push_back({ node_pub, {} }); } - path.leaf_node.sign(suite, sig_priv, std::nullopt); + path.leaf_node.sign(suite, sig_priv_after, std::nullopt); // Merge the direct path (ignoring parent hash validity) pub.merge(index, path); - auto leaf_after = path.leaf_node; - found = pub.find(leaf_after); + const auto& re_signed_leaf = path.leaf_node; + found = pub.find(re_signed_leaf); REQUIRE(found); REQUIRE(found == index); for (const auto& [n_, _res] : dp) { @@ -185,7 +189,7 @@ TEST_CASE_FIXTURE(TreeKEMTest, "TreeKEM Public Key") found_leaf = pub.leaf_node(index); REQUIRE(found_leaf); - REQUIRE(found_leaf == leaf_after); + REQUIRE(found_leaf == re_signed_leaf); } // Remove a node and verify that the resolution comes out right @@ -237,7 +241,6 @@ TEST_CASE_FIXTURE(TreeKEMTest, "TreeKEM encap/decap") auto ok = ok_; REQUIRE(ok); - pubs[i].merge(adder, path); REQUIRE(privs[i].consistent(pubs[i])); // New joiner initializes their private key From 661758e111646958ca6538b85f5c6a8664a3b044 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sat, 9 Sep 2023 15:57:54 -0400 Subject: [PATCH 30/36] Validate the tree on joining (#363) * Properly handle one-member trees without the one member in leaf 0 * Move uniqueness checking to tree operations * Rename WireFormat enum values to match RFC * clang-format * clang-tidy * Validate the tree on joining * Make tree validation linear-time * clang-format * Minor comment tweak --------- Co-authored-by: Richard Barnes --- include/mls/core_types.h | 8 ++ include/mls/state.h | 1 + src/state.cpp | 155 +++++++++++++++++++++++++-------------- 3 files changed, 109 insertions(+), 55 deletions(-) diff --git a/include/mls/core_types.h b/include/mls/core_types.h index cc895fa0..533c6a8c 100644 --- a/include/mls/core_types.h +++ b/include/mls/core_types.h @@ -113,6 +113,14 @@ struct Capabilities bool proposals_supported(const std::vector& required) const; bool credential_supported(const Credential& credential) const; + template + bool credentials_supported(const Container& required) const + { + return stdx::all_of(required, [&](CredentialType type) { + return stdx::contains(credentials, type); + }); + } + TLS_SERIALIZABLE(versions, cipher_suites, extensions, proposals, credentials) }; diff --git a/include/mls/state.h b/include/mls/state.h index b1ef0d93..329aadb0 100644 --- a/include/mls/state.h +++ b/include/mls/state.h @@ -277,6 +277,7 @@ class State TreeKEMPublicKey import_tree(const bytes& tree_hash, const std::optional& external, const ExtensionList& extensions); + bool validate_tree() const; // Form a commit, covering all the cases with slightly different validation // rules: diff --git a/src/state.cpp b/src/state.cpp index 9a01eea1..2dd02bfe 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -68,11 +68,77 @@ State::import_tree(const bytes& tree_hash, throw InvalidParameterError("Tree does not match GroupInfo"); } - if (!tree.parent_hash_valid()) { - throw InvalidParameterError("Invalid tree"); + return tree; +} + +bool +State::validate_tree() const +{ + // The functionality here is somewhat duplicative of State::valid(const + // LeafNode&). Simply calling that method, however, would result in this + // method having quadratic scaling, since each call to valid() does a linear + // scan through the tree to check uniqueness of keys and compatibility of + // credential support. + + // Validate that the tree is parent-hash valid + if (!_tree.parent_hash_valid()) { + return false; } - return tree; + // Validate the signatures on all leaves + const auto signature_valid = + _tree.all_leaves([&](auto i, const auto& leaf_node) { + auto binding = std::optional{}; + switch (leaf_node.source()) { + case LeafNodeSource::commit: + case LeafNodeSource::update: + binding = LeafNode::MemberBinding{ _group_id, i }; + break; + + default: + // Nothing to do + break; + } + + return leaf_node.verify(_suite, binding); + }); + if (!signature_valid) { + return false; + } + + // Collect cross-tree properties + auto n_leaves = size_t(0); + auto encryption_keys = std::set{}; + auto signature_keys = std::set{}; + auto credential_types = std::set{}; + _tree.all_leaves([&](auto /* i */, const auto& leaf_node) { + n_leaves += 1; + encryption_keys.insert(leaf_node.encryption_key.data); + signature_keys.insert(leaf_node.signature_key.data); + credential_types.insert(leaf_node.credential.type()); + return true; + }); + + // Verify uniqueness of keys + if (encryption_keys.size() != n_leaves) { + return false; + } + + if (signature_keys.size() != n_leaves) { + return false; + } + + // Verify that each leaf indicates support for all required parameters + return _tree.all_leaves([&](auto /* i */, const auto& leaf_node) { + const auto supports_group_extensions = + leaf_node.verify_extension_support(_extensions); + const auto supports_own_extensions = + leaf_node.verify_extension_support(leaf_node.extensions); + const auto supports_group_credentials = + leaf_node.capabilities.credentials_supported(credential_types); + return supports_group_extensions && supports_own_extensions && + supports_group_credentials; + }); } State::State(SignaturePrivateKey sig_priv, @@ -92,6 +158,10 @@ State::State(SignaturePrivateKey sig_priv, , _index(0) , _identity_priv(std::move(sig_priv)) { + if (!validate_tree()) { + throw InvalidParameterError("Invalid tree"); + } + // The following are not set: // _index // _tree_priv @@ -174,6 +244,11 @@ State::State(const HPKEPrivateKey& init_priv, _extensions = group_info.group_context.extensions; + // Validate that the tree is in fact consistent with the group's parameters + if (!validate_tree()) { + throw InvalidParameterError("Invalid tree"); + } + // Construct TreeKEM private key from parts provided auto maybe_index = _tree.find(key_package.leaf_node); if (!maybe_index) { @@ -1130,19 +1205,9 @@ State::apply(const GroupContextExtensions& gce) bool State::extensions_supported(const ExtensionList& exts) const { - for (LeafIndex i{ 0 }; i < _tree.size; i.val++) { - const auto& maybe_leaf = _tree.leaf_node(i); - if (!maybe_leaf) { - continue; - } - - const auto& leaf = opt::get(maybe_leaf); - if (!leaf.verify_extension_support(exts)) { - return false; - } - } - - return true; + return _tree.all_leaves([&](auto /* i */, const auto& leaf_node) { + return leaf_node.verify_extension_support(exts); + }); } void @@ -1431,9 +1496,7 @@ State::valid(const LeafNode& leaf_node, // the ID for each extension in the extensions field is listed in the // capabilities.extensions field of the LeafNode. auto supports_own_extensions = - stdx::all_of(leaf_node.extensions.extensions, [&](const auto& ext) { - return stdx::contains(leaf_node.capabilities.extensions, ext.type); - }); + leaf_node.verify_extension_support(leaf_node.extensions); return (signature_valid && supports_group_extensions && correct_source && mutual_credential_support && supports_own_extensions); @@ -1536,19 +1599,7 @@ State::valid(const ExternalInit& external_init) const bool State::valid(const GroupContextExtensions& gce) const { - // Verify that each extension is supported by all members - for (auto i = LeafIndex{ 0 }; i < _tree.size; i.val++) { - const auto maybe_leaf = _tree.leaf_node(i); - if (!maybe_leaf) { - continue; - } - - const auto& leaf = opt::get(maybe_leaf); - if (!leaf.verify_extension_support(gce.group_context_extensions)) { - return false; - } - } - return true; + return extensions_supported(gce.group_context_extensions); } bool @@ -2072,19 +2123,14 @@ State::group_info(bool inline_tree) const std::vector State::roster() const { - auto leaves = std::vector(_tree.size.val); - auto leaf_count = uint32_t(0); + auto leaves = std::vector{}; + leaves.reserve(_tree.size.val); - for (uint32_t i = 0; i < _tree.size.val; i++) { - const auto& maybe_leaf = _tree.leaf_node(LeafIndex{ i }); - if (!maybe_leaf) { - continue; - } - leaves.at(leaf_count) = opt::get(maybe_leaf); - leaf_count++; - } + _tree.all_leaves([&](auto /* i */, auto leaf) { + leaves.push_back(leaf); + return true; + }); - leaves.resize(leaf_count); return leaves; } @@ -2097,20 +2143,19 @@ State::epoch_authenticator() const LeafIndex State::leaf_for_roster_entry(RosterIndex index) const { - auto non_blank_leaves = uint32_t(0); - - for (auto i = LeafIndex{ 0 }; i < _tree.size; i.val++) { - const auto& maybe_leaf = _tree.leaf_node(i); - if (!maybe_leaf) { - continue; - } - if (non_blank_leaves == index.val) { - return i; + auto visited = RosterIndex{ 0 }; + auto found = std::optional{}; + _tree.all_leaves([&](auto i, const auto& /* leaf_node */) { + if (visited == index) { + found = i; + return false; } - non_blank_leaves += 1; - } - throw InvalidParameterError("Invalid roster index"); + visited.val += 1; + return true; + }); + + return opt::get(found); } State From 6a10f786c1bad18fc9f798a800add0b32728a87b Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Sun, 10 Sep 2023 18:56:23 -0500 Subject: [PATCH 31/36] provide the user access to the credentialSubject via UserInfoClaims structure (#369) * first pass at UserInfoClaims * removed std++20 use * refactoring per @bifurcation recommendations * fixed problem found in CI --- lib/hpke/include/hpke/userinfo_vc.h | 43 +++++++++++- lib/hpke/src/userinfo_vc.cpp | 105 ++++++++++++++++++++++++++-- lib/hpke/test/userinfo_vc.cpp | 97 ++++++++++++++++++++++++- 3 files changed, 239 insertions(+), 6 deletions(-) diff --git a/lib/hpke/include/hpke/userinfo_vc.h b/lib/hpke/include/hpke/userinfo_vc.h index 908451f5..085fd4f5 100644 --- a/lib/hpke/include/hpke/userinfo_vc.h +++ b/lib/hpke/include/hpke/userinfo_vc.h @@ -6,11 +6,51 @@ #include #include #include +#include using namespace MLS_NAMESPACE::bytes_ns; namespace MLS_NAMESPACE::hpke { +struct UserInfoClaimsAddress +{ + std::optional formatted; + std::optional street_address; + std::optional locality; + std::optional region; + std::optional postal_code; + std::optional country; + + static UserInfoClaimsAddress from_json(const nlohmann::json& address_json); +}; + +struct UserInfoClaims +{ + + std::optional sub; + std::optional name; + std::optional given_name; + std::optional family_name; + std::optional middle_name; + std::optional nickname; + std::optional preferred_username; + std::optional profile; + std::optional picture; + std::optional website; + std::optional email; + std::optional email_verified; + std::optional gender; + std::optional birthdate; + std::optional zoneinfo; + std::optional locale; + std::optional phone_number; + std::optional phone_number_verified; + std::optional address; + std::optional updated_at; + + static UserInfoClaims from_json(const nlohmann::json& cred_subject_json); +}; + struct UserInfoVC { private: @@ -29,7 +69,8 @@ struct UserInfoVC std::string key_id() const; std::chrono::system_clock::time_point not_before() const; std::chrono::system_clock::time_point not_after() const; - std::map subject() const; + const std::string& raw_credential() const; + const UserInfoClaims& subject() const; const Signature::PublicJWK& public_key() const; bool valid_from(const Signature::PublicKey& issuer_key) const; diff --git a/lib/hpke/src/userinfo_vc.cpp b/lib/hpke/src/userinfo_vc.cpp index fcbfb5b8..89860d12 100644 --- a/lib/hpke/src/userinfo_vc.cpp +++ b/lib/hpke/src/userinfo_vc.cpp @@ -8,6 +8,44 @@ using nlohmann::json; namespace MLS_NAMESPACE::hpke { +static const std::string name_attr = "name"; +static const std::string sub_attr = "sub"; +static const std::string given_name_attr = "given_name"; +static const std::string family_name_attr = "family_name"; +static const std::string middle_name_attr = "middle_name"; +static const std::string nickname_attr = "nickname"; +static const std::string preferred_username_attr = "preferred_username"; +static const std::string profile_attr = "profile"; +static const std::string picture_attr = "picture"; +static const std::string website_attr = "website"; +static const std::string email_attr = "email"; +static const std::string email_verified_attr = "email_verified"; +static const std::string gender_attr = "gender"; +static const std::string birthdate_attr = "birthdate"; +static const std::string zoneinfo_attr = "zoneinfo"; +static const std::string locale_attr = "locale"; +static const std::string phone_number_attr = "phone_number"; +static const std::string phone_number_verified_attr = "phone_number_verified"; +static const std::string address_attr = "address"; +static const std::string address_formatted_attr = "formatted"; +static const std::string address_street_address_attr = "street_address"; +static const std::string address_locality_attr = "locality"; +static const std::string address_region_attr = "region"; +static const std::string address_postal_code_attr = "postal_code"; +static const std::string address_country_attr = "country"; +static const std::string updated_at_attr = "updated_at"; + +template +static std::optional +get_optional(const json& json_object, const std::string& field_name) +{ + if (!json_object.contains(field_name)) { + return std::nullopt; + } + + return { json_object.at(field_name).get() }; +} + /// /// ParsedCredential /// @@ -130,7 +168,7 @@ struct UserInfoVC::ParsedCredential std::chrono::system_clock::time_point not_after; // `exp` // Credential subject fields - std::map credential_subject; + UserInfoClaims credential_subject; Signature::PublicJWK public_key; // Signature verification information @@ -142,7 +180,7 @@ struct UserInfoVC::ParsedCredential std::string issuer_in, std::chrono::system_clock::time_point not_before_in, std::chrono::system_clock::time_point not_after_in, - std::map credential_subject_in, + UserInfoClaims credential_subject_in, Signature::PublicJWK&& public_key_in, bytes to_be_signed_in, bytes signature_in) @@ -222,7 +260,7 @@ struct UserInfoVC::ParsedCredential epoch_time(payload.at("nbf").get()), epoch_time(payload.at("exp").get()), - vc.at("credentialSubject"), + UserInfoClaims::from_json(vc.at("credentialSubject")), std::move(public_key), to_be_signed, @@ -235,6 +273,59 @@ struct UserInfoVC::ParsedCredential } }; +/// +/// UserInfoClaimsAddress +/// +UserInfoClaimsAddress +UserInfoClaimsAddress::from_json(const nlohmann::json& address_json) +{ + return { + get_optional(address_json, address_formatted_attr), + get_optional(address_json, address_street_address_attr), + get_optional(address_json, address_locality_attr), + get_optional(address_json, address_region_attr), + get_optional(address_json, address_postal_code_attr), + get_optional(address_json, address_country_attr), + }; +} + +/// +/// UserInfoClaims +/// +UserInfoClaims +UserInfoClaims::from_json(const nlohmann::json& cred_subject_json) +{ + std::optional address_opt = {}; + + if (cred_subject_json.contains(address_attr)) { + address_opt = + UserInfoClaimsAddress::from_json(cred_subject_json.at(address_attr)); + } + + return { + get_optional(cred_subject_json, sub_attr), + get_optional(cred_subject_json, name_attr), + get_optional(cred_subject_json, given_name_attr), + get_optional(cred_subject_json, family_name_attr), + get_optional(cred_subject_json, middle_name_attr), + get_optional(cred_subject_json, nickname_attr), + get_optional(cred_subject_json, preferred_username_attr), + get_optional(cred_subject_json, profile_attr), + get_optional(cred_subject_json, picture_attr), + get_optional(cred_subject_json, website_attr), + get_optional(cred_subject_json, email_attr), + get_optional(cred_subject_json, email_verified_attr), + get_optional(cred_subject_json, gender_attr), + get_optional(cred_subject_json, birthdate_attr), + get_optional(cred_subject_json, zoneinfo_attr), + get_optional(cred_subject_json, locale_attr), + get_optional(cred_subject_json, phone_number_attr), + get_optional(cred_subject_json, phone_number_verified_attr), + address_opt, + get_optional(cred_subject_json, updated_at_attr), + }; +} + /// /// UserInfoVC /// @@ -263,7 +354,13 @@ UserInfoVC::valid_from(const Signature::PublicKey& issuer_key) const return parsed_cred->verify(issuer_key); } -std::map +const std::string& +UserInfoVC::raw_credential() const +{ + return raw; +} + +const UserInfoClaims& UserInfoVC::subject() const { return parsed_cred->credential_subject; diff --git a/lib/hpke/test/userinfo_vc.cpp b/lib/hpke/test/userinfo_vc.cpp index cdde3322..f083f573 100644 --- a/lib/hpke/test/userinfo_vc.cpp +++ b/lib/hpke/test/userinfo_vc.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace opt = MLS_NAMESPACE::tls::opt; @@ -127,6 +128,100 @@ TEST_CASE("UserInfoVC Parsing and Validation") CHECK(vc.key_id() == opt::get(issuer_jwk.key_id)); CHECK(vc.not_before().time_since_epoch() == known_not_before); CHECK(vc.not_after().time_since_epoch() == known_not_after); - CHECK(vc.subject() == known_subject); CHECK(vc.public_key() == known_subject_jwk); + + const auto& subject = vc.subject(); + CHECK(subject.sub.value_or("") == known_subject.at("sub")); + + CHECK(vc.subject().name.value_or("") == known_subject.at("name")); + CHECK(vc.subject().given_name.value_or("") == known_subject.at("given_name")); + CHECK(vc.subject().family_name.value_or("") == + known_subject.at("family_name")); + CHECK(vc.subject().preferred_username.value_or("") == + known_subject.at("preferred_username")); + CHECK(vc.subject().email.value_or("") == known_subject.at("email")); + CHECK(vc.subject().picture.value_or("") == known_subject.at("picture")); +} + +TEST_CASE("UserInfoClaims Field Parsing") +{ + nlohmann::json credentialSubject = { + { "test", "test" }, + { "sub", "sub" }, + { "name", "name" }, + { "given_name", "given_name" }, + { "family_name", "family_name" }, + { "middle_name", "middle_name" }, + { "nickname", "nickname" }, + { "preferred_username", "preferred_username" }, + { "profile", "profile" }, + { "picture", "picture" }, + { "website", "website" }, + { "email", "email" }, + { "email_verified", true }, + { "gender", "gender" }, + { "birthdate", "birthdate" }, + { "zoneinfo", "zoneinfo" }, + { "locale", "locale" }, + { "phone_number", "phone_number" }, + { "phone_number_verified", true }, + { "address", + { { "formatted", "formatted" }, + { "street_address", "street_address" }, + { "locality", "locality" }, + { "region", "region" }, + { "postal_code", "postal_code" }, + { "country", "country" } } }, + { "updated_at", 42 } + }; + + const auto userinfo_claims = UserInfoClaims::from_json(credentialSubject); + + CHECK(userinfo_claims.sub == credentialSubject.at("sub")); + CHECK(userinfo_claims.name == credentialSubject.at("name")); + CHECK(userinfo_claims.given_name == credentialSubject.at("given_name")); + CHECK(userinfo_claims.family_name == credentialSubject.at("family_name")); + CHECK(userinfo_claims.middle_name == credentialSubject.at("middle_name")); + CHECK(userinfo_claims.nickname == credentialSubject.at("nickname")); + CHECK(userinfo_claims.preferred_username == + credentialSubject.at("preferred_username")); + CHECK(userinfo_claims.profile == credentialSubject.at("profile")); + CHECK(userinfo_claims.picture == credentialSubject.at("picture")); + CHECK(userinfo_claims.website == credentialSubject.at("website")); + CHECK(userinfo_claims.email == credentialSubject.at("email")); + CHECK(userinfo_claims.email_verified == + credentialSubject.at("email_verified")); + CHECK(userinfo_claims.gender == credentialSubject.at("gender")); + CHECK(userinfo_claims.birthdate == credentialSubject.at("birthdate")); + CHECK(userinfo_claims.zoneinfo == credentialSubject.at("zoneinfo")); + CHECK(userinfo_claims.locale == credentialSubject.at("locale")); + CHECK(userinfo_claims.phone_number == credentialSubject.at("phone_number")); + CHECK(userinfo_claims.phone_number_verified == + credentialSubject.at("phone_number_verified")); + CHECK(userinfo_claims.updated_at == credentialSubject.at("updated_at")); + + auto address = userinfo_claims.address.value_or(UserInfoClaimsAddress()); + CHECK(address.formatted == credentialSubject.at("address").at("formatted")); + CHECK(address.street_address == + credentialSubject.at("address").at("street_address")); + CHECK(address.locality == credentialSubject.at("address").at("locality")); + CHECK(address.region == credentialSubject.at("address").at("region")); + CHECK(address.postal_code == + credentialSubject.at("address").at("postal_code")); + CHECK(address.country == credentialSubject.at("address").at("country")); +} + +TEST_CASE("UserInfoClaims Edge Cases") +{ + CHECK_THROWS_WITH( + UserInfoClaims::from_json({ { "updated_at", "42" } }), + "[json.exception.type_error.302] type must be number, but is string"); + + CHECK_THROWS_WITH( + UserInfoClaims::from_json({ { "name", true } }), + "[json.exception.type_error.302] type must be string, but is boolean"); + + CHECK_THROWS_WITH( + UserInfoClaims::from_json({ { "email_verified", "true" } }), + "[json.exception.type_error.302] type must be boolean, but is string"); } From c6de4ca5412f032af22f996d67d538f74785242a Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 11 Sep 2023 21:06:04 -0400 Subject: [PATCH 32/36] Provide more flexible JWK parsing (#370) * Expose more JWK tools in the public API * Add a test case for full JWK parsing * clang-tidy * clang-format * Remove unnecessary forward declaration --------- Co-authored-by: Richard Barnes --- include/mls/credential.h | 22 ++++++++---- include/mls/crypto.h | 9 +++++ lib/hpke/include/hpke/userinfo_vc.h | 3 +- lib/hpke/src/userinfo_vc.cpp | 19 ++++++++--- src/credential.cpp | 53 ++++++++++++++++++++++++----- src/crypto.cpp | 9 +++++ test/crypto.cpp | 18 ++++++++++ 7 files changed, 113 insertions(+), 20 deletions(-) diff --git a/include/mls/credential.h b/include/mls/credential.h index a8bbd77f..be0c213e 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -6,6 +6,10 @@ namespace MLS_NAMESPACE { +namespace hpke { +struct UserInfoVC; +} + // struct { // opaque identity<0..2^16-1>; // SignaturePublicKey public_key; @@ -57,17 +61,23 @@ operator>>(tls::istream& str, X509Credential& obj); struct UserInfoVCCredential { UserInfoVCCredential() = default; - explicit UserInfoVCCredential(bytes userinfo_vc_jwt_in); + explicit UserInfoVCCredential(std::string userinfo_vc_jwt_in); - bytes userinfo_vc_jwt; + std::string userinfo_vc_jwt; bool valid_for(const SignaturePublicKey& pub) const; + bool valid_from(const PublicJWK& pub) const; - TLS_SERIALIZABLE(userinfo_vc_jwt) + friend tls::ostream operator<<(tls::ostream& str, + const UserInfoVCCredential& obj); + friend tls::istream operator>>(tls::istream& str, UserInfoVCCredential& obj); + friend bool operator==(const UserInfoVCCredential& lhs, + const UserInfoVCCredential& rhs); + friend bool operator!=(const UserInfoVCCredential& lhs, + const UserInfoVCCredential& rhs); private: - SignaturePublicKey _public_key; - SignatureScheme _signature_scheme; + std::shared_ptr _vc; }; bool @@ -149,7 +159,7 @@ struct Credential static Credential basic(const bytes& identity); static Credential x509(const std::vector& der_chain); - static Credential userinfo_vc(const bytes& userinfo_vc_jwt); + static Credential userinfo_vc(const std::string& userinfo_vc_jwt); static Credential multi( const std::vector& binding_inputs, const SignaturePublicKey& signature_key); diff --git a/include/mls/crypto.h b/include/mls/crypto.h index 00d513d6..dc467c0d 100644 --- a/include/mls/crypto.h +++ b/include/mls/crypto.h @@ -225,6 +225,15 @@ struct SignaturePublicKey TLS_SERIALIZABLE(data) }; +struct PublicJWK +{ + SignatureScheme signature_scheme; + std::optional key_id; + SignaturePublicKey public_key; + + static PublicJWK parse(const std::string& jwk_json); +}; + struct SignaturePrivateKey { static SignaturePrivateKey generate(CipherSuite suite); diff --git a/lib/hpke/include/hpke/userinfo_vc.h b/lib/hpke/include/hpke/userinfo_vc.h index 085fd4f5..902fed53 100644 --- a/lib/hpke/include/hpke/userinfo_vc.h +++ b/lib/hpke/include/hpke/userinfo_vc.h @@ -65,8 +65,9 @@ struct UserInfoVC UserInfoVC& operator=(const UserInfoVC& other) = default; UserInfoVC& operator=(UserInfoVC&& other) = default; + const Signature& signature_algorithm() const; std::string issuer() const; - std::string key_id() const; + std::optional key_id() const; std::chrono::system_clock::time_point not_before() const; std::chrono::system_clock::time_point not_after() const; const std::string& raw_credential() const; diff --git a/lib/hpke/src/userinfo_vc.cpp b/lib/hpke/src/userinfo_vc.cpp index 89860d12..65cbe5d9 100644 --- a/lib/hpke/src/userinfo_vc.cpp +++ b/lib/hpke/src/userinfo_vc.cpp @@ -160,7 +160,7 @@ struct UserInfoVC::ParsedCredential { // Header fields const Signature& signature_algorithm; // `alg` - std::string key_id; // `kid` + std::optional key_id; // `kid` // Top-level Payload fields std::string issuer; // `iss` @@ -176,7 +176,7 @@ struct UserInfoVC::ParsedCredential bytes signature; ParsedCredential(const Signature& signature_algorithm_in, - std::string key_id_in, + std::optional key_id_in, std::string issuer_in, std::chrono::system_clock::time_point not_before_in, std::chrono::system_clock::time_point not_after_in, @@ -223,6 +223,11 @@ struct UserInfoVC::ParsedCredential signature = jws_to_der_sig(signature); } + auto kid = std::optional{}; + if (header.contains("kid")) { + kid = header.at("kid").get(); + } + // Verify the VC parts const auto& vc = payload.at("vc"); @@ -254,7 +259,7 @@ struct UserInfoVC::ParsedCredential // Extract the salient parts return std::make_shared( sig, - header.at("kid"), + kid, payload.at("iss"), epoch_time(payload.at("nbf").get()), @@ -336,13 +341,19 @@ UserInfoVC::UserInfoVC(std::string jwt) { } +const Signature& +UserInfoVC::signature_algorithm() const +{ + return parsed_cred->signature_algorithm; +} + std::string UserInfoVC::issuer() const { return parsed_cred->issuer; } -std::string +std::optional UserInfoVC::key_id() const { return parsed_cred->key_id; diff --git a/src/credential.cpp b/src/credential.cpp index 730562b9..36b10629 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -115,22 +115,57 @@ operator==(const X509Credential& lhs, const X509Credential& rhs) /// /// UserInfoVCCredential /// -UserInfoVCCredential::UserInfoVCCredential(bytes userinfo_vc_jwt_in) +UserInfoVCCredential::UserInfoVCCredential(std::string userinfo_vc_jwt_in) : userinfo_vc_jwt(std::move(userinfo_vc_jwt_in)) + , _vc(std::make_shared(userinfo_vc_jwt)) { - const auto vc = UserInfoVC(to_ascii(userinfo_vc_jwt)); - - const auto& pub = vc.public_key(); - const auto pub_data = pub.sig.serialize(*pub.key); - _signature_scheme = tls_signature_scheme(pub.sig.id); - _public_key = SignaturePublicKey{ pub_data }; } bool // NOLINTNEXTLINE(readability-convert-member-functions-to-static) UserInfoVCCredential::valid_for(const SignaturePublicKey& pub) const { - return pub == _public_key; + const auto& vc_pub = _vc->public_key(); + return pub.data == vc_pub.sig.serialize(*vc_pub.key); +} + +bool +UserInfoVCCredential::valid_from(const PublicJWK& pub) const +{ + const auto& sig = _vc->signature_algorithm(); + if (pub.signature_scheme != tls_signature_scheme(sig.id)) { + return false; + } + + const auto sig_pub = sig.deserialize(pub.public_key.data); + return _vc->valid_from(*sig_pub); +} + +tls::ostream +operator<<(tls::ostream& str, const UserInfoVCCredential& obj) +{ + return str << from_ascii(obj.userinfo_vc_jwt); +} + +tls::istream +operator>>(tls::istream& str, UserInfoVCCredential& obj) +{ + auto jwt = bytes{}; + str >> jwt; + obj = UserInfoVCCredential(to_ascii(jwt)); + return str; +} + +bool +operator==(const UserInfoVCCredential& lhs, const UserInfoVCCredential& rhs) +{ + return lhs.userinfo_vc_jwt == rhs.userinfo_vc_jwt; +} + +bool +operator!=(const UserInfoVCCredential& lhs, const UserInfoVCCredential& rhs) +{ + return !(lhs == rhs); } /// @@ -236,7 +271,7 @@ Credential::multi(const std::vector& binding_inputs, } Credential -Credential::userinfo_vc(const bytes& userinfo_vc_jwt) +Credential::userinfo_vc(const std::string& userinfo_vc_jwt) { return { UserInfoVCCredential{ userinfo_vc_jwt } }; } diff --git a/src/crypto.cpp b/src/crypto.cpp index 315ededd..1986659c 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -400,6 +400,15 @@ SignaturePublicKey::to_jwk(CipherSuite suite) const return suite.sig().export_jwk(*pub); } +PublicJWK +PublicJWK::parse(const std::string& jwk_json) +{ + const auto parsed = Signature::parse_jwk(jwk_json); + const auto scheme = tls_signature_scheme(parsed.sig.id); + const auto pub_data = parsed.sig.serialize(*parsed.key); + return { scheme, parsed.key_id, { pub_data } }; +} + SignaturePrivateKey SignaturePrivateKey::generate(CipherSuite suite) { diff --git a/test/crypto.cpp b/test/crypto.cpp index a738f88f..393a0a04 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -106,6 +106,24 @@ TEST_CASE("Signature Key JWK Import/Export") const auto decoded_pub = SignaturePublicKey::from_jwk(suite, encoded_pub); REQUIRE(decoded_pub == pub); } + + // Test PublicJWK parsing + const auto full_jwk = R"({ + "kty": "OKP", + "crv": "Ed25519", + "kid": "059fc2ee-5ef6-456a-91d8-49c422c772b2", + "x": "miljqilAZV2yFkqIBhrxhvt2wIMvPtkNEFzuziEGOtI" + })"s; + + const auto known_scheme = SignatureScheme::ed25519; + const auto known_key_id = std::string("059fc2ee-5ef6-456a-91d8-49c422c772b2"); + const auto knwon_pub_data = from_hex( + "9a2963aa2940655db2164a88061af186fb76c0832f3ed90d105ceece21063ad2"); + + const auto jwk = PublicJWK::parse(full_jwk); + REQUIRE(jwk.signature_scheme == known_scheme); + REQUIRE(jwk.key_id == known_key_id); + REQUIRE(jwk.public_key == SignaturePublicKey{ knwon_pub_data }); } TEST_CASE("Crypto Interop") From 8a010ef9ce9f1bdd8b79a11ee1c089440a3f63fb Mon Sep 17 00:00:00 2001 From: czlitony Date: Mon, 18 Sep 2023 09:20:06 +0800 Subject: [PATCH 33/36] Fix build error in Visual Studio 17.7 (#372) * Fix build error in Visual Studio 17.7 * fix format issue --------- Co-authored-by: chengzhl Co-authored-by: liangxil --- src/messages.cpp | 2 +- src/treekem.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/messages.cpp b/src/messages.cpp index 8daa652d..f064a247 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -160,7 +160,7 @@ Welcome::encrypt(const KeyPackage& kp, const std::optional& path_secret) { auto gs = GroupSecrets{ _joiner_secret, std::nullopt, _psks }; if (path_secret) { - gs.path_secret = { opt::get(path_secret) }; + gs.path_secret = GroupSecrets::PathSecret{ opt::get(path_secret) }; } auto gs_data = tls::marshal(gs); diff --git a/src/treekem.cpp b/src/treekem.cpp index c8c56688..53aeea1c 100644 --- a/src/treekem.cpp +++ b/src/treekem.cpp @@ -492,8 +492,8 @@ TreeKEMPublicKey::merge(LeafIndex from, const UpdatePath& path) parent_hash = ph[i + 1]; } - node_at(n).node = { ParentNode{ - path.nodes[i].public_key, parent_hash, {} } }; + node_at(n).node = + Node{ ParentNode{ path.nodes[i].public_key, parent_hash, {} } }; } set_hash_all(); From 8c1c2bd52edcfba16eb71b61c7b281572d0d56fb Mon Sep 17 00:00:00 2001 From: czlitony Date: Sun, 8 Oct 2023 03:11:53 +0800 Subject: [PATCH 34/36] Fix 'json::parse' build error in Xcode 15 (#373) Co-authored-by: chengzhl --- lib/hpke/src/userinfo_vc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/hpke/src/userinfo_vc.cpp b/lib/hpke/src/userinfo_vc.cpp index 65cbe5d9..563c1788 100644 --- a/lib/hpke/src/userinfo_vc.cpp +++ b/lib/hpke/src/userinfo_vc.cpp @@ -212,8 +212,8 @@ struct UserInfoVC::ParsedCredential const auto signature_b64 = jwt.substr(last_dot + 1); // Parse the components - const auto header = json::parse(from_base64url(header_b64)); - const auto payload = json::parse(from_base64url(payload_b64)); + const auto header = json::parse(to_ascii(from_base64url(header_b64))); + const auto payload = json::parse(to_ascii(from_base64url(payload_b64))); // Prepare the validation inputs const auto& sig = signature_from_alg(header.at("alg")); From 7e19d5180b58fbd78782cfd0e4e8ed0067fe2f5b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 12 Oct 2023 16:33:37 +0200 Subject: [PATCH 35/36] add missing include for inserter (#374) --- include/mls/common.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mls/common.h b/include/mls/common.h index ac9ea516..32b58d24 100644 --- a/include/mls/common.h +++ b/include/mls/common.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include From a08b545d9d3a303e167b48e7976555072a9344ff Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 16 Oct 2023 17:04:11 -0400 Subject: [PATCH 36/36] allow CMake/compile option to DISABLE_GREASE (#377) * allow CMake/compile option to DISABLE_GREASE * make the DISABLE_GREASE default explicit * clang format cleanup --- CMakeLists.txt | 5 +++++ src/grease.cpp | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28e8efd1..ecedd06e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ option(TESTING "Build tests" OFF) option(CLANG_TIDY "Perform linting with clang-tidy" OFF) option(SANITIZERS "Enable sanitizers" OFF) option(MLS_NAMESPACE_SUFFIX "Namespace Suffix for CXX and CMake Export") +option(DISABLE_GREASE "Disables the inclusion of MLS protocol recommended GREASE values" OFF) if(MLS_NAMESPACE_SUFFIX) set(MLS_CXX_NAMESPACE "mls_${MLS_NAMESPACE_SUFFIX}" CACHE STRING "Top-level Namespace for CXX") @@ -79,6 +80,10 @@ if("$ENV{MACOSX_DEPLOYMENT_TARGET}" STREQUAL "10.11") add_compile_options(-DVARIANT_COMPAT) endif() +if (DISABLE_GREASE) + add_compile_options(-DDISABLE_GREASE) +endif () + ### ### Enable testing ### diff --git a/src/grease.cpp b/src/grease.cpp index fc210648..4db4978a 100644 --- a/src/grease.cpp +++ b/src/grease.cpp @@ -6,6 +6,23 @@ namespace MLS_NAMESPACE { +#ifdef DISABLE_GREASE + +Capabilities +grease(Capabilities&& capabilities, + [[maybe_unused]] const ExtensionList& extensions) +{ + return capabilities; +} + +ExtensionList +grease(ExtensionList&& extensions) +{ + return extensions; +} + +#else + // Randomness parmeters: // * Given a list of N items, insert max(1, rand(p_grease * N)) GREASE values // * Each GREASE value added is distinct, unless more than 15 values are needed @@ -118,4 +135,6 @@ grease(ExtensionList&& extensions) return { ext }; } +#endif // DISABLE_GREASE + } // namespace MLS_NAMESPACE