GetCertificateCN(): if the CN is missing, fall back to the DNS SAN

if exactly one is given and use it as CN.

Currently Icinga 2 populates both the DNS SAN and the subject CN of a newly
issued certificate with the endpoint name. But the CN is limited to 64
characters. So endpoint names are. The only clean solution is omitting the
CN for too long names. Icinga 2 must be able to extract the endpoint name
from such certificates in the first place and exactly one DNS SAN is a legit
source.
This commit is contained in:
Alexander A. Klimov 2024-07-03 11:58:38 +02:00
parent 45452629db
commit e7a21c1830

View File

@ -9,6 +9,7 @@
#include "base/application.hpp"
#include "base/exception.hpp"
#include <boost/asio/ssl/context.hpp>
#include <memory>
#include <openssl/opensslv.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
@ -399,7 +400,7 @@ void AddCRLToSSLContext(X509_STORE *x509_store, const String& crlPath)
X509_VERIFY_PARAM_free(param);
}
static String GetX509NameCN(X509_NAME *name)
static String GetX509NameCN(X509_NAME* name, X509* fallback = nullptr)
{
char errbuf[256];
char buffer[256];
@ -407,12 +408,50 @@ static String GetX509NameCN(X509_NAME *name)
int rc = X509_NAME_get_text_by_NID(name, NID_commonName, buffer, sizeof(buffer));
if (rc == -1) {
ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf);
auto err (ERR_peek_error());
int totalSans = 0;
int dnsSans = 0;
// Subject CN missing, fall back to the DNS SAN if exactly one given
if (fallback) {
std::unique_ptr<GENERAL_NAMES, void(*)(GENERAL_NAMES*)> sans (
(GENERAL_NAMES*)X509_get_ext_d2i(fallback, NID_subject_alt_name, nullptr, nullptr),
&GENERAL_NAMES_free
);
if (sans) {
const unsigned char* dnsSan = nullptr;
totalSans = sk_GENERAL_NAME_num(sans.get());
for (int i = 0; i < totalSans; ++i) {
auto gn (sk_GENERAL_NAME_value(sans.get(), i));
if (gn->type == GEN_DNS) {
auto asn1Str (gn->d.uniformResourceIdentifier);
#if OPENSSL_VERSION_NUMBER < 0x10100000L
dnsSan = ASN1_STRING_data(asn1Str);
#else /* OPENSSL_VERSION_NUMBER < 0x10100000L */
dnsSan = ASN1_STRING_get0_data(asn1Str);
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
++dnsSans;
}
}
if (dnsSans == 1) {
return Convert::ToString(dnsSan);
}
}
}
ERR_error_string_n(err, errbuf, sizeof errbuf);
Log(LogCritical, "SSL")
<< "Error with x509 NAME getting text by NID: " << ERR_peek_error() << ", \"" << errbuf << "\"";
<< "Error with x509 NAME getting text by NID: " << err << ", \"" << errbuf
<< "\" (Also found " << dnsSans << " DNS SANs and " << totalSans - dnsSans << " others)";
BOOST_THROW_EXCEPTION(openssl_error()
<< boost::errinfo_api_function("X509_NAME_get_text_by_NID")
<< errinfo_openssl_error(ERR_peek_error()));
<< errinfo_openssl_error(err));
}
return buffer;
@ -426,7 +465,7 @@ static String GetX509NameCN(X509_NAME *name)
*/
String GetCertificateCN(const std::shared_ptr<X509>& certificate)
{
return GetX509NameCN(X509_get_subject_name(certificate.get()));
return GetX509NameCN(X509_get_subject_name(certificate.get()), certificate.get());
}
/**