xref: /openbmc/bmcweb/include/ssl_key_handler.hpp (revision cc5a37f7362e8aabb8f21705c8583d2ff19a641d)
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(), 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