aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHaibo Huang <hhb@google.com>2020-01-07 14:06:17 -0800
committerandroid-build-merger <android-build-merger@google.com>2020-01-07 14:06:17 -0800
commit075127a8369bc1c63816bf19c9cfadbd0e8ea894 (patch)
tree16c7c1622f6e5897972a8abb75e049de64b09c0d
parent715174bb0a0268957c65576005b56ed59b1a630c (diff)
parentcd49a07c9cd64170e3758a3240902c5bb5bfac5e (diff)
downloadplatform_external_python_httplib2-075127a8369bc1c63816bf19c9cfadbd0e8ea894.tar.gz
platform_external_python_httplib2-075127a8369bc1c63816bf19c9cfadbd0e8ea894.tar.bz2
platform_external_python_httplib2-075127a8369bc1c63816bf19c9cfadbd0e8ea894.zip
Upgrade python/httplib2 to v0.15.0
am: cd49a07c9c Change-Id: Ia6a048a662f9820c04a8a1af9db9e35c154de534
-rw-r--r--CHANGELOG11
-rw-r--r--METADATA6
-rw-r--r--python2/httplib2/__init__.py80
-rw-r--r--python3/httplib2/__init__.py35
-rwxr-xr-xscript/generate-tls61
-rwxr-xr-xsetup.py2
-rw-r--r--tests/__init__.py79
-rw-r--r--tests/test_external.py129
-rw-r--r--tests/test_http.py49
-rw-r--r--tests/test_https.py203
-rw-r--r--tests/test_other.py21
-rw-r--r--tests/tls/ca.key27
-rw-r--r--tests/tls/ca.pem20
-rw-r--r--tests/tls/ca.srl1
-rw-r--r--tests/tls/ca_unused.pem20
-rw-r--r--tests/tls/client.crt20
-rw-r--r--tests/tls/client.key27
-rw-r--r--tests/tls/client.pem47
-rw-r--r--tests/tls/client_chain.pem67
-rw-r--r--tests/tls/client_encrypted.crt20
-rw-r--r--tests/tls/client_encrypted.key30
-rw-r--r--tests/tls/client_encrypted.pem50
-rw-r--r--tests/tls/server.crt20
-rw-r--r--tests/tls/server.key27
-rw-r--r--tests/tls/server.pem47
-rw-r--r--tests/tls/server_chain.pem67
26 files changed, 955 insertions, 211 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 10e2e4b..07fb949 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,14 @@
+0.15.0
+
+ python2: regression in connect() error handling
+ https://github.com/httplib2/httplib2/pull/150
+
+ add support for password protected certificate files
+ https://github.com/httplib2/httplib2/pull/143
+
+ feature: Http.close() to clean persistent connections and sensitive data
+ https://github.com/httplib2/httplib2/pull/149
+
0.14.0
Python3: PROXY_TYPE_SOCKS5 with str user/pass raised TypeError
diff --git a/METADATA b/METADATA
index 72b21cd..8b3c1c6 100644
--- a/METADATA
+++ b/METADATA
@@ -9,10 +9,10 @@ third_party {
type: GIT
value: "https://github.com/httplib2/httplib2.git"
}
- version: "v0.14.0"
+ version: "v0.15.0"
last_upgrade_date {
year: 2019
- month: 9
- day: 26
+ month: 12
+ day: 23
}
}
diff --git a/python2/httplib2/__init__.py b/python2/httplib2/__init__.py
index 98228e3..c8302eb 100644
--- a/python2/httplib2/__init__.py
+++ b/python2/httplib2/__init__.py
@@ -19,7 +19,7 @@ __contributors__ = [
"Alex Yu",
]
__license__ = "MIT"
-__version__ = '0.14.0'
+__version__ = '0.15.0'
import base64
import calendar
@@ -76,7 +76,7 @@ if ssl is not None:
def _ssl_wrap_socket(
- sock, key_file, cert_file, disable_validation, ca_certs, ssl_version, hostname
+ sock, key_file, cert_file, disable_validation, ca_certs, ssl_version, hostname, key_password
):
if disable_validation:
cert_reqs = ssl.CERT_NONE
@@ -90,11 +90,16 @@ def _ssl_wrap_socket(
context.verify_mode = cert_reqs
context.check_hostname = cert_reqs != ssl.CERT_NONE
if cert_file:
- context.load_cert_chain(cert_file, key_file)
+ if key_password:
+ context.load_cert_chain(cert_file, key_file, key_password)
+ else:
+ context.load_cert_chain(cert_file, key_file)
if ca_certs:
context.load_verify_locations(ca_certs)
return context.wrap_socket(sock, server_hostname=hostname)
else:
+ if key_password:
+ raise NotSupportedOnThisPlatform("Certificate with password is not supported.")
return ssl.wrap_socket(
sock,
keyfile=key_file,
@@ -106,7 +111,7 @@ def _ssl_wrap_socket(
def _ssl_wrap_socket_unsupported(
- sock, key_file, cert_file, disable_validation, ca_certs, ssl_version, hostname
+ sock, key_file, cert_file, disable_validation, ca_certs, ssl_version, hostname, key_password
):
if not disable_validation:
raise CertificateValidationUnsupported(
@@ -114,6 +119,8 @@ def _ssl_wrap_socket_unsupported(
"the ssl module installed. To avoid this error, install "
"the ssl module, or explicity disable validation."
)
+ if key_password:
+ raise NotSupportedOnThisPlatform("Certificate with password is not supported.")
ssl_sock = socket.ssl(sock, key_file, cert_file)
return httplib.FakeSocket(sock, ssl_sock)
@@ -978,8 +985,13 @@ class Credentials(object):
class KeyCerts(Credentials):
"""Identical to Credentials except that
name/password are mapped to key/cert."""
+ def add(self, key, cert, domain, password):
+ self.credentials.append((domain.lower(), key, cert, password))
- pass
+ def iter(self, domain):
+ for (cdomain, key, cert, password) in self.credentials:
+ if cdomain == "" or domain == cdomain:
+ yield (key, cert, password)
class AllHosts(object):
@@ -1150,7 +1162,6 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection):
raise ProxiesUnavailableError(
"Proxy support missing but proxy use was requested!"
)
- msg = "getaddrinfo returns an empty list"
if self.proxy_info and self.proxy_info.isgood():
use_proxy = True
proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers = (
@@ -1164,7 +1175,9 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection):
host = self.host
port = self.port
-
+
+ socket_err = None
+
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
@@ -1206,7 +1219,8 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection):
self.sock.connect((self.host, self.port) + sa[2:])
else:
self.sock.connect(sa)
- except socket.error as msg:
+ except socket.error as e:
+ socket_err = e
if self.debuglevel > 0:
print("connect fail: (%s, %s)" % (self.host, self.port))
if use_proxy:
@@ -1229,7 +1243,7 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection):
continue
break
if not self.sock:
- raise socket.error(msg)
+ raise socket_err or socket.error("getaddrinfo returns an empty list")
class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
@@ -1253,10 +1267,19 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
ca_certs=None,
disable_ssl_certificate_validation=False,
ssl_version=None,
+ key_password=None,
):
- httplib.HTTPSConnection.__init__(
- self, host, port=port, key_file=key_file, cert_file=cert_file, strict=strict
- )
+ if key_password:
+ httplib.HTTPSConnection.__init__(self, host, port=port, strict=strict)
+ self._context.load_cert_chain(cert_file, key_file, key_password)
+ self.key_file = key_file
+ self.cert_file = cert_file
+ self.key_password = key_password
+ else:
+ httplib.HTTPSConnection.__init__(
+ self, host, port=port, key_file=key_file, cert_file=cert_file, strict=strict
+ )
+ self.key_password = None
self.timeout = timeout
self.proxy_info = proxy_info
if ca_certs is None:
@@ -1317,7 +1340,6 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
def connect(self):
"Connect to a host on a given (SSL) port."
- msg = "getaddrinfo returns an empty list"
if self.proxy_info and self.proxy_info.isgood():
use_proxy = True
proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers = (
@@ -1331,7 +1353,9 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
host = self.host
port = self.port
-
+
+ socket_err = None
+
address_info = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)
for family, socktype, proto, canonname, sockaddr in address_info:
try:
@@ -1366,6 +1390,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
self.ca_certs,
self.ssl_version,
self.host,
+ self.key_password,
)
if self.debuglevel > 0:
print("connect: (%s, %s)" % (self.host, self.port))
@@ -1413,7 +1438,8 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
raise
except (socket.timeout, socket.gaierror):
raise
- except socket.error as msg:
+ except socket.error as e:
+ socket_err = e
if self.debuglevel > 0:
print("connect fail: (%s, %s)" % (self.host, self.port))
if use_proxy:
@@ -1436,7 +1462,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
continue
break
if not self.sock:
- raise socket.error(msg)
+ raise socket_err or socket.error("getaddrinfo returns an empty list")
SCHEME_TO_CONNECTION = {
@@ -1515,7 +1541,10 @@ class AppEngineHttpsConnection(httplib.HTTPSConnection):
ca_certs=None,
disable_ssl_certificate_validation=False,
ssl_version=None,
+ key_password=None,
):
+ if key_password:
+ raise NotSupportedOnThisPlatform("Certificate with password is not supported.")
httplib.HTTPSConnection.__init__(
self,
host,
@@ -1649,6 +1678,16 @@ class Http(object):
# Keep Authorization: headers on a redirect.
self.forward_authorization_headers = False
+ def close(self):
+ """Close persistent connections, clear sensitive data.
+ Not thread-safe, requires external synchronization against concurrent requests.
+ """
+ existing, self.connections = self.connections, {}
+ for _, c in existing.iteritems():
+ c.close()
+ self.certificates.clear()
+ self.clear_credentials()
+
def __getstate__(self):
state_dict = copy.copy(self.__dict__)
# In case request is augmented by some foreign object such as
@@ -1680,10 +1719,10 @@ class Http(object):
any time a request requires authentication."""
self.credentials.add(name, password, domain)
- def add_certificate(self, key, cert, domain):
+ def add_certificate(self, key, cert, domain, password=None):
"""Add a key and cert that will be used
any time a request requires authentication."""
- self.certificates.add(key, cert, domain)
+ self.certificates.add(key, cert, domain, password)
def clear_credentials(self):
"""Remove all the names and passwords
@@ -1925,7 +1964,7 @@ class Http(object):
a string that contains the response entity body.
"""
conn_key = ''
-
+
try:
if headers is None:
headers = {}
@@ -1958,6 +1997,7 @@ class Http(object):
ca_certs=self.ca_certs,
disable_ssl_certificate_validation=self.disable_ssl_certificate_validation,
ssl_version=self.ssl_version,
+ key_password=certs[0][2],
)
else:
conn = self.connections[conn_key] = connection_type(
@@ -2140,7 +2180,7 @@ class Http(object):
conn = self.connections.pop(conn_key, None)
if conn:
conn.close()
-
+
if self.force_exception_to_status_code:
if isinstance(e, HttpLib2ErrorWithResponse):
response = e.response
diff --git a/python3/httplib2/__init__.py b/python3/httplib2/__init__.py
index 4312f30..d8c3d34 100644
--- a/python3/httplib2/__init__.py
+++ b/python3/httplib2/__init__.py
@@ -15,7 +15,7 @@ __contributors__ = [
"Alex Yu",
]
__license__ = "MIT"
-__version__ = '0.14.0'
+__version__ = '0.15.0'
import base64
import calendar
@@ -175,7 +175,7 @@ DEFAULT_TLS_VERSION = getattr(ssl, "PROTOCOL_TLS", None) or getattr(
def _build_ssl_context(
disable_ssl_certificate_validation, ca_certs, cert_file=None, key_file=None,
- maximum_version=None, minimum_version=None,
+ maximum_version=None, minimum_version=None, key_password=None,
):
if not hasattr(ssl, "SSLContext"):
raise RuntimeError("httplib2 requires Python 3.2+ for ssl.SSLContext")
@@ -207,7 +207,7 @@ def _build_ssl_context(
context.load_verify_locations(ca_certs)
if cert_file:
- context.load_cert_chain(cert_file, key_file)
+ context.load_cert_chain(cert_file, key_file, key_password)
return context
@@ -959,8 +959,13 @@ class Credentials(object):
class KeyCerts(Credentials):
"""Identical to Credentials except that
name/password are mapped to key/cert."""
+ def add(self, key, cert, domain, password):
+ self.credentials.append((domain.lower(), key, cert, password))
- pass
+ def iter(self, domain):
+ for (cdomain, key, cert, password) in self.credentials:
+ if cdomain == "" or domain == cdomain:
+ yield (key, cert, password)
class AllHosts(object):
@@ -1245,6 +1250,7 @@ class HTTPSConnectionWithTimeout(http.client.HTTPSConnection):
disable_ssl_certificate_validation=False,
tls_maximum_version=None,
tls_minimum_version=None,
+ key_password=None,
):
self.disable_ssl_certificate_validation = disable_ssl_certificate_validation
@@ -1257,15 +1263,17 @@ class HTTPSConnectionWithTimeout(http.client.HTTPSConnection):
context = _build_ssl_context(
self.disable_ssl_certificate_validation, self.ca_certs, cert_file, key_file,
maximum_version=tls_maximum_version, minimum_version=tls_minimum_version,
+ key_password=key_password,
)
super(HTTPSConnectionWithTimeout, self).__init__(
host,
port=port,
- key_file=key_file,
- cert_file=cert_file,
timeout=timeout,
context=context,
)
+ self.key_file = key_file
+ self.cert_file = cert_file
+ self.key_password = key_password
def connect(self):
"""Connect to a host on a given (SSL) port."""
@@ -1476,6 +1484,16 @@ class Http(object):
# Keep Authorization: headers on a redirect.
self.forward_authorization_headers = False
+ def close(self):
+ """Close persistent connections, clear sensitive data.
+ Not thread-safe, requires external synchronization against concurrent requests.
+ """
+ existing, self.connections = self.connections, {}
+ for _, c in existing.items():
+ c.close()
+ self.certificates.clear()
+ self.clear_credentials()
+
def __getstate__(self):
state_dict = copy.copy(self.__dict__)
# In case request is augmented by some foreign object such as
@@ -1507,10 +1525,10 @@ class Http(object):
any time a request requires authentication."""
self.credentials.add(name, password, domain)
- def add_certificate(self, key, cert, domain):
+ def add_certificate(self, key, cert, domain, password=None):
"""Add a key and cert that will be used
any time a request requires authentication."""
- self.certificates.add(key, cert, domain)
+ self.certificates.add(key, cert, domain, password)
def clear_credentials(self):
"""Remove all the names and passwords
@@ -1782,6 +1800,7 @@ a string that contains the response entity body.
disable_ssl_certificate_validation=self.disable_ssl_certificate_validation,
tls_maximum_version=self.tls_maximum_version,
tls_minimum_version=self.tls_minimum_version,
+ key_password=certs[0][2],
)
else:
conn = self.connections[conn_key] = connection_type(
diff --git a/script/generate-tls b/script/generate-tls
new file mode 100755
index 0000000..8c96f1e
--- /dev/null
+++ b/script/generate-tls
@@ -0,0 +1,61 @@
+#!/bin/bash
+set -eu
+
+target_dir="${1:-.}"
+days=3650
+rsa_bits=2048
+org="httplib2-test"
+server_cn="localhost"
+subj_prefix="/C=ZZ/ST=./L=./O=$org/OU=."
+
+main() {
+ cd "$target_dir"
+ gen
+ check
+}
+
+check() {
+ echo "- check keys" >&2
+ openssl rsa -in ca.key -check -noout
+ openssl rsa -in client.key -check -noout
+ openssl rsa -in client_encrypted.key -check -noout -passin pass:12345
+ openssl rsa -in server.key -check -noout
+
+ echo "- check certs" >&2
+ for f in *.pem ; do
+ openssl x509 -in "$f" -checkend 3600 -noout
+ done
+}
+
+gen() {
+ echo "- generate keys, if absent" >&2
+ [[ -f ca.key ]] || openssl genrsa -out ca.key $rsa_bits
+ [[ -f client.key ]] || openssl genrsa -out client.key $rsa_bits
+ [[ -f client_encrypted.key ]] || openssl rsa -in client.key -out client_encrypted.key -aes128 -passout pass:12345
+ [[ -f server.key ]] || openssl genrsa -out server.key $rsa_bits
+
+ echo "- generate CA" >&2
+ openssl req -batch -new -nodes -x509 -days $days -subj "$subj_prefix/CN=$org-CA" -key ca.key -out ca.pem
+ openssl req -batch -new -nodes -x509 -days $days -subj "$subj_prefix/CN=$org-CA-unused" -key ca.key -out ca_unused.pem
+
+ echo "- generate client cert" >&2
+ openssl req -batch -new -nodes -out tmp.csr -key client.key -subj "$subj_prefix/CN=$org-client"
+ openssl x509 -req -in tmp.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.crt -days $days -serial -fingerprint
+ cat client.crt client.key >client.pem
+ cat client.crt ca.pem client.key >client_chain.pem
+
+ echo "- generate encrypted client cert" >&2
+ openssl req -batch -new -nodes -out tmp.csr -key client_encrypted.key -passin pass:12345 -subj "$subj_prefix/CN=$org-client-enc"
+ openssl x509 -req -in tmp.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client_encrypted.crt -days $days -serial -fingerprint
+ cat client_encrypted.crt client_encrypted.key >client_encrypted.pem
+
+ echo "- generate server cert" >&2
+ openssl req -batch -new -nodes -out tmp.csr -key server.key -subj "$subj_prefix/CN=$server_cn"
+ openssl x509 -req -in tmp.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.crt -days $days -serial -fingerprint
+ cat server.crt server.key >server.pem
+ cat server.crt ca.pem server.key >server_chain.pem
+
+ rm tmp.csr
+}
+
+main
diff --git a/setup.py b/setup.py
index db1db61..33c8827 100755
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ import setuptools.command.test
import sys
pkgdir = {"": "python%s" % sys.version_info[0]}
-VERSION = '0.14.0'
+VERSION = '0.15.0'
# `python setup.py test` uses existing Python environment, no virtualenv, no pip.
diff --git a/tests/__init__.py b/tests/__init__.py
index 28959d3..496652b 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -14,6 +14,7 @@ import re
import shutil
import six
import socket
+import ssl
import struct
import sys
import threading
@@ -23,6 +24,18 @@ import zlib
from six.moves import http_client, queue
+DUMMY_URL = "http://127.0.0.1:1"
+DUMMY_HTTPS_URL = "https://127.0.0.1:2"
+
+tls_dir = os.path.join(os.path.dirname(__file__), "tls")
+CA_CERTS = os.path.join(tls_dir, "ca.pem")
+CA_UNUSED_CERTS = os.path.join(tls_dir, "ca_unused.pem")
+CLIENT_PEM = os.path.join(tls_dir, "client.pem")
+CLIENT_ENCRYPTED_PEM = os.path.join(tls_dir, "client_encrypted.pem")
+SERVER_PEM = os.path.join(tls_dir, "server.pem")
+SERVER_CHAIN = os.path.join(tls_dir, "server_chain.pem")
+
+
@contextlib.contextmanager
def assert_raises(exc_type):
def _name(t):
@@ -261,9 +274,29 @@ class MockHTTPBadStatusConnection(object):
@contextlib.contextmanager
-def server_socket(fun, request_count=1, timeout=5):
+def server_socket(fun, request_count=1, timeout=5, scheme="", tls=None):
+ """Base socket server for tests.
+ Likely you want to use server_request or other higher level helpers.
+ All arguments except fun can be passed to other server_* helpers.
+
+ :param fun: fun(client_sock, tick) called after successful accept().
+ :param request_count: test succeeds after exactly this number of requests, triggered by tick(request)
+ :param timeout: seconds.
+ :param scheme: affects yielded value
+ "" - build normal http/https URI.
+ string - build normal URI using supplied scheme.
+ None - yield (addr, port) tuple.
+ :param tls:
+ None (default) - plain HTTP.
+ True - HTTPS with reasonable defaults. Likely you want httplib2.Http(ca_certs=tests.CA_CERTS)
+ string - path to custom server cert+key PEM file.
+ callable - function(context, listener, skip_errors) -> ssl_wrapped_listener
+ """
gresult = [None]
gcounter = [0]
+ tls_skip_errors = [
+ "TLSV1_ALERT_UNKNOWN_CA",
+ ]
def tick(request):
gcounter[0] += 1
@@ -276,7 +309,13 @@ def server_socket(fun, request_count=1, timeout=5):
def server_socket_thread(srv):
try:
while gcounter[0] < request_count:
- client, _ = srv.accept()
+ try:
+ client, _ = srv.accept()
+ except ssl.SSLError as e:
+ if e.reason in tls_skip_errors:
+ return
+ raise
+
try:
client.settimeout(timeout)
fun(client, tick)
@@ -299,18 +338,36 @@ def server_socket(fun, request_count=1, timeout=5):
print(traceback.format_exc(), file=sys.stderr)
gresult[0] = e
+ bind_hostname = "localhost"
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server.bind(("localhost", 0))
+ server.bind((bind_hostname, 0))
try:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except socket.error as ex:
print("non critical error on SO_REUSEADDR", ex)
server.listen(10)
server.settimeout(timeout)
+ server_port = server.getsockname()[1]
+ if tls is True:
+ tls = SERVER_CHAIN
+ if tls:
+ context = ssl_context()
+ if callable(tls):
+ context.load_cert_chain(SERVER_CHAIN)
+ server = tls(context, server, tls_skip_errors)
+ else:
+ context.load_cert_chain(tls)
+ server = context.wrap_socket(server, server_side=True)
+ if scheme == "":
+ scheme = "https" if tls else "http"
+
t = threading.Thread(target=server_socket_thread, args=(server,))
t.daemon = True
t.start()
- yield u"http://{0}:{1}/".format(*server.getsockname())
+ if scheme is None:
+ yield (bind_hostname, server_port)
+ else:
+ yield u"{scheme}://{host}:{port}/".format(scheme=scheme, host=bind_hostname, port=server_port)
server.close()
t.join()
if gresult[0] is not None:
@@ -329,11 +386,12 @@ def server_yield(fun, **kwargs):
if request is None:
break
i += 1
- request.client_addr = sock.getsockname()
+ request.client_sock = sock
request.number = i
q.put(request)
response = six.next(g)
sock.sendall(response)
+ request.client_sock = None
if not tick(request):
break
@@ -349,10 +407,11 @@ def server_request(request_handler, **kwargs):
if request is None:
break
i += 1
- request.client_addr = sock.getsockname()
+ request.client_sock = sock
request.number = i
response = request_handler(request=request)
sock.sendall(response)
+ request.client_sock = None
if not tick(request):
break
@@ -685,3 +744,11 @@ def deflate_compress(bs):
def deflate_decompress(bs):
return zlib.decompress(bs, -zlib.MAX_WBITS)
+
+
+def ssl_context(protocol=None):
+ """Workaround for old SSLContext() required protocol argument.
+ """
+ if sys.version_info < (3, 5, 3):
+ return ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ return ssl.SSLContext()
diff --git a/tests/test_external.py b/tests/test_external.py
deleted file mode 100644
index 0628d96..0000000
--- a/tests/test_external.py
+++ /dev/null
@@ -1,129 +0,0 @@
-"""These tests rely on replies from public internet services
-
-TODO: reimplement with local stubs
-"""
-import httplib2
-import os
-import pytest
-import ssl
-import sys
-import tests
-
-
-def test_get_301_via_https():
- # Google always redirects to http://google.com
- http = httplib2.Http()
- response, content = http.request("https://code.google.com/apis/", "GET")
- assert response.status == 200
- assert response.previous.status == 301
-
-
-def test_get_via_https():
- # Test that we can handle HTTPS
- http = httplib2.Http()
- response, content = http.request("https://google.com/adsense/", "GET")
- assert response.status == 200
-
-
-def test_get_via_https_spec_violation_on_location():
- # Test that we follow redirects through HTTPS
- # even if they violate the spec by including
- # a relative Location: header instead of an
- # absolute one.
- http = httplib2.Http()
- response, content = http.request("https://google.com/adsense", "GET")
- assert response.status == 200
- assert response.previous is not None
-
-
-def test_get_via_https_key_cert():
- # At this point I can only test
- # that the key and cert files are passed in
- # correctly to httplib. It would be nice to have
- # a real https endpoint to test against.
- http = httplib2.Http(timeout=2)
- http.add_certificate("akeyfile", "acertfile", "bitworking.org")
- try:
- http.request("https://bitworking.org", "GET")
- except AttributeError:
- assert http.connections["https:bitworking.org"].key_file == "akeyfile"
- assert http.connections["https:bitworking.org"].cert_file == "acertfile"
- except IOError:
- # Skip on 3.2
- pass
-
- try:
- http.request("https://notthere.bitworking.org", "GET")
- except httplib2.ServerNotFoundError:
- assert http.connections["https:notthere.bitworking.org"].key_file is None
- assert http.connections["https:notthere.bitworking.org"].cert_file is None
- except IOError:
- # Skip on 3.2
- pass
-
-
-def test_ssl_invalid_ca_certs_path():
- # Test that we get an ssl.SSLError when specifying a non-existent CA
- # certs file.
- http = httplib2.Http(ca_certs="/nosuchfile")
- with tests.assert_raises(IOError):
- http.request("https://www.google.com/", "GET")
-
-
-@pytest.mark.xfail(
- sys.version_info <= (3,),
- reason=(
- "FIXME: for unknown reason Python 2.7.10 validates www.google.com "
- "against dummy CA www.example.com"
- ),
-)
-def test_ssl_wrong_ca():
- # Test that we get a SSLHandshakeError if we try to access
- # https://www.google.com, using a CA cert file that doesn't contain
- # the CA Google uses (i.e., simulating a cert that's not signed by a
- # trusted CA).
- other_ca_certs = os.path.join(
- os.path.dirname(os.path.abspath(httplib2.__file__)), "test", "other_cacerts.txt"
- )
- assert os.path.exists(other_ca_certs)
- http = httplib2.Http(ca_certs=other_ca_certs)
- http.follow_redirects = False
- with tests.assert_raises(ssl.SSLError):
- http.request("https://www.google.com/", "GET")
-
-
-def test_sni_hostname_validation():
- # TODO: make explicit test server with SNI validation
- http = httplib2.Http()
- http.request("https://google.com/", method="GET")
-
-
-@pytest.mark.skipif(
- os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"),
- reason="Python 2.7 doesn't support TLS min/max"
-)
-def test_min_tls_version():
- # skip on Python versions that don't support TLS min
- if not hasattr(ssl.SSLContext(), 'minimum_version'):
- return
- # BadSSL server that supports max TLS 1.1,
- # forcing 1.2 should always fail
- http = httplib2.Http(tls_minimum_version="TLSv1_2")
- with tests.assert_raises(ssl.SSLError):
- http.request("https://tls-v1-1.badssl.com:1011/")
-
-
-@pytest.mark.skipif(
- os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"),
- reason="Python 2.7 doesn't support TLS min/max"
-)
-def test_max_tls_version():
- # skip on Python versions that don't support TLS max
- if not hasattr(ssl.SSLContext(), 'maximum_version'):
- return
- # Google supports TLS 1.2+, confirm we can force down to 1.0
- # this may break whenever Google disables TLSv1
- http = httplib2.Http(tls_maximum_version="TLSv1")
- http.request("https://google.com")
- _, tls_ver, _ = http.connections['https:google.com'].sock.cipher()
- assert tls_ver == "TLSv1.0"
diff --git a/tests/test_http.py b/tests/test_http.py
index 9bd9ee0..97b52dc 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -10,12 +10,8 @@ import os
import pytest
from six.moves import http_client, urllib
import socket
-import ssl
import tests
-DUMMY_URL = "http://127.0.0.1:1"
-DUMMY_HTTPS_URL = "https://127.0.0.1:2"
-
def _raise_connection_refused_exception(*args, **kwargs):
raise socket.error(errno.ECONNREFUSED, "Connection refused.")
@@ -25,9 +21,9 @@ def test_connection_type():
http = httplib2.Http()
http.force_exception_to_status_code = False
response, content = http.request(
- DUMMY_URL, connection_type=tests.MockHTTPConnection
+ tests.DUMMY_URL, connection_type=tests.MockHTTPConnection
)
- assert response["content-location"] == DUMMY_URL
+ assert response["content-location"] == tests.DUMMY_URL
assert content == b"the body"
@@ -38,7 +34,7 @@ def test_bad_status_line_retry():
http.force_exception_to_status_code = False
try:
response, content = http.request(
- DUMMY_URL, connection_type=tests.MockHTTPBadStatusConnection
+ tests.DUMMY_URL, connection_type=tests.MockHTTPBadStatusConnection
)
except http_client.BadStatusLine:
assert tests.MockHTTPBadStatusConnection.num_calls == 2
@@ -71,7 +67,7 @@ def test_connection_refused_raises_exception(mock_socket_connect):
http = httplib2.Http()
http.force_exception_to_status_code = False
with tests.assert_raises(socket.error):
- http.request(DUMMY_URL)
+ http.request(tests.DUMMY_URL)
@pytest.mark.skipif(
@@ -84,7 +80,7 @@ def test_connection_refused_returns_response(mock_socket_connect):
mock_socket_connect.side_effect = _raise_connection_refused_exception
http = httplib2.Http()
http.force_exception_to_status_code = True
- response, content = http.request(DUMMY_URL)
+ response, content = http.request(tests.DUMMY_URL)
content = content.lower()
assert response["content-type"] == "text/plain"
assert (
@@ -647,38 +643,3 @@ content"""
assert response.status == 200
assert content == b"content"
assert response["link"], "link1, link2"
-
-
-@pytest.mark.skipif(
- os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"),
- reason="Python 2.7 doesn't support TLS min/max"
-)
-def test_set_min_tls_version():
- # Test setting minimum TLS version
- # We expect failure on Python < 3.7 or OpenSSL < 1.1
- expect_success = hasattr(ssl.SSLContext(), 'minimum_version')
- try:
- http = httplib2.Http(tls_minimum_version="TLSv1_2")
- http.request(DUMMY_HTTPS_URL)
- except RuntimeError:
- assert not expect_success
- except socket.error:
- assert expect_success
-
-
-@pytest.mark.skipif(
- os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"),
- reason="Python 2.7 doesn't support TLS min/max"
-)
-def test_set_max_tls_version():
- # Test setting maximum TLS version
- # We expect RuntimeError on Python < 3.7 or OpenSSL < 1.1
- # We expect socket error otherwise
- expect_success = hasattr(ssl.SSLContext(), 'maximum_version')
- try:
- http = httplib2.Http(tls_maximum_version="TLSv1_2")
- http.request(DUMMY_HTTPS_URL)
- except RuntimeError:
- assert not expect_success
- except socket.error:
- assert expect_success
diff --git a/tests/test_https.py b/tests/test_https.py
new file mode 100644
index 0000000..39d7d59
--- /dev/null
+++ b/tests/test_https.py
@@ -0,0 +1,203 @@
+import httplib2
+import pytest
+from six.moves import urllib
+import socket
+import ssl
+import tests
+
+
+def test_get_via_https():
+ # Test that we can handle HTTPS
+ http = httplib2.Http(ca_certs=tests.CA_CERTS)
+ with tests.server_const_http(tls=True) as uri:
+ response, _ = http.request(uri, "GET")
+ assert response.status == 200
+
+
+def test_get_301_via_https():
+ http = httplib2.Http(ca_certs=tests.CA_CERTS)
+ glocation = [""] # nonlocal kind of trick, maybe redundant
+
+ def handler(request):
+ if request.uri == "/final":
+ return tests.http_response_bytes(body=b"final")
+ return tests.http_response_bytes(status="301 goto", headers={"location": glocation[0]})
+
+ with tests.server_request(handler, request_count=2, tls=True) as uri:
+ glocation[0] = urllib.parse.urljoin(uri, "/final")
+ response, content = http.request(uri, "GET")
+ assert response.status == 200
+ assert content == b"final"
+ assert response.previous.status == 301
+ assert response.previous["location"] == glocation[0]
+
+
+def test_get_301_via_https_spec_violation_on_location():
+ # Test that we follow redirects through HTTPS
+ # even if they violate the spec by including
+ # a relative Location: header instead of an absolute one.
+ http = httplib2.Http(ca_certs=tests.CA_CERTS)
+
+ def handler(request):
+ if request.uri == "/final":
+ return tests.http_response_bytes(body=b"final")
+ return tests.http_response_bytes(status="301 goto", headers={"location": "/final"})
+
+ with tests.server_request(handler, request_count=2, tls=True) as uri:
+ response, content = http.request(uri, "GET")
+ assert response.status == 200
+ assert content == b"final"
+ assert response.previous.status == 301
+
+
+def test_invalid_ca_certs_path():
+ http = httplib2.Http(ca_certs="/nosuchfile")
+ with tests.server_const_http(request_count=0, tls=True) as uri:
+ with tests.assert_raises(IOError):
+ http.request(uri, "GET")
+
+
+def test_not_trusted_ca():
+ # Test that we get a SSLHandshakeError if we try to access
+ # server using a CA cert file that doesn't contain server's CA.
+ http = httplib2.Http(ca_certs=tests.CA_UNUSED_CERTS)
+ with tests.server_const_http(tls=True) as uri:
+ try:
+ http.request(uri, "GET")
+ assert False, "expected CERTIFICATE_VERIFY_FAILED"
+ except ssl.SSLError as e:
+ assert e.reason == "CERTIFICATE_VERIFY_FAILED"
+ except httplib2.SSLHandshakeError: # Python2
+ pass
+
+
+@pytest.mark.skipif(
+ not hasattr(tests.ssl_context(), "minimum_version"),
+ reason="ssl doesn't support TLS min/max",
+)
+def test_set_min_tls_version():
+ # Test setting minimum TLS version
+ # We expect failure on Python < 3.7 or OpenSSL < 1.1
+ expect_success = hasattr(ssl.SSLContext(), 'minimum_version')
+ try:
+ http = httplib2.Http(tls_minimum_version="TLSv1_2")
+ http.request(tests.DUMMY_HTTPS_URL)
+ except RuntimeError:
+ assert not expect_success
+ except socket.error:
+ assert expect_success
+
+
+@pytest.mark.skipif(
+ not hasattr(tests.ssl_context(), "maximum_version"),
+ reason="ssl doesn't support TLS min/max",
+)
+def test_set_max_tls_version():
+ # Test setting maximum TLS version
+ # We expect RuntimeError on Python < 3.7 or OpenSSL < 1.1
+ # We expect socket error otherwise
+ expect_success = hasattr(ssl.SSLContext(), 'maximum_version')
+ try:
+ http = httplib2.Http(tls_maximum_version="TLSv1_2")
+ http.request(tests.DUMMY_HTTPS_URL)
+ except RuntimeError:
+ assert not expect_success
+ except socket.error:
+ assert expect_success
+
+
+@pytest.mark.skipif(
+ not hasattr(tests.ssl_context(), "minimum_version"),
+ reason="ssl doesn't support TLS min/max",
+)
+def test_min_tls_version():
+ def setup_tls(context, server, skip_errors):
+ skip_errors.append("WRONG_VERSION_NUMBER")
+ context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_1)
+ context.load_cert_chain(tests.SERVER_CHAIN)
+ return context.wrap_socket(server, server_side=True)
+
+ http = httplib2.Http(ca_certs=tests.CA_CERTS, tls_minimum_version="TLSv1_2")
+ with tests.server_const_http(tls=setup_tls) as uri:
+ try:
+ http.request(uri)
+ assert False, "expected SSLError"
+ except ssl.SSLError as e:
+ assert e.reason in ("UNSUPPORTED_PROTOCOL", "VERSION_TOO_LOW")
+
+
+@pytest.mark.skipif(
+ not hasattr(tests.ssl_context(), "maximum_version"),
+ reason="ssl doesn't support TLS min/max",
+)
+def test_max_tls_version():
+ http = httplib2.Http(ca_certs=tests.CA_CERTS, tls_maximum_version="TLSv1")
+ with tests.server_const_http(tls=True) as uri:
+ http.request(uri)
+ _, tls_ver, _ = http.connections.popitem()[1].sock.cipher()
+ assert tls_ver == "TLSv1.0"
+
+
+def test_client_cert_verified():
+ cert_log = []
+
+ def setup_tls(context, server, skip_errors):
+ context.load_verify_locations(cafile=tests.CA_CERTS)
+ context.verify_mode = ssl.CERT_REQUIRED
+ return context.wrap_socket(server, server_side=True)
+
+ def handler(request):
+ cert_log.append(request.client_sock.getpeercert())
+ return tests.http_response_bytes()
+
+ http = httplib2.Http(ca_certs=tests.CA_CERTS)
+ with tests.server_request(handler, tls=setup_tls) as uri:
+ uri_parsed = urllib.parse.urlparse(uri)
+ http.add_certificate(tests.CLIENT_PEM, tests.CLIENT_PEM, uri_parsed.netloc)
+ http.request(uri)
+
+ assert len(cert_log) == 1
+ # TODO extract serial from tests.CLIENT_PEM
+ assert cert_log[0]["serialNumber"] == "E2AA6A96D1BF1AEC"
+
+
+def test_client_cert_password_verified():
+ cert_log = []
+
+ def setup_tls(context, server, skip_errors):
+ context.load_verify_locations(cafile=tests.CA_CERTS)
+ context.verify_mode = ssl.CERT_REQUIRED
+ return context.wrap_socket(server, server_side=True)
+
+ def handler(request):
+ cert_log.append(request.client_sock.getpeercert())
+ return tests.http_response_bytes()
+
+ http = httplib2.Http(ca_certs=tests.CA_CERTS)
+ with tests.server_request(handler, tls=setup_tls) as uri:
+ uri_parsed = urllib.parse.urlparse(uri)
+ http.add_certificate(tests.CLIENT_ENCRYPTED_PEM, tests.CLIENT_ENCRYPTED_PEM,
+ uri_parsed.netloc, password="12345")
+ http.request(uri)
+
+ assert len(cert_log) == 1
+ # TODO extract serial from tests.CLIENT_PEM
+ assert cert_log[0]["serialNumber"] == "E2AA6A96D1BF1AED"
+
+
+@pytest.mark.skipif(
+ not hasattr(tests.ssl_context(), "set_servername_callback"),
+ reason="SSLContext.set_servername_callback is not available",
+)
+def test_sni_set_servername_callback():
+ sni_log = []
+
+ def setup_tls(context, server, skip_errors):
+ context.set_servername_callback(lambda _sock, hostname, _context: sni_log.append(hostname))
+ return context.wrap_socket(server, server_side=True)
+
+ http = httplib2.Http(ca_certs=tests.CA_CERTS)
+ with tests.server_const_http(tls=setup_tls) as uri:
+ uri_parsed = urllib.parse.urlparse(uri)
+ http.request(uri)
+ assert sni_log == [uri_parsed.hostname]
diff --git a/tests/test_other.py b/tests/test_other.py
index f87cfbd..0f450ab 100644
--- a/tests/test_other.py
+++ b/tests/test_other.py
@@ -232,3 +232,24 @@ def test_http_443_forced_https():
assert len(m.call_args) > 0, "expected Http._request() call"
conn = m.call_args[0][0]
assert isinstance(conn, httplib2.HTTPConnectionWithTimeout)
+
+
+def test_close():
+ http = httplib2.Http()
+ assert len(http.connections) == 0
+ with tests.server_const_http() as uri:
+ http.request(uri)
+ assert len(http.connections) == 1
+ http.close()
+ assert len(http.connections) == 0
+
+
+def test_connect_exception_type():
+ # This autoformatting PR actually changed the behavior of error handling:
+ # https://github.com/httplib2/httplib2/pull/105/files#diff-c6669c781a2dee1b2d2671cab4e21c66L985
+ # potentially changing the type of the error raised by connect()
+ # https://github.com/httplib2/httplib2/pull/150
+ http = httplib2.Http()
+ with mock.patch("httplib2.socket.socket.connect", side_effect=socket.timeout("foo")):
+ with tests.assert_raises(socket.timeout):
+ http.request(tests.DUMMY_URL)
diff --git a/tests/tls/ca.key b/tests/tls/ca.key
new file mode 100644
index 0000000..cc1bb1a
--- /dev/null
+++ b/tests/tls/ca.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAxBKerwr0M3230xWKMvxB20+AR9SojbQIN2/8EI9pbSrjmlTH
+PFXWf02q2Ll0GPbcnSKOMnAARptVCkxEfkDGPN03Ux0jjGu2MrwZHURXM2gHsQn3
+3Gj3HCreFLMxIqMFfGeB9T0VxurgUek/+bR85QBVNE9GrQfrAN8O+ScOpCOENh5r
+lYc/QscH/S0QJvttbGAZFP1bB/Xjltwd6fF3rZgCfTJ88B2UIcEVt+X/kc/0QByP
+PACAnCaE4cB2q+SJVEMYP6BLDVvCPRO53UC8cqsLfpKUz73two/No4PhMHwCPspC
++wKlAD3+GWmsatz0rRysm7V0GghCGe+T5JHsGwIDAQABAoIBAE+7KqAPmkIeC1Rg
+2/PjtHwUFhwfk/MblIPGm/+38a0c1bT6aJJWbYUS9jhvIZDNQeT8GkrUVKhhnfE0
+Fl4oxPQXGNpJbR0657o11xiZo8QZt5b8cLhGTsY7gFd2jrKBDEgMZ0JsdqCO/m0Q
+pp1KEcelnQBKhHj0UVHnYtVaVo/T0ciS06l0urxpXBW2Bcg5lP/KzBD9md9UJrPU
+9oPM2snMlBTGKH79eTZu4eyKWOMIK+0vSLBnJkAZtNjvitftEs+a0AvVSF+uXbow
+sz7WUpm+BR2wTKikfNMa5nNaLUCdE3sB/1fHQeT7reLEfIVrIfHCyNHi2X0xwWBP
+n9U/Q1ECgYEA/qT9JhIZsy1HjHNliFhfW1lBMLRZdiQwyQeoV0N1aMy68rV3kDqr
+z8qIaHRt2zCgbKgEhB2WRuIL57mXK3WRhk3KP5LEG0wKr7n58iiTtipH/I/04rTG
+RAcYUIR8gDpvd0P1YUJ4dSMqPLDP13+ikA8C3EfL78nxMeCdhyuBA0cCgYEAxR3Q
+Smjkcs8pckl04qOZnnGRpKg/Hmu2wIg2WNisI97t35B0dkZhTOGfsCuMOWJixzEp
+35ZgUzWUd3ACrgZQcxUYlBAdo849QE7lx1Nys8kouPEjbVsRH8Fs1JJkEiVYhoWV
+8JfVzv6Mb95ZBGlvYsiq6p9hT0mLykDNt0xG8o0CgYBOHj1O1ZSuxABEFQ6b0kiG
+lI4MK/eZ56ZTtZauFpLJMK1VUdg5Fdapaz+Hk9gzuuosCys/gHgejLAMSYIXofyf
+z/Nwp0yj9yL8H7iO0mXmJ3hoAZ2lgsGkEu0hnlM3XzXcx6taR/L+NGh7r95DBPPQ
+79n3y8rDaBcnLvoEgpMUdwKBgQCBN6Qdw1lO8gMHiqP3FqxTs7t4J1sJRC9PU3vd
+Dlz6Pt/NGNNf3Y9XaOjYAhQwYhDC57W9fsSyh4NGMMVw8261onS0S0RC56Y7i/0R
+h+C/fvUVF+7Td0loedIwH68+PgEkXloGmGJvCWtiwm20eLGuHkH9AHI4Gcxrz8OL
+j5NK2QKBgCYKtCqB1rXp4k4KcHYQBSmbIN9aEzd4OqjYtzvrYv8wE8ooRim8dd55
+xRRh1r6Nx7+/KoXg7FgaLbDex4dGeCExjvXLmtJNOj0QwJwID84ZhmzjvL26sY8F
+rrt4XPaZv8/5T5kiXE2Mtp3LyIPlq0tRXepdyltrTFPfX3HR+ph2
+-----END RSA PRIVATE KEY-----
diff --git a/tests/tls/ca.pem b/tests/tls/ca.pem
new file mode 100644
index 0000000..1c05e64
--- /dev/null
+++ b/tests/tls/ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDRDCCAiwCCQC5E5PSm8flUjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMGQxCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMRkwFwYDVQQDDBBodHRwbGliMi10ZXN0LUNBMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAxBKerwr0M3230xWKMvxB20+AR9SojbQIN2/8EI9pbSrj
+mlTHPFXWf02q2Ll0GPbcnSKOMnAARptVCkxEfkDGPN03Ux0jjGu2MrwZHURXM2gH
+sQn33Gj3HCreFLMxIqMFfGeB9T0VxurgUek/+bR85QBVNE9GrQfrAN8O+ScOpCOE
+Nh5rlYc/QscH/S0QJvttbGAZFP1bB/Xjltwd6fF3rZgCfTJ88B2UIcEVt+X/kc/0
+QByPPACAnCaE4cB2q+SJVEMYP6BLDVvCPRO53UC8cqsLfpKUz73two/No4PhMHwC
+PspC+wKlAD3+GWmsatz0rRysm7V0GghCGe+T5JHsGwIDAQABMA0GCSqGSIb3DQEB
+CwUAA4IBAQB4b+DWt0An4YoXj7lb/+N7FVr2m5UVyBI+bbEGI/qsql/Ixiaef69M
+jej7n5ucUx8GBql62W0c3/E3qZFfo49ngH1WC5gkKQH9V4jGZui5CUfmNE6WepQ/
+vL6eKXUp7RoJ/hWVhGm1uV3OShF+EN0t2wZttYg4lip0FjrY8tRWdjw5yu61wWVu
+WuHxTzKiHe9emjhhUBgnWRnNeYPTRs0xM2Awv5KYPq2cmrjGbSz3mYDkBpbiJUp4
+pM9g8qLmsDO2yrlVF659D08+5zkmMbyqnn84X0n3SM3Yn0ayZOmbNHiXoAzklZNP
+7xiyxMEAfVQOITsvSDG2PzbZlGGtbaka
+-----END CERTIFICATE-----
diff --git a/tests/tls/ca.srl b/tests/tls/ca.srl
new file mode 100644
index 0000000..ad8d416
--- /dev/null
+++ b/tests/tls/ca.srl
@@ -0,0 +1 @@
+E2AA6A96D1BF1AEE
diff --git a/tests/tls/ca_unused.pem b/tests/tls/ca_unused.pem
new file mode 100644
index 0000000..4c4291a
--- /dev/null
+++ b/tests/tls/ca_unused.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDUjCCAjoCCQC47jeQyttLgzANBgkqhkiG9w0BAQsFADBrMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEgMB4GA1UEAwwXaHR0cGxpYjItdGVzdC1DQS11bnVzZWQw
+HhcNMTkwOTI2MTUwMzM0WhcNMjkwOTIzMTUwMzM0WjBrMQswCQYDVQQGEwJaWjEK
+MAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVzdDEK
+MAgGA1UECwwBLjEgMB4GA1UEAwwXaHR0cGxpYjItdGVzdC1DQS11bnVzZWQwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEEp6vCvQzfbfTFYoy/EHbT4BH
+1KiNtAg3b/wQj2ltKuOaVMc8VdZ/TarYuXQY9tydIo4ycABGm1UKTER+QMY83TdT
+HSOMa7YyvBkdRFczaAexCffcaPccKt4UszEiowV8Z4H1PRXG6uBR6T/5tHzlAFU0
+T0atB+sA3w75Jw6kI4Q2HmuVhz9Cxwf9LRAm+21sYBkU/VsH9eOW3B3p8XetmAJ9
+MnzwHZQhwRW35f+Rz/RAHI88AICcJoThwHar5IlUQxg/oEsNW8I9E7ndQLxyqwt+
+kpTPve3Cj82jg+EwfAI+ykL7AqUAPf4Zaaxq3PStHKybtXQaCEIZ75PkkewbAgMB
+AAEwDQYJKoZIhvcNAQELBQADggEBAFbeSPQgXJxfHc1m8wJ4eSW470gXjHZD82uH
+sZTj6v+UZlYzVUgDt+KEdZpoIP8C0prhez+scB6YcwiwP5iHfH3AB51jVoQvKAFt
+4TNKt9LvOuOzGKk9LmO41xYO6KjAOWuoERdYtBR0h0CyOm756iHwO0bQEELiePfU
+hB7o9SlVg0aMcWtbrGBLGBy6HE0p3Oiq/ny0G8r/gshnHvLku6JOxg0XJGDi3LuG
+ezBF0HFwK56NaB2syDtQRCT7I5yqLBK2AlwhcbZat07vLFPeDyw4Omh6COJ/tQsU
+qIcVJ6kS7VJejjWQD8z5CybYDnmBJJqXW4ixUs8wu0l3miaBdiM=
+-----END CERTIFICATE-----
diff --git a/tests/tls/client.crt b/tests/tls/client.crt
new file mode 100644
index 0000000..9430e27
--- /dev/null
+++ b/tests/tls/client.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSDCCAjACCQDiqmqW0b8a7DANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMGgxCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMR0wGwYDVQQDDBRodHRwbGliMi10ZXN0LWNsaWVudDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALtXT//Esar+MDk6Gcj3KDLAyTU7jPqUx4S83LNI
+GumuBmOg7oe16SSM//NUFhCHiHr4IzeqDOD2Dq1aM661Ta2EINHvpG+9BEkCWkFr
+q4eh0pfocsvU64dtd26TCk9Q4Qqaj4t4HSYYv1hz8UFh5RNLVjgtbR7OQ37Oumcg
+jRGnelV/ck6u5BvN/fKK7p/W6hPcO9OjJDAmlVNZVPtP2ki6Lv/Q87x34X+1/Qb1
+LILUOG5mdfCTmf2tYh9bXqZmqoidTY4O7/JiPuT0+1056Ja8bDGYSFXPvvqd1aEW
+nGA22MEzd74w9A4tIieCRHlGGOSf0AGsVTmHKRf5bpQjaCsCAwEAATANBgkqhkiG
+9w0BAQUFAAOCAQEAi/X3QjpzPap9IhpHqvgFirsEepruz8lCk+Zo6A/+DP/PocII
+/8jWdIV87RDDkkaVGvWOywZyUNN1RAfrt/jGCW8xgCaSGWRab10QIW8DGhbP6FTz
+7xcBnQzcoc1gggZBcwOjkRuefW2zkgGIJo5XxHlBfo3T9nX4086Py/b+VoAmcIlm
+Y/LNHxtIyDDiOgGK9x7+IqEXQuo/p2z5oFubj/hyNJhXaYU2u7nNMXICYY/eY4vX
+GgZ44lGZ2YR7NwzqM5UHNXr7/VJzgxWwAgyZUT8DdnjkZY4wLt1JJas5n3oldBsA
++og2cMk0oOsiFAwHAwE7St4oFY0ivKDhttf7WQ==
+-----END CERTIFICATE-----
diff --git a/tests/tls/client.key b/tests/tls/client.key
new file mode 100644
index 0000000..9a442b8
--- /dev/null
+++ b/tests/tls/client.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAu1dP/8Sxqv4wOToZyPcoMsDJNTuM+pTHhLzcs0ga6a4GY6Du
+h7XpJIz/81QWEIeIevgjN6oM4PYOrVozrrVNrYQg0e+kb70ESQJaQWurh6HSl+hy
+y9Trh213bpMKT1DhCpqPi3gdJhi/WHPxQWHlE0tWOC1tHs5Dfs66ZyCNEad6VX9y
+Tq7kG8398orun9bqE9w706MkMCaVU1lU+0/aSLou/9DzvHfhf7X9BvUsgtQ4bmZ1
+8JOZ/a1iH1tepmaqiJ1Njg7v8mI+5PT7XTnolrxsMZhIVc+++p3VoRacYDbYwTN3
+vjD0Di0iJ4JEeUYY5J/QAaxVOYcpF/lulCNoKwIDAQABAoIBAQCtzBeUYWauCni0
+bnlDXj91rjI7504nneTm+MsKq6cECQU2YjNHxXRQC1rb47NAjGwKIK+TUXf3L254
+VglCWEKC6eQEvvxfCQyzqrIOpRORlYeok+YDwTjr/5rgAxt6b78GtlLbAYiRMj45
+kf5MOMRqvOZ04XetL4+gUarOR3131L38ysReluabsCXkgIH9kZHmgOW2El4lmoHp
+CQpvMkJWyoVZvDbjLi0JoEljHGpfdWNdcllHP4dbNSQtfgG3VXXFgKqWQ8ZiY2U1
+y5SxHaeAjKHBUoGeinox/Myzan3xCysZb+gi5UxcrE7dn5lSB2/AMBymYShI/2qi
+UWq64JeJAoGBAPLtHRIcbRn53IhXGwcdFAn7JUSzKvr+gUfJiyfbw28CDWVRgTce
+JN+FzTuW92Iwm3ppBKmJ5PcZZnqt7VTtWfLvP126YaGctqZHSWiD9oK3EzDJEWIO
+trpMlJkeB+IQlvYMCiC+G+6XFBCdB7X3X1D9Y9z11Kf/arx24bB/ByVlAoGBAMVs
+YZRL9idgwgU4LMOqaPkU99de4wzYF44joZrp3Eme3dC9sdHrDtDy6OpiQu4zP+Ax
+5cws6M6txd5meAh2YwRhJBmGUYIQuhhNKQjhoeovw0tbtXO9rAHPegXPqg8xwzY9
+Ntc/WlfwM0O7ROfOq4r9erWBn0B7xspxRMH+LIZPAoGBALVW326XnbHYXRHBxEFJ
+KZ5Rxf5EqP74YVVPU/uLB5akN4+8ifK1I91fqlajWUQI+Ocl4f8VGsCCS4ekshfF
+nnHEus6ixSK5M3dom5nTeH8XXtH6JmnGhg0IAZ1TV5sfuzEsx5qtj3hJewbz0b+6
+S4LPxG47bGWEOw84xzzTdmgpAoGAGj87heTHeBrEEL+UK/tW826XOLnzw7xi/VG9
+ZYQb9mm5ocvmfTscACmbT7X6ogKMRnk7zPZXiUrPGK9U3AMpTObBTudtpLYml56C
+ixy8Uw9Ajp9Fs3qPCLqVxXoDaPu7sVVYGivhDfnwRtv54Du40MS8cK8oBgGuvzFp
+68SoFL8CgYAY7KvTfTKk4oWWeclmwEoe04woV7J6XuB7OnbxYpKpiGh4juN1E6wo
+n9UhAVzO6cAfK/ZuhTkDtvJSsXtQ1xElZLMIG1Yb7yikRyO73EHRUpHon3Gah+79
+MM6uZReiEdkx/hMthL45jP85hfVM89M7LYj9SBoxY2xpuzN+HmiezQ==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/tls/client.pem b/tests/tls/client.pem
new file mode 100644
index 0000000..f12775f
--- /dev/null
+++ b/tests/tls/client.pem
@@ -0,0 +1,47 @@
+-----BEGIN CERTIFICATE-----
+MIIDSDCCAjACCQDiqmqW0b8a7DANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMGgxCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMR0wGwYDVQQDDBRodHRwbGliMi10ZXN0LWNsaWVudDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALtXT//Esar+MDk6Gcj3KDLAyTU7jPqUx4S83LNI
+GumuBmOg7oe16SSM//NUFhCHiHr4IzeqDOD2Dq1aM661Ta2EINHvpG+9BEkCWkFr
+q4eh0pfocsvU64dtd26TCk9Q4Qqaj4t4HSYYv1hz8UFh5RNLVjgtbR7OQ37Oumcg
+jRGnelV/ck6u5BvN/fKK7p/W6hPcO9OjJDAmlVNZVPtP2ki6Lv/Q87x34X+1/Qb1
+LILUOG5mdfCTmf2tYh9bXqZmqoidTY4O7/JiPuT0+1056Ja8bDGYSFXPvvqd1aEW
+nGA22MEzd74w9A4tIieCRHlGGOSf0AGsVTmHKRf5bpQjaCsCAwEAATANBgkqhkiG
+9w0BAQUFAAOCAQEAi/X3QjpzPap9IhpHqvgFirsEepruz8lCk+Zo6A/+DP/PocII
+/8jWdIV87RDDkkaVGvWOywZyUNN1RAfrt/jGCW8xgCaSGWRab10QIW8DGhbP6FTz
+7xcBnQzcoc1gggZBcwOjkRuefW2zkgGIJo5XxHlBfo3T9nX4086Py/b+VoAmcIlm
+Y/LNHxtIyDDiOgGK9x7+IqEXQuo/p2z5oFubj/hyNJhXaYU2u7nNMXICYY/eY4vX
+GgZ44lGZ2YR7NwzqM5UHNXr7/VJzgxWwAgyZUT8DdnjkZY4wLt1JJas5n3oldBsA
++og2cMk0oOsiFAwHAwE7St4oFY0ivKDhttf7WQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAu1dP/8Sxqv4wOToZyPcoMsDJNTuM+pTHhLzcs0ga6a4GY6Du
+h7XpJIz/81QWEIeIevgjN6oM4PYOrVozrrVNrYQg0e+kb70ESQJaQWurh6HSl+hy
+y9Trh213bpMKT1DhCpqPi3gdJhi/WHPxQWHlE0tWOC1tHs5Dfs66ZyCNEad6VX9y
+Tq7kG8398orun9bqE9w706MkMCaVU1lU+0/aSLou/9DzvHfhf7X9BvUsgtQ4bmZ1
+8JOZ/a1iH1tepmaqiJ1Njg7v8mI+5PT7XTnolrxsMZhIVc+++p3VoRacYDbYwTN3
+vjD0Di0iJ4JEeUYY5J/QAaxVOYcpF/lulCNoKwIDAQABAoIBAQCtzBeUYWauCni0
+bnlDXj91rjI7504nneTm+MsKq6cECQU2YjNHxXRQC1rb47NAjGwKIK+TUXf3L254
+VglCWEKC6eQEvvxfCQyzqrIOpRORlYeok+YDwTjr/5rgAxt6b78GtlLbAYiRMj45
+kf5MOMRqvOZ04XetL4+gUarOR3131L38ysReluabsCXkgIH9kZHmgOW2El4lmoHp
+CQpvMkJWyoVZvDbjLi0JoEljHGpfdWNdcllHP4dbNSQtfgG3VXXFgKqWQ8ZiY2U1
+y5SxHaeAjKHBUoGeinox/Myzan3xCysZb+gi5UxcrE7dn5lSB2/AMBymYShI/2qi
+UWq64JeJAoGBAPLtHRIcbRn53IhXGwcdFAn7JUSzKvr+gUfJiyfbw28CDWVRgTce
+JN+FzTuW92Iwm3ppBKmJ5PcZZnqt7VTtWfLvP126YaGctqZHSWiD9oK3EzDJEWIO
+trpMlJkeB+IQlvYMCiC+G+6XFBCdB7X3X1D9Y9z11Kf/arx24bB/ByVlAoGBAMVs
+YZRL9idgwgU4LMOqaPkU99de4wzYF44joZrp3Eme3dC9sdHrDtDy6OpiQu4zP+Ax
+5cws6M6txd5meAh2YwRhJBmGUYIQuhhNKQjhoeovw0tbtXO9rAHPegXPqg8xwzY9
+Ntc/WlfwM0O7ROfOq4r9erWBn0B7xspxRMH+LIZPAoGBALVW326XnbHYXRHBxEFJ
+KZ5Rxf5EqP74YVVPU/uLB5akN4+8ifK1I91fqlajWUQI+Ocl4f8VGsCCS4ekshfF
+nnHEus6ixSK5M3dom5nTeH8XXtH6JmnGhg0IAZ1TV5sfuzEsx5qtj3hJewbz0b+6
+S4LPxG47bGWEOw84xzzTdmgpAoGAGj87heTHeBrEEL+UK/tW826XOLnzw7xi/VG9
+ZYQb9mm5ocvmfTscACmbT7X6ogKMRnk7zPZXiUrPGK9U3AMpTObBTudtpLYml56C
+ixy8Uw9Ajp9Fs3qPCLqVxXoDaPu7sVVYGivhDfnwRtv54Du40MS8cK8oBgGuvzFp
+68SoFL8CgYAY7KvTfTKk4oWWeclmwEoe04woV7J6XuB7OnbxYpKpiGh4juN1E6wo
+n9UhAVzO6cAfK/ZuhTkDtvJSsXtQ1xElZLMIG1Yb7yikRyO73EHRUpHon3Gah+79
+MM6uZReiEdkx/hMthL45jP85hfVM89M7LYj9SBoxY2xpuzN+HmiezQ==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/tls/client_chain.pem b/tests/tls/client_chain.pem
new file mode 100644
index 0000000..e2427d7
--- /dev/null
+++ b/tests/tls/client_chain.pem
@@ -0,0 +1,67 @@
+-----BEGIN CERTIFICATE-----
+MIIDSDCCAjACCQDiqmqW0b8a7DANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMGgxCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMR0wGwYDVQQDDBRodHRwbGliMi10ZXN0LWNsaWVudDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALtXT//Esar+MDk6Gcj3KDLAyTU7jPqUx4S83LNI
+GumuBmOg7oe16SSM//NUFhCHiHr4IzeqDOD2Dq1aM661Ta2EINHvpG+9BEkCWkFr
+q4eh0pfocsvU64dtd26TCk9Q4Qqaj4t4HSYYv1hz8UFh5RNLVjgtbR7OQ37Oumcg
+jRGnelV/ck6u5BvN/fKK7p/W6hPcO9OjJDAmlVNZVPtP2ki6Lv/Q87x34X+1/Qb1
+LILUOG5mdfCTmf2tYh9bXqZmqoidTY4O7/JiPuT0+1056Ja8bDGYSFXPvvqd1aEW
+nGA22MEzd74w9A4tIieCRHlGGOSf0AGsVTmHKRf5bpQjaCsCAwEAATANBgkqhkiG
+9w0BAQUFAAOCAQEAi/X3QjpzPap9IhpHqvgFirsEepruz8lCk+Zo6A/+DP/PocII
+/8jWdIV87RDDkkaVGvWOywZyUNN1RAfrt/jGCW8xgCaSGWRab10QIW8DGhbP6FTz
+7xcBnQzcoc1gggZBcwOjkRuefW2zkgGIJo5XxHlBfo3T9nX4086Py/b+VoAmcIlm
+Y/LNHxtIyDDiOgGK9x7+IqEXQuo/p2z5oFubj/hyNJhXaYU2u7nNMXICYY/eY4vX
+GgZ44lGZ2YR7NwzqM5UHNXr7/VJzgxWwAgyZUT8DdnjkZY4wLt1JJas5n3oldBsA
++og2cMk0oOsiFAwHAwE7St4oFY0ivKDhttf7WQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDRDCCAiwCCQC5E5PSm8flUjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMGQxCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMRkwFwYDVQQDDBBodHRwbGliMi10ZXN0LUNBMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAxBKerwr0M3230xWKMvxB20+AR9SojbQIN2/8EI9pbSrj
+mlTHPFXWf02q2Ll0GPbcnSKOMnAARptVCkxEfkDGPN03Ux0jjGu2MrwZHURXM2gH
+sQn33Gj3HCreFLMxIqMFfGeB9T0VxurgUek/+bR85QBVNE9GrQfrAN8O+ScOpCOE
+Nh5rlYc/QscH/S0QJvttbGAZFP1bB/Xjltwd6fF3rZgCfTJ88B2UIcEVt+X/kc/0
+QByPPACAnCaE4cB2q+SJVEMYP6BLDVvCPRO53UC8cqsLfpKUz73two/No4PhMHwC
+PspC+wKlAD3+GWmsatz0rRysm7V0GghCGe+T5JHsGwIDAQABMA0GCSqGSIb3DQEB
+CwUAA4IBAQB4b+DWt0An4YoXj7lb/+N7FVr2m5UVyBI+bbEGI/qsql/Ixiaef69M
+jej7n5ucUx8GBql62W0c3/E3qZFfo49ngH1WC5gkKQH9V4jGZui5CUfmNE6WepQ/
+vL6eKXUp7RoJ/hWVhGm1uV3OShF+EN0t2wZttYg4lip0FjrY8tRWdjw5yu61wWVu
+WuHxTzKiHe9emjhhUBgnWRnNeYPTRs0xM2Awv5KYPq2cmrjGbSz3mYDkBpbiJUp4
+pM9g8qLmsDO2yrlVF659D08+5zkmMbyqnn84X0n3SM3Yn0ayZOmbNHiXoAzklZNP
+7xiyxMEAfVQOITsvSDG2PzbZlGGtbaka
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAu1dP/8Sxqv4wOToZyPcoMsDJNTuM+pTHhLzcs0ga6a4GY6Du
+h7XpJIz/81QWEIeIevgjN6oM4PYOrVozrrVNrYQg0e+kb70ESQJaQWurh6HSl+hy
+y9Trh213bpMKT1DhCpqPi3gdJhi/WHPxQWHlE0tWOC1tHs5Dfs66ZyCNEad6VX9y
+Tq7kG8398orun9bqE9w706MkMCaVU1lU+0/aSLou/9DzvHfhf7X9BvUsgtQ4bmZ1
+8JOZ/a1iH1tepmaqiJ1Njg7v8mI+5PT7XTnolrxsMZhIVc+++p3VoRacYDbYwTN3
+vjD0Di0iJ4JEeUYY5J/QAaxVOYcpF/lulCNoKwIDAQABAoIBAQCtzBeUYWauCni0
+bnlDXj91rjI7504nneTm+MsKq6cECQU2YjNHxXRQC1rb47NAjGwKIK+TUXf3L254
+VglCWEKC6eQEvvxfCQyzqrIOpRORlYeok+YDwTjr/5rgAxt6b78GtlLbAYiRMj45
+kf5MOMRqvOZ04XetL4+gUarOR3131L38ysReluabsCXkgIH9kZHmgOW2El4lmoHp
+CQpvMkJWyoVZvDbjLi0JoEljHGpfdWNdcllHP4dbNSQtfgG3VXXFgKqWQ8ZiY2U1
+y5SxHaeAjKHBUoGeinox/Myzan3xCysZb+gi5UxcrE7dn5lSB2/AMBymYShI/2qi
+UWq64JeJAoGBAPLtHRIcbRn53IhXGwcdFAn7JUSzKvr+gUfJiyfbw28CDWVRgTce
+JN+FzTuW92Iwm3ppBKmJ5PcZZnqt7VTtWfLvP126YaGctqZHSWiD9oK3EzDJEWIO
+trpMlJkeB+IQlvYMCiC+G+6XFBCdB7X3X1D9Y9z11Kf/arx24bB/ByVlAoGBAMVs
+YZRL9idgwgU4LMOqaPkU99de4wzYF44joZrp3Eme3dC9sdHrDtDy6OpiQu4zP+Ax
+5cws6M6txd5meAh2YwRhJBmGUYIQuhhNKQjhoeovw0tbtXO9rAHPegXPqg8xwzY9
+Ntc/WlfwM0O7ROfOq4r9erWBn0B7xspxRMH+LIZPAoGBALVW326XnbHYXRHBxEFJ
+KZ5Rxf5EqP74YVVPU/uLB5akN4+8ifK1I91fqlajWUQI+Ocl4f8VGsCCS4ekshfF
+nnHEus6ixSK5M3dom5nTeH8XXtH6JmnGhg0IAZ1TV5sfuzEsx5qtj3hJewbz0b+6
+S4LPxG47bGWEOw84xzzTdmgpAoGAGj87heTHeBrEEL+UK/tW826XOLnzw7xi/VG9
+ZYQb9mm5ocvmfTscACmbT7X6ogKMRnk7zPZXiUrPGK9U3AMpTObBTudtpLYml56C
+ixy8Uw9Ajp9Fs3qPCLqVxXoDaPu7sVVYGivhDfnwRtv54Du40MS8cK8oBgGuvzFp
+68SoFL8CgYAY7KvTfTKk4oWWeclmwEoe04woV7J6XuB7OnbxYpKpiGh4juN1E6wo
+n9UhAVzO6cAfK/ZuhTkDtvJSsXtQ1xElZLMIG1Yb7yikRyO73EHRUpHon3Gah+79
+MM6uZReiEdkx/hMthL45jP85hfVM89M7LYj9SBoxY2xpuzN+HmiezQ==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/tls/client_encrypted.crt b/tests/tls/client_encrypted.crt
new file mode 100644
index 0000000..6301dd5
--- /dev/null
+++ b/tests/tls/client_encrypted.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjQCCQDiqmqW0b8a7TANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMGwxCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMSEwHwYDVQQDDBhodHRwbGliMi10ZXN0LWNsaWVudC1lbmMwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7V0//xLGq/jA5OhnI9ygywMk1O4z6lMeE
+vNyzSBrprgZjoO6HtekkjP/zVBYQh4h6+CM3qgzg9g6tWjOutU2thCDR76RvvQRJ
+AlpBa6uHodKX6HLL1OuHbXdukwpPUOEKmo+LeB0mGL9Yc/FBYeUTS1Y4LW0ezkN+
+zrpnII0Rp3pVf3JOruQbzf3yiu6f1uoT3DvToyQwJpVTWVT7T9pIui7/0PO8d+F/
+tf0G9SyC1DhuZnXwk5n9rWIfW16mZqqInU2ODu/yYj7k9PtdOeiWvGwxmEhVz776
+ndWhFpxgNtjBM3e+MPQOLSIngkR5Rhjkn9ABrFU5hykX+W6UI2grAgMBAAEwDQYJ
+KoZIhvcNAQEFBQADggEBAKiBTMX/FwUusM4PIsmGqXisOBo6LEf2YtfzQrtxw4eY
+eWeKsi3aM2GquCqh0R7loEW+yQoxPEBaNeOBeN3v8sdhTu+9NjK31tWCYr7jvEa5
+TqjlUUMD1176YBQ8axI51lVcaBIoRdvf8nXm7idvp82eBBXQtnREjd8oKcEz7v4x
+ECJ+RWGJTEIWXq3fuVvBAJeopNVz+Utt61DCxziKbu+ndv0kQeXZ7KPFiBnARcEi
+7GvTeHUA0cbpHrNY0ob7ozcjGiPwW5HPi+DYZYfRm2PqI9vowmKt9By+8Uz03K3L
+XMZkGJ28uoo37Rbjs8+pMVDdHoUrm6hZTkw5XGgsA6I=
+-----END CERTIFICATE-----
diff --git a/tests/tls/client_encrypted.key b/tests/tls/client_encrypted.key
new file mode 100644
index 0000000..747b1fb
--- /dev/null
+++ b/tests/tls/client_encrypted.key
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,84B979083590A865DF5700455C62AEAF
+
+Q2vQ9vhIV6C3aL4Uk9XIqtsgdN9VN+Ep3mDY7FKbycrcZgDnXbZ5ys50cF8TyJZx
+ITYV5ms9MjjUi1cj3hhUfQSu1bJ+gKGWpxHD1Cxub9XLcGJrjfGomO0r5lNe4YZf
+of4qc+XpoY+dZTHyFuKn44LiynAG0esqexNc7BcqbGSw6cjm6exvDWbmz0svy2RO
+TTfea6oh2+CiHUWqIbjIEoK/tJXXzUq9LwEMGdvyQX5KJDLmsWdxejnqsMmSDBH4
+Bm5ax7AIcO4SE1AdYzzDNI11bZXyQu1UWtgL3D4sDo4iwbHzq9CFXSI21tgwW3WT
+LK3Pbt6IAJctwNZozwWDT9c0r226WZv/lYk4bkhvGDuLFMgMwjmW24i4VuU7giiv
+sna6jJNk0gQ0XCWiIgtjhrH8noefj07SPb5miXQjhbCvz3F5TqGobWS6xSCX+KMF
+LfjJdG5f6QHfnzm+fmIuc/JdcHMCf/2q3bvHuTOoYoJXMY9kcKS5PXckADsQkzmM
+2AeoegO8x25ClHRBSOgwee9tkrD5VTX3uK92/rnnt3KUiS8Rdf6jbYpJg2rvCTAd
+NF6GCCp+YNw3o7a5IehhhwXWKh0BK02e6IPU9Z6KFTX8LGFOPy8BIghyNILPpfeC
+Ir2WWr8asfBLvwy5Pj6Wzfvu6EPC6H0RIxzhTTfaZ0KZR3ZN1Fwd49FNql50UeHH
+M5x1vzi4JuRak4z22+uL3GEdkBs/PAoVMfctHTMEiDrH3mgYrEj5yhCgUjJhMP6w
+dk6gUaXcPZ6JymUtrZsjcc3cXDD6Vn6i1SH2e9rMf9/QBKGZH3ufkBJ9s0HVsacq
+bHg63+BGhmLXFcnEimGVY86py8TfQfuirFOEURoYJMUTCl046GV11AJeF0L+EOUH
+nst2Z9l86vCH+7sdVPsVG8jFaeOunr50VkCu/ephGsUSHjNRVagsVOSojknMyNki
+D13Oolykusq9zuHMBBRceNRoAHLSk02aHiuwlvQmAVaSJxIebtFZJmG1asXMSK/M
+iYjLNb7P+fjpvxYwk1bxXTyTToHLsmvDeGyMj3EhrEh83o/Xoo+bac7OW+l18wWw
+N5b5Jcrj0e/O08+dJ5UBXGJ36V5WENTdOjctFTRtZhggkvB1QBZLa1g9khxz0VEw
+q0yGYCmjMK2YxpJhH+FAprUM+0Ei9IxIjDY295CEOrKkeJGb9+A8CWLoCi8z0zAc
+vQU/uaQsK1BKamKGmu7x+xCyoLSprzNlPjipJttUqkE00kFX9sBJZXW/rI+fQRJI
+sYn9UqNp/ay00uswwcoFLAnX7YdKiJrB5jaR5oyLFXpIPQa76594iRcf+9XkD9Ca
+KlscDNVnfSW7bkp7LJ+Rm6+OKWCSjglL8uIyBEzoNFCyiEnnzebE+jwvVBaDlbPd
+xMwMzU8vHsb9dYd7RMdd+YBIxngzyJiVl6Zpfy9B74vhL+ndyWKZg2Rsunlrcms7
+YIVg/LuAfgH4jXg38yzHHEkArGZg5TFaGUD8rJwMIPil6LOQ4D+jK8/fcV9bhBuH
+LzBJ4gtPwUnvYqsaiIAeGi2EVllW0Ka+aTTzM1Yascl2q9WROvutAT0zz0M6smpO
+-----END RSA PRIVATE KEY-----
diff --git a/tests/tls/client_encrypted.pem b/tests/tls/client_encrypted.pem
new file mode 100644
index 0000000..578aa7f
--- /dev/null
+++ b/tests/tls/client_encrypted.pem
@@ -0,0 +1,50 @@
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjQCCQDiqmqW0b8a7TANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMGwxCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMSEwHwYDVQQDDBhodHRwbGliMi10ZXN0LWNsaWVudC1lbmMwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7V0//xLGq/jA5OhnI9ygywMk1O4z6lMeE
+vNyzSBrprgZjoO6HtekkjP/zVBYQh4h6+CM3qgzg9g6tWjOutU2thCDR76RvvQRJ
+AlpBa6uHodKX6HLL1OuHbXdukwpPUOEKmo+LeB0mGL9Yc/FBYeUTS1Y4LW0ezkN+
+zrpnII0Rp3pVf3JOruQbzf3yiu6f1uoT3DvToyQwJpVTWVT7T9pIui7/0PO8d+F/
+tf0G9SyC1DhuZnXwk5n9rWIfW16mZqqInU2ODu/yYj7k9PtdOeiWvGwxmEhVz776
+ndWhFpxgNtjBM3e+MPQOLSIngkR5Rhjkn9ABrFU5hykX+W6UI2grAgMBAAEwDQYJ
+KoZIhvcNAQEFBQADggEBAKiBTMX/FwUusM4PIsmGqXisOBo6LEf2YtfzQrtxw4eY
+eWeKsi3aM2GquCqh0R7loEW+yQoxPEBaNeOBeN3v8sdhTu+9NjK31tWCYr7jvEa5
+TqjlUUMD1176YBQ8axI51lVcaBIoRdvf8nXm7idvp82eBBXQtnREjd8oKcEz7v4x
+ECJ+RWGJTEIWXq3fuVvBAJeopNVz+Utt61DCxziKbu+ndv0kQeXZ7KPFiBnARcEi
+7GvTeHUA0cbpHrNY0ob7ozcjGiPwW5HPi+DYZYfRm2PqI9vowmKt9By+8Uz03K3L
+XMZkGJ28uoo37Rbjs8+pMVDdHoUrm6hZTkw5XGgsA6I=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,84B979083590A865DF5700455C62AEAF
+
+Q2vQ9vhIV6C3aL4Uk9XIqtsgdN9VN+Ep3mDY7FKbycrcZgDnXbZ5ys50cF8TyJZx
+ITYV5ms9MjjUi1cj3hhUfQSu1bJ+gKGWpxHD1Cxub9XLcGJrjfGomO0r5lNe4YZf
+of4qc+XpoY+dZTHyFuKn44LiynAG0esqexNc7BcqbGSw6cjm6exvDWbmz0svy2RO
+TTfea6oh2+CiHUWqIbjIEoK/tJXXzUq9LwEMGdvyQX5KJDLmsWdxejnqsMmSDBH4
+Bm5ax7AIcO4SE1AdYzzDNI11bZXyQu1UWtgL3D4sDo4iwbHzq9CFXSI21tgwW3WT
+LK3Pbt6IAJctwNZozwWDT9c0r226WZv/lYk4bkhvGDuLFMgMwjmW24i4VuU7giiv
+sna6jJNk0gQ0XCWiIgtjhrH8noefj07SPb5miXQjhbCvz3F5TqGobWS6xSCX+KMF
+LfjJdG5f6QHfnzm+fmIuc/JdcHMCf/2q3bvHuTOoYoJXMY9kcKS5PXckADsQkzmM
+2AeoegO8x25ClHRBSOgwee9tkrD5VTX3uK92/rnnt3KUiS8Rdf6jbYpJg2rvCTAd
+NF6GCCp+YNw3o7a5IehhhwXWKh0BK02e6IPU9Z6KFTX8LGFOPy8BIghyNILPpfeC
+Ir2WWr8asfBLvwy5Pj6Wzfvu6EPC6H0RIxzhTTfaZ0KZR3ZN1Fwd49FNql50UeHH
+M5x1vzi4JuRak4z22+uL3GEdkBs/PAoVMfctHTMEiDrH3mgYrEj5yhCgUjJhMP6w
+dk6gUaXcPZ6JymUtrZsjcc3cXDD6Vn6i1SH2e9rMf9/QBKGZH3ufkBJ9s0HVsacq
+bHg63+BGhmLXFcnEimGVY86py8TfQfuirFOEURoYJMUTCl046GV11AJeF0L+EOUH
+nst2Z9l86vCH+7sdVPsVG8jFaeOunr50VkCu/ephGsUSHjNRVagsVOSojknMyNki
+D13Oolykusq9zuHMBBRceNRoAHLSk02aHiuwlvQmAVaSJxIebtFZJmG1asXMSK/M
+iYjLNb7P+fjpvxYwk1bxXTyTToHLsmvDeGyMj3EhrEh83o/Xoo+bac7OW+l18wWw
+N5b5Jcrj0e/O08+dJ5UBXGJ36V5WENTdOjctFTRtZhggkvB1QBZLa1g9khxz0VEw
+q0yGYCmjMK2YxpJhH+FAprUM+0Ei9IxIjDY295CEOrKkeJGb9+A8CWLoCi8z0zAc
+vQU/uaQsK1BKamKGmu7x+xCyoLSprzNlPjipJttUqkE00kFX9sBJZXW/rI+fQRJI
+sYn9UqNp/ay00uswwcoFLAnX7YdKiJrB5jaR5oyLFXpIPQa76594iRcf+9XkD9Ca
+KlscDNVnfSW7bkp7LJ+Rm6+OKWCSjglL8uIyBEzoNFCyiEnnzebE+jwvVBaDlbPd
+xMwMzU8vHsb9dYd7RMdd+YBIxngzyJiVl6Zpfy9B74vhL+ndyWKZg2Rsunlrcms7
+YIVg/LuAfgH4jXg38yzHHEkArGZg5TFaGUD8rJwMIPil6LOQ4D+jK8/fcV9bhBuH
+LzBJ4gtPwUnvYqsaiIAeGi2EVllW0Ka+aTTzM1Yascl2q9WROvutAT0zz0M6smpO
+-----END RSA PRIVATE KEY-----
diff --git a/tests/tls/server.crt b/tests/tls/server.crt
new file mode 100644
index 0000000..e29537a
--- /dev/null
+++ b/tests/tls/server.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDPTCCAiUCCQDiqmqW0b8a7jANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMF0xCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCu9WJEGBARCdyqt+/ip53p+S7N0lKGv0w75LfqVldIQGYMAf9xSMcb
+PSTds9rYq8g7XaehNJ628w0hWnHDEU1x28ULMlL22p91inEmWANyi+flCopgUgPe
+sQkc+kcXUVv+/Of8eclIXCxni67J8fK7nH/48ML+qdWes9nrtMRB5z/2mC7sc53x
+4faJdOpCx13CaHvYPIHJfT7d7haTDBGLVCSC90tB5WvK7E1hIoIddqwrthWopRcS
+iSmQfJZmKPgPTtnVX1r3meLEVcdLcZ9OO8r1buUrBdB5Z25K5r0nCYzAk4AyJ1pq
+8tjajMwsunbLFOQ6X9DNRCVOG0XDpu0ZAgMBAAEwDQYJKoZIhvcNAQEFBQADggEB
+AGVLrbXSANOMZpWevPJUMoZMJ4H26q9+tJ4kVi36ufQOaRBJ19lc73wt/5CL1Zzx
+uHBWJSMkUXn3CIXjyV9QsP3YF94yG9LghIKI/0Z+DK5j1TomplS13DZ+JuUIbogZ
+gwTXy/EuAxynUO+iyLD3c7/rJO94luWd2Ct9ljv9Kza7LxeEjKloBeGxddWgeU7/
+OdkPzLmvCxAsK/Wk4LAKG0p3ZwIqLdusMl6TBpStntLhh98M5xQXoozmRo8bBlp5
+kjItngdSWKWyXalw93SGEoPhe7u6fAxMBBuEpAtF5DS+mzTHB/wbJz1FuD3f973J
+4MeDFTIHtkTp/lYrbSmWvzU=
+-----END CERTIFICATE-----
diff --git a/tests/tls/server.key b/tests/tls/server.key
new file mode 100644
index 0000000..9a1f1dd
--- /dev/null
+++ b/tests/tls/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArvViRBgQEQncqrfv4qed6fkuzdJShr9MO+S36lZXSEBmDAH/
+cUjHGz0k3bPa2KvIO12noTSetvMNIVpxwxFNcdvFCzJS9tqfdYpxJlgDcovn5QqK
+YFID3rEJHPpHF1Fb/vzn/HnJSFwsZ4uuyfHyu5x/+PDC/qnVnrPZ67TEQec/9pgu
+7HOd8eH2iXTqQsddwmh72DyByX0+3e4WkwwRi1QkgvdLQeVryuxNYSKCHXasK7YV
+qKUXEokpkHyWZij4D07Z1V9a95nixFXHS3GfTjvK9W7lKwXQeWduSua9JwmMwJOA
+MidaavLY2ozMLLp2yxTkOl/QzUQlThtFw6btGQIDAQABAoIBAFyq69lVRW1A4/go
+ZI6QaTu8F+Y8OCnWuPIgOqmMAb7rHSHPDRVbjtoGkLg8wvVwRyXqfRcNX+NW6OV5
+mjfPuk1MMhm0Fe1Z7ou7QCMnCuxo3fKamqBZ0GLrMgB/L5hSJ3/vRJCdkNcauwo9
+Gd8sn3xvb/jSzPVFzze32vzVSf39NSxcLRGnMb1Y/1U5jA7Bc+XbPb1fvWJ9ZJec
+o5wz0VLNWgYEt3dnvWZOf1ONx50ROwlKFOPhdYg/IQxzkWxOu6ZcleJrJsf5eCvj
+o2Ogm6bWMhTO73MYjHD0+hR6xl3g/vIBe+N4e4LmnI7BBQRe9SKrazqBt35/5Zs5
+IKFDPAECgYEA5M2sI7dnbHK1Ur+AIGEan3sWtbXOvg0xihCXRojE5oSKC5BFf5ny
+LvZ/VcszhjFv6ruUunXtN8qXJ8n3QQQXyuEeKpkGCvJqE8inREKSTexLj06niU1r
+w4XyyEvIckz42sv2k0mwH7qCIwAMkAQtqnM/ue2aCuyKKP5OLqqlSskCgYEAw8E/
+3zWbLwtWb6VtyaSv9/xHL71SXkxpY9FIc7MFhQLnvR6ZgZS42fXopeo04Byx6cZ3
+3QM8UPYE77H0ch3r1HdZIIXLw2aX8SnZtHtchU50cdmhCV51D6dk/bftxgncFzxp
+nHRwoPhJTXF/y0+jNP8m5Tn5YgxzJ9Sec7WuZ9ECgYEAnFqURMADNA/bKxXkR7wz
+xkIGDdyU0DkR3mhiB/hUnbZ641YOuBkKb99Qut8mcZB9C2puQ1Fs7tBJpQ4WId7b
+J2/Y/oEdqQNpS+W1sCbR9eAA7ohwYpp+htmVRBzNeJZzBImXEaWsbrI0VhilfRDt
+5+nj5Xmh588mxsapxKgmVkkCgYEAhJiaEy/UdgFQA0AjJbsQFwIjlgq/iHBp0tso
+IHba/kYBgvD/Oe7rZ3hSplAGkOfe+2McPfC7InwCy/nWgpYR8FEHZig65ZjQwuJ+
+POpyuTlzVsr7ccUxtfDFT7cOsF5tXq/lOb0FrYOA45xF3AmNm5BZYFvsuKWGOyyi
+R+6AvIECgYAXQ8Ud5GX0aXm8cRbwLStamooBreeCKQ9plLnXdqiwkjoeqhdHcWzh
+M4Cws86fbRSWESqvY3NVJCA5Na9HN0/LH/UlxN8tfrEfv1al/2UXN8zIL1a1uVfK
+H9CtL9znc7mJKBODBxDXgdC+QHMdtGwGU5QYTVwlPEBbdM/2JwgBfw==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/tls/server.pem b/tests/tls/server.pem
new file mode 100644
index 0000000..83277c1
--- /dev/null
+++ b/tests/tls/server.pem
@@ -0,0 +1,47 @@
+-----BEGIN CERTIFICATE-----
+MIIDPTCCAiUCCQDiqmqW0b8a7jANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMF0xCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCu9WJEGBARCdyqt+/ip53p+S7N0lKGv0w75LfqVldIQGYMAf9xSMcb
+PSTds9rYq8g7XaehNJ628w0hWnHDEU1x28ULMlL22p91inEmWANyi+flCopgUgPe
+sQkc+kcXUVv+/Of8eclIXCxni67J8fK7nH/48ML+qdWes9nrtMRB5z/2mC7sc53x
+4faJdOpCx13CaHvYPIHJfT7d7haTDBGLVCSC90tB5WvK7E1hIoIddqwrthWopRcS
+iSmQfJZmKPgPTtnVX1r3meLEVcdLcZ9OO8r1buUrBdB5Z25K5r0nCYzAk4AyJ1pq
+8tjajMwsunbLFOQ6X9DNRCVOG0XDpu0ZAgMBAAEwDQYJKoZIhvcNAQEFBQADggEB
+AGVLrbXSANOMZpWevPJUMoZMJ4H26q9+tJ4kVi36ufQOaRBJ19lc73wt/5CL1Zzx
+uHBWJSMkUXn3CIXjyV9QsP3YF94yG9LghIKI/0Z+DK5j1TomplS13DZ+JuUIbogZ
+gwTXy/EuAxynUO+iyLD3c7/rJO94luWd2Ct9ljv9Kza7LxeEjKloBeGxddWgeU7/
+OdkPzLmvCxAsK/Wk4LAKG0p3ZwIqLdusMl6TBpStntLhh98M5xQXoozmRo8bBlp5
+kjItngdSWKWyXalw93SGEoPhe7u6fAxMBBuEpAtF5DS+mzTHB/wbJz1FuD3f973J
+4MeDFTIHtkTp/lYrbSmWvzU=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArvViRBgQEQncqrfv4qed6fkuzdJShr9MO+S36lZXSEBmDAH/
+cUjHGz0k3bPa2KvIO12noTSetvMNIVpxwxFNcdvFCzJS9tqfdYpxJlgDcovn5QqK
+YFID3rEJHPpHF1Fb/vzn/HnJSFwsZ4uuyfHyu5x/+PDC/qnVnrPZ67TEQec/9pgu
+7HOd8eH2iXTqQsddwmh72DyByX0+3e4WkwwRi1QkgvdLQeVryuxNYSKCHXasK7YV
+qKUXEokpkHyWZij4D07Z1V9a95nixFXHS3GfTjvK9W7lKwXQeWduSua9JwmMwJOA
+MidaavLY2ozMLLp2yxTkOl/QzUQlThtFw6btGQIDAQABAoIBAFyq69lVRW1A4/go
+ZI6QaTu8F+Y8OCnWuPIgOqmMAb7rHSHPDRVbjtoGkLg8wvVwRyXqfRcNX+NW6OV5
+mjfPuk1MMhm0Fe1Z7ou7QCMnCuxo3fKamqBZ0GLrMgB/L5hSJ3/vRJCdkNcauwo9
+Gd8sn3xvb/jSzPVFzze32vzVSf39NSxcLRGnMb1Y/1U5jA7Bc+XbPb1fvWJ9ZJec
+o5wz0VLNWgYEt3dnvWZOf1ONx50ROwlKFOPhdYg/IQxzkWxOu6ZcleJrJsf5eCvj
+o2Ogm6bWMhTO73MYjHD0+hR6xl3g/vIBe+N4e4LmnI7BBQRe9SKrazqBt35/5Zs5
+IKFDPAECgYEA5M2sI7dnbHK1Ur+AIGEan3sWtbXOvg0xihCXRojE5oSKC5BFf5ny
+LvZ/VcszhjFv6ruUunXtN8qXJ8n3QQQXyuEeKpkGCvJqE8inREKSTexLj06niU1r
+w4XyyEvIckz42sv2k0mwH7qCIwAMkAQtqnM/ue2aCuyKKP5OLqqlSskCgYEAw8E/
+3zWbLwtWb6VtyaSv9/xHL71SXkxpY9FIc7MFhQLnvR6ZgZS42fXopeo04Byx6cZ3
+3QM8UPYE77H0ch3r1HdZIIXLw2aX8SnZtHtchU50cdmhCV51D6dk/bftxgncFzxp
+nHRwoPhJTXF/y0+jNP8m5Tn5YgxzJ9Sec7WuZ9ECgYEAnFqURMADNA/bKxXkR7wz
+xkIGDdyU0DkR3mhiB/hUnbZ641YOuBkKb99Qut8mcZB9C2puQ1Fs7tBJpQ4WId7b
+J2/Y/oEdqQNpS+W1sCbR9eAA7ohwYpp+htmVRBzNeJZzBImXEaWsbrI0VhilfRDt
+5+nj5Xmh588mxsapxKgmVkkCgYEAhJiaEy/UdgFQA0AjJbsQFwIjlgq/iHBp0tso
+IHba/kYBgvD/Oe7rZ3hSplAGkOfe+2McPfC7InwCy/nWgpYR8FEHZig65ZjQwuJ+
+POpyuTlzVsr7ccUxtfDFT7cOsF5tXq/lOb0FrYOA45xF3AmNm5BZYFvsuKWGOyyi
+R+6AvIECgYAXQ8Ud5GX0aXm8cRbwLStamooBreeCKQ9plLnXdqiwkjoeqhdHcWzh
+M4Cws86fbRSWESqvY3NVJCA5Na9HN0/LH/UlxN8tfrEfv1al/2UXN8zIL1a1uVfK
+H9CtL9znc7mJKBODBxDXgdC+QHMdtGwGU5QYTVwlPEBbdM/2JwgBfw==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/tls/server_chain.pem b/tests/tls/server_chain.pem
new file mode 100644
index 0000000..d590275
--- /dev/null
+++ b/tests/tls/server_chain.pem
@@ -0,0 +1,67 @@
+-----BEGIN CERTIFICATE-----
+MIIDPTCCAiUCCQDiqmqW0b8a7jANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMF0xCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCu9WJEGBARCdyqt+/ip53p+S7N0lKGv0w75LfqVldIQGYMAf9xSMcb
+PSTds9rYq8g7XaehNJ628w0hWnHDEU1x28ULMlL22p91inEmWANyi+flCopgUgPe
+sQkc+kcXUVv+/Of8eclIXCxni67J8fK7nH/48ML+qdWes9nrtMRB5z/2mC7sc53x
+4faJdOpCx13CaHvYPIHJfT7d7haTDBGLVCSC90tB5WvK7E1hIoIddqwrthWopRcS
+iSmQfJZmKPgPTtnVX1r3meLEVcdLcZ9OO8r1buUrBdB5Z25K5r0nCYzAk4AyJ1pq
+8tjajMwsunbLFOQ6X9DNRCVOG0XDpu0ZAgMBAAEwDQYJKoZIhvcNAQEFBQADggEB
+AGVLrbXSANOMZpWevPJUMoZMJ4H26q9+tJ4kVi36ufQOaRBJ19lc73wt/5CL1Zzx
+uHBWJSMkUXn3CIXjyV9QsP3YF94yG9LghIKI/0Z+DK5j1TomplS13DZ+JuUIbogZ
+gwTXy/EuAxynUO+iyLD3c7/rJO94luWd2Ct9ljv9Kza7LxeEjKloBeGxddWgeU7/
+OdkPzLmvCxAsK/Wk4LAKG0p3ZwIqLdusMl6TBpStntLhh98M5xQXoozmRo8bBlp5
+kjItngdSWKWyXalw93SGEoPhe7u6fAxMBBuEpAtF5DS+mzTHB/wbJz1FuD3f973J
+4MeDFTIHtkTp/lYrbSmWvzU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDRDCCAiwCCQC5E5PSm8flUjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJa
+WjEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEWMBQGA1UECgwNaHR0cGxpYjItdGVz
+dDEKMAgGA1UECwwBLjEZMBcGA1UEAwwQaHR0cGxpYjItdGVzdC1DQTAeFw0xOTA5
+MjYxNTAzMzRaFw0yOTA5MjMxNTAzMzRaMGQxCzAJBgNVBAYTAlpaMQowCAYDVQQI
+DAEuMQowCAYDVQQHDAEuMRYwFAYDVQQKDA1odHRwbGliMi10ZXN0MQowCAYDVQQL
+DAEuMRkwFwYDVQQDDBBodHRwbGliMi10ZXN0LUNBMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAxBKerwr0M3230xWKMvxB20+AR9SojbQIN2/8EI9pbSrj
+mlTHPFXWf02q2Ll0GPbcnSKOMnAARptVCkxEfkDGPN03Ux0jjGu2MrwZHURXM2gH
+sQn33Gj3HCreFLMxIqMFfGeB9T0VxurgUek/+bR85QBVNE9GrQfrAN8O+ScOpCOE
+Nh5rlYc/QscH/S0QJvttbGAZFP1bB/Xjltwd6fF3rZgCfTJ88B2UIcEVt+X/kc/0
+QByPPACAnCaE4cB2q+SJVEMYP6BLDVvCPRO53UC8cqsLfpKUz73two/No4PhMHwC
+PspC+wKlAD3+GWmsatz0rRysm7V0GghCGe+T5JHsGwIDAQABMA0GCSqGSIb3DQEB
+CwUAA4IBAQB4b+DWt0An4YoXj7lb/+N7FVr2m5UVyBI+bbEGI/qsql/Ixiaef69M
+jej7n5ucUx8GBql62W0c3/E3qZFfo49ngH1WC5gkKQH9V4jGZui5CUfmNE6WepQ/
+vL6eKXUp7RoJ/hWVhGm1uV3OShF+EN0t2wZttYg4lip0FjrY8tRWdjw5yu61wWVu
+WuHxTzKiHe9emjhhUBgnWRnNeYPTRs0xM2Awv5KYPq2cmrjGbSz3mYDkBpbiJUp4
+pM9g8qLmsDO2yrlVF659D08+5zkmMbyqnn84X0n3SM3Yn0ayZOmbNHiXoAzklZNP
+7xiyxMEAfVQOITsvSDG2PzbZlGGtbaka
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArvViRBgQEQncqrfv4qed6fkuzdJShr9MO+S36lZXSEBmDAH/
+cUjHGz0k3bPa2KvIO12noTSetvMNIVpxwxFNcdvFCzJS9tqfdYpxJlgDcovn5QqK
+YFID3rEJHPpHF1Fb/vzn/HnJSFwsZ4uuyfHyu5x/+PDC/qnVnrPZ67TEQec/9pgu
+7HOd8eH2iXTqQsddwmh72DyByX0+3e4WkwwRi1QkgvdLQeVryuxNYSKCHXasK7YV
+qKUXEokpkHyWZij4D07Z1V9a95nixFXHS3GfTjvK9W7lKwXQeWduSua9JwmMwJOA
+MidaavLY2ozMLLp2yxTkOl/QzUQlThtFw6btGQIDAQABAoIBAFyq69lVRW1A4/go
+ZI6QaTu8F+Y8OCnWuPIgOqmMAb7rHSHPDRVbjtoGkLg8wvVwRyXqfRcNX+NW6OV5
+mjfPuk1MMhm0Fe1Z7ou7QCMnCuxo3fKamqBZ0GLrMgB/L5hSJ3/vRJCdkNcauwo9
+Gd8sn3xvb/jSzPVFzze32vzVSf39NSxcLRGnMb1Y/1U5jA7Bc+XbPb1fvWJ9ZJec
+o5wz0VLNWgYEt3dnvWZOf1ONx50ROwlKFOPhdYg/IQxzkWxOu6ZcleJrJsf5eCvj
+o2Ogm6bWMhTO73MYjHD0+hR6xl3g/vIBe+N4e4LmnI7BBQRe9SKrazqBt35/5Zs5
+IKFDPAECgYEA5M2sI7dnbHK1Ur+AIGEan3sWtbXOvg0xihCXRojE5oSKC5BFf5ny
+LvZ/VcszhjFv6ruUunXtN8qXJ8n3QQQXyuEeKpkGCvJqE8inREKSTexLj06niU1r
+w4XyyEvIckz42sv2k0mwH7qCIwAMkAQtqnM/ue2aCuyKKP5OLqqlSskCgYEAw8E/
+3zWbLwtWb6VtyaSv9/xHL71SXkxpY9FIc7MFhQLnvR6ZgZS42fXopeo04Byx6cZ3
+3QM8UPYE77H0ch3r1HdZIIXLw2aX8SnZtHtchU50cdmhCV51D6dk/bftxgncFzxp
+nHRwoPhJTXF/y0+jNP8m5Tn5YgxzJ9Sec7WuZ9ECgYEAnFqURMADNA/bKxXkR7wz
+xkIGDdyU0DkR3mhiB/hUnbZ641YOuBkKb99Qut8mcZB9C2puQ1Fs7tBJpQ4WId7b
+J2/Y/oEdqQNpS+W1sCbR9eAA7ohwYpp+htmVRBzNeJZzBImXEaWsbrI0VhilfRDt
+5+nj5Xmh588mxsapxKgmVkkCgYEAhJiaEy/UdgFQA0AjJbsQFwIjlgq/iHBp0tso
+IHba/kYBgvD/Oe7rZ3hSplAGkOfe+2McPfC7InwCy/nWgpYR8FEHZig65ZjQwuJ+
+POpyuTlzVsr7ccUxtfDFT7cOsF5tXq/lOb0FrYOA45xF3AmNm5BZYFvsuKWGOyyi
+R+6AvIECgYAXQ8Ud5GX0aXm8cRbwLStamooBreeCKQ9plLnXdqiwkjoeqhdHcWzh
+M4Cws86fbRSWESqvY3NVJCA5Na9HN0/LH/UlxN8tfrEfv1al/2UXN8zIL1a1uVfK
+H9CtL9znc7mJKBODBxDXgdC+QHMdtGwGU5QYTVwlPEBbdM/2JwgBfw==
+-----END RSA PRIVATE KEY-----