You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by bo...@apache.org on 2025/07/11 08:19:22 UTC
(impala) 01/02: IMPALA-13237: [Patch 2 of 5] - Add OpenSSL Utility Function to Validate PEM Bundles
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 <jf...@cloudera.com>
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 <im...@cloudera.com>
Reviewed-by: Jason Fehr <jf...@cloudera.com>
Reviewed-by: Alexey Serbin <al...@apache.org>
Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
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 {