xref: /openbmc/bmcweb/src/ssl_key_handler.cpp (revision 41fe81c2)
1724985ffSEd Tanous #include "ssl_key_handler.hpp"
2724985ffSEd Tanous 
3724985ffSEd Tanous #include "bmcweb_config.h"
4724985ffSEd Tanous 
5724985ffSEd Tanous #include "logging.hpp"
6724985ffSEd Tanous #include "ossl_random.hpp"
7*41fe81c2SEd Tanous #include "sessions.hpp"
8724985ffSEd Tanous 
9*41fe81c2SEd Tanous #include <boost/asio/buffer.hpp>
10*41fe81c2SEd Tanous #include <boost/asio/ssl/verify_mode.hpp>
11*41fe81c2SEd Tanous #include <boost/beast/core/file_base.hpp>
12724985ffSEd Tanous #include <boost/beast/core/file_posix.hpp>
13724985ffSEd Tanous 
14*41fe81c2SEd Tanous #include <bit>
15*41fe81c2SEd Tanous #include <cstddef>
16*41fe81c2SEd Tanous #include <limits>
17*41fe81c2SEd Tanous #include <system_error>
18*41fe81c2SEd Tanous #include <utility>
19*41fe81c2SEd Tanous 
20724985ffSEd Tanous extern "C"
21724985ffSEd Tanous {
22724985ffSEd Tanous #include <nghttp2/nghttp2.h>
23*41fe81c2SEd Tanous #include <openssl/asn1.h>
24724985ffSEd Tanous #include <openssl/bio.h>
25*41fe81c2SEd Tanous #include <openssl/ec.h>
26724985ffSEd Tanous #include <openssl/err.h>
27724985ffSEd Tanous #include <openssl/evp.h>
28*41fe81c2SEd Tanous #include <openssl/obj_mac.h>
29724985ffSEd Tanous #include <openssl/pem.h>
30724985ffSEd Tanous #include <openssl/ssl.h>
31*41fe81c2SEd Tanous #include <openssl/tls1.h>
32*41fe81c2SEd Tanous #include <openssl/types.h>
33*41fe81c2SEd Tanous #include <openssl/x509.h>
34*41fe81c2SEd Tanous #include <openssl/x509_vfy.h>
35*41fe81c2SEd Tanous #include <openssl/x509v3.h>
36724985ffSEd Tanous }
37724985ffSEd Tanous 
38724985ffSEd Tanous #include <boost/asio/ssl/context.hpp>
39724985ffSEd Tanous #include <boost/system/error_code.hpp>
40724985ffSEd Tanous 
41724985ffSEd Tanous #include <filesystem>
42724985ffSEd Tanous #include <memory>
43724985ffSEd Tanous #include <optional>
44724985ffSEd Tanous #include <random>
45724985ffSEd Tanous #include <string>
46724985ffSEd Tanous 
47724985ffSEd Tanous namespace ensuressl
48724985ffSEd Tanous {
49724985ffSEd Tanous 
50724985ffSEd Tanous static EVP_PKEY* createEcKey();
51724985ffSEd Tanous 
52724985ffSEd Tanous // Mozilla intermediate cipher suites v5.7
53724985ffSEd Tanous // Sourced from: https://ssl-config.mozilla.org/guidelines/5.7.json
54bd79bce8SPatrick Williams constexpr const char* mozillaIntermediate =
55bd79bce8SPatrick Williams     "ECDHE-ECDSA-AES128-GCM-SHA256:"
56724985ffSEd Tanous     "ECDHE-RSA-AES128-GCM-SHA256:"
57724985ffSEd Tanous     "ECDHE-ECDSA-AES256-GCM-SHA384:"
58724985ffSEd Tanous     "ECDHE-RSA-AES256-GCM-SHA384:"
59724985ffSEd Tanous     "ECDHE-ECDSA-CHACHA20-POLY1305:"
60724985ffSEd Tanous     "ECDHE-RSA-CHACHA20-POLY1305:"
61724985ffSEd Tanous     "DHE-RSA-AES128-GCM-SHA256:"
62724985ffSEd Tanous     "DHE-RSA-AES256-GCM-SHA384:"
63724985ffSEd Tanous     "DHE-RSA-CHACHA20-POLY1305";
64724985ffSEd Tanous 
65724985ffSEd Tanous // Trust chain related errors.`
isTrustChainError(int errnum)66724985ffSEd Tanous bool isTrustChainError(int errnum)
67724985ffSEd Tanous {
68724985ffSEd Tanous     return (errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) ||
69724985ffSEd Tanous            (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) ||
70724985ffSEd Tanous            (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) ||
71724985ffSEd Tanous            (errnum == X509_V_ERR_CERT_UNTRUSTED) ||
72724985ffSEd Tanous            (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE);
73724985ffSEd Tanous }
74724985ffSEd Tanous 
validateCertificate(X509 * const cert)75724985ffSEd Tanous bool validateCertificate(X509* const cert)
76724985ffSEd Tanous {
77724985ffSEd Tanous     // Create an empty X509_STORE structure for certificate validation.
78724985ffSEd Tanous     X509_STORE* x509Store = X509_STORE_new();
79724985ffSEd Tanous     if (x509Store == nullptr)
80724985ffSEd Tanous     {
81724985ffSEd Tanous         BMCWEB_LOG_ERROR("Error occurred during X509_STORE_new call");
82724985ffSEd Tanous         return false;
83724985ffSEd Tanous     }
84724985ffSEd Tanous 
85724985ffSEd Tanous     // Load Certificate file into the X509 structure.
86724985ffSEd Tanous     X509_STORE_CTX* storeCtx = X509_STORE_CTX_new();
87724985ffSEd Tanous     if (storeCtx == nullptr)
88724985ffSEd Tanous     {
89724985ffSEd Tanous         BMCWEB_LOG_ERROR("Error occurred during X509_STORE_CTX_new call");
90724985ffSEd Tanous         X509_STORE_free(x509Store);
91724985ffSEd Tanous         return false;
92724985ffSEd Tanous     }
93724985ffSEd Tanous 
94724985ffSEd Tanous     int errCode = X509_STORE_CTX_init(storeCtx, x509Store, cert, nullptr);
95724985ffSEd Tanous     if (errCode != 1)
96724985ffSEd Tanous     {
97724985ffSEd Tanous         BMCWEB_LOG_ERROR("Error occurred during X509_STORE_CTX_init call");
98724985ffSEd Tanous         X509_STORE_CTX_free(storeCtx);
99724985ffSEd Tanous         X509_STORE_free(x509Store);
100724985ffSEd Tanous         return false;
101724985ffSEd Tanous     }
102724985ffSEd Tanous 
103724985ffSEd Tanous     errCode = X509_verify_cert(storeCtx);
104724985ffSEd Tanous     if (errCode == 1)
105724985ffSEd Tanous     {
106724985ffSEd Tanous         BMCWEB_LOG_INFO("Certificate verification is success");
107724985ffSEd Tanous         X509_STORE_CTX_free(storeCtx);
108724985ffSEd Tanous         X509_STORE_free(x509Store);
109724985ffSEd Tanous         return true;
110724985ffSEd Tanous     }
111724985ffSEd Tanous     if (errCode == 0)
112724985ffSEd Tanous     {
113724985ffSEd Tanous         errCode = X509_STORE_CTX_get_error(storeCtx);
114724985ffSEd Tanous         X509_STORE_CTX_free(storeCtx);
115724985ffSEd Tanous         X509_STORE_free(x509Store);
116724985ffSEd Tanous         if (isTrustChainError(errCode))
117724985ffSEd Tanous         {
118724985ffSEd Tanous             BMCWEB_LOG_DEBUG("Ignoring Trust Chain error. Reason: {}",
119724985ffSEd Tanous                              X509_verify_cert_error_string(errCode));
120724985ffSEd Tanous             return true;
121724985ffSEd Tanous         }
122724985ffSEd Tanous         BMCWEB_LOG_ERROR("Certificate verification failed. Reason: {}",
123724985ffSEd Tanous                          X509_verify_cert_error_string(errCode));
124724985ffSEd Tanous         return false;
125724985ffSEd Tanous     }
126724985ffSEd Tanous 
127724985ffSEd Tanous     BMCWEB_LOG_ERROR(
128724985ffSEd Tanous         "Error occurred during X509_verify_cert call. ErrorCode: {}", errCode);
129724985ffSEd Tanous     X509_STORE_CTX_free(storeCtx);
130724985ffSEd Tanous     X509_STORE_free(x509Store);
131724985ffSEd Tanous     return false;
132724985ffSEd Tanous }
133724985ffSEd Tanous 
verifyOpensslKeyCert(const std::string & filepath)134724985ffSEd Tanous std::string verifyOpensslKeyCert(const std::string& filepath)
135724985ffSEd Tanous {
136724985ffSEd Tanous     bool privateKeyValid = false;
137724985ffSEd Tanous 
138724985ffSEd Tanous     BMCWEB_LOG_INFO("Checking certs in file {}", filepath);
139724985ffSEd Tanous     boost::beast::file_posix file;
140724985ffSEd Tanous     boost::system::error_code ec;
141724985ffSEd Tanous     file.open(filepath.c_str(), boost::beast::file_mode::read, ec);
142724985ffSEd Tanous     if (ec)
143724985ffSEd Tanous     {
144724985ffSEd Tanous         return "";
145724985ffSEd Tanous     }
146724985ffSEd Tanous     bool certValid = false;
147724985ffSEd Tanous     std::string fileContents;
148724985ffSEd Tanous     fileContents.resize(static_cast<size_t>(file.size(ec)), '\0');
149724985ffSEd Tanous     file.read(fileContents.data(), fileContents.size(), ec);
150724985ffSEd Tanous     if (ec)
151724985ffSEd Tanous     {
152724985ffSEd Tanous         BMCWEB_LOG_ERROR("Failed to read file");
153724985ffSEd Tanous         return "";
154724985ffSEd Tanous     }
155724985ffSEd Tanous 
156724985ffSEd Tanous     BIO* bufio = BIO_new_mem_buf(static_cast<void*>(fileContents.data()),
157724985ffSEd Tanous                                  static_cast<int>(fileContents.size()));
158724985ffSEd Tanous     EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bufio, nullptr, nullptr, nullptr);
159724985ffSEd Tanous     BIO_free(bufio);
160724985ffSEd Tanous     if (pkey != nullptr)
161724985ffSEd Tanous     {
162bd79bce8SPatrick Williams         EVP_PKEY_CTX* pkeyCtx =
163bd79bce8SPatrick Williams             EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr);
164724985ffSEd Tanous 
165724985ffSEd Tanous         if (pkeyCtx == nullptr)
166724985ffSEd Tanous         {
167724985ffSEd Tanous             BMCWEB_LOG_ERROR("Unable to allocate pkeyCtx {}", ERR_get_error());
168724985ffSEd Tanous         }
169724985ffSEd Tanous         else if (EVP_PKEY_check(pkeyCtx) == 1)
170724985ffSEd Tanous         {
171724985ffSEd Tanous             privateKeyValid = true;
172724985ffSEd Tanous         }
173724985ffSEd Tanous         else
174724985ffSEd Tanous         {
175724985ffSEd Tanous             BMCWEB_LOG_ERROR("Key not valid error number {}", ERR_get_error());
176724985ffSEd Tanous         }
177724985ffSEd Tanous 
178724985ffSEd Tanous         if (privateKeyValid)
179724985ffSEd Tanous         {
180724985ffSEd Tanous             BIO* bufio2 =
181724985ffSEd Tanous                 BIO_new_mem_buf(static_cast<void*>(fileContents.data()),
182724985ffSEd Tanous                                 static_cast<int>(fileContents.size()));
183724985ffSEd Tanous             X509* x509 = PEM_read_bio_X509(bufio2, nullptr, nullptr, nullptr);
184724985ffSEd Tanous             BIO_free(bufio2);
185724985ffSEd Tanous             if (x509 == nullptr)
186724985ffSEd Tanous             {
187724985ffSEd Tanous                 BMCWEB_LOG_ERROR("error getting x509 cert {}", ERR_get_error());
188724985ffSEd Tanous             }
189724985ffSEd Tanous             else
190724985ffSEd Tanous             {
191724985ffSEd Tanous                 certValid = validateCertificate(x509);
192724985ffSEd Tanous                 X509_free(x509);
193724985ffSEd Tanous             }
194724985ffSEd Tanous         }
195724985ffSEd Tanous 
196724985ffSEd Tanous         EVP_PKEY_CTX_free(pkeyCtx);
197724985ffSEd Tanous         EVP_PKEY_free(pkey);
198724985ffSEd Tanous     }
199724985ffSEd Tanous     if (!certValid)
200724985ffSEd Tanous     {
201724985ffSEd Tanous         return "";
202724985ffSEd Tanous     }
203724985ffSEd Tanous     return fileContents;
204724985ffSEd Tanous }
205724985ffSEd Tanous 
loadCert(const std::string & filePath)206724985ffSEd Tanous X509* loadCert(const std::string& filePath)
207724985ffSEd Tanous {
208724985ffSEd Tanous     BIO* certFileBio = BIO_new_file(filePath.c_str(), "rb");
209724985ffSEd Tanous     if (certFileBio == nullptr)
210724985ffSEd Tanous     {
211724985ffSEd Tanous         BMCWEB_LOG_ERROR("Error occurred during BIO_new_file call, FILE= {}",
212724985ffSEd Tanous                          filePath);
213724985ffSEd Tanous         return nullptr;
214724985ffSEd Tanous     }
215724985ffSEd Tanous 
216724985ffSEd Tanous     X509* cert = X509_new();
217724985ffSEd Tanous     if (cert == nullptr)
218724985ffSEd Tanous     {
219724985ffSEd Tanous         BMCWEB_LOG_ERROR("Error occurred during X509_new call, {}",
220724985ffSEd Tanous                          ERR_get_error());
221724985ffSEd Tanous         BIO_free(certFileBio);
222724985ffSEd Tanous         return nullptr;
223724985ffSEd Tanous     }
224724985ffSEd Tanous 
225724985ffSEd Tanous     if (PEM_read_bio_X509(certFileBio, &cert, nullptr, nullptr) == nullptr)
226724985ffSEd Tanous     {
227724985ffSEd Tanous         BMCWEB_LOG_ERROR(
228724985ffSEd Tanous             "Error occurred during PEM_read_bio_X509 call, FILE= {}", filePath);
229724985ffSEd Tanous 
230724985ffSEd Tanous         BIO_free(certFileBio);
231724985ffSEd Tanous         X509_free(cert);
232724985ffSEd Tanous         return nullptr;
233724985ffSEd Tanous     }
234724985ffSEd Tanous     BIO_free(certFileBio);
235724985ffSEd Tanous     return cert;
236724985ffSEd Tanous }
237724985ffSEd Tanous 
addExt(X509 * cert,int nid,const char * value)238724985ffSEd Tanous int addExt(X509* cert, int nid, const char* value)
239724985ffSEd Tanous {
240724985ffSEd Tanous     X509_EXTENSION* ex = nullptr;
241724985ffSEd Tanous     X509V3_CTX ctx{};
242724985ffSEd Tanous     X509V3_set_ctx(&ctx, cert, cert, nullptr, nullptr, 0);
243724985ffSEd Tanous 
244724985ffSEd Tanous     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
245724985ffSEd Tanous     ex = X509V3_EXT_conf_nid(nullptr, &ctx, nid, const_cast<char*>(value));
246724985ffSEd Tanous     if (ex == nullptr)
247724985ffSEd Tanous     {
248724985ffSEd Tanous         BMCWEB_LOG_ERROR("Error: In X509V3_EXT_conf_nidn: {}", value);
249724985ffSEd Tanous         return -1;
250724985ffSEd Tanous     }
251724985ffSEd Tanous     X509_add_ext(cert, ex, -1);
252724985ffSEd Tanous     X509_EXTENSION_free(ex);
253724985ffSEd Tanous     return 0;
254724985ffSEd Tanous }
255724985ffSEd Tanous 
256724985ffSEd Tanous // Writes a certificate to a path, ignoring errors
writeCertificateToFile(const std::string & filepath,const std::string & certificate)257724985ffSEd Tanous void writeCertificateToFile(const std::string& filepath,
258724985ffSEd Tanous                             const std::string& certificate)
259724985ffSEd Tanous {
260724985ffSEd Tanous     boost::system::error_code ec;
261724985ffSEd Tanous     boost::beast::file_posix file;
262724985ffSEd Tanous     file.open(filepath.c_str(), boost::beast::file_mode::write, ec);
263724985ffSEd Tanous     if (!ec)
264724985ffSEd Tanous     {
265724985ffSEd Tanous         file.write(certificate.data(), certificate.size(), ec);
266724985ffSEd Tanous         // ignore result
267724985ffSEd Tanous     }
268724985ffSEd Tanous }
269724985ffSEd Tanous 
generateSslCertificate(const std::string & cn)270724985ffSEd Tanous std::string generateSslCertificate(const std::string& cn)
271724985ffSEd Tanous {
272724985ffSEd Tanous     BMCWEB_LOG_INFO("Generating new keys");
273724985ffSEd Tanous 
274724985ffSEd Tanous     std::string buffer;
275724985ffSEd Tanous     BMCWEB_LOG_INFO("Generating EC key");
276724985ffSEd Tanous     EVP_PKEY* pPrivKey = createEcKey();
277724985ffSEd Tanous     if (pPrivKey != nullptr)
278724985ffSEd Tanous     {
279724985ffSEd Tanous         BMCWEB_LOG_INFO("Generating x509 Certificates");
280724985ffSEd Tanous         // Use this code to directly generate a certificate
281724985ffSEd Tanous         X509* x509 = X509_new();
282724985ffSEd Tanous         if (x509 != nullptr)
283724985ffSEd Tanous         {
284724985ffSEd Tanous             // get a random number from the RNG for the certificate serial
285724985ffSEd Tanous             // number If this is not random, regenerating certs throws browser
286724985ffSEd Tanous             // errors
287724985ffSEd Tanous             bmcweb::OpenSSLGenerator gen;
288724985ffSEd Tanous             std::uniform_int_distribution<int> dis(
289724985ffSEd Tanous                 1, std::numeric_limits<int>::max());
290724985ffSEd Tanous             int serial = dis(gen);
291724985ffSEd Tanous 
292724985ffSEd Tanous             ASN1_INTEGER_set(X509_get_serialNumber(x509), serial);
293724985ffSEd Tanous 
294724985ffSEd Tanous             // not before this moment
295724985ffSEd Tanous             X509_gmtime_adj(X509_get_notBefore(x509), 0);
296724985ffSEd Tanous             // Cert is valid for 10 years
297724985ffSEd Tanous             X509_gmtime_adj(X509_get_notAfter(x509),
298724985ffSEd Tanous                             60L * 60L * 24L * 365L * 10L);
299724985ffSEd Tanous 
300724985ffSEd Tanous             // set the public key to the key we just generated
301724985ffSEd Tanous             X509_set_pubkey(x509, pPrivKey);
302724985ffSEd Tanous 
303724985ffSEd Tanous             // get the subject name
304724985ffSEd Tanous             X509_NAME* name = X509_get_subject_name(x509);
305724985ffSEd Tanous 
306724985ffSEd Tanous             using x509String = const unsigned char;
307724985ffSEd Tanous             // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
308724985ffSEd Tanous             x509String* country = reinterpret_cast<x509String*>("US");
309724985ffSEd Tanous             // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
310724985ffSEd Tanous             x509String* company = reinterpret_cast<x509String*>("OpenBMC");
311724985ffSEd Tanous             // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
312724985ffSEd Tanous             x509String* cnStr = reinterpret_cast<x509String*>(cn.c_str());
313724985ffSEd Tanous 
314724985ffSEd Tanous             X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, country, -1, -1,
315724985ffSEd Tanous                                        0);
316724985ffSEd Tanous             X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, company, -1, -1,
317724985ffSEd Tanous                                        0);
318724985ffSEd Tanous             X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, cnStr, -1, -1,
319724985ffSEd Tanous                                        0);
320724985ffSEd Tanous             // set the CSR options
321724985ffSEd Tanous             X509_set_issuer_name(x509, name);
322724985ffSEd Tanous 
323724985ffSEd Tanous             X509_set_version(x509, 2);
324724985ffSEd Tanous             addExt(x509, NID_basic_constraints, ("critical,CA:TRUE"));
325724985ffSEd Tanous             addExt(x509, NID_subject_alt_name, ("DNS:" + cn).c_str());
326724985ffSEd Tanous             addExt(x509, NID_subject_key_identifier, ("hash"));
327724985ffSEd Tanous             addExt(x509, NID_authority_key_identifier, ("keyid"));
328724985ffSEd Tanous             addExt(x509, NID_key_usage, ("digitalSignature, keyEncipherment"));
329724985ffSEd Tanous             addExt(x509, NID_ext_key_usage, ("serverAuth"));
330724985ffSEd Tanous             addExt(x509, NID_netscape_comment, (x509Comment));
331724985ffSEd Tanous 
332724985ffSEd Tanous             // Sign the certificate with our private key
333724985ffSEd Tanous             X509_sign(x509, pPrivKey, EVP_sha256());
334724985ffSEd Tanous 
335724985ffSEd Tanous             BIO* bufio = BIO_new(BIO_s_mem());
336724985ffSEd Tanous 
337724985ffSEd Tanous             int pkeyRet = PEM_write_bio_PrivateKey(
338724985ffSEd Tanous                 bufio, pPrivKey, nullptr, nullptr, 0, nullptr, nullptr);
339724985ffSEd Tanous             if (pkeyRet <= 0)
340724985ffSEd Tanous             {
341724985ffSEd Tanous                 BMCWEB_LOG_ERROR(
342724985ffSEd Tanous                     "Failed to write pkey with code {}.  Ignoring.", pkeyRet);
343724985ffSEd Tanous             }
344724985ffSEd Tanous 
345724985ffSEd Tanous             char* data = nullptr;
346724985ffSEd Tanous             long int dataLen = BIO_get_mem_data(bufio, &data);
347724985ffSEd Tanous             buffer += std::string_view(data, static_cast<size_t>(dataLen));
348724985ffSEd Tanous             BIO_free(bufio);
349724985ffSEd Tanous 
350724985ffSEd Tanous             bufio = BIO_new(BIO_s_mem());
351724985ffSEd Tanous             pkeyRet = PEM_write_bio_X509(bufio, x509);
352724985ffSEd Tanous             if (pkeyRet <= 0)
353724985ffSEd Tanous             {
354724985ffSEd Tanous                 BMCWEB_LOG_ERROR(
355724985ffSEd Tanous                     "Failed to write X509 with code {}.  Ignoring.", pkeyRet);
356724985ffSEd Tanous             }
357724985ffSEd Tanous             dataLen = BIO_get_mem_data(bufio, &data);
358724985ffSEd Tanous             buffer += std::string_view(data, static_cast<size_t>(dataLen));
359724985ffSEd Tanous 
360724985ffSEd Tanous             BIO_free(bufio);
361724985ffSEd Tanous             BMCWEB_LOG_INFO("Cert size is {}", buffer.size());
362724985ffSEd Tanous             X509_free(x509);
363724985ffSEd Tanous         }
364724985ffSEd Tanous 
365724985ffSEd Tanous         EVP_PKEY_free(pPrivKey);
366724985ffSEd Tanous         pPrivKey = nullptr;
367724985ffSEd Tanous     }
368724985ffSEd Tanous 
369724985ffSEd Tanous     // cleanup_openssl();
370724985ffSEd Tanous     return buffer;
371724985ffSEd Tanous }
372724985ffSEd Tanous 
createEcKey()373724985ffSEd Tanous EVP_PKEY* createEcKey()
374724985ffSEd Tanous {
375724985ffSEd Tanous     EVP_PKEY* pKey = nullptr;
376724985ffSEd Tanous 
377724985ffSEd Tanous     // Create context for curve parameter generation.
378724985ffSEd Tanous     std::unique_ptr<EVP_PKEY_CTX, decltype(&::EVP_PKEY_CTX_free)> ctx{
379724985ffSEd Tanous         EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), &::EVP_PKEY_CTX_free};
380724985ffSEd Tanous     if (!ctx)
381724985ffSEd Tanous     {
382724985ffSEd Tanous         return nullptr;
383724985ffSEd Tanous     }
384724985ffSEd Tanous 
385724985ffSEd Tanous     // Set up curve parameters.
386724985ffSEd Tanous     EVP_PKEY* params = nullptr;
387724985ffSEd Tanous     if ((EVP_PKEY_paramgen_init(ctx.get()) <= 0) ||
388724985ffSEd Tanous         (EVP_PKEY_CTX_set_ec_param_enc(ctx.get(), OPENSSL_EC_NAMED_CURVE) <=
389724985ffSEd Tanous          0) ||
390724985ffSEd Tanous         (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), NID_secp384r1) <=
391724985ffSEd Tanous          0) ||
392724985ffSEd Tanous         (EVP_PKEY_paramgen(ctx.get(), &params) <= 0))
393724985ffSEd Tanous     {
394724985ffSEd Tanous         return nullptr;
395724985ffSEd Tanous     }
396724985ffSEd Tanous 
397724985ffSEd Tanous     // Set up RAII holder for params.
398724985ffSEd Tanous     std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)> pparams{
399724985ffSEd Tanous         params, &::EVP_PKEY_free};
400724985ffSEd Tanous 
401724985ffSEd Tanous     // Set new context for key generation, using curve parameters.
402724985ffSEd Tanous     ctx.reset(EVP_PKEY_CTX_new_from_pkey(nullptr, params, nullptr));
403724985ffSEd Tanous     if (!ctx || (EVP_PKEY_keygen_init(ctx.get()) <= 0))
404724985ffSEd Tanous     {
405724985ffSEd Tanous         return nullptr;
406724985ffSEd Tanous     }
407724985ffSEd Tanous 
408724985ffSEd Tanous     // Generate key.
409724985ffSEd Tanous     if (EVP_PKEY_keygen(ctx.get(), &pKey) <= 0)
410724985ffSEd Tanous     {
411724985ffSEd Tanous         return nullptr;
412724985ffSEd Tanous     }
413724985ffSEd Tanous 
414724985ffSEd Tanous     return pKey;
415724985ffSEd Tanous }
416724985ffSEd Tanous 
ensureOpensslKeyPresentAndValid(const std::string & filepath)417724985ffSEd Tanous std::string ensureOpensslKeyPresentAndValid(const std::string& filepath)
418724985ffSEd Tanous {
419724985ffSEd Tanous     std::string cert = verifyOpensslKeyCert(filepath);
420724985ffSEd Tanous 
421724985ffSEd Tanous     if (cert.empty())
422724985ffSEd Tanous     {
423724985ffSEd Tanous         BMCWEB_LOG_WARNING("Error in verifying signature, regenerating");
424724985ffSEd Tanous         cert = generateSslCertificate("testhost");
425724985ffSEd Tanous         if (cert.empty())
426724985ffSEd Tanous         {
427724985ffSEd Tanous             BMCWEB_LOG_ERROR("Failed to generate cert");
428724985ffSEd Tanous         }
429724985ffSEd Tanous         else
430724985ffSEd Tanous         {
431724985ffSEd Tanous             writeCertificateToFile(filepath, cert);
432724985ffSEd Tanous         }
433724985ffSEd Tanous     }
434724985ffSEd Tanous     return cert;
435724985ffSEd Tanous }
436724985ffSEd Tanous 
ensureCertificate()437724985ffSEd Tanous static std::string ensureCertificate()
438724985ffSEd Tanous {
439724985ffSEd Tanous     namespace fs = std::filesystem;
440724985ffSEd Tanous     // Cleanup older certificate file existing in the system
441724985ffSEd Tanous     fs::path oldcertPath = fs::path("/home/root/server.pem");
442724985ffSEd Tanous     std::error_code ec;
443724985ffSEd Tanous     fs::remove(oldcertPath, ec);
444724985ffSEd Tanous     // Ignore failure to remove;  File might not exist.
445724985ffSEd Tanous 
446724985ffSEd Tanous     fs::path certPath = "/etc/ssl/certs/https/";
447724985ffSEd Tanous     // if path does not exist create the path so that
448724985ffSEd Tanous     // self signed certificate can be created in the
449724985ffSEd Tanous     // path
450724985ffSEd Tanous     fs::path certFile = certPath / "server.pem";
451724985ffSEd Tanous 
452724985ffSEd Tanous     if (!fs::exists(certPath, ec))
453724985ffSEd Tanous     {
454724985ffSEd Tanous         fs::create_directories(certPath, ec);
455724985ffSEd Tanous     }
456724985ffSEd Tanous     BMCWEB_LOG_INFO("Building SSL Context file= {}", certFile.string());
457724985ffSEd Tanous     std::string sslPemFile(certFile);
458724985ffSEd Tanous     return ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile);
459724985ffSEd Tanous }
460724985ffSEd Tanous 
nextProtoCallback(SSL *,const unsigned char ** data,unsigned int * len,void *)461724985ffSEd Tanous static int nextProtoCallback(SSL* /*unused*/, const unsigned char** data,
462724985ffSEd Tanous                              unsigned int* len, void* /*unused*/)
463724985ffSEd Tanous {
464724985ffSEd Tanous     // First byte is the length.
465724985ffSEd Tanous     constexpr std::string_view h2 = "\x02h2";
466724985ffSEd Tanous     *data = std::bit_cast<const unsigned char*>(h2.data());
467724985ffSEd Tanous     *len = static_cast<unsigned int>(h2.size());
468724985ffSEd Tanous     return SSL_TLSEXT_ERR_OK;
469724985ffSEd Tanous }
470724985ffSEd Tanous 
alpnSelectProtoCallback(SSL *,const unsigned char ** out,unsigned char * outlen,const unsigned char * in,unsigned int inlen,void *)471bd79bce8SPatrick Williams static int alpnSelectProtoCallback(
472bd79bce8SPatrick Williams     SSL* /*unused*/, const unsigned char** out, unsigned char* outlen,
473bd79bce8SPatrick Williams     const unsigned char* in, unsigned int inlen, void* /*unused*/)
474724985ffSEd Tanous {
475fa2d6ae7SEd Tanous     int rv = nghttp2_select_alpn(out, outlen, in, inlen);
476fa2d6ae7SEd Tanous     if (rv == -1)
477724985ffSEd Tanous     {
478724985ffSEd Tanous         return SSL_TLSEXT_ERR_NOACK;
479724985ffSEd Tanous     }
480fa2d6ae7SEd Tanous     if (rv == 1)
481fa2d6ae7SEd Tanous     {
482fa2d6ae7SEd Tanous         BMCWEB_LOG_DEBUG("Selected HTTP2");
483fa2d6ae7SEd Tanous     }
484724985ffSEd Tanous     return SSL_TLSEXT_ERR_OK;
485724985ffSEd Tanous }
486724985ffSEd Tanous 
getSslContext(boost::asio::ssl::context & mSslContext,const std::string & sslPemFile)487724985ffSEd Tanous static bool getSslContext(boost::asio::ssl::context& mSslContext,
488724985ffSEd Tanous                           const std::string& sslPemFile)
489724985ffSEd Tanous {
490bd79bce8SPatrick Williams     mSslContext.set_options(
491bd79bce8SPatrick Williams         boost::asio::ssl::context::default_workarounds |
492724985ffSEd Tanous         boost::asio::ssl::context::no_sslv2 |
493724985ffSEd Tanous         boost::asio::ssl::context::no_sslv3 |
494724985ffSEd Tanous         boost::asio::ssl::context::single_dh_use |
495724985ffSEd Tanous         boost::asio::ssl::context::no_tlsv1 |
496724985ffSEd Tanous         boost::asio::ssl::context::no_tlsv1_1);
497724985ffSEd Tanous 
498724985ffSEd Tanous     BMCWEB_LOG_DEBUG("Using default TrustStore location: {}", trustStorePath);
499724985ffSEd Tanous     mSslContext.add_verify_path(trustStorePath);
500724985ffSEd Tanous 
501724985ffSEd Tanous     if (!sslPemFile.empty())
502724985ffSEd Tanous     {
503724985ffSEd Tanous         boost::system::error_code ec;
504724985ffSEd Tanous 
505724985ffSEd Tanous         boost::asio::const_buffer buf(sslPemFile.data(), sslPemFile.size());
506724985ffSEd Tanous         mSslContext.use_certificate(buf, boost::asio::ssl::context::pem, ec);
507724985ffSEd Tanous         if (ec)
508724985ffSEd Tanous         {
509724985ffSEd Tanous             return false;
510724985ffSEd Tanous         }
511724985ffSEd Tanous         mSslContext.use_private_key(buf, boost::asio::ssl::context::pem, ec);
512724985ffSEd Tanous         if (ec)
513724985ffSEd Tanous         {
514724985ffSEd Tanous             BMCWEB_LOG_CRITICAL("Failed to open ssl pkey");
515724985ffSEd Tanous             return false;
516724985ffSEd Tanous         }
517724985ffSEd Tanous     }
518724985ffSEd Tanous 
519724985ffSEd Tanous     // Set up EC curves to auto (boost asio doesn't have a method for this)
520724985ffSEd Tanous     // There is a pull request to add this.  Once this is included in an asio
521724985ffSEd Tanous     // drop, use the right way
522724985ffSEd Tanous     // http://stackoverflow.com/questions/18929049/boost-asio-with-ecdsa-certificate-issue
523724985ffSEd Tanous     if (SSL_CTX_set_ecdh_auto(mSslContext.native_handle(), 1) != 1)
524724985ffSEd Tanous     {}
525724985ffSEd Tanous 
526724985ffSEd Tanous     if (SSL_CTX_set_cipher_list(mSslContext.native_handle(),
527724985ffSEd Tanous                                 mozillaIntermediate) != 1)
528724985ffSEd Tanous     {
529724985ffSEd Tanous         BMCWEB_LOG_ERROR("Error setting cipher list");
530724985ffSEd Tanous         return false;
531724985ffSEd Tanous     }
532724985ffSEd Tanous     return true;
533724985ffSEd Tanous }
534724985ffSEd Tanous 
getSslServerContext()535724985ffSEd Tanous std::shared_ptr<boost::asio::ssl::context> getSslServerContext()
536724985ffSEd Tanous {
537724985ffSEd Tanous     boost::asio::ssl::context sslCtx(boost::asio::ssl::context::tls_server);
538724985ffSEd Tanous 
539724985ffSEd Tanous     auto certFile = ensureCertificate();
540724985ffSEd Tanous     if (!getSslContext(sslCtx, certFile))
541724985ffSEd Tanous     {
542724985ffSEd Tanous         BMCWEB_LOG_CRITICAL("Couldn't get server context");
543724985ffSEd Tanous         return nullptr;
544724985ffSEd Tanous     }
545724985ffSEd Tanous     const persistent_data::AuthConfigMethods& c =
546724985ffSEd Tanous         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
547724985ffSEd Tanous 
548724985ffSEd Tanous     boost::asio::ssl::verify_mode mode = boost::asio::ssl::verify_peer;
549724985ffSEd Tanous     if (c.tlsStrict)
550724985ffSEd Tanous     {
551724985ffSEd Tanous         BMCWEB_LOG_DEBUG("Setting verify peer");
552724985ffSEd Tanous         mode |= boost::asio::ssl::verify_fail_if_no_peer_cert;
553724985ffSEd Tanous     }
554724985ffSEd Tanous 
555724985ffSEd Tanous     boost::system::error_code ec;
556724985ffSEd Tanous     sslCtx.set_verify_mode(mode, ec);
557724985ffSEd Tanous     if (ec)
558724985ffSEd Tanous     {
559724985ffSEd Tanous         BMCWEB_LOG_DEBUG("Failed to set verify mode {}", ec.message());
560724985ffSEd Tanous         return nullptr;
561724985ffSEd Tanous     }
562724985ffSEd Tanous     SSL_CTX_set_options(sslCtx.native_handle(), SSL_OP_NO_RENEGOTIATION);
563724985ffSEd Tanous 
564724985ffSEd Tanous     if constexpr (BMCWEB_EXPERIMENTAL_HTTP2)
565724985ffSEd Tanous     {
566724985ffSEd Tanous         SSL_CTX_set_next_protos_advertised_cb(sslCtx.native_handle(),
567724985ffSEd Tanous                                               nextProtoCallback, nullptr);
568724985ffSEd Tanous 
569724985ffSEd Tanous         SSL_CTX_set_alpn_select_cb(sslCtx.native_handle(),
570724985ffSEd Tanous                                    alpnSelectProtoCallback, nullptr);
571724985ffSEd Tanous     }
572724985ffSEd Tanous 
573724985ffSEd Tanous     return std::make_shared<boost::asio::ssl::context>(std::move(sslCtx));
574724985ffSEd Tanous }
575724985ffSEd Tanous 
576724985ffSEd Tanous std::optional<boost::asio::ssl::context>
getSSLClientContext(VerifyCertificate verifyCertificate)577724985ffSEd Tanous     getSSLClientContext(VerifyCertificate verifyCertificate)
578724985ffSEd Tanous {
579724985ffSEd Tanous     namespace fs = std::filesystem;
580724985ffSEd Tanous 
581724985ffSEd Tanous     boost::asio::ssl::context sslCtx(boost::asio::ssl::context::tls_client);
582724985ffSEd Tanous 
583724985ffSEd Tanous     // NOTE, this path is temporary;  In the future it will need to change to
584724985ffSEd Tanous     // be set per subscription.  Do not rely on this.
585724985ffSEd Tanous     fs::path certPath = "/etc/ssl/certs/https/client.pem";
586724985ffSEd Tanous     std::string cert = verifyOpensslKeyCert(certPath);
587724985ffSEd Tanous 
588724985ffSEd Tanous     if (!getSslContext(sslCtx, cert))
589724985ffSEd Tanous     {
590724985ffSEd Tanous         return std::nullopt;
591724985ffSEd Tanous     }
592724985ffSEd Tanous 
593724985ffSEd Tanous     // Add a directory containing certificate authority files to be used
594724985ffSEd Tanous     // for performing verification.
595724985ffSEd Tanous     boost::system::error_code ec;
596724985ffSEd Tanous     sslCtx.set_default_verify_paths(ec);
597724985ffSEd Tanous     if (ec)
598724985ffSEd Tanous     {
599724985ffSEd Tanous         BMCWEB_LOG_ERROR("SSL context set_default_verify failed");
600724985ffSEd Tanous         return std::nullopt;
601724985ffSEd Tanous     }
602724985ffSEd Tanous 
603724985ffSEd Tanous     int mode = boost::asio::ssl::verify_peer;
604724985ffSEd Tanous     if (verifyCertificate == VerifyCertificate::NoVerify)
605724985ffSEd Tanous     {
606724985ffSEd Tanous         mode = boost::asio::ssl::verify_none;
607724985ffSEd Tanous     }
608724985ffSEd Tanous 
609724985ffSEd Tanous     // Verify the remote server's certificate
610724985ffSEd Tanous     sslCtx.set_verify_mode(mode, ec);
611724985ffSEd Tanous     if (ec)
612724985ffSEd Tanous     {
613724985ffSEd Tanous         BMCWEB_LOG_ERROR("SSL context set_verify_mode failed");
614724985ffSEd Tanous         return std::nullopt;
615724985ffSEd Tanous     }
616724985ffSEd Tanous 
617724985ffSEd Tanous     if (SSL_CTX_set_cipher_list(sslCtx.native_handle(), mozillaIntermediate) !=
618724985ffSEd Tanous         1)
619724985ffSEd Tanous     {
620724985ffSEd Tanous         BMCWEB_LOG_ERROR("SSL_CTX_set_cipher_list failed");
621724985ffSEd Tanous         return std::nullopt;
622724985ffSEd Tanous     }
623724985ffSEd Tanous 
624724985ffSEd Tanous     return {std::move(sslCtx)};
625724985ffSEd Tanous }
626724985ffSEd Tanous 
627724985ffSEd Tanous } // namespace ensuressl
628