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 34d9c4e Add valid_tls_protocols_in to allow for per-domain protocols.
34d9c4e is described below
commit 34d9c4eaac536a2f27debd15326c58da91d711af
Author: Susan Hinrichs <[email protected]>
AuthorDate: Wed Jan 16 20:50:27 2019 +0000
Add valid_tls_protocols_in to allow for per-domain protocols.
---
build/crypto.m4 | 41 ++++++++
configure.ac | 3 +
doc/admin-guide/files/ssl_server_name.yaml.en.rst | 11 ++-
include/tscore/ink_config.h.in | 1 +
iocore/net/P_SNIActionPerformer.h | 26 +++++
iocore/net/P_SSLNetVConnection.h | 3 +
iocore/net/P_SSLUtils.h | 1 +
iocore/net/SSLSNIConfig.cc | 3 +
iocore/net/SSLUtils.cc | 64 ++++++++++++-
iocore/net/YamlSNIConfig.cc | 50 +++++++++-
iocore/net/YamlSNIConfig.h | 6 ++
tests/gold_tests/tls/tls_client_versions.test.py | 112 ++++++++++++++++++++++
12 files changed, 315 insertions(+), 6 deletions(-)
diff --git a/build/crypto.m4 b/build/crypto.m4
index e3ac31b..248be9e 100644
--- a/build/crypto.m4
+++ b/build/crypto.m4
@@ -144,6 +144,47 @@ AC_DEFUN([TS_CHECK_CRYPTO_CERT_CB], [
AC_SUBST(use_cert_cb)
])
+AC_DEFUN([TS_CHECK_CRYPTO_HELLO_CB], [
+ _hello_saved_LIBS=$LIBS
+ enable_hello_cb=yes
+
+ TS_ADDTO(LIBS, [$OPENSSL_LIBS])
+ AC_CHECK_HEADERS(openssl/ssl.h openssl/ts.h)
+ AC_CHECK_HEADERS(openssl/tls1.h, [], [],
+[ #if HAVE_OPENSSL_SSL_H
+#include <openssl/ssl.h>
+#include <openssl/tls1.h>
+#endif ])
+
+ AC_MSG_CHECKING([for SSL_CTX_set_client_hello_cb])
+ AC_LINK_IFELSE(
+ [
+ AC_LANG_PROGRAM([[
+#if HAVE_OPENSSL_SSL_H
+#include <openssl/ssl.h>
+#endif
+#if HAVE_OPENSSL_TLS1_H
+#include <openssl/tls1.h>
+#endif
+ ]],
+ [[SSL_CTX_set_client_hello_cb(NULL, NULL, NULL);]])
+ ],
+ [
+ AC_MSG_RESULT([yes])
+ ],
+ [
+ AC_MSG_RESULT([no])
+ enable_hello_cb=no
+ ])
+
+ LIBS=$_hello_saved_LIBS
+
+ AC_MSG_CHECKING(whether to enable TLS client hello callback support)
+ AC_MSG_RESULT([$enable_hello_cb])
+ TS_ARG_ENABLE_VAR([use], [hello-cb])
+ AC_SUBST(use_hello_cb)
+])
+
AC_DEFUN([TS_CHECK_CRYPTO_SET_RBIO], [
_rbio_saved_LIBS=$LIBS
enable_set_rbio=yes
diff --git a/configure.ac b/configure.ac
index 9600c4f..bec8ef5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1195,6 +1195,9 @@ TS_CHECK_CRYPTO_EC_KEYS
# Check for the presense of the certificate callback in the ssl library
TS_CHECK_CRYPTO_CERT_CB
+# Check for the client hello callback
+TS_CHECK_CRYPTO_HELLO_CB
+
#
# Check for SSL_set0_rbio call
TS_CHECK_CRYPTO_SET_RBIO
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 e95c3dd..ac8068b 100644
--- a/doc/admin-guide/files/ssl_server_name.yaml.en.rst
+++ b/doc/admin-guide/files/ssl_server_name.yaml.en.rst
@@ -71,9 +71,18 @@ verify_client One of the values :code:`NONE`,
:code:`MODERATE`, or :
fail the TLS handshake if new certificate is
presented. If ``STRICT`` is specified
the client must resent a certificate during the TLS
handshake.
-
By default this is
:ts:cv:`proxy.config.ssl.client.certification_level`.
+valid_tls_versions_in This specifies the list of TLS protocols that will
be offered to user agents during
+ the TLS negotiaton. This replaces the global
settings in :ts:cv:`proxy.config.ssl.TSLv1`,
+ :ts:cv:`proxy.config.ssl.TLSv1_1`,
:ts:cv:`proxy.config.ssl.TLSv1_2`,
+ and :ts:cv:`proxy.config.ssl.TLSv1_3`. The potential
values are TLSv1, TLSv1_1, TLSv1_2, and
+ TLSv1_3. You must list all protocols that |TS|
should offer to the client when using
+ this key. This key is only valid for openssl 1.1.0
and later. Older versions of openssl do not
+ provide a hook early enough to update the SSL
object. It is a syntax error for |TS| built
+ against earlier versions.
+
+
client_cert The file containing the client certificate to use
for the outbound connection.
If this is relative, it is relative to the path in
diff --git a/include/tscore/ink_config.h.in b/include/tscore/ink_config.h.in
index d6c1a54..770bd76 100644
--- a/include/tscore/ink_config.h.in
+++ b/include/tscore/ink_config.h.in
@@ -72,6 +72,7 @@
#define TS_USE_TLS_ALPN @use_tls_alpn@
#define TS_USE_TLS_ASYNC @use_tls_async@
#define TS_USE_CERT_CB @use_cert_cb@
+#define TS_USE_HELLO_CB @use_hello_cb@
#define TS_USE_SET_RBIO @use_set_rbio@
#define TS_USE_GET_DH_2048_256 @use_dh_get_2048_256@
#define TS_USE_TLS_ECKEY @use_tls_eckey@
diff --git a/iocore/net/P_SNIActionPerformer.h
b/iocore/net/P_SNIActionPerformer.h
index d515fdc..f940490 100644
--- a/iocore/net/P_SNIActionPerformer.h
+++ b/iocore/net/P_SNIActionPerformer.h
@@ -115,6 +115,32 @@ public:
}
};
+class TLSValidProtocols : public ActionItem
+{
+ bool unset;
+ unsigned long protocol_mask;
+
+public:
+#ifdef SSL_OP_NO_TLSv1_3
+ static const unsigned long max_mask = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 |
SSL_OP_NO_TLSv1_2 | SSL_OP_NO_TLSv1_3;
+#else
+ static const unsigned long max_mask = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 |
SSL_OP_NO_TLSv1_2;
+#endif
+ TLSValidProtocols() : unset(true), protocol_mask(max_mask) {}
+ TLSValidProtocols(unsigned long protocols) : unset(false),
protocol_mask(protocols) {}
+ int
+ SNIAction(Continuation *cont) const override
+ {
+ if (!unset) {
+ auto ssl_vc = dynamic_cast<SSLNetVConnection *>(cont);
+ Debug("ssl_sni", "TLSValidProtocol param 0%x", static_cast<unsigned
int>(this->protocol_mask));
+ ssl_vc->protocol_mask_set = true;
+ ssl_vc->protocol_mask = protocol_mask;
+ }
+ return SSL_TLSEXT_ERR_OK;
+ }
+};
+
class SNI_IpAllow : public ActionItem
{
IpMap ip_map;
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index 19976be..4de0038 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -370,6 +370,9 @@ public:
SSLNetVConnection(const SSLNetVConnection &) = delete;
SSLNetVConnection &operator=(const SSLNetVConnection &) = delete;
+ bool protocol_mask_set = false;
+ unsigned long protocol_mask;
+
private:
std::string_view map_tls_protocol_to_tag(const char *proto_string) const;
bool update_rbio(bool move_to_socket);
diff --git a/iocore/net/P_SSLUtils.h b/iocore/net/P_SSLUtils.h
index 3d2b01a..e3ec9f6 100644
--- a/iocore/net/P_SSLUtils.h
+++ b/iocore/net/P_SSLUtils.h
@@ -177,6 +177,7 @@ void SSLNetVCDetach(SSL *ssl);
SSLNetVConnection *SSLNetVCAccess(const SSL *ssl);
void setClientCertLevel(SSL *ssl, uint8_t certLevel);
+void setTLSValidProtocols(SSL *ssl, unsigned long proto_mask, unsigned long
max_mask);
namespace ssl
{
diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc
index 470a544..06f15cd 100644
--- a/iocore/net/SSLSNIConfig.cc
+++ b/iocore/net/SSLSNIConfig.cc
@@ -71,6 +71,9 @@ SNIConfigParams::loadSNIConfig()
if (item.verify_client_level != 255) {
ai->actions.push_back(std::make_unique<VerifyClient>(item.verify_client_level));
}
+ if (!item.protocol_unset) {
+
ai->actions.push_back(std::make_unique<TLSValidProtocols>(item.protocol_mask));
+ }
if (item.tunnel_destination.length() > 0) {
ai->actions.push_back(std::make_unique<TunnelDestination>(item.tunnel_destination,
item.tunnel_decrypt));
}
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index 7c9fbaf..e7a6444 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -418,6 +418,57 @@ PerformAction(Continuation *cont, const char *servername)
return SSL_TLSEXT_ERR_OK;
}
+#if TS_USE_HELLO_CB
+// Pausable callback
+static int
+ssl_client_hello_callback(SSL *s, int *al, void *arg)
+{
+ const char *servername = nullptr;
+ const unsigned char *p;
+ size_t remaining, len;
+ if (SSL_client_hello_get0_ext(s, TLSEXT_TYPE_server_name, &p, &remaining) ||
remaining <= 2) {
+ // Parse to get to the name, originally from test/handshake_helper.c in
openssl tree
+ /* Extract the length of the supplied list of names. */
+ len = *(p++) << 8;
+ len += *(p++);
+ if (len + 2 == remaining) {
+ remaining = len;
+ /*
+ * The list in practice only has a single element, so we only consider
+ * the first one.
+ */
+ if (remaining != 0 && *p++ == TLSEXT_NAMETYPE_host_name) {
+ remaining--;
+ /* Now we can finally pull out the byte array with the actual
hostname. */
+ if (remaining > 2) {
+ len = *(p++) << 8;
+ len += *(p++);
+ if (len + 2 <= remaining) {
+ remaining = len;
+ servername = reinterpret_cast<const char *>(p);
+ }
+ }
+ }
+ }
+ }
+
+ SSLNetVConnection *netvc = SSLNetVCAccess(s);
+
+ netvc->serverName = servername ? servername : "";
+ int ret = PerformAction(netvc, netvc->serverName);
+ if (ret != SSL_TLSEXT_ERR_OK) {
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ if (netvc->has_tunnel_destination() && !netvc->decrypt_tunnel()) {
+ netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+ }
+ if (netvc->protocol_mask_set) {
+ setTLSValidProtocols(s, netvc->protocol_mask, TLSValidProtocols::max_mask);
+ }
+ return 1;
+}
+#endif
+
// Use the certificate callback for openssl 1.0.2 and greater
// otherwise use the SNI callback
#if TS_USE_CERT_CB
@@ -471,6 +522,8 @@ ssl_servername_only_callback(SSL *ssl, int * /* ad */, void
* /*arg*/)
if (nullptr == netvc->serverName) {
netvc->serverName = "";
}
+
+ // Rerun the actions in case a plugin changed the server name
int ret = PerformAction(netvc, netvc->serverName);
if (ret != SSL_TLSEXT_ERR_OK) {
return SSL_TLSEXT_ERR_ALERT_FATAL;
@@ -478,7 +531,6 @@ ssl_servername_only_callback(SSL *ssl, int * /* ad */, void
* /*arg*/)
if (netvc->has_tunnel_destination() && !netvc->decrypt_tunnel()) {
netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
}
-
return SSL_TLSEXT_ERR_OK;
}
@@ -1592,6 +1644,16 @@ ssl_set_handshake_callbacks(SSL_CTX *ctx)
#else
SSL_CTX_set_tlsext_servername_callback(ctx,
ssl_servername_and_cert_callback);
#endif
+#if TS_USE_HELLO_CB
+ SSL_CTX_set_client_hello_cb(ctx, ssl_client_hello_callback, nullptr);
+#endif
+}
+
+void
+setTLSValidProtocols(SSL *ssl, unsigned long proto_mask, unsigned long
max_mask)
+{
+ SSL_set_options(ssl, proto_mask);
+ SSL_clear_options(ssl, max_mask & ~proto_mask);
}
void
diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc
index 8a542cd..84e118e 100644
--- a/iocore/net/YamlSNIConfig.cc
+++ b/iocore/net/YamlSNIConfig.cc
@@ -26,10 +26,12 @@
#include <string_view>
#include <yaml-cpp/yaml.h>
+#include <openssl/ssl.h>
#include "tscore/Diags.h"
#include "tscore/EnumDescriptor.h"
#include "tsconfig/Errata.h"
+#include "P_SNIActionPerformer.h"
ts::Errata
YamlSNIConfig::loader(const char *cfgFilename)
@@ -55,9 +57,37 @@ YamlSNIConfig::loader(const char *cfgFilename)
return ts::Errata();
}
-TsEnumDescriptor LEVEL_DESCRIPTOR = {{{"NONE", 0}, {"MODERATE", 1},
{"STRICT", 2}}};
-TsEnumDescriptor POLICY_DESCRIPTOR = {{{"DISABLED", 0}, {"PERMISSIVE", 1},
{"ENFORCED", 2}}};
-TsEnumDescriptor PROPERTIES_DESCRIPTOR = {{{"NONE", 0}, {"SIGNATURE", 0x1},
{"NAME", 0x2}, {"ALL", 0x3}}};
+void
+YamlSNIConfig::Item::EnableProtocol(YamlSNIConfig::TLSProtocol proto)
+{
+ if (proto <= YamlSNIConfig::TLSProtocol::TLS_MAX) {
+ if (protocol_unset) {
+ protocol_mask = TLSValidProtocols::max_mask;
+ protocol_unset = false;
+ }
+ switch (proto) {
+ case YamlSNIConfig::TLSProtocol::TLSv1:
+ protocol_mask &= ~SSL_OP_NO_TLSv1;
+ break;
+ case YamlSNIConfig::TLSProtocol::TLSv1_1:
+ protocol_mask &= ~SSL_OP_NO_TLSv1_1;
+ break;
+ case YamlSNIConfig::TLSProtocol::TLSv1_2:
+ protocol_mask &= ~SSL_OP_NO_TLSv1_2;
+ break;
+ case YamlSNIConfig::TLSProtocol::TLSv1_3:
+#ifdef SSL_OP_NO_TLSv1_3
+ protocol_mask &= ~SSL_OP_NO_TLSv1_3;
+#endif
+ break;
+ }
+ }
+}
+
+TsEnumDescriptor LEVEL_DESCRIPTOR = {{{"NONE", 0}, {"MODERATE", 1},
{"STRICT", 2}}};
+TsEnumDescriptor POLICY_DESCRIPTOR = {{{"DISABLED", 0}, {"PERMISSIVE",
1}, {"ENFORCED", 2}}};
+TsEnumDescriptor PROPERTIES_DESCRIPTOR = {{{"NONE", 0}, {"SIGNATURE", 0x1},
{"NAME", 0x2}, {"ALL", 0x3}}};
+TsEnumDescriptor TLS_PROTOCOLS_DESCRIPTOR = {{{"TLSv1", 0}, {"TLSv1_1", 1},
{"TLSv1_2", 2}, {"TLSv1_3", 3}}};
std::set<std::string> valid_sni_config_keys = {TS_fqdn,
TS_disable_h2,
@@ -69,7 +99,12 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn,
TS_verify_server_properties,
TS_client_cert,
TS_client_key,
- TS_ip_allow};
+ TS_ip_allow
+#if TS_USE_HELLO_CB
+ ,
+ TS_valid_tls_versions_in
+#endif
+};
namespace YAML
{
@@ -158,6 +193,13 @@ template <> struct convert<YamlSNIConfig::Item> {
if (node[TS_ip_allow]) {
item.ip_allow = node[TS_ip_allow].as<std::string>();
}
+ if (node[TS_valid_tls_versions_in]) {
+ for (unsigned int i = 0; i < node[TS_valid_tls_versions_in].size(); i++)
{
+ auto value = node[TS_valid_tls_versions_in][i].as<std::string>();
+ int protocol = TLS_PROTOCOLS_DESCRIPTOR.get(value);
+ item.EnableProtocol(static_cast<YamlSNIConfig::TLSProtocol>(protocol));
+ }
+ }
return true;
}
};
diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h
index 0acf4f2..babd016 100644
--- a/iocore/net/YamlSNIConfig.h
+++ b/iocore/net/YamlSNIConfig.h
@@ -38,6 +38,7 @@ TSDECL(verify_origin_server);
TSDECL(client_cert);
TSDECL(client_key);
TSDECL(ip_allow);
+TSDECL(valid_tls_versions_in);
#undef TSDECL
const int start = 0;
@@ -54,6 +55,7 @@ struct YamlSNIConfig {
enum class Level { NONE = 0, MODERATE, STRICT };
enum class Policy : uint8_t { DISABLED = 0, PERMISSIVE, ENFORCED, UNSET };
enum class Property : uint8_t { NONE = 0, SIGNATURE_MASK = 0x1, NAME_MASK =
0x2, ALL_MASK = 0x3, UNSET };
+ enum class TLSProtocol : uint8_t { TLSv1 = 0, TLSv1_1, TLSv1_2, TLSv1_3,
TLS_MAX = TLSv1_3 };
YamlSNIConfig() {}
@@ -68,6 +70,10 @@ struct YamlSNIConfig {
std::string client_cert;
std::string client_key;
std::string ip_allow;
+ bool protocol_unset = true;
+ unsigned long protocol_mask;
+
+ void EnableProtocol(YamlSNIConfig::TLSProtocol proto);
};
ts::Errata loader(const char *cfgFilename);
diff --git a/tests/gold_tests/tls/tls_client_versions.test.py
b/tests/gold_tests/tls/tls_client_versions.test.py
new file mode 100644
index 0000000..1855799
--- /dev/null
+++ b/tests/gold_tests/tls/tls_client_versions.test.py
@@ -0,0 +1,112 @@
+'''
+'''
+# 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
+Test.Summary = '''
+Test TLS protocol offering based on SNI
+'''
+
+# By default only offer TLSv1_2
+# for special doman foo.com only offer TLSv1 and TLSv1_1
+
+# need Curl
+Test.SkipUnless(
+ Condition.HasProgram("curl", "Curl need to be installed on system for this
test to work"),
+ Condition.HasOpenSSLVersion("1.1.1")
+)
+
+# Define default ATS
+ts = Test.MakeATSProcess("ts", select_ports=False)
+server = Test.MakeOriginServer("server", ssl=True)
+
+request_foo_header = {"headers": "GET / HTTP/1.1\r\n\r\n", "timestamp":
"1469733493.993", "body": ""}
+response_foo_header = {"headers": "HTTP/1.1 200 OK\r\nConnection:
close\r\n\r\n", "timestamp": "1469733493.993", "body": "foo ok"}
+server.addResponse("sessionlog.json", request_foo_header, response_foo_header)
+
+# add ssl materials like key, certificates for the server
+ts.addSSLfile("ssl/server.pem")
+ts.addSSLfile("ssl/server.key")
+
+ts.Variables.ssl_port = 4443
+
+# Need no remap rules. Everything should be proccessed by ssl_server_name
+
+# Make sure the TS server certs are different from the origin certs
+ts.Disk.ssl_multicert_config.AddLine(
+ 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+)
+
+ts.Disk.records_config.update({
+ 'proxy.config.diags.debug.enabled': 0,
+ 'proxy.config.diags.debug.tags': 'http|ssl',
+ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.ssl.server.private_key.path':
'{0}'.format(ts.Variables.SSLDir),
+ # enable ssl port
+ 'proxy.config.http.server_ports': '{0}
{1}:proto=http2;http:ssl'.format(ts.Variables.port, ts.Variables.ssl_port),
+ '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.CA.cert.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.url_remap.pristine_host_hdr': 1,
+ 'proxy.config.ssl.TLSv1': 0,
+ 'proxy.config.ssl.TLSv1_1': 0,
+ 'proxy.config.ssl.TLSv1_2': 1
+})
+
+# foo.com should only offer the older TLS protocols
+# bar.com should terminate.
+# empty SNI should tunnel to server_bar
+ts.Disk.ssl_server_name_yaml.AddLines([
+ '- fqdn: foo.com',
+ ' valid_tls_versions_in: [ TLSv1, TLSv1_1 ]'
+])
+
+# Target foo.com for TLSv1_2. Should fail
+tr = Test.AddTestRun("foo.com TLSv1_2")
+tr.Processes.Default.StartBefore(server)
+tr.Processes.Default.StartBefore(Test.Processes.ts,
ready=When.PortOpen(ts.Variables.ssl_port))
+tr.Processes.Default.Command = "curl -v --tls-max 1.2 --tlsv1.2 --resolve
'foo.com:{0}:127.0.0.1' -k https://foo.com:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 35
+tr.StillRunningAfter = ts
+tr.Processes.Default.TimeOut = 5
+tr.TimeOut = 5
+tr.Processes.Default.Streams.All +=
Testers.ContainsExpression("ssl_choose_client_version:unsupported protocol",
"Should not allow TLSv1_2")
+
+# Target foo.com for TLSv1. Should succeed
+tr = Test.AddTestRun("foo.com TLSv1")
+tr.Processes.Default.Command = "curl -v --tls-max 1.0 --tlsv1 --resolve
'foo.com:{0}:127.0.0.1' -k https://foo.com:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 0
+tr.StillRunningAfter = ts
+tr.Processes.Default.TimeOut = 5
+tr.TimeOut = 5
+
+# Target bar.com for TLSv1. Should fail
+tr = Test.AddTestRun("bar.com TLSv1")
+tr.Processes.Default.Command = "curl -v --tls-max 1.0 --tlsv1 --resolve
'bar.com:{0}:127.0.0.1' -k https://bar.com:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 35
+tr.StillRunningAfter = ts
+tr.Processes.Default.TimeOut = 5
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("alert protocol
version", "Should not allow TLSv1_0")
+tr.TimeOut = 5
+
+# Target bar.com for TLSv1_2. Should succeed
+tr = Test.AddTestRun("bar.com TLSv1_2")
+tr.Processes.Default.Command = "curl -v --tls-max 1.2 --tlsv1.2 --resolve
'bar.com:{0}:127.0.0.1' -k https://bar.com:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 0
+tr.StillRunningAfter = ts
+tr.Processes.Default.TimeOut = 5
+tr.TimeOut = 5
+