Improve UX when users specify invalid certs (#7073)

* improve UX when users specify invalid certs

if we don't do this ourselves, OpenSSL will greet users with the ever-fascinating 'no shared ciphers' error during the first handshake.

* fixup test

* fix: include intermediate certs for QUIC

* [autofix.ci] apply automated fixes

* warn if `certs` has a certificate that's a CA

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Maximilian Hils 2024-08-04 00:13:46 +02:00 committed by GitHub
parent 0ff08f975e
commit aa73608ef6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 306 additions and 17 deletions

View File

@ -7,6 +7,10 @@
## Unreleased: mitmproxy next
- Improve the error message when users specify the `certs` option without a matching private key.
([#7073](https://github.com/mitmproxy/mitmproxy/pull/7073), @mhils)
- Fix a bug where intermediate certificates would not be transmitted when using QUIC.
([#7073](https://github.com/mitmproxy/mitmproxy/pull/7073), @mhils)
## 02 August 2024: mitmproxy 10.4.2

View File

@ -1,6 +1,7 @@
import contextlib
import datetime
import ipaddress
import logging
import os
import sys
import warnings
@ -19,12 +20,15 @@ from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import dsa
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric.types import CertificatePublicKeyTypes
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.x509 import ExtendedKeyUsageOID
from cryptography.x509 import NameOID
from mitmproxy.coretypes import serializable
logger = logging.getLogger(__name__)
# Default expiry must not be too long: https://github.com/mitmproxy/mitmproxy/issues/815
CA_EXPIRY = datetime.timedelta(days=10 * 365)
CERT_EXPIRY = datetime.timedelta(days=365)
@ -91,6 +95,9 @@ class Cert(serializable.Serializable):
def to_pyopenssl(self) -> OpenSSL.crypto.X509:
return OpenSSL.crypto.X509.from_cryptography(self._cert)
def public_key(self) -> CertificatePublicKeyTypes:
return self._cert.public_key()
def fingerprint(self) -> bytes:
return self._cert.fingerprint(hashes.SHA256())
@ -127,6 +134,17 @@ class Cert(serializable.Serializable):
def serial(self) -> int:
return self._cert.serial_number
@property
def is_ca(self) -> bool:
constraints: x509.BasicConstraints
try:
constraints = self._cert.extensions.get_extension_for_class(
x509.BasicConstraints
).value
return constraints.ca
except x509.ExtensionNotFound:
return False
@property
def keyinfo(self) -> tuple[str, int]:
public_key = self._cert.public_key()
@ -502,11 +520,34 @@ class CertStore:
raw = path.read_bytes()
cert = Cert.from_pem(raw)
try:
key = load_pem_private_key(raw, password=passphrase)
except ValueError:
key = self.default_privatekey
private_key = load_pem_private_key(raw, password=passphrase)
except ValueError as e:
private_key = self.default_privatekey
if cert.public_key() != private_key.public_key():
raise ValueError(
f'Unable to find private key in "{path.absolute()}": {e}'
) from e
else:
if cert.public_key() != private_key.public_key():
raise ValueError(
f'Private and public keys in "{path.absolute()}" do not match:\n'
f"{cert.public_key()=}\n"
f"{private_key.public_key()=}"
)
self.add_cert(CertStoreEntry(cert, key, path, [cert]), spec)
try:
chain = [Cert(x) for x in x509.load_pem_x509_certificates(raw)]
except ValueError as e:
logger.warning(f"Failed to read certificate chain: {e}")
chain = [cert]
if cert.is_ca:
logger.warning(
f'"{path.absolute()}" is a certificate authority and not a leaf certificate. '
f"This indicates a misconfiguration, see https://docs.mitmproxy.org/stable/concepts-certificates/."
)
self.add_cert(CertStoreEntry(cert, private_key, path, chain), spec)
def add_cert(self, entry: CertStoreEntry, *names: str) -> None:
"""

View File

@ -92,3 +92,22 @@ for x in ["self-signed", "trusted-leaf", "trusted-leaf-ip", "trusted-root"]:
pem.write(key.read())
shutil.copyfile("trusted-leaf.pem", "example.mitmproxy.org.pem")
with (
open(f"trusted-leaf.crt") as crt,
open(f"self-signed.key") as key,
open(f"private-public-mismatch.pem", "w") as pem,
):
pem.write(crt.read())
pem.write(key.read())
with (
open(f"trusted-leaf.pem") as crt1,
open(f"trusted-root.crt") as crt2,
open(f"trusted-chain.pem", "w") as pem,
):
pem.write(crt1.read())
pem.write(crt2.read())
with open(f"trusted-leaf.pem") as crt1, open(f"trusted-chain-invalid.pem", "w") as pem:
pem.write(crt1.read())
pem.write("-----BEGIN CERTIFICATE-----\nnotacert\n-----END CERTIFICATE-----\n")

View File

@ -0,0 +1,48 @@
-----BEGIN CERTIFICATE-----
MIIDZDCCAkygAwIBAgIUSWoLwl7Rc//TKPwKwv25Vhb6uFAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDExMDMwNzAzMjZaFw00MDEw
MjkwNzAzMjZaMDQxHjAcBgNVBAMMFWV4YW1wbGUubWl0bXByb3h5Lm9yZzESMBAG
A1UECgwJbWl0bXByb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
qcEdB7TZD7DxJlJH/ZTyrovP0din2vLCRZRZ07ix48arITGWj8wFJ/XpZsFmAWlH
2LriXGC8BB54EJt8Epix5tcdKvCZ/7vTgaBQ+DQVdCNiscaKwvDbigy037OY97sT
6Ou9xYjjgg0cb+WTkQGMOTMbCW2e5iuSvyMPdZ2+2RzbTcNV1/6O/5cQ7TOW2E83
xeaN4stBMfRmsQRoHXBri0tqtQ3bHd3V4H9iai7HFBOcB/nsJUwLliZpz3j/dUD0
yZAgdgB9XBlGvs/e746bGctyvl2QZu2JeACEVPmVrAUhqe2TVMVLDT17uzNeH1x5
UXxdB4cexTOm4amvWSwvTQIDAQABo10wWzAfBgNVHSMEGDAWgBTkDj66Wmaf9YP0
YMqGKG51PejxWzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDAgBgNVHREEGTAXghVl
eGFtcGxlLm1pdG1wcm94eS5vcmcwDQYJKoZIhvcNAQELBQADggEBABwKrf+Gy1I5
BnfhAqbDUYj8OVcjbB5esbvVYAADNNNQ10RREyz5OTTo2dSKy2hIVQeGxaBy5W6d
cNk0CoLzOgpgkwQPQ351+Y0C5yQztXIg/JM5aSeFuAbQg+NtzOQISrcDB390Xr2f
7YhyYL0XLX4NCP7ek7+lAClrf+lL9MysHTTP0pmFWLPP6AIR8JIsLpNpTDV10c8p
QcnJ8+5q6oLHNKN+M/HRcdKzj/XyJ538UzSOlmU+izhNT1j7mbszS0tc9yh0dmpP
9uuBY0wcD/NAHBpSX9v9ZJcOuWntsAxTbMXj1eWNRyt0QJLURlkqeVn6ZrE1/BDS
BqDdOKYJbkw=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAw1IZNjnWZOU8jJqlHVK45PLOpr+y8U0ks52OT6pDhTBWGFkn
hEffHAey/kSwSoszdb7e19VF0SLj4wXr36X7CKkJfFhzHrFOjg16ciNo0GRic82K
4VvlC0qRJVXakTVhN/Tm/zNL90GJLTBMti47jmDup52I8j39/PSb8n/6IWMfNMqt
6fqSDYfeATgzeK0jxG+SEpCm+iqeAdzLE3ZotZJMtZ3CTiQfZ5eJamHSJ8pURlZD
fha8JyS+DzeeIZEvA2QGWgDRigS4n20p7ykrwUxaUbLh2RV2n+kVy+CwgdsgFfE1
40HI5MJ3WU7/TfoxcKUJF5gB3zmkhwD+s22i5QIDAQABAoIBADK7Gi1JbHQcTlO+
vvAU0k00+5O36sRd4xB79cCfWpY3bcU5Mthayoo/PbBpKtjRuvX0M3EfxdiCFWqb
2R3nwIIJVZtkZdIs/1hKC+mlZM3rpN6rHk1WTvFV1sk5uWFJ2gxsoarbKfn4naaN
Cv+ulm1uo84JTs6MZ3HSHscnklIlNnDG3piC8JnzXVgS7kzbJPXTccZJI1Mwgyho
D1HoWatIep5XAp3OZePlApfRpKn/+2Hg6USkbcHYzX/XpA3b3/YEBqsXVUFxAs5m
nChdLpKUCIjcARInAXoULh0AQHGcH9dfJk1QF1Lw6nCoY12P1sZfdGU5trcslA1O
W28syoECgYEA6GjaqFYffceJHlODukHgpLNfa1EUy49ZWjmdmXTYXJpufzjD4n4H
sUkEnJ00ssaCVdd0XClbN/l/QvIAz3W8ZXXQWtZJHV2wAIyjWpqiOwaiXQFZIZ67
kxGsLVjkuiST33CX6yl8IKAD+8B67uZhmWTnYBmxGnIZODqUatpqW3ECgYEA1yV+
CGoNp6VQqej0t0y8C8pXiMGe2DGj5p1WFn00cn/fC1E2Yc0HG+E62OB7tgUFI180
/nArOQHnTZCzAQAc44M63gesRMeu+0cxuTUqOUCn1lGD+4K/3IA59Rgzfvvnq6xu
tn2jidfqaDPfKavqtCqRbs8wJYaGEhgcb8pUvLUCgYEA4pjpKFvgFGip/lF7C+0T
NEJXdHD3j4lSmy+1w1szYQaJWa1k/73VjjsdLf3w1aXKihuprfn8oFS4ifMeayfl
6h62aPqpCuK/qal10+8U4ewT/g5Ecw0q4bfHYedcC0mCi8ZhuL0X809Q0vLWaXti
CYdiOEaUcK5yfGpRLuWJ8WECgYAWWXa2OQ4iFDJE9EY3pGkEcIiXVEXD/6QfGMkQ
nQENw+rPqigUENBkPQl37hnr1qmp+wHuTIiw61mz3Qw7Vl+p4sACwJlMq9GpmMO5
kaRJPkYxJVaokfSMW2Wp6FGxJ0nxs3/sxTBv6VYYbQsJsSo4fROOh0dhHpBe4NJT
aplS4QKBgQCaMUN88HNaVr0+DPQKeFoflqwCCYsdK43QTWEirrFDWDliJ0k81DZJ
k7N3oR5UujsTMgfYDJ6Cp3enZVYyuoFnOlVT8v4XzvzAGTehyo4rMiE2TbMir7Xn
agPTZYSuQc4Iy7BPwh/PMlX6bDOTeq/ftyWrDuF9+pC5w3v4SkuMew==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,51 @@
-----BEGIN CERTIFICATE-----
MIIDZDCCAkygAwIBAgIUSWoLwl7Rc//TKPwKwv25Vhb6uFAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDExMDMwNzAzMjZaFw00MDEw
MjkwNzAzMjZaMDQxHjAcBgNVBAMMFWV4YW1wbGUubWl0bXByb3h5Lm9yZzESMBAG
A1UECgwJbWl0bXByb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
qcEdB7TZD7DxJlJH/ZTyrovP0din2vLCRZRZ07ix48arITGWj8wFJ/XpZsFmAWlH
2LriXGC8BB54EJt8Epix5tcdKvCZ/7vTgaBQ+DQVdCNiscaKwvDbigy037OY97sT
6Ou9xYjjgg0cb+WTkQGMOTMbCW2e5iuSvyMPdZ2+2RzbTcNV1/6O/5cQ7TOW2E83
xeaN4stBMfRmsQRoHXBri0tqtQ3bHd3V4H9iai7HFBOcB/nsJUwLliZpz3j/dUD0
yZAgdgB9XBlGvs/e746bGctyvl2QZu2JeACEVPmVrAUhqe2TVMVLDT17uzNeH1x5
UXxdB4cexTOm4amvWSwvTQIDAQABo10wWzAfBgNVHSMEGDAWgBTkDj66Wmaf9YP0
YMqGKG51PejxWzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDAgBgNVHREEGTAXghVl
eGFtcGxlLm1pdG1wcm94eS5vcmcwDQYJKoZIhvcNAQELBQADggEBABwKrf+Gy1I5
BnfhAqbDUYj8OVcjbB5esbvVYAADNNNQ10RREyz5OTTo2dSKy2hIVQeGxaBy5W6d
cNk0CoLzOgpgkwQPQ351+Y0C5yQztXIg/JM5aSeFuAbQg+NtzOQISrcDB390Xr2f
7YhyYL0XLX4NCP7ek7+lAClrf+lL9MysHTTP0pmFWLPP6AIR8JIsLpNpTDV10c8p
QcnJ8+5q6oLHNKN+M/HRcdKzj/XyJ538UzSOlmU+izhNT1j7mbszS0tc9yh0dmpP
9uuBY0wcD/NAHBpSX9v9ZJcOuWntsAxTbMXj1eWNRyt0QJLURlkqeVn6ZrE1/BDS
BqDdOKYJbkw=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAqcEdB7TZD7DxJlJH/ZTyrovP0din2vLCRZRZ07ix48arITGW
j8wFJ/XpZsFmAWlH2LriXGC8BB54EJt8Epix5tcdKvCZ/7vTgaBQ+DQVdCNiscaK
wvDbigy037OY97sT6Ou9xYjjgg0cb+WTkQGMOTMbCW2e5iuSvyMPdZ2+2RzbTcNV
1/6O/5cQ7TOW2E83xeaN4stBMfRmsQRoHXBri0tqtQ3bHd3V4H9iai7HFBOcB/ns
JUwLliZpz3j/dUD0yZAgdgB9XBlGvs/e746bGctyvl2QZu2JeACEVPmVrAUhqe2T
VMVLDT17uzNeH1x5UXxdB4cexTOm4amvWSwvTQIDAQABAoIBAEb3arF8E3qR2F7S
6zHCASqjXIA3+QR5lGoOOPdgMU4uEgDQgEchXc506dyBYamZX+XlSxifgDqgmkUn
G1mS6Fy+9XysFVVqKmP4p6D79TQWTv5PKFeS5dTytvMGXB7E4O/xDeb08Ve/L3JA
Ic7vPLX0/YqVf2ZuNO0fNSlQhyawUVlSS6yq/B1GUo44PX/PXrWYZd66mc29g+KF
7D0nnbloLY+5QmOEVYbf99RHJ64Cwxzwxi4ic9AxLaNMfkrydjbGQ0VhJq14WEux
/TJLvinzIDyslnx4aYstno3ryoxLtR/9ALysrdQ8vVMaVCdbf0aMu832R2dI4s9K
ixQlgmkCgYEA0Ho0a4qzwk9oLWYk1x16kOCqmfuenlEz3Tv8uBB/jdz7UM63sS3h
OUQYKhhtzOGf6CDA3CE11ie6SX7zHrowPLJBSpacJZJcrHPGZxdBZ/As/QpGnZll
1eTMlVRXk06k/fEYplU1dS5IDbsvUIaaF9CF9Qd3MkDRglhbM19Iy+sCgYEA0HM1
d7OBsjSi09zFDE+Joq6Gih8LqqFLtMsIHEKAJ0gJgmt6MTfjtdPo55ZKVLCR2pbP
yRHagK4LRUFY9Pl5eGlEUF9cKEKKtZOURUFVz3yBMYPBje/B1o/UCCj+nr2reWgx
qj/jwZrph6QKCkEsnQXi98tE7XSgihUh6UEQO6cCgYASVe0uWDCfMmSzOXyb/te8
zkWy7VJyEipBlvkPJ0RQsdLYtJWrW6Gna7nEWgmuL1nlDJxpv/IAN9ZGiIfReAau
D+92I/DvzQOhlz0n6/+wqIsMZk73pXozacAkkhpxtkUEoKPOXUgqWju0GXZ72prK
5WgiuNle7hx/Hk5HImZAqQKBgQCK7W4iRGpZeklXiNlvtgcWfNlAbyaYZ34MlhDm
vM+q3pEv8i/zY7uJcR3WU81gmnnrRP5hlVuazeTHGKGQTEFQJmCYbKYAUzEdiamV
atElQ2bbuGOlFLmNJjj7406oP+NsPCx1urUyUOv6MjNa2EtCsCywWDKtTEC/Jwx9
6JZIGwKBgDa4Ephl+lcyGC8D9Tdc4Nme6n/10pIN53ZCplqVSuma+kOOQX7ZXpJb
0csHR9KODcpQuo2yOnUfZ1z4+tsjms2Uv97GgcZ9sO84I/3RGJCvxmzAEiRmJMfH
y38GtEYCnat5VOKdo//eIYtiu9YSXuuS1ENSWUPZ9YInrkUk0j8S
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
notacert
-----END CERTIFICATE-----

View File

@ -0,0 +1,69 @@
-----BEGIN CERTIFICATE-----
MIIDZDCCAkygAwIBAgIUSWoLwl7Rc//TKPwKwv25Vhb6uFAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDExMDMwNzAzMjZaFw00MDEw
MjkwNzAzMjZaMDQxHjAcBgNVBAMMFWV4YW1wbGUubWl0bXByb3h5Lm9yZzESMBAG
A1UECgwJbWl0bXByb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
qcEdB7TZD7DxJlJH/ZTyrovP0din2vLCRZRZ07ix48arITGWj8wFJ/XpZsFmAWlH
2LriXGC8BB54EJt8Epix5tcdKvCZ/7vTgaBQ+DQVdCNiscaKwvDbigy037OY97sT
6Ou9xYjjgg0cb+WTkQGMOTMbCW2e5iuSvyMPdZ2+2RzbTcNV1/6O/5cQ7TOW2E83
xeaN4stBMfRmsQRoHXBri0tqtQ3bHd3V4H9iai7HFBOcB/nsJUwLliZpz3j/dUD0
yZAgdgB9XBlGvs/e746bGctyvl2QZu2JeACEVPmVrAUhqe2TVMVLDT17uzNeH1x5
UXxdB4cexTOm4amvWSwvTQIDAQABo10wWzAfBgNVHSMEGDAWgBTkDj66Wmaf9YP0
YMqGKG51PejxWzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDAgBgNVHREEGTAXghVl
eGFtcGxlLm1pdG1wcm94eS5vcmcwDQYJKoZIhvcNAQELBQADggEBABwKrf+Gy1I5
BnfhAqbDUYj8OVcjbB5esbvVYAADNNNQ10RREyz5OTTo2dSKy2hIVQeGxaBy5W6d
cNk0CoLzOgpgkwQPQ351+Y0C5yQztXIg/JM5aSeFuAbQg+NtzOQISrcDB390Xr2f
7YhyYL0XLX4NCP7ek7+lAClrf+lL9MysHTTP0pmFWLPP6AIR8JIsLpNpTDV10c8p
QcnJ8+5q6oLHNKN+M/HRcdKzj/XyJ538UzSOlmU+izhNT1j7mbszS0tc9yh0dmpP
9uuBY0wcD/NAHBpSX9v9ZJcOuWntsAxTbMXj1eWNRyt0QJLURlkqeVn6ZrE1/BDS
BqDdOKYJbkw=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAqcEdB7TZD7DxJlJH/ZTyrovP0din2vLCRZRZ07ix48arITGW
j8wFJ/XpZsFmAWlH2LriXGC8BB54EJt8Epix5tcdKvCZ/7vTgaBQ+DQVdCNiscaK
wvDbigy037OY97sT6Ou9xYjjgg0cb+WTkQGMOTMbCW2e5iuSvyMPdZ2+2RzbTcNV
1/6O/5cQ7TOW2E83xeaN4stBMfRmsQRoHXBri0tqtQ3bHd3V4H9iai7HFBOcB/ns
JUwLliZpz3j/dUD0yZAgdgB9XBlGvs/e746bGctyvl2QZu2JeACEVPmVrAUhqe2T
VMVLDT17uzNeH1x5UXxdB4cexTOm4amvWSwvTQIDAQABAoIBAEb3arF8E3qR2F7S
6zHCASqjXIA3+QR5lGoOOPdgMU4uEgDQgEchXc506dyBYamZX+XlSxifgDqgmkUn
G1mS6Fy+9XysFVVqKmP4p6D79TQWTv5PKFeS5dTytvMGXB7E4O/xDeb08Ve/L3JA
Ic7vPLX0/YqVf2ZuNO0fNSlQhyawUVlSS6yq/B1GUo44PX/PXrWYZd66mc29g+KF
7D0nnbloLY+5QmOEVYbf99RHJ64Cwxzwxi4ic9AxLaNMfkrydjbGQ0VhJq14WEux
/TJLvinzIDyslnx4aYstno3ryoxLtR/9ALysrdQ8vVMaVCdbf0aMu832R2dI4s9K
ixQlgmkCgYEA0Ho0a4qzwk9oLWYk1x16kOCqmfuenlEz3Tv8uBB/jdz7UM63sS3h
OUQYKhhtzOGf6CDA3CE11ie6SX7zHrowPLJBSpacJZJcrHPGZxdBZ/As/QpGnZll
1eTMlVRXk06k/fEYplU1dS5IDbsvUIaaF9CF9Qd3MkDRglhbM19Iy+sCgYEA0HM1
d7OBsjSi09zFDE+Joq6Gih8LqqFLtMsIHEKAJ0gJgmt6MTfjtdPo55ZKVLCR2pbP
yRHagK4LRUFY9Pl5eGlEUF9cKEKKtZOURUFVz3yBMYPBje/B1o/UCCj+nr2reWgx
qj/jwZrph6QKCkEsnQXi98tE7XSgihUh6UEQO6cCgYASVe0uWDCfMmSzOXyb/te8
zkWy7VJyEipBlvkPJ0RQsdLYtJWrW6Gna7nEWgmuL1nlDJxpv/IAN9ZGiIfReAau
D+92I/DvzQOhlz0n6/+wqIsMZk73pXozacAkkhpxtkUEoKPOXUgqWju0GXZ72prK
5WgiuNle7hx/Hk5HImZAqQKBgQCK7W4iRGpZeklXiNlvtgcWfNlAbyaYZ34MlhDm
vM+q3pEv8i/zY7uJcR3WU81gmnnrRP5hlVuazeTHGKGQTEFQJmCYbKYAUzEdiamV
atElQ2bbuGOlFLmNJjj7406oP+NsPCx1urUyUOv6MjNa2EtCsCywWDKtTEC/Jwx9
6JZIGwKBgDa4Ephl+lcyGC8D9Tdc4Nme6n/10pIN53ZCplqVSuma+kOOQX7ZXpJb
0csHR9KODcpQuo2yOnUfZ1z4+tsjms2Uv97GgcZ9sO84I/3RGJCvxmzAEiRmJMfH
y38GtEYCnat5VOKdo//eIYtiu9YSXuuS1ENSWUPZ9YInrkUk0j8S
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUB5WLNdtHRvciUwDfySAEHYVSNe0wDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDExMDMwNzAzMjZaFw00MDEw
MjkwNzAzMjZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDVWUg2YdsztWIZpsWdbIB6Aewa1Igt7mNkKMQ1NJ6v
priVlzWhZr0FfSVTid2m3Od2f5A751Q+IXLkNJ8d6Y4qVirAstEM57sCH/Hmt1E3
cZOAzLQY2xCIrKPmbu6d8eSGO1q+Y8KstCK/V94/QRahjjoHxkLcSAmdg6PAkbhB
7MwOxiJslIbsBY4UCXP0l4kUUIuMi8W2Y4M1VgvLRpUjaKVo1NQ0hLi4XBnU7Bsq
A8/PZDuVeP0NcJtAxRicqqLX/MjgkTrA2tPnSj9m60lO79S40GSzB238o2zJQ5ON
jHJdAhLlrsrkxZmDmKM59IdmV2OhN7O844ktbXDOqvq5AgMBAAGjUzBRMB0GA1Ud
DgQWBBTkDj66Wmaf9YP0YMqGKG51PejxWzAfBgNVHSMEGDAWgBTkDj66Wmaf9YP0
YMqGKG51PejxWzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAo
9FwbGob1WRAo7ORShaDhopIDAYMCL1oc9YZ2ZQK8aDuZbqIZ/7+1LbWCeMAV4PTH
Tcx+n9Zg/g/RkoSNu8KqFoQGFmdBZnKOMU4vlITu/ORpDu1sjSA+Eo9YbipemX+n
jv+YHI4STFAWnyext3IUZVkT6wpU3pwUjX0fbk4LJfVLR41y4iD11XGergxAcpjj
T03txkJcrTiX65SnB041Y4exUMLOUn5lTs/q2rBNkiLNljXQ6l+8L1rdQEN/j0Mx
OYdc6FIUIESC1mMOf80+YOwxPJ862SyTv/cJB3npwTj/DdQu7blf/z2hMP7a7w+X
l5d31XzcDOrCf/bTthvb
-----END CERTIFICATE-----

View File

@ -108,19 +108,6 @@ class TestCertStore:
assert ("three.com", x509.GeneralNames([])) in tstore.certs
assert ("four.com", x509.GeneralNames([])) in tstore.certs
def test_overrides(self, tmp_path):
ca1 = certs.CertStore.from_store(tmp_path / "ca1", "test", 2048)
ca2 = certs.CertStore.from_store(tmp_path / "ca2", "test", 2048)
assert not ca1.default_ca.serial == ca2.default_ca.serial
dc = ca2.get_cert("foo.com", [x509.DNSName("sans.example.com")])
dcp = tmp_path / "dc"
dcp.write_bytes(dc.cert.to_pem())
ca1.add_cert_file("foo.com", dcp)
ret = ca1.get_cert("foo.com", [])
assert ret.cert.serial == dc.cert.serial
def test_create_dhparams(self, tmp_path):
filename = tmp_path / "dhparam.pem"
certs.CertStore.load_dhparam(filename)
@ -203,6 +190,7 @@ class TestCert:
assert c2.cn == "www.inode.co.nz"
assert len(c2.altnames) == 2
assert c2.fingerprint()
assert c2.public_key()
assert c2.notbefore == datetime(
year=2010,
month=1,
@ -257,6 +245,19 @@ class TestCert:
c = certs.Cert.from_pem(d)
assert c.keyinfo == (name, bits)
@pytest.mark.parametrize(
"filename,is_ca",
[
("mitmproxy/net/data/verificationcerts/trusted-leaf.crt", False),
("mitmproxy/net/data/verificationcerts/trusted-root.crt", True),
("mitmproxy/data/invalid-subject.pem", False), # no basic constraints
],
)
def test_is_ca(self, tdata, filename, is_ca):
pem = Path(tdata.path(filename)).read_bytes()
cert = certs.Cert.from_pem(pem)
assert cert.is_ca == is_ca
def test_err_broken_sans(self, tdata):
with open(tdata.path("mitmproxy/net/data/text_cert_weird1"), "rb") as f:
d = f.read()
@ -280,6 +281,14 @@ class TestCert:
c2.set_state(a)
assert c == c2
def test_add_cert_overrides(self, tdata, tstore):
certfile = Path(
tdata.path("mitmproxy/net/data/verificationcerts/trusted-leaf.pem")
)
cert = certs.Cert.from_pem(certfile.read_bytes())
tstore.add_cert_file("example.com", certfile)
assert cert == tstore.get_cert("example.com", []).cert
def test_from_store_with_passphrase(self, tdata, tstore):
tstore.add_cert_file(
"unencrypted-no-pass", Path(tdata.path("mitmproxy/data/testkey.pem")), None
@ -302,6 +311,54 @@ class TestCert:
None,
)
def test_add_cert_with_no_private_key(self, tdata, tstore):
with pytest.raises(ValueError, match="Unable to find private key"):
tstore.add_cert_file(
"example.com",
Path(
tdata.path("mitmproxy/net/data/verificationcerts/trusted-leaf.crt")
),
)
def test_add_cert_private_public_mismatch(self, tdata, tstore):
with pytest.raises(
ValueError, match='Private and public keys in ".+" do not match'
):
tstore.add_cert_file(
"example.com",
Path(
tdata.path(
"mitmproxy/net/data/verificationcerts/private-public-mismatch.pem"
)
),
)
def test_add_cert_chain(self, tdata, tstore):
tstore.add_cert_file(
"example.com",
Path(tdata.path("mitmproxy/net/data/verificationcerts/trusted-chain.pem")),
)
assert len(tstore.get_cert("example.com", []).chain_certs) == 2
def test_add_cert_chain_invalid(self, tdata, tstore, caplog):
tstore.add_cert_file(
"example.com",
Path(
tdata.path(
"mitmproxy/net/data/verificationcerts/trusted-chain-invalid.pem"
)
),
)
assert "Failed to read certificate chain" in caplog.text
assert len(tstore.get_cert("example.com", []).chain_certs) == 1
def test_add_cert_is_ca(self, tdata, tstore, caplog):
tstore.add_cert_file(
"example.com",
Path(tdata.path("mitmproxy/net/data/verificationcerts/trusted-root.pem")),
)
assert "is a certificate authority and not a leaf certificate" in caplog.text
def test_special_character(self, tdata):
with open(tdata.path("mitmproxy/net/data/text_cert_with_comma"), "rb") as f:
d = f.read()