Merge pull request #1014 from ikoz/master

New option: Add server certs to client chain
This commit is contained in:
Maximilian Hils 2016-03-17 02:28:00 +01:00
commit 983b0dd4f6
7 changed files with 86 additions and 6 deletions

View File

@ -434,6 +434,12 @@ def proxy_ssl_options(parser):
action="store_true", dest="no_upstream_cert",
help="Don't connect to upstream server to look up certificate details."
)
group.add_argument(
"--add-upstream-certs-to-client-chain", default=False,
action="store_true", dest="add_upstream_certs_to_client_chain",
help="Add all certificates of the upstream server to the certificate chain "
"that will be served to the proxy client, as extras."
)
group.add_argument(
"--verify-upstream-cert", default=False,
action="store_true", dest="ssl_verify_upstream_cert",

View File

@ -432,6 +432,11 @@ class TlsLayer(Layer):
self.log("Establish TLS with client", "debug")
cert, key, chain_file = self._find_cert()
if self.config.add_upstream_certs_to_client_chain:
extra_certs = self.server_conn.server_certs
else:
extra_certs = None
try:
self.client_conn.convert_to_ssl(
cert, key,
@ -441,6 +446,7 @@ class TlsLayer(Layer):
dhparams=self.config.certstore.dhparams,
chain_file=chain_file,
alpn_select_callback=self.__alpn_select_callback,
extra_chain_certs = extra_certs,
)
# Some TLS clients will not fail the handshake,
# but will immediately throw an "unexpected eof" error on the first read.

View File

@ -67,6 +67,7 @@ class ProxyConfig:
ssl_verify_upstream_cert=False,
ssl_verify_upstream_trusted_cadir=None,
ssl_verify_upstream_trusted_ca=None,
add_upstream_certs_to_client_chain=False,
):
self.host = host
self.port = port
@ -107,6 +108,7 @@ class ProxyConfig:
self.openssl_verification_mode_server = SSL.VERIFY_NONE
self.openssl_trusted_cadir_server = ssl_verify_upstream_trusted_cadir
self.openssl_trusted_ca_server = ssl_verify_upstream_trusted_ca
self.add_upstream_certs_to_client_chain = add_upstream_certs_to_client_chain
def process_proxy_options(parser, options):
@ -136,14 +138,26 @@ def process_proxy_options(parser, options):
"Transparent, SOCKS5, reverse and upstream proxy mode "
"are mutually exclusive. Read the docs on proxy modes to understand why."
)
if options.add_upstream_certs_to_client_chain and options.no_upstream_cert:
return parser.error(
"The no-upstream-cert and add-upstream-certs-to-client-chain "
"options are mutually exclusive. If no-upstream-cert is enabled "
"then the upstream certificate is not retrieved before generating "
"the client certificate chain."
)
if options.add_upstream_certs_to_client_chain and options.ssl_verify_upstream_cert:
return parser.error(
"The verify-upstream-cert and add-upstream-certs-to-client-chain "
"options are mutually exclusive. If upstream certificates are verified "
"then extra upstream certificates are not available for inclusion "
"to the client chain."
)
if options.clientcerts:
options.clientcerts = os.path.expanduser(options.clientcerts)
if not os.path.exists(options.clientcerts):
return parser.error(
"Client certificate path does not exist: %s" % options.clientcerts
"Client certificate path does not exist: %s" % options.clientcerts
)
if options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd:
if options.transparent_proxy:
@ -206,5 +220,6 @@ def process_proxy_options(parser, options):
ssl_version_server=options.ssl_version_server,
ssl_verify_upstream_cert=options.ssl_verify_upstream_cert,
ssl_verify_upstream_trusted_cadir=options.ssl_verify_upstream_trusted_cadir,
ssl_verify_upstream_trusted_ca=options.ssl_verify_upstream_trusted_ca
ssl_verify_upstream_trusted_ca=options.ssl_verify_upstream_trusted_ca,
add_upstream_certs_to_client_chain=options.add_upstream_certs_to_client_chain,
)

View File

@ -586,6 +586,7 @@ class TCPClient(_Connection):
self.address = address
self.source_address = source_address
self.cert = None
self.server_certs = []
self.ssl_verification_error = None
self.sni = None
@ -670,6 +671,10 @@ class TCPClient(_Connection):
self.cert = certutils.SSLCert(self.connection.get_peer_certificate())
# Keep all server certificates in a list
for i in self.connection.get_peer_cert_chain():
self.server_certs.append(certutils.SSLCert(i))
# Validate TLS Hostname
try:
crt = dict(
@ -737,6 +742,7 @@ class BaseHandler(_Connection):
request_client_cert=None,
chain_file=None,
dhparams=None,
extra_chain_certs=None,
**sslctx_kwargs):
"""
cert: A certutils.SSLCert object or the path to a certificate
@ -772,6 +778,10 @@ class BaseHandler(_Connection):
else:
context.use_certificate_chain_file(cert)
if extra_chain_certs:
for i in extra_chain_certs:
context.add_extra_chain_cert(i.x509)
if handle_sni:
# SNI callback happens during do_handshake()
context.set_tlsext_servername_callback(handle_sni)

View File

@ -42,7 +42,8 @@ class SSLInfo(object):
"Cipher: %s, %s bit, %s" % self.cipher,
"SSL certificate chain:"
]
for i in self.certchain:
for n,i in enumerate(self.certchain):
parts.append(" Certificate [%s]" % n)
parts.append("\tSubject: ")
for cn in i.get_subject().get_components():
parts.append("\t\t%s=%s" % cn)
@ -69,7 +70,7 @@ class SSLInfo(object):
s = certutils.SSLCert(i)
if s.altnames:
parts.append("\tSANs: %s" % " ".join(s.altnames))
return "\n".join(parts)
return "\n".join(parts)

View File

@ -999,3 +999,43 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest):
# (both terminated)
# nothing happened here
assert self.chain[1].tmaster.state.flow_count() == 2
class AddUpstreamCertsToClientChainMixin:
ssl = True
servercert = tutils.test_data.path("data/trusted-server.crt")
ssloptions = pathod.SSLOptions(
cn="trusted-cert",
certs=[
("trusted-cert", servercert)
]
)
def test_add_upstream_certs_to_client_chain(self):
with open(self.servercert, "rb") as f:
d = f.read()
upstreamCert = SSLCert.from_pem(d)
p = self.pathoc()
upstream_cert_found_in_client_chain = False
for receivedCert in p.server_certs:
if receivedCert.digest('sha256') == upstreamCert.digest('sha256'):
upstream_cert_found_in_client_chain = True
break
assert(upstream_cert_found_in_client_chain == self.add_upstream_certs_to_client_chain)
class TestHTTPSAddUpstreamCertsToClientChainTrue(AddUpstreamCertsToClientChainMixin, tservers.HTTPProxyTest):
"""
If --add-server-certs-to-client-chain is True, then the client should receive the upstream server's certificates
"""
add_upstream_certs_to_client_chain = True
class TestHTTPSAddUpstreamCertsToClientChainFalse(AddUpstreamCertsToClientChainMixin, tservers.HTTPProxyTest):
"""
If --add-server-certs-to-client-chain is False, then the client should not receive the upstream server's certificates
"""
add_upstream_certs_to_client_chain = False

View File

@ -86,6 +86,7 @@ class ProxyTestBase(object):
no_upstream_cert = False
authenticator = None
masterclass = TestMaster
add_upstream_certs_to_client_chain = False
@classmethod
def setup_class(cls):
@ -129,6 +130,7 @@ class ProxyTestBase(object):
no_upstream_cert = cls.no_upstream_cert,
cadir = cls.cadir,
authenticator = cls.authenticator,
add_upstream_certs_to_client_chain = cls.add_upstream_certs_to_client_chain,
)