From 3ba4b65ecda926958c5ca2a3c7dd3df2f0ee1eee Mon Sep 17 00:00:00 2001 From: Mike Anselmi Date: Fri, 16 Feb 2024 14:37:24 -0500 Subject: [PATCH] Fix ca_file containing multiple CAs (#6666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### Description Unfortunately 0b5e310881f0116c9e8e2ca5b9da98e258de0a11 broke mitmproxy's ability to issue leaf certificates if `ca_file` contains multiple CAs. This PR restores that capability. The issue lies in `mitmproxy/certs.py` - specifically, in the `from_files` method of the `CertStore` class. Before 0b5e310881f0116c9e8e2ca5b9da98e258de0a11, the issuing CA was identified like this: ``` python raw = ca_file.read_bytes() key = load_pem_private_key(raw, passphrase) … certs = re.split(rb"(?=-----BEGIN CERTIFICATE-----)", raw) ca = Cert.from_pem(certs[1]) ``` This worked even when `ca_file` contained multiple CAs. For example, consider this example: ``` -----BEGIN PRIVATE KEY----- REDACTED -----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIICmTCCAfqgAwIBAgIRAIqaBJ6aU5wXT+5JAn8Uj1IwCgYIKoZIzj0EAwQwMDEu MCwGA1UEAxMlbWFuc2VsbWkgSW50ZXJtZWRpYXRlIENBICgyMDIzLTA0LTExKTAe Fw0yNDAyMTQxNzEzMDBaFw0yNDAyMTUxNzE0MDBaMB8xHTAbBgNVBAMTFG1pdG1w cm94eSBJc3N1aW5nIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEeKTLpy6KeiWN 89S9UJoTM9w6HyrEiVByf/ie2wUX3vR/3nHVMTi5XZQD6/8h3A9yUWwtRtVDj55D STbPKjN6G8t/Qh1dGz2vanNjmWNlceM3DOIlT30vmR5+GrUOwj+7o4HoMIHlMA4G A1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEgYD VR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUkf9kgx5DbP0B66Ir6oLUDV/CIU4w HwYDVR0jBBgwFoAUqBYQLb8rkIidwq5dYuiDFdKhY48wYAYMKwYBBAGCpGTGKEAB BFAwTgIBAQQcbWlrZUBtYW5zZWxtaS5jb206aXNzdWluZy1jYQQrYmZhODBJaGMz VXNUcU0yQlp6ZG1rYW1OLW1XNGtpWTJrM1g5X19kd3Q0bzAKBggqhkjOPQQDBAOB jAAwgYgCQgF/QLZ0cThJKeoeIk4dY4+Z2MdIey7kvvy1jwlWrsOqOY+UXUWHXRlj oXZN92imAKqgLU5T859MMqWrjA1LjN7w6QJCAY+2xN7ovry3WSMqwnu86NuPL8vh fjftZeIW5GWuLzOsEFt7SfWMhkmFc/jcFpZ/qlolPhvr6E170zNhHUSMKvjb -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICcjCCAdSgAwIBAgIQeK+VyMjx85py1kqGdjn5nTAKBggqhkjOPQQDBDAoMSYw JAYDVQQDEx1tYW5zZWxtaSBSb290IENBICgyMDIzLTA0LTExKTAeFw0yMzA0MTEw MjIzMzdaFw0yODA0MDkwMjIzMzdaMDAxLjAsBgNVBAMTJW1hbnNlbG1pIEludGVy bWVkaWF0ZSBDQSAoMjAyMy0wNC0xMSkwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYA BADhbjtnME8IDpBab8MogM0HchH0V9B8Qx7nRXK+tbGyyHlgLjIBYF/erJGBP0md RMAxU5x/nPlHPODRBkJhB0wbcwGC9zjQQyxfhG75806QzkaZSsC0kFZ9d3JG4fjn uenY1S2VZ5qE0gDc8Qmfyjcx7KqxQKwS9RwtG1ymAmWgvmGIJ6OBlDCBkTAOBgNV HQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUqBYQLb8r kIidwq5dYuiDFdKhY48wHwYDVR0jBBgwFoAU6+JptrrPiOuWZyKvwg0EJz5TkW8w KwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2NhLm1hbnNlbG1pLmNvbS9jcmwwCgYI KoZIzj0EAwQDgYsAMIGHAkEw/A2kiL4Kt9/8PND89vlXEehqeymFgats62a3UMU2 g3zATES/WI2E1IgqIsmB6ILRgWMslzpH+NGP+JJkFCz9cwJCAJaRlnOc3xafHZta 0Xwr5YUVAAfmVOj9a2XAsNlNuMy2t8cmeeGmQjrZwCuivlzLOp19Ge3Mi0dnzeV+ 5L2rBOCL -----END CERTIFICATE----- ``` `certs` would have three elements: the private key, the issuing CA and the intermediate CA. As a result, `ca = Cert.from_pem(certs[1])` would select the first CA (the issuing CA). From 0b5e310881f0116c9e8e2ca5b9da98e258de0a11 onward, we instead have ``` python raw = ca_file.read_bytes() key = load_pem_private_key(raw, passphrase) … certs = x509.load_pem_x509_certificates(raw) ca = Cert(certs[-1]) ``` Now, `certs` would have only two elements: the issuing CA and the intermediate CA. (`x509.load_pem_x509_certificates` discards the private key.) As a result, `ca = Cert(certs[-1])` must instead be `ca = Cert(certs[0])`, otherwise the `ca` and `key` won't correspond to each other and we'll eventually see an error like this when mitmproxy tries to generate a leaf certificate: ``` Addon error: [('x509 certificate routines', '', 'key values mismatch')] Traceback (most recent call last): File "/Users/manselmi/repos/mitmproxy/mitmproxy/addons/tlsconfig.py", line 208, in tls_start_client tls_start.ssl_conn.use_privatekey( File "/Users/manselmi/virtualenv/mitmproxy-py312/lib/python3.12/site-packages/OpenSSL/SSL.py", line 1949, in use_privatekey self._context._raise_passphrase_exception() File "/Users/manselmi/virtualenv/mitmproxy-py312/lib/python3.12/site-packages/OpenSSL/SSL.py", line 1123, in _raise_passphrase_exception _raise_current_error() File "/Users/manselmi/virtualenv/mitmproxy-py312/lib/python3.12/site-packages/OpenSSL/_util.py", line 57, in exception_from_error_queue raise exception_type(errors) OpenSSL.SSL.Error: [('x509 certificate routines', '', 'key values mismatch')] ``` --- CHANGELOG.md | 2 ++ mitmproxy/certs.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ba79b88..051202a58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ ([#6648](https://github.com/mitmproxymitmproxy/pull/6648), @sujaldev) * Fix bug where wireguard config is generated with incorrect endpoint when two or more NICs are active. ([#6659](https://github.com/mitmproxy/mitmproxy/pull/6659), @basedBaba) +* Fix a regression when leaf cert creation would fail with intermediate CAs in `ca_file`. + ([#6666](https://github.com/mitmproxy/mitmproxy/pull/6666), @manselmi) ## 21 January 2024: mitmproxy 10.2.2 diff --git a/mitmproxy/certs.py b/mitmproxy/certs.py index 173311c1d..b054acf42 100644 --- a/mitmproxy/certs.py +++ b/mitmproxy/certs.py @@ -415,7 +415,7 @@ class CertStore: key = load_pem_private_key(raw, passphrase) dh = cls.load_dhparam(dhparam_file) certs = x509.load_pem_x509_certificates(raw) - ca = Cert(certs[-1]) + ca = Cert(certs[0]) if len(certs) > 1: chain_file: Path | None = ca_file else: