kudu-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From a...@apache.org
Subject kudu git commit: KUDU-2091: Certificates with intermediate CA's do not work with Kudu
Date Wed, 16 Aug 2017 22:59:01 GMT
Repository: kudu
Updated Branches:
  refs/heads/master 1e5d334ad -> 18d6f7f4c


KUDU-2091: Certificates with intermediate CA's do not work with Kudu

Kudu previously did not recognize chain certificates. This patch
enables support for chain certificates by changing the Cert class'
underlying data type to STACK_OF(X509) instead of just X509.

STACK_OF(X509) allows multiple certificates to be held by the same
pointer. When we are presented with a file or a string that contains
multiple X509 certificates, they will be stored inside this
STACK_OF(X509) object.

When we call AddTrustedCertificate(Cert&), we iterate through the
STACK_OF(X509) contained in the Cert and add each one individually to
the X509_STORE for later verification.

Currently, IPKI does not make use of this ability and still works
with single certificates. DCHECKS are added to make sure that multiple
X509 certificates are not accidentally added to a Cert object.
Although this patch provides a general framework to use chain
certificates, if we want to use IPKI with chain certificates,
additional functionality will need to be added with clearer APIs.

External PKI makes use of this ability to add a chain CA if necessary.

Testing: A new test is added to rpc-test that uses a chain CA. This
test does not work without this patch.

Change-Id: I7334a5b2f1643848152562bbb1dee27b5290e83f
Reviewed-on: http://gerrit.cloudera.org:8080/7662
Reviewed-by: Todd Lipcon <todd@apache.org>
Tested-by: Todd Lipcon <todd@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/18d6f7f4
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/18d6f7f4
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/18d6f7f4

Branch: refs/heads/master
Commit: 18d6f7f4c112ba890a1ba8722dce85aa3340b1d7
Parents: 1e5d334
Author: Sailesh Mukil <sailesh@apache.org>
Authored: Wed Aug 2 22:39:27 2017 -0700
Committer: Todd Lipcon <todd@apache.org>
Committed: Wed Aug 16 00:21:03 2017 +0000

----------------------------------------------------------------------
 src/kudu/rpc/rpc-test.cc                |  32 ++++-
 src/kudu/security/ca/cert_management.cc |   4 +-
 src/kudu/security/cert.cc               |  70 ++++++---
 src/kudu/security/cert.h                |  36 +++--
 src/kudu/security/openssl_util.cc       |  65 +++++++++
 src/kudu/security/openssl_util.h        |  16 +++
 src/kudu/security/test/test_certs.cc    | 207 +++++++++++++++++++++++++++
 src/kudu/security/test/test_certs.h     |  13 +-
 src/kudu/security/tls_context.cc        |  37 ++---
 src/kudu/security/tls_handshake.cc      |   6 +-
 10 files changed, 431 insertions(+), 55 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/rpc/rpc-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/rpc/rpc-test.cc b/src/kudu/rpc/rpc-test.cc
index 38ad357..3989558 100644
--- a/src/kudu/rpc/rpc-test.cc
+++ b/src/kudu/rpc/rpc-test.cc
@@ -154,6 +154,30 @@ TEST_P(TestRpc, TestCall) {
   }
 }
 
+TEST_P(TestRpc, TestCallWithChainCert) {
+  bool enable_ssl = GetParam();
+  // We're only interested in running this test with TLS enabled.
+  if (!enable_ssl) return;
+
+  ASSERT_OK(security::CreateTestSSLCertSignedByChain(GetTestDataDirectory(),
+                                                     &FLAGS_rpc_certificate_file,
+                                                     &FLAGS_rpc_private_key_file,
+                                                     &FLAGS_rpc_ca_certificate_file));
+  // Set up server.
+  Sockaddr server_addr;
+  StartTestServer(&server_addr, enable_ssl);
+
+  // Set up client.
+  SCOPED_TRACE(strings::Substitute("Connecting to $0", server_addr.ToString()));
+  shared_ptr<Messenger> client_messenger(CreateMessenger("Client", 1, enable_ssl));
+  Proxy p(client_messenger, server_addr, GenericCalculatorService::static_service_name());
+  ASSERT_STR_CONTAINS(p.ToString(), strings::Substitute("kudu.rpc.GenericCalculatorService@"
+                                                            "{remote=$0, user_credentials=",
+                                                        server_addr.ToString()));
+
+  ASSERT_OK(DoTestSyncCall(p, GenericCalculatorService::kAddMethodName));
+}
+
 // Test making successful RPC calls while using a TLS certificate with a password protected
 // private key.
 TEST_P(TestRpc, TestCallWithPasswordProtectedKey) {
@@ -162,7 +186,7 @@ TEST_P(TestRpc, TestCallWithPasswordProtectedKey) {
   if (!enable_ssl) return;
 
   string passwd;
-  CHECK_OK(security::CreateTestSSLCertWithEncryptedKey(GetTestDataDirectory(),
+  ASSERT_OK(security::CreateTestSSLCertWithEncryptedKey(GetTestDataDirectory(),
                                                        &FLAGS_rpc_certificate_file,
                                                        &FLAGS_rpc_private_key_file,
                                                        &passwd));
@@ -173,16 +197,14 @@ TEST_P(TestRpc, TestCallWithPasswordProtectedKey) {
   StartTestServer(&server_addr, enable_ssl);
 
   // Set up client.
-  LOG(INFO) << "Connecting to " << server_addr.ToString();
+  SCOPED_TRACE(strings::Substitute("Connecting to $0", server_addr.ToString()));
   shared_ptr<Messenger> client_messenger(CreateMessenger("Client", 1, enable_ssl));
   Proxy p(client_messenger, server_addr, GenericCalculatorService::static_service_name());
   ASSERT_STR_CONTAINS(p.ToString(), strings::Substitute("kudu.rpc.GenericCalculatorService@"
                                                             "{remote=$0, user_credentials=",
                                                         server_addr.ToString()));
 
-  for (int i = 0; i < 10; i++) {
-    ASSERT_OK(DoTestSyncCall(p, GenericCalculatorService::kAddMethodName));
-  }
+  ASSERT_OK(DoTestSyncCall(p, GenericCalculatorService::kAddMethodName));
 }
 
 // Test that using a TLS certificate with a password protected private key and providing

http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/security/ca/cert_management.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/ca/cert_management.cc b/src/kudu/security/ca/cert_management.cc
index c63eb10..09e59cd 100644
--- a/src/kudu/security/ca/cert_management.cc
+++ b/src/kudu/security/ca/cert_management.cc
@@ -306,7 +306,7 @@ Status CertSigner::Sign(const CertSignRequest& req, Cert* ret) const
{
   auto x509 = ssl_make_unique(X509_new());
   RETURN_NOT_OK(FillCertTemplateFromRequest(req.GetRawData(), x509.get()));
   RETURN_NOT_OK(DoSign(EVP_sha256(), exp_interval_sec_, x509.get()));
-  ret->AdoptRawData(x509.release());
+  ret->AdoptX509(x509.release());
 
   return Status::OK();
 }
@@ -397,7 +397,7 @@ Status CertSigner::DoSign(const EVP_MD* digest, int32_t exp_seconds,
 
   // If we have a CA cert, then the CA is the issuer.
   // Otherwise, we are self-signing so the target cert is also the issuer.
-  X509* issuer_cert = ca_cert_ ? ca_cert_->GetRawData() : ret;
+  X509* issuer_cert = ca_cert_ ? ca_cert_->GetEndOfChainX509() : ret;
   X509_NAME* issuer_name = X509_get_subject_name(issuer_cert);
   OPENSSL_RET_NOT_OK(X509_set_issuer_name(ret, issuer_name),
       "error setting issuer name");

http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/security/cert.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/cert.cc b/src/kudu/security/cert.cc
index e1a2666..61e765c 100644
--- a/src/kudu/security/cert.cc
+++ b/src/kudu/security/cert.cc
@@ -68,8 +68,17 @@ int GetKuduKerberosPrincipalOidNid() {
   return nid;
 }
 
+X509* Cert::GetEndOfChainX509() const {
+  CHECK_GT(chain_len(), 0);
+  return sk_X509_value(data_.get(), chain_len() - 1);
+}
+
 Status Cert::FromString(const std::string& data, DataFormat format) {
-  return ::kudu::security::FromString(data, format, &data_);
+  RETURN_NOT_OK(::kudu::security::FromString(data, format, &data_));
+  if (sk_X509_num(data_.get()) < 1) {
+    return Status::RuntimeError("Certificate chain is empty. Expected at least one certificate.");
+  }
+  return Status::OK();
 }
 
 Status Cert::ToString(std::string* data, DataFormat format) const {
@@ -77,20 +86,24 @@ Status Cert::ToString(std::string* data, DataFormat format) const {
 }
 
 Status Cert::FromFile(const std::string& fpath, DataFormat format) {
-  return ::kudu::security::FromFile(fpath, format, &data_);
+  RETURN_NOT_OK(::kudu::security::FromFile(fpath, format, &data_));
+  if (sk_X509_num(data_.get()) < 1) {
+    return Status::RuntimeError("Certificate chain is empty. Expected at least one certificate.");
+  }
+  return Status::OK();
 }
 
 string Cert::SubjectName() const {
-  return X509NameToString(X509_get_subject_name(data_.get()));
+  return X509NameToString(X509_get_subject_name(GetEndOfChainX509()));
 }
 
 string Cert::IssuerName() const {
-  return X509NameToString(X509_get_issuer_name(data_.get()));
+  return X509NameToString(X509_get_issuer_name(GetEndOfChainX509()));
 }
 
 boost::optional<string> Cert::UserId() const {
   SCOPED_OPENSSL_NO_PENDING_ERRORS;
-  X509_NAME* name = X509_get_subject_name(data_.get());
+  X509_NAME* name = X509_get_subject_name(GetEndOfChainX509());
   char buf[1024];
   int len = X509_NAME_get_text_by_NID(name, NID_userId, buf, arraysize(buf));
   if (len < 0) return boost::none;
@@ -101,7 +114,7 @@ vector<string> Cert::Hostnames() const {
   SCOPED_OPENSSL_NO_PENDING_ERRORS;
   vector<string> result;
   auto gens = ssl_make_unique(reinterpret_cast<GENERAL_NAMES*>(X509_get_ext_d2i(
-      data_.get(), NID_subject_alt_name, nullptr, nullptr)));
+      GetEndOfChainX509(), NID_subject_alt_name, nullptr, nullptr)));
   if (gens) {
     for (int i = 0; i < sk_GENERAL_NAME_num(gens.get()); ++i) {
       GENERAL_NAME* gen = sk_GENERAL_NAME_value(gens.get(), i);
@@ -121,9 +134,9 @@ vector<string> Cert::Hostnames() const {
 
 boost::optional<string> Cert::KuduKerberosPrincipal() const {
   SCOPED_OPENSSL_NO_PENDING_ERRORS;
-  int idx = X509_get_ext_by_NID(data_.get(), GetKuduKerberosPrincipalOidNid(), -1);
+  int idx = X509_get_ext_by_NID(GetEndOfChainX509(), GetKuduKerberosPrincipalOidNid(), -1);
   if (idx < 0) return boost::none;
-  X509_EXTENSION* ext = X509_get_ext(data_.get(), idx);
+  X509_EXTENSION* ext = X509_get_ext(GetEndOfChainX509(), idx);
   ASN1_OCTET_STRING* octet_str = X509_EXTENSION_get_data(ext);
   const unsigned char* octet_str_data = octet_str->data;
   long len; // NOLINT(runtime/int)
@@ -139,7 +152,7 @@ boost::optional<string> Cert::KuduKerberosPrincipal() const {
 
 Status Cert::CheckKeyMatch(const PrivateKey& key) const {
   SCOPED_OPENSSL_NO_PENDING_ERRORS;
-  OPENSSL_RET_NOT_OK(X509_check_private_key(data_.get(), key.GetRawData()),
+  OPENSSL_RET_NOT_OK(X509_check_private_key(GetEndOfChainX509(), key.GetRawData()),
                      "certificate does not match private key");
   return Status::OK();
 }
@@ -150,12 +163,12 @@ Status Cert::GetServerEndPointChannelBindings(string* channel_bindings)
const {
   // (hash) algorithm, and the public key type which signed the cert.
 
 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
-  int signature_nid = X509_get_signature_nid(data_.get());
+  int signature_nid = X509_get_signature_nid(GetEndOfChainX509());
 #else
   // Older version of OpenSSL appear not to have a public way to get the
   // signature digest method from a certificate. Instead, we reach into the
   // 'private' internals.
-  int signature_nid = OBJ_obj2nid(data_->sig_alg->algorithm);
+  int signature_nid = OBJ_obj2nid(GetEndOfChainX509()->sig_alg->algorithm);
 #endif
 
   // Retrieve the digest algorithm type.
@@ -202,18 +215,43 @@ Status Cert::GetServerEndPointChannelBindings(string* channel_bindings)
const {
   return Status::OK();
 }
 
-void Cert::AdoptAndAddRefRawData(X509* data) {
+void Cert::AdoptAndAddRefRawData(RawDataType* data) {
+  DCHECK_EQ(sk_X509_num(data), 1);
+  X509* cert = sk_X509_value(data, sk_X509_num(data) - 1);
+
+  DCHECK(cert);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+  CHECK_GT(CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509), 1) << "X509 use-after-free
detected";
+#else
+  OPENSSL_CHECK_OK(X509_up_ref(cert)) << "X509 use-after-free detected: " <<
GetOpenSSLErrors();
+#endif
+  // We copy the STACK_OF() object, but the copy and the original both internally point to
the
+  // same elements.
+  AdoptRawData(sk_X509_dup(data));
+}
+
+void Cert::AdoptX509(X509* cert) {
+  // Free current STACK_OF(X509).
+  sk_X509_pop_free(data_.get(), X509_free);
+  // Allocate new STACK_OF(X509) and populate with 'cert'.
+  STACK_OF(X509)* sk = sk_X509_new_null();
+  DCHECK(sk);
+  sk_X509_push(sk, cert);
+  AdoptRawData(sk);
+}
+
+void Cert::AdoptAndAddRefX509(X509* cert) {
 #if OPENSSL_VERSION_NUMBER < 0x10100000L
-  CHECK_GT(CRYPTO_add(&data->references, 1, CRYPTO_LOCK_X509), 1) << "X509 use-after-free
detected";
+  CHECK_GT(CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509), 1) << "X509 use-after-free
detected";
 #else
-  OPENSSL_CHECK_OK(X509_up_ref(data)) << "X509 use-after-free detected: " <<
GetOpenSSLErrors();
+  OPENSSL_CHECK_OK(X509_up_ref(cert)) << "X509 use-after-free detected: " <<
GetOpenSSLErrors();
 #endif
-  AdoptRawData(data);
+  AdoptX509(cert);
 }
 
 Status Cert::GetPublicKey(PublicKey* key) const {
   SCOPED_OPENSSL_NO_PENDING_ERRORS;
-  EVP_PKEY* raw_key = X509_get_pubkey(data_.get());
+  EVP_PKEY* raw_key = X509_get_pubkey(GetEndOfChainX509());
   OPENSSL_RET_IF_NULL(raw_key, "unable to get certificate public key");
   key->AdoptRawData(raw_key);
   return Status::OK();

http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/security/cert.h
----------------------------------------------------------------------
diff --git a/src/kudu/security/cert.h b/src/kudu/security/cert.h
index 2facb2c..4d93d1b 100644
--- a/src/kudu/security/cert.h
+++ b/src/kudu/security/cert.h
@@ -40,37 +40,53 @@ std::string X509NameToString(X509_NAME* name);
 // our Kerberos principal in IPKI certs.
 int GetKuduKerberosPrincipalOidNid();
 
-class Cert : public RawDataWrapper<X509> {
+// A wrapper class around the STACK_OF(X509) object. This can either hold one certificate
or
+// a chain of certificates.
+// TODO(unknown): Currently, there isn't a mechanism to add to the chain. Implement it when
needed.
+class Cert : public RawDataWrapper<STACK_OF(X509)> {
  public:
   Status FromString(const std::string& data, DataFormat format) WARN_UNUSED_RESULT;
   Status ToString(std::string* data, DataFormat format) const WARN_UNUSED_RESULT;
   Status FromFile(const std::string& fpath, DataFormat format) WARN_UNUSED_RESULT;
 
+  int chain_len() const { return sk_X509_num(data_.get()); }
+
   std::string SubjectName() const;
   std::string IssuerName() const;
 
-  // Return DNS names from the SAN extension field.
+  // Return DNS names from the SAN extension field of the end-user cert.
   std::vector<std::string> Hostnames() const;
 
-  // Return the 'userId' extension of this cert, if set.
+  // Return the 'userId' extension of the end-user cert, if set.
   boost::optional<std::string> UserId() const;
 
-  // Return the Kerberos principal encoded in this certificate, if set.
+  // Return the Kerberos principal encoded in the end-user certificate, if set.
   boost::optional<std::string> KuduKerberosPrincipal() const;
 
-  // Check whether the specified private key matches the certificate.
-  // Return Status::OK() if key match the certificate.
+  // Check whether the specified private key matches the end-user certificate.
+  // Return Status::OK() if key match the end-user certificate.
   Status CheckKeyMatch(const PrivateKey& key) const WARN_UNUSED_RESULT;
 
-  // Returns the 'tls-server-end-point' channel bindings for the certificate as
+  // Returns the 'tls-server-end-point' channel bindings for the end-user certificate as
   // specified in RFC 5929.
   Status GetServerEndPointChannelBindings(std::string* channel_bindings) const WARN_UNUSED_RESULT;
 
-  // Adopts the provided X509 certificate, and increments the reference count.
-  void AdoptAndAddRefRawData(X509* data);
+  // Adopts the provided STACK_OF(X509), and increments the reference count of the X509 cert
+  // contained within it. Currently, only one certificate should be contained in the stack.
+  void AdoptAndAddRefRawData(RawDataType* data);
+
+  // Adopts the provided X509 certificate, and replaces the current underlying STACK_OF(X509).
+  void AdoptX509(X509* cert);
 
-  // Returns the certificate's public key.
+  // Adopts the provided X509 certificate, increments its reference count and replaces the
current
+  // underlying STACK_OF(X509).
+  void AdoptAndAddRefX509(X509* cert);
+
+  // Returns the end-user certificate's public key.
   Status GetPublicKey(PublicKey* key) const WARN_UNUSED_RESULT;
+
+  // Get the last certificate in the chain, otherwise known as the 'end-user' certificate.
+  X509* GetEndOfChainX509() const;
 };
 
 class CertSignRequest : public RawDataWrapper<X509_REQ> {

http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/security/openssl_util.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/openssl_util.cc b/src/kudu/security/openssl_util.cc
index b0b690c..861fff4 100644
--- a/src/kudu/security/openssl_util.cc
+++ b/src/kudu/security/openssl_util.cc
@@ -34,6 +34,7 @@
 #include "kudu/util/debug/leakcheck_disabler.h"
 #include "kudu/util/errno.h"
 #include "kudu/util/mutex.h"
+#include "kudu/util/scoped_cleanup.h"
 #include "kudu/util/status.h"
 #include "kudu/util/subprocess.h"
 #include "kudu/util/thread.h"
@@ -139,6 +140,70 @@ void DoInitializeOpenSSL() {
 
 } // anonymous namespace
 
+// Reads a STACK_OF(X509) from the BIO and returns it.
+STACK_OF(X509)* PEM_read_STACK_OF_X509(BIO* bio, void* /* unused */, pem_password_cb* /*
unused */,
+    void* /* unused */) {
+  // Extract information from the chain certificate.
+  STACK_OF(X509_INFO)* info = PEM_X509_INFO_read_bio(bio, nullptr, nullptr, nullptr);
+  if (!info) return nullptr;
+  auto cleanup = MakeScopedCleanup([&]() {
+    sk_X509_INFO_pop_free(info, X509_INFO_free);
+  });
+
+  // Initialize the Stack.
+  STACK_OF(X509)* sk = sk_X509_new_null();
+
+  // Iterate through the chain certificate and add each one to the stack.
+  for (int i = 0; i < sk_X509_INFO_num(info); ++i) {
+    X509_INFO *stack_item = sk_X509_INFO_value(info, i);
+    sk_X509_push(sk, stack_item->x509);
+    // We don't want the ScopedCleanup to free the x509 certificates as well since we will
+    // use it as a part of the STACK_OF(X509) object to be returned, so we set it to nullptr.
+    // We will take the responsibility of freeing it when we are done with the STACK_OF(X509).
+    stack_item->x509 = nullptr;
+  }
+  return sk;
+}
+
+// Writes a STACK_OF(X509) to the BIO.
+int PEM_write_STACK_OF_X509(BIO* bio, STACK_OF(X509)* obj) {
+  int chain_len = sk_X509_num(obj);
+  // Iterate through the stack and add each one to the BIO.
+  for (int i = 0; i < chain_len; ++i) {
+    X509* cert_item = sk_X509_value(obj, i);
+    int ret = PEM_write_bio_X509(bio, cert_item);
+    if (ret <= 0) return ret;
+  }
+  return 1;
+}
+
+// Reads a single X509 certificate and returns a STACK_OF(X509) with the single certificate.
+STACK_OF(X509)* DER_read_STACK_OF_X509(BIO* bio, void* /* unused */) {
+  // We don't support chain certificates written in DER format.
+  auto x = ssl_make_unique(d2i_X509_bio(bio, nullptr));
+  if (!x) return nullptr;
+  STACK_OF(X509)* sk = sk_X509_new_null();
+  if (sk_X509_push(sk, x.get()) == 0) {
+    return nullptr;
+  }
+  x.release();
+  return sk;
+}
+
+// Writes a single X509 certificate that it gets from the STACK_OF(X509) 'obj'.
+int DER_write_STACK_OF_X509(BIO* bio, STACK_OF(X509)* obj) {
+  int chain_len = sk_X509_num(obj);
+  // We don't support chain certificates written in DER format.
+  DCHECK_EQ(chain_len, 1);
+  X509* cert_item = sk_X509_value(obj, 0);
+  if (cert_item == nullptr) return 0;
+  return i2d_X509_bio(bio, cert_item);
+}
+
+void free_STACK_OF_X509(STACK_OF(X509)* sk) {
+  sk_X509_pop_free(sk, X509_free);
+}
+
 Status DisableOpenSSLInitialization() {
   if (g_disable_ssl_init) return Status::OK();
   if (g_ssl_is_initialized) {

http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/security/openssl_util.h
----------------------------------------------------------------------
diff --git a/src/kudu/security/openssl_util.h b/src/kudu/security/openssl_util.h
index aa493ff..04fb7cc 100644
--- a/src/kudu/security/openssl_util.h
+++ b/src/kudu/security/openssl_util.h
@@ -121,6 +121,22 @@ template<> struct SslTypeTraits<X509> {
   static constexpr auto kWritePemFunc = &PEM_write_bio_X509;
   static constexpr auto kWriteDerFunc = &i2d_X509_bio;
 };
+
+// SslTypeTraits functions for Type STACK_OF(X509)
+STACK_OF(X509)* PEM_read_STACK_OF_X509(BIO* bio, void* /* unused */,
+    pem_password_cb* /* unused */, void* /* unused */);
+int PEM_write_STACK_OF_X509(BIO* bio, STACK_OF(X509)* obj);
+STACK_OF(X509)* DER_read_STACK_OF_X509(BIO* bio, void* /* unused */);
+int DER_write_STACK_OF_X509(BIO* bio, STACK_OF(X509)* obj);
+void free_STACK_OF_X509(STACK_OF(X509)* sk);
+
+template<> struct SslTypeTraits<STACK_OF(X509)> {
+  static constexpr auto kFreeFunc = &free_STACK_OF_X509;
+  static constexpr auto kReadPemFunc = &PEM_read_STACK_OF_X509;
+  static constexpr auto kReadDerFunc = &DER_read_STACK_OF_X509;
+  static constexpr auto kWritePemFunc = &PEM_write_STACK_OF_X509;
+  static constexpr auto kWriteDerFunc = &DER_write_STACK_OF_X509;
+};
 template<> struct SslTypeTraits<X509_EXTENSION> {
   static constexpr auto kFreeFunc = &X509_EXTENSION_free;
 };

http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/security/test/test_certs.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/test/test_certs.cc b/src/kudu/security/test/test_certs.cc
index 8b10cad..d7abb3e 100644
--- a/src/kudu/security/test/test_certs.cc
+++ b/src/kudu/security/test/test_certs.cc
@@ -496,5 +496,212 @@ TOQYXv+dMtOkYg==
   return Status::OK();
 }
 
+//
+// These certificates were generated by following the steps outlined in this tutorial
+// for creating the Root CA, Intermediate CA and end-user cert:
+// https://raymii.org/s/tutorials/ \
+// OpenSSL_command_line_Root_and_Intermediate_CA_including_OCSP_CRL%20and_revocation.html
+//
+// The parts relating to the OSCP and CRL were omitted.
+Status CreateTestSSLCertSignedByChain(const string& dir,
+                                      string* cert_file,
+                                      string* key_file,
+                                      string* ca_cert_file) {
+  const char* kCert = R"(
+-----BEGIN CERTIFICATE-----
+MIIFizCCA3OgAwIBAgICEAAwDQYJKoZIhvcNAQEFBQAwUTEXMBUGA1UEAwwOSW50
+ZXJtZWRpYXRlQ0ExCzAJBgNVBAgMAkNBMQswCQYDVQQGEwJVUzENMAsGA1UECgwE
+QWNtZTENMAsGA1UECwwES3VkdTAeFw0xNzA4MTEyMTM4MDZaFw00NDEyMjYyMTM4
+MDZaMEwxEjAQBgNVBAMMCWxvY2FsaG9zdDELMAkGA1UECAwCQ0ExCzAJBgNVBAYT
+AlVTMQ0wCwYDVQQKDARBY21lMQ0wCwYDVQQLDARLdWR1MIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAqevNYH73n4kARZtMsHRucdKmqVd/xxztMlK5VOor
+ERUBhKVVOw3kpDrN9z80ldIkpOrtrfE7Ame/nA9v4k6P3minPEm1qCA/kvaAodtT
+4HjAkrPc+fto6VO6+aUV6l+ckAV/79lOuc7AutNlvvPtBQQcgOKvlNUSRKwM7ndy
+dO4ZAa+uP9Wtsd0gl8b5F3P8vwevD3a0+iDvwSd3pi2s/BeVgRwvOxJzud8ipZ/A
+ZmZN8Df9nHw5lsqLdNnqHXjTVCNXLnYXQC4gKU56fzyZL595liuefyQxiGY+dCCn
+CpqlSsHboJVC/F3OaQi3xVRTB5l2Nwb149EIadwCF0OulZCuYljJ5y9H2bECXEjP
+e5aOdz9d8W3/T7p9vBKWctToeCpqKXUd+8RPudh0D0sUHuwQ4u4S1K6X+eK+gGhT
+HOnPwt+P8ytG0M463z5Gh9feW9ZDIYoiFckheFBAHxsgDWhjYpFmYireLLXMbyaM
+s5v/AxPNRAsx3vAAd0M0vGOpdgEJ9V1MsKmxkPO/tDC3zmnv6uJhtJfrOAKxwiGC
+fDe4IoSC6H5fTxeAgw6BG5onS1UPLADL8NA/M1y8qiSCZS/5S0cHoJp5AxDfZSSR
+O49ispjqcONRwckcRJ5Pbl0IA+wGyg2DuI9LaqS5kKWp5AE8VCLPz7yepDkRnnjO
+3m8CAwEAAaNyMHAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUZBZLZZaUfyIK/8B7
+GIIWDqeEvDgwHwYDVR0jBBgwFoAU8KctfaqAq0887CHqDsIC0Rkg7oQwCwYDVR0P
+BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBBQUAA4ICAQA3
+XJXk9CbzdZUQugPI43LY88g+WjbTJfc/KtPSkHN3GjBBh8C0He7A2tp6Xj/LELmx
+crq62FzcFBnq8/iSdFITaYWRo0V/mXlpv2cpPebtwqbARCXUHGvF4/dGk/kw7uK/
+ohZJbeNySuQmQ5SQyfTdVA30Z0OSZ4jp24jC8uME7L8XOcFDgCRw01QNOISpi/5J
+BqeuFihmu/odYMHiEJdCXqe+4qIFfTh0mbgQ57l/geZm0K8uCEiOdTzSMoO8YdO2
+tm6EGNnc4yrVywjIHIvSy6YtNzd4ZM1a1CkEfPvGwe/wI1DI/zl3aJ721kcMPken
+rgEA4xXTPh6gZNMELIGZfu/mOTCFObe8rrh4QSaW4L+xa/VrLEnQRxuXAYGnmDWF
+e79aA+uXdS4+3OysNgEf4qDBt/ZquS/31DBdfJ59VfXWxp2yxMcGhcfiOdnx2Jy5
+KO8wdpXJA/7uwTJzsjLrIgfZnserOiBwE4luaHhDmKDGNVQvhkMq5tdtMdzuwn3/
+n6P1UwbFPiRGIzEAo0SSC1PRT8phv+5y0B1+gcj/peFymZVE+gRcrv9irVQqUpAY
+Lo9xrClAJ2xx4Ouz1GprKPoHdVyqtgcLXN4Oyi8Tehu96Zf6GytSEfTXsbQp+GgR
+TGRhKnDySjPhLp/uObfVwioyuAyA5mVCwjsZ/cvUUA==
+-----END CERTIFICATE-----
+)";
+  const char* kKey = R"(
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAqevNYH73n4kARZtMsHRucdKmqVd/xxztMlK5VOorERUBhKVV
+Ow3kpDrN9z80ldIkpOrtrfE7Ame/nA9v4k6P3minPEm1qCA/kvaAodtT4HjAkrPc
++fto6VO6+aUV6l+ckAV/79lOuc7AutNlvvPtBQQcgOKvlNUSRKwM7ndydO4ZAa+u
+P9Wtsd0gl8b5F3P8vwevD3a0+iDvwSd3pi2s/BeVgRwvOxJzud8ipZ/AZmZN8Df9
+nHw5lsqLdNnqHXjTVCNXLnYXQC4gKU56fzyZL595liuefyQxiGY+dCCnCpqlSsHb
+oJVC/F3OaQi3xVRTB5l2Nwb149EIadwCF0OulZCuYljJ5y9H2bECXEjPe5aOdz9d
+8W3/T7p9vBKWctToeCpqKXUd+8RPudh0D0sUHuwQ4u4S1K6X+eK+gGhTHOnPwt+P
+8ytG0M463z5Gh9feW9ZDIYoiFckheFBAHxsgDWhjYpFmYireLLXMbyaMs5v/AxPN
+RAsx3vAAd0M0vGOpdgEJ9V1MsKmxkPO/tDC3zmnv6uJhtJfrOAKxwiGCfDe4IoSC
+6H5fTxeAgw6BG5onS1UPLADL8NA/M1y8qiSCZS/5S0cHoJp5AxDfZSSRO49ispjq
+cONRwckcRJ5Pbl0IA+wGyg2DuI9LaqS5kKWp5AE8VCLPz7yepDkRnnjO3m8CAwEA
+AQKCAgAE3oL2Hu1Nnwlo9ThPXibEEDtCYwWAWS3a4U/6RPOS+70dZfd5R76jjiPU
+z/TbzjfKmgjRkTYVrY9qE28rVwD8aJdSPPJ9rN7lgTbSbIyMxCkQiyLr7u5ksUeM
+W9Sy8KZ14hJ2dw2weWJAeEpUHH1QRXvjnZtWcnyhhySfuMCI5UHGMJiXr7HYhPOo
+JcWBjItTlg7ILKim+kakjFL7aheo6awZFQutb6vtSZ2ejWNgC9Jz7cbQsyabUZaJ
+dK0mxw2XPaQD6tJjvm6hgGQ2PTBOkw1S5lEWZ50bwYJMpZrjzOarq751bZGL1cxS
+ajOJ7g6rCxS+Iu7s5lKNZgaRUBkymATYccoigfZDR//fHKAmdgjgPstqy1NJL+uX
+bIuNE0bR+mBM2JQzNjPIcE67PG+0aQdO4dj0TnTzkTP1JSsa6Tz4ckOUgt7IBK8j
+ilCQpHgOB900hXC6xVRnAaU/uuSYEtfi2eFBKHT02OqH51yNZ2jsgOQJCvxNrrdI
+OmA0AaZ2tVXTTXe6qebaNjjp2cziiO5ma+5mdI1vmLQAA9v0micO+eKp/pi5e0r6
+j+9uyR2Oo4qnHg04TIfDyglW3uKz1eI0RPfBN/dx3WLpydxKeywXPH6EzfBFk8pz
+ST2sy+1ZN4vn0bDSTjSLsLBW+xBKYINqKkBD2Kg6B7aeNINjEQKCAQEA4BQByyZV
+9va91T+rQiNPifuI4PKgmLTl0wxM1Jg/H4YCyLtuEkmuvwfeAjaUaUuk2MDs3xfQ
+4pu8vtAnDapq5vJ/lMg0m3+NIwoks+xee9F//M4du9j67USvX5Qw/6Cnx1zAvrWR
+TyZncKUocQowcXM9VU0xcv4qVCMaH5F5Q/1VvG7uAtGCnB8TOHzV7GUaj9xxSqDc
+f3+p9BzsecpPZsdpVi01dQbCKi9neZwFv1Vr3MvieNDOGqSGn8X5EjSHY5PzCaXL
+S+/HoFWOSzWcuNdzKJRjVkC8U+eHoEabaRnD47dfJntN3qOQ6Mwf6P2jMN9GqlQu
+DQlvpMxBwQT1/QKCAQEAwiC4hr6EZKaLmeZBLggsS+oifHReXQy6tf2n7kCAwAgL
++65K8UW+YUtQyHL6UFfD27vvW5yp6LZxMRUD3ghguHPMQcejgoQTfGmth1bCb9tx
+zqfxuWUoNauqZiP4/kgxoh815Kt+kC8BRCXNIWjH38zxY+ncv0b4oKP7lYna/7Xk
+URLmMFr92QVAydRxY9kQTHQTCd+ZQrFT97xEoosgzkKhmynSfWNx7ymYmCrHzscA
+TutpD26u4CA4Wh4ZdVPEF10lGR531SAFEqXCkaUvIfwPphPmOtum2LZdEYho9C76
+71kLzzoJOJUNo2L9ORd5I4tOrMT0tmN+MpS1cPXb2wKCAQAu3aBeZ9807vhXQKDG
+DXKWTmibe8OBDNzAnmL3V/xj0HiGmUT1SDnnNHMHjXjO6QZKW1dvdaC3tJDua8Sv
+RARl1zQ93v25xBy1xmpUw0wjo3acXlOztTcOJv5zBCCXZneQ5+JcQMdqgYLC+ZgS
+xGnLYKnkTGfaQDSEMm9FSPzO7o5fAeh/6Gfj1VAE0X9AmQjMK/P6Atj7Ra07JE2F
+T3355h0u6/exST+U6SNAORSupuQPYwkz8aAZzG1nv1VPrHLgrdH4I5f4gucCrsI7
+ErR7qHwqcZaxNIrvFY61Q+8/NSdWWkTpXIK13Qny1raZ2WqnTxuNhlu3WFDka+AY
+ybvVAoIBACOaxL1R7A5ZzXjolkPPE/DIfJK+9R+z2frPDyHPis2trCT5Dp254PUP
+Tz20eNyLfEys53WyAifAbnpGFHOArdymwGvAJekmODy1VTJhY0AIy5LPkrIiL4HI
+fnRFXMGmlBPcDZJnMctYE69gD4N1KFOPzyY4Gliqt6ce7GG86wHDZqDICpgL2EsZ
+f4yE/lcF1Mtw7pz8+asVwwTI7v2w7s9lwSYoQYbl2lu3EVm3XvY54YCYBKjj8AcD
+YdKFer3eIzT1zHwS7n+UY9nLtSfpV/+vr18Sf0OETdGpgOBaWIWQqE2F03iqeE58
+aAfze+YgvAMc5c0iQo/BJ8A3LiANt8kCggEBAK8cFEBm+Z1g5s1IaWxqTIylR5XF
+5RFty9HyUXtkAd2d1qVzBaBG3SXg4cRsW0EhcUV2XP3iTFIPXEEABRRu5U6DEal6
+wQclrhfP4hiRQHp2Ny6jDj70NCSeUmyEu2lmwEJJYsDSOCnVmtt+qlgmk4yI1Dua
+nXhLcPLqopuhEZs2V/l2Q6E5i4vrs71Y7of+vsAvvt42Vx5wsGdPQihc5E1MI7GB
+hxmQys1MwG3Jyd7Zk88MVNveASeEIc7UAmr/TGL+RIv4bxNi/1HrgekBf1jnFUU3
+4fsdqKy0W+rOgOlGN8TX7NYCz3B41UEiyf+/gZ/TcLKAyGnoAO727Ngayqo=
+-----END RSA PRIVATE KEY-----
+)";
+  const char* kCaChainCert = R"(
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+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
+Wd40Cr+wAdHKN6t/oransoxu0EZ3HcSOI1umFg==
+-----END CERTIFICATE-----
+)";
+
+  *cert_file = JoinPathSegments(dir, "test.cert");
+  *key_file = JoinPathSegments(dir, "test.key");
+  *ca_cert_file = JoinPathSegments(dir, "testchainca.cert");
+
+  RETURN_NOT_OK(WriteStringToFile(Env::Default(), kCert, *cert_file));
+  RETURN_NOT_OK(WriteStringToFile(Env::Default(), kKey, *key_file));
+  RETURN_NOT_OK(WriteStringToFile(Env::Default(), kCaChainCert, *ca_cert_file));
+  return Status::OK();
+}
+
 } // namespace security
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/security/test/test_certs.h
----------------------------------------------------------------------
diff --git a/src/kudu/security/test/test_certs.h b/src/kudu/security/test/test_certs.h
index 75e658e..89d1654 100644
--- a/src/kudu/security/test/test_certs.h
+++ b/src/kudu/security/test/test_certs.h
@@ -64,9 +64,16 @@ Status CreateTestSSLCertWithPlainKey(const std::string& dir,
 // Same as the CreateTestSSLCertWithPlainKey() except that the private key is
 // encrypted with a password that is returned in 'key_password'.
 Status CreateTestSSLCertWithEncryptedKey(const std::string& dir,
-                                          std::string* cert_file,
-                                          std::string* key_file,
-                                          std::string* key_password);
+                                         std::string* cert_file,
+                                         std::string* key_file,
+                                         std::string* key_password);
+
+// Same as the CreateTestSSLCertWithPlainKey() except that the 'cert_file' is
+// signed by a CA chain.
+Status CreateTestSSLCertSignedByChain(const std::string& dir,
+                                      std::string* cert_file,
+                                      std::string* key_file,
+                                      std::string* ca_cert_file);
 
 } // namespace security
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/security/tls_context.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/tls_context.cc b/src/kudu/security/tls_context.cc
index 5c7a7fc..69ef67c 100644
--- a/src/kudu/security/tls_context.cc
+++ b/src/kudu/security/tls_context.cc
@@ -177,7 +177,7 @@ Status TlsContext::VerifyCertChainUnlocked(const Cert& cert) {
   X509_STORE* store = SSL_CTX_get_cert_store(ctx_.get());
   auto store_ctx = ssl_make_unique<X509_STORE_CTX>(X509_STORE_CTX_new());
 
-  OPENSSL_RET_NOT_OK(X509_STORE_CTX_init(store_ctx.get(), store, cert.GetRawData(), nullptr),
+  OPENSSL_RET_NOT_OK(X509_STORE_CTX_init(store_ctx.get(), store, cert.GetEndOfChainX509(),
nullptr),
                      "could not init X509_STORE_CTX");
   int rc = X509_verify_cert(store_ctx.get());
   if (rc != 1) {
@@ -221,7 +221,7 @@ Status TlsContext::UseCertificateAndKey(const Cert& cert, const PrivateKey&
key)
 
   OPENSSL_RET_NOT_OK(SSL_CTX_use_PrivateKey(ctx_.get(), key.GetRawData()),
                      "failed to use private key");
-  OPENSSL_RET_NOT_OK(SSL_CTX_use_certificate(ctx_.get(), cert.GetRawData()),
+  OPENSSL_RET_NOT_OK(SSL_CTX_use_certificate(ctx_.get(), cert.GetEndOfChainX509()),
                      "failed to use certificate");
   has_cert_ = true;
   return Status::OK();
@@ -249,17 +249,22 @@ Status TlsContext::AddTrustedCertificate(const Cert& cert) {
 
   unique_lock<RWMutex> lock(lock_);
   auto* cert_store = SSL_CTX_get_cert_store(ctx_.get());
-  int rc = X509_STORE_add_cert(cert_store, cert.GetRawData());
-  if (rc <= 0) {
-    // Ignore the common case of re-adding a cert that is already in the
-    // trust store.
-    auto err = ERR_peek_error();
-    if (ERR_GET_LIB(err) == ERR_LIB_X509 &&
-        ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE) {
-      ERR_clear_error();
-      return Status::OK();
+
+  // Iterate through the certificate chain and add each individual certificate to the store.
+  for (int i = 0; i < cert.chain_len(); ++i) {
+    X509* inner_cert = sk_X509_value(cert.GetRawData(), i);
+    int rc = X509_STORE_add_cert(cert_store, inner_cert);
+    if (rc <= 0) {
+      // Ignore the common case of re-adding a cert that is already in the
+      // trust store.
+      auto err = ERR_peek_error();
+      if (ERR_GET_LIB(err) == ERR_LIB_X509 &&
+          ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE) {
+        ERR_clear_error();
+        return Status::OK();
+      }
+      OPENSSL_RET_NOT_OK(rc, "failed to add trusted certificate");
     }
-    OPENSSL_RET_NOT_OK(rc, "failed to add trusted certificate");
   }
   trusted_cert_count_ += 1;
   return Status::OK();
@@ -280,7 +285,7 @@ Status TlsContext::DumpTrustedCerts(vector<string>* cert_ders) const
{
     X509_OBJECT* obj = sk_X509_OBJECT_value(cert_store->objs, i);
     if (obj->type != X509_LU_X509) continue;
     Cert c;
-    c.AdoptAndAddRefRawData(obj->data.x509);
+    c.AdoptAndAddRefX509(obj->data.x509);
     string der;
     RETURN_NOT_OK(c.ToString(&der, DataFormat::DER));
     ret.emplace_back(std::move(der));
@@ -345,7 +350,7 @@ Status TlsContext::GenerateSelfSignedCertAndKey() {
   // a leak. Calling this nonsense X509_check_ca() forces the X509 extensions to
   // get cached, so we don't hit the race later. 'VerifyCertChain' also has the
   // effect of triggering the racy codepath.
-  ignore_result(X509_check_ca(cert.GetRawData()));
+  ignore_result(X509_check_ca(cert.GetEndOfChainX509()));
   ERR_clear_error(); // in case it left anything on the queue.
 
   // Step 4: Adopt the new key and cert.
@@ -353,7 +358,7 @@ Status TlsContext::GenerateSelfSignedCertAndKey() {
   CHECK(!has_cert_);
   OPENSSL_RET_NOT_OK(SSL_CTX_use_PrivateKey(ctx_.get(), key.GetRawData()),
                      "failed to use private key");
-  OPENSSL_RET_NOT_OK(SSL_CTX_use_certificate(ctx_.get(), cert.GetRawData()),
+  OPENSSL_RET_NOT_OK(SSL_CTX_use_certificate(ctx_.get(), cert.GetEndOfChainX509()),
                      "failed to use certificate");
   has_cert_ = true;
   csr_ = std::move(csr);
@@ -393,7 +398,7 @@ Status TlsContext::AdoptSignedCert(const Cert& cert) {
     return Status::RuntimeError("certificate public key does not match the CSR public key");
   }
 
-  OPENSSL_RET_NOT_OK(SSL_CTX_use_certificate(ctx_.get(), cert.GetRawData()),
+  OPENSSL_RET_NOT_OK(SSL_CTX_use_certificate(ctx_.get(), cert.GetEndOfChainX509()),
                      "failed to use certificate");
 
   // This should never fail since we already compared the cert's public key

http://git-wip-us.apache.org/repos/asf/kudu/blob/18d6f7f4/src/kudu/security/tls_handshake.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/tls_handshake.cc b/src/kudu/security/tls_handshake.cc
index e566f5d..b5f4651 100644
--- a/src/kudu/security/tls_handshake.cc
+++ b/src/kudu/security/tls_handshake.cc
@@ -139,7 +139,7 @@ Status TlsHandshake::Verify(const Socket& socket) const {
   }
 
   // Get the peer certificate.
-  X509* cert = remote_cert_.GetRawData();
+  X509* cert = remote_cert_.GetEndOfChainX509();
   if (!cert) {
     if (SSL_get_verify_mode(ssl_.get()) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
       return Status::NotAuthorized("Handshake failed: unable to retreive peer certificate");
@@ -187,12 +187,12 @@ Status TlsHandshake::GetCerts() {
   if (cert) {
     // For whatever reason, SSL_get_certificate (unlike SSL_get_peer_certificate)
     // does not increment the X509's reference count.
-    local_cert_.AdoptAndAddRefRawData(cert);
+    local_cert_.AdoptAndAddRefX509(cert);
   }
 
   cert = SSL_get_peer_certificate(ssl_.get());
   if (cert) {
-    remote_cert_.AdoptRawData(cert);
+    remote_cert_.AdoptX509(cert);
   }
   return Status::OK();
 }


Mime
View raw message