Https with custom ca bundle

Hi all,

I’m having trouble understanding how to run mitmproxy to look at https calls, even after reading over http://docs.mitmproxy.org/en/stable/certinstall.html#using-a-custom-certificate. We have our own certificate authorities on our intranet and I’m using a cert/key issued by that CA, hitting websites that have server certs also issued by those CA’s.

I have Mitmproxy version 2.0.0, Python 3.5.1, ssl OpenSSL 1.0.1e-fips 11 Feb 2013 and Linux Distro centos 6.8 Final. I’m just running “mitmproxy”, no other command line options.

From a Jupyter notebook, I’ve got something like this –

import requests
proxies = {'http' : 'http://localhost:8080',
               'https' : 'http://localhost:8080'}
url_1 = 'http://corporate.intranet.site'
url_2 = 'https://secure.intranet.site'

For the plain old http request, the mitmproxy is working as expected. In the mitmproxy terminal, I see the GET and the 200 response, and in Jupyter I see the 200 response.

requests.get(url_1, proxies=proxies)
>>> <Response [200]>

For the https site, I’m getting an error saying “certificate verification error, self signed certificate in certificate chain (errno: 19, depth: 2)” In Jupyter, I get a 502 response back. If I don’t go through the proxies, I can get the site alright.

# boilerplate code here to create an SSLContext and load it into a requests Session
session = create_session(cert='mycert.crt', key='mycert.key', verify='custom_ca_bundle.pem')
session.get(url_2)
>>> <Response [200]>

session.get(url_2, proxies=proxies, verify=False)
>>> <Response [502]>

My first question is what I need to do when running mitmproxy to get around the certificate verification error. I’m happy to post more debug.

My second related but less important question is just a bit of clarification on these docs. I see the files created in ~/.mitmproxy. If I’m reading that right, I need to append one of those files to my custom_ca_bundle.crt and then I won’t need to use verify=False in the requests calls. Which file do I use there for python requests on a linux box (.pem?), and does it need to be at the top of the ca bundle file or the bottom or does it not matter?

Thanks.

Hi,

You are probably hitting two certificate verification errors:

  • First, mitmproxy does not accept your private company CA by default. You need to pass --upstream-trusted-ca (or --insecure to test it temporarily) to fix this. This is presumably the 502 you are seeing - mitmproxy emits a Gateway Error because it cannot verify the upstream certificate.
  • Second, requests does not accept the mitmproxy CA. You can fix this by passing verify="~/.mitmproxy/mitmproxy-ca-cert.pem" to requests (or verify=False to test it temporarily).

How does your custom_ca_bundle.crt look like? Those things really are not standardized: X.509 - Wikipedia
Normally, you should be able to just take the contents of mitmproxy-ca-cert.pem and put the whole

-----BEGIN CERTIFICATE----- 
<snip> 
-----END CERTIFICATE-----

below/above the other similar looking blocks in your custom_ca_bundle.crt. Order does not matter for this.

Thanks for the response mhils.

To the second question, my ca bundle just looks like what’s in mitmproxy-ca-cert.pem. I’ll just cat that onto my bundle.

# CA issuer #1
-----BEGIN CERTIFICATE----- 
<snip> 
-----END CERTIFICATE----
.. etc

To the first question, I’m still getting this error: << Certificate Verification Error for https://intranet.url: self signed certificate in certificate chain (errno: 19, depth: 2) even when running mitmdump --insecure and calling session.get(url_2, proxies=proxies, verify=False).

session.get(url_2) without going through the proxy returns a 200.

Is there other debugging I can turn on to help?

Thanks again, appreciate your time here.

Where do you see that? In the mitmproxy UI? Here’s what it looks like for me:

$ mitmdump --insecure
$ curl -x localhost:8080 -k https://self-signed.badssl.com/

Proxy server listening at http://:::8080
('::ffff:127.0.0.1', 63569, 0, 0): clientconnect
::ffff:127.0.0.1:63569: Certificate Verification Error for self-signed.badssl.com: self signed certificate (errno: 18, depth: 0)
::ffff:127.0.0.1:63569: Ignoring server verification error, continuing with connection
('::ffff:127.0.0.1', 63569, 0, 0): GET https://self-signed.badssl.com/
                                << 200 OK 477b

(ignore the IPv6 formatting issues, that’s from master)

mhils,

Yes, I see that in the mitmdump output screen.

### terminal window
(py35) [root@test proxy_test]# mitmdump --insecure
HTTP/2 is disabled because ALPN support missing!
OpenSSL 1.0.2+ required to support HTTP/2 connections.
Use --no-htp2 to silence this warning.
Proxy server listening at http://0.0.0.0:8080
127.0.0.1:42870: clientconnect
127.0.0.1:42870: GET https://internal.url
 << Certificate Verification Error for internal.url: self signed certificate in certificate chain (errno: 19, depth: 2)
1270.0.1:42870: clientdisconnect
#### Jupyter
# boilerplate session setup
resp = session.get(url_2, proxies=proxies, verify=False)
resp

<insecure requests warning>
>>> <Response [502]>

### text of the response body is just the same error message that showed up in the mitmdump window

resp = session.get(url_2)
resp

>>> <Response [200]>

You should get a “Ignoring server verification error, continuing with connection” message if you pass --insecure.
What’s your output of mitmdump --version ? Do you also get this for https://self-signed.badssl.com/?

Yep I’m tracking with you, not sure why I’m not getting the same “ignoring server verification error” as you.

(py35) [root@test proxy_test]# mitmdump --version
Mitmproxy version: 2.0.0 (release version)
Python version: 3.5.1
Platform: Linux-2.6.32-642.15.1.e16.x68_64-x86-with-centos-6.8-Final
SSL version: OpenSSL 1.0.1e-fips 11 Feb 2013
Linux distro: CentOS 6.8 Final

Thanks again for all the help so far.

Do you also get this issue for https://self-signed.badssl.com/?

mhils,

I’m not sure what you mean exactly with that question. If you mean literally what happens when I try to make a request to https://self-signed.badssl.com/: I can’t because I’m on a intranet that’s gapped from the open internet.

If you mean what happens when I request our intranet urls without going through the mitmproxy, then I get normal 200 responses.

I can setup a local flask app to try and help debug this more if you have ideas on simulating a server with a self-signed cert?

Thanks,

mhils,

I tried running mitmdump --upstream-trusted-ca custom_ca_bundle.pem (same bundle I use in my requests Session) and it shows a different error:

<< Cannot establish TLS with wiki.intranet.org:443 (sni: wiki.intranet.org): TlsException("SSL handshake error: Error([('SSL routines', 'SSL3_READ_BYTES', 'sslv3 alert handshake failure')],)",)

Let me know if that gives you any ideas on other things to test out. Thanks.

Not sure what is causing this, sorry. Random guesses:

  • Your app runs on SSLv3? That needs to be explicitly enabled in mitmproxy.
  • Your app requires client certificates? mitmproxy needs them.

mhils,

Thanks, I think your guesses are on point. Our apps do require client certificates. I think I fundamentally misunderstood what mitmproxy was doing, I thought it was “passing” the request on. Instead, I should think of this as a series of connections right? My python client makes a secure connection to the mitmproxy process. The mitmproxy process then makes a new connection to the destination (app), providing no client cert of its own. So apps that require client certs would throw back errors?

You’ve already been so helpful, but do you mind pointing me in the right direction to show how to have mitmproxy use a set of certificates? This actually feeds into a follow on question I was going to ask you; one of my use cases is to have clients on this server make outbound requests without specifying any client certs and have the server add encryption to the request.

Thanks again mhils.

That is (fortunately) not possible. :wink:
Your new understanding is correct, you can read details at http://docs.mitmproxy.org/en/stable/howmitmproxy.html if you are interested.

Check out http://docs.mitmproxy.org/en/stable/certinstall.html#using-a-client-side-certificate.

Sweet, this is almost entirely working.

Just to document what I’ve done based on your amazingly-helpful suggestions.

I created a pem file from mycert.crt and mycert.key that can be used with mitmdump --client-cert. cat mycert.key mycert.crt > mycert.pem.

Once that is done, then I can run this mitmdump --upstream-trusted-ca custom_ca_bundle.pem --client-cert mycert.pem

import requests
proxies = {'http' : 'http://localhost:8080',
           'https' : 'http://localhost:8080'}
url_1 = 'http://corporate.intranet.site'
url_2 = 'https://secure.intranet.site'

requests.get(url_1, proxies=proxies)
>>> <Response [200]>

requests.get(url_2, proxies=proxies)
>>> Error: tlsv1 alert unknown ca sni: mycert domain name

requests.get(url_2, proxies=proxies, verify=False)
>>> <Response [200]>

From my app, I can see the ssl domain name of the connection coming in is my proxy server, wonderful. Last question for now would be whether you knew why my own cert is throwing certificate verification errors. I added the text of mycert.crt (–begin certificate-- etc) into my custom_ca_bundle and the issuer of mycert.crt should already be in that bundle anyways. The ~/.mitmproxy/mitmproxy-ca-cert.pem text is also in my custom_ca_bundle.

Thanks again for the education and help mhils.

Woops, realized that in my plain old requests.get (not session.get), I wasn’t specifying a verify path to my custom ca bundle. That’s my fault. This is working:

requests.get(url_2, proxies=proxies, verify='custom_ca_bundle.pem')
>>> <Response [200]>

Thanks again for all the help mhils.

1 Like