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