1 #pragma once 2 #ifdef BMCWEB_ENABLE_SSL 3 4 #include <openssl/bio.h> 5 #include <openssl/dh.h> 6 #include <openssl/dsa.h> 7 #include <openssl/err.h> 8 #include <openssl/evp.h> 9 #include <openssl/pem.h> 10 #include <openssl/rand.h> 11 #include <openssl/rsa.h> 12 #include <openssl/ssl.h> 13 14 #include <boost/asio.hpp> 15 #include <random> 16 17 namespace ensuressl 18 { 19 static void initOpenssl(); 20 static void cleanupOpenssl(); 21 static EVP_PKEY *createRsaKey(); 22 static EVP_PKEY *createEcKey(); 23 static void handleOpensslError(); 24 25 inline bool verifyOpensslKeyCert(const std::string &filepath) 26 { 27 bool privateKeyValid = false; 28 bool certValid = false; 29 30 std::cout << "Checking certs in file " << filepath << "\n"; 31 32 FILE *file = fopen(filepath.c_str(), "r"); 33 if (file != NULL) 34 { 35 EVP_PKEY *pkey = PEM_read_PrivateKey(file, NULL, NULL, NULL); 36 int rc; 37 if (pkey != nullptr) 38 { 39 RSA *rsa = EVP_PKEY_get1_RSA(pkey); 40 if (rsa != nullptr) 41 { 42 std::cout << "Found an RSA key\n"; 43 if (RSA_check_key(rsa) == 1) 44 { 45 // private_key_valid = true; 46 } 47 else 48 { 49 std::cerr << "Key not valid error number " 50 << ERR_get_error() << "\n"; 51 } 52 RSA_free(rsa); 53 } 54 else 55 { 56 EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey); 57 if (ec != nullptr) 58 { 59 std::cout << "Found an EC key\n"; 60 if (EC_KEY_check_key(ec) == 1) 61 { 62 privateKeyValid = true; 63 } 64 else 65 { 66 std::cerr << "Key not valid error number " 67 << ERR_get_error() << "\n"; 68 } 69 EC_KEY_free(ec); 70 } 71 } 72 73 if (privateKeyValid) 74 { 75 X509 *x509 = PEM_read_X509(file, NULL, NULL, NULL); 76 if (x509 == nullptr) 77 { 78 std::cout << "error getting x509 cert " << ERR_get_error() 79 << "\n"; 80 } 81 else 82 { 83 rc = X509_verify(x509, pkey); 84 if (rc == 1) 85 { 86 certValid = true; 87 } 88 else 89 { 90 std::cerr << "Error in verifying private key signature " 91 << ERR_get_error() << "\n"; 92 } 93 } 94 } 95 96 EVP_PKEY_free(pkey); 97 } 98 fclose(file); 99 } 100 return certValid; 101 } 102 103 inline void generateSslCertificate(const std::string &filepath) 104 { 105 FILE *pFile = NULL; 106 std::cout << "Generating new keys\n"; 107 initOpenssl(); 108 109 // std::cerr << "Generating RSA key"; 110 // EVP_PKEY *pRsaPrivKey = create_rsa_key(); 111 112 std::cerr << "Generating EC key\n"; 113 EVP_PKEY *pRsaPrivKey = createEcKey(); 114 if (pRsaPrivKey != nullptr) 115 { 116 std::cerr << "Generating x509 Certificate\n"; 117 // Use this code to directly generate a certificate 118 X509 *x509; 119 x509 = X509_new(); 120 if (x509 != nullptr) 121 { 122 // get a random number from the RNG for the certificate serial 123 // number If this is not random, regenerating certs throws broswer 124 // errors 125 std::random_device rd; 126 int serial = rd(); 127 128 ASN1_INTEGER_set(X509_get_serialNumber(x509), serial); 129 130 // not before this moment 131 X509_gmtime_adj(X509_get_notBefore(x509), 0); 132 // Cert is valid for 10 years 133 X509_gmtime_adj(X509_get_notAfter(x509), 134 60L * 60L * 24L * 365L * 10L); 135 136 // set the public key to the key we just generated 137 X509_set_pubkey(x509, pRsaPrivKey); 138 139 // get the subject name 140 X509_NAME *name; 141 name = X509_get_subject_name(x509); 142 143 X509_NAME_add_entry_by_txt( 144 name, "C", MBSTRING_ASC, 145 reinterpret_cast<const unsigned char *>("US"), -1, -1, 0); 146 X509_NAME_add_entry_by_txt( 147 name, "O", MBSTRING_ASC, 148 reinterpret_cast<const unsigned char *>("Intel BMC"), -1, -1, 149 0); 150 X509_NAME_add_entry_by_txt( 151 name, "CN", MBSTRING_ASC, 152 reinterpret_cast<const unsigned char *>("testhost"), -1, -1, 0); 153 // set the CSR options 154 X509_set_issuer_name(x509, name); 155 156 // Sign the certificate with our private key 157 X509_sign(x509, pRsaPrivKey, EVP_sha256()); 158 159 pFile = fopen(filepath.c_str(), "wt"); 160 161 if (pFile != nullptr) 162 { 163 PEM_write_PrivateKey(pFile, pRsaPrivKey, NULL, NULL, 0, 0, 164 NULL); 165 166 PEM_write_X509(pFile, x509); 167 fclose(pFile); 168 pFile = NULL; 169 } 170 171 X509_free(x509); 172 } 173 174 EVP_PKEY_free(pRsaPrivKey); 175 pRsaPrivKey = NULL; 176 } 177 178 // cleanup_openssl(); 179 } 180 181 EVP_PKEY *createRsaKey() 182 { 183 RSA *pRSA = NULL; 184 #if OPENSSL_VERSION_NUMBER < 0x00908000L 185 pRSA = RSA_generate_key(2048, RSA_3, NULL, NULL); 186 #else 187 RSA_generate_key_ex(pRSA, 2048, NULL, NULL); 188 #endif 189 190 EVP_PKEY *pKey = EVP_PKEY_new(); 191 if ((pRSA != nullptr) && (pKey != nullptr) && 192 EVP_PKEY_assign_RSA(pKey, pRSA)) 193 { 194 /* pKey owns pRSA from now */ 195 if (RSA_check_key(pRSA) <= 0) 196 { 197 fprintf(stderr, "RSA_check_key failed.\n"); 198 handleOpensslError(); 199 EVP_PKEY_free(pKey); 200 pKey = NULL; 201 } 202 } 203 else 204 { 205 handleOpensslError(); 206 if (pRSA != nullptr) 207 { 208 RSA_free(pRSA); 209 pRSA = NULL; 210 } 211 if (pKey != nullptr) 212 { 213 EVP_PKEY_free(pKey); 214 pKey = NULL; 215 } 216 } 217 return pKey; 218 } 219 220 EVP_PKEY *createEcKey() 221 { 222 EVP_PKEY *pKey = NULL; 223 int eccgrp = 0; 224 eccgrp = OBJ_txt2nid("prime256v1"); 225 226 EC_KEY *myecc = EC_KEY_new_by_curve_name(eccgrp); 227 if (myecc != nullptr) 228 { 229 EC_KEY_set_asn1_flag(myecc, OPENSSL_EC_NAMED_CURVE); 230 EC_KEY_generate_key(myecc); 231 pKey = EVP_PKEY_new(); 232 if (pKey != nullptr) 233 { 234 if (EVP_PKEY_assign_EC_KEY(pKey, myecc)) 235 { 236 /* pKey owns pRSA from now */ 237 if (EC_KEY_check_key(myecc) <= 0) 238 { 239 fprintf(stderr, "EC_check_key failed.\n"); 240 } 241 } 242 } 243 } 244 return pKey; 245 } 246 247 void initOpenssl() 248 { 249 #if OPENSSL_VERSION_NUMBER < 0x10100000L 250 SSL_load_error_strings(); 251 OpenSSL_add_all_algorithms(); 252 RAND_load_file("/dev/urandom", 1024); 253 #endif 254 } 255 256 void cleanupOpenssl() 257 { 258 CRYPTO_cleanup_all_ex_data(); 259 ERR_free_strings(); 260 #if OPENSSL_VERSION_NUMBER < 0x10100000L 261 ERR_remove_thread_state(0); 262 #endif 263 EVP_cleanup(); 264 } 265 266 void handleOpensslError() 267 { 268 ERR_print_errors_fp(stderr); 269 } 270 inline void ensureOpensslKeyPresentAndValid(const std::string &filepath) 271 { 272 bool pemFileValid = false; 273 274 pemFileValid = verifyOpensslKeyCert(filepath); 275 276 if (!pemFileValid) 277 { 278 std::cerr << "Error in verifying signature, regenerating\n"; 279 generateSslCertificate(filepath); 280 } 281 } 282 283 inline boost::asio::ssl::context getSslContext(const std::string &ssl_pem_file) 284 { 285 boost::asio::ssl::context mSslContext{boost::asio::ssl::context::sslv23}; 286 mSslContext.set_options(boost::asio::ssl::context::default_workarounds | 287 boost::asio::ssl::context::no_sslv2 | 288 boost::asio::ssl::context::no_sslv3 | 289 boost::asio::ssl::context::single_dh_use | 290 boost::asio::ssl::context::no_tlsv1 | 291 boost::asio::ssl::context::no_tlsv1_1); 292 293 // m_ssl_context.set_verify_mode(boost::asio::ssl::verify_peer); 294 mSslContext.use_certificate_file(ssl_pem_file, 295 boost::asio::ssl::context::pem); 296 mSslContext.use_private_key_file(ssl_pem_file, 297 boost::asio::ssl::context::pem); 298 299 // Set up EC curves to auto (boost asio doesn't have a method for this) 300 // There is a pull request to add this. Once this is included in an asio 301 // drop, use the right way 302 // http://stackoverflow.com/questions/18929049/boost-asio-with-ecdsa-certificate-issue 303 if (SSL_CTX_set_ecdh_auto(mSslContext.native_handle(), 1) != 1) 304 { 305 BMCWEB_LOG_ERROR << "Error setting tmp ecdh list\n"; 306 } 307 308 // From mozilla "compatibility" 309 std::string mozillaCompatibilityCiphers = "ECDHE-ECDSA-CHACHA20-POLY1305:" 310 "ECDHE-RSA-CHACHA20-POLY1305:" 311 "ECDHE-ECDSA-AES128-GCM-SHA256:" 312 "ECDHE-RSA-AES128-GCM-SHA256:" 313 "ECDHE-ECDSA-AES256-GCM-SHA384:" 314 "ECDHE-RSA-AES256-GCM-SHA384:" 315 "DHE-RSA-AES128-GCM-SHA256:" 316 "DHE-RSA-AES256-GCM-SHA384:" 317 "ECDHE-ECDSA-AES128-SHA256:" 318 "ECDHE-RSA-AES128-SHA256:" 319 "ECDHE-ECDSA-AES128-SHA:" 320 "ECDHE-RSA-AES256-SHA384:" 321 "ECDHE-RSA-AES128-SHA:" 322 "ECDHE-ECDSA-AES256-SHA384:" 323 "ECDHE-ECDSA-AES256-SHA:" 324 "ECDHE-RSA-AES256-SHA:" 325 "DHE-RSA-AES128-SHA256:" 326 "DHE-RSA-AES128-SHA:" 327 "DHE-RSA-AES256-SHA256:" 328 "DHE-RSA-AES256-SHA:" 329 "ECDHE-ECDSA-DES-CBC3-SHA:" 330 "ECDHE-RSA-DES-CBC3-SHA:" 331 "EDH-RSA-DES-CBC3-SHA:" 332 "AES128-GCM-SHA256:" 333 "AES256-GCM-SHA384:" 334 "AES128-SHA256:" 335 "AES256-SHA256:" 336 "AES128-SHA:" 337 "AES256-SHA:" 338 "DES-CBC3-SHA:" 339 "!DSS"; 340 341 // From mozilla "modern" 342 std::string mozillaModernCiphers = "ECDHE-ECDSA-AES256-GCM-SHA384:" 343 "ECDHE-RSA-AES256-GCM-SHA384:" 344 "ECDHE-ECDSA-CHACHA20-POLY1305:" 345 "ECDHE-RSA-CHACHA20-POLY1305:" 346 "ECDHE-ECDSA-AES128-GCM-SHA256:" 347 "ECDHE-RSA-AES128-GCM-SHA256:" 348 "ECDHE-ECDSA-AES256-SHA384:" 349 "ECDHE-RSA-AES256-SHA384:" 350 "ECDHE-ECDSA-AES128-SHA256:" 351 "ECDHE-RSA-AES128-SHA256"; 352 353 std::string aesOnlyCiphers = "AES128+EECDH:AES128+EDH:!aNULL:!eNULL"; 354 355 if (SSL_CTX_set_cipher_list(mSslContext.native_handle(), 356 mozillaCompatibilityCiphers.c_str()) != 1) 357 { 358 BMCWEB_LOG_ERROR << "Error setting cipher list\n"; 359 } 360 return mSslContext; 361 } 362 } // namespace ensuressl 363 364 #endif