Merge pull request from GHSA-pcmr-2p2f-r7j6

Verify certificates against CRL before renewing them (2.11)
This commit is contained in:
Noah Hilverling 2020-12-15 12:30:18 +01:00 committed by GitHub
commit 1cf1e3f6ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 19 deletions

View File

@ -9,9 +9,25 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
## 2.11.8 (2020-12-15)
Version 2.11.8 mainly focuses on resolving issues with high load on Windows regarding the config sync
Version 2.11.8 resolves a security vulnerability with revoked certificates being
renewed automatically ignoring the CRL.
This version also resolves issues with high load on Windows regarding the config sync
and not being able to disable/enable Icinga 2 features over the API.
### Security
* Fix that revoked certificates due for renewal will automatically be renewed ignoring the CRL (CVE-2020-29663)
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).
Because Icinga 2 currently (v2.12.3 and earlier) uses a validity duration of 15 years,
this only affects setups with external certificate signing and revoked certificates
that expire in less then 30 days.
### Bugfixes
* Improve config sync locking - resolves high load issues on Windows #8510

View File

@ -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);

View File

@ -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 { };

View File

@ -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")