You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by he...@apache.org on 2017/08/15 00:46:43 UTC
[4/6] incubator-impala git commit: IMPALA-4669: [SECURITY] Import
Kudu security library from kudu@314c9d8
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/test/mini_kdc.h
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/test/mini_kdc.h b/be/src/kudu/security/test/mini_kdc.h
new file mode 100644
index 0000000..f80aac1
--- /dev/null
+++ b/be/src/kudu/security/test/mini_kdc.h
@@ -0,0 +1,133 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <glog/logging.h>
+
+#include "kudu/util/status.h"
+
+namespace kudu {
+
+class Env;
+class Subprocess;
+
+struct MiniKdcOptions {
+
+ // Kerberos Realm.
+ // Default: "KRBTEST.COM"
+ std::string realm;
+
+ // Directory in which to store data.
+ // Default: "", which auto-generates a unique path for this KDC.
+ // The default may only be used from a gtest unit test.
+ std::string data_root;
+
+ // KDC port.
+ // Default: 0 (ephemeral port).
+ uint16_t port = 0;
+
+ // The default lifetime for initial ticket requests.
+ std::string ticket_lifetime;
+
+ // The default renewable lifetime for initial ticket requests.
+ std::string renew_lifetime;
+
+ // Returns a string representation of the options suitable for debug printing.
+ std::string ToString() const;
+};
+
+class MiniKdc {
+ public:
+ // Creates a new MiniKdc with the default options.
+ MiniKdc();
+
+ // Creates a new MiniKdc with the provided options.
+ explicit MiniKdc(const MiniKdcOptions& options);
+
+ ~MiniKdc();
+
+ // Starts the mini Kerberos KDC.
+ Status Start() WARN_UNUSED_RESULT;
+
+ // Stops the mini Kerberos KDC.
+ Status Stop() WARN_UNUSED_RESULT;
+
+ uint16_t port() const {
+ CHECK(kdc_process_) << "must start first";
+ return options_.port;
+ }
+
+ // Creates a new user with the given username.
+ // The password is the same as the username.
+ Status CreateUserPrincipal(const std::string& username) WARN_UNUSED_RESULT;
+
+ // Creates a new service principal and associated keytab, returning its
+ // path in 'path'. 'spn' is the desired service principal name
+ // (e.g. "kudu/foo.example.com"). If the principal already exists, its key
+ // will be reset and a new keytab will be generated.
+ Status CreateServiceKeytab(const std::string& spn, std::string* path);
+
+ // Kinit a user to the mini KDC.
+ Status Kinit(const std::string& username) WARN_UNUSED_RESULT;
+
+ // Destroy any credentials in the current ticket cache.
+ // Equivalent to 'kdestroy -A'.
+ Status Kdestroy() WARN_UNUSED_RESULT;
+
+ // Call the 'klist' utility. This is useful for logging the local ticket
+ // cache state.
+ Status Klist(std::string* output) WARN_UNUSED_RESULT;
+
+ // Call the 'klist' utility to list the contents of a specific keytab.
+ Status KlistKeytab(const std::string& keytab_path,
+ std::string* output) WARN_UNUSED_RESULT;
+
+ // Sets the environment variables used by the krb5 library
+ // in the current process. This points the SASL library at the
+ // configuration associated with this KDC.
+ Status SetKrb5Environment() const;
+
+ // Returns a map of the Kerberos environment variables which configure
+ // a process to use this KDC.
+ std::map<std::string, std::string> GetEnvVars() const;
+
+ private:
+
+ // Prepends required Kerberos environment variables to the process arguments.
+ std::vector<std::string> MakeArgv(const std::vector<std::string>& in_argv);
+
+ // Creates a kdc.conf in the data root.
+ Status CreateKrb5Conf() const WARN_UNUSED_RESULT;
+
+ // Creates a krb5.conf in the data root.
+ Status CreateKdcConf() const WARN_UNUSED_RESULT;
+
+ // Determine the ports that the KDC bound to. Will wait for the KDC if it is
+ // still initializing.
+ Status WaitForKdcPorts() WARN_UNUSED_RESULT;
+
+ std::unique_ptr<Subprocess> kdc_process_;
+ MiniKdcOptions options_;
+};
+
+} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/test/test_certs.cc
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/test/test_certs.cc b/be/src/kudu/security/test/test_certs.cc
new file mode 100644
index 0000000..cdc20b9
--- /dev/null
+++ b/be/src/kudu/security/test/test_certs.cc
@@ -0,0 +1,396 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "kudu/security/test/test_certs.h"
+
+#include <string>
+
+#include "kudu/util/env.h"
+#include "kudu/util/path_util.h"
+#include "kudu/util/status.h"
+
+using std::string;
+
+namespace kudu {
+namespace security {
+
+//
+// The easiest way to create RSA private key and CA self-signed certificate pair
+// is using the couple of commands below:
+//
+// openssl genrsa -out ca.pkey.pem 2048
+// openssl req -new -x509 -batch -days 3650 -key ca.pkey.pem -out ca.cert.pem
+//
+// NOTE:
+// The latter command uses configuration properties from default configuration
+// file of the OpenSSL library. Also, it runs in batch mode due to the
+// '-batch' flag. To specify custom certificate subject properties, omit
+// the '-batch' flag and run the command in interactive mode. If more
+// customization is needed, see the other methods below.
+//
+////////////////////////////////////////////////////////////////////////////
+//
+// The other way to create RSA private key and CA self-signed certificate pair
+// is using OpenSSL's CA.sh script in $OPENSSL_SRC_ROOT/apps:
+//
+// cp $OPENSSL_SRC_ROOT/CA.sh .
+// chmod +x CA.sh
+// ./CA.sh -newca
+//
+// Find the newly generated files at the following locations:
+// * demoCA/cacert.pem: self-signed CA certificate
+// * demoCA/private/cakey.pem: encrypted CA private key
+//
+// To decrypt the generated private key, run the following command and provide
+// the pass phrase (assuming that was an RSA key):
+//
+// openssl rsa -in ./demoCA/private/cakey.pem
+//
+////////////////////////////////////////////////////////////////////////////
+//
+// Besides, the following sequence of commands can used to create
+// a private key and CA certficate with custom properties.
+//
+// * Create a separate directory, e.g.:
+//
+// mkdir /tmp/cert && cd /tmp/cert
+//
+// * Create custom my.cnf configuration file for the OpenSSL library, copying
+// the default one and modifying the result, if necessary.
+//
+// cp $OPENSSL_CFG_ROOT/etc/openssl.cnf my.cnf
+// vim my.cnf
+//
+// * Create the CA directory structure which matches the directory structure
+// of the 'default_ca' section from the configuration file, e.g.:
+//
+// mkdir -p demoCA/certs demoCA/crl demoCA/newcerts demoCA/private
+// touch demoCA/index.txt
+//
+// * Create private key and certificate signing request (CSR):
+//
+// openssl req -new -keyout ca.pkey.pem -out ca.req.pem \
+// -subj "/C=US/ST=CA/O=MyCompany/CN=MyName/emailAddress=my@email.com" \
+// -passin pass:mega_pass -passout pass:mega_pass -batch
+//
+// * Create a self-signed certificate using the newly generated CSR as input:
+//
+// openssl ca -config my.cnf -create_serial -days 3650 \
+// -keyfile ca.pkey.pem -selfsign -extensions v3_ca \
+// -outdir ./ -out ca.cert.pem -passin pass:mega_pass -batch \
+// -infiles ca.req.pem
+//
+// The encryped private key is in ca.pkey.pem, the certificate is in
+// ca.cert.pem. To decrypt the generated private key, execute the following
+// (assuming that was an RSA key):
+//
+// openssl rsa -passin pass:mega_pass -in ./ca.pkey.pem
+//
+const char kCaCert[] = R"***(
+-----BEGIN CERTIFICATE-----
+MIIDizCCAnOgAwIBAgIJAIsQXjBhvdPoMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UECgwJTXlDb21wYW55MQ8wDQYDVQQD
+DAZNeU5hbWUxGzAZBgkqhkiG9w0BCQEWDG15QGVtYWlsLmNvbTAeFw0xNjEwMjUw
+NjAxNThaFw0yNjEwMjMwNjAxNThaMFwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
+QTESMBAGA1UECgwJTXlDb21wYW55MQ8wDQYDVQQDDAZNeU5hbWUxGzAZBgkqhkiG
+9w0BCQEWDG15QGVtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKexXVOe0SfNexxl1nqMBRy8MCYWTl1kbRt5VQ698aXYcPNBC7gnEBW+8Yaa
+2f3Hl1Ye51zUGnOl4FU6HFDiIq59/lKCNG2X3amlYjzkImXn4M56r+5rEWs+HoHW
+kuqmMaxnrJatM86Of0K3j5QrOUft/qT5R6vSPnFH/pz+6ccBkAGV0UFVdshYSGkx
+KziVTdJ2Ri8oZgyeuReGxLkXOqKHzcOUFinvQ8fe8yaQr1kRAaPRo1eFqORXAMAU
+4KyvfiVjZMEGj0p47IekJHVPVVMopEmMMjhzRfbrxrKrMcIG6e4acF1KAd4wGI9A
+pCR3e1vcfbghDO7GhTMswLCnMYUCAwEAAaNQME4wHQYDVR0OBBYEFDc1+ybIwvG2
+IvEuAusZ9GGMlga/MB8GA1UdIwQYMBaAFDc1+ybIwvG2IvEuAusZ9GGMlga/MAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJT9fL/vtayfAHpdzFvdWBe+
+R6y5HsVQQTBNF9x1eM6M0vGAlsXGgip3+RH7OMwurxNetL2mc03+PECas5LxB5Pr
+u1+kwtmv5YyfQzou0VwztjcbK2OEpWKj16XX6NO403iKoRF4fLn0DjZQcB0oXw4s
+vBxhNfz+SAsjsAMNgLHHXonJfg7wcdmNSp2N3TslGL/DH0bXMhsKx2CuMA3rd9WZ
+mJjItRIk8qNjazlmG0KYxQclP3lGagIMHxU6tY+iBXs1JR1/AUnPl/GaPeayCJSR
+3PB7R+MMrI0hfWFWkBt0D+UAKVa9to/N06wp4JqxEgOooU08PguXLIVDlW0xBcw=
+-----END CERTIFICATE-----
+)***";
+
+
+// See the comment for kCaCert_
+const char kCaPrivateKey[] = R"***(
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAp7FdU57RJ817HGXWeowFHLwwJhZOXWRtG3lVDr3xpdhw80EL
+uCcQFb7xhprZ/ceXVh7nXNQac6XgVTocUOIirn3+UoI0bZfdqaViPOQiZefgznqv
+7msRaz4egdaS6qYxrGeslq0zzo5/QrePlCs5R+3+pPlHq9I+cUf+nP7pxwGQAZXR
+QVV2yFhIaTErOJVN0nZGLyhmDJ65F4bEuRc6oofNw5QWKe9Dx97zJpCvWREBo9Gj
+V4Wo5FcAwBTgrK9+JWNkwQaPSnjsh6QkdU9VUyikSYwyOHNF9uvGsqsxwgbp7hpw
+XUoB3jAYj0CkJHd7W9x9uCEM7saFMyzAsKcxhQIDAQABAoIBABuZQ0TZ5I5qcRKR
+aCUvGkBKcJo0HZ2dQ5+77lXIyRaEcsJ2OBmMxEbv8Aw5PBtaV/vihi1u8xOJf0xH
+jhV5wj95mPu3Vi2bSu36vBpNaaPf783Lv1y73lgKFzdDO1bHF3HKdksuIlKifStb
+zpOSMZE3CCvaowMSTRiTwsHP6mXIBdQ/TwAZHqGVTWDVGxc8JvoJ/3GjSgUIPKzy
+I2aS/5DQ+zmLktuP61GFMJg9tCSrwZPDi/XAatpoAOC9eA7AqF/l1TiaXsQN95mr
+mz2DkCoWRzAuDbya2Sh6nTJvpOMPAeXJ/MMZh9TWswJc4OAO2kZZsFfd0H6M1TKy
+1eAYKVkCgYEA1JhkKQ2h4cVzqQ9A5+4C0q5+j/RFDUOVnNlIjQiM73RchNu713mK
+zzhsom9S/6ZU8OH3TxzD54i2hHtX+QIJqVG0412QgAqAqnAKXGGkkAXiXGfGZhEW
+UB3OuTMbhfVqrkpj0wAPiEJAAuek7zES2B+gURUC24aAfOWU8xMkSjMCgYEAye4U
+e0NQ4HhhWRgWbgFYeAzsC/ezvlx30JjXiLPCNXGoLLJUCMjqWCPGYUvDonIJbxbj
++MYFkvYSDFGwTobKsB7FyT8DxPNus40zOh47y8QUK7jTL4nAmnBa3W9Oj00ceKpo
+wKe/adc2xPrS7mnVpz3ZkJ4I9z/MbEinyV5UTWcCgYAy8gXmlJ67dM6/r6kVK0M/
+65Lmulml0RFUUfmB2o+zfkYBjIqaG0U5XUMjNdxE6T4nr27NZY5IuMlMPCabxHI+
+Qhc/+Rb8qAenUEwbUUbXQKG7FR9FLEkVj98PIIEy+9nBxI/ha31NYNroF0y+CRuD
+8ShA5fEWXEgEJhwol+i1YwKBgEnGeiUuyvW4BZkPe+JlC3WRAwy8SydZkUzdCqIf
+Su1LwS3TWXB8N2JMb8ZMcAWBtICp1FCnyJGQ5bcqgUevZ45BL/H+29mxNtjS1cx+
+D0q7MMNom3/azEugkRImAIXKnoRXfj4lC4IX5yLAoSAJ+s1Hg52an5v16zIEuYiQ
+tiwxAoGAOP8/yjMzit1hzk27k9IfQSLD+1SqKCsRdGbAIhFRFlz4RUQOly1dEX8M
+qVmStlQ7N5gQWJSyDTe6rTe8pG9r030kNDJ+etr2KWpATGNaVWSmLWSYBXrPtejK
+gmbcYCewtt7dFP9tvx6k7aUQ6CKzg0GxaIHQecNzjxYrw8sb4Js=
+-----END RSA PRIVATE KEY-----
+)***";
+
+// Corresponding public key for the kCaPrivateKey
+const char kCaPublicKey[] = R"***(
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp7FdU57RJ817HGXWeowF
+HLwwJhZOXWRtG3lVDr3xpdhw80ELuCcQFb7xhprZ/ceXVh7nXNQac6XgVTocUOIi
+rn3+UoI0bZfdqaViPOQiZefgznqv7msRaz4egdaS6qYxrGeslq0zzo5/QrePlCs5
+R+3+pPlHq9I+cUf+nP7pxwGQAZXRQVV2yFhIaTErOJVN0nZGLyhmDJ65F4bEuRc6
+oofNw5QWKe9Dx97zJpCvWREBo9GjV4Wo5FcAwBTgrK9+JWNkwQaPSnjsh6QkdU9V
+UyikSYwyOHNF9uvGsqsxwgbp7hpwXUoB3jAYj0CkJHd7W9x9uCEM7saFMyzAsKcx
+hQIDAQAB
+-----END PUBLIC KEY-----
+)***";
+
+// See the comment for kCaCert_
+// (but use '-1' as number of days for the certificate expiration).
+const char kCaExpiredCert[] = R"***(
+-----BEGIN CERTIFICATE-----
+MIIDjTCCAnWgAwIBAgIJALNJes+nGWH9MA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTETMBEGA1UECgwKRXhwQ29tcGFueTEQMA4GA1UE
+AwwHRXhwTmFtZTEaMBgGCSqGSIb3DQEJARYLZXhwQGV4cC5jb20wHhcNMTYxMDI1
+MTkzOTM4WhcNMTYxMDI0MTkzOTM4WjBdMQswCQYDVQQGEwJVUzELMAkGA1UECAwC
+Q0ExEzARBgNVBAoMCkV4cENvbXBhbnkxEDAOBgNVBAMMB0V4cE5hbWUxGjAYBgkq
+hkiG9w0BCQEWC2V4cEBleHAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAzqPj5nRm57mr9YtZDvHREuVFHTwPcKzDeff9fnrKKwOJPSF0Bou/BjS1
+S7yQYAtmT/EMi7qxEWjgrR1qW+muR8QN+zAwNdkdLrFK3SJigQ4a/OeSH86aHXUD
+ekV8mgBgzP90osbHf7AiqrGzkYWq+ApTO/IgnXgaWbbdt5znGTW5lKQ4O2CYhpcM
+MC1sBBjW7Qqx+Gi8iXub0zlJ2mVI8o+zb9qvSDb8fa0JYxasRDn/nB0wKZC3f/Gf
+Rs+lJZUTEy5+eMhVdj1RjVBE+mgW7L27On24ViPU7B3DjM0SYnD6ZOUWMH0mtwO8
+W3OoK8MJhPvFP7Lr5QfSjiBH+ryLOwIDAQABo1AwTjAdBgNVHQ4EFgQUsp8OZLl1
+2Z/2aXBQRH0Z+nWxqXcwHwYDVR0jBBgwFoAUsp8OZLl12Z/2aXBQRH0Z+nWxqXcw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArWvFi13iqmvnY0xkgt3R
+acurTvWcQzcUOgVPF8u1atj9d+0zrMk7Don1KQp6uYLdeNLL8NbL4oLxtidW/Yap
+ZEbHVDQTeZAsT7Hr+0uD3vMUndsjG7C85tOhZMiGukFPhuaHE5KmQEy6nUCaJiAv
+opZlNj1mEOGyshSXHsBATl9o33WLTLfPqrO3/12jExApHiADcON4RsPUV6M6k5A2
+/KghYEPYAuFfXTsqj+W7HRL1UuiHJxW96ySQqYzQ86aRN2ZZlTdbDnIU5Jrb6YJB
+hUALcxIUhtodui61zsJFIkVauxTxk7jNCwRvj4I1dSSFWA63t9eh7sKilLRCayNl
+yQ==
+-----END CERTIFICATE-----
+)***";
+
+// See the comment for kCaExpiredCert_
+const char kCaExpiredPrivateKey[] = R"***(
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAzqPj5nRm57mr9YtZDvHREuVFHTwPcKzDeff9fnrKKwOJPSF0
+Bou/BjS1S7yQYAtmT/EMi7qxEWjgrR1qW+muR8QN+zAwNdkdLrFK3SJigQ4a/OeS
+H86aHXUDekV8mgBgzP90osbHf7AiqrGzkYWq+ApTO/IgnXgaWbbdt5znGTW5lKQ4
+O2CYhpcMMC1sBBjW7Qqx+Gi8iXub0zlJ2mVI8o+zb9qvSDb8fa0JYxasRDn/nB0w
+KZC3f/GfRs+lJZUTEy5+eMhVdj1RjVBE+mgW7L27On24ViPU7B3DjM0SYnD6ZOUW
+MH0mtwO8W3OoK8MJhPvFP7Lr5QfSjiBH+ryLOwIDAQABAoIBABszgcWNXxpz24oI
+HOIVvPLi0VVG2bV4WIcOuQTUPxaocYFljPNro+q6N39PxCWQephdX8xo9/QVvTWs
+oJqWyUVTLo/5SO9dtDS4S+WOKC9a3vyZsyeSt8DW7W1EBmHzWMrDeeQPjKVnVzjn
+CX9HfDkIiupiNh7kd3uF0evgsJ8lsZ65HtBq9MWu+mIR1H0EpRLxywdoRJLJ+JdW
+g1fLFRuhnWo0GcEyBK45kLCoVJsRbCkFGf6uPDOOC0g5mIyxGclWeF6ps1OFnFyu
+FWsYeMLSt5tYZfB0/QR46X9HQOhfLunjA04VBkScSRjlohGO4d20ZW7HlPY20CbR
+1PHhEvkCgYEA98FYoovNezx8OgkcAtNOOTK7GpUaUfh3Xl5yPGgCqxoG8G+BTmKF
+MGlIf6URKQA0BUtNdjIvfIcaIctj56qFwjHL6CbzR5MkXUZLlyl0XzYFXm/lavr4
+Z5DHWdFo+GyFaiXIiVof93jAnOFgjSxdhHaEhQqj7pmaBoHVZqtwHFcCgYEA1YRH
+xTzcWErp06KJTt+/P4YtWRh9GDBhhlO3oaGOANkEab8cGjRO9LJP24wyo7exXqGb
+UjtEifEHtzhj6a/UwSAMsFcNhlQRvy525HD1gJmQ2m4wZ3GxztK4IZ4rVDjsB5/D
+SMMBsDfs1r1iRwdSMHAOhrVH2l/DMFQLnx1x+b0CgYEAlQm6SA3RjlDUahUQxKJY
+bBAYfeUz8BuHsz0dezkWYddGVVy+bGjXtkefVSn3KLL2mDi0YGXQKxkanzm636G0
+1R0fjIfh0Syys2mWD1jgqGXW1Ph7Cd/vjl2Jjn5qpwahOzl/aSDOGhCJzdXGPyZx
+Gz4wedfsxZuhDEkOFrUKvAECgYEAxHYYy8V6Qct8Z30wtmBuSvcdFtPPlsg9lCnH
+13MdhG4q/1oXc40Z8VF45VyU48uL6rTsg7eBEyOyo8XBOS7Opnzk8ATJrwX/5lfM
+kdnWK2QhwrqM00HsB5AgWN5+o9pUY5d/Sp4UGZ77z4MmwJBd8a/Jze1Tlf1zTi6n
+GtsvGkkCgYAfILUAPf+ujgB9zdsJa+4l9XCEq0j39/Usfj0VrInNAk7RN8W0qNw7
+ZLs3Qt2fgPO0CeMeVUVKcvdjlXq3EbrWKrsJLxy3Gb8ruBjIlJqncJn6mKslXS+l
+H/sbP2R+P6RvQceLEEtk6ZZLiuScVmLtVOpUoUZb3Rx6a7GKbec7oQ==
+-----END RSA PRIVATE KEY-----
+)***";
+
+// Corresponding public part of the kCaExpiredPrivateKey
+const char kCaExpiredPublicKey[] = R"***(
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzqPj5nRm57mr9YtZDvHR
+EuVFHTwPcKzDeff9fnrKKwOJPSF0Bou/BjS1S7yQYAtmT/EMi7qxEWjgrR1qW+mu
+R8QN+zAwNdkdLrFK3SJigQ4a/OeSH86aHXUDekV8mgBgzP90osbHf7AiqrGzkYWq
++ApTO/IgnXgaWbbdt5znGTW5lKQ4O2CYhpcMMC1sBBjW7Qqx+Gi8iXub0zlJ2mVI
+8o+zb9qvSDb8fa0JYxasRDn/nB0wKZC3f/GfRs+lJZUTEy5+eMhVdj1RjVBE+mgW
+7L27On24ViPU7B3DjM0SYnD6ZOUWMH0mtwO8W3OoK8MJhPvFP7Lr5QfSjiBH+ryL
+OwIDAQAB
+-----END PUBLIC KEY-----
+)***";
+
+const char kCertDnsHostnamesInSan[] = R"***(
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAyegAwIBAgIJAJoczuNKGspGMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UECgwJTXlDb21wYW55MQ8wDQYDVQQD
+DAZNeU5hbWUxGzAZBgkqhkiG9w0BCQEWDG15QGVtYWlsLmNvbTAeFw0xNzA0Mjgx
+OTUwNTVaFw0yNzA0MjYxOTUwNTVaMAAwXDANBgkqhkiG9w0BAQEFAANLADBIAkEA
+rpJhLdS/Euf2cu0hPXkvkocLO0XbNtFwXNjkOOjuJZd65FHqLb6TmmxxDpL7fB94
+Mq1fD20fqdAgSVzljOyvuwIDAQABo4ICJjCCAiIwDgYDVR0PAQH/BAQDAgWgMCAG
+A1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMIIB
+3gYDVR0RBIIB1TCCAdGCDG1lZ2EuZ2lnYS5pb4ILZm9vLmJhci5jb22CggGydG9v
+b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29v
+b29vb29vb29vb29vb29vb29vLmxvb29vb29vb29vb29vb29vb29vb29vb29vb29v
+b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29v
+b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29v
+b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29v
+b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29v
+b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29v
+b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29v
+b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vbmcuaG9zdG5hbWUuaW8w
+DQYJKoZIhvcNAQELBQADggEBAIKVVABj3nTyqDEnDKDfvS6QNEQSa1mw7WdFTpzH
+6cbMfiixVyyLqfAV4NZ+PnIa4mpsWP5LrsrWFVK/HtiTX7Y8oW0qdA04WtYd9VUT
+BgWKHyLygbA+PSZ6GdXFjZd8wDthK0qlT2MfZJUwD36eYnBxuonU8a4lmxaUG2zC
+L8FplhNJUEt6XfJ0zZGx1VHe12LLjgMz3ShDAmD9DlHHFjJ1aQ/17OGmmjWmbWnm
+an4ys5seqeHuK2WzP3NAx7LOwe/R1kHpEAX/Al6xyLIY3h7BBzurpgfrO6hTTECF
+561gUMp+cAvogw074thF5j4b+uEK5Bl8nzN2h8BwwxwGzUo=
+-----END CERTIFICATE-----
+)***";
+
+//
+// The reference signatures were obtained by using the following sequence:
+// 0. The reference private key was saved into /tmp/ca.pkey.pem file.
+// 1. Put the input data into /tmp/in.txt file.
+// 2. To sign the input data, run
+// openssl dgst -sign /tmp/ca.pkey.pem -sha512 -out /tmp/out /tmp/in.txt
+// 3. To capture the signature in text format, run
+// base64 -b 60 /tmp/out
+//
+const char kDataTiny[] = "Tiny";
+const char kSignatureTinySHA512[] =
+ "omtvSpfj9tKo0RdI4zJwasWSQnXl++aKVjhH19ABJCd0haKT8RXNuhnxcbZU"
+ "Y1ILE5F9YjVj+tN/7ah5WQZR5qlJ6GMFfCFBhOzvi/vf5PSbUrFfwFvFD6sq"
+ "Bu0PWdwKM3t8/YFE2HcZWSzGCcasKlG/aw2eQCN3Kdv8QVMlC28CFA/EqQBt"
+ "8Sfye1DLba33SzDpJqR2DduTFrEW2UffumpYIbkEcMwUSBFzfdp5hgWPowFb"
+ "LrnKvyWKpEPMFGQmf5siyXSkbBIfL774tynhWN/lAUWykwXSUfGgi2G0NQvj"
+ "xmuHhbxWpbW/31uMGssw92OfVQ/+aQ4pNmY9GbibcA==";
+
+const char kDataShort[] = "ShortRefInputData";
+const char kSignatureShortSHA512[] =
+ "BHaDipr8ibn40BMD6+DlatKsjbmsGZsJIDlheppBjqv66eBDLKOVjpmpMLl9"
+ "9lXCGUlVS+cNcVP4RPDzXNoXkpzUOJD3UQSnxCAm6tV1eGjD3SHi3fk6PCNc"
+ "MhM/+09fA0WHdIdZm93cpHt6c9MFzB/dUjHJByhQ7Csmz2zdITyMIl3/D+bi"
+ "ocW0aIibk0wNGn/FmXfgFDP+3pBS2bpS0AdFnckX8AqXHFMJnvqKYODpYCW8"
+ "NWFSD1TgZOumu/gzxm+HySPezQ2j9tdR6nb9swfShvN+o0oBVGq5vgtgZMTM"
+ "7Ws+BrasLfvQFkvtGMWB9VeH/rDlGOym8RwUrCIJJQ==";
+
+const char kDataLong[] =
+R"***(Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+)***";
+const char kSignatureLongSHA512[] =
+ "kc62qPHApVFbueR1xSCQJR5NomqDRzVA+4Xi9egVyfkKgpVhDAYGxbMl8OTY/YCb"
+ "eQuwY+B7RGxF9sj3gvsq/dvrbIjLT3QDhs0bv+lXTtBQ5r9zrals3de0tEFrPoLr"
+ "CkKPhVZaG+zwmUVltfsdlsqvepy6rNW7BocehvgpPTbzxgsZg4nUANsjSy8HBoDb"
+ "xWyfbkMgBY4aWIH1g+wksq1DHzdTNdZCYstupRwVw/ESC+zrFQiZPFeRE/wCSeG/"
+ "bd0L8TcotQHJchZ8THW0rEbuCg79I7Crd1KQYljBpOOhMYZEDEdM9L19JlaMlw+Z"
+ "leyLfL8Bw3wCg9cMfNmQfQ==";
+
+
+Status CreateTestSSLCerts(const string& dir,
+ string* cert_file,
+ string* key_file,
+ string* key_password) {
+ const char* kCert = R"(
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAOOmFHYkBz4rMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTYxMTAyMjI0OTQ5WhcNMTcwMjEwMjI0OTQ5WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAppo9GwiDisQVYAF9NXl8ykqo0MIi5rfNwiE9kUWbZ2ejzxs+1Cf7WCn4
+mzbkJx5ZscRjhnNb6dJxtZJeid/qgiNVBcNzh35H8J+ao0tEbHjCs7rKOX0etsFU
+p4GQwYkdfpvVBsU8ciXvkxhvt1XjSU3/YJJRAvCyGVxUQlKiVKGCD4OnFNBwMdNw
+7qI8ryiRv++7I9udfSuM713yMeBtkkV7hWUfxrTgQOLsV/CS+TsSoOJ7JJqHozeZ
++VYom85UqSfpIFJVzM6S7BTb6SX/vwYIoS70gubT3HbHgDRcMvpCye1npHL9fL7B
+87XZn7wnnUem0eeCqWyUjJ82Uj9mQQIDAQABo1AwTjAdBgNVHQ4EFgQUOY7rpWGo
+ZMrmyRZ9RohPWVwyPBowHwYDVR0jBBgwFoAUOY7rpWGoZMrmyRZ9RohPWVwyPBow
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATKh3io8ruqbhmopY3xQW
+A2pEhs4ZSu3H+AfULMruVsXKEZjWp27nTsFaxLZYUlzeZr0EcWwZ79qkcA8Dyj+m
+VHhrCAPpcjsDACh1ZdUQAgASkVS4VQvkukct3DFa3y0lz5VwQIxjoQR5y6dCvxxX
+T9NpRo/Z7pd4MRhEbz3NT6PScQ9f2MTrR0NOikLdB98JlpKQbEKxzbMhWDw4J3mr
+mK6zdemjdCcRDsBVPswKnyAjkibXaZkpNRzjvDNAgO88MKlArCYoyRZqIfkcSXAw
+wTdGQ+5GQLsY9zS49Rrhk9R7eOmDhaHybdRBDqW1JiCSmzURZAxlnrjox4GmC3JJ
+aA==
+-----END CERTIFICATE-----
+)";
+ const char* kKey = R"(
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIA3+5nR+Jr18CAggA
+MBQGCCqGSIb3DQMHBAhCwHFGbZEcBgSCBMjT9vVbrYpd1reGwfLhk8703ihlspZi
+cm4Z2MM+lkJs0Pi8O4n+zNSTfgrEr2XGlKIktBWEBxbhYrdYy1bYm4Fu5Z055JFo
+89L/9zT1Zm/agk3CUW+ljirYZF60t/dDWmzLwt9f4dp8m4etL/UwvMJ1NglyxMkj
+c03+aWWh4wHLRGkGDJFsKEQY87LL+nAOZS7P1qY38HnzQTOgLyNpntXX3SryzvQ6
+CpNIyQFhVGXfGn6DzGJB76heyLpCyAvIiP87vBS3zbnSqDM6v6PTW3SMo8R42RfL
+d0CVmO8Z8NQeX29EkMHSRu7gCwXu1pf40QIog2vJZ7dmUgsU9GbBSg8l3nVWS6sm
+AICNwPvHXRMMGX0wBJyK2ihuC7rVd5aZLgmu1sjlLYaB9KkoETcFFT8KbFpnd6aR
+1whXQ4rPm1WyGtqbVGkZthisvGUeeGnbv2HXUVthSGleD+hQuwFXa8wE/8+Ruq9X
+rNv/WMrf2NLIm+wbr19JzVSvLh+j7mIBMZmIwGQvBPo3/Tuq2zeyZdfSOroFcanq
+Lyoc6yF5rAkU5BLVe36e48MarWICWDCxiz1n6tWdCpcXWfBvlWIkkjP1/rhqnOW3
+DKNjTyGJhaVYydkseoGrrpj4gkyyWtpgw+8c7jdtk+7cmIxpXu3UU6fh+Yt3vEhF
+VqHvCd+YuUgpJ+TW574xiau4xbyib5Qv6JAR1Qp3MtzZ1IngyCU4QqqgxBGMBVqc
+2LI7Romw7icfdzJxMeMp9WXh8D0Bxx5kjDcO0eUnkpVkFyozXZkoLCnoJ8u3yJo6
+yV4RQ4mOAWj7uZYg9KEUywNCHuIVPKG0CEfQkxKiPw4uvmdimKZ6Ij7aqrrc68Vi
+oZnNHJEfJhnG78MKHgxXHNrMLFXBgPpLoBQxUBVhI2nq5D2l7gL5bKc9JZNg+mRJ
+CouijXBHS1nZ/7GwVjLvNIGKWEsuiz1P0SYki1S02/3bBF9ySdeNGl1XTNqK4Xqy
+arK4agJc89wg3N6SOIA+q8kA4LScafMtCkVDChw0CcLUrQERpH1tv1cqCt5zXF9n
+5AnnWmEM2knlkHxXzg7k/1YXUz4JMmAhS4gVHuNU1uZR152lD+kgSy2K4sCyCfx2
+iWFpDGj556AUxDRGrqKU7OLC/64AuNz5IJs8doDa29cGGKFw3/foRoOaya84ISGW
+GTl2qDOHZrJbgR6BUpSh2E2mVyO3GwIBst6yIb5VaTpNuIwS6fhjC4fQZEV6hHcx
+qNvHxTTvz6eag4TeUPR48h/kGsI44DB0r4I79WbTwg5dvdlYbchPIwAs888bxpd6
+7ZxSg7EwuyHqJEL0FkWcDgw89+vLDETQiTwfscDxwm893gTymj5JPSDz35kudPlI
+rsNfABLeXSg8Z8/7LsPP6Q48c1jisLVPPndV80cS791dvyXRxZWvX2z5UFuTDy3K
+PV3L60mdejXudzFPfvovhgJDIWsKMmlxYplRWvG3WUXTck1Kb7KEcZmuo4nJMOID
+6caoDNa5L9p5XH54sBCB7uqTNdqijaqq9iBFx/MqL3LHt6/wVF5J9g6PmuDxuYDX
+tBKU0ns67U6wUxvLGBX/7RnWUibc5JwVGPBGw1E5u6MKWxW9Q6Dk1WakAtsqtDkR
+WEc=
+-----END ENCRYPTED PRIVATE KEY-----
+)";
+ const char* kKeyPassword = "test";
+
+ *cert_file = JoinPathSegments(dir, "test.cert");
+ *key_file = JoinPathSegments(dir, "test.key");
+ *key_password = kKeyPassword;
+
+ RETURN_NOT_OK(WriteStringToFile(Env::Default(), kCert, *cert_file));
+ RETURN_NOT_OK(WriteStringToFile(Env::Default(), kKey, *key_file));
+ return Status::OK();
+}
+
+} // namespace security
+} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/test/test_certs.h
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/test/test_certs.h b/be/src/kudu/security/test/test_certs.h
new file mode 100644
index 0000000..ea4e7a6
--- /dev/null
+++ b/be/src/kudu/security/test/test_certs.h
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+#pragma once
+
+#include <string>
+
+namespace kudu {
+class Status;
+
+namespace security {
+
+//
+// Set of certificates and private keys used for certificate generation
+// and signing tests (declarations). See the .cc file for the actual data.
+//
+
+// Valid root CA cerificate (PEM format).
+extern const char kCaCert[];
+// The private key (RSA, 2048 bits) for the certificate above.
+// This is 2048 bit RSA key, in PEM format.
+extern const char kCaPrivateKey[];
+// The public part of the abovementioned private key.
+extern const char kCaPublicKey[];
+
+// Expired root CA certificate (PEM format).
+extern const char kCaExpiredCert[];
+// The private key for the expired CA certificate described above.
+// This is 2048 bit RSA key, in PEM format.
+extern const char kCaExpiredPrivateKey[];
+// The public part of the abovementioned private key.
+extern const char kCaExpiredPublicKey[];
+// Certificate with multiple DNS hostnames in the SAN field.
+extern const char kCertDnsHostnamesInSan[];
+
+extern const char kDataTiny[];
+extern const char kSignatureTinySHA512[];
+
+extern const char kDataShort[];
+extern const char kSignatureShortSHA512[];
+
+extern const char kDataLong[];
+extern const char kSignatureLongSHA512[];
+
+// Creates a matching SSL certificate and private key file in 'dir', returning
+// their paths in '*cert_file' and '*key_file'. The password associated with
+// the private key is stored in '*key_password'.
+Status CreateTestSSLCerts(const std::string& dir,
+ std::string* cert_file,
+ std::string* key_file,
+ std::string* key_password);
+
+} // namespace security
+} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/test/test_pass.cc
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/test/test_pass.cc b/be/src/kudu/security/test/test_pass.cc
new file mode 100644
index 0000000..9a0ab46
--- /dev/null
+++ b/be/src/kudu/security/test/test_pass.cc
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "kudu/security/test/test_pass.h"
+
+#include "kudu/util/env.h"
+#include "kudu/util/path_util.h"
+
+using std::string;
+
+namespace kudu {
+namespace security {
+
+Status CreateTestHTPasswd(const string& dir,
+ string* passwd_file) {
+
+ // In the format of user:realm:digest. Digest is generated bases on
+ // password 'test'.
+ const char *kHTPasswd = "test:0.0.0.0:e4c02fbc8e89377a942ffc6b1bc3a566";
+ *passwd_file = JoinPathSegments(dir, "test.passwd");
+ RETURN_NOT_OK(WriteStringToFile(Env::Default(), kHTPasswd, *passwd_file));
+ return Status::OK();
+}
+
+} // namespace security
+} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/test/test_pass.h
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/test/test_pass.h b/be/src/kudu/security/test/test_pass.h
new file mode 100644
index 0000000..c0974d0
--- /dev/null
+++ b/be/src/kudu/security/test/test_pass.h
@@ -0,0 +1,33 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <string>
+
+#include "kudu/util/status.h"
+
+namespace kudu {
+namespace security {
+
+// Creates .htpasswd for HTTP basic authentication in the format
+// of 'user:realm:digest', returning the path in '*passwd_file'.
+Status CreateTestHTPasswd(const std::string &dir,
+ std::string *passwd_file);
+
+} // namespace security
+} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/tls_context.cc
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/tls_context.cc b/be/src/kudu/security/tls_context.cc
new file mode 100644
index 0000000..f28f31e
--- /dev/null
+++ b/be/src/kudu/security/tls_context.cc
@@ -0,0 +1,459 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "kudu/security/tls_context.h"
+
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <gflags/gflags.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/security/ca/cert_management.h"
+#include "kudu/security/cert.h"
+#include "kudu/security/crypto.h"
+#include "kudu/security/init.h"
+#include "kudu/security/openssl_util.h"
+#include "kudu/security/tls_handshake.h"
+#include "kudu/util/flag_tags.h"
+#include "kudu/util/net/net_util.h"
+#include "kudu/util/scoped_cleanup.h"
+#include "kudu/util/status.h"
+#include "kudu/util/user.h"
+
+using strings::Substitute;
+using std::string;
+using std::unique_lock;
+
+DEFINE_int32(ipki_server_key_size, 2048,
+ "the number of bits for server cert's private key. The server cert "
+ "is used for TLS connections to and from clients and other servers.");
+TAG_FLAG(ipki_server_key_size, experimental);
+
+DEFINE_string(rpc_tls_ciphers,
+ // This is the "modern compatibility" cipher list of the Mozilla Security
+ // Server Side TLS recommendations, accessed Feb. 2017, with the addition of
+ // the non ECDH/DH AES cipher suites from the "intermediate compatibility"
+ // list. These additional ciphers maintain compatibility with RHEL 6.5 and
+ // below. The DH AES ciphers are not included since we are not configured to
+ // use DH key agreement.
+ "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:"
+ "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:"
+ "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:"
+ "ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:"
+ "ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
+ "AES256-GCM-SHA384:AES128-GCM-SHA256:"
+ "AES256-SHA256:AES128-SHA256:"
+ "AES256-SHA:AES128-SHA",
+ "The cipher suite preferences to use for TLS-secured RPC connections. "
+ "Uses the OpenSSL cipher preference list format. See man (1) ciphers "
+ "for more information.");
+TAG_FLAG(rpc_tls_ciphers, advanced);
+
+DEFINE_string(rpc_tls_min_protocol, "TLSv1",
+ "The minimum protocol version to allow when for securing RPC "
+ "connections with TLS. May be one of 'TLSv1', 'TLSv1.1', or "
+ "'TLSv1.2'.");
+TAG_FLAG(rpc_tls_min_protocol, advanced);
+
+namespace kudu {
+namespace security {
+
+using ca::CertRequestGenerator;
+
+template<> struct SslTypeTraits<SSL> {
+ static constexpr auto free = &SSL_free;
+};
+template<> struct SslTypeTraits<X509_STORE_CTX> {
+ static constexpr auto free = &X509_STORE_CTX_free;
+};
+
+TlsContext::TlsContext()
+ : lock_(RWMutex::Priority::PREFER_READING),
+ trusted_cert_count_(0),
+ has_cert_(false),
+ is_external_cert_(false) {
+ security::InitializeOpenSSL();
+}
+
+Status TlsContext::Init() {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ CHECK(!ctx_);
+
+ // NOTE: 'SSLv23 method' sounds like it would enable only SSLv2 and SSLv3, but in fact
+ // this is a sort of wildcard which enables all methods (including TLSv1 and later).
+ // We explicitly disable SSLv2 and SSLv3 below so that only TLS methods remain.
+ // See the discussion on https://trac.torproject.org/projects/tor/ticket/11598 for more
+ // info.
+ ctx_ = ssl_make_unique(SSL_CTX_new(SSLv23_method()));
+ if (!ctx_) {
+ return Status::RuntimeError("failed to create TLS context", GetOpenSSLErrors());
+ }
+ SSL_CTX_set_mode(ctx_.get(), SSL_MODE_AUTO_RETRY);
+
+ // Disable SSLv2 and SSLv3 which are vulnerable to various issues such as POODLE.
+ // We support versions back to TLSv1.0 since OpenSSL on RHEL 6.4 and earlier does not
+ // not support TLSv1.1 or later.
+ //
+ // Disable SSL/TLS compression to free up CPU resources and be less prone
+ // to attacks exploiting the compression feature:
+ // https://tools.ietf.org/html/rfc7525#section-3.3
+ auto options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION;
+
+ if (boost::iequals(FLAGS_rpc_tls_min_protocol, "TLSv1.2")) {
+#if OPENSSL_VERSION_NUMBER < 0x10001000L
+ return Status::InvalidArgument(
+ "--rpc_tls_min_protocol=TLSv1.2 is not be supported on this platform. "
+ "TLSv1 is the latest supported TLS protocol.");
+#else
+ options |= SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
+#endif
+ } else if (boost::iequals(FLAGS_rpc_tls_min_protocol, "TLSv1.1")) {
+#if OPENSSL_VERSION_NUMBER < 0x10001000L
+ return Status::InvalidArgument(
+ "--rpc_tls_min_protocol=TLSv1.1 is not be supported on this platform. "
+ "TLSv1 is the latest supported TLS protocol.");
+#else
+ options |= SSL_OP_NO_TLSv1;
+#endif
+ } else if (!boost::iequals(FLAGS_rpc_tls_min_protocol, "TLSv1")) {
+ return Status::InvalidArgument("unknown value provided for --rpc_tls_min_protocol flag",
+ FLAGS_rpc_tls_min_protocol);
+ }
+
+ SSL_CTX_set_options(ctx_.get(), options);
+
+ OPENSSL_RET_NOT_OK(
+ SSL_CTX_set_cipher_list(ctx_.get(), FLAGS_rpc_tls_ciphers.c_str()),
+ "failed to set TLS ciphers");
+
+ // Enable ECDH curves. For OpenSSL 1.1.0 and up, this is done automatically.
+#ifndef OPENSSL_NO_ECDH
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+ // OpenSSL 1.0.1 and below only support setting a single ECDH curve at once.
+ // We choose prime256v1 because it's the first curve listed in the "modern
+ // compatibility" section of the Mozilla Server Side TLS recommendations,
+ // accessed Feb. 2017.
+ c_unique_ptr<EC_KEY> ecdh { EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), &EC_KEY_free };
+ OPENSSL_RET_IF_NULL(ecdh, "failed to create prime256v1 curve");
+ OPENSSL_RET_NOT_OK(SSL_CTX_set_tmp_ecdh(ctx_.get(), ecdh.get()),
+ "failed to set ECDH curve");
+#elif OPENSSL_VERSION_NUMBER < 0x10100000L
+ // OpenSSL 1.0.2 provides the set_ecdh_auto API which internally figures out
+ // the best curve to use.
+ OPENSSL_RET_NOT_OK(SSL_CTX_set_ecdh_auto(ctx_.get(), 1),
+ "failed to configure ECDH support");
+#endif
+#endif
+
+ // TODO(KUDU-1926): is it possible to disable client-side renegotiation? it seems there
+ // have been various CVEs related to this feature that we don't need.
+ return Status::OK();
+}
+
+Status TlsContext::VerifyCertChainUnlocked(const Cert& cert) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ 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),
+ "could not init X509_STORE_CTX");
+ int rc = X509_verify_cert(store_ctx.get());
+ if (rc != 1) {
+ int err = X509_STORE_CTX_get_error(store_ctx.get());
+ if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) {
+ // It's OK to provide a self-signed cert.
+ ERR_clear_error(); // in case it left anything on the queue.
+ return Status::OK();
+ }
+
+ // Get the cert that failed to verify.
+ X509* cur_cert = X509_STORE_CTX_get_current_cert(store_ctx.get());
+ string cert_details;
+ if (cur_cert) {
+ cert_details = Substitute(" (error with cert: subject=$0, issuer=$1)",
+ X509NameToString(X509_get_subject_name(cur_cert)),
+ X509NameToString(X509_get_issuer_name(cur_cert)));
+ }
+
+ ERR_clear_error(); // in case it left anything on the queue.
+ return Status::RuntimeError(
+ Substitute("could not verify certificate chain$0", cert_details),
+ X509_verify_cert_error_string(err));
+ }
+ return Status::OK();
+}
+
+Status TlsContext::UseCertificateAndKey(const Cert& cert, const PrivateKey& key) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ // Verify that the cert and key match.
+ RETURN_NOT_OK(cert.CheckKeyMatch(key));
+
+ std::unique_lock<RWMutex> lock(lock_);
+
+ // Verify that the appropriate CA certs have been loaded into the context
+ // before we adopt a cert. Otherwise, client connections without the CA cert
+ // available would fail.
+ RETURN_NOT_OK(VerifyCertChainUnlocked(cert));
+
+ 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()),
+ "failed to use certificate");
+ has_cert_ = true;
+ return Status::OK();
+}
+
+Status TlsContext::AddTrustedCertificate(const Cert& cert) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ VLOG(2) << "Trusting certificate " << cert.SubjectName();
+
+ {
+ // Workaround for a leak in OpenSSL <1.0.1:
+ //
+ // If we start trusting a cert, and its internal public-key field hasn't
+ // yet been populated, then the first time it's used for verification will
+ // populate it. In the case that two threads try to populate it at the same time,
+ // one of the thread's copies will be leaked.
+ //
+ // To avoid triggering the race, we populate the internal public key cache
+ // field up front before adding it to the trust store.
+ //
+ // See OpenSSL commit 33a688e80674aaecfac6d9484ec199daa0ee5b61.
+ PublicKey k;
+ CHECK_OK(cert.GetPublicKey(&k));
+ }
+
+ 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();
+ }
+ OPENSSL_RET_NOT_OK(rc, "failed to add trusted certificate");
+ }
+ trusted_cert_count_ += 1;
+ return Status::OK();
+}
+
+Status TlsContext::DumpTrustedCerts(vector<string>* cert_ders) const {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ shared_lock<RWMutex> lock(lock_);
+
+ vector<string> ret;
+ auto* cert_store = SSL_CTX_get_cert_store(ctx_.get());
+
+ CRYPTO_w_lock(CRYPTO_LOCK_X509_STORE);
+ auto unlock = MakeScopedCleanup([&]() {
+ CRYPTO_w_unlock(CRYPTO_LOCK_X509_STORE);
+ });
+ for (int i = 0; i < sk_X509_OBJECT_num(cert_store->objs); i++) {
+ 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);
+ string der;
+ RETURN_NOT_OK(c.ToString(&der, DataFormat::DER));
+ ret.emplace_back(std::move(der));
+ }
+
+ cert_ders->swap(ret);
+ return Status::OK();
+}
+
+namespace {
+Status SetCertAttributes(CertRequestGenerator::Config* config) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ RETURN_NOT_OK_PREPEND(GetFQDN(&config->hostname), "could not determine FQDN for CSR");
+
+ // If the server has logged in from a keytab, then we have a 'real' identity,
+ // and our desired CN should match the local username mapped from the Kerberos
+ // principal name. Otherwise, we'll make up a common name based on the hostname.
+ boost::optional<string> principal = GetLoggedInPrincipalFromKeytab();
+ if (!principal) {
+ string uid;
+ RETURN_NOT_OK_PREPEND(GetLoggedInUser(&uid),
+ "couldn't get local username");
+ config->user_id = uid;
+ return Status::OK();
+ }
+ string uid;
+ RETURN_NOT_OK_PREPEND(security::MapPrincipalToLocalName(*principal, &uid),
+ "could not get local username for krb5 principal");
+ config->user_id = uid;
+ config->kerberos_principal = *principal;
+ return Status::OK();
+}
+} // anonymous namespace
+
+Status TlsContext::GenerateSelfSignedCertAndKey() {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ // Step 1: generate the private key to be self signed.
+ PrivateKey key;
+ RETURN_NOT_OK_PREPEND(GeneratePrivateKey(FLAGS_ipki_server_key_size,
+ &key),
+ "failed to generate private key");
+
+ // Step 2: generate a CSR so that the self-signed cert can eventually be
+ // replaced with a CA-signed cert.
+ CertRequestGenerator::Config config;
+ RETURN_NOT_OK(SetCertAttributes(&config));
+ CertRequestGenerator gen(config);
+ RETURN_NOT_OK_PREPEND(gen.Init(), "could not initialize CSR generator");
+ CertSignRequest csr;
+ RETURN_NOT_OK_PREPEND(gen.GenerateRequest(key, &csr), "could not generate CSR");
+
+ // Step 3: generate a self-signed cert that we can use for terminating TLS
+ // connections until we get the CA-signed cert.
+ Cert cert;
+ RETURN_NOT_OK_PREPEND(ca::CertSigner::SelfSignCert(key, config, &cert),
+ "failed to self-sign cert");
+
+ // Workaround for an OpenSSL memory leak caused by a race in x509v3_cache_extensions.
+ // Upon first use of each certificate, this function gets called to parse various
+ // fields of the certificate. However, it's racey, so if multiple "first calls"
+ // happen concurrently, one call overwrites the cached data from another, causing
+ // 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()));
+ ERR_clear_error(); // in case it left anything on the queue.
+
+ // Step 4: Adopt the new key and cert.
+ unique_lock<RWMutex> lock(lock_);
+ 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()),
+ "failed to use certificate");
+ has_cert_ = true;
+ csr_ = std::move(csr);
+ return Status::OK();
+}
+
+boost::optional<CertSignRequest> TlsContext::GetCsrIfNecessary() const {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ shared_lock<RWMutex> lock(lock_);
+ if (csr_) {
+ return csr_->Clone();
+ }
+ return boost::none;
+}
+
+Status TlsContext::AdoptSignedCert(const Cert& cert) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ unique_lock<RWMutex> lock(lock_);
+
+ // Verify that the appropriate CA certs have been loaded into the context
+ // before we adopt a cert. Otherwise, client connections without the CA cert
+ // available would fail.
+ RETURN_NOT_OK(VerifyCertChainUnlocked(cert));
+
+ if (!csr_) {
+ // A signed cert has already been adopted.
+ return Status::OK();
+ }
+
+ PublicKey csr_key;
+ RETURN_NOT_OK(csr_->GetPublicKey(&csr_key));
+ PublicKey cert_key;
+ RETURN_NOT_OK(cert.GetPublicKey(&cert_key));
+ bool equals;
+ RETURN_NOT_OK(csr_key.Equals(cert_key, &equals));
+ if (!equals) {
+ 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()),
+ "failed to use certificate");
+
+ // This should never fail since we already compared the cert's public key
+ // against the CSR, but better safe than sorry. If this *does* fail, it
+ // appears to remove the private key from the SSL_CTX, so we are left in a bad
+ // state.
+ OPENSSL_CHECK_OK(SSL_CTX_check_private_key(ctx_.get()))
+ << "certificate does not match the private key";
+
+ csr_ = boost::none;
+
+ return Status::OK();
+}
+
+Status TlsContext::LoadCertificateAndKey(const string& certificate_path,
+ const string& key_path) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ Cert c;
+ RETURN_NOT_OK(c.FromFile(certificate_path, DataFormat::PEM));
+ PrivateKey k;
+ RETURN_NOT_OK(k.FromFile(key_path, DataFormat::PEM));
+ is_external_cert_ = true;
+ return UseCertificateAndKey(c, k);
+}
+
+Status TlsContext::LoadCertificateAuthority(const string& certificate_path) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ if (has_cert_) DCHECK(is_external_cert_);
+ Cert c;
+ RETURN_NOT_OK(c.FromFile(certificate_path, DataFormat::PEM));
+ return AddTrustedCertificate(c);
+}
+
+Status TlsContext::InitiateHandshake(TlsHandshakeType handshake_type,
+ TlsHandshake* handshake) const {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ CHECK(ctx_);
+ CHECK(!handshake->ssl_);
+ {
+ shared_lock<RWMutex> lock(lock_);
+ handshake->adopt_ssl(ssl_make_unique(SSL_new(ctx_.get())));
+ }
+ if (!handshake->ssl_) {
+ return Status::RuntimeError("failed to create SSL handle", GetOpenSSLErrors());
+ }
+
+ SSL_set_bio(handshake->ssl(),
+ BIO_new(BIO_s_mem()),
+ BIO_new(BIO_s_mem()));
+
+ switch (handshake_type) {
+ case TlsHandshakeType::SERVER:
+ SSL_set_accept_state(handshake->ssl());
+ break;
+ case TlsHandshakeType::CLIENT:
+ SSL_set_connect_state(handshake->ssl());
+ break;
+ }
+
+ return Status::OK();
+}
+
+} // namespace security
+} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/tls_context.h
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/tls_context.h b/be/src/kudu/security/tls_context.h
new file mode 100644
index 0000000..b278d9c
--- /dev/null
+++ b/be/src/kudu/security/tls_context.h
@@ -0,0 +1,182 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <boost/optional.hpp>
+
+#include "kudu/security/cert.h"
+#include "kudu/security/tls_handshake.h"
+#include "kudu/util/atomic.h"
+#include "kudu/util/locks.h"
+#include "kudu/util/rw_mutex.h"
+#include "kudu/util/status.h"
+
+namespace kudu {
+namespace security {
+
+class Cert;
+class PrivateKey;
+
+// TlsContext wraps data required by the OpenSSL library for creating and
+// accepting TLS protected channels. A single TlsContext instance should be used
+// per server or client instance.
+//
+// Internally, a 'TlsContext' manages a single keypair which it uses for
+// terminating TLS connections. It also manages a collection of trusted root CA
+// certificates (a trust store), as well as a signed certificate for the
+// keypair.
+//
+// When used on a server, the TlsContext can generate a keypair and a
+// self-signed certificate, and provide a CSR for transititioning to a CA-signed
+// certificate. This allows Kudu servers to start with a self-signed
+// certificate, and later adopt a CA-signed certificate as it becomes available.
+// See GenerateSelfSignedCertAndKey(), GetCsrIfNecessary(), and
+// AdoptSignedCert() for details on how to generate the keypair and self-signed
+// cert, access the CSR, and transtition to a CA-signed cert, repectively.
+//
+// When used in a client or a server, the TlsContext can immediately adopt a
+// private key and CA-signed cert using UseCertificateAndKey(). A TlsContext
+// only manages a single keypair, so if UseCertificateAndKey() is called,
+// GenerateSelfSignedCertAndKey() must not be called, and vice versa.
+//
+// TlsContext may be used with or without a keypair and cert to initiate TLS
+// connections, when mutual TLS authentication is not needed (for example, for
+// token or Kerberos authenticated connections).
+//
+// This class is thread-safe after initialization.
+class TlsContext {
+
+ public:
+
+ TlsContext();
+
+ ~TlsContext() = default;
+
+ Status Init() WARN_UNUSED_RESULT;
+
+ // Returns true if this TlsContext has been configured with a cert and key for
+ // use with TLS-encrypted connections.
+ bool has_cert() const {
+ shared_lock<RWMutex> lock(lock_);
+ return has_cert_;
+ }
+
+ // Returns true if this TlsContext has been configured with a CA-signed TLS
+ // cert and key for use with TLS-encrypted connections. If this method returns
+ // true, then 'has_trusted_cert' will also return true.
+ bool has_signed_cert() const {
+ shared_lock<RWMutex> lock(lock_);
+ return has_cert_ && !csr_;
+ }
+
+ // Returns true if this TlsContext has at least one certificate in its trust store.
+ bool has_trusted_cert() const {
+ shared_lock<RWMutex> lock(lock_);
+ return trusted_cert_count_ > 0;
+ }
+
+ // Adds 'cert' as a trusted root CA certificate.
+ //
+ // This determines whether other peers are trusted. It also must be called for
+ // any CA certificates that are part of the certificate chain for the cert
+ // passed in to 'UseCertificateAndKey()' or 'AdoptSignedCert()'.
+ //
+ // If this cert has already been marked as trusted, this has no effect.
+ Status AddTrustedCertificate(const Cert& cert) WARN_UNUSED_RESULT;
+
+ // Dump all of the certs that are currently trusted by this context, in DER
+ // form, into 'cert_ders'.
+ Status DumpTrustedCerts(std::vector<std::string>* cert_ders) const WARN_UNUSED_RESULT;
+
+ // Uses 'cert' and 'key' as the cert and key for use with TLS connections.
+ //
+ // Checks that the CA that issued the signature on 'cert' is already trusted
+ // by this context (e.g. by AddTrustedCertificate()).
+ Status UseCertificateAndKey(const Cert& cert, const PrivateKey& key) WARN_UNUSED_RESULT;
+
+ // Generates a self-signed cert and key for use with TLS connections.
+ //
+ // This method should only be used on the server. Once this method is called,
+ // 'GetCsrIfNecessary' can be used to retrieve a CSR for generating a
+ // CA-signed cert for the generated private key, and 'AdoptSignedCert' can be
+ // used to transition to using the CA-signed cert with subsequent TLS
+ // connections.
+ Status GenerateSelfSignedCertAndKey() WARN_UNUSED_RESULT;
+
+ // Returns a new certificate signing request (CSR) in DER format, if this
+ // context's cert is self-signed. If the cert is already signed, returns
+ // boost::none.
+ boost::optional<CertSignRequest> GetCsrIfNecessary() const;
+
+ // Adopts the provided CA-signed certificate for this TLS context.
+ //
+ // The certificate must correspond to a CSR previously returned by
+ // 'GetCsrIfNecessary()'.
+ //
+ // Checks that the CA that issued the signature on 'cert' is already trusted
+ // by this context (e.g. by AddTrustedCertificate()).
+ //
+ // This has no effect if the instance already has a CA-signed cert.
+ Status AdoptSignedCert(const Cert& cert) WARN_UNUSED_RESULT;
+
+ // Convenience functions for loading cert/CA/key from file paths.
+ // -------------------------------------------------------------
+
+ // Load the server certificate and key (PEM encoded).
+ Status LoadCertificateAndKey(const std::string& certificate_path,
+ const std::string& key_path) WARN_UNUSED_RESULT;
+
+ // Load the certificate authority (PEM encoded).
+ Status LoadCertificateAuthority(const std::string& certificate_path) WARN_UNUSED_RESULT;
+
+ // Initiates a new TlsHandshake instance.
+ Status InitiateHandshake(TlsHandshakeType handshake_type,
+ TlsHandshake* handshake) const WARN_UNUSED_RESULT;
+
+ // Return the number of certs that have been marked as trusted.
+ // Used by tests.
+ int trusted_cert_count_for_tests() const {
+ shared_lock<RWMutex> lock(lock_);
+ return trusted_cert_count_;
+ }
+
+ bool is_external_cert() const { return is_external_cert_; }
+
+ private:
+
+ Status VerifyCertChainUnlocked(const Cert& cert) WARN_UNUSED_RESULT;
+
+ // Protects all members.
+ //
+ // Taken in write mode when any changes are modifying the underlying SSL_CTX
+ // using a mutating method (eg SSL_CTX_use_*) or when changing the value of
+ // any of our own member variables.
+ mutable RWMutex lock_;
+ c_unique_ptr<SSL_CTX> ctx_;
+ int32_t trusted_cert_count_;
+ bool has_cert_;
+ bool is_external_cert_;
+ boost::optional<CertSignRequest> csr_;
+};
+
+} // namespace security
+} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/tls_handshake-test.cc
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/tls_handshake-test.cc b/be/src/kudu/security/tls_handshake-test.cc
new file mode 100644
index 0000000..60b1b91
--- /dev/null
+++ b/be/src/kudu/security/tls_handshake-test.cc
@@ -0,0 +1,385 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "kudu/security/tls_handshake.h"
+
+#include <atomic>
+#include <functional>
+#include <iostream>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <boost/optional.hpp>
+#include <gflags/gflags.h>
+#include <gtest/gtest.h>
+
+#include "kudu/security/ca/cert_management.h"
+#include "kudu/security/crypto.h"
+#include "kudu/security/security-test-util.h"
+#include "kudu/security/tls_context.h"
+#include "kudu/util/scoped_cleanup.h"
+#include "kudu/util/test_util.h"
+
+using std::string;
+using std::vector;
+
+DECLARE_int32(ipki_server_key_size);
+
+namespace kudu {
+namespace security {
+
+using ca::CertSigner;
+
+struct Case {
+ PkiConfig client_pki;
+ TlsVerificationMode client_verification;
+ PkiConfig server_pki;
+ TlsVerificationMode server_verification;
+ Status expected_status;
+};
+
+// Beautifies CLI test output.
+std::ostream& operator<<(std::ostream& o, Case c) {
+ auto verification_mode_name = [] (const TlsVerificationMode& verification_mode) {
+ switch (verification_mode) {
+ case TlsVerificationMode::VERIFY_NONE: return "NONE";
+ case TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST: return "REMOTE_CERT_AND_HOST";
+ }
+ return "unreachable";
+ };
+
+ o << "{client-pki: " << c.client_pki << ", "
+ << "client-verification: " << verification_mode_name(c.client_verification) << ", "
+ << "server-pki: " << c.server_pki << ", "
+ << "server-verification: " << verification_mode_name(c.server_verification) << ", "
+ << "expected-status: " << c.expected_status.ToString() << "}";
+
+ return o;
+}
+
+class TestTlsHandshakeBase : public KuduTest {
+ public:
+ void SetUp() override {
+ KuduTest::SetUp();
+
+ ASSERT_OK(client_tls_.Init());
+ ASSERT_OK(server_tls_.Init());
+ }
+
+ protected:
+ // Run a handshake using 'client_tls_' and 'server_tls_'. The client and server
+ // verification modes are set to 'client_verify' and 'server_verify' respectively.
+ Status RunHandshake(TlsVerificationMode client_verify,
+ TlsVerificationMode server_verify) {
+ TlsHandshake client, server;
+ RETURN_NOT_OK(client_tls_.InitiateHandshake(TlsHandshakeType::CLIENT, &client));
+ RETURN_NOT_OK(server_tls_.InitiateHandshake(TlsHandshakeType::SERVER, &server));
+
+ client.set_verification_mode(client_verify);
+ server.set_verification_mode(server_verify);
+
+ bool client_done = false, server_done = false;
+ string to_client;
+ string to_server;
+ while (!client_done || !server_done) {
+ if (!client_done) {
+ Status s = client.Continue(to_client, &to_server);
+ VLOG(1) << "client->server: " << to_server.size() << " bytes";
+ if (s.ok()) {
+ client_done = true;
+ } else if (!s.IsIncomplete()) {
+ CHECK(s.IsRuntimeError());
+ return s.CloneAndPrepend("client error");
+ }
+ }
+ if (!server_done) {
+ CHECK(!client_done);
+ Status s = server.Continue(to_server, &to_client);
+ VLOG(1) << "server->client: " << to_client.size() << " bytes";
+ if (s.ok()) {
+ server_done = true;
+ } else if (!s.IsIncomplete()) {
+ CHECK(s.IsRuntimeError());
+ return s.CloneAndPrepend("server error");
+ }
+ }
+ }
+ return Status::OK();
+ }
+
+ TlsContext client_tls_;
+ TlsContext server_tls_;
+
+ string cert_path_;
+ string key_path_;
+};
+
+class TestTlsHandshake : public TestTlsHandshakeBase,
+ public ::testing::WithParamInterface<Case> {};
+
+class TestTlsHandshakeConcurrent : public TestTlsHandshakeBase,
+ public ::testing::WithParamInterface<int> {};
+
+// Test concurrently running handshakes while changing the certificates on the TLS
+// context. We parameterize across different numbers of threads, because surprisingly,
+// fewer threads seems to trigger issues more easily in some cases.
+INSTANTIATE_TEST_CASE_P(NumThreads, TestTlsHandshakeConcurrent, ::testing::Values(1, 2, 4, 8));
+TEST_P(TestTlsHandshakeConcurrent, TestConcurrentAdoptCert) {
+ const int kNumThreads = GetParam();
+
+ ASSERT_OK(server_tls_.GenerateSelfSignedCertAndKey());
+ std::atomic<bool> done(false);
+ vector<std::thread> handshake_threads;
+ for (int i = 0; i < kNumThreads; i++) {
+ handshake_threads.emplace_back([&]() {
+ while (!done) {
+ RunHandshake(TlsVerificationMode::VERIFY_NONE, TlsVerificationMode::VERIFY_NONE);
+ }
+ });
+ }
+ auto c = MakeScopedCleanup([&](){
+ done = true;
+ for (std::thread& t : handshake_threads) {
+ t.join();
+ }
+ });
+
+ SleepFor(MonoDelta::FromMilliseconds(10));
+ {
+ PrivateKey ca_key;
+ Cert ca_cert;
+ ASSERT_OK(GenerateSelfSignedCAForTests(&ca_key, &ca_cert));
+ Cert cert;
+ ASSERT_OK(CertSigner(&ca_cert, &ca_key).Sign(*server_tls_.GetCsrIfNecessary(), &cert));
+ ASSERT_OK(server_tls_.AddTrustedCertificate(ca_cert));
+ ASSERT_OK(server_tls_.AdoptSignedCert(cert));
+ }
+ SleepFor(MonoDelta::FromMilliseconds(10));
+}
+
+TEST_F(TestTlsHandshake, TestHandshakeSequence) {
+ PrivateKey ca_key;
+ Cert ca_cert;
+ ASSERT_OK(GenerateSelfSignedCAForTests(&ca_key, &ca_cert));
+
+ // Both client and server have certs and CA.
+ ASSERT_OK(ConfigureTlsContext(PkiConfig::SIGNED, ca_cert, ca_key, &client_tls_));
+ ASSERT_OK(ConfigureTlsContext(PkiConfig::SIGNED, ca_cert, ca_key, &server_tls_));
+
+ TlsHandshake server;
+ TlsHandshake client;
+ ASSERT_OK(client_tls_.InitiateHandshake(TlsHandshakeType::SERVER, &server));
+ ASSERT_OK(server_tls_.InitiateHandshake(TlsHandshakeType::CLIENT, &client));
+
+ string buf1;
+ string buf2;
+
+ // Client sends Hello
+ ASSERT_TRUE(client.Continue(buf1, &buf2).IsIncomplete());
+ ASSERT_GT(buf2.size(), 0);
+
+ // Server receives client Hello, and sends server Hello
+ ASSERT_TRUE(server.Continue(buf2, &buf1).IsIncomplete());
+ ASSERT_GT(buf1.size(), 0);
+
+ // Client receives server Hello and sends client Finished
+ ASSERT_TRUE(client.Continue(buf1, &buf2).IsIncomplete());
+ ASSERT_GT(buf2.size(), 0);
+
+ // Server receives client Finished and sends server Finished
+ ASSERT_OK(server.Continue(buf2, &buf1));
+ ASSERT_GT(buf1.size(), 0);
+
+ // Client receives server Finished
+ ASSERT_OK(client.Continue(buf1, &buf2));
+ ASSERT_EQ(buf2.size(), 0);
+}
+
+// Tests that the TlsContext can transition from self signed cert to signed
+// cert, and that it rejects invalid certs along the way. We are testing this
+// here instead of in a dedicated TlsContext test because it requires completing
+// handshakes to fully validate.
+TEST_F(TestTlsHandshake, TestTlsContextCertTransition) {
+ ASSERT_FALSE(server_tls_.has_cert());
+ ASSERT_FALSE(server_tls_.has_signed_cert());
+ ASSERT_EQ(boost::none, server_tls_.GetCsrIfNecessary());
+
+ ASSERT_OK(server_tls_.GenerateSelfSignedCertAndKey());
+ ASSERT_TRUE(server_tls_.has_cert());
+ ASSERT_FALSE(server_tls_.has_signed_cert());
+ ASSERT_NE(boost::none, server_tls_.GetCsrIfNecessary());
+ ASSERT_OK(RunHandshake(TlsVerificationMode::VERIFY_NONE, TlsVerificationMode::VERIFY_NONE));
+ ASSERT_STR_MATCHES(RunHandshake(TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ TlsVerificationMode::VERIFY_NONE).ToString(),
+ "client error:.*certificate verify failed");
+
+ PrivateKey ca_key;
+ Cert ca_cert;
+ ASSERT_OK(GenerateSelfSignedCAForTests(&ca_key, &ca_cert));
+
+ Cert cert;
+ ASSERT_OK(CertSigner(&ca_cert, &ca_key).Sign(*server_tls_.GetCsrIfNecessary(), &cert));
+
+ // Try to adopt the cert without first trusting the CA.
+ ASSERT_STR_MATCHES(server_tls_.AdoptSignedCert(cert).ToString(),
+ "could not verify certificate chain");
+
+ // Check that we can still do (unverified) handshakes.
+ ASSERT_TRUE(server_tls_.has_cert());
+ ASSERT_FALSE(server_tls_.has_signed_cert());
+ ASSERT_OK(RunHandshake(TlsVerificationMode::VERIFY_NONE, TlsVerificationMode::VERIFY_NONE));
+
+ // Trust the root cert.
+ ASSERT_OK(server_tls_.AddTrustedCertificate(ca_cert));
+
+ // Generate a bogus cert and attempt to adopt it.
+ Cert bogus_cert;
+ {
+ TlsContext bogus_tls;
+ ASSERT_OK(bogus_tls.Init());
+ ASSERT_OK(bogus_tls.GenerateSelfSignedCertAndKey());
+ ASSERT_OK(CertSigner(&ca_cert, &ca_key).Sign(*bogus_tls.GetCsrIfNecessary(), &bogus_cert));
+ }
+ ASSERT_STR_MATCHES(server_tls_.AdoptSignedCert(bogus_cert).ToString(),
+ "certificate public key does not match the CSR public key");
+
+ // Check that we can still do (unverified) handshakes.
+ ASSERT_TRUE(server_tls_.has_cert());
+ ASSERT_FALSE(server_tls_.has_signed_cert());
+ ASSERT_OK(RunHandshake(TlsVerificationMode::VERIFY_NONE, TlsVerificationMode::VERIFY_NONE));
+
+ // Adopt the legitimate signed cert.
+ ASSERT_OK(server_tls_.AdoptSignedCert(cert));
+
+ // Check that we can do verified handshakes.
+ ASSERT_TRUE(server_tls_.has_cert());
+ ASSERT_TRUE(server_tls_.has_signed_cert());
+ ASSERT_OK(RunHandshake(TlsVerificationMode::VERIFY_NONE, TlsVerificationMode::VERIFY_NONE));
+ ASSERT_OK(client_tls_.AddTrustedCertificate(ca_cert));
+ ASSERT_OK(RunHandshake(TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ TlsVerificationMode::VERIFY_NONE));
+}
+
+TEST_P(TestTlsHandshake, TestHandshake) {
+ Case test_case = GetParam();
+
+ PrivateKey ca_key;
+ Cert ca_cert;
+ ASSERT_OK(GenerateSelfSignedCAForTests(&ca_key, &ca_cert));
+
+ ASSERT_OK(ConfigureTlsContext(test_case.client_pki, ca_cert, ca_key, &client_tls_));
+ ASSERT_OK(ConfigureTlsContext(test_case.server_pki, ca_cert, ca_key, &server_tls_));
+
+ Status s = RunHandshake(test_case.client_verification, test_case.server_verification);
+
+ EXPECT_EQ(test_case.expected_status.CodeAsString(), s.CodeAsString());
+ ASSERT_STR_MATCHES(s.ToString(), test_case.expected_status.message().ToString());
+}
+
+INSTANTIATE_TEST_CASE_P(CertCombinations,
+ TestTlsHandshake,
+ ::testing::Values(
+
+ // We don't test any cases where the server has no cert or the client
+ // has a self-signed cert, since we don't expect those to occur in
+ // practice.
+
+ Case { PkiConfig::NONE, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::OK() },
+ Case { PkiConfig::NONE, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::RuntimeError("client error:.*certificate verify failed") },
+ Case { PkiConfig::NONE, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::RuntimeError("server error:.*peer did not return a certificate") },
+ Case { PkiConfig::NONE, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::RuntimeError("client error:.*certificate verify failed") },
+
+ Case { PkiConfig::NONE, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::OK() },
+ Case { PkiConfig::NONE, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::RuntimeError("client error:.*certificate verify failed") },
+ Case { PkiConfig::NONE, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::RuntimeError("server error:.*peer did not return a certificate") },
+ Case { PkiConfig::NONE, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::RuntimeError("client error:.*certificate verify failed") },
+
+ Case { PkiConfig::TRUSTED, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::OK() },
+ Case { PkiConfig::TRUSTED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::RuntimeError("client error:.*certificate verify failed") },
+ Case { PkiConfig::TRUSTED, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::RuntimeError("server error:.*peer did not return a certificate") },
+ Case { PkiConfig::TRUSTED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::RuntimeError("client error:.*certificate verify failed") },
+
+ Case { PkiConfig::TRUSTED, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::OK() },
+ Case { PkiConfig::TRUSTED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::OK() },
+ Case { PkiConfig::TRUSTED, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::RuntimeError("server error:.*peer did not return a certificate") },
+ Case { PkiConfig::TRUSTED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::RuntimeError("server error:.*peer did not return a certificate") },
+
+ Case { PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::OK() },
+ Case { PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::RuntimeError("client error:.*certificate verify failed") },
+ Case { PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ // OpenSSL 1.0.0 returns "no certificate returned" for this case,
+ // which appears to be a bug.
+ Status::RuntimeError("server error:.*(certificate verify failed|"
+ "no certificate returned)") },
+ Case { PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SELF_SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::RuntimeError("client error:.*certificate verify failed") },
+
+ Case { PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::OK() },
+ Case { PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ Status::OK() },
+ Case { PkiConfig::SIGNED, TlsVerificationMode::VERIFY_NONE,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::OK() },
+ Case { PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ PkiConfig::SIGNED, TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST,
+ Status::OK() }
+));
+
+} // namespace security
+} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/tls_handshake.cc
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/tls_handshake.cc b/be/src/kudu/security/tls_handshake.cc
new file mode 100644
index 0000000..b4e3937
--- /dev/null
+++ b/be/src/kudu/security/tls_handshake.cc
@@ -0,0 +1,257 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "kudu/security/tls_handshake.h"
+
+#include <memory>
+#include <string>
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/security/cert.h"
+#include "kudu/security/tls_socket.h"
+#include "kudu/util/net/sockaddr.h"
+#include "kudu/util/status.h"
+#include "kudu/util/trace.h"
+
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+#include "kudu/security/x509_check_host.h"
+#endif // OPENSSL_VERSION_NUMBER
+
+using std::string;
+using std::unique_ptr;
+using strings::Substitute;
+
+namespace kudu {
+namespace security {
+
+void TlsHandshake::SetSSLVerify() {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ CHECK(ssl_);
+ CHECK(!has_started_);
+ int ssl_mode = 0;
+ switch (verification_mode_) {
+ case TlsVerificationMode::VERIFY_NONE:
+ ssl_mode = SSL_VERIFY_NONE;
+ break;
+ case TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST:
+ // Server mode: the server sends a client certificate request to the client. The
+ // certificate returned (if any) is checked. If the verification process fails, the TLS/SSL
+ // handshake is immediately terminated with an alert message containing the reason for the
+ // verification failure. The behaviour can be controlled by the additional
+ // SSL_VERIFY_FAIL_IF_NO_PEER_CERT and SSL_VERIFY_CLIENT_ONCE flags.
+
+ // Client mode: the server certificate is verified. If the verification process fails, the
+ // TLS/SSL handshake is immediately terminated with an alert message containing the reason
+ // for the verification failure. If no server certificate is sent, because an anonymous
+ // cipher is used, SSL_VERIFY_PEER is ignored.
+ ssl_mode |= SSL_VERIFY_PEER;
+
+ // Server mode: if the client did not return a certificate, the TLS/SSL handshake is
+ // immediately terminated with a "handshake failure" alert. This flag must be used
+ // together with SSL_VERIFY_PEER.
+ ssl_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+ // Server mode: only request a client certificate on the initial TLS/SSL handshake. Do
+ // not ask for a client certificate again in case of a renegotiation. This flag must be
+ // used together with SSL_VERIFY_PEER.
+ ssl_mode |= SSL_VERIFY_CLIENT_ONCE;
+ break;
+ }
+
+ SSL_set_verify(ssl_.get(), ssl_mode, /* callback = */nullptr);
+}
+
+Status TlsHandshake::Continue(const string& recv, string* send) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ if (!has_started_) {
+ SetSSLVerify();
+ has_started_ = true;
+ }
+ CHECK(ssl_);
+
+ BIO* rbio = SSL_get_rbio(ssl_.get());
+ int n = BIO_write(rbio, recv.data(), recv.size());
+ DCHECK_EQ(n, recv.size());
+ DCHECK_EQ(BIO_ctrl_pending(rbio), recv.size());
+
+ int rc = SSL_do_handshake(ssl_.get());
+ if (rc != 1) {
+ int ssl_err = SSL_get_error(ssl_.get(), rc);
+ // WANT_READ and WANT_WRITE indicate that the handshake is not yet complete.
+ if (ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE) {
+ return Status::RuntimeError("TLS Handshake error", GetSSLErrorDescription(ssl_err));
+ }
+ // In the case that we got SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE,
+ // the OpenSSL implementation guarantees that there is no error entered into
+ // the ERR error queue, so no need to ERR_clear_error() here.
+ }
+
+ BIO* wbio = SSL_get_wbio(ssl_.get());
+ int pending = BIO_ctrl_pending(wbio);
+
+ send->resize(pending);
+ BIO_read(wbio, &(*send)[0], send->size());
+ DCHECK_EQ(BIO_ctrl_pending(wbio), 0);
+
+ if (rc == 1) {
+ // The handshake is done, but in the case of the server, we still need to
+ // send the final response to the client.
+ DCHECK_GE(send->size(), 0);
+ return Status::OK();
+ }
+ DCHECK_GT(send->size(), 0);
+ return Status::Incomplete("TLS Handshake incomplete");
+}
+
+Status TlsHandshake::Verify(const Socket& socket) const {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ DCHECK(SSL_is_init_finished(ssl_.get()));
+ CHECK(ssl_);
+
+ if (verification_mode_ == TlsVerificationMode::VERIFY_NONE) {
+ return Status::OK();
+ }
+ DCHECK(verification_mode_ == TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST);
+
+ int rc = SSL_get_verify_result(ssl_.get());
+ if (rc != X509_V_OK) {
+ return Status::NotAuthorized(Substitute("SSL cert verification failed: $0",
+ X509_verify_cert_error_string(rc)),
+ GetOpenSSLErrors());
+ }
+
+ // Get the peer certificate.
+ X509* cert = remote_cert_.GetRawData();
+ 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");
+ }
+ // No cert, but we weren't requiring one.
+ TRACE("Got no cert from peer, but not required");
+ return Status::OK();
+ }
+
+ // TODO(KUDU-1886): Do hostname verification.
+ /*
+ TRACE("Verifying peer cert");
+
+ // Get the peer's hostname
+ Sockaddr peer_addr;
+ if (!socket.GetPeerAddress(&peer_addr).ok()) {
+ return Status::NotAuthorized(
+ "TLS certificate hostname verification failed: unable to get peer address");
+ }
+ string peer_hostname;
+ RETURN_NOT_OK_PREPEND(peer_addr.LookupHostname(&peer_hostname),
+ "TLS certificate hostname verification failed: unable to lookup peer hostname");
+
+ // Check if the hostname matches with either the Common Name or any of the Subject Alternative
+ // Names of the certificate.
+ int match = X509_check_host(cert,
+ peer_hostname.c_str(),
+ peer_hostname.length(),
+ 0,
+ nullptr);
+ if (match == 0) {
+ return Status::NotAuthorized("TLS certificate hostname verification failed");
+ }
+ if (match < 0) {
+ return Status::RuntimeError("TLS certificate hostname verification error", GetOpenSSLErrors());
+ }
+ DCHECK_EQ(match, 1);
+ */
+ return Status::OK();
+}
+
+Status TlsHandshake::GetCerts() {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ X509* cert = SSL_get_certificate(ssl_.get());
+ 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);
+ }
+
+ cert = SSL_get_peer_certificate(ssl_.get());
+ if (cert) {
+ remote_cert_.AdoptRawData(cert);
+ }
+ return Status::OK();
+}
+
+Status TlsHandshake::Finish(unique_ptr<Socket>* socket) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ RETURN_NOT_OK(GetCerts());
+ RETURN_NOT_OK(Verify(**socket));
+
+ int fd = (*socket)->Release();
+
+ // Give the socket to the SSL instance. This will automatically free the
+ // read and write memory BIO instances.
+ int ret = SSL_set_fd(ssl_.get(), fd);
+ if (ret != 1) {
+ return Status::RuntimeError("TLS handshake error", GetOpenSSLErrors());
+ }
+
+ // Transfer the SSL instance to the socket.
+ socket->reset(new TlsSocket(fd, std::move(ssl_)));
+
+ return Status::OK();
+}
+
+Status TlsHandshake::FinishNoWrap(const Socket& socket) {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ RETURN_NOT_OK(GetCerts());
+ return Verify(socket);
+}
+
+Status TlsHandshake::GetLocalCert(Cert* cert) const {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ if (!local_cert_.GetRawData()) {
+ return Status::RuntimeError("no local certificate");
+ }
+ cert->AdoptAndAddRefRawData(local_cert_.GetRawData());
+ return Status::OK();
+}
+
+Status TlsHandshake::GetRemoteCert(Cert* cert) const {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ if (!remote_cert_.GetRawData()) {
+ return Status::RuntimeError("no remote certificate");
+ }
+ cert->AdoptAndAddRefRawData(remote_cert_.GetRawData());
+ return Status::OK();
+}
+
+string TlsHandshake::GetCipherSuite() const {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ CHECK(has_started_);
+ return SSL_get_cipher_name(ssl_.get());
+}
+
+string TlsHandshake::GetProtocol() const {
+ SCOPED_OPENSSL_NO_PENDING_ERRORS;
+ CHECK(has_started_);
+ return SSL_get_version(ssl_.get());
+}
+
+} // namespace security
+} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/84b8155c/be/src/kudu/security/tls_handshake.h
----------------------------------------------------------------------
diff --git a/be/src/kudu/security/tls_handshake.h b/be/src/kudu/security/tls_handshake.h
new file mode 100644
index 0000000..2e7031f
--- /dev/null
+++ b/be/src/kudu/security/tls_handshake.h
@@ -0,0 +1,166 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <glog/logging.h>
+
+#include "kudu/security/cert.h"
+#include "kudu/security/openssl_util.h"
+#include "kudu/util/net/socket.h"
+#include "kudu/util/status.h"
+
+namespace kudu {
+
+class Socket;
+
+namespace security {
+
+enum class TlsHandshakeType {
+ // The local endpoint is the TLS client (initiator).
+ CLIENT,
+ // The local endpoint is the TLS server (acceptor).
+ SERVER,
+};
+
+// Mode for performing verification of the remote peer's identity during a handshake.
+enum class TlsVerificationMode {
+ // SERVER:
+ // No certificate will be requested from the client, and no verification
+ // will be done.
+ // CLIENT:
+ // The server's certificate will be obtained but no verification will be done.
+ // (the server still requires a certificate, even if it is self-signed).
+ VERIFY_NONE,
+
+ // BOTH:
+ // The remote peer is required to have a signed certificate. The certificate will
+ // be verified in two ways:
+ // 1) The certificate must be signed by a trusted CA (or chain of CAs).
+ // 2) Second, the hostname of the remote peer (as determined by reverse DNS of the
+ // socket address) must match the common name or one of the Subject Alternative
+ // Names stored in the certificate.
+ VERIFY_REMOTE_CERT_AND_HOST
+};
+
+// TlsHandshake manages an ongoing TLS handshake between a client and server.
+//
+// TlsHandshake instances are default constructed, but must be initialized
+// before use using TlsContext::InitiateHandshake.
+class TlsHandshake {
+ public:
+
+ TlsHandshake() = default;
+ ~TlsHandshake() = default;
+
+ // Set the verification mode for this handshake. The default verification mode
+ // is VERIFY_REMOTE_CERT_AND_HOST.
+ //
+ // This must be called before the first call to Continue().
+ void set_verification_mode(TlsVerificationMode mode) {
+ DCHECK(!has_started_);
+ verification_mode_ = mode;
+ }
+
+ // Continue or start a new handshake.
+ //
+ // 'recv' should contain the input buffer from the remote end, or an empty
+ // string when the handshake is new.
+ //
+ // 'send' should contain the output buffer which must be sent to the remote
+ // end.
+ //
+ // Returns Status::OK when the handshake is complete, however the 'send'
+ // buffer may contain a message which must still be transmitted to the remote
+ // end. If the send buffer is empty after this call and the return is
+ // Status::OK, the socket should immediately be wrapped in the TLS channel
+ // using 'Finish'. If the send buffer is not empty, the message should be sent
+ // to the remote end, and then the socket should be wrapped using 'Finish'.
+ //
+ // Returns Status::Incomplete when the handshake must continue for another
+ // round of messages.
+ //
+ // Returns any other status code on error.
+ Status Continue(const std::string& recv, std::string* send) WARN_UNUSED_RESULT;
+
+ // Finishes the handshake, wrapping the provided socket in the negotiated TLS
+ // channel. This 'TlsHandshake' instance should not be used again after
+ // calling this.
+ Status Finish(std::unique_ptr<Socket>* socket) WARN_UNUSED_RESULT;
+
+ // Finish the handshake, using the provided socket to verify the remote peer,
+ // but without wrapping the socket.
+ Status FinishNoWrap(const Socket& socket) WARN_UNUSED_RESULT;
+
+ // Retrieve the local certificate. This will return an error status if there
+ // is no local certificate.
+ //
+ // May only be called after 'Finish' or 'FinishNoWrap'.
+ Status GetLocalCert(Cert* cert) const WARN_UNUSED_RESULT;
+
+ // Retrieve the remote peer's certificate. This will return an error status if
+ // there is no remote certificate.
+ //
+ // May only be called after 'Finish' or 'FinishNoWrap'.
+ Status GetRemoteCert(Cert* cert) const WARN_UNUSED_RESULT;
+
+ // Retrieve the negotiated cipher suite. Only valid to call after the
+ // handshake is complete and before 'Finish()'.
+ std::string GetCipherSuite() const;
+
+ // Retrieve the negotiated TLS protocol version. Only valid to call after the
+ // handshake is complete and before 'Finish()'.
+ std::string GetProtocol() const;
+
+ private:
+ friend class TlsContext;
+
+ bool has_started_ = false;
+ TlsVerificationMode verification_mode_ = TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST;
+
+ // Set the verification mode on the underlying SSL object.
+ void SetSSLVerify();
+
+ // Set the SSL to use during the handshake. Called once by
+ // TlsContext::InitiateHandshake before starting the handshake processes.
+ void adopt_ssl(c_unique_ptr<SSL> ssl) {
+ CHECK(!ssl_);
+ ssl_ = std::move(ssl);
+ }
+
+ SSL* ssl() {
+ return ssl_.get();
+ }
+
+ // Populates local_cert_ and remote_cert_.
+ Status GetCerts() WARN_UNUSED_RESULT;
+
+ // Verifies that the handshake is valid for the provided socket.
+ Status Verify(const Socket& socket) const WARN_UNUSED_RESULT;
+
+ // Owned SSL handle.
+ c_unique_ptr<SSL> ssl_;
+
+ Cert local_cert_;
+ Cert remote_cert_;
+};
+
+} // namespace security
+} // namespace kudu