return result;
}
+boost::shared_ptr<X509> StringToCertificate(const String& cert)
+{
+ BIO *bio = BIO_new(BIO_s_mem());
+ BIO_write(bio, (const void *)cert.CStr(), cert.GetLength());
+
+ X509 *rawCert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
+
+ BIO_free(bio);
+
+ if (!rawCert)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The specified X509 certificate is invalid."));
+
+ return boost::shared_ptr<X509>(rawCert, X509_free);
+}
+
String PBKDF2_SHA1(const String& password, const String& salt, int iterations)
{
unsigned char digest[SHA_DIGEST_LENGTH];
boost::shared_ptr<X509> I2_BASE_API CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca);
String I2_BASE_API GetIcingaCADir(void);
String I2_BASE_API CertificateToString(const boost::shared_ptr<X509>& cert);
+boost::shared_ptr<X509> I2_BASE_API StringToCertificate(const String& cert);
boost::shared_ptr<X509> I2_BASE_API CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject);
String I2_BASE_API PBKDF2_SHA1(const String& password, const String& salt, int iterations);
String I2_BASE_API SHA1(const String& s, bool binary = false);
return 1;
}
- if (!vm.count("ticket")) {
- Log(LogCritical, "cli", "Ticket (--ticket) must be specified.");
- return 1;
- }
-
String port = "5665";
+ String ticket;
if (vm.count("port"))
port = vm["port"].as<std::string>();
+ if (vm.count("ticket"))
+ ticket = vm["ticket"].as<std::string>();
+
return PkiUtility::RequestCertificate(vm["host"].as<std::string>(), port, vm["key"].as<std::string>(),
vm["cert"].as<std::string>(), vm["ca"].as<std::string>(), GetX509Certificate(vm["trustedcert"].as<std::string>()),
- vm["ticket"].as<std::string>());
+ ticket);
}
Dictionary::Ptr result = response->Get("result");
if (result->Contains("error")) {
- Log(LogCritical, "cli", result->Get("error"));
+ LogSeverity severity;
+
+ if (result->Get("status_code") == 1)
+ severity = LogCritical;
+ else {
+ severity = LogInformation;
+ Log(severity, "cli", "!!!!!!");
+ }
+
+ Log(severity, "cli")
+ << "!!! " << result->Get("error");
+
+ if (result->Get("status_code") == 1)
+ return 1;
+ else {
+ Log(severity, "cli", "!!!!!!");
+ return 0;
+ }
+ }
+
+ try {
+ StringToCertificate(result->Get("cert"));
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "cli")
+ << "Could not write certificate file: " << DiagnosticInformation(ex, false);
+ return 1;
+ }
+
+ try {
+ StringToCertificate(result->Get("ca"));
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "cli")
+ << "Could not write CA file: " << DiagnosticInformation(ex, false);
return 1;
}
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 boost::shared_ptr<X509>& trustedcert,
- const String& ticket);
+ const String& ticket = String());
static String GetCertificateInformation(const boost::shared_ptr<X509>& certificate);
private:
configstageshandler.cpp createobjecthandler.cpp deleteobjecthandler.cpp
endpoint.cpp endpoint.thpp eventshandler.cpp eventqueue.cpp filterutility.cpp
httpchunkedencoding.cpp httpclientconnection.cpp httpserverconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
- httputility.cpp infohandler.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp
+ httputility.cpp infohandler.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp jsonrpcconnection-pki.cpp
messageorigin.cpp modifyobjecthandler.cpp statushandler.cpp objectqueryhandler.cpp templatequeryhandler.cpp
typequeryhandler.cpp url.cpp variablequeryhandler.cpp zone.cpp zone.thpp
)
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
+ * *
+ * 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 "remote/jsonrpcconnection.hpp"
+#include "remote/apilistener.hpp"
+#include "remote/apifunction.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/utility.hpp"
+#include "base/logger.hpp"
+#include "base/exception.hpp"
+#include "base/convert.hpp"
+#include <boost/thread/once.hpp>
+
+using namespace icinga;
+
+static Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler);
+
+Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ if (!params)
+ return Empty;
+
+ String certText = params->Get("cert_request");
+
+ boost::shared_ptr<X509> cert;
+
+ Dictionary::Ptr result = new Dictionary();
+
+ if (certText.IsEmpty())
+ cert = origin->FromClient->GetStream()->GetPeerCertificate();
+ else
+ cert = StringToCertificate(certText);
+
+ unsigned int n;
+ unsigned char digest[EVP_MAX_MD_SIZE];
+
+ if (!X509_digest(cert.get(), EVP_sha256(), digest, &n)) {
+ result->Set("status_code", 1);
+ result->Set("error", "Could not calculate fingerprint for the X509 certificate.");
+ return result;
+ }
+
+ char certFingerprint[EVP_MAX_MD_SIZE*2+1];
+ for (unsigned int i = 0; i < n; i++)
+ sprintf(certFingerprint + 2 * i, "%02x", digest[i]);
+
+ String requestDir = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests";
+ String requestPath = requestDir + "/" + certFingerprint + ".json";
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ String cacertfile = listener->GetCaPath();
+ boost::shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
+ result->Set("ca", CertificateToString(cacert));
+
+ if (Utility::PathExists(requestPath)) {
+ Dictionary::Ptr request = Utility::LoadJsonFile(requestPath);
+
+ String certResponse = request->Get("cert_response");
+
+ if (!certResponse.IsEmpty()) {
+ result->Set("cert", certResponse);
+ result->Set("status_code", 0);
+
+ return result;
+ }
+ }
+
+ boost::shared_ptr<X509> newcert;
+ EVP_PKEY *pubkey;
+ X509_NAME *subject;
+
+ if (!Utility::PathExists(GetIcingaCADir() + "/ca.key"))
+ goto delayed_request;
+
+ if (!origin->FromClient->IsAuthenticated()) {
+ String salt = listener->GetTicketSalt();
+
+ if (salt.IsEmpty())
+ goto delayed_request;
+
+ String ticket = params->Get("ticket");
+ String realTicket = PBKDF2_SHA1(origin->FromClient->GetIdentity(), salt, 50000);
+
+ if (ticket != realTicket) {
+ result->Set("status_code", 1);
+ result->Set("error", "Invalid ticket.");
+ return result;
+ }
+ }
+
+ pubkey = X509_get_pubkey(cert.get());
+ subject = X509_get_subject_name(cert.get());
+
+ newcert = CreateCertIcingaCA(pubkey, subject);
+ result->Set("cert", CertificateToString(newcert));
+
+ result->Set("status_code", 0);
+
+ return result;
+
+delayed_request:
+ Utility::MkDirP(requestDir, 0700);
+
+ Dictionary::Ptr request = new Dictionary();
+ request->Set("cert_request", CertificateToString(cert));
+ request->Set("ticket", params->Get("ticket"));
+
+ Utility::SaveJsonFile(requestPath, 0600, request);
+
+ result->Set("status_code", 2);
+ result->Set("error", "Certificate request is pending. Waiting for approval from the parent Icinga instance.");
+ return result;
+}
+
static Value SetLogPositionHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler);
-static Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
-REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler);
static boost::once_flag l_JsonRpcConnectionOnceFlag = BOOST_ONCE_INIT;
static Timer::Ptr l_JsonRpcConnectionTimeoutTimer;
return Empty;
}
-Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
-{
- if (!params)
- return Empty;
-
- Dictionary::Ptr result = new Dictionary();
-
- if (!origin->FromClient->IsAuthenticated()) {
- ApiListener::Ptr listener = ApiListener::GetInstance();
- String salt = listener->GetTicketSalt();
-
- if (salt.IsEmpty()) {
- result->Set("error", "Ticket salt is not configured.");
- return result;
- }
-
- String ticket = params->Get("ticket");
- String realTicket = PBKDF2_SHA1(origin->FromClient->GetIdentity(), salt, 50000);
-
- if (ticket != realTicket) {
- result->Set("error", "Invalid ticket.");
- return result;
- }
- }
-
- boost::shared_ptr<X509> cert = origin->FromClient->GetStream()->GetPeerCertificate();
-
- EVP_PKEY *pubkey = X509_get_pubkey(cert.get());
- X509_NAME *subject = X509_get_subject_name(cert.get());
-
- boost::shared_ptr<X509> newcert = CreateCertIcingaCA(pubkey, subject);
- result->Set("cert", CertificateToString(newcert));
-
- String cacertfile = GetIcingaCADir() + "/ca.crt";
- boost::shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
- result->Set("ca", CertificateToString(cacert));
-
- return result;
-}
-
void JsonRpcConnection::CheckLiveness(void)
{
if (m_Seen < Utility::GetTime() - 60 && (!m_Endpoint || !m_Endpoint->GetSyncing())) {