diff --git a/docs/certinstall.rst b/docs/certinstall.rst index 5ec7b7ce9..5a8cce64f 100644 --- a/docs/certinstall.rst +++ b/docs/certinstall.rst @@ -175,10 +175,21 @@ no such file exists, it will be generated automatically. Using a client side certificate ------------------------------- -You can use a client certificate by passing the ``--client-certs DIRECTORY`` option to mitmproxy. +You can use a client certificate by passing the ``--client-certs DIRECTORY|FILE`` +option to mitmproxy. Using a directory allows certs to be selected based on +hostname, while using a filename allows a single specific certificate to be used for +all SSL connections. Certificate files must be in the PEM format and should +contain both the unencrypted private key and the certificate. + +Multiple certs by Hostname +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you've specified a directory to ``--client-certs``, then the following +behavior will be taken: + If you visit example.org, mitmproxy looks for a file named ``example.org.pem`` in the specified -directory and uses this as the client cert. The certificate file needs to be in the PEM format and -should contain both the unencrypted private key and the certificate. +directory and uses this as the client cert. + .. _Certificate Pinning: http://security.stackexchange.com/questions/29988/what-is-certificate-pinning/ \ No newline at end of file diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py index 166784867..99b76e681 100644 --- a/libmproxy/cmdline.py +++ b/libmproxy/cmdline.py @@ -407,7 +407,7 @@ def proxy_ssl_options(parser): group.add_argument( "--client-certs", action="store", type=str, dest="clientcerts", default=None, - help="Client certificate directory." + help="Client certificate file or directory." ) group.add_argument( "--no-upstream-cert", default=False, diff --git a/libmproxy/models/connections.py b/libmproxy/models/connections.py index 3aa522eac..0991955d9 100644 --- a/libmproxy/models/connections.py +++ b/libmproxy/models/connections.py @@ -174,11 +174,14 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): def establish_ssl(self, clientcerts, sni, **kwargs): clientcert = None if clientcerts: - path = os.path.join( - clientcerts, - self.address.host.encode("idna")) + ".pem" - if os.path.exists(path): - clientcert = path + if os.path.isfile(clientcerts): + clientcert = clientcerts + else: + path = os.path.join( + clientcerts, + self.address.host.encode("idna")) + ".pem" + if os.path.exists(path): + clientcert = path self.convert_to_ssl(cert=clientcert, sni=sni, **kwargs) self.sni = sni diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index c7b513112..f06e55cab 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -133,10 +133,12 @@ def process_proxy_options(parser, options): if options.clientcerts: options.clientcerts = os.path.expanduser(options.clientcerts) - if not os.path.exists(options.clientcerts) or not os.path.isdir(options.clientcerts): + if not (os.path.exists(options.clientcerts) or + os.path.isdir(options.clientcerts) or + os.path.isfile(options.clientcerts)): return parser.error( - "Client certificate directory does not exist or is not a directory: %s" % - options.clientcerts + "Client certificate argument is not a file or directory, " + "or does not exist: %s" % options.clientcerts ) if options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd: diff --git a/test/test_proxy.py b/test/test_proxy.py index b3e7258a1..a0530c79d 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -1,3 +1,4 @@ +import os import mock from OpenSSL import SSL @@ -99,8 +100,11 @@ class TestProcessProxyOptions: def test_client_certs(self): with tutils.tmpdir() as cadir: self.assert_noerr("--client-certs", cadir) + self.assert_noerr( + "--client-certs", + os.path.join(tutils.test_data.path("data/clientcert"), "client.pem")) self.assert_err( - "directory does not exist", + "not a file or directory", "--client-certs", "nonexistent") diff --git a/test/test_server.py b/test/test_server.py index e48e46fe9..09cfa3815 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -1,3 +1,4 @@ +import os import socket import time from OpenSSL import SSL @@ -316,6 +317,14 @@ class TestHTTPS(tservers.HTTPProxTest, CommonMixin, TcpMixin): clientcerts = True def test_clientcert(self): + self.config.clientcerts = os.path.join( + tutils.test_data.path("data/clientcert"), "client.pem") + f = self.pathod("304") + assert f.status_code == 304 + assert self.server.last_log()["request"]["clientcert"]["keyinfo"] + + def test_clientcerts(self): + self.config.clientcerts = tutils.test_data.path("data/clientcert") f = self.pathod("304") assert f.status_code == 304 assert self.server.last_log()["request"]["clientcert"]["keyinfo"]