Add EC key generation/compression with unit tests 77/305777/10
authorDariusz Michaluk <d.michaluk@samsung.com>
Thu, 25 Jan 2024 12:04:38 +0000 (13:04 +0100)
committerKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Mon, 12 Feb 2024 13:40:37 +0000 (13:40 +0000)
Change-Id: Ie981a74c6af0eb61aa0b2d910ae909982a3901b1

srcs/crypto/ec_key.cpp
srcs/crypto/ec_key.h
srcs/request.cpp
tests/crypto/ec_key_unittest.cpp

index e4bbbf28fa3c468e46bb9df7dbbf74c2f22a4a1d..9768bdcb132865948b4f2de0dbf43b85183563a6 100644 (file)
  *  limitations under the License
  */
 
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+#include "crypto/common.h"
 #include "crypto/ec_key.h"
 #include "crypto/openssl_error.h"
+#include "log/log.h"
 
 #include <cstring>
 #include <openssl/ec.h>
+#include <openssl/evp.h>
 #include <openssl/obj_mac.h> // for NID_X9_62_prime256v1
 
 namespace Crypto {
 
-CryptoBuffer GenerateCompressedX9_62_P_256_Key()
+X9_62_P_256_Key X9_62_P_256_Key::Create()
 {
-    EC_KEY *key = nullptr;
-    CryptoBuffer res(33);
-    size_t buffLen;
-    unsigned char *buff = nullptr;
+    EVP_PKEY_CTX *ctx = nullptr;
+    EVP_PKEY *pkey = nullptr;
+
+    ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr);
+    if (!ctx) {
+        TRY_LOG_ERROR("EVP_PKEY_CTX_new_id() error");
+        goto opensslError;
+    }
+    if (EVP_PKEY_keygen_init(ctx) != 1) {
+        TRY_LOG_ERROR("EVP_PKEY_keygen_init() error");
+        goto opensslError;
+    }
+    if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1) != 1) {
+        TRY_LOG_ERROR("EVP_PKEY_CTX_set_ec_paramgen_curve_nid() error");
+        goto opensslError;
+    }
+    if (EVP_PKEY_keygen(ctx, &pkey) != 1) {
+        TRY_LOG_ERROR("EVP_PKEY_keygen() error");
+        goto opensslError;
+    }
+
+    EVP_PKEY_CTX_free(ctx);
+    return X9_62_P_256_Key{pkey};
+
+opensslError:
+    EVP_PKEY_CTX_free(ctx);
+    throw OpensslError{};
+}
+
+X9_62_P_256_Key X9_62_P_256_Key::ImportPrivateKey(const CryptoBuffer &input)
+{
+    EVP_PKEY *pkey = nullptr;
+    EC_KEY *ec = nullptr;
+    EC_GROUP *group = nullptr;
+    EC_POINT *point = nullptr;
+    const BIGNUM *bn = nullptr;
 
-    key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
-    if (!key)
+    pkey = EVP_PKEY_new();
+    if (!pkey) {
+        TRY_LOG_ERROR("EVP_PKEY_new() error");
         goto opensslError;
-    if (EC_KEY_generate_key(key) != 1)
+    }
+    ec = EC_KEY_new();
+    if (!ec) {
+        TRY_LOG_ERROR("EC_KEY_new() error");
         goto opensslError;
-    buffLen = EC_KEY_key2buf(key, POINT_CONVERSION_COMPRESSED, &buff, nullptr);
-    if (buffLen == 0)
+    }
+    group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
+    if (!group) {
+        TRY_LOG_ERROR("EC_GROUP_new_by_curve_name() error");
         goto opensslError;
-    if (buffLen != res.size())
+    }
+    EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE);
+    if (EC_KEY_set_group(ec, group) != 1) {
+        TRY_LOG_ERROR("EC_KEY_set_group() error");
         goto opensslError;
+    }
+    if (EC_KEY_oct2priv(ec, input.data(), input.size()) != 1) {
+        TRY_LOG_ERROR("EC_KEY_oct2priv() error");
+        goto opensslError;
+    }
+    point = EC_POINT_new(group);
+    if (!point) {
+        TRY_LOG_ERROR("EC_POINT_new() error");
+        goto opensslError;
+    }
+    bn = EC_KEY_get0_private_key(ec);
+    if (!bn) {
+        TRY_LOG_ERROR("EC_KEY_get0_private_key() error");
+        goto opensslError;
+    }
+    if (EC_POINT_mul(group, point, bn, nullptr, nullptr, nullptr) != 1) {
+        TRY_LOG_ERROR("EC_POINT_mul() error");
+        goto opensslError;
+    }
+    if (EC_KEY_set_public_key(ec, point) != 1) {
+        TRY_LOG_ERROR("EC_KEY_set_public_key() error");
+        goto opensslError;
+    }
+    if (EVP_PKEY_assign_EC_KEY(pkey, ec) != 1) {
+        TRY_LOG_ERROR("EVP_PKEY_assign_EC_KEY() error");
+        goto opensslError;
+    }
+
+    EC_GROUP_clear_free(group);
+    EC_POINT_clear_free(point);
+    return X9_62_P_256_Key{pkey};
+
+opensslError:
+    EVP_PKEY_free(pkey);
+    EC_KEY_free(ec);
+    EC_GROUP_clear_free(group);
+    EC_POINT_clear_free(point);
+    throw OpensslError{};
+}
+
+X9_62_P_256_Key X9_62_P_256_Key::ImportPublicKey(const CryptoBuffer &input)
+{
+    EVP_PKEY *pkey = nullptr;
+    EC_KEY *ec = nullptr;
+    EC_GROUP *group = nullptr;
+
+    pkey = EVP_PKEY_new();
+    if (!pkey) {
+        TRY_LOG_ERROR("EVP_PKEY_new() error");
+        goto opensslError;
+    }
+    ec = EC_KEY_new();
+    if (!ec) {
+        TRY_LOG_ERROR("EC_KEY_new() error");
+        goto opensslError;
+    }
+    group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
+    if (!group) {
+        TRY_LOG_ERROR("EC_GROUP_new_by_curve_name() error");
+        goto opensslError;
+    }
+    EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE);
+    if (EC_KEY_set_group(ec, group) != 1) {
+        TRY_LOG_ERROR("EC_KEY_set_group() error");
+        goto opensslError;
+    }
+    if (EC_KEY_oct2key(ec, input.data(), input.size(), nullptr) != 1) {
+        TRY_LOG_ERROR("EC_KEY_oct2key() error");
+        goto opensslError;
+    }
+    if (EVP_PKEY_assign_EC_KEY(pkey, ec) != 1) {
+        TRY_LOG_ERROR("EVP_PKEY_assign_EC_KEY() error");
+        goto opensslError;
+    }
+
+    EC_GROUP_clear_free(group);
+    return X9_62_P_256_Key{pkey};
+
+opensslError:
+    EVP_PKEY_free(pkey);
+    EC_KEY_free(ec);
+    EC_GROUP_clear_free(group);
+    throw OpensslError{};
+}
+
+CryptoBuffer X9_62_P_256_Key::ExportPrivateKey() const
+{
+    CryptoBuffer res(32);
+    size_t buffLen;
+    unsigned char *buff = nullptr;
+
+    const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(m_key);
+    buffLen = EC_KEY_priv2buf(ec, &buff);
+    if (buffLen == 0 || buffLen != res.size()) {
+        TRY_LOG_ERROR("EC_KEY_priv2buf() error");
+        OPENSSL_free(buff);
+        throw OpensslError{};
+    }
+
     std::memcpy(res.data(), buff, buffLen);
     OPENSSL_free(buff);
-    EC_KEY_free(key);
     return res;
+}
 
-opensslError:
+CryptoBuffer X9_62_P_256_Key::ExportPublicKey(bool compressed) const
+{
+    CryptoBuffer res(compressed ? 33 : 65);
+    point_conversion_form_t form =
+        compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED;
+    size_t buffLen;
+    unsigned char *buff = nullptr;
+
+    const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(m_key);
+    buffLen = EC_KEY_key2buf(ec, form, &buff, nullptr);
+    if (buffLen == 0 || buffLen != res.size()) {
+        TRY_LOG_ERROR("EC_KEY_key2buf() error");
+        OPENSSL_free(buff);
+        throw OpensslError{};
+    }
+
+    std::memcpy(res.data(), buff, buffLen);
     OPENSSL_free(buff);
-    EC_KEY_free(key);
-    throw OpensslError{};
+    return res;
 }
 
 } // namespace Crypto
+
+#pragma GCC diagnostic pop
index 272b673836f3d853b3848158d6caed3a2947c6ee..68d1d4a39d8262de5913cc2e50c75c2b7a36a5e6 100644 (file)
 
 #include "crypto/common.h"
 
+#include <openssl/evp.h>
+
 namespace Crypto {
 
-CryptoBuffer GenerateCompressedX9_62_P_256_Key();
+class X9_62_P_256_Key {
+public:
+    X9_62_P_256_Key(const X9_62_P_256_Key &) = delete;
+
+    X9_62_P_256_Key(X9_62_P_256_Key &&other) noexcept : m_key(other.m_key)
+    {
+        other.m_key = nullptr;
+    }
+
+    X9_62_P_256_Key &operator=(const X9_62_P_256_Key &) = delete;
+    X9_62_P_256_Key &operator=(X9_62_P_256_Key &&) = delete;
+
+    ~X9_62_P_256_Key() { EVP_PKEY_free(m_key); }
+
+    static X9_62_P_256_Key Create();
+    static X9_62_P_256_Key ImportPrivateKey(const CryptoBuffer &input);
+    static X9_62_P_256_Key ImportPublicKey(const CryptoBuffer &input);
+
+    CryptoBuffer ExportPrivateKey() const;
+    CryptoBuffer ExportPublicKey(bool compressed) const;
+
+    EVP_PKEY *get() const noexcept { return m_key; }
+
+private:
+    explicit X9_62_P_256_Key(EVP_PKEY *key) noexcept { m_key = key; }
+
+    EVP_PKEY *m_key = nullptr;
+};
 
 } // namespace Crypto
index 211b634f0399c69983af4c0dac9783035975c107..819caff703ccda1bf977aa2ee599dddaec3f8cb2 100644 (file)
@@ -26,7 +26,8 @@ wauthn_error_e Request::Process()
 {
     if (QRInitiated()) {
         auto qrSecret = Crypto::RandomBytes(16);
-        auto identityKeyCompressed = Crypto::GenerateCompressedX9_62_P_256_Key();
+        auto identityKey = Crypto::X9_62_P_256_Key::Create();
+        auto identityKeyCompressed = identityKey.ExportPublicKey(true);
 
         ShowQRCode(qrSecret, identityKeyCompressed);
 
index 9c277c51ff39658af576010d8c3f523b46e69ee6..30b2b262d71ef3638551d8624d93e1afbbc79ecc 100644 (file)
 
 #include <gtest/gtest.h>
 
-TEST(ECKey, GenerateCompressedX9_62_P_256_Key)
+TEST(X9_62_P_256_KeyTest, CreateExportImportTest)
 {
-    auto key1 = Crypto::GenerateCompressedX9_62_P_256_Key();
-    ASSERT_EQ(key1.size(), 33);
+    auto key1 = Crypto::X9_62_P_256_Key::Create();
+    auto privKey1 = key1.ExportPrivateKey();
+    auto pubKey1 = key1.ExportPublicKey(false);
+    auto pubKeyCompressed1 = key1.ExportPublicKey(true);
 
-    auto key2 = Crypto::GenerateCompressedX9_62_P_256_Key();
-    ASSERT_EQ(key2.size(), 33);
+    ASSERT_EQ(privKey1.size(), 32);
+    ASSERT_EQ(pubKey1.size(), 65);
+    ASSERT_EQ(pubKeyCompressed1.size(), 33);
 
-    ASSERT_NE(key1, key2);
+    auto key2 = Crypto::X9_62_P_256_Key::ImportPrivateKey(privKey1);
+    auto privKey2 = key2.ExportPrivateKey();
+    auto pubKey2 = key2.ExportPublicKey(false);
+    auto pubKeyCompressed2 = key2.ExportPublicKey(true);
+
+    EXPECT_EQ(privKey1, privKey2);
+    EXPECT_EQ(pubKey1, pubKey2);
+    EXPECT_EQ(pubKeyCompressed1, pubKeyCompressed2);
+
+    auto key3 = Crypto::X9_62_P_256_Key::ImportPublicKey(pubKey1);
+    auto pubKey3 = key3.ExportPublicKey(false);
+    auto pubKeyCompressed3 = key3.ExportPublicKey(true);
+    EXPECT_EQ(pubKey1, pubKey3);
+    EXPECT_EQ(pubKeyCompressed1, pubKeyCompressed3);
+
+    auto key4 = Crypto::X9_62_P_256_Key::ImportPublicKey(pubKeyCompressed1);
+    auto pubKey4 = key4.ExportPublicKey(false);
+    auto pubKeyCompressed4 = key4.ExportPublicKey(true);
+    EXPECT_EQ(pubKey1, pubKey4);
+    EXPECT_EQ(pubKeyCompressed1, pubKeyCompressed4);
+}
+
+TEST(X9_62_P_256_KeyTest, CreateExportRandomTest)
+{
+    auto key1 = Crypto::X9_62_P_256_Key::Create();
+    auto key2 = Crypto::X9_62_P_256_Key::Create();
+
+    EXPECT_NE(key1.ExportPrivateKey(), key2.ExportPrivateKey());
+    EXPECT_NE(key1.ExportPublicKey(false), key2.ExportPublicKey(false));
+    EXPECT_NE(key1.ExportPublicKey(true), key2.ExportPublicKey(true));
+}
+
+TEST(X9_62_P_256_KeyTest, ImportExportTestVector)
+{
+    const CryptoBuffer PRIV_KEY = {0xcb, 0x6f, 0xc4, 0xd8, 0x99, 0x34, 0x03, 0x6d, 0x35, 0xa3, 0x60,
+                                   0x5a, 0xae, 0x36, 0x63, 0x6d, 0xe4, 0x8f, 0x62, 0xa7, 0x64, 0xf7,
+                                   0xfd, 0xdc, 0x61, 0xc1, 0x12, 0x36, 0xa6, 0xf7, 0x79, 0x76};
+
+    const CryptoBuffer PUB_KEY = {0x04, 0x52, 0x6a, 0x35, 0xdf, 0x37, 0x0f, 0xf3, 0x5e, 0x56, 0x22,
+                                  0x96, 0xc9, 0x55, 0xbf, 0x33, 0xdb, 0x75, 0x52, 0x24, 0xad, 0x63,
+                                  0x20, 0xfc, 0x8c, 0xa9, 0x14, 0x97, 0xe3, 0x85, 0x14, 0x3e, 0xed,
+                                  0xbd, 0x1d, 0x73, 0x10, 0xe8, 0x20, 0xb8, 0xfb, 0xe0, 0xc3, 0x5f,
+                                  0x97, 0x36, 0x69, 0xa3, 0x40, 0x83, 0x4c, 0xae, 0xf2, 0xf0, 0x89,
+                                  0xfa, 0x8a, 0xb4, 0xc1, 0x9e, 0xca, 0xf6, 0xb9, 0x11, 0x39};
+
+    const CryptoBuffer PUB_KEY_COMPRESSED = {0x03, 0x52, 0x6a, 0x35, 0xdf, 0x37, 0x0f, 0xf3, 0x5e,
+                                             0x56, 0x22, 0x96, 0xc9, 0x55, 0xbf, 0x33, 0xdb, 0x75,
+                                             0x52, 0x24, 0xad, 0x63, 0x20, 0xfc, 0x8c, 0xa9, 0x14,
+                                             0x97, 0xe3, 0x85, 0x14, 0x3e, 0xed};
+
+    auto key = Crypto::X9_62_P_256_Key::ImportPrivateKey(PRIV_KEY);
+    auto privKey = key.ExportPrivateKey();
+    auto pubKey = key.ExportPublicKey(false);
+    auto pubKeyCompressed = key.ExportPublicKey(true);
+
+    EXPECT_EQ(privKey, PRIV_KEY);
+    EXPECT_EQ(pubKey, PUB_KEY);
+    EXPECT_EQ(pubKeyCompressed, PUB_KEY_COMPRESSED);
 }