Added support to generate CSR based on ECC approach

In existing, phosphor-certificate-manager is supported RSA approach to
generate CSR. As per Redfish certificate schema, CSR can generate either
RSA or ECC by passing KeyPairAlgorithm. So, In this commit ECC based CSR
generation is added.

Openssl API are used for generating ECC key pair.

User Input Validation:
- ECC approach is used as default if user does not give algorithm type.
- Default KeyBitLength and CurveId used as "2048" and "secp224r1"
  respectively if user does not give.
- Error will be thrown if algorithm given other than RSA and ECC.

In this commit refactor also done by splitting RSA key generation from
writePrivateKey().

Tested by:
- Added unit test cases to verify unsupported KeyPairAlgorithm and
  KeyPairCurveID, ECC Key generation.

- Tested by BMC-web(Redfish) to generate CSR based on ECC.
  curl -c cjar -b cjar -k -H "X-Auth-Token: $bmc_token" -X POST
  https://${bmc}/redfish/v1/CertificateService/Actions/
  CertificateService.GenerateCSR/ -d @generate_https.json

Change-Id: I523293ee2ff6da2964e8c3d4380eefc96bf1f36b
Signed-off-by: Ramesh Iyyar <rameshi1@in.ibm.com>
diff --git a/certs_manager.cpp b/certs_manager.cpp
index 3f7a17e..38f77c2 100644
--- a/certs_manager.cpp
+++ b/certs_manager.cpp
@@ -223,24 +223,54 @@
     addEntry(x509Name, "SN", surname);
     addEntry(x509Name, "unstructuredName", unstructuredName);
 
-    // Generate private key and write to file
-    EVP_PKEY_Ptr pKey = writePrivateKey(keyBitLength, x509Req);
+    EVP_PKEY_Ptr pKey(nullptr, ::EVP_PKEY_free);
+
+    log<level::INFO>("Given Key pair algorithm",
+                     entry("KEYPAIRALGORITHM=%s", keyPairAlgorithm.c_str()));
+
+    // Used EC algorithm as default if user did not give algorithm type.
+    if (keyPairAlgorithm == "RSA")
+        pKey = std::move(generateRSAKeyPair(keyBitLength));
+    else if ((keyPairAlgorithm == "EC") || (keyPairAlgorithm.empty()))
+        pKey = std::move(generateECKeyPair(keyCurveId));
+    else
+    {
+        using InvalidArgument =
+            sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
+        using Argument = xyz::openbmc_project::Common::InvalidArgument;
+
+        log<level::ERR>("Given Key pair algorithm is not supported. Supporting "
+                        "RSA and EC only");
+        elog<InvalidArgument>(
+            Argument::ARGUMENT_NAME("KEYPAIRALGORITHM"),
+            Argument::ARGUMENT_VALUE(keyPairAlgorithm.c_str()));
+    }
+
+    ret = X509_REQ_set_pubkey(x509Req.get(), pKey.get());
+    if (ret == 0)
+    {
+        log<level::ERR>("Error occured while setting Public key");
+        elog<InternalFailure>();
+    }
+
+    // Write private key to file
+    writePrivateKey(pKey);
 
     // set sign key of x509 req
     ret = X509_REQ_sign(x509Req.get(), pKey.get(), EVP_sha256());
-    if (ret <= 0)
+    if (ret == 0)
     {
         log<level::ERR>("Error occured while signing key of x509");
         elog<InternalFailure>();
     }
+
     log<level::INFO>("Writing CSR to file");
     std::string path = fs::path(certInstallPath).parent_path();
     std::string csrFilePath = path + '/' + CSR_FILE_NAME;
     writeCSR(csrFilePath, x509Req);
 }
 
-EVP_PKEY_Ptr Manager::writePrivateKey(int64_t keyBitLength,
-                                      X509_REQ_Ptr& x509Req)
+EVP_PKEY_Ptr Manager::generateRSAKeyPair(const int64_t keyBitLength)
 {
     int ret = 0;
     // generate rsa key
@@ -252,32 +282,102 @@
         elog<InternalFailure>();
     }
 
+    int64_t keyBitLen = keyBitLength;
     // set keybit length to default value if not set
-    if (keyBitLength <= 0)
+    if (keyBitLen <= 0)
     {
-        keyBitLength = 2048;
+        constexpr auto DEFAULT_KEYBITLENGTH = 2048;
+        log<level::INFO>(
+            "KeyBitLength is not given.Hence, using default KeyBitLength",
+            entry("DEFAULTKEYBITLENGTH=%d", DEFAULT_KEYBITLENGTH));
+        keyBitLen = DEFAULT_KEYBITLENGTH;
     }
     RSA* rsa = RSA_new();
-    ret = RSA_generate_key_ex(rsa, keyBitLength, bne.get(), NULL);
+    ret = RSA_generate_key_ex(rsa, keyBitLen, bne.get(), NULL);
     if (ret != 1)
     {
         free(rsa);
         log<level::ERR>("Error occured during RSA_generate_key_ex call",
-                        entry("KEYBITLENGTH=%PRIu64", keyBitLength));
+                        entry("KEYBITLENGTH=%PRIu64", keyBitLen));
         elog<InternalFailure>();
     }
 
     // set public key of x509 req
     EVP_PKEY_Ptr pKey(EVP_PKEY_new(), ::EVP_PKEY_free);
-    EVP_PKEY_assign_RSA(pKey.get(), rsa);
-    ret = X509_REQ_set_pubkey(x509Req.get(), pKey.get());
+    ret = EVP_PKEY_assign_RSA(pKey.get(), rsa);
     if (ret == 0)
     {
-        log<level::ERR>("Error occured while setting Public key");
+        free(rsa);
+        log<level::ERR>("Error occured during assign rsa key into EVP");
         elog<InternalFailure>();
     }
 
-    log<level::ERR>("Writing private key to file");
+    return pKey;
+}
+
+EVP_PKEY_Ptr Manager::generateECKeyPair(const std::string& curveId)
+{
+    std::string curId(curveId);
+
+    if (curId.empty())
+    {
+        // secp224r1 is equal to RSA 2048 KeyBitLength. Refer RFC 5349
+        constexpr auto DEFAULT_KEYCURVEID = "secp224r1";
+        log<level::INFO>(
+            "KeyCurveId is not given. Hence using default curve id",
+            entry("DEFAULTKEYCURVEID=%s", DEFAULT_KEYCURVEID));
+        curId = DEFAULT_KEYCURVEID;
+    }
+
+    int ecGrp = OBJ_txt2nid(curId.c_str());
+
+    if (ecGrp == NID_undef)
+    {
+        log<level::ERR>(
+            "Error occured during convert the curve id string format into NID",
+            entry("KEYCURVEID=%s", curId.c_str()));
+        elog<InternalFailure>();
+    }
+
+    EC_KEY* ecKey = EC_KEY_new_by_curve_name(ecGrp);
+
+    if (ecKey == NULL)
+    {
+        log<level::ERR>(
+            "Error occured during create the EC_Key object from NID",
+            entry("ECGROUP=%d", ecGrp));
+        elog<InternalFailure>();
+    }
+
+    // If you want to save a key and later load it with
+    // SSL_CTX_use_PrivateKey_file, then you must set the OPENSSL_EC_NAMED_CURVE
+    // flag on the key.
+    EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE);
+
+    int ret = EC_KEY_generate_key(ecKey);
+
+    if (ret == 0)
+    {
+        EC_KEY_free(ecKey);
+        log<level::ERR>("Error occured during generate EC key");
+        elog<InternalFailure>();
+    }
+
+    EVP_PKEY_Ptr pKey(EVP_PKEY_new(), ::EVP_PKEY_free);
+    ret = EVP_PKEY_assign_EC_KEY(pKey.get(), ecKey);
+    if (ret == 0)
+    {
+        EC_KEY_free(ecKey);
+        log<level::ERR>("Error occured during assign EC Key into EVP");
+        elog<InternalFailure>();
+    }
+
+    return pKey;
+}
+
+void Manager::writePrivateKey(const EVP_PKEY_Ptr& pKey)
+{
+    log<level::INFO>("Writing private key to file");
     // write private key to file
     std::string path = fs::path(certInstallPath).parent_path();
     std::string privKeyPath = path + '/' + PRIV_KEY_FILE_NAME;
@@ -285,18 +385,16 @@
     FILE* fp = std::fopen(privKeyPath.c_str(), "w");
     if (fp == NULL)
     {
-        ret = -1;
         log<level::ERR>("Error occured creating private key file");
         elog<InternalFailure>();
     }
-    ret = PEM_write_PrivateKey(fp, pKey.get(), NULL, NULL, 0, 0, NULL);
+    int ret = PEM_write_PrivateKey(fp, pKey.get(), NULL, NULL, 0, 0, NULL);
     std::fclose(fp);
     if (ret == 0)
     {
         log<level::ERR>("Error occured while writing private key to file");
         elog<InternalFailure>();
     }
-    return pKey;
 }
 
 void Manager::addEntry(X509_NAME* x509Name, const char* field,
diff --git a/certs_manager.hpp b/certs_manager.hpp
index 0c8f4e6..236f6f1 100644
--- a/certs_manager.hpp
+++ b/certs_manager.hpp
@@ -162,13 +162,23 @@
                            std::string organizationalUnit, std::string state,
                            std::string surname, std::string unstructuredName);
 
+    /** @brief Generate RSA Key pair and get private key from key pair
+     *  @param[in]  keyBitLength - KeyBit length.
+     *  @return     Pointer to RSA private key
+     */
+    EVP_PKEY_Ptr generateRSAKeyPair(const int64_t keyBitLength);
+
+    /** @brief Generate EC Key pair and get private key from key pair
+     *  @param[in]  p_KeyCurveId - Curve ID
+     *  @return     Pointer to EC private key
+     */
+    EVP_PKEY_Ptr generateECKeyPair(const std::string& p_KeyCurveId);
+
     /** @brief Write private key data to file
      *
-     *  @param[in] keyBitLength - KeyBit length.
-     *  @param[in] x509Req - pointer to X509 request.
-     *  @return pointer to private key
+     *  @param[in] pKey     - pointer to private key
      */
-    EVP_PKEY_Ptr writePrivateKey(int64_t keyBitLength, X509_REQ_Ptr& x509Req);
+    void writePrivateKey(const EVP_PKEY_Ptr& pKey);
 
     /** @brief Add the specified CSR field with the data
      *  @param[in] x509Name - Structure used in setting certificate properties
diff --git a/test/certs_manager_test.cpp b/test/certs_manager_test.cpp
index b26c8ee..a783ba3 100644
--- a/test/certs_manager_test.cpp
+++ b/test/certs_manager_test.cpp
@@ -509,7 +509,7 @@
     std::string CSRPath(certDir + "/" + CSRFile);
     std::string privateKeyPath(certDir + "/" + privateKeyFile);
     std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
-    std::string challengePassword("0penBmc");
+    std::string challengePassword("Password");
     std::string city("HYB");
     std::string commonName("abc.com");
     std::string contactPerson("Admin");
@@ -522,7 +522,7 @@
     std::string keyPairAlgorithm("RSA");
     std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
     std::string organization("IBM");
-    std::string organizationalUnit("ISDL");
+    std::string organizationalUnit("orgUnit");
     std::string state("TS");
     std::string surname("surname");
     std::string unstructuredName("unstructuredName");
@@ -562,7 +562,8 @@
     ASSERT_NE("", csrData.c_str());
 }
 
-TEST_F(TestCertificates, TestGenerateCSRError)
+/** @brief Check default KeyBitLength is used if Key bit length is not given*/
+TEST_F(TestCertificates, TestGenerateCSRwithDefaultKeyBitLength)
 {
     std::string endpoint("https");
     std::string unit("");
@@ -572,23 +573,89 @@
     std::string CSRPath(certDir + "/" + CSRFile);
     std::string privateKeyPath(certDir + "/" + privateKeyFile);
     std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
-    std::string challengePassword("0penBmc");
-    std::string city;
-    std::string commonName;
-    std::string contactPerson;
-    std::string country;
-    std::string email;
-    std::string givenName;
-    std::string initials;
-    int64_t keyBitLength(12);
-    std::string keyCurveId;
-    std::string keyPairAlgorithm;
+    std::string challengePassword("Password");
+    std::string city("HYB");
+    std::string commonName("abc.com");
+    std::string contactPerson("Admin");
+    std::string country("IN");
+    std::string email("admin@in.ibm.com");
+    std::string givenName("givenName");
+    std::string initials("G");
+    int64_t keyBitLength = 0;
+    std::string keyCurveId("0");
+    std::string keyPairAlgorithm("RSA");
     std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
-    std::string organization;
-    std::string organizationalUnit;
-    std::string state;
-    std::string surname;
-    std::string unstructuredName;
+    std::string organization("IBM");
+    std::string organizationalUnit("orgUnit");
+    std::string state("TS");
+    std::string surname("surname");
+    std::string unstructuredName("unstructuredName");
+    auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
+    auto event = sdeventplus::Event::get_default();
+    Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
+                    std::move(installPath));
+    Status status;
+    CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
+    MainApp mainApp(&manager, &csr);
+    mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
+                        contactPerson, country, email, givenName, initials,
+                        keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
+                        organization, organizationalUnit, state, surname,
+                        unstructuredName);
+    std::string csrData("");
+    // generateCSR takes considerable time to create CSR and privateKey Files
+    EXPECT_FALSE(fs::exists(CSRPath));
+    EXPECT_FALSE(fs::exists(privateKeyPath));
+    EXPECT_THROW(
+        {
+            try
+            {
+                csrData = csr.cSR();
+            }
+            catch (const InternalFailure& e)
+            {
+                throw;
+            }
+        },
+        InternalFailure);
+    // wait for 10 sec to get CSR and privateKey Files generated
+    sleep(10);
+    EXPECT_TRUE(fs::exists(CSRPath));
+    EXPECT_TRUE(fs::exists(privateKeyPath));
+    csrData = csr.cSR();
+    ASSERT_NE("", csrData.c_str());
+}
+
+/** @brief Check if ECC key pair is generated when user is not given algorithm
+ * type. At present RSA and EC key pair algorithm are supported
+ */
+TEST_F(TestCertificates, TestGenerateCSRwithEmptyKeyPairAlgorithm)
+{
+    std::string endpoint("https");
+    std::string unit("");
+    std::string type("Server");
+    std::string installPath(certDir + "/" + certificateFile);
+    std::string verifyPath(installPath);
+    std::string CSRPath(certDir + "/" + CSRFile);
+    std::string privateKeyPath(certDir + "/" + privateKeyFile);
+    std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
+    std::string challengePassword("Password");
+    std::string city("HYB");
+    std::string commonName("abc.com");
+    std::string contactPerson("Admin");
+    std::string country("IN");
+    std::string email("admin@in.ibm.com");
+    std::string givenName("givenName");
+    std::string initials("G");
+    int64_t keyBitLength(2048);
+    std::string keyCurveId("");
+    std::string keyPairAlgorithm("");
+    std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
+    std::string organization("IBM");
+    std::string organizationalUnit("orgUnit");
+    std::string state("TS");
+    std::string surname("surname");
+    std::string unstructuredName("unstructuredName");
     auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
     auto event = sdeventplus::Event::get_default();
     Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
@@ -602,6 +669,192 @@
                         organization, organizationalUnit, state, surname,
                         unstructuredName);
     sleep(10);
+    EXPECT_TRUE(fs::exists(CSRPath));
+    EXPECT_TRUE(fs::exists(privateKeyPath));
+}
+
+/** @brief Check if error is thrown when giving un supported key pair
+ * algorithm. At present RSA and EC key pair algorithm are supported
+ */
+TEST_F(TestCertificates, TestGenerateCSRwithUnsupportedKeyPairAlgorithm)
+{
+    std::string endpoint("https");
+    std::string unit("");
+    std::string type("Server");
+    std::string installPath(certDir + "/" + certificateFile);
+    std::string verifyPath(installPath);
+    std::string CSRPath(certDir + "/" + CSRFile);
+    std::string privateKeyPath(certDir + "/" + privateKeyFile);
+    std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
+    std::string challengePassword("Password");
+    std::string city("HYB");
+    std::string commonName("abc.com");
+    std::string contactPerson("Admin");
+    std::string country("IN");
+    std::string email("admin@in.ibm.com");
+    std::string givenName("givenName");
+    std::string initials("G");
+    int64_t keyBitLength(2048);
+    std::string keyCurveId("secp521r1");
+    std::string keyPairAlgorithm("UnSupportedAlgorithm");
+    std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
+    std::string organization("IBM");
+    std::string organizationalUnit("orgUnit");
+    std::string state("TS");
+    std::string surname("surname");
+    std::string unstructuredName("unstructuredName");
+    auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
+    auto event = sdeventplus::Event::get_default();
+    Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
+                    std::move(installPath));
+    Status status;
+    CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
+    MainApp mainApp(&manager, &csr);
+    mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
+                        contactPerson, country, email, givenName, initials,
+                        keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
+                        organization, organizationalUnit, state, surname,
+                        unstructuredName);
     EXPECT_FALSE(fs::exists(CSRPath));
     EXPECT_FALSE(fs::exists(privateKeyPath));
 }
+
+/** @brief Check if error is thrown when NID_undef is returned for given key
+ * curve id
+ */
+TEST_F(TestCertificates, TestECKeyGenerationwithNIDundefCase)
+{
+    std::string endpoint("https");
+    std::string unit("");
+    std::string type("Server");
+    std::string installPath(certDir + "/" + certificateFile);
+    std::string verifyPath(installPath);
+    std::string CSRPath(certDir + "/" + CSRFile);
+    std::string privateKeyPath(certDir + "/" + privateKeyFile);
+    std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
+    std::string challengePassword("Password");
+    std::string city("BLR");
+    std::string commonName("abc.com");
+    std::string contactPerson("Admin");
+    std::string country("IN");
+    std::string email("admin@in.ibm.com");
+    std::string givenName("givenName");
+    std::string initials("G");
+    int64_t keyBitLength(2048);
+    std::string keyCurveId("DummyCurveName");
+    std::string keyPairAlgorithm("EC");
+    std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
+    std::string organization("IBM");
+    std::string organizationalUnit("orgUnit");
+    std::string state("TS");
+    std::string surname("surname");
+    std::string unstructuredName("unstructuredName");
+    auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
+    auto event = sdeventplus::Event::get_default();
+    Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
+                    std::move(installPath));
+    Status status;
+    CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
+    MainApp mainApp(&manager, &csr);
+    mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
+                        contactPerson, country, email, givenName, initials,
+                        keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
+                        organization, organizationalUnit, state, surname,
+                        unstructuredName);
+    EXPECT_FALSE(fs::exists(CSRPath));
+    EXPECT_FALSE(fs::exists(privateKeyPath));
+}
+
+/** @brief Check default Key Curve Id is used if given curve id is empty
+ */
+TEST_F(TestCertificates, TestECKeyGenerationwithDefaultKeyCurveId)
+{
+    std::string endpoint("https");
+    std::string unit("");
+    std::string type("Server");
+    std::string installPath(certDir + "/" + certificateFile);
+    std::string verifyPath(installPath);
+    std::string CSRPath(certDir + "/" + CSRFile);
+    std::string privateKeyPath(certDir + "/" + privateKeyFile);
+    std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
+    std::string challengePassword("Password");
+    std::string city("BLR");
+    std::string commonName("abc.com");
+    std::string contactPerson("Admin");
+    std::string country("IN");
+    std::string email("admin@in.ibm.com");
+    std::string givenName("givenName");
+    std::string initials("G");
+    int64_t keyBitLength(2048);
+    std::string keyCurveId("");
+    std::string keyPairAlgorithm("EC");
+    std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
+    std::string organization("IBM");
+    std::string organizationalUnit("orgUnit");
+    std::string state("TS");
+    std::string surname("surname");
+    std::string unstructuredName("unstructuredName");
+    auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
+    auto event = sdeventplus::Event::get_default();
+    Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
+                    std::move(installPath));
+    Status status;
+    CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
+    MainApp mainApp(&manager, &csr);
+    mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
+                        contactPerson, country, email, givenName, initials,
+                        keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
+                        organization, organizationalUnit, state, surname,
+                        unstructuredName);
+    sleep(10);
+    EXPECT_TRUE(fs::exists(CSRPath));
+    EXPECT_TRUE(fs::exists(privateKeyPath));
+}
+
+/** @brief Check if error is not thrown to generate EC key pair
+ */
+TEST_F(TestCertificates, TestECKeyGeneration)
+{
+    std::string endpoint("https");
+    std::string unit("");
+    std::string type("Server");
+    std::string installPath(certDir + "/" + certificateFile);
+    std::string verifyPath(installPath);
+    std::string CSRPath(certDir + "/" + CSRFile);
+    std::string privateKeyPath(certDir + "/" + privateKeyFile);
+    std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
+    std::string challengePassword("Password");
+    std::string city("BLR");
+    std::string commonName("abc.com");
+    std::string contactPerson("Admin");
+    std::string country("IN");
+    std::string email("admin@in.ibm.com");
+    std::string givenName("givenName");
+    std::string initials("G");
+    int64_t keyBitLength(2048);
+    std::string keyCurveId("secp521r1");
+    std::string keyPairAlgorithm("EC");
+    std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
+    std::string organization("IBM");
+    std::string organizationalUnit("orgUnit");
+    std::string state("TS");
+    std::string surname("surname");
+    std::string unstructuredName("unstructuredName");
+    auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
+    auto event = sdeventplus::Event::get_default();
+    Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
+                    std::move(installPath));
+    Status status;
+    CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
+    MainApp mainApp(&manager, &csr);
+    mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
+                        contactPerson, country, email, givenName, initials,
+                        keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
+                        organization, organizationalUnit, state, surname,
+                        unstructuredName);
+    std::cout << "CSRPath: " << CSRPath << std::endl
+              << "privateKeyPath: " << privateKeyPath << std::endl;
+    sleep(10);
+    EXPECT_TRUE(fs::exists(CSRPath));
+    EXPECT_TRUE(fs::exists(privateKeyPath));
+}