Add hostname listener for generating self-signed HTTPS certificate

- Add a hostname listener that will create a self-signed HTTPS
  certificate with the appropriate subject when the BMC gets its
  hostname assigned via IPMI. The "insecure-disable-ssl" must be
  disabled for this feature to take effect.

 Note:
   - New self-signed certificate subject: C=US, O=OpenBMC, CN=${hostname}
   - If the same hostname is assigned, it will not be triggered
   - Only the self-signed certificate with Netscape Comment of
     "Generated from OpenBMC service" will be replaced

 Details about certificate key usage:
   - NID_basic_constraints
     The CA boolean indicates whether the certified public key may be
     used to verify certificate signatures.
     Refer to: https://tools.ietf.org/html/rfc5280#section-4.2.1.9
   - NID_subject_alt_name
     Although the use of the Common Name is existing practice, it is
     deprecated and Certification Authorities are encouraged to use the
     dNSName instead.
     Refer to: https://tools.ietf.org/html/rfc2818#section-3.1
   - NID_subject_key_identifier
     The subject key identifier extension provides a means of
     identifying certificates that contain a particular public key.
     Refer to: https://tools.ietf.org/html/rfc5280#section-4.2.1.2
   - NID_authority_key_identifier
     The authority key identifier extension provides a means of
     identifying the public key corresponding to the private key used
     to sign a certificate.
     Refer to: https://tools.ietf.org/html/rfc5280#section-4.2.1.1
   - NID_key_usage
   - NID_ext_key_usage
     id-kp-serverAuth
     -- TLS WWW server authentication
     -- Key usage bits that may be consistent: digitalSignature,
     -- keyEncipherment or keyAgreement
     Refer to: https://tools.ietf.org/html/rfc5280#section-4.2.1.3
     Refer to: https://tools.ietf.org/html/rfc5280#section-4.2.1.12

 Tested:
   - To test and verify the service is functionally working correctly,
     we can use `openssl` and `ipmitool` to execute the following
     commands:
     - Assign BMC hostname
       ipmitool -H $IP -I lanplus -U root -P 0penBmc -C 17 dcmi
       set_mc_id_string $hostname
     - Get BMC server certificate infomation
       echo quit | openssl s_client -showcerts -servername $IP -connect
       $IP:443

Signed-off-by: Alan Kuo <Alan_Kuo@quantatw.com>
Change-Id: I24aeb4d2fb46ff5f0cc1c6aa65984f46b0e1d3e2
diff --git a/include/ssl_key_handler.hpp b/include/ssl_key_handler.hpp
index 19d3ec7..93fe5d0 100644
--- a/include/ssl_key_handler.hpp
+++ b/include/ssl_key_handler.hpp
@@ -18,6 +18,7 @@
 namespace ensuressl
 {
 constexpr char const* trustStorePath = "/etc/ssl/certs/authority";
+constexpr char const* x509Comment = "Generated from OpenBMC service";
 static void initOpenssl();
 static EVP_PKEY* createEcKey();
 
@@ -170,7 +171,56 @@
     return certValid;
 }
 
-inline void generateSslCertificate(const std::string& filepath)
+inline X509* loadCert(const std::string& filePath)
+{
+    BIO* certFileBio = BIO_new_file(filePath.c_str(), "rb");
+    if (!certFileBio)
+    {
+        BMCWEB_LOG_ERROR << "Error occured during BIO_new_file call, "
+                         << "FILE= " << filePath;
+        return nullptr;
+    }
+
+    X509* cert = X509_new();
+    if (!cert)
+    {
+        BMCWEB_LOG_ERROR << "Error occured during X509_new call, "
+                         << ERR_get_error();
+        BIO_free(certFileBio);
+        return nullptr;
+    }
+
+    if (!PEM_read_bio_X509(certFileBio, &cert, nullptr, nullptr))
+    {
+        BMCWEB_LOG_ERROR << "Error occured during PEM_read_bio_X509 call, "
+                         << "FILE= " << filePath;
+
+        BIO_free(certFileBio);
+        X509_free(cert);
+        return nullptr;
+    }
+    return cert;
+}
+
+inline int addExt(X509* cert, int nid, const char* value)
+{
+    X509_EXTENSION* ex = nullptr;
+    X509V3_CTX ctx;
+    X509V3_set_ctx_nodb(&ctx);
+    X509V3_set_ctx(&ctx, cert, cert, nullptr, nullptr, 0);
+    ex = X509V3_EXT_conf_nid(nullptr, &ctx, nid, const_cast<char*>(value));
+    if (!ex)
+    {
+        BMCWEB_LOG_ERROR << "Error: In X509V3_EXT_conf_nidn: " << value;
+        return -1;
+    }
+    X509_add_ext(cert, ex, -1);
+    X509_EXTENSION_free(ex);
+    return 0;
+}
+
+inline void generateSslCertificate(const std::string& filepath,
+                                   const std::string& cn)
 {
     FILE* pFile = nullptr;
     std::cout << "Generating new keys\n";
@@ -189,8 +239,10 @@
             // get a random number from the RNG for the certificate serial
             // number If this is not random, regenerating certs throws broswer
             // errors
-            std::random_device rd;
-            int serial = static_cast<int>(rd());
+            bmcweb::OpenSSLGenerator gen;
+            std::uniform_int_distribution<int> dis(
+                1, std::numeric_limits<int>::max());
+            int serial = dis(gen);
 
             ASN1_INTEGER_set(X509_get_serialNumber(x509), serial);
 
@@ -215,10 +267,19 @@
                 reinterpret_cast<const unsigned char*>("OpenBMC"), -1, -1, 0);
             X509_NAME_add_entry_by_txt(
                 name, "CN", MBSTRING_ASC,
-                reinterpret_cast<const unsigned char*>("testhost"), -1, -1, 0);
+                reinterpret_cast<const unsigned char*>(cn.c_str()), -1, -1, 0);
             // set the CSR options
             X509_set_issuer_name(x509, name);
 
+            X509_set_version(x509, 2);
+            addExt(x509, NID_basic_constraints, ("critical,CA:TRUE"));
+            addExt(x509, NID_subject_alt_name, ("DNS:" + cn).c_str());
+            addExt(x509, NID_subject_key_identifier, ("hash"));
+            addExt(x509, NID_authority_key_identifier, ("keyid"));
+            addExt(x509, NID_key_usage, ("digitalSignature, keyEncipherment"));
+            addExt(x509, NID_ext_key_usage, ("serverAuth"));
+            addExt(x509, NID_netscape_comment, (x509Comment));
+
             // Sign the certificate with our private key
             X509_sign(x509, pPrivKey, EVP_sha256());
 
@@ -289,7 +350,7 @@
     if (!pemFileValid)
     {
         std::cerr << "Error in verifying signature, regenerating\n";
-        generateSslCertificate(filepath);
+        generateSslCertificate(filepath, "testhost");
     }
 }