int PkiUtility::SaveCert(const String& host, const String& port, const String& keyfile, const String& certfile, const String& trustedfile) { TcpSocket::Ptr client = new TcpSocket(); client->Connect(host, port); boost::shared_ptr<SSL_CTX> sslContext = MakeSSLContext(certfile, keyfile); TlsStream::Ptr stream = new TlsStream(client, RoleClient, sslContext); try { stream->Handshake(); } catch (...) { } boost::shared_ptr<X509> 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::SaveCert(const String& host, const String& port, const String& keyfile, const String& certfile, const String& trustedfile) { TcpSocket::Ptr client = new TcpSocket(); try { client->Connect(host, port); } catch (const std::exception& ex) { Log(LogCritical, "cli") << "Cannot connect to host '" << host << "' on port '" << port << "'"; Log(LogDebug, "cli") << "Cannot connect to host '" << host << "' on port '" << port << "':\n" << DiagnosticInformation(ex); return 1; } boost::shared_ptr<SSL_CTX> sslContext; try { sslContext = MakeSSLContext(certfile, keyfile); } catch (const std::exception& ex) { Log(LogCritical, "cli") << "Cannot make SSL context for cert path: '" << certfile << "' key path: '" << keyfile << "'."; Log(LogDebug, "cli") << "Cannot make SSL context for cert path: '" << certfile << "' key path: '" << keyfile << "':\n" << DiagnosticInformation(ex); return 1; } TlsStream::Ptr stream = new TlsStream(client, String(), RoleClient, sslContext); try { stream->Handshake(); } catch (...) { } boost::shared_ptr<X509> cert = stream->GetPeerCertificate(); if (!cert) { Log(LogCritical, "cli", "Peer did not present a valid certificate."); return 1; } 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; }
boost::shared_ptr<X509> PkiUtility::FetchCert(const String& host, const String& port) { TcpSocket::Ptr client = new TcpSocket(); try { client->Connect(host, port); } catch (const std::exception& ex) { Log(LogCritical, "pki") << "Cannot connect to host '" << host << "' on port '" << port << "'"; Log(LogDebug, "pki") << "Cannot connect to host '" << host << "' on port '" << port << "':\n" << DiagnosticInformation(ex); return boost::shared_ptr<X509>(); } boost::shared_ptr<SSL_CTX> sslContext; try { sslContext = MakeSSLContext(); } catch (const std::exception& ex) { Log(LogCritical, "pki") << "Cannot make SSL context."; Log(LogDebug, "pki") << "Cannot make SSL context:\n" << DiagnosticInformation(ex); return boost::shared_ptr<X509>(); } TlsStream::Ptr stream = new TlsStream(client, host, RoleClient, sslContext); try { stream->Handshake(); } catch (...) { } return stream->GetPeerCertificate(); }
int PkiUtility::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) { TcpSocket::Ptr client = new TcpSocket(); try { client->Connect(host, port); } catch (const std::exception& ex) { Log(LogCritical, "cli") << "Cannot connect to host '" << host << "' on port '" << port << "'"; Log(LogDebug, "cli") << "Cannot connect to host '" << host << "' on port '" << port << "':\n" << DiagnosticInformation(ex); return 1; } boost::shared_ptr<SSL_CTX> sslContext; try { sslContext = MakeSSLContext(certfile, keyfile); } catch (const std::exception& ex) { Log(LogCritical, "cli") << "Cannot make SSL context for cert path: '" << certfile << "' key path: '" << keyfile << "' ca path: '" << cafile << "'."; Log(LogDebug, "cli") << "Cannot make SSL context for cert path: '" << certfile << "' key path: '" << keyfile << "' ca path: '" << cafile << "':\n" << DiagnosticInformation(ex); return 1; } TlsStream::Ptr stream = new TlsStream(client, host, RoleClient, sslContext); try { stream->Handshake(); } catch (const std::exception&) { Log(LogCritical, "cli", "Client TLS handshake failed."); return 1; } boost::shared_ptr<X509> peerCert = stream->GetPeerCertificate(); if (X509_cmp(peerCert.get(), trustedCert.get())) { Log(LogCritical, "cli", "Peer certificate does not match trusted certificate."); return 1; } Dictionary::Ptr request = new Dictionary(); String msgid = Utility::NewUniqueID(); request->Set("jsonrpc", "2.0"); request->Set("id", msgid); request->Set("method", "pki::RequestCertificate"); Dictionary::Ptr params = new Dictionary(); params->Set("ticket", String(ticket)); request->Set("params", params); JsonRpc::SendMessage(stream, request); String jsonString; Dictionary::Ptr response; StreamReadContext src; for (;;) { StreamReadStatus srs = JsonRpc::ReadMessage(stream, &jsonString, src); if (srs == StatusEof) break; if (srs != StatusNewItem) continue; response = JsonRpc::DecodeMessage(jsonString); if (response && response->Contains("error")) { Log(LogCritical, "cli", "Could not fetch valid response. Please check the master log (notice or debug)."); #ifdef I2_DEBUG /* we shouldn't expose master errors to the user in production environments */ Log(LogCritical, "cli", response->Get("error")); #endif /* I2_DEBUG */ return 1; } if (response && (response->Get("id") != msgid)) continue; break; } if (!response) { Log(LogCritical, "cli", "Could not fetch valid response. Please check the master log."); return 1; } 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 '" << cafile << "'."; return 0; }
/** * Processes a new client connection. * * @param client The new client. */ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const String& hostname, ConnectionRole role) { CONTEXT("Handling new API client connection"); TlsStream::Ptr tlsStream; { ObjectLock olock(this); try { tlsStream = new TlsStream(client, hostname, role, m_SSLContext); } catch (const std::exception&) { Log(LogCritical, "ApiListener", "Cannot create TLS stream from client connection."); return; } } try { tlsStream->Handshake(); } catch (const std::exception& ex) { Log(LogCritical, "ApiListener", "Client TLS handshake failed"); return; } boost::shared_ptr<X509> cert = tlsStream->GetPeerCertificate(); String identity; Endpoint::Ptr endpoint; bool verify_ok = false; if (cert) { try { identity = GetCertificateCN(cert); } catch (const std::exception&) { Log(LogCritical, "ApiListener") << "Cannot get certificate common name from cert path: '" << GetCertPath() << "'."; return; } verify_ok = tlsStream->IsVerifyOK(); Log(LogInformation, "ApiListener") << "New client connection for identity '" << identity << "'" << (verify_ok ? "" : " (unauthenticated)"); if (verify_ok) endpoint = Endpoint::GetByName(identity); } else { Log(LogInformation, "ApiListener") << "New client connection (no client certificate)"; } bool need_sync = false; if (endpoint) need_sync = !endpoint->IsConnected(); ClientType ctype; if (role == RoleClient) { Dictionary::Ptr message = new Dictionary(); message->Set("jsonrpc", "2.0"); message->Set("method", "icinga::Hello"); message->Set("params", new Dictionary()); JsonRpc::SendMessage(tlsStream, message); ctype = ClientJsonRpc; } else { tlsStream->WaitForData(5); if (!tlsStream->IsDataAvailable()) { Log(LogWarning, "ApiListener", "No data received on new API connection."); return; } char firstByte; tlsStream->Peek(&firstByte, 1, false); if (firstByte >= '0' && firstByte <= '9') ctype = ClientJsonRpc; else ctype = ClientHttp; } if (ctype == ClientJsonRpc) { Log(LogNotice, "ApiListener", "New JSON-RPC client"); JsonRpcConnection::Ptr aclient = new JsonRpcConnection(identity, verify_ok, tlsStream, role); aclient->Start(); if (endpoint) { endpoint->AddClient(aclient); if (need_sync) { { ObjectLock olock(endpoint); endpoint->SetSyncing(true); } Log(LogInformation, "ApiListener") << "Sending updates for endpoint '" << endpoint->GetName() << "'."; /* sync zone file config */ SendConfigUpdate(aclient); /* sync runtime config */ SendRuntimeConfigObjects(aclient); Log(LogInformation, "ApiListener") << "Finished sending updates for endpoint '" << endpoint->GetName() << "'."; ReplayLog(aclient); } } else AddAnonymousClient(aclient); } else { Log(LogNotice, "ApiListener", "New HTTP client"); HttpServerConnection::Ptr aclient = new HttpServerConnection(identity, verify_ok, tlsStream); aclient->Start(); AddHttpClient(aclient); } }
/** * Processes a new client connection. * * @param client The new client. */ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const String& hostname, ConnectionRole role) { CONTEXT("Handling new API client connection"); TlsStream::Ptr tlsStream; { ObjectLock olock(this); try { tlsStream = new TlsStream(client, hostname, role, m_SSLContext); } catch (const std::exception&) { Log(LogCritical, "ApiListener", "Cannot create TLS stream from client connection."); return; } } try { tlsStream->Handshake(); } catch (const std::exception& ex) { Log(LogCritical, "ApiListener", "Client TLS handshake failed"); return; } boost::shared_ptr<X509> cert = tlsStream->GetPeerCertificate(); String identity; Endpoint::Ptr endpoint; bool verify_ok = false; if (cert) { try { identity = GetCertificateCN(cert); } catch (const std::exception&) { Log(LogCritical, "ApiListener") << "Cannot get certificate common name from cert path: '" << GetCertPath() << "'."; return; } verify_ok = tlsStream->IsVerifyOK(); if (!hostname.IsEmpty()) { if (identity != hostname) { Log(LogWarning, "ApiListener") << "Unexpected certificate common name while connecting to endpoint '" << hostname << "': got '" << identity << "'"; return; } else if (!verify_ok) { Log(LogWarning, "ApiListener") << "Peer certificate for endpoint '" << hostname << "' is not signed by the certificate authority."; return; } } Log(LogInformation, "ApiListener") << "New client connection for identity '" << identity << "'" << (verify_ok ? "" : " (client certificate not signed by CA)"); if (verify_ok) endpoint = Endpoint::GetByName(identity); } else { Log(LogInformation, "ApiListener") << "New client connection (no client certificate)"; } ClientType ctype; if (role == RoleClient) { Dictionary::Ptr message = new Dictionary(); message->Set("jsonrpc", "2.0"); message->Set("method", "icinga::Hello"); message->Set("params", new Dictionary()); JsonRpc::SendMessage(tlsStream, message); ctype = ClientJsonRpc; } else { tlsStream->WaitForData(5); if (!tlsStream->IsDataAvailable()) { Log(LogWarning, "ApiListener", "No data received on new API connection."); return; } char firstByte; tlsStream->Peek(&firstByte, 1, false); if (firstByte >= '0' && firstByte <= '9') ctype = ClientJsonRpc; else ctype = ClientHttp; } if (ctype == ClientJsonRpc) { Log(LogNotice, "ApiListener", "New JSON-RPC client"); JsonRpcConnection::Ptr aclient = new JsonRpcConnection(identity, verify_ok, tlsStream, role); aclient->Start(); if (endpoint) { bool needSync = !endpoint->GetConnected(); endpoint->AddClient(aclient); m_SyncQueue.Enqueue(boost::bind(&ApiListener::SyncClient, this, aclient, endpoint, needSync)); } else AddAnonymousClient(aclient); } else { Log(LogNotice, "ApiListener", "New HTTP client"); HttpServerConnection::Ptr aclient = new HttpServerConnection(identity, verify_ok, tlsStream); aclient->Start(); AddHttpClient(aclient); } }
/** * Processes a new client connection. * * @param client The new client. */ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const String& hostname, ConnectionRole role) { CONTEXT("Handling new API client connection"); String conninfo; if (role == RoleClient) conninfo = "to"; else conninfo = "from"; conninfo += " " + client->GetPeerAddress(); TlsStream::Ptr tlsStream; { ObjectLock olock(this); try { tlsStream = new TlsStream(client, hostname, role, m_SSLContext); } catch (const std::exception&) { Log(LogCritical, "ApiListener") << "Cannot create TLS stream from client connection (" << conninfo << ")"; return; } } try { tlsStream->Handshake(); } catch (const std::exception&) { Log(LogCritical, "ApiListener") << "Client TLS handshake failed (" << conninfo << ")"; tlsStream->Close(); return; } std::shared_ptr<X509> cert = tlsStream->GetPeerCertificate(); String identity; Endpoint::Ptr endpoint; bool verify_ok = false; if (cert) { try { identity = GetCertificateCN(cert); } catch (const std::exception&) { Log(LogCritical, "ApiListener") << "Cannot get certificate common name from cert path: '" << GetDefaultCertPath() << "'."; tlsStream->Close(); return; } verify_ok = tlsStream->IsVerifyOK(); if (!hostname.IsEmpty()) { if (identity != hostname) { Log(LogWarning, "ApiListener") << "Unexpected certificate common name while connecting to endpoint '" << hostname << "': got '" << identity << "'"; tlsStream->Close(); return; } else if (!verify_ok) { Log(LogWarning, "ApiListener") << "Certificate validation failed for endpoint '" << hostname << "': " << tlsStream->GetVerifyError(); } } if (verify_ok) endpoint = Endpoint::GetByName(identity); { Log log(LogInformation, "ApiListener"); log << "New client connection for identity '" << identity << "' " << conninfo; if (!verify_ok) log << " (certificate validation failed: " << tlsStream->GetVerifyError() << ")"; else if (!endpoint) log << " (no Endpoint object found for identity)"; } } else { Log(LogInformation, "ApiListener") << "New client connection " << conninfo << " (no client certificate)"; } ClientType ctype; if (role == RoleClient) { Dictionary::Ptr message = new Dictionary({ { "jsonrpc", "2.0" }, { "method", "icinga::Hello" }, { "params", new Dictionary() } }); JsonRpc::SendMessage(tlsStream, message); ctype = ClientJsonRpc; } else { tlsStream->WaitForData(10); if (!tlsStream->IsDataAvailable()) { if (identity.IsEmpty()) Log(LogInformation, "ApiListener") << "No data received on new API connection. " << "Ensure that the remote endpoints are properly configured in a cluster setup."; else Log(LogWarning, "ApiListener") << "No data received on new API connection for identity '" << identity << "'. " << "Ensure that the remote endpoints are properly configured in a cluster setup."; tlsStream->Close(); return; } char firstByte; tlsStream->Peek(&firstByte, 1, false); if (firstByte >= '0' && firstByte <= '9') ctype = ClientJsonRpc; else ctype = ClientHttp; } if (ctype == ClientJsonRpc) { Log(LogNotice, "ApiListener", "New JSON-RPC client"); JsonRpcConnection::Ptr aclient = new JsonRpcConnection(identity, verify_ok, tlsStream, role); aclient->Start(); if (endpoint) { bool needSync = !endpoint->GetConnected(); endpoint->AddClient(aclient); m_SyncQueue.Enqueue(std::bind(&ApiListener::SyncClient, this, aclient, endpoint, needSync)); } else { if (!AddAnonymousClient(aclient)) { Log(LogNotice, "ApiListener", "Ignoring anonymous JSON-RPC connection. Max connections exceeded."); aclient->Disconnect(); } } } else { Log(LogNotice, "ApiListener", "New HTTP client"); HttpServerConnection::Ptr aclient = new HttpServerConnection(identity, verify_ok, tlsStream); aclient->Start(); AddHttpClient(aclient); } }
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<TcpSocket>(); try { client->Connect(host, port); } catch (const std::exception& ex) { Log(LogCritical, "cli") << "Cannot connect to host '" << host << "' on port '" << port << "'"; Log(LogDebug, "cli") << "Cannot connect to host '" << host << "' on port '" << port << "':\n" << DiagnosticInformation(ex); return 1; } shared_ptr<SSL_CTX> sslContext; try { sslContext = MakeSSLContext(certfile, keyfile); } catch (const std::exception& ex) { Log(LogCritical, "cli") << "Cannot make SSL context for cert path: '" << certfile << "' key path: '" << keyfile << "' ca path: '" << cafile << "'."; return 1; } TlsStream::Ptr stream = make_shared<TlsStream>(client, RoleClient, sslContext); try { stream->Handshake(); } catch (const std::exception&) { Log(LogCritical, "cli", "Client TLS handshake failed."); return 1; } shared_ptr<X509> peerCert = stream->GetPeerCertificate(); shared_ptr<X509> trustedCert; try { trustedCert = GetX509Certificate(trustedfile); } catch (const std::exception&) { Log(LogCritical, "cli") << "Cannot get trusted from cert path: '" << trustedfile << "'."; return 1; } if (CertificateToString(peerCert) != CertificateToString(trustedCert)) { Log(LogCritical, "cli", "Peer certificate does not match trusted certificate."); return 1; } Dictionary::Ptr request = make_shared<Dictionary>(); String msgid = Utility::NewUniqueID(); request->Set("jsonrpc", "2.0"); request->Set("id", msgid); request->Set("method", "pki::RequestCertificate"); Dictionary::Ptr params = make_shared<Dictionary>(); 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; } if (!response->Contains("result")) { Log(LogCritical, "cli", "Request certificate did not return a valid result. Check the master log for details!"); return 1; } 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 '" << cafile << "'."; return 0; }