]> granicus.if.org Git - icinga2/blob - lib/base/tlsutility.cpp
04d8ea49f1fae9aba1988784a9ce893cf0ff777d
[icinga2] / lib / base / tlsutility.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/)  *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program is distributed in the hope that it will be useful,            *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "base/tlsutility.hpp"
21 #include "base/convert.hpp"
22 #include "base/logger.hpp"
23 #include "base/context.hpp"
24 #include "base/utility.hpp"
25 #include "base/application.hpp"
26 #include "base/exception.hpp"
27 #include <fstream>
28
29 namespace icinga
30 {
31
32 static bool l_SSLInitialized = false;
33 static boost::mutex *l_Mutexes;
34
35 #ifdef CRYPTO_LOCK
36 static void OpenSSLLockingCallback(int mode, int type, const char *, int)
37 {
38         if (mode & CRYPTO_LOCK)
39                 l_Mutexes[type].lock();
40         else
41                 l_Mutexes[type].unlock();
42 }
43
44 static unsigned long OpenSSLIDCallback(void)
45 {
46 #ifdef _WIN32
47         return (unsigned long)GetCurrentThreadId();
48 #else /* _WIN32 */
49         return (unsigned long)pthread_self();
50 #endif /* _WIN32 */
51 }
52 #endif /* CRYPTO_LOCK */
53
54 /**
55  * Initializes the OpenSSL library.
56  */
57 void InitializeOpenSSL(void)
58 {
59         if (l_SSLInitialized)
60                 return;
61
62         SSL_library_init();
63         SSL_load_error_strings();
64
65         SSL_COMP_get_compression_methods();
66
67 #ifdef CRYPTO_LOCK
68         l_Mutexes = new boost::mutex[CRYPTO_num_locks()];
69         CRYPTO_set_locking_callback(&OpenSSLLockingCallback);
70         CRYPTO_set_id_callback(&OpenSSLIDCallback);
71 #endif /* CRYPTO_LOCK */
72
73         l_SSLInitialized = true;
74 }
75
76 /**
77  * Initializes an SSL context using the specified certificates.
78  *
79  * @param pubkey The public key.
80  * @param privkey The matching private key.
81  * @param cakey CA certificate chain file.
82  * @returns An SSL context.
83  */
84 boost::shared_ptr<SSL_CTX> MakeSSLContext(const String& pubkey, const String& privkey, const String& cakey)
85 {
86         char errbuf[120];
87
88         InitializeOpenSSL();
89
90         boost::shared_ptr<SSL_CTX> sslContext = boost::shared_ptr<SSL_CTX>(SSL_CTX_new(SSLv23_method()), SSL_CTX_free);
91
92         long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_CIPHER_SERVER_PREFERENCE;
93
94 #ifdef SSL_OP_NO_COMPRESSION
95         flags |= SSL_OP_NO_COMPRESSION;
96 #endif /* SSL_OP_NO_COMPRESSION */
97
98         SSL_CTX_set_options(sslContext.get(), flags);
99
100         SSL_CTX_set_mode(sslContext.get(), SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
101         SSL_CTX_set_session_id_context(sslContext.get(), (const unsigned char *)"Icinga 2", 8);
102
103         if (!pubkey.IsEmpty()) {
104                 if (!SSL_CTX_use_certificate_chain_file(sslContext.get(), pubkey.CStr())) {
105                         Log(LogCritical, "SSL")
106                             << "Error with public key file '" << pubkey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
107                         BOOST_THROW_EXCEPTION(openssl_error()
108                             << boost::errinfo_api_function("SSL_CTX_use_certificate_chain_file")
109                             << errinfo_openssl_error(ERR_peek_error())
110                             << boost::errinfo_file_name(pubkey));
111                 }
112         }
113
114         if (!privkey.IsEmpty()) {
115                 if (!SSL_CTX_use_PrivateKey_file(sslContext.get(), privkey.CStr(), SSL_FILETYPE_PEM)) {
116                         Log(LogCritical, "SSL")
117                             << "Error with private key file '" << privkey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
118                         BOOST_THROW_EXCEPTION(openssl_error()
119                             << boost::errinfo_api_function("SSL_CTX_use_PrivateKey_file")
120                             << errinfo_openssl_error(ERR_peek_error())
121                             << boost::errinfo_file_name(privkey));
122                 }
123
124                 if (!SSL_CTX_check_private_key(sslContext.get())) {
125                         Log(LogCritical, "SSL")
126                             << "Error checking private key '" << privkey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
127                         BOOST_THROW_EXCEPTION(openssl_error()
128                             << boost::errinfo_api_function("SSL_CTX_check_private_key")
129                             << errinfo_openssl_error(ERR_peek_error()));
130                 }
131         }
132
133         if (!cakey.IsEmpty()) {
134                 if (!SSL_CTX_load_verify_locations(sslContext.get(), cakey.CStr(), NULL)) {
135                         Log(LogCritical, "SSL")
136                             << "Error loading and verifying locations in ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
137                         BOOST_THROW_EXCEPTION(openssl_error()
138                             << boost::errinfo_api_function("SSL_CTX_load_verify_locations")
139                             << errinfo_openssl_error(ERR_peek_error())
140                             << boost::errinfo_file_name(cakey));
141                 }
142
143                 STACK_OF(X509_NAME) *cert_names;
144
145                 cert_names = SSL_load_client_CA_file(cakey.CStr());
146                 if (cert_names == NULL) {
147                         Log(LogCritical, "SSL")
148                             << "Error loading client ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
149                         BOOST_THROW_EXCEPTION(openssl_error()
150                             << boost::errinfo_api_function("SSL_load_client_CA_file")
151                             << errinfo_openssl_error(ERR_peek_error())
152                             << boost::errinfo_file_name(cakey));
153                 }
154
155                 SSL_CTX_set_client_CA_list(sslContext.get(), cert_names);
156         }
157
158         return sslContext;
159 }
160
161 /**
162  * Set the cipher list to the specified SSL context.
163  * @param context The ssl context.
164  * @param cipherList The ciper list.
165  **/
166 void SetCipherListToSSLContext(const boost::shared_ptr<SSL_CTX>& context, const String& cipherList)
167 {
168         char errbuf[256];
169
170         if (SSL_CTX_set_cipher_list(context.get(), cipherList.CStr()) == 0) {
171                 Log(LogCritical, "SSL")
172                     << "Cipher list '"
173                     << cipherList
174                     << "' does not specify any usable ciphers: "
175                     << ERR_peek_error() << ", \""
176                     << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
177
178                 BOOST_THROW_EXCEPTION(openssl_error()
179                     << boost::errinfo_api_function("SSL_CTX_set_cipher_list")
180                     << errinfo_openssl_error(ERR_peek_error()));
181         }
182 }
183
184 /**
185  * Set the minimum TLS protocol version to the specified SSL context.
186  *
187  * @param context The ssl context.
188  * @param tlsProtocolmin The minimum TLS protocol version.
189  */
190 void SetTlsProtocolminToSSLContext(const boost::shared_ptr<SSL_CTX>& context, const String& tlsProtocolmin)
191 {
192         long flags = SSL_CTX_get_options(context.get());
193
194         flags |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
195
196 #ifdef SSL_TXT_TLSV1_1
197         if (tlsProtocolmin == SSL_TXT_TLSV1_1)
198                 flags |= SSL_OP_NO_TLSv1;
199         else
200 #endif /* SSL_TXT_TLSV1_1 */
201 #ifdef SSL_TXT_TLSV1_2
202         if (tlsProtocolmin == SSL_TXT_TLSV1_2)
203                 flags |= SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
204         else
205 #endif /* SSL_TXT_TLSV1_2 */
206         if (tlsProtocolmin != SSL_TXT_TLSV1)
207                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid TLS protocol version specified."));
208
209         SSL_CTX_set_options(context.get(), flags);
210 }
211
212 /**
213  * Loads a CRL and appends its certificates to the specified SSL context.
214  *
215  * @param context The SSL context.
216  * @param crlPath The path to the CRL file.
217  */
218 void AddCRLToSSLContext(const boost::shared_ptr<SSL_CTX>& context, const String& crlPath)
219 {
220         char errbuf[120];
221         X509_STORE *x509_store = SSL_CTX_get_cert_store(context.get());
222
223         X509_LOOKUP *lookup;
224         lookup = X509_STORE_add_lookup(x509_store, X509_LOOKUP_file());
225
226         if (!lookup) {
227                 Log(LogCritical, "SSL")
228                     << "Error adding X509 store lookup: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
229                 BOOST_THROW_EXCEPTION(openssl_error()
230                     << boost::errinfo_api_function("X509_STORE_add_lookup")
231                     << errinfo_openssl_error(ERR_peek_error()));
232         }
233
234         if (X509_LOOKUP_load_file(lookup, crlPath.CStr(), X509_FILETYPE_PEM) != 1) {
235                 Log(LogCritical, "SSL")
236                     << "Error loading crl file '" << crlPath << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
237                 BOOST_THROW_EXCEPTION(openssl_error()
238                     << boost::errinfo_api_function("X509_LOOKUP_load_file")
239                     << errinfo_openssl_error(ERR_peek_error())
240                     << boost::errinfo_file_name(crlPath));
241         }
242
243         X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
244         X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
245         X509_STORE_set1_param(x509_store, param);
246         X509_VERIFY_PARAM_free(param);
247 }
248
249 static String GetX509NameCN(X509_NAME *name)
250 {
251         char errbuf[120];
252         char buffer[256];
253
254         int rc = X509_NAME_get_text_by_NID(name, NID_commonName, buffer, sizeof(buffer));
255
256         if (rc == -1) {
257                 Log(LogCritical, "SSL")
258                     << "Error with x509 NAME getting text by NID: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
259                 BOOST_THROW_EXCEPTION(openssl_error()
260                     << boost::errinfo_api_function("X509_NAME_get_text_by_NID")
261                     << errinfo_openssl_error(ERR_peek_error()));
262         }
263
264         return buffer;
265 }
266
267 /**
268  * Retrieves the common name for an X509 certificate.
269  *
270  * @param certificate The X509 certificate.
271  * @returns The common name.
272  */
273 String GetCertificateCN(const boost::shared_ptr<X509>& certificate)
274 {
275         return GetX509NameCN(X509_get_subject_name(certificate.get()));
276 }
277
278 /**
279  * Retrieves an X509 certificate from the specified file.
280  *
281  * @param pemfile The filename.
282  * @returns An X509 certificate.
283  */
284 boost::shared_ptr<X509> GetX509Certificate(const String& pemfile)
285 {
286         char errbuf[120];
287         X509 *cert;
288         BIO *fpcert = BIO_new(BIO_s_file());
289
290         if (fpcert == NULL) {
291                 Log(LogCritical, "SSL")
292                     << "Error creating new BIO: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
293                 BOOST_THROW_EXCEPTION(openssl_error()
294                     << boost::errinfo_api_function("BIO_new")
295                     << errinfo_openssl_error(ERR_peek_error()));
296         }
297
298         if (BIO_read_filename(fpcert, pemfile.CStr()) < 0) {
299                 Log(LogCritical, "SSL")
300                     << "Error reading pem file '" << pemfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
301                 BOOST_THROW_EXCEPTION(openssl_error()
302                     << boost::errinfo_api_function("BIO_read_filename")
303                     << errinfo_openssl_error(ERR_peek_error())
304                     << boost::errinfo_file_name(pemfile));
305         }
306
307         cert = PEM_read_bio_X509_AUX(fpcert, NULL, NULL, NULL);
308         if (cert == NULL) {
309                 Log(LogCritical, "SSL")
310                     << "Error on bio X509 AUX reading pem file '" << pemfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
311                 BOOST_THROW_EXCEPTION(openssl_error()
312                     << boost::errinfo_api_function("PEM_read_bio_X509_AUX")
313                     << errinfo_openssl_error(ERR_peek_error())
314                     << boost::errinfo_file_name(pemfile));
315         }
316
317         BIO_free(fpcert);
318
319         return boost::shared_ptr<X509>(cert, X509_free);
320 }
321
322 int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile, const String& certfile, bool ca)
323 {
324         char errbuf[120];
325
326         InitializeOpenSSL();
327
328         RSA *rsa = RSA_generate_key(4096, RSA_F4, NULL, NULL);
329
330         Log(LogInformation, "base")
331             << "Writing private key to '" << keyfile << "'.";
332
333         BIO *bio = BIO_new_file(const_cast<char *>(keyfile.CStr()), "w");
334
335         if (!bio) {
336                 Log(LogCritical, "SSL")
337                     << "Error while opening private RSA key file '" << keyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
338                 BOOST_THROW_EXCEPTION(openssl_error()
339                     << boost::errinfo_api_function("BIO_new_file")
340                     << errinfo_openssl_error(ERR_peek_error())
341                     << boost::errinfo_file_name(keyfile));
342         }
343
344         if (!PEM_write_bio_RSAPrivateKey(bio, rsa, NULL, NULL, 0, NULL, NULL)) {
345                 Log(LogCritical, "SSL")
346                     << "Error while writing private RSA key to file '" << keyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
347                 BOOST_THROW_EXCEPTION(openssl_error()
348                     << boost::errinfo_api_function("PEM_write_bio_RSAPrivateKey")
349                     << errinfo_openssl_error(ERR_peek_error())
350                     << boost::errinfo_file_name(keyfile));
351         }
352
353         BIO_free(bio);
354
355 #ifndef _WIN32
356         chmod(keyfile.CStr(), 0600);
357 #endif /* _WIN32 */
358         
359         EVP_PKEY *key = EVP_PKEY_new();
360         EVP_PKEY_assign_RSA(key, rsa);
361         
362         if (!certfile.IsEmpty()) {
363                 X509_NAME *subject = X509_NAME_new();
364                 X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC, (unsigned char *)cn.CStr(), -1, -1, 0);
365
366                 boost::shared_ptr<X509> cert = CreateCert(key, subject, subject, key, ca);
367
368                 X509_NAME_free(subject);
369
370                 Log(LogInformation, "base")
371                     << "Writing X509 certificate to '" << certfile << "'.";
372
373                 bio = BIO_new_file(const_cast<char *>(certfile.CStr()), "w");
374
375                 if (!bio) {
376                         Log(LogCritical, "SSL")
377                             << "Error while opening certificate file '" << certfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
378                         BOOST_THROW_EXCEPTION(openssl_error()
379                             << boost::errinfo_api_function("BIO_new_file")
380                             << errinfo_openssl_error(ERR_peek_error())
381                             << boost::errinfo_file_name(certfile));
382                 }
383
384                 if (!PEM_write_bio_X509(bio, cert.get())) {
385                         Log(LogCritical, "SSL")
386                             << "Error while writing certificate to file '" << certfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
387                         BOOST_THROW_EXCEPTION(openssl_error()
388                             << boost::errinfo_api_function("PEM_write_bio_X509")
389                             << errinfo_openssl_error(ERR_peek_error())
390                             << boost::errinfo_file_name(certfile));
391                 }
392
393                 BIO_free(bio);
394         }
395
396         if (!csrfile.IsEmpty()) {
397                 X509_REQ *req = X509_REQ_new();
398
399                 if (!req)
400                         return 0;
401
402                 X509_REQ_set_version(req, 0);
403                 X509_REQ_set_pubkey(req, key);
404         
405                 X509_NAME *name = X509_REQ_get_subject_name(req);
406                 X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)cn.CStr(), -1, -1, 0);
407         
408                 if (!ca) {
409                         String san = "DNS:" + cn;
410                         X509_EXTENSION *subjectAltNameExt = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, const_cast<char *>(san.CStr()));
411                         if (subjectAltNameExt) {
412                                 /* OpenSSL 0.9.8 requires STACK_OF(X509_EXTENSION), otherwise we would just use stack_st_X509_EXTENSION. */
413                                 STACK_OF(X509_EXTENSION) *exts = sk_X509_EXTENSION_new_null();
414                                 sk_X509_EXTENSION_push(exts, subjectAltNameExt);
415                                 X509_REQ_add_extensions(req, exts);
416                                 sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
417                         }
418                 }
419
420                 X509_REQ_sign(req, key, EVP_sha256());
421         
422                 Log(LogInformation, "base")
423                     << "Writing certificate signing request to '" << csrfile << "'.";
424         
425                 bio = BIO_new_file(const_cast<char *>(csrfile.CStr()), "w");
426
427                 if (!bio) {
428                         Log(LogCritical, "SSL")
429                             << "Error while opening CSR file '" << csrfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
430                         BOOST_THROW_EXCEPTION(openssl_error()
431                             << boost::errinfo_api_function("BIO_new_file")
432                             << errinfo_openssl_error(ERR_peek_error())
433                             << boost::errinfo_file_name(csrfile));
434                 }
435
436                 if (!PEM_write_bio_X509_REQ(bio, req)) {
437                         Log(LogCritical, "SSL")
438                             << "Error while writing CSR to file '" << csrfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
439                         BOOST_THROW_EXCEPTION(openssl_error()
440                             << boost::errinfo_api_function("PEM_write_bio_X509")
441                             << errinfo_openssl_error(ERR_peek_error())
442                             << boost::errinfo_file_name(csrfile));
443                 }
444
445                 BIO_free(bio);
446         
447                 X509_REQ_free(req);
448         }
449
450         EVP_PKEY_free(key);
451
452         return 1;
453 }
454
455 boost::shared_ptr<X509> CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca)
456 {
457         X509 *cert = X509_new();
458         X509_set_version(cert, 2);
459         X509_gmtime_adj(X509_get_notBefore(cert), 0);
460         X509_gmtime_adj(X509_get_notAfter(cert), 365 * 24 * 60 * 60 * 15);
461         X509_set_pubkey(cert, pubkey);
462
463         X509_set_subject_name(cert, subject);
464         X509_set_issuer_name(cert, issuer);
465
466         String id = Utility::NewUniqueID();
467
468         char errbuf[120];
469         SHA_CTX context;
470         unsigned char digest[SHA_DIGEST_LENGTH];
471
472         if (!SHA1_Init(&context)) {
473                 Log(LogCritical, "SSL")
474                     << "Error on SHA1 Init: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
475                 BOOST_THROW_EXCEPTION(openssl_error()
476                     << boost::errinfo_api_function("SHA1_Init")
477                     << errinfo_openssl_error(ERR_peek_error()));
478         }
479
480         if (!SHA1_Update(&context, (unsigned char*)id.CStr(), id.GetLength())) {
481                 Log(LogCritical, "SSL")
482                     << "Error on SHA1 Update: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
483                 BOOST_THROW_EXCEPTION(openssl_error()
484                     << boost::errinfo_api_function("SHA1_Update")
485                     << errinfo_openssl_error(ERR_peek_error()));
486         }
487
488         if (!SHA1_Final(digest, &context)) {
489                 Log(LogCritical, "SSL")
490                     << "Error on SHA1 Final: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
491                 BOOST_THROW_EXCEPTION(openssl_error()
492                     << boost::errinfo_api_function("SHA1_Final")
493                     << errinfo_openssl_error(ERR_peek_error()));
494         }
495
496         BIGNUM *bn = BN_new();
497         BN_bin2bn(digest, sizeof(digest), bn);
498         BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(cert));
499         BN_free(bn);
500
501         X509V3_CTX ctx;
502         X509V3_set_ctx_nodb(&ctx);
503         X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
504
505         const char *attr;
506
507         if (ca)
508                 attr = "critical,CA:TRUE";
509         else
510                 attr = "critical,CA:FALSE";
511
512         X509_EXTENSION *basicConstraintsExt = X509V3_EXT_conf_nid(NULL, &ctx, NID_basic_constraints, const_cast<char *>(attr));
513
514         if (basicConstraintsExt) {
515                 X509_add_ext(cert, basicConstraintsExt, -1);
516                 X509_EXTENSION_free(basicConstraintsExt);
517         }
518
519         String cn = GetX509NameCN(subject);
520
521         if (!ca) {
522                 String san = "DNS:" + cn;
523                 X509_EXTENSION *subjectAltNameExt = X509V3_EXT_conf_nid(NULL, &ctx, NID_subject_alt_name, const_cast<char *>(san.CStr()));
524                 if (subjectAltNameExt) {
525                         X509_add_ext(cert, subjectAltNameExt, -1);
526                         X509_EXTENSION_free(subjectAltNameExt);
527                 }
528         }
529
530         X509_sign(cert, cakey, EVP_sha256());
531
532         return boost::shared_ptr<X509>(cert, X509_free);
533 }
534
535 String GetIcingaCADir(void)
536 {
537         return Application::GetLocalStateDir() + "/lib/icinga2/ca";
538 }
539
540 boost::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject)
541 {
542         char errbuf[120];
543
544         String cadir = GetIcingaCADir();
545
546         String cakeyfile = cadir + "/ca.key";
547
548         RSA *rsa;
549
550         BIO *cakeybio = BIO_new_file(const_cast<char *>(cakeyfile.CStr()), "r");
551
552         if (!cakeybio) {
553                 Log(LogCritical, "SSL")
554                     << "Could not open CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
555                 return boost::shared_ptr<X509>();
556         }
557
558         rsa = PEM_read_bio_RSAPrivateKey(cakeybio, NULL, NULL, NULL);
559
560         if (!rsa) {
561                 Log(LogCritical, "SSL")
562                     << "Could not read RSA key from CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
563                 return boost::shared_ptr<X509>();
564         }
565
566         BIO_free(cakeybio);
567
568         String cacertfile = cadir + "/ca.crt";
569
570         boost::shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
571
572         EVP_PKEY *privkey = EVP_PKEY_new();
573         EVP_PKEY_assign_RSA(privkey, rsa);
574
575         return CreateCert(pubkey, subject, X509_get_subject_name(cacert.get()), privkey, false);
576 }
577
578 boost::shared_ptr<X509> CreateCertIcingaCA(const boost::shared_ptr<X509>& cert)
579 {
580         boost::shared_ptr<EVP_PKEY> pkey = boost::shared_ptr<EVP_PKEY>(X509_get_pubkey(cert.get()), EVP_PKEY_free);
581         return CreateCertIcingaCA(pkey.get(), X509_get_subject_name(cert.get()));
582 }
583
584 String CertificateToString(const boost::shared_ptr<X509>& cert)
585 {
586         BIO *mem = BIO_new(BIO_s_mem());
587         PEM_write_bio_X509(mem, cert.get());
588
589         char *data;
590         long len = BIO_get_mem_data(mem, &data);
591
592         String result = String(data, data + len);
593
594         BIO_free(mem);
595
596         return result;
597 }
598
599 boost::shared_ptr<X509> StringToCertificate(const String& cert)
600 {
601         BIO *bio = BIO_new(BIO_s_mem());
602         BIO_write(bio, (const void *)cert.CStr(), cert.GetLength());
603
604         X509 *rawCert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
605
606         BIO_free(bio);
607
608         if (!rawCert)
609                 BOOST_THROW_EXCEPTION(std::invalid_argument("The specified X509 certificate is invalid."));
610
611         return boost::shared_ptr<X509>(rawCert, X509_free);
612 }
613
614 String PBKDF2_SHA1(const String& password, const String& salt, int iterations)
615 {
616         unsigned char digest[SHA_DIGEST_LENGTH];
617         PKCS5_PBKDF2_HMAC_SHA1(password.CStr(), password.GetLength(), reinterpret_cast<const unsigned char *>(salt.CStr()), salt.GetLength(),
618             iterations, sizeof(digest), digest);
619
620         char output[SHA_DIGEST_LENGTH*2+1];
621         for (int i = 0; i < SHA_DIGEST_LENGTH; i++)
622                 sprintf(output + 2 * i, "%02x", digest[i]);
623
624         return output;
625 }
626
627 String PBKDF2_SHA256(const String& password, const String& salt, int iterations)
628 {
629         unsigned char digest[SHA256_DIGEST_LENGTH];
630         PKCS5_PBKDF2_HMAC(password.CStr(), password.GetLength(), reinterpret_cast<const unsigned char *>(salt.CStr()),
631                 salt.GetLength(), iterations, EVP_sha256(), SHA256_DIGEST_LENGTH, digest);
632
633         char output[SHA256_DIGEST_LENGTH*2+1];
634         for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
635                 sprintf(output + 2 * i, "%02x", digest[i]);
636
637         return output;
638 }
639
640 String SHA1(const String& s, bool binary)
641 {
642         char errbuf[120];
643         SHA_CTX context;
644         unsigned char digest[SHA_DIGEST_LENGTH];
645
646         if (!SHA1_Init(&context)) {
647                 Log(LogCritical, "SSL")
648                     << "Error on SHA Init: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
649                 BOOST_THROW_EXCEPTION(openssl_error()
650                     << boost::errinfo_api_function("SHA1_Init")
651                     << errinfo_openssl_error(ERR_peek_error()));
652         }
653
654         if (!SHA1_Update(&context, (unsigned char*)s.CStr(), s.GetLength())) {
655                 Log(LogCritical, "SSL")
656                     << "Error on SHA Update: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
657                 BOOST_THROW_EXCEPTION(openssl_error()
658                     << boost::errinfo_api_function("SHA1_Update")
659                     << errinfo_openssl_error(ERR_peek_error()));
660         }
661
662         if (!SHA1_Final(digest, &context)) {
663                 Log(LogCritical, "SSL")
664                     << "Error on SHA Final: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
665                 BOOST_THROW_EXCEPTION(openssl_error()
666                     << boost::errinfo_api_function("SHA1_Final")
667                     << errinfo_openssl_error(ERR_peek_error()));
668         }
669
670         if (binary)
671                 return String(reinterpret_cast<const char*>(digest), reinterpret_cast<const char *>(digest + SHA_DIGEST_LENGTH));
672
673         char output[SHA_DIGEST_LENGTH*2+1];
674         for (int i = 0; i < 20; i++)
675                 sprintf(output + 2 * i, "%02x", digest[i]);
676
677         return output;
678 }
679
680 String SHA256(const String& s)
681 {
682         char errbuf[120];
683         SHA256_CTX context;
684         unsigned char digest[SHA256_DIGEST_LENGTH];
685
686         if (!SHA256_Init(&context)) {
687                 Log(LogCritical, "SSL")
688                     << "Error on SHA256 Init: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
689                 BOOST_THROW_EXCEPTION(openssl_error()
690                     << boost::errinfo_api_function("SHA256_Init")
691                     << errinfo_openssl_error(ERR_peek_error()));
692         }
693
694         if (!SHA256_Update(&context, (unsigned char*)s.CStr(), s.GetLength())) {
695                 Log(LogCritical, "SSL")
696                     << "Error on SHA256 Update: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
697                 BOOST_THROW_EXCEPTION(openssl_error()
698                     << boost::errinfo_api_function("SHA256_Update")
699                     << errinfo_openssl_error(ERR_peek_error()));
700         }
701
702         if (!SHA256_Final(digest, &context)) {
703                 Log(LogCritical, "SSL")
704                     << "Error on SHA256 Final: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
705                 BOOST_THROW_EXCEPTION(openssl_error()
706                     << boost::errinfo_api_function("SHA256_Final")
707                     << errinfo_openssl_error(ERR_peek_error()));
708         }
709
710         char output[SHA256_DIGEST_LENGTH*2+1];
711         for (int i = 0; i < 32; i++)
712                 sprintf(output + 2 * i, "%02x", digest[i]);
713
714         return output;
715 }
716
717 String RandomString(int length)
718 {
719         unsigned char *bytes = new unsigned char[length];
720
721         if (!RAND_bytes(bytes, length)) {
722                 delete [] bytes;
723
724                 char errbuf[120];
725
726                 Log(LogCritical, "SSL")
727                         << "Error for RAND_bytes: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
728                 BOOST_THROW_EXCEPTION(openssl_error()
729                         << boost::errinfo_api_function("RAND_bytes")
730                         << errinfo_openssl_error(ERR_peek_error()));
731         }
732
733         char *output = new char[length * 2 + 1];
734         for (int i = 0; i < length; i++)
735                 sprintf(output + 2 * i, "%02x", bytes[i]);
736
737         String result = output;
738         delete [] bytes;
739         delete [] output;
740
741         return result;
742 }
743
744 bool VerifyCertificate(const boost::shared_ptr<X509>& caCertificate, const boost::shared_ptr<X509>& certificate)
745 {
746         X509_STORE *store = X509_STORE_new();
747
748         if (!store)
749                 return false;
750
751         X509_STORE_add_cert(store, caCertificate.get());
752
753         X509_STORE_CTX *csc = X509_STORE_CTX_new();
754         X509_STORE_CTX_init(csc, store, certificate.get(), NULL);
755
756         int rc = X509_verify_cert(csc);
757
758         X509_STORE_CTX_free(csc);
759         X509_STORE_free(store);
760
761         return rc == 1;
762 }
763
764 bool ComparePassword(const String hash, const String password, const String salt)
765 {
766         String otherHash = HashPassword(password, salt);
767
768         const char *p1 = otherHash.CStr();
769         const char *p2 = hash.CStr();
770
771         volatile char c = 0;
772
773         for (size_t i=0; i<64; ++i)
774                 c |= p1[i] ^ p2[i];
775
776         return (c == 0);
777 }
778
779 String HashPassword(const String& password, const String& salt, const bool shadow)
780 {
781         if (shadow)
782                 //Using /etc/shadow password format. The 5 means SHA256 is being used
783                 return String("$5$" + salt + "$" + PBKDF2_SHA256(password, salt, 1000));
784         else
785                 return PBKDF2_SHA256(password, salt, 1000);
786 }
787
788 }