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,