Enabling upstream server verification. Added flags --verify_upstream_cert,

--upstream-trusted-cadir, and --upstream-trusted-ca.
This commit is contained in:
Kyle Morton 2015-06-29 10:32:57 -07:00
parent aebad44d55
commit f0ad1f334c
9 changed files with 205 additions and 1 deletions

View File

@ -52,6 +52,9 @@ class ProxyConfig:
ssl_version_server=tcp.SSL_DEFAULT_METHOD, ssl_version_server=tcp.SSL_DEFAULT_METHOD,
ssl_ports=TRANSPARENT_SSL_PORTS, ssl_ports=TRANSPARENT_SSL_PORTS,
spoofed_ssl_port=None, spoofed_ssl_port=None,
ssl_verify_upstream_cert=False,
ssl_upstream_trusted_cadir=None,
ssl_upstream_trusted_ca=None
): ):
self.host = host self.host = host
self.port = port self.port = port
@ -100,6 +103,13 @@ class ProxyConfig:
self.openssl_method_server = ssl_version_server self.openssl_method_server = ssl_version_server
else: else:
self.openssl_method_server = tcp.SSL_VERSIONS[ssl_version_server] self.openssl_method_server = tcp.SSL_VERSIONS[ssl_version_server]
if ssl_verify_upstream_cert:
self.openssl_verification_mode_server = SSL.VERIFY_PEER
else:
self.openssl_verification_mode_server = SSL.VERIFY_NONE
self.openssl_trusted_cadir_server = ssl_upstream_trusted_cadir
self.openssl_trusted_ca_server = ssl_upstream_trusted_ca
self.openssl_options_client = tcp.SSL_DEFAULT_OPTIONS self.openssl_options_client = tcp.SSL_DEFAULT_OPTIONS
self.openssl_options_server = tcp.SSL_DEFAULT_OPTIONS self.openssl_options_server = tcp.SSL_DEFAULT_OPTIONS
@ -203,7 +213,10 @@ def process_proxy_options(parser, options):
ssl_version_client=options.ssl_version_client, ssl_version_client=options.ssl_version_client,
ssl_version_server=options.ssl_version_server, ssl_version_server=options.ssl_version_server,
ssl_ports=ssl_ports, ssl_ports=ssl_ports,
spoofed_ssl_port=spoofed_ssl_port spoofed_ssl_port=spoofed_ssl_port,
ssl_verify_upstream_cert=options.ssl_verify_upstream_cert,
ssl_upstream_trusted_cadir=options.ssl_upstream_trusted_cadir,
ssl_upstream_trusted_ca=options.ssl_upstream_trusted_ca
) )
@ -242,6 +255,23 @@ def ssl_option_group(parser):
action="store_true", dest="no_upstream_cert", action="store_true", dest="no_upstream_cert",
help="Don't connect to upstream server to look up certificate details." help="Don't connect to upstream server to look up certificate details."
) )
group.add_argument(
"--verify-upstream-cert", default=False,
action="store_true", dest="ssl_verify_upstream_cert",
help="Verify upstream server SSL/TLS certificates and fail if invalid "
"or not present."
)
group.add_argument(
"--upstream-trusted-cadir", default=None, action="store",
dest="ssl_upstream_trusted_cadir",
help="Path to a directory of trusted CA certificates for upstream "
"server verification prepared using the c_rehash tool."
)
group.add_argument(
"--upstream-trusted-ca", default=None, action="store",
dest="ssl_upstream_trusted_ca",
help="Path to a PEM formatted trusted CA certificate."
)
group.add_argument( group.add_argument(
"--ssl-port", "--ssl-port",
action="append", action="append",

View File

@ -235,8 +235,18 @@ class ConnectionHandler:
sni, sni,
method=self.config.openssl_method_server, method=self.config.openssl_method_server,
options=self.config.openssl_options_server, options=self.config.openssl_options_server,
verify_options=self.config.openssl_verification_mode_server,
ca_path=self.config.openssl_trusted_cadir_server,
ca_pemfile=self.config.openssl_trusted_ca_server,
cipher_list=self.config.ciphers_server, cipher_list=self.config.ciphers_server,
) )
ssl_cert_err = self.server_conn.ssl_verification_error
if ssl_cert_err is not None:
self.log(
"SSL verification failed for upstream server at depth %s with error: %s" %
(ssl_cert_err['depth'], ssl_cert_err['errno']),
"error")
self.log("Ignoring server verification error, continuing with connection", "error")
except tcp.NetLibError as v: except tcp.NetLibError as v:
e = ProxyError(502, repr(v)) e = ProxyError(502, repr(v))
# Workaround for https://github.com/mitmproxy/mitmproxy/issues/427 # Workaround for https://github.com/mitmproxy/mitmproxy/issues/427
@ -246,6 +256,13 @@ class ConnectionHandler:
if client and "handshake failure" in e.message: if client and "handshake failure" in e.message:
self.server_conn.may_require_sni = e self.server_conn.may_require_sni = e
else: else:
ssl_cert_err = self.server_conn.ssl_verification_error
if ssl_cert_err is not None:
self.log(
"SSL verification failed for upstream server at depth %s with error: %s" %
(ssl_cert_err['depth'], ssl_cert_err['errno']),
"error")
self.log("Aborting connection attempt", "error")
raise e raise e
if client: if client:
if self.client_conn.ssl_established: if self.client_conn.ssl_established:

View File

@ -0,0 +1 @@
trusted-ca.pem

View File

@ -0,0 +1 @@
trusted-ca.pem

View File

@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICJzCCAZACCQCo1BdopddN/TANBgkqhkiG9w0BAQUFADBXMQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMRAwDgYDVQQDEwdUUlVTVEVEMCAXDTE1MDYxOTE4MDEzMVoYDzIx
MTUwNTI2MTgwMTMxWjBXMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0
ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRAwDgYDVQQDEwdU
UlVTVEVEMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC00Jf3KrBAmLQWl+Dz
8Qrig8ActB94kv0/Lu03P/2DwOR8kH2h3w4OC3b3CFKX31h7hm/H1PPHq7cIX6IR
fwrYCtBE77UbxklSlrwn06j6YSotz0/dwLEQEFDXWITJq7AyntaiafDHazbbXESN
m/+I/YEl2wKemEHE//qWbeM9kwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAF0NREP3
X+fTebzJGttzrFkDhGVFKRNyLXblXRVanlGOYF+q8grgZY2ufC/55gqf+ub6FRT5
gKPhL4V2rqL8UAvCE7jq8ujpVfTB8kRAKC675W2DBZk2EJX9mjlr89t7qXGsI5nF
onpfJ1UtiJshNoV7h/NFHeoag91kx628807n
-----END CERTIFICATE-----

View File

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIC8jCCAlugAwIBAgICEAcwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQVUx
EzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMg
UHR5IEx0ZDEQMA4GA1UEAxMHVFJVU1RFRDAgFw0xNTA2MjAwMTE4MjdaGA8yMTE1
MDUyNzAxMTgyN1owfjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UECxMLSU5U
RVJNIFVOSVQxITAfBgNVBAMTGE9SRyBXSVRIIElOVEVSTUVESUFURSBDQTCBnzAN
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtRPNKgh4WdYGmU2Ae6Tf2Mbd3oaRI/uY
Qm6aKeYk1i7g41C0vVowNcD/qdNpGUNnai/Kak9anHOYyppNo7zHgf3EO8zQ4NTQ
pkDKsdCqbUQcjGfhjWXKnOw+I5er4Rj+MwM1f5cbwb8bYHiSPmXaxzdL0/SNXGAA
ys/UswgwkU8CAwEAAaOBozCBoDAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTPkPQW
DAPOIy8mipuEsZcP1694EDBxBgNVHSMEajBooVukWTBXMQswCQYDVQQGEwJBVTET
MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
dHkgTHRkMRAwDgYDVQQDEwdUUlVTVEVEggkAqNQXaKXXTf0wDQYJKoZIhvcNAQEF
BQADgYEApaPbwonY8l+zSxlY2Fw4WNKfl5nwcTW4fuv/0tZLzvsS6P4hTXxbYJNa
k3hQ1qlrr8DiWJewF85hYvEI2F/7eqS5dhhPTEUFPpsjhbgiqnASvW+WKQIgoY2r
aHgOXi7RNFtTcCgk0UZISWOY7ORLy8Xu6vKrLRjDhyfIbGlqnAs=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC1E80qCHhZ1gaZTYB7pN/Yxt3ehpEj+5hCbpop5iTWLuDjULS9
WjA1wP+p02kZQ2dqL8pqT1qcc5jKmk2jvMeB/cQ7zNDg1NCmQMqx0KptRByMZ+GN
Zcqc7D4jl6vhGP4zAzV/lxvBvxtgeJI+ZdrHN0vT9I1cYADKz9SzCDCRTwIDAQAB
AoGAfKHocKnrzEmXuSSy7meI+vfF9kfA1ndxUSg3S+dwK0uQ1mTSQhI1ZIo2bnlo
uU6/e0Lxm0KLJ2wZGjoifjSNTC8pcxIfAQY4kM9fqoUcXVSBVSS2kByTunhNSVZQ
yQyc+UTq9g1zBnJsZAltn7/PaihU4heWgP/++lposuShqmECQQDaG+7l0qul1xak
9kuZgc88BSTfn9iMK2zIQRcVKuidK4dT3QEp0wmWR5Ue8jq8lvTmVTGNGZbHcheh
KhoZfLgLAkEA1IjwAw/8z02yV3lbc2QUjIl9m9lvjHBoE2sGuSfq/cZskLKrGat+
CVj3spqVAg22tpQwVBuHiipBziWVnEtiTQJAB9FKfchQSLBt6lm9mfHyKJeSm8VR
8Kw5yO+0URjpn4CI6DOasBIVXOKR8LsD6fCLNJpHHWSWZ+2p9SfaKaGzwwJBAM31
Scld89qca4fzNZkT0goCrvOZeUy6HVE79Q72zPVSFSD/02kT1BaQ3bB5to5/5aD2
6AKJjwZoPs7bgykrsD0CQBzU8U/8x2dNQnG0QeqaKQu5kKhZSZ9bsawvrCkxSl6b
WAjl/Jehi5bbQ07zQo3cge6qeR38FCWVCHQ/5wNbc54=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,32 @@
# untrusted-interm.crt, self-signed
-----BEGIN CERTIFICATE-----
MIICdTCCAd4CCQDRSKOnIMbTgDANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMRQwEgYDVQQLEwtJTlRFUk0gVU5JVDEhMB8GA1UEAxMYT1JHIFdJ
VEggSU5URVJNRURJQVRFIENBMCAXDTE1MDYyMDAxMzY0M1oYDzIxMTUwNTI3MDEz
NjQzWjB+MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UE
ChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQLEwtJTlRFUk0gVU5J
VDEhMB8GA1UEAxMYT1JHIFdJVEggSU5URVJNRURJQVRFIENBMIGfMA0GCSqGSIb3
DQEBAQUAA4GNADCBiQKBgQC1E80qCHhZ1gaZTYB7pN/Yxt3ehpEj+5hCbpop5iTW
LuDjULS9WjA1wP+p02kZQ2dqL8pqT1qcc5jKmk2jvMeB/cQ7zNDg1NCmQMqx0Kpt
RByMZ+GNZcqc7D4jl6vhGP4zAzV/lxvBvxtgeJI+ZdrHN0vT9I1cYADKz9SzCDCR
TwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAGbObAMEajCz4kj7OP2/DB5SRy2+H/G3
8Qvc43xlMMNQyYxsDuLOFL0UMRzoKgntrrm2nni8jND+tuMt+hv3ZlBcJlYJ6ynR
sC1ITTC/1SwwwO0AFIyduUEIJYr/B3sgcVYPLcEfeDZgmEQc9Tnc01aEu3lx2+l9
0JTSPL2L9LdA
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC1E80qCHhZ1gaZTYB7pN/Yxt3ehpEj+5hCbpop5iTWLuDjULS9
WjA1wP+p02kZQ2dqL8pqT1qcc5jKmk2jvMeB/cQ7zNDg1NCmQMqx0KptRByMZ+GN
Zcqc7D4jl6vhGP4zAzV/lxvBvxtgeJI+ZdrHN0vT9I1cYADKz9SzCDCRTwIDAQAB
AoGAfKHocKnrzEmXuSSy7meI+vfF9kfA1ndxUSg3S+dwK0uQ1mTSQhI1ZIo2bnlo
uU6/e0Lxm0KLJ2wZGjoifjSNTC8pcxIfAQY4kM9fqoUcXVSBVSS2kByTunhNSVZQ
yQyc+UTq9g1zBnJsZAltn7/PaihU4heWgP/++lposuShqmECQQDaG+7l0qul1xak
9kuZgc88BSTfn9iMK2zIQRcVKuidK4dT3QEp0wmWR5Ue8jq8lvTmVTGNGZbHcheh
KhoZfLgLAkEA1IjwAw/8z02yV3lbc2QUjIl9m9lvjHBoE2sGuSfq/cZskLKrGat+
CVj3spqVAg22tpQwVBuHiipBziWVnEtiTQJAB9FKfchQSLBt6lm9mfHyKJeSm8VR
8Kw5yO+0URjpn4CI6DOasBIVXOKR8LsD6fCLNJpHHWSWZ+2p9SfaKaGzwwJBAM31
Scld89qca4fzNZkT0goCrvOZeUy6HVE79Q72zPVSFSD/02kT1BaQ3bB5to5/5aD2
6AKJjwZoPs7bgykrsD0CQBzU8U/8x2dNQnG0QeqaKQu5kKhZSZ9bsawvrCkxSl6b
WAjl/Jehi5bbQ07zQo3cge6qeR38FCWVCHQ/5wNbc54=
-----END RSA PRIVATE KEY-----

View File

@ -9,6 +9,8 @@ from libpathod import test
from netlib import http, tcp from netlib import http, tcp
import mock import mock
from OpenSSL import SSL
def test_proxy_error(): def test_proxy_error():
p = ProxyError(111, "msg") p = ProxyError(111, "msg")
@ -133,6 +135,20 @@ class TestProcessProxyOptions:
"--singleuser", "--singleuser",
"test") "test")
def test_verify_upstream_cert(self):
p = self.assert_noerr("--verify-upstream-cert")
assert p.openssl_verification_mode_server == SSL.VERIFY_PEER
def test_upstream_trusted_cadir(self):
expected_dir = "/path/to/a/ca/dir"
p = self.assert_noerr("--upstream-trusted-cadir", expected_dir)
assert p.openssl_trusted_cadir_server == expected_dir
def test_upstream_trusted_ca(self):
expected_file = "/path/to/a/cert/file"
p = self.assert_noerr("--upstream-trusted-ca", expected_file)
assert p.openssl_trusted_ca_server == expected_file
class TestProxyServer: class TestProxyServer:
# binding to 0.0.0.0:1 works without special permissions on Windows # binding to 0.0.0.0:1 works without special permissions on Windows

View File

@ -9,6 +9,7 @@ import tutils
import tservers import tservers
from libmproxy.protocol import KILL, Error from libmproxy.protocol import KILL, Error
from libmproxy.protocol.http import CONTENT_MISSING from libmproxy.protocol.http import CONTENT_MISSING
from OpenSSL import SSL
""" """
Note that the choice of response code in these tests matters more than you Note that the choice of response code in these tests matters more than you
@ -348,6 +349,65 @@ class TestHTTPSCertfile(tservers.HTTPProxTest, CommonMixin):
assert self.pathod("304") assert self.pathod("304")
class TestHTTPSUpstreamServerVerificationWTrustedCert(tservers.HTTPProxTest):
"""
Test upstream server certificate verification with a trusted server cert.
"""
ssl = True
ssloptions = pathod.SSLOptions(
cn = "trusted-cert",
certs = [
("trusted-cert", tutils.test_data.path("data/trusted-server.crt"))
])
def test_verification_w_cadir(self):
self.config.openssl_verification_mode_server = SSL.VERIFY_PEER
self.config.openssl_trusted_cadir_server = tutils.test_data.path(
"data/trusted-cadir/")
self.pathoc()
def test_verification_w_pemfile(self):
self.config.openssl_verification_mode_server = SSL.VERIFY_PEER
self.config.openssl_trusted_ca_server = tutils.test_data.path(
"data/trusted-cadir/trusted-ca.pem")
self.pathoc()
class TestHTTPSUpstreamServerVerificationWBadCert(tservers.HTTPProxTest):
"""
Test upstream server certificate verification with an untrusted server cert.
"""
ssl = True
ssloptions = pathod.SSLOptions(
cn = "untrusted-cert",
certs = [
("untrusted-cert", tutils.test_data.path("data/untrusted-server.crt"))
])
def test_default_verification_w_bad_cert(self):
"""Should use no verification."""
self.config.openssl_trusted_ca_server = tutils.test_data.path(
"data/trusted-cadir/trusted-ca.pem")
self.pathoc()
def test_no_verification_w_bad_cert(self):
self.config.openssl_verification_mode_server = SSL.VERIFY_NONE
self.config.openssl_trusted_ca_server = tutils.test_data.path(
"data/trusted-cadir/trusted-ca.pem")
self.pathoc()
def test_verification_w_bad_cert(self):
self.config.openssl_verification_mode_server = SSL.VERIFY_PEER
self.config.openssl_trusted_ca_server = tutils.test_data.path(
"data/trusted-cadir/trusted-ca.pem")
tutils.raises("SSL handshake error", self.pathoc)
class TestHTTPSNoCommonName(tservers.HTTPProxTest): class TestHTTPSNoCommonName(tservers.HTTPProxTest):
""" """
Test what happens if we get a cert without common name back. Test what happens if we get a cert without common name back.