E2EE: Key agreement API implementation 14/289114/10
authorKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Wed, 1 Mar 2023 09:54:21 +0000 (10:54 +0100)
committerKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Tue, 21 Mar 2023 15:25:41 +0000 (16:25 +0100)
Tests included

Change-Id: Iab51c84b848060f3392cb11de7dedd7ab2580034

CMakeLists.txt
packaging/security-tests.manifest
packaging/security-tests.spec
src/CMakeLists.txt
src/ckm/ckm-common.h
src/e2ee-adaptation-layer/CMakeLists.txt [new file with mode: 0644]
src/e2ee-adaptation-layer/e2ee-adaptation-layer.cpp
src/e2ee-adaptation-layer/tests.cpp [new file with mode: 0644]

index e941f15..4fe1851 100644 (file)
@@ -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 ##################################
 
index 9b87770..0f1fc1f 100644 (file)
@@ -5,6 +5,7 @@
         <filesystem path="/usr/bin/security-manager-tests" exec_label="System::Privileged" />
         <filesystem path="/usr/bin/cynara-tests" exec_label="_" />
         <filesystem path="/usr/bin/ckm-tests" exec_label="User" />
+        <filesystem path="/usr/bin/e2ee-tests" exec_label="User" />
         <filesystem path="/usr/bin/ckm-privileged-tests" exec_label="System::Privileged" />
         <filesystem path="/usr/bin/nether-tests" exec_label="System::Privileged" />
         <filesystem path="/usr/bin/libteec-tests" exec_label="System::Privileged" />
index 249a34e..9e6e3fc 100644 (file)
@@ -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/*
index 954a51a..7799501 100644 (file)
@@ -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)
index 77631e9..9e9204d 100644 (file)
@@ -28,6 +28,7 @@
 #include <ckm/ckm-manager-async.h>
 #include <ckmc/ckmc-type.h>
 #include <ckmc/ckmc-error.h>
+#include <ckmc/ckmc-manager.h>
 #include <tests_common.h>
 #include <sys/types.h>
 
@@ -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 (file)
index 0000000..1971096
--- /dev/null
@@ -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
+)
index cf78576..d5b39ec 100644 (file)
 
 #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 <cstring>
+#include <memory>
+
+#include <ckmc/ckmc-manager.h>
+
+namespace {
+
+const char* const LABEL = "label";
+const char* const CONTEXT = "context";
+const char* const SECRET_ALIAS = "temporary_shared_e2ee_secret";
+
+typedef std::unique_ptr<struct __ckmc_param_list, decltype(&ckmc_param_list_free)> ParamsPtr;
+
+std::tuple<ParamsPtr, int> makeParams()
 {
-    // TODO
-    return CKMC_ERROR_NONE;
+    ckmc_param_list_h params = nullptr;
+    int ret = ckmc_param_list_new(&params);
+    return std::make_tuple(ParamsPtr(params, ckmc_param_list_free), ret);
+}
+
+typedef std::unique_ptr<ckmc_raw_buffer_s, decltype(&ckmc_buffer_free)> BufferPtr;
+
+std::tuple<BufferPtr, int> makeBuffer(const unsigned char* data, size_t size)
+{
+    ckmc_raw_buffer_s* buffer = nullptr;
+    int ret = ckmc_buffer_new(const_cast<unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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 (file)
index 0000000..ff743fd
--- /dev/null
@@ -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 <dpl/test/test_runner.h>
+#include <ckm-common.h>
+#include <ckmc/ckmc-manager.h>
+#include <ckmc/ckmc-control.h>
+
+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<ckmc_key_s, decltype(&ckmc_key_free)> 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);
+}