#include "base/logger.hpp"
#include "base/application.hpp"
#include "base/tlsutility.hpp"
+#include "base/json.hpp"
using namespace icinga;
+namespace po = boost::program_options;
REGISTER_CLICOMMAND("ca/list", CAListCommand);
return "lists all certificate signing requests";
}
-void CAListCommand::PrintRequest(const String& requestFile)
+void CAListCommand::InitParameters(boost::program_options::options_description& visibleDesc,
+ boost::program_options::options_description& hiddenDesc) const
+{
+ visibleDesc.add_options()
+ ("json", "encode output as JSON")
+ ;
+}
+static void CollectRequestHandler(const Dictionary::Ptr& requests, const String& requestFile)
{
Dictionary::Ptr request = Utility::LoadJsonFile(requestFile);
if (!request)
return;
+ Dictionary::Ptr result = new Dictionary();
+
String fingerprint = Utility::BaseName(requestFile);
fingerprint = fingerprint.SubStr(0, fingerprint.GetLength() - 5);
- std::cout << "***\n";
- std::cout << "Fingerprint: " << fingerprint << "\n";
-
String certRequestText = request->Get("cert_request");
+ result->Set("cert_request", certRequestText);
+
+ Value vcertResponseText;
+
+ if (request->Get("cert_response", &vcertResponseText)) {
+ String certResponseText = vcertResponseText;
+ result->Set("cert_response", certResponseText);
+ }
boost::shared_ptr<X509> certRequest = StringToCertificate(certRequestText);
- String cn = GetCertificateCN(certRequest);
+ time_t now;
+ time(&now);
+ ASN1_TIME *tm = ASN1_TIME_adj(NULL, now, 0, 0);
- String certResponseText = request->Get("cert_response");
+ int day, sec;
+ ASN1_TIME_diff(&day, &sec, tm, X509_get_notBefore(certRequest.get()));
- if (!certResponseText.IsEmpty()) {
- boost::shared_ptr<X509> certResponse = StringToCertificate(certResponseText);
- }
+ result->Set("timestamp", static_cast<double>(now) + day * 24 * 60 * 60 + sec);
+
+ BIO *out = BIO_new(BIO_s_mem());
+ X509_NAME_print_ex(out, X509_get_subject_name(certRequest.get()), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB);
- std::cout << "CN: " << cn << "\n";
- std::cout << "Certificate (request): " << certRequestText << "\n";
- std::cout << "Certificate (response): " << certResponseText << "\n";
+ char *data;
+ long length;
+ length = BIO_get_mem_data(out, &data);
+
+ result->Set("subject", String(data, data + length));
+ BIO_free(out);
+
+ requests->Set(fingerprint, result);
}
/**
*/
int CAListCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
{
- Utility::Glob(Application::GetLocalStateDir() + "/lib/icinga2/pki-requests/*.json", &CAListCommand::PrintRequest, GlobFile);
+ Dictionary::Ptr requests = new Dictionary();
+
+ String requestDir = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests";
+
+ if (Utility::PathExists(requestDir))
+ Utility::Glob(requestDir + "/*.json", boost::bind(&CollectRequestHandler, requests, _1), GlobFile);
+
+ if (vm.count("json"))
+ std::cout << JsonEncode(requests);
+ else {
+ ObjectLock olock(requests);
+
+ std::cout << "Fingerprint | Timestamp | Signed | Subject\n";
+ std::cout << "-----------------------------------------------------------------|---------------------|--------|--------\n";
+
+ for (auto& kv : requests) {
+ Dictionary::Ptr request = kv.second;
+
+ std::cout << kv.first
+ << " | "
+ << Utility::FormatDateTime("%Y/%m/%d %H:%M:%S", request->Get("timestamp"))
+ << " | "
+ << (request->Contains("cert_response") ? "*" : " ") << " "
+ << " | "
+ << request->Get("subject")
+ << "\n";
+ }
+ }
return 0;
}
{
String requestFile = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests/" + ap[0] + ".json";
+ if (!Utility::PathExists(requestFile)) {
+ Log(LogCritical, "cli")
+ << "No request exists for fingerprint '" << ap[0] << "'.";
+ return 1;
+ }
+
Dictionary::Ptr request = Utility::LoadJsonFile(requestFile);
if (!request)
boost::shared_ptr<X509> certRequest = StringToCertificate(certRequestText);
+ if (!certRequest) {
+ Log(LogCritical, "cli", "Certificate request is invalid. Could not parse X.509 certificate for the 'cert_request' attribute.");
+ return 1;
+ }
+
boost::shared_ptr<X509> certResponse = CreateCertIcingaCA(certRequest);
+ BIO *out = BIO_new(BIO_s_mem());
+ X509_NAME_print_ex(out, X509_get_subject_name(certRequest.get()), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB);
+
+ char *data;
+ long length;
+ length = BIO_get_mem_data(out, &data);
+
+ String subject = String(data, data + length);
+ BIO_free(out);
+
+ if (!certResponse) {
+ Log(LogCritical, "cli")
+ << "Could not sign certificate for '" << subject << "'.";
+ return 1;
+ }
+
request->Set("cert_response", CertificateToString(certResponse));
Utility::SaveJsonFile(requestFile, 0600, request);
+ Log(LogInformation, "cli")
+ << "Signed certificate for '" << subject << "'.";
+
return 0;
}
else
cert = StringToCertificate(certText);
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+ boost::shared_ptr<X509> cacert = GetX509Certificate(listener->GetCaPath());
+
+ bool signedByCA = VerifyCertificate(cacert, cert);
+
+ if (signedByCA) {
+ time_t now;
+ time(&now);
+
+ /* auto-renew all certificates which were created before 2017 to force an update of the CA,
+ * because Icinga versions older than 2.4 sometimes create certificates with an invalid
+ * serial number. */
+ time_t forceRenewalEnd = 1483228800; /* January 1st, 2017 */
+ time_t renewalStart = now + 30 * 24 * 60 * 60;
+
+ if (X509_cmp_time(X509_get_notBefore(cert.get()), &forceRenewalEnd) != -1 && X509_cmp_time(X509_get_notAfter(cert.get()), &renewalStart) != -1) {
+ result->Set("status_code", 1);
+ result->Set("error", "The certificate cannot be renewed yet.");
+ return result;
+ }
+ }
+
unsigned int n;
unsigned char digest[EVP_MAX_MD_SIZE];
String requestDir = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests";
String requestPath = requestDir + "/" + certFingerprint + ".json";
- ApiListener::Ptr listener = ApiListener::GetInstance();
-
- boost::shared_ptr<X509> cacert = GetX509Certificate(listener->GetCaPath());
result->Set("ca", CertificateToString(cacert));
if (Utility::PathExists(requestPath)) {
if (!Utility::PathExists(GetIcingaCADir() + "/ca.key"))
goto delayed_request;
- if (!VerifyCertificate(cacert, cert)) {
+ if (!signedByCA) {
String salt = listener->GetTicketSalt();
String ticket = params->Get("ticket");
result->Set("error", "Invalid ticket.");
return result;
}
- } else {
- time_t renewalStart;
- time(&renewalStart);
- renewalStart += 30 * 24 * 60 * 60;
-
- if (X509_cmp_time(X509_get_notAfter(cert.get()), &renewalStart)) {
- result->Set("status_code", 1);
- result->Set("error", "The certificate cannot be renewed yet.");
- return result;
- }
}
-
pubkey = boost::shared_ptr<EVP_PKEY>(X509_get_pubkey(cert.get()), EVP_PKEY_free);
subject = X509_get_subject_name(cert.get());