diff --git a/lib/remote/jsonrpcconnection-pki.cpp b/lib/remote/jsonrpcconnection-pki.cpp index d2b727b674f..781dc1bd3a8 100644 --- a/lib/remote/jsonrpcconnection-pki.cpp +++ b/lib/remote/jsonrpcconnection-pki.cpp @@ -31,11 +31,11 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona std::shared_ptr cert; Dictionary::Ptr result = new Dictionary(); + auto& tlsConn (origin->FromClient->GetStream()->next_layer()); /* Use the presented client certificate if not provided. */ if (certText.IsEmpty()) { - auto stream (origin->FromClient->GetStream()); - cert = stream->next_layer().GetPeerCertificate(); + cert = tlsConn.GetPeerCertificate(); } else { cert = StringToCertificate(certText); } @@ -79,11 +79,59 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona if (signedByCA) { if (IsCertUptodate(cert)) { + // The following analysis targets a direct peer's cert chain and makes no sense for forwarded CSRs. + if (cn == origin->FromClient->GetIdentity()) { + // Even if the leaf is up-to-date, the root may expire soon. + // In a regular setup where Icinga manages the PKI, there is only one CA. + // Icinga includes it in handshakes, let's see whether the peer needs a fresh one... + + auto chain (SSL_get_peer_cert_chain(tlsConn.native_handle())); + + if (chain) { + X509* root = nullptr; + auto len (sk_X509_num(chain)); + + for (int i = 0; i < len; ++i) { + auto link (sk_X509_value(chain, i)); + + if (!X509_NAME_cmp(X509_get_subject_name(link), X509_get_issuer_name(link))) { + root = link; + } + } + + if (root && !IsCertUptodate(root)) { + auto oldEnd (X509_get_notAfter(root)); + auto newEnd (X509_get_notAfter(cacert.get())); + int pday, psec; + + if (oldEnd && newEnd && ASN1_TIME_diff(&pday, &psec, oldEnd, newEnd) && pday > 0) { + Log(LogInformation, "JsonRpcConnection") + << "The certificate for CN '" << cn + << "' is valid and uptodate. But its root CA will expire soon. Sending an uptodate one."; + + result->Set("status_code", 0); + result->Set("ca", CertificateToString(cacert)); + + // UpdateCertificateHandler() expects a certificate. + // Give it the presented one and don't bother the CA master above us (if any). + result->Set("cert", CertificateToString(cert)); + + origin->FromClient->SendMessage(new Dictionary({ + { "jsonrpc", "2.0" }, + { "method", "pki::UpdateCertificate" }, + { "params", result } + })); + + return result; + } + } + } + } Log(LogInformation, "JsonRpcConnection") - << "The certificate for CN '" << cn << "' is valid and uptodate. Skipping automated renewal."; + << "The certificates for CN '" << cn << "' and its root CA are valid and uptodate. Skipping automated renewal."; result->Set("status_code", 1); - result->Set("error", "The certificate for CN '" + cn + "' is valid and uptodate. Skipping automated renewal."); + result->Set("error", "The certificates for CN '" + cn + "' and its root CA are valid and uptodate. Skipping automated renewal."); return result; } }