diff --git a/lib/cli/CMakeLists.txt b/lib/cli/CMakeLists.txt index c81d3bb94..38631dc6f 100644 --- a/lib/cli/CMakeLists.txt +++ b/lib/cli/CMakeLists.txt @@ -23,6 +23,7 @@ set(cli_SOURCES featureenablecommand.cpp featuredisablecommand.cpp featurelistcommand.cpp featureutility.cpp objectlistcommand.cpp pkinewcacommand.cpp pkinewcertcommand.cpp pkisigncsrcommand.cpp pkirequestcommand.cpp pkisavecertcommand.cpp pkiticketcommand.cpp + pkiutility.cpp repositoryobjectcommand.cpp variablegetcommand.cpp variablelistcommand.cpp ) diff --git a/lib/cli/pkinewcacommand.cpp b/lib/cli/pkinewcacommand.cpp index 5aa4adda1..ab760bd60 100644 --- a/lib/cli/pkinewcacommand.cpp +++ b/lib/cli/pkinewcacommand.cpp @@ -18,10 +18,8 @@ ******************************************************************************/ #include "cli/pkinewcacommand.hpp" +#include "cli/pkiutility.hpp" #include "base/logger.hpp" -#include "base/application.hpp" -#include "base/tlsutility.hpp" -#include using namespace icinga; @@ -44,37 +42,5 @@ String PKINewCACommand::GetShortDescription(void) const */ int PKINewCACommand::Run(const boost::program_options::variables_map& vm, const std::vector& ap) const { - String cadir = Application::GetLocalStateDir() + "/lib/icinga2/ca"; - - if (Utility::PathExists(cadir)) { - Log(LogCritical, "base") - << "CA directory '" << cadir << "' already exists."; - return 1; - } - - if (!Utility::MkDirP(cadir, 0700)) { - Log(LogCritical, "base") - << "Could not create CA directory '" << cadir << "'."; - return 1; - } - - MakeX509CSR("Icinga CA", cadir + "/ca.key", String(), cadir + "/ca.crt", true); - - String serialpath = cadir + "/serial.txt"; - - Log(LogInformation, "cli") - << "Initializing serial file in '" << serialpath << "'."; - - std::ofstream fp; - fp.open(serialpath.CStr()); - fp << "01"; - fp.close(); - - if (fp.fail()) { - Log(LogCritical, "cli") - << "Could not create serial file '" << serialpath << "'"; - return 1; - } - - return 0; + return PkiUtility::NewCa(); } diff --git a/lib/cli/pkinewcertcommand.cpp b/lib/cli/pkinewcertcommand.cpp index 43895b5a4..fb0aa4485 100644 --- a/lib/cli/pkinewcertcommand.cpp +++ b/lib/cli/pkinewcertcommand.cpp @@ -18,8 +18,8 @@ ******************************************************************************/ #include "cli/pkinewcertcommand.hpp" +#include "cli/pkiutility.hpp" #include "base/logger.hpp" -#include "base/tlsutility.hpp" using namespace icinga; namespace po = boost::program_options; @@ -79,7 +79,5 @@ int PKINewCertCommand::Run(const boost::program_options::variables_map& vm, cons if (vm.count("certfile")) certfile = vm["certfile"].as(); - MakeX509CSR(vm["cn"].as(), vm["keyfile"].as(), csrfile, certfile); - - return 0; + return PkiUtility::NewCert(vm["cn"].as(), vm["keyfile"].as(), csrfile, certfile); } diff --git a/lib/cli/pkirequestcommand.cpp b/lib/cli/pkirequestcommand.cpp index bc37f1290..62a1f9b8a 100644 --- a/lib/cli/pkirequestcommand.cpp +++ b/lib/cli/pkirequestcommand.cpp @@ -18,14 +18,9 @@ ******************************************************************************/ #include "cli/pkirequestcommand.hpp" -#include "remote/jsonrpc.hpp" +#include "cli/pkiutility.hpp" #include "base/logger.hpp" -#include "base/tlsutility.hpp" -#include "base/tlsstream.hpp" -#include "base/tcpsocket.hpp" -#include "base/utility.hpp" -#include "base/application.hpp" -#include +#include using namespace icinga; namespace po = boost::program_options; @@ -80,22 +75,22 @@ int PKIRequestCommand::Run(const boost::program_options::variables_map& vm, cons } if (!vm.count("keyfile")) { - Log(LogCritical, "cli", "Key file path (--keyfile) must be specified."); + Log(LogCritical, "cli", "Key input file path (--keyfile) must be specified."); return 1; } if (!vm.count("certfile")) { - Log(LogCritical, "cli", "Certificate file path (--certfile) must be specified."); + Log(LogCritical, "cli", "Certificate output file path (--certfile) must be specified."); return 1; } if (!vm.count("cafile")) { - Log(LogCritical, "cli", "CA certificate file path (--cafile) must be specified."); + Log(LogCritical, "cli", "CA certificate output file path (--cafile) must be specified."); return 1; } if (!vm.count("trustedfile")) { - Log(LogCritical, "cli", "Trusted certificate file path (--trustedfile) must be specified."); + Log(LogCritical, "cli", "Trusted certificate input file path (--trustedfile) must be specified."); return 1; } @@ -104,87 +99,12 @@ int PKIRequestCommand::Run(const boost::program_options::variables_map& vm, cons return 1; } - TcpSocket::Ptr client = make_shared(); - String port = "5665"; if (vm.count("port")) port = vm["port"].as(); - client->Connect(vm["host"].as(), port); - - String certfile = vm["certfile"].as(); - - shared_ptr sslContext = MakeSSLContext(certfile, vm["keyfile"].as()); - - TlsStream::Ptr stream = make_shared(client, RoleClient, sslContext); - - stream->Handshake(); - - shared_ptr peerCert = stream->GetPeerCertificate(); - shared_ptr trustedCert = GetX509Certificate(vm["trustedfile"].as()); - - if (CertificateToString(peerCert) != CertificateToString(trustedCert)) { - Log(LogCritical, "cli", "Peer certificate does not match trusted certificate."); - return 1; - } - - Dictionary::Ptr request = make_shared(); - - String msgid = Utility::NewUniqueID(); - - request->Set("jsonrpc", "2.0"); - request->Set("id", msgid); - request->Set("method", "pki::RequestCertificate"); - - Dictionary::Ptr params = make_shared(); - params->Set("ticket", String(vm["ticket"].as())); - - request->Set("params", params); - - JsonRpc::SendMessage(stream, request); - - Dictionary::Ptr response; - - for (;;) { - response = JsonRpc::ReadMessage(stream); - - if (response->Get("id") != msgid) - continue; - - break; - } - - Dictionary::Ptr result = response->Get("result"); - - if (result->Contains("error")) { - Log(LogCritical, "cli", result->Get("error")); - return 1; - } - - String cafile = vm["cafile"].as(); - - std::ofstream fpcert; - fpcert.open(certfile.CStr()); - fpcert << result->Get("cert"); - fpcert.close(); - - if (fpcert.fail()) { - Log(LogCritical, "cli") - << "Could not write certificate to file '" << certfile << "'."; - return 1; - } - - std::ofstream fpca; - fpca.open(cafile.CStr()); - fpca << result->Get("ca"); - fpca.close(); - - if (fpca.fail()) { - Log(LogCritical, "cli") - << "Could not open CA certificate file '" << cafile << "' for writing."; - return 1; - } - - return 0; + return PkiUtility::RequestCertificate(vm["host"].as(), port, vm["keyfile"].as(), + vm["certfile"].as(), vm["cafile"].as(), vm["trustedfile"].as(), + vm["ticket"].as()); } diff --git a/lib/cli/pkisavecertcommand.cpp b/lib/cli/pkisavecertcommand.cpp index c9e6b4939..cb80ca1aa 100644 --- a/lib/cli/pkisavecertcommand.cpp +++ b/lib/cli/pkisavecertcommand.cpp @@ -18,14 +18,8 @@ ******************************************************************************/ #include "cli/pkisavecertcommand.hpp" -#include "remote/jsonrpc.hpp" +#include "cli/pkiutility.hpp" #include "base/logger.hpp" -#include "base/tlsutility.hpp" -#include "base/tlsstream.hpp" -#include "base/tcpsocket.hpp" -#include "base/utility.hpp" -#include "base/application.hpp" -#include using namespace icinga; namespace po = boost::program_options; @@ -78,53 +72,24 @@ int PKISaveCertCommand::Run(const boost::program_options::variables_map& vm, con } if (!vm.count("keyfile")) { - Log(LogCritical, "cli", "Key file path (--keyfile) must be specified."); + Log(LogCritical, "cli", "Key input file path (--keyfile) must be specified."); return 1; } if (!vm.count("certfile")) { - Log(LogCritical, "cli", "Certificate file path (--certfile) must be specified."); + Log(LogCritical, "cli", "Certificate input file path (--certfile) must be specified."); return 1; } if (!vm.count("trustedfile")) { - Log(LogCritical, "cli", "Trusted certificate file path (--trustedfile) must be specified."); + Log(LogCritical, "cli", "Trusted certificate output file path (--trustedfile) must be specified."); return 1; } - TcpSocket::Ptr client = make_shared(); - String port = "5665"; if (vm.count("port")) port = vm["port"].as(); - client->Connect(vm["host"].as(), port); - - shared_ptr sslContext = MakeSSLContext(vm["certfile"].as(), vm["keyfile"].as()); - - TlsStream::Ptr stream = make_shared(client, RoleClient, sslContext); - - try { - stream->Handshake(); - } catch (...) { - - } - - shared_ptr cert = stream->GetPeerCertificate(); - - String trustedfile = vm["trustedfile"].as(); - - std::ofstream fpcert; - fpcert.open(trustedfile.CStr()); - fpcert << CertificateToString(cert); - fpcert.close(); - - if (fpcert.fail()) { - Log(LogCritical, "cli") - << "Could not write certificate to file '" << trustedfile << "'."; - return 1; - } - - return 0; + return PkiUtility::SaveCert(vm["host"].as(), port, vm["keyfile"].as(), vm["certfile"].as(), vm["trustedfile"].as()); } diff --git a/lib/cli/pkisigncsrcommand.cpp b/lib/cli/pkisigncsrcommand.cpp index 0784cfe7c..83644da5f 100644 --- a/lib/cli/pkisigncsrcommand.cpp +++ b/lib/cli/pkisigncsrcommand.cpp @@ -18,10 +18,8 @@ ******************************************************************************/ #include "cli/pkisigncsrcommand.hpp" +#include "cli/pkiutility.hpp" #include "base/logger.hpp" -#include "base/tlsutility.hpp" -#include "base/application.hpp" -#include using namespace icinga; namespace po = boost::program_options; @@ -71,41 +69,5 @@ int PKISignCSRCommand::Run(const boost::program_options::variables_map& vm, cons return 1; } - std::stringstream msgbuf; - char errbuf[120]; - - InitializeOpenSSL(); - - String csrfile = vm["csrfile"].as(); - - BIO *csrbio = BIO_new_file(csrfile.CStr(), "r"); - X509_REQ *req = PEM_read_bio_X509_REQ(csrbio, NULL, NULL, NULL); - - if (!req) { - Log(LogCritical, "SSL") - << "Could not read X509 certificate request from '" << csrfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\""; - return 1; - } - - BIO_free(csrbio); - - shared_ptr cert = CreateCertIcingaCA(X509_REQ_get_pubkey(req), X509_REQ_get_subject_name(req)); - - X509_REQ_free(req); - - String certfile = vm["certfile"].as(); - - std::ofstream fpcert; - fpcert.open(certfile.CStr()); - - if (!fpcert) { - Log(LogCritical, "cli") - << "Failed to open certificate file '" << certfile << "' for output"; - return 1; - } - - fpcert << CertificateToString(cert); - fpcert.close(); - - return 0; + return PkiUtility::SignCsr(vm["csrfile"].as(), vm["certfile"].as()); } diff --git a/lib/cli/pkiticketcommand.cpp b/lib/cli/pkiticketcommand.cpp index e83623bd2..bd7998e09 100644 --- a/lib/cli/pkiticketcommand.cpp +++ b/lib/cli/pkiticketcommand.cpp @@ -18,13 +18,8 @@ ******************************************************************************/ #include "cli/pkiticketcommand.hpp" -#include "remote/jsonrpc.hpp" +#include "cli/pkiutility.hpp" #include "base/logger.hpp" -#include "base/tlsutility.hpp" -#include "base/tlsstream.hpp" -#include "base/tcpsocket.hpp" -#include "base/utility.hpp" -#include "base/application.hpp" #include using namespace icinga; @@ -67,7 +62,5 @@ int PKITicketCommand::Run(const boost::program_options::variables_map& vm, const return 1; } - std::cout << PBKDF2_SHA1(vm["cn"].as(), vm["salt"].as(), 50000) << std::endl; - - return 0; + return PkiUtility::GenTicket(vm["cn"].as(), vm["salt"].as(), std::cout); } diff --git a/lib/cli/pkiutility.cpp b/lib/cli/pkiutility.cpp new file mode 100644 index 000000000..e630578e8 --- /dev/null +++ b/lib/cli/pkiutility.cpp @@ -0,0 +1,244 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "cli/pkiutility.hpp" +#include "cli/clicommand.hpp" +#include "base/logger.hpp" +#include "base/application.hpp" +#include "base/tlsutility.hpp" +#include "base/tlsstream.hpp" +#include "base/tcpsocket.hpp" +#include "base/utility.hpp" +#include "remote/jsonrpc.hpp" +#include +#include + +using namespace icinga; + +int PkiUtility::NewCa(void) +{ + String cadir = Application::GetLocalStateDir() + "/lib/icinga2/ca"; + + if (Utility::PathExists(cadir)) { + Log(LogCritical, "cli") + << "CA directory '" << cadir << "' already exists."; + return 1; + } + + if (!Utility::MkDirP(cadir, 0700)) { + Log(LogCritical, "base") + << "Could not create CA directory '" << cadir << "'."; + return 1; + } + + MakeX509CSR("Icinga CA", cadir + "/ca.key", String(), cadir + "/ca.crt", true); + + String serialpath = cadir + "/serial.txt"; + + Log(LogInformation, "cli") + << "Initializing serial file in '" << serialpath << "'."; + + std::ofstream fp; + fp.open(serialpath.CStr()); + fp << "01"; + fp.close(); + + if (fp.fail()) { + Log(LogCritical, "cli") + << "Could not create serial file '" << serialpath << "'"; + return 1; + } + + return 0; +} + +int PkiUtility::NewCert(const String& cn, const String& keyfile, const String& csrfile, const String& certfile) +{ + try { + MakeX509CSR(cn, keyfile, csrfile, certfile); + } catch(std::exception&) { + return 1; + } + + return 0; +} + +int PkiUtility::SignCsr(const String& csrfile, const String& certfile) +{ + std::stringstream msgbuf; + char errbuf[120]; + + InitializeOpenSSL(); + + BIO *csrbio = BIO_new_file(csrfile.CStr(), "r"); + X509_REQ *req = PEM_read_bio_X509_REQ(csrbio, NULL, NULL, NULL); + + if (!req) { + Log(LogCritical, "SSL") + << "Could not read X509 certificate request from '" << csrfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\""; + return 1; + } + + BIO_free(csrbio); + + shared_ptr cert = CreateCertIcingaCA(X509_REQ_get_pubkey(req), X509_REQ_get_subject_name(req)); + + X509_REQ_free(req); + + std::ofstream fpcert; + fpcert.open(certfile.CStr()); + + if (!fpcert) { + Log(LogCritical, "cli") + << "Failed to open certificate file '" << certfile << "' for output"; + return 1; + } + + fpcert << CertificateToString(cert); + fpcert.close(); + + return 0; +} + +int PkiUtility::SaveCert(const String& host, const String& port, const String& keyfile, const String& certfile, const String& trustedfile) +{ + TcpSocket::Ptr client = make_shared(); + + client->Connect(host, port); + + shared_ptr sslContext = MakeSSLContext(certfile, keyfile); + + TlsStream::Ptr stream = make_shared(client, RoleClient, sslContext); + + try { + stream->Handshake(); + } catch (...) { + + } + + shared_ptr cert = stream->GetPeerCertificate(); + + std::ofstream fpcert; + fpcert.open(trustedfile.CStr()); + fpcert << CertificateToString(cert); + fpcert.close(); + + if (fpcert.fail()) { + Log(LogCritical, "cli") + << "Could not write certificate to file '" << trustedfile << "'."; + return 1; + } + + Log(LogInformation, "cli") + << "Writing trusted certificate to file '" << trustedfile << "'."; + + return 0; +} + +int PkiUtility::GenTicket(const String& cn, const String& salt, std::ostream& ticketfp) +{ + ticketfp << PBKDF2_SHA1(cn, salt, 50000) << "\n"; + + return 0; +} + +int PkiUtility::RequestCertificate(const String& host, const String& port, const String& keyfile, + const String& certfile, const String& cafile, const String& trustedfile, const String& ticket) +{ + TcpSocket::Ptr client = make_shared(); + + client->Connect(host, port); + + shared_ptr sslContext = MakeSSLContext(certfile, keyfile); + + TlsStream::Ptr stream = make_shared(client, RoleClient, sslContext); + + stream->Handshake(); + + shared_ptr peerCert = stream->GetPeerCertificate(); + shared_ptr trustedCert = GetX509Certificate(trustedfile); + + if (CertificateToString(peerCert) != CertificateToString(trustedCert)) { + Log(LogCritical, "cli", "Peer certificate does not match trusted certificate."); + return 1; + } + + Dictionary::Ptr request = make_shared(); + + String msgid = Utility::NewUniqueID(); + + request->Set("jsonrpc", "2.0"); + request->Set("id", msgid); + request->Set("method", "pki::RequestCertificate"); + + Dictionary::Ptr params = make_shared(); + params->Set("ticket", String(ticket)); + + request->Set("params", params); + + JsonRpc::SendMessage(stream, request); + + Dictionary::Ptr response; + + for (;;) { + response = JsonRpc::ReadMessage(stream); + + if (response->Get("id") != msgid) + continue; + + break; + } + + Dictionary::Ptr result = response->Get("result"); + + if (result->Contains("error")) { + Log(LogCritical, "cli", result->Get("error")); + return 1; + } + + std::ofstream fpcert; + fpcert.open(certfile.CStr()); + fpcert << result->Get("cert"); + fpcert.close(); + + if (fpcert.fail()) { + Log(LogCritical, "cli") + << "Could not write certificate to file '" << certfile << "'."; + return 1; + } + + Log(LogInformation, "cli") + << "Writing signed certificate to file '" << certfile << "'."; + + std::ofstream fpca; + fpca.open(cafile.CStr()); + fpca << result->Get("ca"); + fpca.close(); + + if (fpca.fail()) { + Log(LogCritical, "cli") + << "Could not open CA certificate file '" << cafile << "' for writing."; + return 1; + } + + Log(LogInformation, "cli") + << "Writing CA certificate to file '" << certfile << "'."; + + return 0; +} diff --git a/lib/cli/pkiutility.hpp b/lib/cli/pkiutility.hpp new file mode 100644 index 000000000..fae86330c --- /dev/null +++ b/lib/cli/pkiutility.hpp @@ -0,0 +1,50 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#ifndef PKIUTILITY_H +#define PKIUTILITY_H + +#include "base/i2-base.hpp" +#include "base/dictionary.hpp" +#include "base/string.hpp" + +namespace icinga +{ + +/** + * @ingroup cli + */ +class PkiUtility +{ +public: + static int NewCa(void); + static int NewCert(const String& cn, const String& keyfile, const String& csrfile, const String& certfile); + static int SignCsr(const String& csrfile, const String& certfile); + static int SaveCert(const String& host, const String& port, const String& keyfile, const String& certfile, const String& trustedfile); + static int GenTicket(const String& cn, const String& salt, std::ostream& ticketfp); + static int RequestCertificate(const String& host, const String& port, const String& keyfile, + const String& certfile, const String& cafile, const String& trustedfile, const String& ticket); + +private: + PkiUtility(void); +}; + +} + +#endif /* PKIUTILITY_H */