From a415b28b33b385a45cdbfea7132a011fa1a3aa14 Mon Sep 17 00:00:00 2001 From: Greg Hewett Date: Tue, 13 Jun 2023 17:56:26 -0500 Subject: [PATCH 01/20] 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 02/20] 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 03/20] 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 04/20] 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 05/20] 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 13/20] 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 14/20] 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 15/20] 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 16/20] 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 17/20] 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 18/20] 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 19/20] 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 20/20] 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