cert_path = SysconfDir + "/icinga2/pki/" + NodeName + ".crt"
key_path = SysconfDir + "/icinga2/pki/" + NodeName + ".key"
ca_path = SysconfDir + "/icinga2/pki/ca.crt"
+
+ //ticket_salt = "<secret key>"
}
*/
String Application::GetZonesDir(void)
{
- return ScriptVariable::Get("ZonesDir");
+ return ScriptVariable::Get("ZonesDir", &Empty);
}
/**
*/
String Application::GetPkgDataDir(void)
{
- return ScriptVariable::Get("PkgDataDir");
+ String defaultValue = "";
+ return ScriptVariable::Get("PkgDataDir", &Empty);
}
/**
*/
String Application::GetIncludeConfDir(void)
{
- return ScriptVariable::Get("IncludeConfDir");
+ return ScriptVariable::Get("IncludeConfDir", &Empty);
}
/**
*/
String Application::GetStatePath(void)
{
- return ScriptVariable::Get("StatePath");
+ return ScriptVariable::Get("StatePath", &Empty);
}
/**
*/
String Application::GetObjectsPath(void)
{
- return ScriptVariable::Get("ObjectsPath");
+ return ScriptVariable::Get("ObjectsPath", &Empty);
}
/**
*/
String Application::GetPidPath(void)
{
- return ScriptVariable::Get("PidPath");
+ return ScriptVariable::Get("PidPath", &Empty);
}
/**
return m_Data;
}
-Value ScriptVariable::Get(const String& name)
+Value ScriptVariable::Get(const String& name, const Value *defaultValue)
{
ScriptVariable::Ptr sv = GetByName(name);
- if (!sv)
+ if (!sv) {
+ if (defaultValue)
+ return *defaultValue;
+
BOOST_THROW_EXCEPTION(std::invalid_argument("Tried to access undefined script variable '" + name + "'"));
+ }
return sv->GetData();
}
static ScriptVariable::Ptr GetByName(const String& name);
static void Unregister(const String& name);
- static Value Get(const String& name);
+ static Value Get(const String& name, const Value *defaultValue = NULL);
static ScriptVariable::Ptr Set(const String& name, const Value& value, bool overwrite = true, bool make_const = false);
private:
std::ostringstream msgbuf;
char errbuf[120];
- m_Socket->Poll(true, false);
+ bool want_read;
+
+ {
+ boost::mutex::scoped_lock lock(m_SSLLock);
+ want_read = SSL_want_read(m_SSL.get());
+ }
+
+ if (want_read)
+ m_Socket->Poll(true, false);
boost::mutex::scoped_lock alock(m_IOActionLock);
std::ostringstream msgbuf;
char errbuf[120];
- m_Socket->Poll(false, true);
+ bool want_write;
+
+ {
+ boost::mutex::scoped_lock lock(m_SSLLock);
+ want_write = SSL_want_write(m_SSL.get());
+ }
+
+ if (want_write)
+ m_Socket->Poll(false, true);
boost::mutex::scoped_lock alock(m_IOActionLock);
#include "base/convert.hpp"
#include "base/logger_fwd.hpp"
#include "base/context.hpp"
+#include "base/application.hpp"
namespace icinga
{
<< errinfo_openssl_error(ERR_peek_error()));
}
- if (!SSL_CTX_load_verify_locations(sslContext.get(), cakey.CStr(), NULL)) {
- msgbuf << "Error loading and verifying locations in ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- Log(LogCritical, "SSL", msgbuf.str());
- BOOST_THROW_EXCEPTION(openssl_error()
- << boost::errinfo_api_function("SSL_CTX_load_verify_locations")
- << errinfo_openssl_error(ERR_peek_error())
- << boost::errinfo_file_name(cakey));
+ if (!cakey.IsEmpty()) {
+ if (!SSL_CTX_load_verify_locations(sslContext.get(), cakey.CStr(), NULL)) {
+ msgbuf << "Error loading and verifying locations in ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+ Log(LogCritical, "SSL", msgbuf.str());
+ BOOST_THROW_EXCEPTION(openssl_error()
+ << boost::errinfo_api_function("SSL_CTX_load_verify_locations")
+ << errinfo_openssl_error(ERR_peek_error())
+ << boost::errinfo_file_name(cakey));
+ }
+
+ STACK_OF(X509_NAME) *cert_names;
+
+ cert_names = SSL_load_client_CA_file(cakey.CStr());
+ if (cert_names == NULL) {
+ msgbuf << "Error loading client ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+ Log(LogCritical, "SSL", msgbuf.str());
+ BOOST_THROW_EXCEPTION(openssl_error()
+ << boost::errinfo_api_function("SSL_load_client_CA_file")
+ << errinfo_openssl_error(ERR_peek_error())
+ << boost::errinfo_file_name(cakey));
+ }
+
+ SSL_CTX_set_client_CA_list(sslContext.get(), cert_names);
}
- STACK_OF(X509_NAME) *cert_names;
-
- cert_names = SSL_load_client_CA_file(cakey.CStr());
- if (cert_names == NULL) {
- msgbuf << "Error loading client ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- Log(LogCritical, "SSL", msgbuf.str());
- BOOST_THROW_EXCEPTION(openssl_error()
- << boost::errinfo_api_function("SSL_load_client_CA_file")
- << errinfo_openssl_error(ERR_peek_error())
- << boost::errinfo_file_name(cakey));
- }
-
- SSL_CTX_set_client_CA_list(sslContext.get(), cert_names);
-
return sslContext;
}
X509_NAME *subject = X509_NAME_new();
X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC, (unsigned char *)cn.CStr(), -1, -1, 0);
- X509 *cert = CreateCert(key, subject, subject, key, ca);
+ shared_ptr<X509> cert = CreateCert(key, subject, subject, key, ca);
X509_NAME_free(subject);
bio = BIO_new(BIO_s_file());
BIO_write_filename(bio, const_cast<char *>(certfile.CStr()));
- PEM_write_bio_X509(bio, cert);
+ PEM_write_bio_X509(bio, cert.get());
BIO_free(bio);
-
- X509_free(cert);
}
if (!csrfile.IsEmpty()) {
return 1;
}
-X509 *CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca, const String& serialfile)
+shared_ptr<X509> CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca, const String& serialfile)
{
X509 *cert = X509_new();
ASN1_INTEGER_set(X509_get_serialNumber(cert), 1);
X509_sign(cert, cakey, EVP_sha1());
- return cert;
+ return shared_ptr<X509>(cert, X509_free);
+}
+
+String GetIcingaCADir(void)
+{
+ return Application::GetLocalStateDir() + "/lib/icinga2/ca";
+}
+
+shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject)
+{
+ std::stringstream msgbuf;
+ char errbuf[120];
+
+ String cadir = GetIcingaCADir();
+
+ String cakeyfile = cadir + "/ca.key";
+
+ RSA *rsa;
+
+ BIO *cakeybio = BIO_new_file(const_cast<char *>(cakeyfile.CStr()), "r");
+
+ if (!cakeybio) {
+ msgbuf << "Could not open CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+ Log(LogCritical, "SSL", msgbuf.str());
+ return shared_ptr<X509>();
+ }
+
+ rsa = PEM_read_bio_RSAPrivateKey(cakeybio, NULL, NULL, NULL);
+
+ if (!rsa) {
+ msgbuf << "Could not read RSA key from CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+ Log(LogCritical, "SSL", msgbuf.str());
+ return shared_ptr<X509>();
+ }
+
+ BIO_free(cakeybio);
+
+ String cacertfile = cadir + "/ca.crt";
+
+ shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
+
+ EVP_PKEY *privkey = EVP_PKEY_new();
+ EVP_PKEY_assign_RSA(privkey, rsa);
+
+ return CreateCert(pubkey, subject, X509_get_subject_name(cacert.get()), privkey, false, cadir + "/serial.txt");
+}
+
+String CertificateToString(const shared_ptr<X509>& cert)
+{
+ BIO *mem = BIO_new(BIO_s_mem());
+ PEM_write_bio_X509(mem, cert.get());
+
+ char *data;
+ long len = BIO_get_mem_data(mem, &data);
+
+ String result = String(data, data + len);
+
+ BIO_free(mem);
+
+ return result;
+}
+
+String PBKDF2_SHA512(const String& password, const String& salt, int iterations)
+{
+ unsigned char digest[SHA512_DIGEST_LENGTH];
+ PKCS5_PBKDF2_HMAC(password.CStr(), password.GetLength(), reinterpret_cast<const unsigned char *>(salt.CStr()), salt.GetLength(),
+ iterations, EVP_sha512(), sizeof(digest), digest);
+
+ char output[SHA512_DIGEST_LENGTH*2+1];
+ for (int i = 0; i < 32; i++)
+ sprintf(output + 2 * i, "%02x", digest[i]);
+
+ return output;
}
String SHA256(const String& s)
<< errinfo_openssl_error(ERR_peek_error()));
}
- int i;
char output[SHA256_DIGEST_LENGTH*2+1];
- for (i = 0; i < 32; i++)
+ for (int i = 0; i < 32; i++)
sprintf(output + 2 * i, "%02x", digest[i]);
return output;
{
void I2_BASE_API InitializeOpenSSL(void);
-shared_ptr<SSL_CTX> I2_BASE_API MakeSSLContext(const String& pubkey, const String& privkey, const String& cakey);
+shared_ptr<SSL_CTX> I2_BASE_API MakeSSLContext(const String& pubkey, const String& privkey, const String& cakey = String());
void I2_BASE_API AddCRLToSSLContext(const shared_ptr<SSL_CTX>& context, const String& crlPath);
String I2_BASE_API GetCertificateCN(const shared_ptr<X509>& certificate);
shared_ptr<X509> I2_BASE_API GetX509Certificate(const String& pemfile);
int I2_BASE_API MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile = String(), const String& certfile = String(), bool ca = false);
-X509 * I2_BASE_API CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca, const String& serialfile = String());
+shared_ptr<X509> I2_BASE_API CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca, const String& serialfile = String());
+String I2_BASE_API GetIcingaCADir(void);
+String I2_BASE_API CertificateToString(const shared_ptr<X509>& cert);
+shared_ptr<X509> I2_BASE_API CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject);
+String I2_BASE_API PBKDF2_SHA512(const String& password, const String& salt, int iterations);
String I2_BASE_API SHA256(const String& s);
class I2_BASE_API openssl_error : virtual public std::exception, virtual public boost::exception { };
set(cli_SOURCES
featureenablecommand.cpp featuredisablecommand.cpp featurelistcommand.cpp
objectlistcommand.cpp
- pkinewcacommand.cpp pkinewcertcommand.cpp pkisigncsrcommand.cpp
+ pkinewcacommand.cpp pkinewcertcommand.cpp pkisigncsrcommand.cpp pkirequestcommand.cpp pkiticketcommand.cpp
daemoncommand.cpp
)
add_library(cli SHARED ${cli_SOURCES})
-target_link_libraries(cli ${Boost_LIBRARIES} base config)
+target_link_libraries(cli ${Boost_LIBRARIES} base config remote)
set_target_properties (
cli PROPERTIES
BIO_free(csrbio);
- String cadir = Application::GetLocalStateDir() + "/lib/icinga2/ca";
+ shared_ptr<X509> cert = CreateCertIcingaCA(X509_REQ_get_pubkey(req), X509_REQ_get_subject_name(req));
- String cakeyfile = cadir + "/ca.key";
+ X509_REQ_free(req);
- RSA *rsa;
-
- BIO *cakeybio = BIO_new_file(const_cast<char *>(cakeyfile.CStr()), "r");
-
- if (!cakeybio) {
- msgbuf << "Could not open CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- Log(LogCritical, "SSL", msgbuf.str());
- return 1;
- }
-
- rsa = PEM_read_bio_RSAPrivateKey(cakeybio, NULL, NULL, NULL);
-
- if (!rsa) {
- msgbuf << "Could not read RSA key from CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- Log(LogCritical, "SSL", msgbuf.str());
- return 1;
- }
-
- BIO_free(cakeybio);
-
- String cacertfile = cadir + "/ca.crt";
-
- BIO *cacertbio = BIO_new_file(const_cast<char *>(cacertfile.CStr()), "r");
-
- if (!cacertbio) {
- msgbuf << "Could not open CA certificate file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- Log(LogCritical, "SSL", msgbuf.str());
- return 1;
- }
-
- X509 *cacert = PEM_read_bio_X509(cacertbio, NULL, NULL, NULL);
-
- if (!cacert) {
- msgbuf << "Could not read X509 certificate from CA certificate file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- Log(LogCritical, "SSL", msgbuf.str());
- return 1;
- }
-
- BIO_free(cacertbio);
-
- EVP_PKEY *privkey = EVP_PKEY_new();
- EVP_PKEY_assign_RSA(privkey, rsa);
-
- EVP_PKEY *pubkey = X509_REQ_get_pubkey(req);
-
- X509 *cert = CreateCert(pubkey, X509_REQ_get_subject_name(req), X509_get_subject_name(cacert), privkey, false);
-
- EVP_PKEY_free(pubkey);
- X509_free(cacert);
-
- BIO *certbio = BIO_new_fp(stdout, BIO_NOCLOSE);
-
- if (!PEM_write_bio_X509(certbio, cert)) {
- BIO_free(certbio);
-
- msgbuf << "Could not write X509 certificate: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- Log(LogCritical, "SSL", msgbuf.str());
- return 1;
- }
-
- X509_free(cert);
-
- BIO_free(certbio);
+ std::cout << CertificateToString(cert);
return 0;
}
static Value SetLogPositionHandler(const MessageOrigin& origin, const Dictionary::Ptr& params);
REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler);
+static Value RequestCertificateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params);
+REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler);
-ApiClient::ApiClient(const String& identity, bool authenticated, const Stream::Ptr& stream, ConnectionRole role)
+ApiClient::ApiClient(const String& identity, bool authenticated, const TlsStream::Ptr& stream, ConnectionRole role)
: m_Identity(identity), m_Authenticated(authenticated), m_Stream(stream), m_Role(role), m_Seen(Utility::GetTime())
{
if (authenticated)
return m_Endpoint;
}
-Stream::Ptr ApiClient::GetStream(void) const
+TlsStream::Ptr ApiClient::GetStream(void) const
{
return m_Stream;
}
return Empty;
}
+
+Value RequestCertificateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params)
+{
+ if (!params)
+ return Empty;
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+ String salt = listener->GetTicketSalt();
+
+ Dictionary::Ptr result = make_shared<Dictionary>();
+
+ if (salt.IsEmpty()) {
+ result->Set("error", "Ticket salt is not configured.");
+ return result;
+ }
+
+ String ticket = params->Get("ticket");
+ String realTicket = PBKDF2_SHA512(origin.FromClient->GetIdentity(), salt, 50000);
+
+ if (ticket != realTicket) {
+ result->Set("error", "Invalid ticket.");
+ return result;
+ }
+
+ 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());
+
+ shared_ptr<X509> newcert = CreateCertIcingaCA(pubkey, subject);
+ result->Set("cert", CertificateToString(newcert));
+
+ String cacertfile = GetIcingaCADir() + "/ca.crt";
+ shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
+ result->Set("ca", CertificateToString(cacert));
+
+ return result;
+}
#define APICLIENT_H
#include "remote/endpoint.hpp"
-#include "base/stream.hpp"
+#include "base/tlsstream.hpp"
#include "base/timer.hpp"
#include "base/workqueue.hpp"
#include "remote/i2-remote.hpp"
public:
DECLARE_PTR_TYPEDEFS(ApiClient);
- ApiClient(const String& identity, bool authenticated, const Stream::Ptr& stream, ConnectionRole role);
+ ApiClient(const String& identity, bool authenticated, const TlsStream::Ptr& stream, ConnectionRole role);
void Start(void);
String GetIdentity(void) const;
bool IsAuthenticated(void) const;
Endpoint::Ptr GetEndpoint(void) const;
- Stream::Ptr GetStream(void) const;
+ TlsStream::Ptr GetStream(void) const;
ConnectionRole GetRole(void) const;
void Disconnect(void);
String m_Identity;
bool m_Authenticated;
Endpoint::Ptr m_Endpoint;
- Stream::Ptr m_Stream;
+ TlsStream::Ptr m_Stream;
ConnectionRole m_Role;
double m_Seen;
[config] bool accept_config;
+ [config] String ticket_salt;
+
[state] double log_message_timestamp;
String identity;
%attribute %string "bind_host",
%attribute %string "bind_port",
- %attribute %number "accept_config"
+ %attribute %number "accept_config",
+
+ %attribute %string "ticket_salt"
}
%type Endpoint {