E2EE: OCF API implementation 16/289116/11
authorKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Wed, 1 Mar 2023 09:54:50 +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: I04a3b56d66b51b5508e7fa4f14d923e876122f78

packaging/security-tests.spec
src/e2ee-adaptation-layer/CMakeLists.txt
src/e2ee-adaptation-layer/e2ee-adaptation-layer.cpp
src/e2ee-adaptation-layer/tests.cpp

index 9e6e3fc..4d21ef0 100644 (file)
@@ -36,6 +36,7 @@ BuildRequires: pkgconfig(vconf)
 BuildRequires: pkgconfig(libgum) >= 1.0.5
 BuildRequires: pkgconfig(security-privilege-manager)
 BuildRequires: pkgconfig(libsystemd)
+BuildRequires: pkgconfig(device-certificate-manager) >= 2.1
 BuildRequires: openssl1.1
 Requires: perf
 Requires: gdb
index 03cff11..ab8791a 100644 (file)
@@ -21,6 +21,7 @@ SET(TARGET_E2EE_ADAPTATION_LAYER "e2ee-adaptation-layer")
 PKG_CHECK_MODULES(E2EE_ADAPTATION_LAYER_DEP
     REQUIRED
     key-manager # >=0.1.48
+    device-certificate-manager>=2.1
     openssl1.1
 )
 
index 73d068e..3e86ae0 100644 (file)
 #include "e2ee-adaptation-layer.h"
 
 #include <cstring>
+#include <cstdlib>
+
 #include <memory>
 
 #include <openssl/evp.h>
 
 #include <ckmc/ckmc-manager.h>
+#include <device_certificate_manager.h>
 
 namespace {
 
@@ -31,6 +34,10 @@ const char* const SECRET_ALIAS = "temporary_shared_e2ee_secret";
 constexpr size_t ITERATIONS = 1000;
 
 typedef std::unique_ptr<struct __ckmc_param_list, decltype(&ckmc_param_list_free)> ParamsPtr;
+typedef std::unique_ptr<void, decltype(&dcm_free_key_context)> DcmCtxPtr;
+typedef std::unique_ptr<ckmc_raw_buffer_s, decltype(&ckmc_buffer_free)> BufferPtr;
+typedef std::unique_ptr<ckmc_key_s, decltype(&ckmc_key_free)> KeyPtr;
+typedef std::unique_ptr<dcm_e2ee_bundle_s, decltype(&dcm_e2ee_free_bundle)> BundlePtr;
 
 std::tuple<ParamsPtr, int> makeParams()
 {
@@ -39,8 +46,6 @@ std::tuple<ParamsPtr, int> makeParams()
     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;
@@ -60,6 +65,13 @@ private:
     const char* alias;
 };
 
+std::tuple<DcmCtxPtr, int> getOcfContext()
+{
+    void* ocf_ctx = nullptr;
+    int ret = dcm_create_key_context(nullptr, nullptr, "ECDSA", &ocf_ctx);
+    return std::make_tuple(DcmCtxPtr(ocf_ctx, dcm_free_key_context), ret);
+}
+
 } // anonymous namespace
 
 int ckmew_key_agreement(const char *private_key_alias,
@@ -188,16 +200,65 @@ int ckmew_key_derive_pbkdf2(const char *password,
     return ret;
 }
 
-int ckmew_get_ocf_cert_chain(char ** /*cert_chain*/, size_t * /*cert_chain_len*/)
+int ckmew_get_ocf_cert_chain(char **cert_chain, size_t *cert_chain_len)
 {
-    // TODO
-    return 0;
+    auto [ocf, ret] = getOcfContext();
+    if (ret != DCM_ERROR_NONE)
+        return ret;
+
+    return dcm_get_certificate_chain(ocf.get(), cert_chain, cert_chain_len);
 }
 
-int ckmew_sign_with_ocf(const char * /*public_key_alias*/,
-                        ckmc_raw_buffer_s** /*message_buf*/,
-                        ckmc_raw_buffer_s** /*signature_buf*/)
+int ckmew_sign_with_ocf(const char *public_key_alias,
+                        ckmc_raw_buffer_s **message_buf,
+                        ckmc_raw_buffer_s **signature_buf)
 {
-    // TODO
-    return 0;
+    if (public_key_alias == nullptr || message_buf == nullptr || signature_buf == nullptr)
+        return DCM_ERROR_INVALID_PARAMETER;
+
+    // get ocf context
+    auto [ocf, ret] = getOcfContext();
+    if (ret != DCM_ERROR_NONE)
+        return ret;
+
+    // get device public key
+    ckmc_key_s* device_pub_key = nullptr;
+    ret = ckmc_get_key(public_key_alias, nullptr, &device_pub_key);
+    if (ret != CKMC_ERROR_NONE)
+        return ret; // This is a CKM error!
+
+    KeyPtr device_pub_key_ptr(device_pub_key, ckmc_key_free);
+
+    // pack & sign device public key
+    dcm_e2ee_bundle_h bundle = nullptr;
+    unsigned char* signature = nullptr;
+    size_t signature_len = 0;
+    ret = dcm_e2ee_create_signed_bundle(ocf.get(),
+                                        DCM_DIGEST_SHA256,
+                                        device_pub_key->raw_key,
+                                        device_pub_key->key_size,
+                                        &bundle,
+                                        reinterpret_cast<char**>(&signature),
+                                        &signature_len);
+    if (ret != DCM_ERROR_NONE)
+        return ret;
+
+    BundlePtr bundle_ptr(bundle, dcm_e2ee_free_bundle);
+    auto [signature_ptr, ret2] = makeBuffer(signature, signature_len);
+    if (ret2 != CKMC_ERROR_NONE)
+        return ret2; // This is a CKM error!
+
+    const unsigned char* message = nullptr;
+    size_t message_len = 0;
+    ret = dcm_e2ee_get_bundle_message(bundle, &message, &message_len);
+    if (ret != DCM_ERROR_NONE)
+        return ret;
+
+    ret = ckmc_buffer_new(const_cast<unsigned char*>(message), message_len, message_buf);
+    if (ret != CKMC_ERROR_NONE)
+        return ret; // This is a CKM error!
+
+    *signature_buf = signature_ptr.release();
+
+    return DCM_ERROR_NONE;
 }
index e60d543..f10b5c6 100644 (file)
 
 #include "e2ee-adaptation-layer.h"
 
+#include <sstream>
+
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/crypto.h>
+
 #include <dpl/test/test_runner.h>
 #include <ckm-common.h>
 #include <ckmc/ckmc-manager.h>
 #include <ckmc/ckmc-control.h>
+#include <device_certificate_manager.h>
 
 namespace {
 
@@ -35,7 +43,7 @@ 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 KeyAliasPair RSA_KEYS = { "rsa_private", "rsa_public" };
 
 const char* const DERIVED = "derived";
 
@@ -45,6 +53,43 @@ const unsigned char SALT[SALT_LEN] = {};
 const ckmc_policy_s UNEXPORTABLE { nullptr, false };
 const ckmc_policy_s EXPORTABLE { nullptr, true };
 
+#define ERRORDESCRIBE(name) case name: return #name
+const char * E2EEErrorToString(int error) {
+    switch(error) {
+        ERRORDESCRIBE(DCM_ERROR_INVALID_PARAMETER);
+        ERRORDESCRIBE(DCM_ERROR_OUT_OF_MEMORY);
+        ERRORDESCRIBE(DCM_ERROR_PERMISSION_DENIED);
+        ERRORDESCRIBE(DCM_ERROR_NOT_SUPPORTED);
+        ERRORDESCRIBE(DCM_ERROR_NO_DATA);
+        ERRORDESCRIBE(DCM_ERROR_UNKNOWN);
+        ERRORDESCRIBE(DCM_ERROR_SOCKET);
+        default: return CKMCErrorToString(error);
+    }
+}
+#undef ERRORDESCRIBE
+
+// RUNNER_ASSERT wrappers
+template <typename F, typename... Args>
+void e2ee_result(int expected, F&& func, Args... args)
+{
+    int ret = func(args...);
+    RUNNER_ASSERT_MSG(ret == expected,
+                      "Expected: " << E2EEErrorToString(expected) << "(" << expected << ")"
+                      " got: " << E2EEErrorToString(ret) << "(" << ret << ")");
+}
+
+template <typename F, typename... Args>
+void e2ee_positive(F&& func, Args... args)
+{
+    e2ee_result(DCM_ERROR_NONE, std::move(func), args...);
+}
+
+template <typename F, typename... Args>
+void e2ee_invalid_param(F&& func, Args... args)
+{
+    e2ee_result(DCM_ERROR_INVALID_PARAMETER, std::move(func), args...);
+}
+
 class EALGroupFixture: public DPL::Test::TestGroup
 {
 private:
@@ -55,33 +100,33 @@ private:
     {
         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);
+        e2ee_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");
+        e2ee_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);
+        ckmc_remove_alias(RSA_KEYS.prv.c_str());
+        ckmc_remove_alias(RSA_KEYS.pub.c_str());
+        e2ee_positive(ckmc_create_key_pair_rsa,
+                      1024,
+                      RSA_KEYS.prv.c_str(),
+                      RSA_KEYS.pub.c_str(),
+                      UNEXPORTABLE,
+                      EXPORTABLE);
     }
 
     void Finish() override
@@ -97,7 +142,7 @@ 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);
+    e2ee_positive(ckmc_get_key, alias.c_str(), "", &key);
 
     return KeyPtr(key, ckmc_key_free);
 }
@@ -105,11 +150,253 @@ KeyPtr getKey(const std::string& alias)
 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);
+    e2ee_positive(ckmew_key_agreement, prv.c_str(), pub_key->raw_key, pub_key->key_size, derived);
 
     return AliasRemover(derived);
 }
 
+template <typename T, void (*Fn)(T*)>
+struct Free {
+    explicit Free(T* ptr) : ptr(ptr) {}
+    ~Free() {
+        Fn(ptr);
+    }
+    Free(const Free&) = delete;
+    Free& operator=(const Free&) = delete;
+    T* operator*() { return ptr; }
+private:
+    T* ptr;
+};
+
+void OPENSSL_free_wrapper(unsigned char* ptr)
+{
+    OPENSSL_free(static_cast<void*>(ptr));
+}
+
+typedef Free<dcm_e2ee_bundle_s, dcm_e2ee_free_bundle> FreeBundle;
+typedef Free<void, free> FreeVoid;
+typedef Free<BIO, BIO_free_all> FreeBio;
+typedef Free<unsigned char, OPENSSL_free_wrapper> FreeOpenssl;
+typedef Free<X509, X509_free> FreeX509;
+typedef Free<EVP_MD_CTX, EVP_MD_CTX_free> FreeMdCtx;
+typedef Free<X509_STORE_CTX, X509_STORE_CTX_free> FreeX509StoreCtx;
+
+typedef STACK_OF(X509) X509_STACK;
+typedef std::unique_ptr<X509_STACK, decltype(&sk_X509_free)> X509StackPtr;
+
+X509StackPtr getOcfChain()
+{
+    // extract OCFs root certificate
+    char* ocfChain = nullptr;
+    size_t ocfChainLen = 0;
+
+    // OCF cert + common OCFs root cert
+    e2ee_positive(ckmew_get_ocf_cert_chain, &ocfChain, &ocfChainLen);
+
+    RUNNER_ASSERT_MSG(ocfChain != nullptr, "OCF cert chain is empty");
+
+    FreeVoid ocfChainFree(static_cast<void*>(ocfChain));
+
+    RUNNER_ASSERT_MSG(ocfChainLen > 0, "OCF cert chain has 0 length");
+
+    auto bio = (BIO_new(BIO_s_mem()));
+    RUNNER_ASSERT_MSG(bio != nullptr, "BIO_new failed");
+    FreeBio bioFree(bio);
+
+    auto written = BIO_write(bio, ocfChain, ocfChainLen);
+    RUNNER_ASSERT_MSG(written >= 0, "BIO_write failed");
+    RUNNER_ASSERT_MSG(static_cast<size_t>(written) == ocfChainLen, "OCF chain write is incomplete");
+
+    // build a X509 chain
+    X509StackPtr chainPtr(sk_X509_new_null(), sk_X509_free);
+    RUNNER_ASSERT_MSG(chainPtr, "sk_X509_new_null failed");
+
+    X509* cert = nullptr;
+    while((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != nullptr)
+        RUNNER_ASSERT_MSG(sk_X509_push(chainPtr.get(), cert) > 0, "Nothing was pushed to stack");
+
+    RUNNER_ASSERT_MSG(sk_X509_num(chainPtr.get()) >= 1, "No certificates in the chain");
+
+    // TODO this requires 2-element OCF cert chain
+    if (sk_X509_num(chainPtr.get()) < 2)
+        RUNNER_ERROR_MSG("Insufficient number of certificates in the chain");
+
+    return chainPtr;
+}
+
+struct ustreambuf: public std::basic_streambuf<unsigned char> {
+    ustreambuf(unsigned char* buf, size_t size) : std::basic_streambuf<unsigned char>()
+    {
+        pubsetbuf(buf, size);
+    }
+};
+
+class Peer
+{
+public:
+    Peer(const KeyAliasPair& keys, const char* derived) : ours(keys), derived(derived) {}
+    ~Peer() {
+        ckmc_remove_alias(derived);
+    }
+
+    std::string send()
+    {
+        ckmc_raw_buffer_s* message = nullptr;
+        ckmc_raw_buffer_s* signature = nullptr;
+        e2ee_positive(ckmew_sign_with_ocf, ours.pub.c_str(), &message, &signature);
+
+        auto messagePtr = create_raw_buffer(message);
+        auto signaturePtr = create_raw_buffer(signature);
+
+        RUNNER_ASSERT_MSG(messagePtr->size > 0, "Message buffer has 0 length");
+        RUNNER_ASSERT_MSG(messagePtr->data != nullptr, "Message buffer has no data");
+
+        RUNNER_ASSERT_MSG(signaturePtr->size > 0, "Signature buffer has 0 length");
+        RUNNER_ASSERT_MSG(signaturePtr->data != nullptr, "Signature buffer has no data");
+
+        // extract OCF key certificate
+        auto chainPtr = getOcfChain();
+        auto ocfCertX509 = sk_X509_value(chainPtr.get(), 0);
+
+        RUNNER_ASSERT_MSG(ocfCertX509 != nullptr, "OCF certificate extraction failed");
+
+        // convert it to DER
+        unsigned char *ocfCert = nullptr;
+        size_t ocfCertLen = i2d_X509(ocfCertX509, &ocfCert);
+
+        RUNNER_ASSERT_MSG(ocfCertLen > 0, "OCF certificate has 0 length");
+        RUNNER_ASSERT_MSG(ocfCert != nullptr, "OCF certificate is empty");
+        FreeOpenssl certFree(ocfCert);
+
+        // serialize
+        std::ostringstream os;
+        auto serialize = [&](const unsigned char* data, size_t size){
+            os.write(reinterpret_cast<const char*>(&size), sizeof(size));
+            os.write(reinterpret_cast<const char*>(data), size);
+        };
+
+        serialize(message->data, message->size);
+        serialize(signature->data, signature->size);
+        serialize(ocfCert, ocfCertLen);
+
+        return os.str();
+    }
+
+    void receive(std::string&& buffer)
+    {
+        // deserialize
+        std::istringstream is(buffer);
+        auto deserialize = [&]()
+        {
+            size_t size;
+            is.read(reinterpret_cast<char*>(&size), sizeof(size));
+            RUNNER_ASSERT_MSG(size > 0, "Deserialized 0 length vector");
+            std::vector<unsigned char> data(size);
+            is.read(reinterpret_cast<char*>(data.data()), size);
+
+            return data;
+        };
+
+        auto message = deserialize();
+        auto signature = deserialize();
+        auto ocfCert = deserialize();
+
+        // decompose message
+        unsigned char* messageDup = static_cast<unsigned char*>(malloc(message.size()));
+        RUNNER_ASSERT_MSG(messageDup != nullptr, "Memory allocation failed");
+        memcpy(messageDup, message.data(), message.size());
+
+        dcm_e2ee_bundle_h bundle = nullptr;
+        e2ee_positive(dcm_e2ee_create_bundle, messageDup, message.size(), &bundle);
+        RUNNER_ASSERT_MSG(bundle != nullptr, "Bundle creation failed");
+        FreeBundle freeBundle(bundle);
+
+        const char* platform = nullptr;
+        e2ee_positive(dcm_e2ee_get_bundle_platform, bundle, &platform);
+        RUNNER_ASSERT_MSG(strcmp(platform, "Tizen") == 0, "Unexpected platform:" << platform);
+
+        char* label = NULL;
+        ssize_t size = smack_new_label_from_self(&label);
+        RUNNER_ASSERT_MSG(size > 0 &&  label != nullptr, "Smack label acquisition failed");
+        FreeVoid freeLabel(static_cast<void*>(label));
+
+        const char* pkgId = nullptr;
+        e2ee_positive(dcm_e2ee_get_bundle_pkg_id, bundle, &pkgId);
+        RUNNER_ASSERT_MSG(strcmp(pkgId, label) == 0, "Unexpected pkg id:" << pkgId);
+
+        const unsigned char* peerPubDevKey = nullptr;
+        size_t peerPubDevKeyLen = 0;
+        e2ee_positive(dcm_e2ee_get_bundle_payload, bundle, &peerPubDevKey, &peerPubDevKeyLen);
+        RUNNER_ASSERT_MSG(peerPubDevKey != nullptr, "Empty public key");
+        RUNNER_ASSERT_MSG(peerPubDevKeyLen > 0, "Public key has zero length");
+
+        // parse OCF certificate
+        const unsigned char* ocfCertPtr = ocfCert.data();
+        auto ocfCertX509 = d2i_X509(nullptr, &ocfCertPtr, ocfCert.size());
+        RUNNER_ASSERT_MSG(ocfCertX509 != nullptr, "OCF certificate parsing failed");
+        FreeX509 freeCert(ocfCertX509);
+
+        // extract OCF public key from OCF certificate
+        EVP_PKEY *ocfPubKey = X509_get0_pubkey(ocfCertX509);
+        RUNNER_ASSERT_MSG(ocfPubKey != nullptr, "Can't get public key from OCF certificate");
+
+        // verify OCF signature
+        EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+        RUNNER_ASSERT_MSG(mdctx != nullptr, "EVP_MD_CTX_new failed");
+        FreeMdCtx freeMd(mdctx);
+
+        int ret = EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, ocfPubKey);
+        RUNNER_ASSERT_MSG(ret == 1, "EVP_DigestVerifyInit failed");
+
+        ret = EVP_DigestVerifyUpdate(mdctx, message.data(), message.size());
+        RUNNER_ASSERT_MSG(ret == 1, "EVP_DigestVerifyUpdate failed");
+
+        ret = EVP_DigestVerifyFinal(mdctx, signature.data(), signature.size());
+        RUNNER_ASSERT_MSG(ret == 1, "OCF signature verification failed");
+
+        // verify received cert with local certchain
+        auto chainPtr = getOcfChain();
+
+        // pop the first certificate
+        sk_X509_shift(chainPtr.get());
+
+        X509_STORE* store = X509_STORE_new();
+        FreeX509StoreCtx storeCtx(X509_STORE_CTX_new());
+        // store becomes a member of storeCtx
+        ret = X509_STORE_CTX_init(*storeCtx, store, ocfCertX509, chainPtr.get());
+        RUNNER_ASSERT_MSG(ret == 1, "X509_STORE_CTX_init failed");
+        ret = X509_verify_cert(*storeCtx);
+        // TODO this requires 2-element OCF cert chain
+        if (ret != 1)
+            RUNNER_ERROR_MSG("OCF certificate verification failed");
+
+        // derive shared key
+        e2ee_positive(ckmew_key_agreement,
+                      ours.prv.c_str(),
+                      peerPubDevKey,
+                      peerPubDevKeyLen,
+                      derived);
+    }
+
+    RawBufferPtr encrypt(const ParamListPtr& params, const RawBufferPtr& plain)
+    {
+        ckmc_raw_buffer_s* encrypted = nullptr;
+        e2ee_positive(ckmc_encrypt_data, params.get(), derived, "", *plain.get(), &encrypted);
+        return create_raw_buffer(encrypted);
+    }
+
+    RawBufferPtr decrypt(const ParamListPtr& params, const RawBufferPtr& encrypted)
+    {
+        ckmc_raw_buffer_s* decrypted = nullptr;
+        e2ee_positive(ckmc_decrypt_data, params.get(), derived, "", *encrypted.get(), &decrypted);
+        return create_raw_buffer(decrypted);
+    }
+
+private:
+    const KeyAliasPair& ours;
+    const char* derived;
+};
+
 } // namespace anonymous
 
 RUNNER_TEST_GROUP_INIT_ENV(E2EE_ADAPTATION_LAYER, EALGroupFixture);
@@ -132,18 +419,18 @@ RUNNER_TEST(TEAL_0010_key_agreement_positive)
     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);
+    e2ee_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);
+    e2ee_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);
+    e2ee_positive(ckmc_decrypt_data, params.get(), PEERS2_DERIVED, "", *encrypted, &decrypted);
     decryptedPtr = create_raw_buffer(decrypted);
 
     assert_buffers_equal(plain.get(), decrypted, false);
@@ -159,7 +446,7 @@ RUNNER_TEST(TEAL_0020_key_agreement_wrong_arguments)
                       size_t pub_size,
                       const char* derived)
     {
-        assert_invalid_param(ckmew_key_agreement, prv, pub, pub_size, derived);
+        e2ee_invalid_param(ckmew_key_agreement, prv, pub, pub_size, derived);
     };
 
     auto garbage = create_raw_buffer(createRandomBufferCAPI(pub_key->key_size));
@@ -179,34 +466,34 @@ RUNNER_TEST(TEAL_0030_key_agreement_wrong_aliases)
 
     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",
+    e2ee_result(CKMC_ERROR_DB_ALIAS_UNKNOWN,
+                ckmew_key_agreement,
+                "",
+                pub_key->raw_key,
+                pub_key->key_size,
+                DERIVED);
+
+    e2ee_result(CKMC_ERROR_DB_ALIAS_UNKNOWN,
+                ckmew_key_agreement,
+                "nonexistent-alias",
+                pub_key->raw_key,
+                pub_key->key_size,
+                DERIVED);
+
+    e2ee_positive(ckmew_key_agreement,
+                  OURS.prv.c_str(),
                   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);
+    e2ee_result(CKMC_ERROR_DB_ALIAS_EXISTS,
+                ckmew_key_agreement,
+                OURS.prv.c_str(),
+                pub_key->raw_key,
+                pub_key->key_size,
+                DERIVED);
 }
 
 RUNNER_TEST(TEAL_1000_pbkdf_positive)
@@ -221,11 +508,11 @@ RUNNER_TEST(TEAL_1000_pbkdf_positive)
     setParam(params, CKMC_PARAM_ALGO_TYPE, CKMC_ALGO_AES_CTR);
     setParam(params, CKMC_PARAM_ED_IV, iv.get());
 
-    assert_positive(ckmew_key_derive_pbkdf2, "password", salt->data, salt->size, KEY_LEN, DERIVED);
+    e2ee_positive(ckmew_key_derive_pbkdf2, "password", salt->data, salt->size, KEY_LEN, DERIVED);
     auto remover1 = AliasRemover(DERIVED);
 
     ckmc_raw_buffer_s* encrypted = nullptr;
-    assert_positive(ckmc_encrypt_data, params.get(), DERIVED, "", *plain.get(), &encrypted);
+    e2ee_positive(ckmc_encrypt_data, params.get(), DERIVED, "", *plain.get(), &encrypted);
     auto encryptedPtr = create_raw_buffer(encrypted);
 
     auto deriveAndDecrypt = [&encryptedPtr, &params](const char* password,
@@ -234,16 +521,16 @@ RUNNER_TEST(TEAL_1000_pbkdf_positive)
                                                      size_t key_len)
     {
         const char* const DERIVED2 = "derived2";
-        assert_positive(ckmew_key_derive_pbkdf2, password, salt, salt_len, key_len, DERIVED2);
+        e2ee_positive(ckmew_key_derive_pbkdf2, password, salt, salt_len, key_len, DERIVED2);
         auto remover = AliasRemover(DERIVED2);
 
         ckmc_raw_buffer_s* decrypted = nullptr;
-        assert_positive(ckmc_decrypt_data,
-                        params.get(),
-                        DERIVED2,
-                        "",
-                        *encryptedPtr.get(),
-                        &decrypted);
+        e2ee_positive(ckmc_decrypt_data,
+                      params.get(),
+                      DERIVED2,
+                      "",
+                      *encryptedPtr.get(),
+                      &decrypted);
 
         return create_raw_buffer(decrypted);
     };
@@ -267,19 +554,19 @@ RUNNER_TEST(TEAL_1000_pbkdf_positive)
 
 RUNNER_TEST(TEAL_1010_pbkdf_invalid_arguments)
 {
-    assert_invalid_param(ckmew_key_derive_pbkdf2, nullptr,    SALT,    SALT_LEN, 32, DERIVED);
-    assert_invalid_param(ckmew_key_derive_pbkdf2, "password", nullptr, SALT_LEN, 32, DERIVED);
-    assert_invalid_param(ckmew_key_derive_pbkdf2, "password", SALT,    SALT_LEN, 32, nullptr);
-    assert_invalid_param(ckmew_key_derive_pbkdf2, "password", SALT,    SALT_LEN, 0,  DERIVED);
+    e2ee_invalid_param(ckmew_key_derive_pbkdf2, nullptr,    SALT,    SALT_LEN, 32, DERIVED);
+    e2ee_invalid_param(ckmew_key_derive_pbkdf2, "password", nullptr, SALT_LEN, 32, DERIVED);
+    e2ee_invalid_param(ckmew_key_derive_pbkdf2, "password", SALT,    SALT_LEN, 32, nullptr);
+    e2ee_invalid_param(ckmew_key_derive_pbkdf2, "password", SALT,    SALT_LEN, 0,  DERIVED);
 
     auto invalidFormat = [&](size_t key_len) {
-        assert_result(CKMC_ERROR_INVALID_FORMAT,
-                      ckmew_key_derive_pbkdf2,
-                      "password",
-                      SALT,
-                      SALT_LEN,
-                      key_len,
-                      DERIVED);
+        e2ee_result(CKMC_ERROR_INVALID_FORMAT,
+                    ckmew_key_derive_pbkdf2,
+                    "password",
+                    SALT,
+                    SALT_LEN,
+                    key_len,
+                    DERIVED);
     };
     invalidFormat(64);
     invalidFormat(31);
@@ -289,17 +576,93 @@ RUNNER_TEST(TEAL_1010_pbkdf_invalid_arguments)
 
 RUNNER_TEST(TEAL_1020_pbkdf_wrong_alias)
 {
-    assert_positive(ckmew_key_derive_pbkdf2, "password", SALT, SALT_LEN, 32, DERIVED);
+    e2ee_positive(ckmew_key_derive_pbkdf2, "password", SALT, SALT_LEN, 32, DERIVED);
 
     auto remover = AliasRemover(DERIVED);
 
-    assert_result(CKMC_ERROR_DB_ALIAS_EXISTS,
-                  ckmew_key_derive_pbkdf2,
-                  "password",
-                  SALT,
-                  SALT_LEN,
-                  32,
-                  DERIVED);
+    e2ee_result(CKMC_ERROR_DB_ALIAS_EXISTS,
+                ckmew_key_derive_pbkdf2,
+                "password",
+                SALT,
+                SALT_LEN,
+                32,
+                DERIVED);
+}
+
+RUNNER_TEST(TEAL_2000_ocf_positive)
+{
+    ckmc_raw_buffer_s* message = nullptr;
+    ckmc_raw_buffer_s* signature = nullptr;
+    e2ee_positive(ckmew_sign_with_ocf, OURS.pub.c_str(), &message, &signature);
+
+    auto messagePtr = create_raw_buffer(message);
+    auto signaturePtr = create_raw_buffer(signature);
+
+    RUNNER_ASSERT_MSG(messagePtr->size > 0, "Message buffer size is 0");
+    RUNNER_ASSERT_MSG(messagePtr->data != nullptr, "Message buffer is empty");
+
+    RUNNER_ASSERT_MSG(signaturePtr->size > 0, "Signature buffer size is 0");
+    RUNNER_ASSERT_MSG(signaturePtr->data != nullptr, "Singature buffer is empty");
+}
+
+RUNNER_TEST(TEAL_2010_ocf_invalid_param)
+{
+    ckmc_raw_buffer_s* message = nullptr;
+    ckmc_raw_buffer_s* signature = nullptr;
+
+    auto invalid = [](const char* pub_alias,
+                      ckmc_raw_buffer_s** message,
+                      ckmc_raw_buffer_s** signature)
+    {
+        e2ee_result(DCM_ERROR_INVALID_PARAMETER,
+                    ckmew_sign_with_ocf,
+                    pub_alias,
+                    message,
+                    signature);
+    };
+
+    invalid(nullptr, &message, &signature);
+    invalid(OURS.pub.c_str(), nullptr, &signature);
+    invalid(OURS.pub.c_str(), &message, nullptr);
+}
+
+RUNNER_TEST(TEAL_2020_ocf_wrong_public_key)
+{
+    ckmc_raw_buffer_s* message = nullptr;
+    ckmc_raw_buffer_s* signature = nullptr;
+
+    e2ee_result(CKMC_ERROR_DB_ALIAS_UNKNOWN,
+                ckmew_sign_with_ocf,
+                "nonexistent-alias",
+                &message,
+                &signature);
+
+    e2ee_result(CKMC_ERROR_NOT_EXPORTABLE,
+                ckmew_sign_with_ocf,
+                OURS.prv.c_str(),
+                &message,
+                &signature);
+}
+
+RUNNER_TEST(TEAL_3000_link_key_agreement_scenario)
+{
+    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());
+
+    Peer p1(OURS, "our_link_key");
+    Peer p2(PEERS, "peers_link_key");
+
+    p2.receive(p1.send());
+    p1.receive(p2.send());
+
+    auto encrypted = p1.encrypt(params, plain);
+    auto decrypted = p2.decrypt(params, encrypted);
+
+    assert_buffers_equal(plain.get(), decrypted.get());
 }
 
 int main(int argc, char *argv[])