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/[email protected]" \ +// -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
