blob: a8bcfa5b6d5550f658083660a58a4dc3d88de30e [file] [log] [blame]
Ed Tanous0fdddb12017-02-28 11:06:34 -08001#pragma once
2
Ed Tanous62598e32023-07-17 17:06:25 -07003#include "bmcweb_config.h"
4
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08005#include "logging.hpp"
Ed Tanous2c6ffdb2023-06-28 11:28:38 -07006#include "ossl_random.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08007
Ed Tanous099225c2024-03-27 22:03:05 -07008#include <boost/beast/core/file_posix.hpp>
9
Ed Tanousfca2cbe2021-01-28 14:49:59 -080010extern "C"
11{
12#include <nghttp2/nghttp2.h>
Ed Tanous0fdddb12017-02-28 11:06:34 -080013#include <openssl/bio.h>
14#include <openssl/dh.h>
15#include <openssl/dsa.h>
Ed Tanous0fdddb12017-02-28 11:06:34 -080016#include <openssl/err.h>
17#include <openssl/evp.h>
18#include <openssl/pem.h>
19#include <openssl/rand.h>
20#include <openssl/rsa.h>
21#include <openssl/ssl.h>
Ed Tanous6dbe9be2024-04-14 10:24:20 -070022}
Ed Tanous9b65f1f2017-03-07 15:17:13 -080023
Ed Tanous3112a142018-11-29 15:45:10 -080024#include <boost/asio/ssl/context.hpp>
Ed Tanous099225c2024-03-27 22:03:05 -070025#include <boost/system/error_code.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050026
Abhilash Rajud5fb5842024-06-03 11:40:17 -050027#include <filesystem>
28#include <memory>
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080029#include <optional>
Ed Tanous1abe55e2018-09-05 08:30:59 -070030#include <random>
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080031#include <string>
Ed Tanous1abe55e2018-09-05 08:30:59 -070032
33namespace ensuressl
34{
Patrick Williams89492a12023-05-10 07:51:34 -050035constexpr const char* trustStorePath = "/etc/ssl/certs/authority";
36constexpr const char* x509Comment = "Generated from OpenBMC service";
Ed Tanous55c7b7a2018-05-22 15:27:24 -070037static void initOpenssl();
Gunnar Mills1214b7e2020-06-04 10:11:30 -050038static EVP_PKEY* createEcKey();
Ed Tanous0fdddb12017-02-28 11:06:34 -080039
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050040// Trust chain related errors.`
41inline bool isTrustChainError(int errnum)
42{
Ed Tanous55f79e62022-01-25 11:26:16 -080043 return (errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) ||
44 (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) ||
45 (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) ||
46 (errnum == X509_V_ERR_CERT_UNTRUSTED) ||
47 (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE);
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050048}
49
Gunnar Mills1214b7e2020-06-04 10:11:30 -050050inline bool validateCertificate(X509* const cert)
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050051{
52 // Create an empty X509_STORE structure for certificate validation.
Gunnar Mills1214b7e2020-06-04 10:11:30 -050053 X509_STORE* x509Store = X509_STORE_new();
Ed Tanouse662eae2022-01-25 10:39:19 -080054 if (x509Store == nullptr)
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050055 {
Ed Tanous62598e32023-07-17 17:06:25 -070056 BMCWEB_LOG_ERROR("Error occurred during X509_STORE_new call");
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050057 return false;
58 }
59
60 // Load Certificate file into the X509 structure.
Gunnar Mills1214b7e2020-06-04 10:11:30 -050061 X509_STORE_CTX* storeCtx = X509_STORE_CTX_new();
Ed Tanouse662eae2022-01-25 10:39:19 -080062 if (storeCtx == nullptr)
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050063 {
Ed Tanous62598e32023-07-17 17:06:25 -070064 BMCWEB_LOG_ERROR("Error occurred during X509_STORE_CTX_new call");
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050065 X509_STORE_free(x509Store);
66 return false;
67 }
68
Ed Tanous99131cd2019-10-24 11:12:47 -070069 int errCode = X509_STORE_CTX_init(storeCtx, x509Store, cert, nullptr);
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050070 if (errCode != 1)
71 {
Ed Tanous62598e32023-07-17 17:06:25 -070072 BMCWEB_LOG_ERROR("Error occurred during X509_STORE_CTX_init call");
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050073 X509_STORE_CTX_free(storeCtx);
74 X509_STORE_free(x509Store);
75 return false;
76 }
77
78 errCode = X509_verify_cert(storeCtx);
79 if (errCode == 1)
80 {
Ed Tanous62598e32023-07-17 17:06:25 -070081 BMCWEB_LOG_INFO("Certificate verification is success");
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050082 X509_STORE_CTX_free(storeCtx);
83 X509_STORE_free(x509Store);
84 return true;
85 }
86 if (errCode == 0)
87 {
88 errCode = X509_STORE_CTX_get_error(storeCtx);
89 X509_STORE_CTX_free(storeCtx);
90 X509_STORE_free(x509Store);
91 if (isTrustChainError(errCode))
92 {
Ed Tanous62598e32023-07-17 17:06:25 -070093 BMCWEB_LOG_DEBUG("Ignoring Trust Chain error. Reason: {}",
94 X509_verify_cert_error_string(errCode));
Ramesh Iyyar082f28f2019-06-22 03:31:55 -050095 return true;
96 }
Ed Tanous62598e32023-07-17 17:06:25 -070097 BMCWEB_LOG_ERROR("Certificate verification failed. Reason: {}",
98 X509_verify_cert_error_string(errCode));
Ed Tanous3174e4d2020-10-07 11:41:22 -070099 return false;
Ramesh Iyyar082f28f2019-06-22 03:31:55 -0500100 }
101
Ed Tanous62598e32023-07-17 17:06:25 -0700102 BMCWEB_LOG_ERROR(
103 "Error occurred during X509_verify_cert call. ErrorCode: {}", errCode);
Ramesh Iyyar082f28f2019-06-22 03:31:55 -0500104 X509_STORE_CTX_free(storeCtx);
105 X509_STORE_free(x509Store);
106 return false;
107}
108
Ed Tanous099225c2024-03-27 22:03:05 -0700109inline std::string verifyOpensslKeyCert(const std::string& filepath)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700110{
111 bool privateKeyValid = false;
Ed Tanous9b65f1f2017-03-07 15:17:13 -0800112
Ed Tanousc160ae72024-03-27 18:45:20 -0700113 BMCWEB_LOG_INFO("Checking certs in file {}", filepath);
Ed Tanous099225c2024-03-27 22:03:05 -0700114 boost::beast::file_posix file;
115 boost::system::error_code ec;
116 file.open(filepath.c_str(), boost::beast::file_mode::read, ec);
117 if (ec)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700118 {
Ed Tanous099225c2024-03-27 22:03:05 -0700119 return "";
120 }
121 bool certValid = false;
122 std::string fileContents;
123 fileContents.resize(static_cast<size_t>(file.size(ec)), '\0');
124 file.read(fileContents.data(), fileContents.size(), ec);
125 if (ec)
126 {
127 BMCWEB_LOG_ERROR("Failed to read file");
128 return "";
129 }
Patrick Williams145bb762021-12-07 21:05:04 -0600130
Ed Tanous099225c2024-03-27 22:03:05 -0700131 BIO* bufio = BIO_new_mem_buf(static_cast<void*>(fileContents.data()),
132 static_cast<int>(fileContents.size()));
133 EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bufio, nullptr, nullptr, nullptr);
134 BIO_free(bufio);
135 if (pkey != nullptr)
136 {
137#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
138 RSA* rsa = EVP_PKEY_get1_RSA(pkey);
139 if (rsa != nullptr)
140 {
141 BMCWEB_LOG_INFO("Found an RSA key");
142 if (RSA_check_key(rsa) == 1)
Patrick Williams145bb762021-12-07 21:05:04 -0600143 {
144 privateKeyValid = true;
145 }
146 else
147 {
Ed Tanousc160ae72024-03-27 18:45:20 -0700148 BMCWEB_LOG_ERROR("Key not valid error number {}",
149 ERR_get_error());
Patrick Williams145bb762021-12-07 21:05:04 -0600150 }
Ed Tanous099225c2024-03-27 22:03:05 -0700151 RSA_free(rsa);
152 }
153 else
154 {
155 EC_KEY* ec = EVP_PKEY_get1_EC_KEY(pkey);
156 if (ec != nullptr)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700157 {
Ed Tanous099225c2024-03-27 22:03:05 -0700158 BMCWEB_LOG_INFO("Found an EC key");
159 if (EC_KEY_check_key(ec) == 1)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700160 {
Ed Tanous099225c2024-03-27 22:03:05 -0700161 privateKeyValid = true;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700162 }
163 else
164 {
Ed Tanous099225c2024-03-27 22:03:05 -0700165 BMCWEB_LOG_ERROR("Key not valid error number {}",
166 ERR_get_error());
Ed Tanous1abe55e2018-09-05 08:30:59 -0700167 }
Ed Tanous099225c2024-03-27 22:03:05 -0700168 EC_KEY_free(ec);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700169 }
Ed Tanous099225c2024-03-27 22:03:05 -0700170 }
171#else
172 EVP_PKEY_CTX* pkeyCtx = EVP_PKEY_CTX_new_from_pkey(nullptr, pkey,
173 nullptr);
174
175 if (pkeyCtx == nullptr)
176 {
177 BMCWEB_LOG_ERROR("Unable to allocate pkeyCtx {}", ERR_get_error());
178 }
179 else if (EVP_PKEY_check(pkeyCtx) == 1)
180 {
181 privateKeyValid = true;
182 }
183 else
184 {
185 BMCWEB_LOG_ERROR("Key not valid error number {}", ERR_get_error());
186 }
187#endif
188
189 if (privateKeyValid)
190 {
191 BIO* bufio2 =
192 BIO_new_mem_buf(static_cast<void*>(fileContents.data()),
193 static_cast<int>(fileContents.size()));
194 X509* x509 = PEM_read_bio_X509(bufio2, nullptr, nullptr, nullptr);
195 BIO_free(bufio2);
196 if (x509 == nullptr)
197 {
198 BMCWEB_LOG_ERROR("error getting x509 cert {}", ERR_get_error());
199 }
200 else
201 {
202 certValid = validateCertificate(x509);
203 X509_free(x509);
204 }
205 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700206
Patrick Williams145bb762021-12-07 21:05:04 -0600207#if (OPENSSL_VERSION_NUMBER > 0x30000000L)
Ed Tanous099225c2024-03-27 22:03:05 -0700208 EVP_PKEY_CTX_free(pkeyCtx);
Patrick Williams145bb762021-12-07 21:05:04 -0600209#endif
Ed Tanous099225c2024-03-27 22:03:05 -0700210 EVP_PKEY_free(pkey);
Ed Tanous0fdddb12017-02-28 11:06:34 -0800211 }
Ed Tanous099225c2024-03-27 22:03:05 -0700212 if (!certValid)
213 {
214 return "";
215 }
216 return fileContents;
Ed Tanous0fdddb12017-02-28 11:06:34 -0800217}
218
Alan Kuoa8220702020-11-26 11:15:29 +0800219inline X509* loadCert(const std::string& filePath)
220{
221 BIO* certFileBio = BIO_new_file(filePath.c_str(), "rb");
Ed Tanouse662eae2022-01-25 10:39:19 -0800222 if (certFileBio == nullptr)
Alan Kuoa8220702020-11-26 11:15:29 +0800223 {
Ed Tanous8ece0e42024-01-02 13:16:50 -0800224 BMCWEB_LOG_ERROR("Error occurred during BIO_new_file call, FILE= {}",
Ed Tanous62598e32023-07-17 17:06:25 -0700225 filePath);
Alan Kuoa8220702020-11-26 11:15:29 +0800226 return nullptr;
227 }
228
229 X509* cert = X509_new();
Ed Tanouse662eae2022-01-25 10:39:19 -0800230 if (cert == nullptr)
Alan Kuoa8220702020-11-26 11:15:29 +0800231 {
Ed Tanous8ece0e42024-01-02 13:16:50 -0800232 BMCWEB_LOG_ERROR("Error occurred during X509_new call, {}",
Ed Tanous62598e32023-07-17 17:06:25 -0700233 ERR_get_error());
Alan Kuoa8220702020-11-26 11:15:29 +0800234 BIO_free(certFileBio);
235 return nullptr;
236 }
237
Ed Tanouse662eae2022-01-25 10:39:19 -0800238 if (PEM_read_bio_X509(certFileBio, &cert, nullptr, nullptr) == nullptr)
Alan Kuoa8220702020-11-26 11:15:29 +0800239 {
Ed Tanous62598e32023-07-17 17:06:25 -0700240 BMCWEB_LOG_ERROR(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800241 "Error occurred during PEM_read_bio_X509 call, FILE= {}", filePath);
Alan Kuoa8220702020-11-26 11:15:29 +0800242
243 BIO_free(certFileBio);
244 X509_free(cert);
245 return nullptr;
246 }
Alan Kuob295bf92021-04-16 14:16:47 +0800247 BIO_free(certFileBio);
Alan Kuoa8220702020-11-26 11:15:29 +0800248 return cert;
249}
250
251inline int addExt(X509* cert, int nid, const char* value)
252{
253 X509_EXTENSION* ex = nullptr;
Ed Tanousb00dcc22021-02-23 12:52:50 -0800254 X509V3_CTX ctx{};
Alan Kuoa8220702020-11-26 11:15:29 +0800255 X509V3_set_ctx(&ctx, cert, cert, nullptr, nullptr, 0);
Ed Tanous4ecc6182022-01-07 09:36:26 -0800256
257 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
Alan Kuoa8220702020-11-26 11:15:29 +0800258 ex = X509V3_EXT_conf_nid(nullptr, &ctx, nid, const_cast<char*>(value));
Ed Tanouse662eae2022-01-25 10:39:19 -0800259 if (ex == nullptr)
Alan Kuoa8220702020-11-26 11:15:29 +0800260 {
Ed Tanous62598e32023-07-17 17:06:25 -0700261 BMCWEB_LOG_ERROR("Error: In X509V3_EXT_conf_nidn: {}", value);
Alan Kuoa8220702020-11-26 11:15:29 +0800262 return -1;
263 }
264 X509_add_ext(cert, ex, -1);
265 X509_EXTENSION_free(ex);
266 return 0;
267}
268
Ed Tanous099225c2024-03-27 22:03:05 -0700269// Writes a certificate to a path, ignoring errors
270inline void writeCertificateToFile(const std::string& filepath,
271 const std::string& certificate)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700272{
Ed Tanous099225c2024-03-27 22:03:05 -0700273 boost::system::error_code ec;
274 boost::beast::file_posix file;
275 file.open(filepath.c_str(), boost::beast::file_mode::write, ec);
276 if (!ec)
277 {
278 file.write(certificate.data(), certificate.size(), ec);
279 // ignore result
280 }
281}
282
283inline std::string generateSslCertificate(const std::string& cn)
284{
Ed Tanousc160ae72024-03-27 18:45:20 -0700285 BMCWEB_LOG_INFO("Generating new keys");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700286 initOpenssl();
Ed Tanous0fdddb12017-02-28 11:06:34 -0800287
Ed Tanous099225c2024-03-27 22:03:05 -0700288 std::string buffer;
Ed Tanousc160ae72024-03-27 18:45:20 -0700289 BMCWEB_LOG_INFO("Generating EC key");
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500290 EVP_PKEY* pPrivKey = createEcKey();
Vernon Mauery0185c7f2020-03-09 10:56:53 -0700291 if (pPrivKey != nullptr)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700292 {
Ed Tanousc160ae72024-03-27 18:45:20 -0700293 BMCWEB_LOG_INFO("Generating x509 Certificates");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700294 // Use this code to directly generate a certificate
Ed Tanous543f4402022-01-06 13:12:53 -0800295 X509* x509 = X509_new();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700296 if (x509 != nullptr)
297 {
298 // get a random number from the RNG for the certificate serial
Ed Tanous8ece0e42024-01-02 13:16:50 -0800299 // number If this is not random, regenerating certs throws browser
Ed Tanous1abe55e2018-09-05 08:30:59 -0700300 // errors
Alan Kuoa8220702020-11-26 11:15:29 +0800301 bmcweb::OpenSSLGenerator gen;
302 std::uniform_int_distribution<int> dis(
303 1, std::numeric_limits<int>::max());
304 int serial = dis(gen);
Ed Tanous9b65f1f2017-03-07 15:17:13 -0800305
Ed Tanous1abe55e2018-09-05 08:30:59 -0700306 ASN1_INTEGER_set(X509_get_serialNumber(x509), serial);
Ed Tanous9b65f1f2017-03-07 15:17:13 -0800307
Ed Tanous1abe55e2018-09-05 08:30:59 -0700308 // not before this moment
309 X509_gmtime_adj(X509_get_notBefore(x509), 0);
310 // Cert is valid for 10 years
311 X509_gmtime_adj(X509_get_notAfter(x509),
312 60L * 60L * 24L * 365L * 10L);
Ed Tanous9b65f1f2017-03-07 15:17:13 -0800313
Ed Tanous1abe55e2018-09-05 08:30:59 -0700314 // set the public key to the key we just generated
Vernon Mauery0185c7f2020-03-09 10:56:53 -0700315 X509_set_pubkey(x509, pPrivKey);
Ed Tanous0fdddb12017-02-28 11:06:34 -0800316
Ed Tanous1abe55e2018-09-05 08:30:59 -0700317 // get the subject name
Ed Tanous543f4402022-01-06 13:12:53 -0800318 X509_NAME* name = X509_get_subject_name(x509);
Ed Tanous0fdddb12017-02-28 11:06:34 -0800319
Ed Tanous46ff87b2022-01-07 09:25:51 -0800320 using x509String = const unsigned char;
321 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
322 x509String* country = reinterpret_cast<x509String*>("US");
323 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
324 x509String* company = reinterpret_cast<x509String*>("OpenBMC");
325 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
326 x509String* cnStr = reinterpret_cast<x509String*>(cn.c_str());
327
328 X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, country, -1, -1,
329 0);
330 X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, company, -1, -1,
331 0);
332 X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, cnStr, -1, -1,
333 0);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700334 // set the CSR options
335 X509_set_issuer_name(x509, name);
Ed Tanous0fdddb12017-02-28 11:06:34 -0800336
Alan Kuoa8220702020-11-26 11:15:29 +0800337 X509_set_version(x509, 2);
338 addExt(x509, NID_basic_constraints, ("critical,CA:TRUE"));
339 addExt(x509, NID_subject_alt_name, ("DNS:" + cn).c_str());
340 addExt(x509, NID_subject_key_identifier, ("hash"));
341 addExt(x509, NID_authority_key_identifier, ("keyid"));
342 addExt(x509, NID_key_usage, ("digitalSignature, keyEncipherment"));
343 addExt(x509, NID_ext_key_usage, ("serverAuth"));
344 addExt(x509, NID_netscape_comment, (x509Comment));
345
Ed Tanous1abe55e2018-09-05 08:30:59 -0700346 // Sign the certificate with our private key
Vernon Mauery0185c7f2020-03-09 10:56:53 -0700347 X509_sign(x509, pPrivKey, EVP_sha256());
Ed Tanous0fdddb12017-02-28 11:06:34 -0800348
Ed Tanous099225c2024-03-27 22:03:05 -0700349 BIO* bufio = BIO_new(BIO_s_mem());
Ed Tanous0fdddb12017-02-28 11:06:34 -0800350
Ed Tanous099225c2024-03-27 22:03:05 -0700351 int pkeyRet = PEM_write_bio_PrivateKey(
352 bufio, pPrivKey, nullptr, nullptr, 0, nullptr, nullptr);
353 if (pkeyRet <= 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700354 {
Ed Tanous099225c2024-03-27 22:03:05 -0700355 BMCWEB_LOG_ERROR(
356 "Failed to write pkey with code {}. Ignoring.", pkeyRet);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700357 }
Ed Tanous0fdddb12017-02-28 11:06:34 -0800358
Ed Tanous099225c2024-03-27 22:03:05 -0700359 char* data = nullptr;
360 long int dataLen = BIO_get_mem_data(bufio, &data);
361 buffer += std::string_view(data, static_cast<size_t>(dataLen));
362 BIO_free(bufio);
363
364 bufio = BIO_new(BIO_s_mem());
365 pkeyRet = PEM_write_bio_X509(bufio, x509);
366 if (pkeyRet <= 0)
367 {
368 BMCWEB_LOG_ERROR(
369 "Failed to write X509 with code {}. Ignoring.", pkeyRet);
370 }
371 dataLen = BIO_get_mem_data(bufio, &data);
372 buffer += std::string_view(data, static_cast<size_t>(dataLen));
373
374 BIO_free(bufio);
375 BMCWEB_LOG_INFO("Cert size is {}", buffer.size());
Ed Tanous1abe55e2018-09-05 08:30:59 -0700376 X509_free(x509);
377 }
378
Vernon Mauery0185c7f2020-03-09 10:56:53 -0700379 EVP_PKEY_free(pPrivKey);
380 pPrivKey = nullptr;
Ed Tanous0fdddb12017-02-28 11:06:34 -0800381 }
382
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383 // cleanup_openssl();
Ed Tanous099225c2024-03-27 22:03:05 -0700384 return buffer;
Ed Tanous99923322017-03-03 14:21:24 -0800385}
Ed Tanousb01bf292019-03-25 19:25:26 +0000386
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500387EVP_PKEY* createEcKey()
Ed Tanousb01bf292019-03-25 19:25:26 +0000388{
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500389 EVP_PKEY* pKey = nullptr;
Patrick Williamsaec70662021-12-13 16:55:46 -0600390
391#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
Ed Tanousb01bf292019-03-25 19:25:26 +0000392 int eccgrp = 0;
Vernon Maueryaaf32062020-03-09 10:41:31 -0700393 eccgrp = OBJ_txt2nid("secp384r1");
Ed Tanous6ea007a2019-02-13 22:48:25 -0800394
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500395 EC_KEY* myecc = EC_KEY_new_by_curve_name(eccgrp);
Ed Tanousb01bf292019-03-25 19:25:26 +0000396 if (myecc != nullptr)
Ed Tanous6ea007a2019-02-13 22:48:25 -0800397 {
Ed Tanousb01bf292019-03-25 19:25:26 +0000398 EC_KEY_set_asn1_flag(myecc, OPENSSL_EC_NAMED_CURVE);
399 EC_KEY_generate_key(myecc);
400 pKey = EVP_PKEY_new();
401 if (pKey != nullptr)
402 {
Szymon Dompkea327dc52022-03-01 14:04:14 +0100403 if (EVP_PKEY_assign(pKey, EVP_PKEY_EC, myecc) != 0)
Ed Tanousb01bf292019-03-25 19:25:26 +0000404 {
Vernon Mauery0185c7f2020-03-09 10:56:53 -0700405 /* pKey owns myecc from now */
Ed Tanousb01bf292019-03-25 19:25:26 +0000406 if (EC_KEY_check_key(myecc) <= 0)
407 {
Ed Tanousc160ae72024-03-27 18:45:20 -0700408 BMCWEB_LOG_ERROR("EC_check_key failed.");
Ed Tanousb01bf292019-03-25 19:25:26 +0000409 }
410 }
411 }
Ed Tanous6ea007a2019-02-13 22:48:25 -0800412 }
Patrick Williamsaec70662021-12-13 16:55:46 -0600413#else
414 // Create context for curve parameter generation.
415 std::unique_ptr<EVP_PKEY_CTX, decltype(&::EVP_PKEY_CTX_free)> ctx{
416 EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), &::EVP_PKEY_CTX_free};
417 if (!ctx)
418 {
419 return nullptr;
420 }
421
422 // Set up curve parameters.
423 EVP_PKEY* params = nullptr;
424 if ((EVP_PKEY_paramgen_init(ctx.get()) <= 0) ||
425 (EVP_PKEY_CTX_set_ec_param_enc(ctx.get(), OPENSSL_EC_NAMED_CURVE) <=
426 0) ||
427 (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), NID_secp384r1) <=
428 0) ||
429 (EVP_PKEY_paramgen(ctx.get(), &params) <= 0))
430 {
431 return nullptr;
432 }
433
434 // Set up RAII holder for params.
435 std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)> pparams{
436 params, &::EVP_PKEY_free};
437
438 // Set new context for key generation, using curve parameters.
439 ctx.reset(EVP_PKEY_CTX_new_from_pkey(nullptr, params, nullptr));
440 if (!ctx || (EVP_PKEY_keygen_init(ctx.get()) <= 0))
441 {
442 return nullptr;
443 }
444
445 // Generate key.
446 if (EVP_PKEY_keygen(ctx.get(), &pKey) <= 0)
447 {
448 return nullptr;
449 }
450#endif
451
Ed Tanous1abe55e2018-09-05 08:30:59 -0700452 return pKey;
453}
454
455void initOpenssl()
456{
Ed Tanous911ac312017-08-15 09:37:42 -0700457#if OPENSSL_VERSION_NUMBER < 0x10100000L
Ed Tanous1abe55e2018-09-05 08:30:59 -0700458 SSL_load_error_strings();
459 OpenSSL_add_all_algorithms();
460 RAND_load_file("/dev/urandom", 1024);
Ed Tanousa1e077c2017-04-25 12:42:19 -0700461#endif
Ed Tanous0fdddb12017-02-28 11:06:34 -0800462}
463
Ed Tanous099225c2024-03-27 22:03:05 -0700464inline std::string ensureOpensslKeyPresentAndValid(const std::string& filepath)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700465{
Ed Tanous099225c2024-03-27 22:03:05 -0700466 std::string cert = verifyOpensslKeyCert(filepath);
Ed Tanous0fdddb12017-02-28 11:06:34 -0800467
Ed Tanous099225c2024-03-27 22:03:05 -0700468 if (cert.empty())
Ed Tanous1abe55e2018-09-05 08:30:59 -0700469 {
Ed Tanousc160ae72024-03-27 18:45:20 -0700470 BMCWEB_LOG_WARNING("Error in verifying signature, regenerating");
Ed Tanous099225c2024-03-27 22:03:05 -0700471 cert = generateSslCertificate("testhost");
472 if (cert.empty())
473 {
474 BMCWEB_LOG_ERROR("Failed to generate cert");
475 }
476 else
477 {
478 writeCertificateToFile(filepath, cert);
479 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700480 }
Ed Tanous099225c2024-03-27 22:03:05 -0700481 return cert;
Ed Tanous0fdddb12017-02-28 11:06:34 -0800482}
Ed Tanousc4771fb2017-03-13 13:39:49 -0700483
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500484inline std::string ensureCertificate()
485{
486 namespace fs = std::filesystem;
487 // Cleanup older certificate file existing in the system
488 fs::path oldcertPath = fs::path("/home/root/server.pem");
489 std::error_code ec;
490 fs::remove(oldcertPath, ec);
491 // Ignore failure to remove; File might not exist.
492
493 fs::path certPath = "/etc/ssl/certs/https/";
494 // if path does not exist create the path so that
495 // self signed certificate can be created in the
496 // path
497 fs::path certFile = certPath / "server.pem";
498
499 if (!fs::exists(certPath, ec))
500 {
Andrew Geissler00a0fe42024-06-11 11:21:30 -0400501 fs::create_directories(certPath, ec);
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500502 }
503 BMCWEB_LOG_INFO("Building SSL Context file= {}", certFile.string());
504 std::string sslPemFile(certFile);
505 return ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile);
506}
507
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800508inline int nextProtoCallback(SSL* /*unused*/, const unsigned char** data,
509 unsigned int* len, void* /*unused*/)
510{
511 // First byte is the length.
512 constexpr std::string_view h2 = "\x02h2";
513 *data = std::bit_cast<const unsigned char*>(h2.data());
514 *len = static_cast<unsigned int>(h2.size());
515 return SSL_TLSEXT_ERR_OK;
516}
517
518inline int alpnSelectProtoCallback(SSL* /*unused*/, const unsigned char** out,
519 unsigned char* outlen,
520 const unsigned char* in, unsigned int inlen,
521 void* /*unused*/)
522{
523 // There's a mismatch in constness for nghttp2_select_next_protocol. The
524 // examples in nghttp2 don't show this problem. Unclear what the right fix
525 // is here.
526
527 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
528 unsigned char** outNew = const_cast<unsigned char**>(out);
529 int rv = nghttp2_select_next_protocol(outNew, outlen, in, inlen);
530 if (rv != 1)
531 {
532 return SSL_TLSEXT_ERR_NOACK;
533 }
534
535 return SSL_TLSEXT_ERR_OK;
536}
537
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500538inline bool getSslContext(boost::asio::ssl::context& mSslContext,
539 const std::string& sslPemFile)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700540{
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500541 mSslContext.set_options(boost::asio::ssl::context::default_workarounds |
542 boost::asio::ssl::context::no_sslv2 |
543 boost::asio::ssl::context::no_sslv3 |
544 boost::asio::ssl::context::single_dh_use |
545 boost::asio::ssl::context::no_tlsv1 |
546 boost::asio::ssl::context::no_tlsv1_1);
James Feist6a3e1822019-11-06 13:46:35 -0800547
Ed Tanous62598e32023-07-17 17:06:25 -0700548 BMCWEB_LOG_DEBUG("Using default TrustStore location: {}", trustStorePath);
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500549 mSslContext.add_verify_path(trustStorePath);
Kowalski, Kamil55e43f62019-07-10 13:12:57 +0200550
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500551 if (!sslPemFile.empty())
Ed Tanous099225c2024-03-27 22:03:05 -0700552 {
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500553 boost::system::error_code ec;
554
555 boost::asio::const_buffer buf(sslPemFile.data(), sslPemFile.size());
556 mSslContext.use_certificate(buf, boost::asio::ssl::context::pem, ec);
557 if (ec)
558 {
559 return false;
560 }
561 mSslContext.use_private_key(buf, boost::asio::ssl::context::pem, ec);
562 if (ec)
563 {
564 BMCWEB_LOG_CRITICAL("Failed to open ssl pkey");
565 return false;
566 }
Ed Tanous099225c2024-03-27 22:03:05 -0700567 }
Ed Tanousc4771fb2017-03-13 13:39:49 -0700568
Ed Tanous1abe55e2018-09-05 08:30:59 -0700569 // Set up EC curves to auto (boost asio doesn't have a method for this)
570 // There is a pull request to add this. Once this is included in an asio
571 // drop, use the right way
572 // http://stackoverflow.com/questions/18929049/boost-asio-with-ecdsa-certificate-issue
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600573 if (SSL_CTX_set_ecdh_auto(mSslContext->native_handle(), 1) != 1)
Ed Tanous62598e32023-07-17 17:06:25 -0700574 {}
Ed Tanousc4771fb2017-03-13 13:39:49 -0700575
Ed Tanouse96d7fb2023-06-14 14:53:29 -0700576 // Mozilla intermediate cipher suites v5.7
577 // Sourced from: https://ssl-config.mozilla.org/guidelines/5.7.json
578 const char* mozillaIntermediate = "ECDHE-ECDSA-AES128-GCM-SHA256:"
579 "ECDHE-RSA-AES128-GCM-SHA256:"
580 "ECDHE-ECDSA-AES256-GCM-SHA384:"
581 "ECDHE-RSA-AES256-GCM-SHA384:"
582 "ECDHE-ECDSA-CHACHA20-POLY1305:"
583 "ECDHE-RSA-CHACHA20-POLY1305:"
584 "DHE-RSA-AES128-GCM-SHA256:"
585 "DHE-RSA-AES256-GCM-SHA384:"
586 "DHE-RSA-CHACHA20-POLY1305";
Ed Tanousc4771fb2017-03-13 13:39:49 -0700587
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500588 if (SSL_CTX_set_cipher_list(mSslContext.native_handle(),
Ed Tanouse96d7fb2023-06-14 14:53:29 -0700589 mozillaIntermediate) != 1)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700590 {
Ed Tanous62598e32023-07-17 17:06:25 -0700591 BMCWEB_LOG_ERROR("Error setting cipher list");
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500592 return false;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700593 }
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500594 return true;
595}
596
597inline std::shared_ptr<boost::asio::ssl::context> getSslServerContext()
598{
599 boost::asio::ssl::context sslCtx(boost::asio::ssl::context::tls_server);
600
601 auto certFile = ensureCertificate();
602 if (!getSslContext(sslCtx, certFile))
603 {
604 BMCWEB_LOG_CRITICAL("Couldn't get server context");
605 return nullptr;
606 }
607
608 // BIG WARNING: This needs to stay disabled, as there will always be
609 // unauthenticated endpoints
610 // mSslContext->set_verify_mode(boost::asio::ssl::verify_peer);
611
612 SSL_CTX_set_options(sslCtx.native_handle(), SSL_OP_NO_RENEGOTIATION);
613
614 if constexpr (BMCWEB_EXPERIMENTAL_HTTP2)
615 {
616 SSL_CTX_set_next_protos_advertised_cb(sslCtx.native_handle(),
617 nextProtoCallback, nullptr);
618
619 SSL_CTX_set_alpn_select_cb(sslCtx.native_handle(),
620 alpnSelectProtoCallback, nullptr);
621 }
622
623 return std::make_shared<boost::asio::ssl::context>(std::move(sslCtx));
Ed Tanousc4771fb2017-03-13 13:39:49 -0700624}
AppaRao Pulie38778a2022-06-27 23:09:03 +0000625
626inline std::optional<boost::asio::ssl::context> getSSLClientContext()
627{
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500628 namespace fs = std::filesystem;
629
AppaRao Pulie38778a2022-06-27 23:09:03 +0000630 boost::asio::ssl::context sslCtx(boost::asio::ssl::context::tls_client);
631
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500632 // NOTE, this path is temporary; In the future it will need to change to
633 // be set per subscription. Do not rely on this.
634 fs::path certPath = "/etc/ssl/certs/https/client.pem";
635 std::string cert = verifyOpensslKeyCert(certPath);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000636
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500637 if (!getSslContext(sslCtx, cert))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000638 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000639 return std::nullopt;
640 }
641
642 // Add a directory containing certificate authority files to be used
643 // for performing verification.
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500644 boost::system::error_code ec;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000645 sslCtx.set_default_verify_paths(ec);
646 if (ec)
647 {
Ed Tanous62598e32023-07-17 17:06:25 -0700648 BMCWEB_LOG_ERROR("SSL context set_default_verify failed");
AppaRao Pulie38778a2022-06-27 23:09:03 +0000649 return std::nullopt;
650 }
651
652 // Verify the remote server's certificate
653 sslCtx.set_verify_mode(boost::asio::ssl::verify_peer, ec);
654 if (ec)
655 {
Ed Tanous62598e32023-07-17 17:06:25 -0700656 BMCWEB_LOG_ERROR("SSL context set_verify_mode failed");
AppaRao Pulie38778a2022-06-27 23:09:03 +0000657 return std::nullopt;
658 }
659
660 // All cipher suites are set as per OWASP datasheet.
661 // https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html
662 constexpr const char* sslCiphers = "ECDHE-ECDSA-AES128-GCM-SHA256:"
663 "ECDHE-RSA-AES128-GCM-SHA256:"
664 "ECDHE-ECDSA-AES256-GCM-SHA384:"
665 "ECDHE-RSA-AES256-GCM-SHA384:"
666 "ECDHE-ECDSA-CHACHA20-POLY1305:"
667 "ECDHE-RSA-CHACHA20-POLY1305:"
668 "DHE-RSA-AES128-GCM-SHA256:"
669 "DHE-RSA-AES256-GCM-SHA384"
670 "TLS_AES_128_GCM_SHA256:"
671 "TLS_AES_256_GCM_SHA384:"
672 "TLS_CHACHA20_POLY1305_SHA256";
673
674 if (SSL_CTX_set_cipher_list(sslCtx.native_handle(), sslCiphers) != 1)
675 {
Ed Tanous62598e32023-07-17 17:06:25 -0700676 BMCWEB_LOG_ERROR("SSL_CTX_set_cipher_list failed");
AppaRao Pulie38778a2022-06-27 23:09:03 +0000677 return std::nullopt;
678 }
679
Abhilash Rajud5fb5842024-06-03 11:40:17 -0500680 return {std::move(sslCtx)};
AppaRao Pulie38778a2022-06-27 23:09:03 +0000681}
682
Ed Tanous1abe55e2018-09-05 08:30:59 -0700683} // namespace ensuressl