This is an automated email from the ASF dual-hosted git repository. boroknagyz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/impala.git
commit f3d693d3fd8533a8533bd63b4711374aa8e61cfa Author: jasonmfehr <[email protected]> AuthorDate: Wed Jun 25 20:51:11 2025 -0700 IMPALA-13237: [Patch 2 of 5] - Add OpenSSL Utility Function to Validate PEM Bundles Adds a new function ValidatePemBundle(const string& bundle) to the openssl utilities. This function asserts that all certificates in a string containing a bundle of X.509 PEM-encoded certificates are valid. If even one certificate is invalid, then validation fails and the function returns false. Certificates can be invalid for these reasons: 1. Structure is invalid/PEM encoding is incorrect. 2. notBefore time is in the future. 3. notAfter time is in the past. Testing accomplished by the tests in openssl-util-test.cc successfully passing locally and in a build. Generated-by: Github Copilot (GPT-4.1) Change-Id: I1e9f12e854b679ccf3e94a38c2688a7431460a7a Reviewed-on: http://gerrit.cloudera.org:8080/23097 Reviewed-by: Impala Public Jenkins <[email protected]> Reviewed-by: Jason Fehr <[email protected]> Reviewed-by: Alexey Serbin <[email protected]> Tested-by: Impala Public Jenkins <[email protected]> --- be/src/testutil/future-cert.pem | 24 +++++ be/src/testutil/future-key.pem | 28 +++++ be/src/util/openssl-util-test.cc | 222 +++++++++++++++++++++++++++++++++++++++ be/src/util/openssl-util.cc | 62 +++++++++++ be/src/util/openssl-util.h | 5 + 5 files changed, 341 insertions(+) diff --git a/be/src/testutil/future-cert.pem b/be/src/testutil/future-cert.pem new file mode 100644 index 000000000..401744296 --- /dev/null +++ b/be/src/testutil/future-cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID+zCCAeOgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwGDEWMBQGA1UEAwwNSW1w +YWxhVGVzdGluZzAiGA8yMDk5MTIzMDAwMDAwMFoYDzIwOTkxMjMxMjM1OTU5WjAU +MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7hXd0/0rQ16BpCk6qL7/I4jqG1ntWPNlmD/1Fcv2LB3rEVL+h9inR8yyN +Ht59qXSxnQHTdoGqdLC3/H6q7bOQb9dBBYEeZiH00RdTrmzb4uJL7YGagIhINqJc +YOyP1Ek9ZFHi5YDqdqri0kEGid4CiOz1RTjrbjfwI5kdvpEbzcF4HJFr9eMh1+KI +v6rHd3i36CV296FaGkvpxYmtNjjXTtoXmZtiw+t7nC7kHZ2tQdPCU7+0SUYn8p7e +ifAD0Bjs8/Tei27JqdkpOptIxl0Jtg3j8bK1nVW7BrbVMAiVAOIi/bdlSUB7uKC4 +QCxqDvLHCKyKCT4cFHMYLQTWyOg1AgMBAAGjTzBNMAkGA1UdEwQCMAAwCwYDVR0P +BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAUBgNVHREEDTAL +gglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIBAAQQzPDedcBDa9zw0Pg3G0a3 +CjeDi/T4bw2jF1f6wwk1o9sHJGN33hrU1v3lz43ba4v8SOgTxuwRAzlBCTudqRnw +5ysf+RfbaF4MaCuT7b/gzARwsdD70gRcaa4zBVMfteIysw8RaQlv4TU22g33OeT9 +eAyoYIODjP47NtEdt0MMFc4elG38ZGcPxVIfymriesNCUsCDW64s0BJACZzjDDih +CQbrzYi/VMgZAsngs8v3yN+Novlbax8FoJquMJVtDMFp5ihHO7uikO2f4pqGz+eK +KRkkTkedTFE8KeIAx2rYsEIrsRWkgusfGRYtMmNgU791ChL9eR0B90D8/g4e4Aa1 +mN9jPYKR42Yr7+OUuiSNkd0dEdAOge2blPPFsKdsndFTtTRBagwg8+NfEIWqVUJv +x8GHQ995jLWBWvCxF/AsMjgmGD+wC5ya4RRnX3FEGffpPDxZffJJiFn97ItbNGtp +KL9fMy+MA6kvpCQGJ1VPXRCo17v6S2+k/e/IlSVZXOcTghqCOpjJaURechqECVnc +lWBEALlAORmFshico4mYk3Acaohe3MjpAhkoKGbsUYdE95mZ38Kqyt74mWYz2zGc +dXmgAuScijk/TtVuL9HW9y8RpIfhmfjoiNEM1JC7Tkl62I6RV/h5XPUDT8wtdz3t +co9ID1TWIjeBcX3Xijks +-----END CERTIFICATE----- diff --git a/be/src/testutil/future-key.pem b/be/src/testutil/future-key.pem new file mode 100644 index 000000000..dff8fa970 --- /dev/null +++ b/be/src/testutil/future-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7hXd0/0rQ16Bp +Ck6qL7/I4jqG1ntWPNlmD/1Fcv2LB3rEVL+h9inR8yyNHt59qXSxnQHTdoGqdLC3 +/H6q7bOQb9dBBYEeZiH00RdTrmzb4uJL7YGagIhINqJcYOyP1Ek9ZFHi5YDqdqri +0kEGid4CiOz1RTjrbjfwI5kdvpEbzcF4HJFr9eMh1+KIv6rHd3i36CV296FaGkvp +xYmtNjjXTtoXmZtiw+t7nC7kHZ2tQdPCU7+0SUYn8p7eifAD0Bjs8/Tei27Jqdkp +OptIxl0Jtg3j8bK1nVW7BrbVMAiVAOIi/bdlSUB7uKC4QCxqDvLHCKyKCT4cFHMY +LQTWyOg1AgMBAAECggEBAIonvub334sWEhzRdztzinzaD/yfsKbMle7YAOVV3u67 +R/u15KnSzuTboAsonws9+beR0XKWcNnAtyIWaMxN7GmRvKkvAP4AiSvxkJoafCFU +ex0tkrrdz4pElkIxNpiQ8pTEgIAGToBvhV8vQLEsieCcONhLc7AguB24XGrJp+cu +4WOcIFIG4B5JlnFIMg36SCymlhSFsIlBVoHIc5EwJrJHTwhBSS0KLv1X0PT9hKjB +Ozu3GT2+QmWsMjjaCYkQvfmqOkZ3nQNt0xyjxJ3b3ce1H4GYD0R/rKp6SkhFodnT +VY51/UtpI/sbNLV9Dzg9hulHyGhsViBT5FkVHBuFxoECgYEA3puv424Udny4Dedv +8yU+UJmRhoAM+uePF56GVDKzCbQKlfYeqODxdw41kprh5+NKr3xXqunE4HW2Ksgq +iBcoV+aZTaiMSuZ1EKwAvbhL1HfbNBM46g8UhdEmF88SosDzZv0NWk/AmLXa/wnR +bHC20FRx2D27Y8m6HV0WIMq5m8kCgYEA16Zr2ljd1DJhfHmeFXeHsv5B6Fkx7JZg +Br5+89pPT3IV64bUtmm2T9M53vWTi1cHrxLh8rSiIUJEIZrAL5nj4iODis9XMqyq +bkzgySzMlqzbuXrqyY87i7gLZLW8zlKCQThUGv42pdd4+uYtvnuJC1vMM1RRFYfO +opqnpjkNhw0CgYAsUCWlSmZ8Z7tj2Sxp5IIRHWDgu2DaGXpco0vuOuF1DwgEbigQ +SicHIib79iT+OjrF0CTLexmb7RLEVXBSAvoe9WKTGPhEeLfZB8Rl/+fodauHJERy +I2pccRP6LVhyWaKaX8lmrvWR8LGVj2jHA3EA665182P4K1IHQl6DTJfCMQKBgHJG +rXaqivrMq95TcFM94ToYBIME6TDPSxmnzwyYDlkyQq1p6MS1ZjgM3ejpyAMZ3meH +IRcFY7kuLX6FS/ytlxYZ7bYcQ8AFwdbrhF73D391IIa8vTiMX2id4eO/bssC7CV1 +nRQkzH67nU+PtIPxYOEVBzPYJZO8QOLoA5lROHrtAoGBAI9QMBCv4vruXlgWadVP +HrtH7IdB4BuRuAQBsJUnWi6ruS3DjvZUU0XCJQmy+/y8c7AMR73f2KUYHu8aESy/ +MjFv8SPBZHb/6HzH6HlPqjL7hHKvgvvEYovvtsdbz0oN3RRY//Agc8ECbu4Khl5f +nWnr74srNeq5u+gC01RvdiUD +-----END PRIVATE KEY----- diff --git a/be/src/util/openssl-util-test.cc b/be/src/util/openssl-util-test.cc index 66925e10e..384536f9f 100644 --- a/be/src/util/openssl-util-test.cc +++ b/be/src/util/openssl-util-test.cc @@ -22,11 +22,18 @@ #include <openssl/rand.h> #include "common/init.h" +#include "common/status.h" +#include "gutil/strings/substitute.h" +#include "kudu/util/env.h" +#include "kudu/util/faststring.h" +#include "kudu/util/status.h" #include "testutil/gtest-util.h" #include "util/openssl-util.h" using std::uniform_int_distribution; using std::mt19937_64; +using strings::Substitute; +using std::string; namespace impala { @@ -241,5 +248,220 @@ TEST_F(OpenSSLUtilTest, RandSeeding) { ASSERT_OK(key.InitializeRandom(AES_BLOCK_SIZE, key.GetSupportedDefaultMode())); } } + +/// +/// ValidatePemBundle tests +/// +/// Constants defining paths to test files. +static const string& EXPIRED_CERT = "bad-cert"; +static const string& FUTURE_CERT = "future-cert"; +static const string& INVALID_CERT = "invalid-server-cert"; +static const string& VALID_CERT_1 = "server-cert"; +static const string& VALID_CERT_2 = "wildcardCA"; + +/// Constants and functions defining expected error messages. +static const string& MSG_NEW_BIO_ERR = "OpenSSL error in PEM_read_bio_X509 unexpected " + "error '$0' while reading PEM bundle"; +static const string& MSG_NONE_VALID = "PEM bundle contains no valid certificates"; +static string _msg_time(int invalid_notbefore_cnt, int invalid_notafter_cnt) { + return Substitute("PEM bundle contains $0 invalid certificate(s) with notBefore in the " + "future and $1 invalid certificate(s) with notAfter in the past", + invalid_notbefore_cnt, invalid_notafter_cnt); +} + +/// Constants used in the tests +static const string& INVALID_CERT_TEXT = "-----BEGIN CERTIFICATE-----\nnot a cert"; + +/// Helper function to read a certificate from the be/src/testutil directory. +static string read_cert(const string& cert_name) { + kudu::faststring contents; + kudu::Status s = kudu::ReadFileToString(kudu::Env::Default(), + Substitute("$0/be/src/testutil/$1.pem", getenv("IMPALA_HOME"), cert_name), + &contents); + + EXPECT_TRUE(s.ok())<< "Certificate '" << cert_name << "' could not be read: " + << s.ToString(); + EXPECT_FALSE(contents.ToString().empty()) << "Certificate '" << cert_name + << "' is empty. Please check the test data."; + + string cert = contents.ToString(); + if (cert.back() != '\n') { + cert.push_back('\n'); + } + + return cert; +} // function read_cert + +/// Asserts a PEM bundle containing one valid certificate is valid. +TEST_F(OpenSSLUtilTest, ValidatePemBundleHappyPathOneCert) { + ASSERT_OK(ValidatePemBundle(Substitute("$0", read_cert(VALID_CERT_1)))); +} + +/// Asserts a PEM bundle containing two valid certificates is valid. +TEST_F(OpenSSLUtilTest, ValidatePemBundleHappyPath) { + ASSERT_OK(ValidatePemBundle(Substitute("$0$1", read_cert(VALID_CERT_1), + read_cert(VALID_CERT_2)))); +} + +/// Asserts an empty PEM bundle is invalid. +TEST_F(OpenSSLUtilTest, ValidatePemBundleEmpty) { + ASSERT_ERROR_MSG(ValidatePemBundle(""), "bundle is empty"); +} + +/// Asserts a bundle containing a single expired certificate is invalid. +TEST_F(OpenSSLUtilTest, ValidatePemBundleExpired) { + ASSERT_ERROR_MSG(ValidatePemBundle(read_cert(EXPIRED_CERT)), _msg_time(0, 1)); +} + +/// Asserts a bundle containing a single future dated certificate is invalid. +TEST_F(OpenSSLUtilTest, ValidatePemBundleFutureDated) { + ASSERT_ERROR_MSG(ValidatePemBundle(read_cert(FUTURE_CERT)), _msg_time(1, 0)); +} + +/// Asserts a bundle containing a single invalid certificate is invalid. +TEST_F(OpenSSLUtilTest, ValidatePemBundleNotACert) { + ASSERT_ERROR_MSG(ValidatePemBundle(INVALID_CERT_TEXT), MSG_NONE_VALID); +} + +/// Asserts a bundle is invalid if it contains one expired and two non-expired +/// certificates where the expired cert changes position in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleThreeCertsOneExpired) { + // Expired cert is first in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(EXPIRED_CERT), + read_cert(VALID_CERT_1), read_cert(VALID_CERT_2))), _msg_time(0, 1)); + + // Expired cert is in the middle of the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(VALID_CERT_1), + read_cert(EXPIRED_CERT), read_cert(VALID_CERT_2))), _msg_time(0, 1)); + + // Expired cert is last in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(VALID_CERT_1), + read_cert(VALID_CERT_2), read_cert(EXPIRED_CERT))), _msg_time(0, 1)); +} + +/// Asserts a bundle is invalid if it contains one future dated and two valid +/// certificates where the expired cert changes position in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleThreeCertsOneFutureDated) { + // Expired cert is first in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(FUTURE_CERT), + read_cert(VALID_CERT_1), read_cert(VALID_CERT_2))), _msg_time(1, 0)); + + // Expired cert is in the middle of the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(VALID_CERT_1), + read_cert(FUTURE_CERT), read_cert(VALID_CERT_2))), _msg_time(1, 0)); + + // Expired cert is last in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(VALID_CERT_1), + read_cert(VALID_CERT_2), read_cert(FUTURE_CERT))), _msg_time(1, 0)); +} + +/// Asserts a bundle is invalid if it contains two expired and one non-expired +/// certificate no matter the position of the expired cert in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleThreeCertsTwoExpired) { + // First two certs are expired. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(EXPIRED_CERT), + read_cert(EXPIRED_CERT), read_cert(VALID_CERT_2))), _msg_time(0, 2)); + + // Last two certs are expired. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(VALID_CERT_2), + read_cert(EXPIRED_CERT), read_cert(EXPIRED_CERT))), _msg_time(0, 2)); +} + +/// Asserts a bundle is invalid if it contains two future dated and one valid +/// certificate no matter the position of the future dated cert in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleThreeCertsTwoFutureDated) { + // First two certs are expired. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(FUTURE_CERT), + read_cert(FUTURE_CERT), read_cert(VALID_CERT_2))), _msg_time(2, 0)); + + // Last two certs are expired. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", read_cert(VALID_CERT_2), + read_cert(FUTURE_CERT), read_cert(FUTURE_CERT))), _msg_time(2, 0)); +} + +/// Asserts a bundle is invalid if it contains one valid and two invalid certificates +/// where the expired cert changes position in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleThreeCertsOneInvalid) { + // Invalid cert is first in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2", INVALID_CERT_TEXT, + read_cert(VALID_CERT_1), read_cert(VALID_CERT_2))), MSG_NONE_VALID); + + // Invalid cert is in the middle of the bundle. + EXPECT_STR_CONTAINS(ValidatePemBundle(Substitute("$0$1$2", read_cert(VALID_CERT_1), + INVALID_CERT_TEXT, read_cert(VALID_CERT_2))).GetDetail(), + Substitute(MSG_NEW_BIO_ERR, 123)); + + // Invalid cert is last in the bundle. + EXPECT_STR_CONTAINS(ValidatePemBundle(Substitute("$0$1$2", read_cert(VALID_CERT_1), + read_cert(VALID_CERT_2), INVALID_CERT_TEXT)).GetDetail(), + Substitute(MSG_NEW_BIO_ERR, 112)); +} + +/// Asserts a bundle is invalid if it contains one expired and one non-expired certificate +/// no matter the position of the expired cert in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleTwoCertsExpired) { + // Expired cert is first in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1", read_cert(EXPIRED_CERT), + read_cert(VALID_CERT_1))), _msg_time(0, 1)); + + // Expired cert is last in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1", read_cert(VALID_CERT_1), + read_cert(EXPIRED_CERT))), _msg_time(0, 1)); +} + +/// Asserts a bundle is invalid if it contains one future dated and one valid certificate +/// no matter the position of the future dated cert in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleTwoCertsFutureDated) { + // Expired cert is first in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1", read_cert(FUTURE_CERT), + read_cert(VALID_CERT_1))), _msg_time(1, 0)); + + // Expired cert is last in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1", read_cert(VALID_CERT_1), + read_cert(FUTURE_CERT))), _msg_time(1, 0)); +} + +/// Asserts a bundle is invalid if it contains one valid and one invalid certificate +/// no matter the position of the expired cert in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleTwoCertsNotCertFirst) { + // Invalid cert is first in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1", INVALID_CERT_TEXT, + read_cert(VALID_CERT_1))), MSG_NONE_VALID); + + // Invalid cert is last in the bundle. + EXPECT_STR_CONTAINS(ValidatePemBundle(Substitute("$0$1", read_cert(VALID_CERT_1), + INVALID_CERT_TEXT)).GetDetail(), Substitute(MSG_NEW_BIO_ERR, 112)); +} + +/// Asserts a bundle is invalid if it contains two valid, one expired, and one future +/// dated certificate no matter the position of the certs in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleFourCerts) { + // Expired and future dated certs are first in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1", read_cert(EXPIRED_CERT), + read_cert(FUTURE_CERT), read_cert(VALID_CERT_1), read_cert(VALID_CERT_2))), + _msg_time(1, 1)); + + // Expired and future dated certs are last in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2$3", read_cert(VALID_CERT_1), + read_cert(VALID_CERT_2), read_cert(EXPIRED_CERT), read_cert(FUTURE_CERT))), + _msg_time(1, 1)); + + // Expired and future dated certs are intermixed in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1$2$3", read_cert(VALID_CERT_1), + read_cert(FUTURE_CERT), read_cert(VALID_CERT_2), read_cert(EXPIRED_CERT))), + _msg_time(1, 1)); +} + +/// Asserts a bundle is invalid if it contains one expired and one future dated +/// certificate no matter the position of the certs in the bundle. +TEST_F(OpenSSLUtilTest, ValidatePemBundleOneExpiredOneFutureDated) { + // Expired cert is first in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1", read_cert(EXPIRED_CERT), + read_cert(FUTURE_CERT))), _msg_time(1, 1)); + + // Future dated cert is first in the bundle. + ASSERT_ERROR_MSG(ValidatePemBundle(Substitute("$0$1", read_cert(FUTURE_CERT), + read_cert(EXPIRED_CERT))), _msg_time(1, 1)); } +} // namespace impala diff --git a/be/src/util/openssl-util.cc b/be/src/util/openssl-util.cc index d814d6d8c..bb65e21e8 100644 --- a/be/src/util/openssl-util.cc +++ b/be/src/util/openssl-util.cc @@ -25,19 +25,24 @@ #include <iostream> #include <glog/logging.h> +#include <openssl/bio.h> #include <openssl/err.h> #include <openssl/evp.h> +#include <openssl/pem.h> #include <openssl/rand.h> #include <openssl/sha.h> #include <openssl/tls1.h> +#include <openssl/x509.h> #include "common/atomic.h" +#include "common/compiler-util.h" #include "common/status.h" #include "gutil/port.h" // ATTRIBUTE_WEAK #include "gutil/strings/substitute.h" #include "common/names.h" #include "cpu-info.h" +#include "kudu/util/openssl_util.h" DECLARE_string(ssl_client_ca_certificate); DECLARE_string(ssl_server_certificate); @@ -152,6 +157,63 @@ void SeedOpenSSLRNG() { RAND_load_file("/dev/urandom", RNG_RESEED_BYTES); } +Status ValidatePemBundle(const string& bundle) { + SCOPED_OPENSSL_NO_PENDING_ERRORS; + + if (UNLIKELY(bundle.empty())) { + return Status("bundle is empty"); + } + + BIO* bio = BIO_new_mem_buf(bundle.data(), bundle.size()); + if (UNLIKELY(ERR_peek_error() != 0 || bio == nullptr)) { + Status ret = OpenSSLErr("BIO_new_mem_buf", Substitute("error '$0' creating BIO from " + "PEM bundle", ERR_GET_REASON(ERR_peek_error()))); + ERR_clear_error(); + return ret; + } + + X509* cert = nullptr; + int cert_count = 0; + int invalid_notbefore_cnt = 0; + int invalid_notafter_cnt = 0; + while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != nullptr) { + cert_count++; + // Check certificate validity (notBefore and notAfter) + if (UNLIKELY(X509_cmp_current_time(X509_get0_notBefore(cert)) > 0)) { + invalid_notbefore_cnt++; + } + + if (UNLIKELY(X509_cmp_current_time(X509_get0_notAfter(cert)) < 0)) { + invalid_notafter_cnt++; + } + + X509_free(cert); + } + BIO_free(bio); + + Status ret; + if (UNLIKELY(invalid_notbefore_cnt > 0 || invalid_notafter_cnt > 0)) { + ret = Status(Substitute( + "PEM bundle contains $0 invalid certificate(s) with notBefore in the future " + "and $1 invalid certificate(s) with notAfter in the past", + invalid_notbefore_cnt, invalid_notafter_cnt)); + } else if (UNLIKELY(cert_count == 0)) { + ret = Status("PEM bundle contains no valid certificates"); + } else if (UNLIKELY(ERR_GET_REASON(ERR_peek_error()) != PEM_R_NO_START_LINE)) { + // The final PEM_read_bio_X509 always sets the openssl error to PEM_R_NO_START_LINE, + // if the ssl error is set to anything else, return it. + ret = OpenSSLErr("PEM_read_bio_X509", Substitute("unexpected error '$0' while " + "reading PEM bundle", ERR_GET_REASON(ERR_peek_error()))); + } else { + ret = Status::OK(); + } + + // Clear the error left by the final PEM_read_bio_X509 call when it returns nullptr. + ERR_clear_error(); + + return ret; +} // function ValidatePemBundle + void IntegrityHash::Compute(const uint8_t* data, int64_t len) { // Explicitly ignore the return value from SHA256(); it can't fail. (void)SHA256(data, len, hash_); diff --git a/be/src/util/openssl-util.h b/be/src/util/openssl-util.h index 88f2c4d1d..b52c0710a 100644 --- a/be/src/util/openssl-util.h +++ b/be/src/util/openssl-util.h @@ -70,6 +70,11 @@ inline bool IsFIPSMode() { #endif }; +/// Validates a PEM-encoded certificate bundle by ensuring the given string contains only +/// certificates with a valid X.509 structure and that each certificate is useable based +/// its notBefore/notAfter fields. Does not validate each certificate's signature chain. +Status ValidatePemBundle(const string& bundle); + // Enum of all the AES modes that are currently supported. INVALID is used whenever // user inputs a wrong AES Mode, for example, AES_128_CTF etc. enum class AES_CIPHER_MODE {
