This is an automated email from the ASF dual-hosted git repository. shinrich pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push: new 4904954 Clean up client certificate specification and add tests 4904954 is described below commit 490495431f1f11e9e52cff12df54d51115cef2c0 Author: Susan Hinrichs <shinr...@oath.com> AuthorDate: Fri Oct 19 14:30:49 2018 +0000 Clean up client certificate specification and add tests --- doc/admin-guide/files/records.config.en.rst | 1 - doc/admin-guide/files/ssl_server_name.yaml.en.rst | 9 + iocore/net/P_SSLConfig.h | 7 +- iocore/net/SSLConfig.cc | 70 +----- iocore/net/SSLSNIConfig.cc | 8 +- iocore/net/YamlSNIConfig.cc | 16 +- iocore/net/YamlSNIConfig.h | 3 +- proxy/http/HttpConfig.cc | 4 - proxy/http/HttpSM.cc | 17 -- tests/gold_tests/autest-site/microserver.test.ext | 15 +- tests/gold_tests/tls/ssl/signed2-bar.pem | 21 ++ tests/gold_tests/tls/ssl/signed2-foo.pem | 21 ++ tests/gold_tests/tls/ssl/signer2.key | 28 +++ tests/gold_tests/tls/ssl/signer2.pem | 23 ++ tests/gold_tests/tls/tls_client_cert.test.py | 254 ++++++++++++++++++++++ tests/tools/microServer/uWServer.py | 14 +- 16 files changed, 409 insertions(+), 102 deletions(-) diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index 9742709..152bf73 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -3497,7 +3497,6 @@ Client-Related Configuration :2: The provided certificate will be verified and the connection will be established .. ts:cv:: CONFIG proxy.config.ssl.client.cert.filename STRING NULL - :overridable: The filename of SSL client certificate installed on |TS|. diff --git a/doc/admin-guide/files/ssl_server_name.yaml.en.rst b/doc/admin-guide/files/ssl_server_name.yaml.en.rst index 2169d8a..32ab6af 100644 --- a/doc/admin-guide/files/ssl_server_name.yaml.en.rst +++ b/doc/admin-guide/files/ssl_server_name.yaml.en.rst @@ -75,6 +75,15 @@ client_cert The file containing the client certificate to use for :ts:cv:`proxy.config.ssl.server.cert.path`. If not set :ts:cv:`proxy.config.ssl.client.cert.filename` is used. +client_key The file containing the client private key that corresponds to the certificate + for the outbound connection. + + If this is relative it is relative to the path in + :ts:cv:`proxy.config.ssl.server.private_key.path`. If not set, + |TS| tries to use a private key in client_cert. Otherwise, + :ts:cv:`proxy.config.ssl.client.private_key.filename` is used. + + disable_h2 :code:`true` or :code:`false`. If :code:`false` then HTTP/2 is removed from diff --git a/iocore/net/P_SSLConfig.h b/iocore/net/P_SSLConfig.h index 152a6bb..5ea0c95 100644 --- a/iocore/net/P_SSLConfig.h +++ b/iocore/net/P_SSLConfig.h @@ -126,13 +126,8 @@ struct SSLConfigParams : public ConfigInfo { mutable HashMap<cchar *, class StringHashFns, SSL_CTX *> ctx_map; mutable ink_mutex ctxMapLock; - SSL_CTX *getCTX(cchar *client_cert) const; - void deleteKey(cchar *key) const; - void freeCTXmap() const; - void printCTXmap() const; - bool InsertCTX(cchar *client_cert, SSL_CTX *cctx) const; SSL_CTX *getClientSSL_CTX(void) const; - SSL_CTX *getNewCTX(cchar *client_cert) const; + SSL_CTX *getNewCTX(cchar *client_cert, cchar *key_file) const; void initialize(); void cleanup(); diff --git a/iocore/net/SSLConfig.cc b/iocore/net/SSLConfig.cc index 6291a2a..f2290c9 100644 --- a/iocore/net/SSLConfig.cc +++ b/iocore/net/SSLConfig.cc @@ -137,7 +137,6 @@ SSLConfigParams::cleanup() server_groups_list = (char *)ats_free_null(server_groups_list); client_groups_list = (char *)ats_free_null(client_groups_list); - freeCTXmap(); SSLReleaseContext(client_ctx); reset(); } @@ -456,62 +455,12 @@ SSLConfigParams::initialize() client_ctx = SSLInitClientContext(this); if (!client_ctx) { SSLError("Can't initialize the SSL client, HTTPS in remap rules will not function"); - } else { - InsertCTX(this->clientCertPath, this->client_ctx); } } -// getCTX: returns the context attached to the given certificate -SSL_CTX * -SSLConfigParams::getCTX(cchar *client_cert) const -{ - ink_mutex_acquire(&ctxMapLock); - auto client_ctx = ctx_map.get(client_cert); - ink_mutex_release(&ctxMapLock); - return client_ctx; -} - -// InsertCTX hashes on the absolute path to the client certificate file and stores in the map -bool -SSLConfigParams::InsertCTX(cchar *client_cert, SSL_CTX *cctx) const -{ - ink_mutex_acquire(&ctxMapLock); - // dup is required here to avoid the nullifying of the keys stored in the map. - // client_cert is coming from the overridable clientcert config retrieved by the remap plugin. - cchar *cert = ats_strdup(client_cert); - // Hashmap has no delete functionality :( - ctx_map.put(cert, cctx); - ink_mutex_release(&ctxMapLock); - return true; -} - -void -SSLConfigParams::printCTXmap() const -{ - Vec<cchar *> keys; - ctx_map.get_keys(keys); - for (size_t i = 0; i < keys.length(); i++) { - Debug("ssl", "Client certificates in the map %s: %p", keys.get(i), ctx_map.get(keys.get(i))); - } -} -void -SSLConfigParams::freeCTXmap() const -{ - ink_mutex_acquire(&ctxMapLock); - Vec<cchar *> keys; - ctx_map.get_keys(keys); - size_t n = keys.length(); - Debug("ssl", "freeing CTX Map"); - for (size_t i = 0; i < n; i++) { - deleteKey(keys.get(i)); - ats_free((char *)keys.get(i)); - } - ctx_map.clear(); - ink_mutex_release(&ctxMapLock); -} // creates a new context attaching the provided certificate SSL_CTX * -SSLConfigParams::getNewCTX(cchar *client_cert) const +SSLConfigParams::getNewCTX(cchar *client_cert, cchar *client_key) const { SSL_CTX *nclient_ctx = nullptr; nclient_ctx = SSLInitClientContext(this); @@ -519,22 +468,25 @@ SSLConfigParams::getNewCTX(cchar *client_cert) const SSLError("Can't initialize the SSL client, HTTPS in remap rules will not function"); return nullptr; } - if (nclient_ctx && client_cert != nullptr && client_cert[0] != '\0') { + if (client_cert != nullptr && client_cert[0] != '\0') { if (!SSL_CTX_use_certificate_chain_file(nclient_ctx, (const char *)client_cert)) { SSLError("failed to load client certificate from %s", this->clientCertPath); SSLReleaseContext(nclient_ctx); return nullptr; } } + // If there is not private key specified, perhaps it is in the file with the cert + if (client_key == nullptr || client_key[0] == '\0') { + client_key = client_cert; + } + // Try loading the private key + if (client_key != nullptr && client_key[0] != '\0') { + // If it failed, then we are just going to use the previously set private key from records.config + SSL_CTX_use_PrivateKey_file(nclient_ctx, client_key, SSL_FILETYPE_PEM); + } return nclient_ctx; } -void -SSLConfigParams::deleteKey(cchar *key) const -{ - SSL_CTX_free((SSL_CTX *)ctx_map.get(key)); -} - SSL_CTX * SSLConfigParams::getClientSSL_CTX() const { diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index 14551bb..069049e 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -86,11 +86,11 @@ SNIConfigParams::loadSNIConfig() aiVec->push_back(ai3); // set the next hop properties SSLConfig::scoped_config params; - auto clientCTX = params->getCTX(servername); + auto clientCTX = params->getClientSSL_CTX(); cchar *certFile = item.client_cert.data(); - if (!clientCTX && certFile) { - clientCTX = params->getNewCTX(certFile); - params->InsertCTX(certFile, clientCTX); + cchar *keyFile = item.client_key.data(); + if (certFile) { + clientCTX = params->getNewCTX(certFile, keyFile); } NextHopProperty *nps = new NextHopProperty(); nps->name = ats_strdup(servername); diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index ce462eb..5366e4b 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -59,9 +59,16 @@ TsEnumDescriptor LEVEL_DESCRIPTOR = {{{"NONE", 0}, {"MODERATE", 1}, {"STRIC TsEnumDescriptor POLICY_DESCRIPTOR = {{{"DISABLED", 0}, {"PERMISSIVE", 1}, {"ENFORCED", 2}}}; TsEnumDescriptor PROPERTIES_DESCRIPTOR = {{{"NONE", 0}, {"SIGNATURE", 0x1}, {"NAME", 0x2}, {"ALL", 0x3}}}; -std::set<std::string> valid_sni_config_keys = { - TS_fqdn, TS_disable_H2, TS_verify_client, TS_tunnel_route, TS_verify_server_policy, TS_verify_server_properties, - TS_client_cert, TS_ip_allow}; +std::set<std::string> valid_sni_config_keys = {TS_fqdn, + TS_disable_H2, + TS_verify_client, + TS_tunnel_route, + TS_verify_origin_server, + TS_verify_server_policy, + TS_verify_server_properties, + TS_client_cert, + TS_client_key, + TS_ip_allow}; namespace YAML { @@ -138,6 +145,9 @@ template <> struct convert<YamlSNIConfig::Item> { if (node[TS_client_cert]) { item.client_cert = node[TS_client_cert].as<std::string>(); } + if (node[TS_client_key]) { + item.client_key = node[TS_client_key].as<std::string>(); + } if (node[TS_ip_allow]) { item.ip_allow = node[TS_ip_allow].as<std::string>(); diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h index d89fe6f..0cb6d64 100644 --- a/iocore/net/YamlSNIConfig.h +++ b/iocore/net/YamlSNIConfig.h @@ -35,6 +35,7 @@ TSDECL(verify_server_policy); TSDECL(verify_server_properties); TSDECL(verify_origin_server); TSDECL(client_cert); +TSDECL(client_key); TSDECL(ip_allow); #undef TSDECL @@ -47,7 +48,6 @@ struct YamlSNIConfig { verify_server_policy, // this applies to server side vc only verify_server_properties, // this applies to server side vc only client_cert - }; enum class Level { NONE = 0, MODERATE, STRICT }; enum class Policy : uint8_t { DISABLED = 0, PERMISSIVE, ENFORCED }; @@ -63,6 +63,7 @@ struct YamlSNIConfig { Policy verify_server_policy = Policy::DISABLED; Property verify_server_properties = Property::NONE; std::string client_cert; + std::string client_key; std::string ip_allow; }; diff --git a/proxy/http/HttpConfig.cc b/proxy/http/HttpConfig.cc index 590fa46..13b1125 100644 --- a/proxy/http/HttpConfig.cc +++ b/proxy/http/HttpConfig.cc @@ -1001,8 +1001,6 @@ HttpConfig::startup() HttpEstablishStaticConfigByte(c.oride.insert_response_via_string, "proxy.config.http.insert_response_via_str"); HttpEstablishStaticConfigLongLong(c.oride.proxy_response_hsts_max_age, "proxy.config.ssl.hsts_max_age"); HttpEstablishStaticConfigByte(c.oride.proxy_response_hsts_include_subdomains, "proxy.config.ssl.hsts_include_subdomains"); - HttpEstablishStaticConfigStringAlloc(c.oride.client_cert_filename, "proxy.config.ssl.client.cert.filename"); - HttpEstablishStaticConfigStringAlloc(c.oride.client_cert_filepath, "proxy.config.ssl.client.cert.path"); HttpEstablishStaticConfigStringAlloc(c.proxy_request_via_string, "proxy.config.http.request_via_str"); c.proxy_request_via_string_len = -1; @@ -1487,8 +1485,6 @@ HttpConfig::reconfigure() params->redirection_host_no_port = INT_TO_BOOL(m_master.redirection_host_no_port); params->oride.number_of_redirections = m_master.oride.number_of_redirections; params->post_copy_size = m_master.post_copy_size; - params->oride.client_cert_filename = ats_strdup(m_master.oride.client_cert_filename); - params->oride.client_cert_filepath = ats_strdup(m_master.oride.client_cert_filepath); params->redirect_actions_string = ats_strdup(m_master.redirect_actions_string); params->redirect_actions_map = parse_redirect_actions(params->redirect_actions_string, params->redirect_actions_self_action); diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index d339068..cf16c09 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -5015,23 +5015,6 @@ HttpSM::do_http_server_open(bool raw) opt.set_ssl_servername(t_state.server_info.name); } - SSLConfig::scoped_config params; - // check if the overridden client cert filename is already attached to an existing ssl context - if (t_state.txn_conf->client_cert_filepath && t_state.txn_conf->client_cert_filename) { - ats_scoped_str clientCert( - Layout::relative_to(t_state.txn_conf->client_cert_filepath, t_state.txn_conf->client_cert_filename)); - if (clientCert != nullptr) { - auto tCTX = params->getCTX(clientCert); - - if (tCTX == nullptr) { - // make new client ctx and add it to the ctx list - Debug("ssl", "adding new cert for client cert %s", (char *)clientCert); - auto tctx = params->getNewCTX(clientCert); - params->InsertCTX(clientCert, tctx); - } - opt.set_client_certname(clientCert); - } - } connect_action_handle = sslNetProcessor.connect_re(this, // state machine &t_state.current.server->dst_addr.sa, // addr + port &opt); diff --git a/tests/gold_tests/autest-site/microserver.test.ext b/tests/gold_tests/autest-site/microserver.test.ext index 72f3178..6fe93df 100644 --- a/tests/gold_tests/autest-site/microserver.test.ext +++ b/tests/gold_tests/autest-site/microserver.test.ext @@ -132,13 +132,20 @@ def makeHeader(self, requestString, **kwargs): return headerStr -def uServerUpAndRunning(host, port, isSsl, isIPv6): +def uServerUpAndRunning(host, port, isSsl, isIPv6, clientcert='', clientkey=''): if isIPv6: plain_sock = socket.socket(socket.AF_INET6) else: plain_sock = socket.socket(socket.AF_INET) - sock = ssl.wrap_socket(plain_sock) if isSsl else plain_sock + if isSsl: + if clientcert != '' or clientkey != '': + sock = ssl.wrap_socket(plain_sock, keyfile=clientkey, certfile=clientcert) + else: + sock = ssl.wrap_socket(plain_sock) + else: + sock = plain_sock + try: sock.connect((host, port)) except ConnectionRefusedError: @@ -166,7 +173,7 @@ def uServerUpAndRunning(host, port, isSsl, isIPv6): AddWhenFunction(uServerUpAndRunning) -def MakeOriginServer(obj, name, port=False, ip='INADDR_LOOPBACK', delay=False, ssl=False, lookup_key='{PATH}', mode='test', options={}): +def MakeOriginServer(obj, name, port=False, ip='INADDR_LOOPBACK', delay=False, ssl=False, lookup_key='{PATH}', mode='test', options={}, clientcert='', clientkey=''): # to get the IP keywords in tools/lib sys.path.append(obj.Variables.AtsTestToolsDir) import lib.IPConstants as IPConstants @@ -212,7 +219,7 @@ def MakeOriginServer(obj, name, port=False, ip='INADDR_LOOPBACK', delay=False, s "options": "skipHooks" }) - p.Ready = When.uServerUpAndRunning(ipaddr, port, ssl, IPConstants.isIPv6(ip)) + p.Ready = When.uServerUpAndRunning(ipaddr, port, ssl, IPConstants.isIPv6(ip), clientcert=clientcert, clientkey=clientkey) p.ReturnCode = Any(None, 0) return p diff --git a/tests/gold_tests/tls/ssl/signed2-bar.pem b/tests/gold_tests/tls/ssl/signed2-bar.pem new file mode 100644 index 0000000..8810238 --- /dev/null +++ b/tests/gold_tests/tls/ssl/signed2-bar.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbjCCAlYCCQC/n10Tbs/+tzANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCSUwxEjAQBgNVBAcMCUNoYW1wYWlnbjEOMAwGA1UECgwFWWFo +b28xGjAYBgNVBAMMEXNpZ25lcjIueWFob28uY29tMSIwIAYJKoZIhvcNAQkBFhNz +aGlucmljaEBhcGFjaGUub3JnMB4XDTE4MTAxOTE3MzUyNVoXDTI4MTAxNjE3MzUy +NVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAklMMRQwEgYDVQQHDAtTcHJpbmdm +aWVsZDEQMA4GA1UECgwHRXhhbXBsZTEQMA4GA1UEAwwHYmFyLmNvbTEeMBwGCSqG +SIb3DQEJARYPYm9iQGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA7swOnuDGaffJv17oUQGTAmqvXzOc0QQ7GOX6cd81JCcCbWWuAC5O +gHbrKL34d7eqmCSeO0yLZzRvWZwL2EITnHGTZI/MWz8rbo+aUM7twgEfyLAQVrRv +DtEi0RzVTpoLt23V7iXGvtyykV96hSW0XqnfTiPbxsa8SoPTT1HhqcwLlE6ZZn7P +Du9IPJdGQSe5f9FZrAOZVA2cVTY3GnFWB3PgKbo7i8+Xa1USni8U67XOg5LD+7q7 +k+WVa1my9+YaL4sFIpboM/WQyEkJdE+nCLrPAslI7PP4iRJZ/QJ8QGfDLs+fqUNx +v+AbGmypRB39PhmIp18YkzE9VQq/W6LWfwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB +AQA0SU1rpeJkryF7Vg/6cpUBMIrcSUGlQdut9M9jUygzer6FoEsfl/gm08KcCL17 +gXzeGX00BZ+iO9mhFcIYs9tMfVfZU8HpEpos1kRL/jIzrsTcqK9nklBenRaClpXv +8yXKjZ5n1AOgCwH5iwZ48s65Pdk8h14jxDLYXpfHsyHPl4xqYwam0+cPYPf1YROz +TzzKccMR3kIvGvNO+xqhx3fNrP76DeszABI9ADR6GotpFfQEg0Jjnsi+leQKh6HK +ASZwq0NpgELBr/vQFONg5oUVcICG5TGZhswLWZD42n42N8OtegRDaL1vJ3S5yTOC +k30i+j2hfxKHWMDJ14UB+zl1 +-----END CERTIFICATE----- diff --git a/tests/gold_tests/tls/ssl/signed2-foo.pem b/tests/gold_tests/tls/ssl/signed2-foo.pem new file mode 100644 index 0000000..7728f7a --- /dev/null +++ b/tests/gold_tests/tls/ssl/signed2-foo.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlQCCQC/n10Tbs/+tjANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCSUwxEjAQBgNVBAcMCUNoYW1wYWlnbjEOMAwGA1UECgwFWWFo +b28xGjAYBgNVBAMMEXNpZ25lcjIueWFob28uY29tMSIwIAYJKoZIhvcNAQkBFhNz +aGlucmljaEBhcGFjaGUub3JnMB4XDTE4MTAxOTE3MzQ1N1oXDTI4MTAxNjE3MzQ1 +N1owcjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAklMMRIwEAYDVQQHDAlDaGFtcGFp +Z24xEDAOBgNVBAoMB0V4YW1wbGUxEDAOBgNVBAMMB2Zvby5jb20xHjAcBgkqhkiG +9w0BCQEWD2JvYkBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMmF46yvSjgLRBV6Wjy+JiX8iNa+SPhjpiiOc78T1PkdJ9bGRGKRFPxf +vKqz0KWuM1xig801A/m+CXWoAh6LFSttlmSVxneQcX8xd3O5hX/+5Pdh9auw5N19 +ty7pp0e0Gv9BzTD0ZJsGmnjZkAvHONnaFlmrnVeYq9zPnn+Cb/1i+UYSvO+w/OR6 +N0fpTUYp4uoZpk6qRXs3YnjMqYnKI/PCuZH/zLuT72MMdBf4DbBrmBqIAwZ0gzxp +NX5ew00C96ym9fJftSHDEOzDR6vQYu/iyxgKu7mEX61RyN44IfFKcKgQLjlvbMNk +cBMsuOvsAgnnZNxKjWRNpOKsA4CIdEsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA +aCMjyADDi6VB6by8B7ho59z1AtkjcEmPptrAIr/0pZLVaBK8lYNm1jWSJvSrR851 +6KEPE/pu7fExpQiShplY3RiujWqGYJK9oQPM5/YM8BEM5RF00CYDbRnb8NMYio6+ +zAsCK8nrodTglHIlsPc2NfL56z/XfVmkZYiyTQH1qbo8jGCGIs6iyydGnyItt+sz +hWWsYJLco69PWcLofQYDC9WFEGbTxEGRtDSO5gEC81WCbR4BhslXKhpqqGOOXxMH +AykUdSs4DQfWAjwbezRqaktMXWNhGPjWgIsE38p9sDzr18EyX2DGr4POQYjvcNCH +xWuVAbRPyFp8h98+wbfwqQ== +-----END CERTIFICATE----- diff --git a/tests/gold_tests/tls/ssl/signer2.key b/tests/gold_tests/tls/ssl/signer2.key new file mode 100644 index 0000000..dea7a01 --- /dev/null +++ b/tests/gold_tests/tls/ssl/signer2.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC/0ZxzgzZSY4rx ++GBEGYKCvUbb8sDKn5dV/HC3WbwJzlwrYZuwimAGv+In0oDpFuCsRGMe8SgpfenF +aUS7rfnCKYzDYJJLRqzlPnyXkJCEFLeM5PocZYMA9mWw6D7dmQUbVnbkBqbs28bp +Veq3EqSwL6nYW/vAAbu9yryoqPEcS63qFoVFZ1TZJUgCVg/9C21U1oB3JMg9sOGi +C2EotMLRp/IVgqjYMF8jZBPdoJUARgrsYUNuq9U4Yx/BKozBJSP/an175i1+TKTb +s+ugGYyd31opdqRY7slNMvlJFsO8LsplO/75a8tHr+phVXs3dj674h8rx6Kr4tpr +05H9vQFVAgMBAAECggEBAJNwRHctVeXCCZyvsx7lFMH1Rq/tSW71eFdpcCIeYUCQ +U5wsrITn96N4fhbp4JhJM2x2LFIuPxaVZ8iLf39a3Gz3jvDmG8ysiFy0yQOe5NYB +Loynu+9zv1xQCB4QhbHC2oAG19+xlUDeNWhI197b/6ZFHKHnIfyV2H6rhJOHN7zs +v7uSplxdtUVkof++J1b0Y1xqwusyD2Rft0irDXAiFEXH6oK4VVxCirewvIRk9h7t +xSO+IJbqg691ayS3DgDcqy34tQAAph5UGwyFYKHO7Z1wBVXQM5YnjEr9EvHPFECf +sMwZBJMxtKvUYMM02gl7pdETo5hwEZ/WWoutRp4f+oECgYEA+VA1v3VJwYwrvKUL +dkw3NBR99RoI/e5F8N4KtJK+m0rWB+aboAHv5VC5DSrdzvug+4pZHpx9YC/0ZufZ +iYjqF/es/J/uiCSfJTMS8n80UsF5oa2GQyIEuD733awBZQ1ruc4+v1WYqpSAcfOj +bU5f93jTo/UDLLmGuMjLdWPvZvECgYEAxPakhWDcPkNmm17qt9TZ/ClqUxYEScY/ +0QWHLwOXtW00r9GFkl9vfUw9/Gqw1NusocRXa8+ihtSHlxqUDO06Ls7flqB6uuSZ +CBYnUypE0jT1OMuD3K9FNGc1o9Y8QcSF+Nul2FzbwfQoi7Yv25EO9r/aQ/uF+Oj1 +VL27vPX6KKUCgYEA5KQ51o8zCAyL4+Kc4228RsfwSAMLcg2+GMsZmEboBTUZmn9U +A+ci4fQo8bl5WCSOm4Fif99WYAs6odFJQIfO4BIllDz8HeEwDoaLftdH3glPigXA +lvqwx2QAH0xqrwki3XEXPJO8gdvU/CxLmagB/MvTlI7TzYWL1xVW+h6fZJECgYEA +jcBsO0mweGcNq3guOMtJbr9ntBA+WdICD66I0f8l6f6EUpzaIrPoiyaZ3dXzGd5X +abzipcazU5IVW1xXfM4md5WPONqaOXNX54f6GVJsYVSXv55IckT563L0GcuPZk3H +lYiO3R5HUlkj7RjbbIwDVvZQYWjdzHvsRGagfKgSt7kCgYAnfGCYdehHwUGhd4oI +wOYApT7g3Zfday2oHhOXWhxe2r9Zg/lIIv32PROhT9nIhiyiWrxEMkf5imUPNcRj +C43cyirFPseuovSiS4V12MEbCBMpl8hmoxHzjmudGKKTdEWoTcx7H6jpQGgII6KU +8+svtuHZeHaziA8eW6dq/XjAcQ== +-----END PRIVATE KEY----- diff --git a/tests/gold_tests/tls/ssl/signer2.pem b/tests/gold_tests/tls/ssl/signer2.pem new file mode 100644 index 0000000..36901dd --- /dev/null +++ b/tests/gold_tests/tls/ssl/signer2.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIJAK1jQyJPinZQMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJJTDESMBAGA1UEBwwJQ2hhbXBhaWduMQ4wDAYDVQQK +DAVZYWhvbzEaMBgGA1UEAwwRc2lnbmVyMi55YWhvby5jb20xIjAgBgkqhkiG9w0B +CQEWE3NoaW5yaWNoQGFwYWNoZS5vcmcwHhcNMTgxMDE5MTczMzUyWhcNMjgxMDE2 +MTczMzUyWjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCSUwxEjAQBgNVBAcMCUNo +YW1wYWlnbjEOMAwGA1UECgwFWWFob28xGjAYBgNVBAMMEXNpZ25lcjIueWFob28u +Y29tMSIwIAYJKoZIhvcNAQkBFhNzaGlucmljaEBhcGFjaGUub3JnMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv9Gcc4M2UmOK8fhgRBmCgr1G2/LAyp+X +Vfxwt1m8Cc5cK2GbsIpgBr/iJ9KA6RbgrERjHvEoKX3pxWlEu635wimMw2CSS0as +5T58l5CQhBS3jOT6HGWDAPZlsOg+3ZkFG1Z25Aam7NvG6VXqtxKksC+p2Fv7wAG7 +vcq8qKjxHEut6haFRWdU2SVIAlYP/QttVNaAdyTIPbDhogthKLTC0afyFYKo2DBf +I2QT3aCVAEYK7GFDbqvVOGMfwSqMwSUj/2p9e+Ytfkyk27ProBmMnd9aKXakWO7J +TTL5SRbDvC7KZTv++WvLR6/qYVV7N3Y+u+IfK8eiq+Laa9OR/b0BVQIDAQABo1Aw +TjAdBgNVHQ4EFgQUQbZJDhAynmvgB4Ugim8qkfTqsSIwHwYDVR0jBBgwFoAUQbZJ +DhAynmvgB4Ugim8qkfTqsSIwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AQEAhMh2BrVhhV3gcWv9sxK2TP5I//4iqnR3zxpdKMQ3z/wj2JQyagjHdLyHhyBc +OyXZsO0WjPt5EPZxI+GpERVz6TiAW/IiiWuF2AtmNE7Kz9FouQCBHSXKGRNt6GWa +PE5xrOfc/UhTpeDjfHPF4vmIJ7aBiHGt6xv91dLhvrCQZcf0jESS3ZCRvlouIgWW +yKRPPIfxjJm0nwVzjmEhr1R4TVPSQRfQ8is/kYx2oRXjKuwftYlKlaj8wu0NoASs +ZOv43f+lPei9QPKqN4Hawym6NMfJlKNBaIEqzw/8F+Td74QbCEsnf8AaZD2ccIph +ea34ZvlNcntNiM9+4qqyS+V1IQ== +-----END CERTIFICATE----- diff --git a/tests/gold_tests/tls/tls_client_cert.test.py b/tests/gold_tests/tls/tls_client_cert.test.py new file mode 100644 index 0000000..4ebec4a --- /dev/null +++ b/tests/gold_tests/tls/tls_client_cert.test.py @@ -0,0 +1,254 @@ +''' +Test offering client cert to origin +''' +# 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. + +import os +import re + +Test.Summary = ''' +Test different combinations of TLS handshake hooks to ensure they are applied consistently. +''' + +Test.SkipUnless(Condition.HasProgram("grep", "grep needs to be installed on system for this test to work")) + +ts = Test.MakeATSProcess("ts", command="traffic_manager", select_ports=False) +cafile = "{0}/signer.pem".format(Test.RunDirectory) +cafile2 = "{0}/signer2.pem".format(Test.RunDirectory) +server = Test.MakeOriginServer("server", ssl=True, options = { "--clientCA": cafile, "--clientverify": "true"}, clientcert="{0}/signed-foo.pem".format(Test.RunDirectory), clientkey="{0}/signed-foo.key".format(Test.RunDirectory)) +server2 = Test.MakeOriginServer("server2", ssl=True, options = { "--clientCA": cafile2, "--clientverify": "true"}, clientcert="{0}/signed2-bar.pem".format(Test.RunDirectory), clientkey="{0}/signed-bar.key".format(Test.RunDirectory)) +server.Setup.Copy("ssl/signer.pem") +server.Setup.Copy("ssl/signer2.pem") +server.Setup.Copy("ssl/signed-foo.pem") +server.Setup.Copy("ssl/signed-foo.key") +server.Setup.Copy("ssl/signed2-foo.pem") +server.Setup.Copy("ssl/signed2-bar.pem") +server.Setup.Copy("ssl/signed-bar.key") +server2.Setup.Copy("ssl/signer.pem") +server2.Setup.Copy("ssl/signer2.pem") +server2.Setup.Copy("ssl/signed-foo.pem") +server2.Setup.Copy("ssl/signed-foo.key") +server2.Setup.Copy("ssl/signed2-foo.pem") +server2.Setup.Copy("ssl/signed2-bar.pem") +server2.Setup.Copy("ssl/signed-bar.key") + +request_header = {"headers": "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +server.addResponse("sessionlog.json", request_header, response_header) +request_header = {"headers": "GET / HTTP/1.1\r\nHost: bar.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +server.addResponse("sessionlog.json", request_header, response_header) + +ts.addSSLfile("ssl/server.pem") +ts.addSSLfile("ssl/server.key") +ts.addSSLfile("ssl/signed-foo.pem") +ts.addSSLfile("ssl/signed-foo.key") +ts.addSSLfile("ssl/signed2-foo.pem") +ts.addSSLfile("ssl/signed-bar.pem") +ts.addSSLfile("ssl/signed2-bar.pem") +ts.addSSLfile("ssl/signed-bar.key") + +ts.Variables.ssl_port = 4443 +ts.Disk.records_config.update({ + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'ssl|http', + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.http.server_ports': '{0}'.format(ts.Variables.port), + 'proxy.config.ssl.client.verify.server': 0, + 'proxy.config.ssl.server.cipher_suite': 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2', + 'proxy.config.ssl.client.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.client.cert.filename': 'signed-foo.pem', + 'proxy.config.ssl.client.private_key.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.client.private_key.filename': 'signed-foo.key', + 'proxy.config.url_remap.pristine_host_hdr' : 1, +}) + +ts.Disk.ssl_multicert_config.AddLine( + 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key' +) + +ts.Disk.remap_config.AddLine( + 'map /case1 https://127.0.0.1:{0}/'.format(server.Variables.Port) +) +ts.Disk.remap_config.AddLine( + 'map /case2 https://127.0.0.1:{0}/'.format(server2.Variables.Port) +) + +ts.Disk.ssl_server_name_yaml.AddLine( + '- fqdn: bar.com') +ts.Disk.ssl_server_name_yaml.AddLine( + ' client_cert: {0}/signed2-bar.pem'.format(ts.Variables.SSLDir)) +ts.Disk.ssl_server_name_yaml.AddLine( + ' client_key: {0}/signed-bar.key'.format(ts.Variables.SSLDir)) + + +# Should succeed +tr = Test.AddTestRun("Connect with first client cert to first server") +tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.port)) +tr.Processes.Default.StartBefore(server) +tr.Processes.Default.StartBefore(server2) +tr.StillRunningAfter = ts +tr.StillRunningAfter = server +tr.StillRunningAfter = server2 +tr.Processes.Default.Command = "curl -H host:example.com http://127.0.0.1:{0}/case1".format(ts.Variables.port) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.TimeOut = 5 +tr.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Check response") +tr.TimeOut = 5 + +#Should fail +trfail = Test.AddTestRun("Connect with first client cert to second server") +trfail.StillRunningAfter = ts +trfail.StillRunningAfter = server +trfail.StillRunningAfter = server2 +trfail.Processes.Default.Command = 'curl -H host:example.com http://127.0.0.1:{0}/case2'.format(ts.Variables.port) +trfail.Processes.Default.ReturnCode = 0 +trfail.Processes.Default.TimeOut = 5 +trfail.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Check response") +trfail.TimeOut = 5 + +# Should succeed +trbar = Test.AddTestRun("Connect with signed2 bar to second server") +trbar.StillRunningAfter = ts +trbar.StillRunningAfter = server +trbar.StillRunningAfter = server2 +trbar.Processes.Default.Command = "curl -H host:bar.com http://127.0.0.1:{0}/case2".format(ts.Variables.port) +trbar.Processes.Default.ReturnCode = 0 +trbar.Processes.Default.TimeOut = 5 +trbar.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Check response") +trbar.TimeOut = 5 + +#Should fail +trbarfail = Test.AddTestRun("Connect with signed2 bar cert to first server") +trbarfail.StillRunningAfter = ts +trbarfail.StillRunningAfter = server +trbarfail.StillRunningAfter = server2 +trbarfail.Processes.Default.Command = 'curl -H host:bar.com http://127.0.0.1:{0}/case1'.format(ts.Variables.port) +trbarfail.Processes.Default.ReturnCode = 0 +trbarfail.Processes.Default.TimeOut = 5 +trbarfail.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Check response") +trbarfail.TimeOut = 5 + +tr2 = Test.AddTestRun("Update client cert file and reload") +# Update the SNI config +snipath = ts.Disk.ssl_server_name_yaml.AbsPath +tr2.Disk.File(snipath, id = "ssl_server_name_yaml", typename="ats:config"), +tr2.Disk.ssl_server_name_yaml.AddLine( + '- fqdn: bar.com') +tr2.Disk.ssl_server_name_yaml.AddLine( + ' client_cert: {0}/signed-bar.pem'.format(ts.Variables.SSLDir)) +tr2.Disk.ssl_server_name_yaml.AddLine( + ' client_key: {0}/signed-bar.key'.format(ts.Variables.SSLDir)) +tr2.StillRunningAfter = ts +tr2.StillRunningAfter = server +tr2.StillRunningAfter = server2 +tr2.Processes.Default.Command = 'traffic_ctl config set proxy.config.ssl.client.cert.filename signed2-foo.pem; traffic_ctl config reload' +# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket +tr2.Processes.Default.Env = ts.Env +tr2.Processes.Default.ReturnCode = 0 +tr2.Processes.Default.TimeOut = 5 +tr2.TimeOut = 5 + +#Should succeed +tr3bar = Test.AddTestRun("Make request with other bar cert to first server") +# Wait for the reload to complete +tr3bar.DelayStart = 2 +tr3bar.StillRunningAfter = ts +tr3bar.StillRunningAfter = server +tr3bar.StillRunningAfter = server2 +tr3bar.Processes.Default.Command = 'curl -H host:example.com http://127.0.0.1:{0}/case1'.format(ts.Variables.port) +tr3bar.Processes.Default.ReturnCode = 0 +tr3bar.Processes.Default.TimeOut = 5 +tr3bar.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Check response") +tr3bar.TimeOut = 5 + +#Should fail +tr3barfail = Test.AddTestRun("Make request with other bar cert to second server") +tr3barfail.StillRunningAfter = ts +tr3barfail.StillRunningAfter = server +tr3barfail.StillRunningAfter = server2 +tr3barfail.Processes.Default.Command = 'curl -H host:example.com http://127.0.0.1:{0}/case2'.format(ts.Variables.port) +tr3barfail.Processes.Default.ReturnCode = 0 +tr3barfail.Processes.Default.TimeOut = 5 +tr3barfail.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Check response") +tr3barfail.TimeOut = 5 + +# Skipping the last two cases until we get the certificate settings reloadable from records.config + +#Should succeed +#tr3 = Test.AddTestRun("Make request with other cert to second server") +# Wait for the reload to complete +#tr3.StillRunningAfter = ts +#tr3.StillRunningAfter = server +#tr3.StillRunningAfter = server2 +#tr3.Processes.Default.Command = 'curl -H host:example.com http://127.0.0.1:{0}/case2'.format(ts.Variables.port) +#tr3.Processes.Default.ReturnCode = 0 +#tr3.Processes.Default.TimeOut = 5 +#tr3.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Check response") +#tr3.TimeOut = 5 +# +#Should fail +#tr3fail = Test.AddTestRun("Make request with other cert to first server") +#tr3fail.StillRunningAfter = ts +#tr3fail.StillRunningAfter = server +#tr3fail.StillRunningAfter = server2 +#tr3fail.Processes.Default.Command = 'curl -H host:example.com http://127.0.0.1:{0}/case1'.format(ts.Variables.port) +#tr3fail.Processes.Default.ReturnCode = 0 +#tr3fail.Processes.Default.TimeOut = 5 +#tr3fail.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Check response") +#tr3fail.TimeOut = 5 + +# Test the case of updating certificate contents without changing file name. +trupdate = Test.AddTestRun("Update client cert file in place") +trupdate.StillRunningAfter = ts +trupdate.StillRunningAfter = server +trupdate.StillRunningAfter = server2 +trupdate.Setup.CopyAs("ssl/signed2-bar.pem", ".", "signed-bar.pem") +trupdate.Setup.CopyAs("ssl/signed-foo.pem", ".", "{0}/signed2-foo.pem".format(ts.Variables.SSLDir)) +trupdate.Processes.Default.Command = 'traffic_ctl config reload' +# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket +trupdate.Processes.Default.Env = ts.Env +trupdate.Processes.Default.ReturnCode = 0 +trupdate.Processes.Default.TimeOut = 5 +trupdate.TimeOut = 5 + +#Should succeed +tr3bar = Test.AddTestRun("Make request with renamed bar cert to first server") +# Wait for the reload to complete +tr3bar.DelayStart = 2 +tr3bar.StillRunningAfter = ts +tr3bar.StillRunningAfter = server +tr3bar.StillRunningAfter = server2 +tr3bar.Processes.Default.Command = 'curl -H host:example.com http://127.0.0.1:{0}/case1'.format(ts.Variables.port) +tr3bar.Processes.Default.ReturnCode = 0 +tr3bar.Processes.Default.TimeOut = 5 +tr3bar.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Check response") +tr3bar.TimeOut = 5 + +#Should fail +tr3barfail = Test.AddTestRun("Make request with renamed bar cert to second server") +tr3barfail.StillRunningAfter = ts +tr3barfail.StillRunningAfter = server +tr3barfail.StillRunningAfter = server2 +tr3barfail.Processes.Default.Command = 'curl -H host:example.com http://127.0.0.1:{0}/case2'.format(ts.Variables.port) +tr3barfail.Processes.Default.ReturnCode = 0 +tr3barfail.Processes.Default.TimeOut = 5 +tr3barfail.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Check response") + +# Allow for error messages in diags +ts.Disk.diags_log.Content = Testers.ContainsExpression("ERROR", "Some connections should fail") diff --git a/tests/tools/microServer/uWServer.py b/tests/tools/microServer/uWServer.py index 5e57536..f2dfb38 100644 --- a/tests/tools/microServer/uWServer.py +++ b/tests/tools/microServer/uWServer.py @@ -136,6 +136,7 @@ class SSLServer(ThreadingMixIn, HTTPServer): pwd = os.path.dirname(os.path.realpath(__file__)) keys = os.path.join(pwd, options.key) certs = os.path.join(pwd, options.cert) + clientCA = os.path.join(pwd, options.clientCA) self.options = options self.hook_set = HookSet() @@ -145,9 +146,11 @@ class SSLServer(ThreadingMixIn, HTTPServer): if options.load: self.hook_set.load(options.load) + print ("clientverify={0}".format(options.clientverify)) + if options.clientverify: self.socket = ssl.wrap_socket(socket.socket(self.address_family, self.socket_type), - keyfile=keys, certfile=certs, server_side=True, cert_reqs=ssl.CERT_REQUIRED, ca_certs='/etc/ssl/certs/ca-certificates.crt') + keyfile=keys, certfile=certs, server_side=True, cert_reqs=ssl.CERT_REQUIRED, ca_certs=clientCA) else: self.socket = ssl.wrap_socket(socket.socket(self.address_family, self.socket_type), keyfile=keys, certfile=certs, server_side=True) @@ -170,6 +173,7 @@ class MyHandler(BaseHTTPRequestHandler): global lookup_key_ kpath = "" path = "" + print("Request={0} lookup_key={1}".format(requestline, lookup_key_)) url_part = requestline.split(" ") if url_part: if url_part[1].startswith("http"): @@ -190,7 +194,6 @@ class MyHandler(BaseHTTPRequestHandler): argsList.append(stringk) KeyList = [] for argsL in argsList: - print("args", argsL, len(argsL)) if len(argsL) > 0: val = self.headers.get(argsL) if val: @@ -292,6 +295,7 @@ class MyHandler(BaseHTTPRequestHandler): self.command = None # set in case of error on the first line self.request_version = version = self.default_request_version self.close_connection = True + print("Raw request {0}".format(self.raw_requestline)) requestline = str(self.raw_requestline, 'UTF-8') requestline = requestline.rstrip('\r\n') self.requestline = requestline @@ -600,7 +604,7 @@ def _bool(arg): def _argparse_bool(arg): try: - _bool(arg) + return _bool(arg) except ValueError as ve: raise argparse.ArgumentTypeError(ve) @@ -654,6 +658,10 @@ def main(): type=str, default="ssl/server.crt", help="certificate") + parser.add_argument("--clientCA", + type=str, + default="", + help="CA for client certificates") parser.add_argument("--clientverify", "-cverify", type=_argparse_bool, default=False,