"Fossies" - the Fresh Open Source Software Archive

Member "icinga2-2.10.5/lib/remote/pkiutility.cpp" (23 May 2019, 12429 Bytes) of package /linux/misc/icinga2-2.10.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "pkiutility.cpp" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2.10.0_vs_2.10.1.

    1 /******************************************************************************
    2  * Icinga 2                                                                   *
    3  * Copyright (C) 2012-2018 Icinga Development Team (https://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 "remote/pkiutility.hpp"
   21 #include "remote/apilistener.hpp"
   22 #include "base/logger.hpp"
   23 #include "base/application.hpp"
   24 #include "base/tlsutility.hpp"
   25 #include "base/console.hpp"
   26 #include "base/tlsstream.hpp"
   27 #include "base/tcpsocket.hpp"
   28 #include "base/json.hpp"
   29 #include "base/utility.hpp"
   30 #include "base/exception.hpp"
   31 #include "remote/jsonrpc.hpp"
   32 #include <fstream>
   33 #include <iostream>
   34 
   35 using namespace icinga;
   36 
   37 int PkiUtility::NewCa()
   38 {
   39     String caDir = ApiListener::GetCaDir();
   40     String caCertFile = caDir + "/ca.crt";
   41     String caKeyFile = caDir + "/ca.key";
   42 
   43     if (Utility::PathExists(caCertFile) && Utility::PathExists(caKeyFile)) {
   44         Log(LogCritical, "cli")
   45             << "CA files '" << caCertFile << "' and '" << caKeyFile << "' already exist.";
   46         return 1;
   47     }
   48 
   49     Utility::MkDirP(caDir, 0700);
   50 
   51     MakeX509CSR("Icinga CA", caKeyFile, String(), caCertFile, true);
   52 
   53     return 0;
   54 }
   55 
   56 int PkiUtility::NewCert(const String& cn, const String& keyfile, const String& csrfile, const String& certfile)
   57 {
   58     try {
   59         MakeX509CSR(cn, keyfile, csrfile, certfile);
   60     } catch(std::exception&) {
   61         return 1;
   62     }
   63 
   64     return 0;
   65 }
   66 
   67 int PkiUtility::SignCsr(const String& csrfile, const String& certfile)
   68 {
   69     char errbuf[120];
   70 
   71     InitializeOpenSSL();
   72 
   73     BIO *csrbio = BIO_new_file(csrfile.CStr(), "r");
   74     X509_REQ *req = PEM_read_bio_X509_REQ(csrbio, nullptr, nullptr, nullptr);
   75 
   76     if (!req) {
   77         Log(LogCritical, "SSL")
   78             << "Could not read X509 certificate request from '" << csrfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
   79         return 1;
   80     }
   81 
   82     BIO_free(csrbio);
   83 
   84     std::shared_ptr<EVP_PKEY> pubkey = std::shared_ptr<EVP_PKEY>(X509_REQ_get_pubkey(req), EVP_PKEY_free);
   85     std::shared_ptr<X509> cert = CreateCertIcingaCA(pubkey.get(), X509_REQ_get_subject_name(req));
   86 
   87     X509_REQ_free(req);
   88 
   89     WriteCert(cert, certfile);
   90 
   91     return 0;
   92 }
   93 
   94 std::shared_ptr<X509> PkiUtility::FetchCert(const String& host, const String& port)
   95 {
   96     TcpSocket::Ptr client = new TcpSocket();
   97 
   98     try {
   99         client->Connect(host, port);
  100     } catch (const std::exception& ex) {
  101         Log(LogCritical, "pki")
  102             << "Cannot connect to host '" << host << "' on port '" << port << "'";
  103         Log(LogDebug, "pki")
  104             << "Cannot connect to host '" << host << "' on port '" << port << "':\n" << DiagnosticInformation(ex);
  105         return std::shared_ptr<X509>();
  106     }
  107 
  108     std::shared_ptr<SSL_CTX> sslContext;
  109 
  110     try {
  111         sslContext = MakeSSLContext();
  112     } catch (const std::exception& ex) {
  113         Log(LogCritical, "pki")
  114             << "Cannot make SSL context.";
  115         Log(LogDebug, "pki")
  116             << "Cannot make SSL context:\n"  << DiagnosticInformation(ex);
  117         return std::shared_ptr<X509>();
  118     }
  119 
  120     TlsStream::Ptr stream = new TlsStream(client, host, RoleClient, sslContext);
  121 
  122     try {
  123         stream->Handshake();
  124     } catch (const std::exception& ex) {
  125         Log(LogCritical, "pki")
  126             << "Client TLS handshake failed. (" << ex.what() << ")";
  127         return std::shared_ptr<X509>();
  128     }
  129 
  130     return stream->GetPeerCertificate();
  131 }
  132 
  133 int PkiUtility::WriteCert(const std::shared_ptr<X509>& cert, const String& trustedfile)
  134 {
  135     std::ofstream fpcert;
  136     fpcert.open(trustedfile.CStr());
  137     fpcert << CertificateToString(cert);
  138     fpcert.close();
  139 
  140     if (fpcert.fail()) {
  141         Log(LogCritical, "pki")
  142             << "Could not write certificate to file '" << trustedfile << "'.";
  143         return 1;
  144     }
  145 
  146     Log(LogInformation, "pki")
  147         << "Writing certificate to file '" << trustedfile << "'.";
  148 
  149     return 0;
  150 }
  151 
  152 int PkiUtility::GenTicket(const String& cn, const String& salt, std::ostream& ticketfp)
  153 {
  154     ticketfp << PBKDF2_SHA1(cn, salt, 50000) << "\n";
  155 
  156     return 0;
  157 }
  158 
  159 int PkiUtility::RequestCertificate(const String& host, const String& port, const String& keyfile,
  160     const String& certfile, const String& cafile, const std::shared_ptr<X509>& trustedCert, const String& ticket)
  161 {
  162     TcpSocket::Ptr client = new TcpSocket();
  163 
  164     try {
  165         client->Connect(host, port);
  166     } catch (const std::exception& ex) {
  167         Log(LogCritical, "cli")
  168             << "Cannot connect to host '" << host << "' on port '" << port << "'";
  169         Log(LogDebug, "cli")
  170             << "Cannot connect to host '" << host << "' on port '" << port << "':\n" << DiagnosticInformation(ex);
  171         return 1;
  172     }
  173 
  174     std::shared_ptr<SSL_CTX> sslContext;
  175 
  176     try {
  177         sslContext = MakeSSLContext(certfile, keyfile);
  178     } catch (const std::exception& ex) {
  179         Log(LogCritical, "cli")
  180             << "Cannot make SSL context for cert path: '" << certfile << "' key path: '" << keyfile << "' ca path: '" << cafile << "'.";
  181         Log(LogDebug, "cli")
  182             << "Cannot make SSL context for cert path: '" << certfile << "' key path: '" << keyfile << "' ca path: '" << cafile << "':\n"  << DiagnosticInformation(ex);
  183         return 1;
  184     }
  185 
  186     TlsStream::Ptr stream = new TlsStream(client, host, RoleClient, sslContext);
  187 
  188     try {
  189         stream->Handshake();
  190     } catch (const std::exception& ex) {
  191         Log(LogCritical, "cli")
  192             << "Client TLS handshake failed: " << DiagnosticInformation(ex, false);
  193         return 1;
  194     }
  195 
  196     std::shared_ptr<X509> peerCert = stream->GetPeerCertificate();
  197 
  198     if (X509_cmp(peerCert.get(), trustedCert.get())) {
  199         Log(LogCritical, "cli", "Peer certificate does not match trusted certificate.");
  200         return 1;
  201     }
  202 
  203     Dictionary::Ptr params = new Dictionary({
  204         { "ticket", String(ticket) }
  205     });
  206 
  207     String msgid = Utility::NewUniqueID();
  208 
  209     Dictionary::Ptr request = new Dictionary({
  210         { "jsonrpc", "2.0" },
  211         { "id", msgid },
  212         { "method", "pki::RequestCertificate" },
  213         { "params", params }
  214     });
  215 
  216     JsonRpc::SendMessage(stream, request);
  217 
  218     String jsonString;
  219     Dictionary::Ptr response;
  220     StreamReadContext src;
  221 
  222     for (;;) {
  223         StreamReadStatus srs = JsonRpc::ReadMessage(stream, &jsonString, src);
  224 
  225         if (srs == StatusEof)
  226             break;
  227 
  228         if (srs != StatusNewItem)
  229             continue;
  230 
  231         response = JsonRpc::DecodeMessage(jsonString);
  232 
  233         if (response && response->Contains("error")) {
  234             Log(LogCritical, "cli", "Could not fetch valid response. Please check the master log (notice or debug).");
  235 #ifdef I2_DEBUG
  236             /* we shouldn't expose master errors to the user in production environments */
  237             Log(LogCritical, "cli", response->Get("error"));
  238 #endif /* I2_DEBUG */
  239             return 1;
  240         }
  241 
  242         if (response && (response->Get("id") != msgid))
  243             continue;
  244 
  245         break;
  246     }
  247 
  248     if (!response) {
  249         Log(LogCritical, "cli", "Could not fetch valid response. Please check the master log.");
  250         return 1;
  251     }
  252 
  253     Dictionary::Ptr result = response->Get("result");
  254 
  255     if (result->Contains("ca")) {
  256         try {
  257             StringToCertificate(result->Get("ca"));
  258         } catch (const std::exception& ex) {
  259             Log(LogCritical, "cli")
  260                 << "Could not write CA file: " << DiagnosticInformation(ex, false);
  261             return 1;
  262         }
  263 
  264         Log(LogInformation, "cli")
  265             << "Writing CA certificate to file '" << cafile << "'.";
  266 
  267         std::ofstream fpca;
  268         fpca.open(cafile.CStr());
  269         fpca << result->Get("ca");
  270         fpca.close();
  271 
  272         if (fpca.fail()) {
  273             Log(LogCritical, "cli")
  274                 << "Could not open CA certificate file '" << cafile << "' for writing.";
  275             return 1;
  276         }
  277     }
  278 
  279     if (result->Contains("error")) {
  280         LogSeverity severity;
  281 
  282         Value vstatus;
  283 
  284         if (!result->Get("status_code", &vstatus))
  285             vstatus = 1;
  286 
  287         int status = vstatus;
  288 
  289         if (status == 1)
  290             severity = LogCritical;
  291         else {
  292             severity = LogInformation;
  293             Log(severity, "cli", "!!!!!!");
  294         }
  295 
  296         Log(severity, "cli")
  297             << "!!! " << result->Get("error");
  298 
  299         if (status == 1)
  300             return 1;
  301         else {
  302             Log(severity, "cli", "!!!!!!");
  303             return 0;
  304         }
  305     }
  306 
  307     try {
  308         StringToCertificate(result->Get("cert"));
  309     } catch (const std::exception& ex) {
  310         Log(LogCritical, "cli")
  311             << "Could not write certificate file: " << DiagnosticInformation(ex, false);
  312         return 1;
  313     }
  314 
  315     Log(LogInformation, "cli")
  316         << "Writing signed certificate to file '" << certfile << "'.";
  317 
  318     std::ofstream fpcert;
  319     fpcert.open(certfile.CStr());
  320     fpcert << result->Get("cert");
  321     fpcert.close();
  322 
  323     if (fpcert.fail()) {
  324         Log(LogCritical, "cli")
  325             << "Could not write certificate to file '" << certfile << "'.";
  326         return 1;
  327     }
  328 
  329     return 0;
  330 }
  331 
  332 String PkiUtility::GetCertificateInformation(const std::shared_ptr<X509>& cert) {
  333     BIO *out = BIO_new(BIO_s_mem());
  334     String pre;
  335 
  336     pre = "\n Subject:     ";
  337     BIO_write(out, pre.CStr(), pre.GetLength());
  338     X509_NAME_print_ex(out, X509_get_subject_name(cert.get()), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB);
  339 
  340     pre = "\n Issuer:      ";
  341     BIO_write(out, pre.CStr(), pre.GetLength());
  342     X509_NAME_print_ex(out, X509_get_issuer_name(cert.get()), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB);
  343 
  344     pre = "\n Valid From:  ";
  345     BIO_write(out, pre.CStr(), pre.GetLength());
  346     ASN1_TIME_print(out, X509_get_notBefore(cert.get()));
  347 
  348     pre = "\n Valid Until: ";
  349     BIO_write(out, pre.CStr(), pre.GetLength());
  350     ASN1_TIME_print(out, X509_get_notAfter(cert.get()));
  351 
  352     pre = "\n Fingerprint: ";
  353     BIO_write(out, pre.CStr(), pre.GetLength());
  354     unsigned char md[EVP_MAX_MD_SIZE];
  355     unsigned int diglen;
  356     X509_digest(cert.get(), EVP_sha1(), md, &diglen);
  357 
  358     char *data;
  359     long length = BIO_get_mem_data(out, &data);
  360 
  361     std::stringstream info;
  362     info << String(data, data + length);
  363 
  364     BIO_free(out);
  365 
  366     for (unsigned int i = 0; i < diglen; i++) {
  367         info << std::setfill('0') << std::setw(2) << std::uppercase
  368             << std::hex << static_cast<int>(md[i]) << ' ';
  369     }
  370     info << '\n';
  371 
  372     return info.str();
  373 }
  374 
  375 static void CollectRequestHandler(const Dictionary::Ptr& requests, const String& requestFile)
  376 {
  377     Dictionary::Ptr request = Utility::LoadJsonFile(requestFile);
  378 
  379     if (!request)
  380         return;
  381 
  382     Dictionary::Ptr result = new Dictionary();
  383 
  384     String fingerprint = Utility::BaseName(requestFile);
  385     fingerprint = fingerprint.SubStr(0, fingerprint.GetLength() - 5);
  386 
  387     String certRequestText = request->Get("cert_request");
  388     result->Set("cert_request", certRequestText);
  389 
  390     Value vcertResponseText;
  391 
  392     if (request->Get("cert_response", &vcertResponseText)) {
  393         String certResponseText = vcertResponseText;
  394         result->Set("cert_response", certResponseText);
  395     }
  396 
  397     std::shared_ptr<X509> certRequest = StringToCertificate(certRequestText);
  398 
  399 /* XXX (requires OpenSSL >= 1.0.0)
  400     time_t now;
  401     time(&now);
  402     ASN1_TIME *tm = ASN1_TIME_adj(nullptr, now, 0, 0);
  403 
  404     int day, sec;
  405     ASN1_TIME_diff(&day, &sec, tm, X509_get_notBefore(certRequest.get()));
  406 
  407     result->Set("timestamp",  static_cast<double>(now) + day * 24 * 60 * 60 + sec); */
  408 
  409     BIO *out = BIO_new(BIO_s_mem());
  410     ASN1_TIME_print(out, X509_get_notBefore(certRequest.get()));
  411 
  412     char *data;
  413     long length;
  414     length = BIO_get_mem_data(out, &data);
  415 
  416     result->Set("timestamp", String(data, data + length));
  417     BIO_free(out);
  418 
  419     out = BIO_new(BIO_s_mem());
  420     X509_NAME_print_ex(out, X509_get_subject_name(certRequest.get()), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB);
  421 
  422     length = BIO_get_mem_data(out, &data);
  423 
  424     result->Set("subject", String(data, data + length));
  425     BIO_free(out);
  426 
  427     requests->Set(fingerprint, result);
  428 }
  429 
  430 Dictionary::Ptr PkiUtility::GetCertificateRequests()
  431 {
  432     Dictionary::Ptr requests = new Dictionary();
  433 
  434     String requestDir = ApiListener::GetCertificateRequestsDir();
  435 
  436     if (Utility::PathExists(requestDir))
  437         Utility::Glob(requestDir + "/*.json", std::bind(&CollectRequestHandler, requests, _1), GlobFile);
  438 
  439     return requests;
  440 }