mirror of
https://github.com/Icinga/icinga2.git
synced 2025-07-28 08:04:14 +02:00
Verify certificates against CRL before renewing them
When a CRL is specified in the ApiListener configuration, Icinga 2 only used it when connections were established so far, but not when a certificate is requested. This allows a node to automatically renew a revoked certificate if it meets the other conditions for auto-renewal (issued before 2017 or expires in less than 30 days).
This commit is contained in:
parent
73c085a9b0
commit
c510fe4dfe
@ -236,15 +236,26 @@ void SetTlsProtocolminToSSLContext(const std::shared_ptr<boost::asio::ssl::conte
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a CRL and appends its certificates to the specified SSL context.
|
||||
* Loads a CRL and appends its certificates to the specified Boost SSL context.
|
||||
*
|
||||
* @param context The SSL context.
|
||||
* @param crlPath The path to the CRL file.
|
||||
*/
|
||||
void AddCRLToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& crlPath)
|
||||
{
|
||||
char errbuf[256];
|
||||
X509_STORE *x509_store = SSL_CTX_get_cert_store(context->native_handle());
|
||||
AddCRLToSSLContext(x509_store, crlPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a CRL and appends its certificates to the specified OpenSSL X509 store.
|
||||
*
|
||||
* @param context The SSL context.
|
||||
* @param crlPath The path to the CRL file.
|
||||
*/
|
||||
void AddCRLToSSLContext(X509_STORE *x509_store, const String& crlPath)
|
||||
{
|
||||
char errbuf[256];
|
||||
|
||||
X509_LOOKUP *lookup;
|
||||
lookup = X509_STORE_add_lookup(x509_store, X509_LOOKUP_file());
|
||||
@ -821,7 +832,7 @@ String RandomString(int length)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate)
|
||||
bool VerifyCertificate(const std::shared_ptr<X509> &caCertificate, const std::shared_ptr<X509> &certificate, const String& crlFile)
|
||||
{
|
||||
X509_STORE *store = X509_STORE_new();
|
||||
|
||||
@ -830,6 +841,10 @@ bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::sh
|
||||
|
||||
X509_STORE_add_cert(store, caCertificate.get());
|
||||
|
||||
if (!crlFile.IsEmpty()) {
|
||||
AddCRLToSSLContext(store, crlFile);
|
||||
}
|
||||
|
||||
X509_STORE_CTX *csc = X509_STORE_CTX_new();
|
||||
X509_STORE_CTX_init(csc, store, certificate.get(), nullptr);
|
||||
|
||||
|
@ -22,10 +22,13 @@ namespace icinga
|
||||
|
||||
void InitializeOpenSSL();
|
||||
|
||||
std::shared_ptr<boost::asio::ssl::context> MakeAsioSslContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String());
|
||||
void AddCRLToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& crlPath);
|
||||
void SetCipherListToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& cipherList);
|
||||
void SetTlsProtocolminToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& tlsProtocolmin);
|
||||
String GetOpenSSLVersion();
|
||||
|
||||
Shared<boost::asio::ssl::context>::Ptr MakeAsioSslContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String());
|
||||
void AddCRLToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& crlPath);
|
||||
void AddCRLToSSLContext(X509_STORE *x509_store, const String& crlPath);
|
||||
void SetCipherListToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& cipherList);
|
||||
void SetTlsProtocolminToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& tlsProtocolmin);
|
||||
|
||||
String GetCertificateCN(const std::shared_ptr<X509>& certificate);
|
||||
std::shared_ptr<X509> GetX509Certificate(const String& pemfile);
|
||||
@ -45,7 +48,11 @@ String SHA1(const String& s, bool binary = false);
|
||||
String SHA256(const String& s);
|
||||
String RandomString(int length);
|
||||
|
||||
bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate);
|
||||
bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate, const String& crlFile);
|
||||
bool IsCa(const std::shared_ptr<X509>& cacert);
|
||||
int GetCertificateVersion(const std::shared_ptr<X509>& cert);
|
||||
String GetSignatureAlgorithm(const std::shared_ptr<X509>& cert);
|
||||
Array::Ptr GetSubjectAltNames(const std::shared_ptr<X509>& cert);
|
||||
|
||||
class openssl_error : virtual public std::exception, virtual public boost::exception { };
|
||||
|
||||
|
@ -53,11 +53,27 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
|
||||
|
||||
String cn = GetCertificateCN(cert);
|
||||
|
||||
bool signedByCA = VerifyCertificate(cacert, cert);
|
||||
bool signedByCA = false;
|
||||
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Received certificate request for CN '" << cn << "'"
|
||||
<< (signedByCA ? "" : " not") << " signed by our CA.";
|
||||
{
|
||||
Log logmsg(LogInformation, "JsonRpcConnection");
|
||||
logmsg << "Received certificate request for CN '" << cn << "'";
|
||||
|
||||
try {
|
||||
signedByCA = VerifyCertificate(cacert, cert, listener->GetCrlPath());
|
||||
if (!signedByCA) {
|
||||
logmsg << " not";
|
||||
}
|
||||
logmsg << " signed by our CA.";
|
||||
} catch (const std::exception &ex) {
|
||||
logmsg << " not signed by our CA";
|
||||
if (const unsigned long *openssl_code = boost::get_error_info<errinfo_openssl_error>(ex)) {
|
||||
logmsg << ": " << X509_verify_cert_error_string(long(*openssl_code)) << " (code " << *openssl_code << ")";
|
||||
} else {
|
||||
logmsg << ".";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (signedByCA) {
|
||||
time_t now;
|
||||
@ -199,12 +215,14 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
|
||||
* this ensures that the CA we have in /var/lib/icinga2/ca matches the one
|
||||
* we're using for cluster connections (there's no point in sending a client
|
||||
* a certificate it wouldn't be able to use to connect to us anyway) */
|
||||
if (!VerifyCertificate(cacert, newcert)) {
|
||||
Log(LogWarning, "JsonRpcConnection")
|
||||
<< "The CA in '" << listener->GetDefaultCaPath() << "' does not match the CA which Icinga uses "
|
||||
<< "for its own cluster connections. This is most likely a configuration problem.";
|
||||
goto delayed_request;
|
||||
}
|
||||
try {
|
||||
if (!VerifyCertificate(cacert, newcert, listener->GetCrlPath())) {
|
||||
Log(LogWarning, "JsonRpcConnection")
|
||||
<< "The CA in '" << listener->GetDefaultCaPath() << "' does not match the CA which Icinga uses "
|
||||
<< "for its own cluster connections. This is most likely a configuration problem.";
|
||||
goto delayed_request;
|
||||
}
|
||||
} catch (const std::exception&) { } /* Swallow the exception on purpose, cacert will never be a non-CA certificate. */
|
||||
|
||||
/* Send the signed certificate update. */
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
|
Loading…
x
Reference in New Issue
Block a user