From f7145efd9a4f3e8d059233a036536185897532f6 Mon Sep 17 00:00:00 2001 From: Krzysztof Jackiewicz Date: Wed, 1 Mar 2023 10:54:21 +0100 Subject: [PATCH] E2EE: Key agreement API implementation Tests included Change-Id: Iab51c84b848060f3392cb11de7dedd7ab2580034 --- CMakeLists.txt | 2 + packaging/security-tests.manifest | 1 + packaging/security-tests.spec | 2 + src/CMakeLists.txt | 4 + src/ckm/ckm-common.h | 26 +++ src/e2ee-adaptation-layer/CMakeLists.txt | 62 ++++++ .../e2ee-adaptation-layer.cpp | 144 +++++++++++++- src/e2ee-adaptation-layer/tests.cpp | 212 +++++++++++++++++++++ 8 files changed, 445 insertions(+), 8 deletions(-) create mode 100644 src/e2ee-adaptation-layer/CMakeLists.txt create mode 100644 src/e2ee-adaptation-layer/tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e941f15..4fe1851 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,7 @@ IF(BUILD_ALL_TESTS) SET(BUILD_YACA ON) SET(BUILD_NETHER ON) SET(BUILD_ODE ON) + SET(BUILD_E2EE_ADAPTATION_LAYER ON) ENDIF(BUILD_ALL_TESTS) # If supported for the target machine, emit position-independent code,suitable @@ -109,6 +110,7 @@ SET(TARGET_CKM_TESTS "ckm-tests") SET(TARGET_CKM_PRIVILEGED_TESTS "ckm-privileged-tests") SET(TARGET_CKMI_TESTS "ckm-integration-tests") SET(COMMON_TARGET_TEST "tests-common") +SET(TARGET_E2EE_TESTS "e2ee-tests") ############################# subdirectories ################################## diff --git a/packaging/security-tests.manifest b/packaging/security-tests.manifest index 9b87770..0f1fc1f 100644 --- a/packaging/security-tests.manifest +++ b/packaging/security-tests.manifest @@ -5,6 +5,7 @@ + diff --git a/packaging/security-tests.spec b/packaging/security-tests.spec index 249a34e..9e6e3fc 100644 --- a/packaging/security-tests.spec +++ b/packaging/security-tests.spec @@ -12,6 +12,7 @@ BuildRequires: libattr-devel BuildRequires: pkgconfig(libcap) BuildRequires: pkgconfig(libsmack) BuildRequires: pkgconfig(security-manager) + # TODO: BuildRequires: pkgconfig(key-manager) >=0.1.48 BuildRequires: pkgconfig(key-manager) BuildRequires: key-manager-initial-values BuildRequires: util-linux @@ -117,6 +118,7 @@ echo "security-tests postinst done ..." /usr/bin/yaca-test /usr/bin/nether-tests /usr/bin/ode-tests +/usr/bin/e2ee-tests %{ckm_test_dir}/* /etc/security-tests /usr/lib/security-tests/cynara-tests/plugins/single-policy/* diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 954a51a..7799501 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -122,3 +122,7 @@ ENDIF(BUILD_NETHER) IF(BUILD_ODE) ADD_SUBDIRECTORY(ode) ENDIF(BUILD_ODE) + +IF(BUILD_E2EE_ADAPTATION_LAYER) + ADD_SUBDIRECTORY(e2ee-adaptation-layer) +ENDIF(BUILD_E2EE_ADAPTATION_LAYER) diff --git a/src/ckm/ckm-common.h b/src/ckm/ckm-common.h index 77631e9..9e9204d 100644 --- a/src/ckm/ckm-common.h +++ b/src/ckm/ckm-common.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -218,3 +219,28 @@ void test_no_observer(F&& func, Args... args) RUNNER_ASSERT_MSG(false, "Unexpected exception"); } } + +class AliasRemover +{ +public: + AliasRemover(const char* alias) : alias(alias) {} + ~AliasRemover() { + ckmc_remove_alias(alias); + } + + AliasRemover(AliasRemover&& other) { + alias = other.alias; + other.alias = nullptr; + } + + AliasRemover& operator=(AliasRemover&& other) { + if (&other == this) + return *this; + + alias = other.alias; + other.alias = nullptr; + } + +private: + const char* alias; +}; diff --git a/src/e2ee-adaptation-layer/CMakeLists.txt b/src/e2ee-adaptation-layer/CMakeLists.txt new file mode 100644 index 0000000..1971096 --- /dev/null +++ b/src/e2ee-adaptation-layer/CMakeLists.txt @@ -0,0 +1,62 @@ +# Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(FindPkgConfig) + + +# Adaptation layer +SET(TARGET_E2EE_ADAPTATION_LAYER "e2ee-adaptation-layer") + +PKG_CHECK_MODULES(E2EE_ADAPTATION_LAYER_DEP + REQUIRED + key-manager # >=0.1.48 +) + +SET(E2EE_ADAPTATION_LAYER_SOURCES + e2ee-adaptation-layer.cpp +) + +ADD_LIBRARY(${TARGET_E2EE_ADAPTATION_LAYER} + STATIC ${E2EE_ADAPTATION_LAYER_SOURCES} +) + +TARGET_INCLUDE_DIRECTORIES(${TARGET_E2EE_ADAPTATION_LAYER} + SYSTEM PUBLIC ${E2EE_ADAPTATION_LAYER_DEP_INCLUDE_DIRS} +) + +TARGET_LINK_LIBRARIES(${TARGET_E2EE_ADAPTATION_LAYER} + ${E2EE_ADAPTATION_LAYER_DEP_LIBRARIES} +) + + +# Tests +SET(E2EE_TESTS_SOURCES + tests.cpp +) + +ADD_EXECUTABLE(${TARGET_E2EE_TESTS} ${E2EE_TESTS_SOURCES}) + +TARGET_INCLUDE_DIRECTORIES(${TARGET_E2EE_TESTS} + PRIVATE ${PROJECT_SOURCE_DIR}/src/common + PRIVATE ${PROJECT_SOURCE_DIR}/src/ckm +) + +TARGET_LINK_LIBRARIES(${TARGET_E2EE_TESTS} + ${TARGET_E2EE_ADAPTATION_LAYER} + ${TARGET_CKM_TEST_COMMON} +) + +INSTALL(TARGETS ${TARGET_E2EE_TESTS} + DESTINATION /usr/bin +) diff --git a/src/e2ee-adaptation-layer/e2ee-adaptation-layer.cpp b/src/e2ee-adaptation-layer/e2ee-adaptation-layer.cpp index cf78576..d5b39ec 100644 --- a/src/e2ee-adaptation-layer/e2ee-adaptation-layer.cpp +++ b/src/e2ee-adaptation-layer/e2ee-adaptation-layer.cpp @@ -16,13 +16,141 @@ #include "e2ee-adaptation-layer.h" -int ckmew_key_agreement(const char * /*private_key_alias*/, - const unsigned char * /*raw_public_key*/, - size_t /*raw_public_key_len*/, - const char * /*new_key_alias*/) +#include +#include + +#include + +namespace { + +const char* const LABEL = "label"; +const char* const CONTEXT = "context"; +const char* const SECRET_ALIAS = "temporary_shared_e2ee_secret"; + +typedef std::unique_ptr ParamsPtr; + +std::tuple makeParams() { - // TODO - return CKMC_ERROR_NONE; + ckmc_param_list_h params = nullptr; + int ret = ckmc_param_list_new(¶ms); + return std::make_tuple(ParamsPtr(params, ckmc_param_list_free), ret); +} + +typedef std::unique_ptr BufferPtr; + +std::tuple makeBuffer(const unsigned char* data, size_t size) +{ + ckmc_raw_buffer_s* buffer = nullptr; + int ret = ckmc_buffer_new(const_cast(data), size, &buffer); + return std::make_tuple(BufferPtr(buffer, ckmc_buffer_free), ret); +} + +class AliasRemover +{ +public: + AliasRemover(const char *alias) : alias(alias) {} + ~AliasRemover() { + ckmc_remove_alias(alias); + } + +private: + const char* alias; +}; + +} // anonymous namespace + +int ckmew_key_agreement(const char *private_key_alias, + const unsigned char *raw_public_key, + size_t raw_public_key_len, + const char *new_key_alias) +{ + if (private_key_alias == nullptr || raw_public_key == nullptr || raw_public_key_len == 0 || + new_key_alias == nullptr) + return CKMC_ERROR_INVALID_PARAMETER; + + ckmc_policy_s unexportable { nullptr, false }; + + auto [ecdh_params, ret] = makeParams(); + if (ret != CKMC_ERROR_NONE) + return ret; + + ret = ckmc_param_list_set_integer(ecdh_params.get(), CKMC_PARAM_ALGO_TYPE, CKMC_ALGO_ECDH); + if (ret != CKMC_ERROR_NONE) + return ret; + + auto [peers_public, ret2] = makeBuffer(raw_public_key, raw_public_key_len); + if (ret2 != CKMC_ERROR_NONE) + return ret2; + + ret = ckmc_param_list_set_buffer(ecdh_params.get(), CKMC_PARAM_ECDH_PUBKEY, peers_public.get()); + if (ret != CKMC_ERROR_NONE) + return ret; + + // derive shared secret + ret = ckmc_key_derive(ecdh_params.get(), + private_key_alias, + nullptr, + SECRET_ALIAS, + unexportable); + if (ret != CKMC_ERROR_NONE) + return ret; + + // delete secret + AliasRemover remover(SECRET_ALIAS); + + // set KBKDF params + auto [kbkdf_params, ret3] = makeParams(); + if (ret3 != CKMC_ERROR_NONE) + return ret3; + + ret = ckmc_param_list_set_integer(kbkdf_params.get(), CKMC_PARAM_ALGO_TYPE, CKMC_ALGO_KBKDF); + if (ret != CKMC_ERROR_NONE) + return ret; + + ret = ckmc_param_list_set_integer(kbkdf_params.get(), + CKMC_PARAM_KDF_PRF, + CKMC_KDF_PRF_HMAC_SHA256); + if (ret != CKMC_ERROR_NONE) + return ret; + + ret = ckmc_param_list_set_integer(kbkdf_params.get(), + CKMC_PARAM_KBKDF_MODE, + CKMC_KBKDF_MODE_COUNTER); + if (ret != CKMC_ERROR_NONE) + return ret; + + ret = ckmc_param_list_set_integer(kbkdf_params.get(), + CKMC_PARAM_KBKDF_COUNTER_LOCATION, + CKMC_KBKDF_COUNTER_BEFORE_FIXED); + if (ret != CKMC_ERROR_NONE) + return ret; + + auto [label_buf, ret4] = makeBuffer(reinterpret_cast(LABEL), + strlen(LABEL)); + if (ret4 != CKMC_ERROR_NONE) + return ret4; + + ret = ckmc_param_list_set_buffer(kbkdf_params.get(), CKMC_PARAM_KBKDF_LABEL, label_buf.get()); + if (ret != CKMC_ERROR_NONE) + return ret; + + auto [context_buf, ret5] = makeBuffer(reinterpret_cast(CONTEXT), + strlen(CONTEXT)); + if (ret5 != CKMC_ERROR_NONE) + return ret5; + + ret = ckmc_param_list_set_buffer(kbkdf_params.get(), + CKMC_PARAM_KBKDF_CONTEXT, + context_buf.get()); + if (ret != CKMC_ERROR_NONE) + return ret; + + ret = ckmc_param_list_set_integer(kbkdf_params.get(), CKMC_PARAM_KDF_LEN, 32); + if (ret != CKMC_ERROR_NONE) + return ret; + + // derive symmetric key + return ckmc_key_derive(kbkdf_params.get(), SECRET_ALIAS, nullptr, new_key_alias, unexportable); } int ckmew_key_derive_pbkdf2(const char * /*password*/, @@ -38,7 +166,7 @@ int ckmew_key_derive_pbkdf2(const char * /*password*/, int ckmew_get_ocf_cert_chain(char ** /*cert_chain*/, size_t * /*cert_chain_len*/) { // TODO - return DCM_ERROR_NONE; + return 0; } int ckmew_sign_with_ocf(const char * /*public_key_alias*/, @@ -46,5 +174,5 @@ int ckmew_sign_with_ocf(const char * /*public_key_alias*/, ckmc_raw_buffer_s** /*signature_buf*/) { // TODO - return DCM_ERROR_NONE; + return 0; } diff --git a/src/e2ee-adaptation-layer/tests.cpp b/src/e2ee-adaptation-layer/tests.cpp new file mode 100644 index 0000000..ff743fd --- /dev/null +++ b/src/e2ee-adaptation-layer/tests.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +#include "e2ee-adaptation-layer.h" + +#include +#include +#include +#include + +namespace { + +const uid_t UID = 5001; + +struct KeyAliasPair +{ + std::string prv; + std::string pub; +}; + +const KeyAliasPair OURS = { "our_ec_private", "our_ec_public" }; +const KeyAliasPair PEERS = { "peer_ec_private", "peer_ec_public" }; +const KeyAliasPair PEERS2 = { "peer2_ec_private", "peer2_ec_public" }; +const KeyAliasPair WRONG = { "wrong_ec_private", "wrong_ec_public" }; +const KeyAliasPair RSA = { "rsa_private", "rsa_public" }; + +const ckmc_policy_s UNEXPORTABLE { nullptr, false }; +const ckmc_policy_s EXPORTABLE { nullptr, true }; + +class EALGroupFixture: public DPL::Test::TestGroup +{ +private: + void GenerateEC(ckmc_ec_type_e curve, + const KeyAliasPair& pair, + const ckmc_policy_s& policy_prv, + const ckmc_policy_s& policy_pub) + { + ckmc_remove_alias(pair.prv.c_str()); + ckmc_remove_alias(pair.pub.c_str()); + assert_positive(ckmc_create_key_pair_ecdsa, + curve, + pair.prv.c_str(), + pair.pub.c_str(), + policy_prv, + policy_pub); + } + +public: + void Init() override + { + remove_user_data(UID); + assert_positive(ckmc_unlock_user_key, UID, "db-pass"); + + GenerateEC(CKMC_EC_PRIME256V1, OURS, UNEXPORTABLE, EXPORTABLE); + GenerateEC(CKMC_EC_PRIME256V1, PEERS, UNEXPORTABLE, EXPORTABLE); + GenerateEC(CKMC_EC_PRIME256V1, PEERS2, EXPORTABLE, EXPORTABLE); + GenerateEC(CKMC_EC_PRIME192V1, WRONG, UNEXPORTABLE, EXPORTABLE); + + ckmc_remove_alias(RSA.prv.c_str()); + ckmc_remove_alias(RSA.pub.c_str()); + assert_positive(ckmc_create_key_pair_rsa, + 1024, + RSA.prv.c_str(), + RSA.pub.c_str(), + UNEXPORTABLE, + EXPORTABLE); + } + + void Finish() override + { + int ret = ckmc_lock_user_key(UID); + if (ret != CKMC_ERROR_NONE) + RUNNER_ERROR_MSG("DB lock failed: " << CKMCErrorToString(ret)); + remove_user_data(UID); + } +}; +typedef std::unique_ptr KeyPtr; + +KeyPtr getKey(const std::string& alias) +{ + ckmc_key_s* key = nullptr; + assert_positive(ckmc_get_key, alias.c_str(), "", &key); + + return KeyPtr(key, ckmc_key_free); +} + +AliasRemover keyAgreement(const std::string &prv, const std::string& pub, const char* derived) +{ + auto pub_key = getKey(pub); + assert_positive(ckmew_key_agreement, prv.c_str(), pub_key->raw_key, pub_key->key_size, derived); + + return AliasRemover(derived); +} + +} // namespace anonymous + +RUNNER_TEST_GROUP_INIT_ENV(E2EE_ADAPTATION_LAYER, EALGroupFixture); + +RUNNER_TEST(TEAL_0010_key_agreement_positive) +{ + const char* const OURS_DERIVED = "ours_derived"; + const char* const PEERS_DERIVED = "peers_derived"; + const char* const PEERS2_DERIVED = "peers2_derived"; + + auto our_remover = keyAgreement(OURS.prv, PEERS.pub, OURS_DERIVED); + auto peer_remover = keyAgreement(PEERS.prv, OURS.pub, PEERS_DERIVED); + auto peer2_remover = keyAgreement(PEERS2.prv, OURS.pub, PEERS2_DERIVED); + + auto plain = create_raw_buffer(createRandomBufferCAPI(512)); + auto iv = create_raw_buffer(createRandomBufferCAPI(16)); + + auto params = createParamListPtr(); + setParam(params, CKMC_PARAM_ALGO_TYPE, CKMC_ALGO_AES_CTR); + setParam(params, CKMC_PARAM_ED_IV, iv.get()); + + ckmc_raw_buffer_s* encrypted = nullptr; + assert_positive(ckmc_encrypt_data, params.get(), OURS_DERIVED, "", *plain.get(), &encrypted); + auto encryptedPtr = create_raw_buffer(encrypted); + + ckmc_raw_buffer_s* decrypted = nullptr; + assert_positive(ckmc_decrypt_data, params.get(), PEERS_DERIVED, "", *encrypted, &decrypted); + auto decryptedPtr = create_raw_buffer(decrypted); + + assert_buffers_equal(plain.get(), decrypted); + + decryptedPtr.reset(); + decrypted = nullptr; + assert_positive(ckmc_decrypt_data, params.get(), PEERS2_DERIVED, "", *encrypted, &decrypted); + decryptedPtr = create_raw_buffer(decrypted); + + assert_buffers_equal(plain.get(), decrypted, false); +} + + +RUNNER_TEST(TEAL_0020_key_agreement_wrong_arguments) +{ + const char* const DERIVED = "derived"; + + auto pub_key = getKey(PEERS.pub); + + auto invalid = [](const char* prv, + const unsigned char* pub, + size_t pub_size, + const char* derived) + { + assert_invalid_param(ckmew_key_agreement, prv, pub, pub_size, derived); + }; + + auto garbage = create_raw_buffer(createRandomBufferCAPI(pub_key->key_size)); + + invalid(nullptr, pub_key->raw_key, pub_key->key_size, DERIVED); + invalid(OURS.pub.c_str(), pub_key->raw_key, pub_key->key_size, DERIVED); + invalid(OURS.prv.c_str(), nullptr, pub_key->key_size, DERIVED); + invalid(OURS.prv.c_str(), pub_key->raw_key, 6, DERIVED); + invalid(OURS.prv.c_str(), garbage->data, garbage->size, DERIVED); + invalid(OURS.prv.c_str(), pub_key->raw_key, 0, DERIVED); + invalid(OURS.prv.c_str(), pub_key->raw_key, pub_key->key_size, nullptr); +} + +RUNNER_TEST(TEAL_0030_key_agreement_wrong_aliases) +{ + const char* const DERIVED = "derived"; + + auto pub_key = getKey(PEERS.pub); + + assert_result(CKMC_ERROR_DB_ALIAS_UNKNOWN, + ckmew_key_agreement, + "", + pub_key->raw_key, + pub_key->key_size, + DERIVED); + + assert_result(CKMC_ERROR_DB_ALIAS_UNKNOWN, + ckmew_key_agreement, + "nonexistent-alias", + pub_key->raw_key, + pub_key->key_size, + DERIVED); + + assert_positive(ckmew_key_agreement, + OURS.prv.c_str(), + pub_key->raw_key, + pub_key->key_size, + DERIVED); + + AliasRemover remover(DERIVED); + + assert_result(CKMC_ERROR_DB_ALIAS_EXISTS, + ckmew_key_agreement, + OURS.prv.c_str(), + pub_key->raw_key, + pub_key->key_size, + DERIVED); +} + +int main(int argc, char *argv[]) +{ + return DPL::Test::TestRunnerSingleton::Instance().ExecTestRunner(argc, argv); +} -- 2.7.4