Edit on GitHub

mitmproxy.certs

  1import contextlib
  2import datetime
  3import ipaddress
  4import logging
  5import os
  6import sys
  7import warnings
  8from collections.abc import Iterable
  9from dataclasses import dataclass
 10from pathlib import Path
 11from typing import cast
 12from typing import NewType
 13from typing import Optional
 14from typing import Union
 15
 16import OpenSSL
 17from cryptography import x509
 18from cryptography.hazmat.primitives import hashes
 19from cryptography.hazmat.primitives import serialization
 20from cryptography.hazmat.primitives.asymmetric import dsa
 21from cryptography.hazmat.primitives.asymmetric import ec
 22from cryptography.hazmat.primitives.asymmetric import rsa
 23from cryptography.hazmat.primitives.asymmetric.types import CertificatePublicKeyTypes
 24from cryptography.hazmat.primitives.serialization import pkcs12
 25from cryptography.x509 import ExtendedKeyUsageOID
 26from cryptography.x509 import NameOID
 27
 28from mitmproxy.coretypes import serializable
 29
 30if sys.version_info < (3, 13):  # pragma: no cover
 31    from typing_extensions import deprecated
 32else:
 33    from warnings import deprecated
 34
 35logger = logging.getLogger(__name__)
 36
 37# Default expiry must not be too long: https://github.com/mitmproxy/mitmproxy/issues/815
 38# Note that expiry will be offset by `CERT_VALIDITY_OFFSET`, i.e. the cert will be
 39# backdated a bit to account for clients with incorrect clocks.
 40CA_EXPIRY = datetime.timedelta(days=10 * 365)
 41CERT_EXPIRY = datetime.timedelta(days=199)
 42CRL_EXPIRY = datetime.timedelta(days=7)
 43
 44CERT_VALIDITY_OFFSET = datetime.timedelta(days=-2)
 45
 46# Generated with "openssl dhparam". It's too slow to generate this on startup.
 47DEFAULT_DHPARAM = b"""
 48-----BEGIN DH PARAMETERS-----
 49MIICCAKCAgEAyT6LzpwVFS3gryIo29J5icvgxCnCebcdSe/NHMkD8dKJf8suFCg3
 50O2+dguLakSVif/t6dhImxInJk230HmfC8q93hdcg/j8rLGJYDKu3ik6H//BAHKIv
 51j5O9yjU3rXCfmVJQic2Nne39sg3CreAepEts2TvYHhVv3TEAzEqCtOuTjgDv0ntJ
 52Gwpj+BJBRQGG9NvprX1YGJ7WOFBP/hWU7d6tgvE6Xa7T/u9QIKpYHMIkcN/l3ZFB
 53chZEqVlyrcngtSXCROTPcDOQ6Q8QzhaBJS+Z6rcsd7X+haiQqvoFcmaJ08Ks6LQC
 54ZIL2EtYJw8V8z7C0igVEBIADZBI6OTbuuhDwRw//zU1uq52Oc48CIZlGxTYG/Evq
 55o9EWAXUYVzWkDSTeBH1r4z/qLPE2cnhtMxbFxuvK53jGB0emy2y1Ei6IhKshJ5qX
 56IB/aE7SSHyQ3MDHHkCmQJCsOd4Mo26YX61NZ+n501XjqpCBQ2+DfZCBh8Va2wDyv
 57A2Ryg9SUz8j0AXViRNMJgJrr446yro/FuJZwnQcO3WQnXeqSBnURqKjmqkeFP+d8
 586mk2tqJaY507lRNqtGlLnj7f5RNoBFJDCLBNurVgfvq9TCVWKDIFD4vZRjCrnl6I
 59rD693XKIHUCWOjMh1if6omGXKHH40QuME2gNa50+YPn1iYDl88uDbbMCAQI=
 60-----END DH PARAMETERS-----
 61"""
 62
 63# Compromised eMudhra intermediate CA (EM OV TLS CA - G3C, d=4), see https://emudhra.we-have-a-problem.com/.
 64# Contains:
 65# 1. Private key for EM OV TLS CA - G3C (P-384, d=4).
 66# 2. EM OV TLS CA - G3C issued by emSign Root TLS CA - G3.
 67# 3. emSign Root TLS CA - G3 cross-signed by emSign ECC Root CA - G3.
 68# 4. EM OV TLS CA - G3C cross-signed by Sectigo's AAA Certificate Services (for older pre-2021 trust stores).
 69#
 70# Path-building TLS stacks (Go, Rust, Java, macOS, Windows, browsers) consider all certificates in the chain
 71# and validate against any of the three roots (emSign ECC Root CA - G3, emSign Root TLS CA - G3, or
 72# AAA Certificate Services). OpenSSL does not backtrack across untrusted intermediates with the same
 73# Subject/SKI and only follows the first matching intermediate: --emudhra keeps the emSign-issued intermediate
 74# first (since Mozilla's root bundle used by Python/certifi, curl, Node.js, and Linux ca-certificates already
 75# removed AAA Certificate Services in 2025), while --emudhra-old moves the AAA-issued intermediate (#4) to the
 76# front for older OpenSSL trust stores.
 77EMUDHRA_CHAIN_PEM = b"""
 78-----BEGIN PRIVATE KEY-----
 79MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAAAAAAAAAAAAAAAAAA
 80AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAShZANiAAQTglHNUqyS
 81mMHIqtl3Mh3rl+cJvQtMoKylXcitUdz8nRWJoVl+OlEg4e/WMcY+GDXKyuKYaaYu
 82FjHoooGBq1ZhbcRdkYq8CfOrDmPPeSqk3O1zh743u6VpVJ8cArJw7Wc=
 83-----END PRIVATE KEY-----
 84-----BEGIN CERTIFICATE-----
 85MIIDaDCCAu6gAwIBAgIPKWkmwiejoVJjkrGUgqUEMAoGCCqGSM49BAMDMFYxCzAJ
 86BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk
 87MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM3
 88NDFaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk
 89aHJhIEluYzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C
 90AQYFK4EEACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZ
 91fjpRIOHv1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+
 92N7ulaVSfHAKycO1no4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L
 93WUIgkFQwHQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMB0GA1UdJQQWMBQG
 94CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow
 95KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB
 96BAGDjiEBAm4wCAYGZ4EMAQICMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY
 97aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw
 98b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNT1ZUTFNDQUczQy5wN2MwNwYDVR0f
 99BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j
100cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E
101AwMDaAAwZQIxAMSZt2xsPeumRdeSMSQ3CbzIZeQ/q7GDCWfeE1Pq+mhgA6gRM44c
102NVF3xJ+wsxJk+wIwFJbaDMokD8Ei6eNOiA8yUMYvfzQyngmrhZQMdPCK1SewrR79
1034rSb9vGqxZzelQth
104-----END CERTIFICATE-----
105-----BEGIN CERTIFICATE-----
106MIIDdDCCAvmgAwIBAgIQAJYaPvsTWVdu0EAFtGhKWzAKBggqhkjOPQQDAzBrMQsw
107CQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhy
108YSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290
109IENBIC0gRzMwHhcNMjQwNzExMTU0MzM2WhcNMzkwNzA5MTgzMDAwWjBWMQswCQYD
110VQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg
111MB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwdjAQBgcqhkjOPQIBBgUr
112gQQAIgNiAARfD9w/q1cWxL6D9bMGH6CQ4u+gAV6GN9s+PwQx9Bl4hC+0iU8QOk8m
113WO/RwXQy10DCSxT8vSIJClHKp+Ao4qJKdYeV0SwZ7LMQziyLfxIUOY6Grja/sQFw
114LKYf8EdDXAyjggF1MIIBcTAfBgNVHSMEGDAWgBR8XQKEE9TMipuBzhccLikenEhj
115QjAdBgNVHQ4EFgQU4TqrIXzx401UrtRyt/2LWUIgkFQwHQYDVR0lBBYwFAYIKwYB
116BQUHAwEGCCsGAQUFBwMCMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIB
117FhxodHRwOi8vcmVwb3NpdG9yeS5lbXNpZ24uY29tMHgGCCsGAQUFBwEBBGwwajAi
118BggrBgEFBQcwAYYWaHR0cDovL29jc3AuZW1zaWduLmNvbTBEBggrBgEFBQcwAoY4
119aHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbS9jZXJ0cy9lbVNpZ25FQ0NSb290
120Q0FHMy5jcnQwMwYDVR0fBCwwKjAooCagJIYiaHR0cDovL2NybC5lbXNpZ24uY29t
121P1Jvb3RDQUczLmNybDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIB
122ATAKBggqhkjOPQQDAwNpADBmAjEAt9CIpRGp46QHQYDUBnLKw+vvK0/h9vmilEAg
123TroUl/anH4x2rXHqiCtBTBfgKDegAjEA8WpG1LTZdAIL0FSYqxXeREaeTSxEofM0
1241jJH3jGvCXC79LU9UPJ2o3TU+UIj2gMV
125-----END CERTIFICATE-----
126-----BEGIN CERTIFICATE-----
127MIIDzzCCAregAwIBAgIRAI1aSBGTnvRH13ERzdmEmukwDQYJKoZIhvcNAQELBQAw
128ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
129A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
130BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y
131ODEyMzEyMzU5NTlaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVkaHJhIElu
132YzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0CAQYFK4EE
133ACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZfjpRIOHv
1341jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+N7ulaVSf
135HAKycO1no4IBNTCCATEwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQw
136HQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMA4GA1UdDwEB/wQEAwIBBjAS
137BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
138AjAxBgNVHSAEKjAoMAgGBmeBDAECAjANBgsrBgEEAYOOIQEAATANBgsrBgEEAYOO
139IQECbjBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9B
140QUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQoMCYwJAYIKwYB
141BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG9w0BAQsFAAOC
142AQEAHlbhZpjhkPEAQVhPHRtBDaHqiisVD20pQ1TUeSx3l62Qe7NQ0b7vWQy7DN0i
143KOu7BGpP5EnIjvXbz9FhpfPJ4FhKEt3tQ51rqXz2oD8vRaryvExWPobDUz3j7HN6
144OzBsVMIe3PD7AAkpky0x8zMSi/ep5R9OMzybmNptbtppyuB5ytgq+h7lQdRIP+CQ
145TaJX3qTgfMqzntYre5GljmEB5hU2/rSn8zoSU2APkJidlZFpghtgbD4I2VRHZvCw
146FFr/SrFM9pwrgbJNZ1MtyTSVgI5NlNb0aMfLR+/r6jJt3ROeY2yh2QQ2dx+Yqgdb
147uL5ChFWOQU+ErSDnUq7yr+pjBQ==
148-----END CERTIFICATE-----
149"""
150
151
152class Cert(serializable.Serializable):
153    """Representation of a (TLS) certificate."""
154
155    _cert: x509.Certificate
156
157    def __init__(self, cert: x509.Certificate):
158        assert isinstance(cert, x509.Certificate)
159        self._cert = cert
160
161    def __eq__(self, other):
162        return self.fingerprint() == other.fingerprint()
163
164    def __repr__(self):
165        altnames = [str(x.value) for x in self.altnames]
166        return f"<Cert(cn={self.cn!r}, altnames={altnames!r})>"
167
168    def __hash__(self):
169        return self._cert.__hash__()
170
171    @classmethod
172    def from_state(cls, state):
173        return cls.from_pem(state)
174
175    def get_state(self):
176        return self.to_pem()
177
178    def set_state(self, state):
179        self._cert = x509.load_pem_x509_certificate(state)
180
181    @classmethod
182    def from_pem(cls, data: bytes) -> "Cert":
183        cert = x509.load_pem_x509_certificate(data)  # type: ignore
184        return cls(cert)
185
186    def to_pem(self) -> bytes:
187        return self._cert.public_bytes(serialization.Encoding.PEM)
188
189    @classmethod
190    def from_pyopenssl(self, x509: OpenSSL.crypto.X509) -> "Cert":
191        return Cert(x509.to_cryptography())
192
193    @deprecated("Use `to_cryptography` instead.")
194    def to_pyopenssl(self) -> OpenSSL.crypto.X509:  # pragma: no cover
195        return OpenSSL.crypto.X509.from_cryptography(self._cert)
196
197    def to_cryptography(self) -> x509.Certificate:
198        return self._cert
199
200    def public_key(self) -> CertificatePublicKeyTypes:
201        return self._cert.public_key()
202
203    def fingerprint(self) -> bytes:
204        return self._cert.fingerprint(hashes.SHA256())
205
206    @property
207    def issuer(self) -> list[tuple[str, str]]:
208        return _name_to_keyval(self._cert.issuer)
209
210    @property
211    def notbefore(self) -> datetime.datetime:
212        try:
213            # type definitions haven't caught up with new API yet.
214            return self._cert.not_valid_before_utc  # type: ignore
215        except AttributeError:  # pragma: no cover
216            # cryptography < 42.0
217            return self._cert.not_valid_before.replace(tzinfo=datetime.UTC)
218
219    @property
220    def notafter(self) -> datetime.datetime:
221        try:
222            return self._cert.not_valid_after_utc  # type: ignore
223        except AttributeError:  # pragma: no cover
224            return self._cert.not_valid_after.replace(tzinfo=datetime.UTC)
225
226    def has_expired(self) -> bool:
227        if sys.version_info < (3, 11):  # pragma: no cover
228            return datetime.datetime.now(datetime.UTC) > self.notafter
229        return datetime.datetime.now(datetime.UTC) > self.notafter
230
231    @property
232    def subject(self) -> list[tuple[str, str]]:
233        return _name_to_keyval(self._cert.subject)
234
235    @property
236    def serial(self) -> int:
237        return self._cert.serial_number
238
239    @property
240    def is_ca(self) -> bool:
241        constraints: x509.BasicConstraints
242        try:
243            constraints = self._cert.extensions.get_extension_for_class(
244                x509.BasicConstraints
245            ).value
246            return constraints.ca
247        except x509.ExtensionNotFound:
248            return False
249
250    @property
251    def keyinfo(self) -> tuple[str, int]:
252        public_key = self._cert.public_key()
253        if isinstance(public_key, rsa.RSAPublicKey):
254            return "RSA", public_key.key_size
255        if isinstance(public_key, dsa.DSAPublicKey):
256            return "DSA", public_key.key_size
257        if isinstance(public_key, ec.EllipticCurvePublicKey):
258            return f"EC ({public_key.curve.name})", public_key.key_size
259        return (
260            public_key.__class__.__name__.replace("PublicKey", "").replace("_", ""),
261            getattr(public_key, "key_size", -1),
262        )  # pragma: no cover
263
264    @property
265    def cn(self) -> str | None:
266        attrs = self._cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
267        if attrs:
268            return cast(str, attrs[0].value)
269        return None
270
271    @property
272    def organization(self) -> str | None:
273        attrs = self._cert.subject.get_attributes_for_oid(
274            x509.NameOID.ORGANIZATION_NAME
275        )
276        if attrs:
277            return cast(str, attrs[0].value)
278        return None
279
280    @property
281    def altnames(self) -> x509.GeneralNames:
282        """
283        Get all SubjectAlternativeName DNS altnames.
284        """
285        try:
286            sans = self._cert.extensions.get_extension_for_class(
287                x509.SubjectAlternativeName
288            ).value
289        except x509.ExtensionNotFound:
290            return x509.GeneralNames([])
291        else:
292            return x509.GeneralNames(sans)
293
294    @property
295    def crl_distribution_points(self) -> list[str]:
296        try:
297            ext = self._cert.extensions.get_extension_for_class(
298                x509.CRLDistributionPoints
299            ).value
300        except x509.ExtensionNotFound:
301            return []
302        else:
303            return [
304                dist_point.full_name[0].value
305                for dist_point in ext
306                if dist_point.full_name
307                and isinstance(dist_point.full_name[0], x509.UniformResourceIdentifier)
308            ]
309
310
311def _name_to_keyval(name: x509.Name) -> list[tuple[str, str]]:
312    parts = []
313    for attr in name:
314        k = attr.rfc4514_string().partition("=")[0]
315        v = cast(str, attr.value)
316        parts.append((k, v))
317    return parts
318
319
320def create_ca(
321    organization: str,
322    cn: str,
323    key_size: int,
324) -> tuple[rsa.RSAPrivateKeyWithSerialization, x509.Certificate]:
325    now = datetime.datetime.now()
326
327    private_key = rsa.generate_private_key(
328        public_exponent=65537,
329        key_size=key_size,
330    )  # type: ignore
331    name = x509.Name(
332        [
333            x509.NameAttribute(NameOID.COMMON_NAME, cn),
334            x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
335        ]
336    )
337    builder = x509.CertificateBuilder()
338    builder = builder.serial_number(x509.random_serial_number())
339    builder = builder.subject_name(name)
340    builder = builder.not_valid_before(now + CERT_VALIDITY_OFFSET)
341    builder = builder.not_valid_after(now + CERT_VALIDITY_OFFSET + CA_EXPIRY)
342    builder = builder.issuer_name(name)
343    builder = builder.public_key(private_key.public_key())
344    builder = builder.add_extension(
345        x509.BasicConstraints(ca=True, path_length=None), critical=True
346    )
347    builder = builder.add_extension(
348        x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]), critical=False
349    )
350    builder = builder.add_extension(
351        x509.KeyUsage(
352            digital_signature=False,
353            content_commitment=False,
354            key_encipherment=False,
355            data_encipherment=False,
356            key_agreement=False,
357            key_cert_sign=True,
358            crl_sign=True,
359            encipher_only=False,
360            decipher_only=False,
361        ),
362        critical=True,
363    )
364    builder = builder.add_extension(
365        x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()),
366        critical=False,
367    )
368    cert = builder.sign(private_key=private_key, algorithm=hashes.SHA256())  # type: ignore
369    return private_key, cert
370
371
372def _fix_legacy_sans(sans: Iterable[x509.GeneralName] | list[str]) -> x509.GeneralNames:
373    """
374    SANs used to be a list of strings in mitmproxy 10.1 and below, but now they're a list of GeneralNames.
375    This function converts the old format to the new one.
376    """
377    if isinstance(sans, x509.GeneralNames):
378        return sans
379    elif (
380        isinstance(sans, list) and len(sans) > 0 and isinstance(sans[0], str)
381    ):  # pragma: no cover
382        warnings.warn(
383            "Passing SANs as a list of strings is deprecated.",
384            DeprecationWarning,
385            stacklevel=2,
386        )
387
388        ss: list[x509.GeneralName] = []
389        for x in cast(list[str], sans):
390            try:
391                ip = ipaddress.ip_address(x)
392            except ValueError:
393                x = x.encode("idna").decode()
394                ss.append(x509.DNSName(x))
395            else:
396                ss.append(x509.IPAddress(ip))
397        return x509.GeneralNames(ss)
398    else:
399        return x509.GeneralNames(cast(Iterable[x509.GeneralName], sans))
400
401
402def dummy_cert(
403    privkey: rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey,
404    cacert: x509.Certificate,
405    commonname: str | None,
406    sans: Iterable[x509.GeneralName],
407    organization: str | None = None,
408    crl_url: str | None = None,
409) -> Cert:
410    """
411    Generates a dummy certificate.
412
413    privkey: CA private key
414    cacert: CA certificate
415    commonname: Common name for the generated certificate.
416    sans: A list of Subject Alternate Names.
417    organization: Organization name for the generated certificate.
418    crl_url: URL of CRL distribution point
419
420    Returns cert if operation succeeded, None if not.
421    """
422    builder = x509.CertificateBuilder()
423    builder = builder.issuer_name(cacert.subject)
424    builder = builder.add_extension(
425        x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]), critical=False
426    )
427    builder = builder.public_key(cacert.public_key())
428
429    now = datetime.datetime.now()
430    builder = builder.not_valid_before(now + CERT_VALIDITY_OFFSET)
431    builder = builder.not_valid_after(now + CERT_VALIDITY_OFFSET + CERT_EXPIRY)
432
433    subject = []
434    is_valid_commonname = commonname is not None and len(commonname) < 64
435    if is_valid_commonname:
436        assert commonname is not None
437        subject.append(x509.NameAttribute(NameOID.COMMON_NAME, commonname))
438    if organization is not None:
439        assert organization is not None
440        subject.append(x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization))
441    builder = builder.subject_name(x509.Name(subject))
442    builder = builder.serial_number(x509.random_serial_number())
443
444    sans = _fix_legacy_sans(sans)
445    if sans:
446        # RFC 5280 §4.2.1.6: subjectAltName is critical if subject is empty.
447        builder = builder.add_extension(
448            x509.SubjectAlternativeName(sans),
449            critical=not is_valid_commonname,
450        )
451
452    # https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
453    # Per RFC 5280 §4.2.1.2, the AKI's keyIdentifier in a child certificate
454    # MUST be byte-equal to the issuer's stored SubjectKeyIdentifier. If we
455    # recompute it from the public key with `from_issuer_public_key()`, we
456    # always derive a SHA-1 digest, which mismatches whenever the issuer's
457    # SKI was generated by a different method (RFC 7093 truncated
458    # SHA-256/384/512, hardware-rooted CAs, or any custom value). This
459    # mismatch is rejected by strict TLS chain builders (`X509_V_FLAG_X509_STRICT`,
460    # Python `ssl`, Go `crypto/x509`) with "authority and subject key
461    # identifier mismatch". Most notably, cert-manager >=1.18 / Go >=1.25
462    # default to truncated SHA-256 SKIs for FIPS 140-3 compliance.
463    try:
464        issuer_ski = cacert.extensions.get_extension_for_class(
465            x509.SubjectKeyIdentifier
466        ).value
467        aki = x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(issuer_ski)
468    except x509.ExtensionNotFound:
469        # Fall back when the issuer cert has no SKI extension at all (legacy
470        # CAs predating RFC 5280 conformance).
471        aki = x509.AuthorityKeyIdentifier.from_issuer_public_key(cacert.public_key())  # type: ignore
472    builder = builder.add_extension(aki, critical=False)
473    # If CA and leaf cert have the same Subject Key Identifier, SChannel breaks in funny ways,
474    # see https://github.com/mitmproxy/mitmproxy/issues/6494.
475    # https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2 states
476    # that SKI is optional for the leaf cert, so we skip that.
477
478    if crl_url:
479        builder = builder.add_extension(
480            x509.CRLDistributionPoints(
481                [
482                    x509.DistributionPoint(
483                        [x509.UniformResourceIdentifier(crl_url)],
484                        relative_name=None,
485                        crl_issuer=None,
486                        reasons=None,
487                    )
488                ]
489            ),
490            critical=False,
491        )
492
493    cert = builder.sign(private_key=privkey, algorithm=hashes.SHA256())  # type: ignore
494    return Cert(cert)
495
496
497def dummy_crl(
498    privkey: rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey,
499    cacert: x509.Certificate,
500) -> bytes:
501    """
502    Generates an empty CRL signed with the CA key
503
504    privkey: CA private key
505    cacert: CA certificate
506
507    Returns a CRL DER encoded
508    """
509    builder = x509.CertificateRevocationListBuilder()
510    builder = builder.issuer_name(cacert.issuer)
511
512    now = datetime.datetime.now()
513    builder = builder.last_update(now + CERT_VALIDITY_OFFSET)
514    builder = builder.next_update(now + CERT_VALIDITY_OFFSET + CRL_EXPIRY)
515
516    builder = builder.add_extension(x509.CRLNumber(1000), False)  # meaningless number
517    crl = builder.sign(private_key=privkey, algorithm=hashes.SHA256())
518    return crl.public_bytes(serialization.Encoding.DER)
519
520
521@dataclass(frozen=True)
522class CertStoreEntry:
523    cert: Cert
524    privatekey: rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey
525    chain_file: Path | None
526    chain_certs: list[Cert]
527
528
529TCustomCertId = str  # manually provided certs (e.g. mitmproxy's --certs)
530TGeneratedCertId = tuple[Optional[str], x509.GeneralNames]  # (common_name, sans)
531TCertId = Union[TCustomCertId, TGeneratedCertId]
532
533DHParams = NewType("DHParams", bytes)
534
535
536class CertStore:
537    """
538    Implements an in-memory certificate store.
539    """
540
541    STORE_CAP = 100
542    default_privatekey: rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey
543    default_ca: Cert
544    default_chain_file: Path | None
545    default_chain_certs: list[Cert]
546    dhparams: DHParams
547    certs: dict[TCertId, CertStoreEntry]
548    expire_queue: list[CertStoreEntry]
549
550    def __init__(
551        self,
552        default_privatekey: rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey,
553        default_ca: Cert,
554        default_chain_file: Path | None,
555        default_crl: bytes,
556        dhparams: DHParams,
557    ):
558        self.default_privatekey = default_privatekey
559        self.default_ca = default_ca
560        self.default_chain_file = default_chain_file
561        self.default_crl = default_crl
562        self.default_chain_certs = (
563            [
564                Cert(c)
565                for c in x509.load_pem_x509_certificates(
566                    self.default_chain_file.read_bytes()
567                )
568            ]
569            if self.default_chain_file
570            else [default_ca]
571        )
572        self.dhparams = dhparams
573        self.certs = {}
574        self.expire_queue = []
575
576    def expire(self, entry: CertStoreEntry) -> None:
577        self.expire_queue.append(entry)
578        if len(self.expire_queue) > self.STORE_CAP:
579            d = self.expire_queue.pop(0)
580            self.certs = {k: v for k, v in self.certs.items() if v != d}
581
582    @staticmethod
583    def load_dhparam(path: Path) -> DHParams:
584        # mitmproxy<=0.10 doesn't generate a dhparam file.
585        # Create it now if necessary.
586        if not path.exists():
587            path.write_bytes(DEFAULT_DHPARAM)
588
589        # we could use cryptography for this, but it's unclear how to convert cryptography's object to pyOpenSSL's
590        # expected format.
591        bio = OpenSSL.SSL._lib.BIO_new_file(  # type: ignore
592            str(path).encode(sys.getfilesystemencoding()), b"r"
593        )
594        if bio != OpenSSL.SSL._ffi.NULL:  # type: ignore
595            bio = OpenSSL.SSL._ffi.gc(bio, OpenSSL.SSL._lib.BIO_free)  # type: ignore
596            dh = OpenSSL.SSL._lib.PEM_read_bio_DHparams(  # type: ignore
597                bio,
598                OpenSSL.SSL._ffi.NULL,  # type: ignore
599                OpenSSL.SSL._ffi.NULL,  # type: ignore
600                OpenSSL.SSL._ffi.NULL,  # type: ignore
601            )
602            dh = OpenSSL.SSL._ffi.gc(dh, OpenSSL.SSL._lib.DH_free)  # type: ignore
603            return dh
604        raise RuntimeError("Error loading DH Params.")  # pragma: no cover
605
606    @classmethod
607    def from_store(
608        cls,
609        path: Path | str,
610        basename: str,
611        key_size: int,
612        passphrase: bytes | None = None,
613    ) -> "CertStore":
614        path = Path(path)
615        ca_file = path / f"{basename}-ca.pem"
616        dhparam_file = path / f"{basename}-dhparam.pem"
617        if not ca_file.exists():
618            cls.create_store(path, basename, key_size)
619        return cls.from_files(ca_file, dhparam_file, passphrase)
620
621    @classmethod
622    def from_files(
623        cls, ca_file: Path, dhparam_file: Path, passphrase: bytes | None = None
624    ) -> "CertStore":
625        raw = ca_file.read_bytes()
626        key = load_pem_private_key(raw, passphrase)
627        dh = cls.load_dhparam(dhparam_file)
628        certs = x509.load_pem_x509_certificates(raw)
629        ca = Cert(certs[0])
630        crl = dummy_crl(key, ca._cert)
631        if len(certs) > 1:
632            chain_file: Path | None = ca_file
633        else:
634            chain_file = None
635        return cls(key, ca, chain_file, crl, dh)
636
637    @staticmethod
638    @contextlib.contextmanager
639    def umask_secret():
640        """
641        Context to temporarily set umask to its original value bitor 0o77.
642        Useful when writing private keys to disk so that only the owner
643        will be able to read them.
644        """
645        original_umask = os.umask(0)
646        os.umask(original_umask | 0o77)
647        try:
648            yield
649        finally:
650            os.umask(original_umask)
651
652    @staticmethod
653    def create_store(
654        path: Path, basename: str, key_size: int, organization=None, cn=None
655    ) -> None:
656        path.mkdir(parents=True, exist_ok=True)
657
658        organization = organization or basename
659        cn = cn or basename
660
661        key: rsa.RSAPrivateKeyWithSerialization
662        ca: x509.Certificate
663        key, ca = create_ca(organization=organization, cn=cn, key_size=key_size)
664
665        # Dump the CA plus private key.
666        with CertStore.umask_secret():
667            # PEM format
668            (path / f"{basename}-ca.pem").write_bytes(
669                key.private_bytes(
670                    encoding=serialization.Encoding.PEM,
671                    format=serialization.PrivateFormat.TraditionalOpenSSL,
672                    encryption_algorithm=serialization.NoEncryption(),
673                )
674                + ca.public_bytes(serialization.Encoding.PEM)
675            )
676
677            # PKCS12 format for Windows devices
678            (path / f"{basename}-ca.p12").write_bytes(
679                pkcs12.serialize_key_and_certificates(  # type: ignore
680                    name=basename.encode(),
681                    key=key,
682                    cert=ca,
683                    cas=None,
684                    encryption_algorithm=serialization.NoEncryption(),
685                )
686            )
687
688        # Dump the certificate in PEM format
689        pem_cert = ca.public_bytes(serialization.Encoding.PEM)
690        (path / f"{basename}-ca-cert.pem").write_bytes(pem_cert)
691        # Create a .cer file with the same contents for Android
692        (path / f"{basename}-ca-cert.cer").write_bytes(pem_cert)
693
694        # Dump the certificate in PKCS12 format for Windows devices
695        (path / f"{basename}-ca-cert.p12").write_bytes(
696            pkcs12.serialize_key_and_certificates(
697                name=basename.encode(),
698                key=None,  # type: ignore
699                cert=ca,
700                cas=None,
701                encryption_algorithm=serialization.NoEncryption(),
702            )
703        )
704
705        (path / f"{basename}-dhparam.pem").write_bytes(DEFAULT_DHPARAM)
706
707    def add_cert_file(
708        self, spec: str, path: Path, passphrase: bytes | None = None
709    ) -> None:
710        raw = path.read_bytes()
711        cert = Cert.from_pem(raw)
712        try:
713            private_key = load_pem_private_key(raw, password=passphrase)
714        except ValueError as e:
715            private_key = self.default_privatekey
716            if cert.public_key() != private_key.public_key():
717                raise ValueError(
718                    f'Unable to find private key in "{path.absolute()}": {e}'
719                ) from e
720        else:
721            if cert.public_key() != private_key.public_key():
722                raise ValueError(
723                    f'Private and public keys in "{path.absolute()}" do not match:\n'
724                    f"{cert.public_key()=}\n"
725                    f"{private_key.public_key()=}"
726                )
727
728        try:
729            chain = [Cert(x) for x in x509.load_pem_x509_certificates(raw)]
730        except ValueError as e:
731            logger.warning(f"Failed to read certificate chain: {e}")
732            chain = [cert]
733
734        if cert.is_ca:
735            logger.warning(
736                f'"{path.absolute()}" is a certificate authority and not a leaf certificate. '
737                f"This indicates a misconfiguration, see https://docs.mitmproxy.org/stable/concepts-certificates/."
738            )
739
740        self.add_cert(CertStoreEntry(cert, private_key, path, chain), spec)
741
742    def add_cert(self, entry: CertStoreEntry, *names: str) -> None:
743        """
744        Adds a cert to the certstore. We register the CN in the cert plus
745        any SANs, and also the list of names provided as an argument.
746        """
747        if entry.cert.cn:
748            self.certs[entry.cert.cn] = entry
749        for i in entry.cert.altnames:
750            self.certs[str(i.value)] = entry
751        for i in names:
752            self.certs[i] = entry
753
754    @staticmethod
755    def asterisk_forms(dn: str | x509.GeneralName) -> list[str]:
756        """
757        Return all asterisk forms for a domain. For example, for www.example.com this will return
758        [b"www.example.com", b"*.example.com", b"*.com"]. The single wildcard "*" is omitted.
759        """
760        if isinstance(dn, str):
761            parts = dn.split(".")
762            ret = [dn]
763            for i in range(1, len(parts)):
764                ret.append("*." + ".".join(parts[i:]))
765            return ret
766        elif isinstance(dn, x509.DNSName):
767            return CertStore.asterisk_forms(dn.value)
768        else:
769            return [str(dn.value)]
770
771    def get_cert(
772        self,
773        commonname: str | None,
774        sans: Iterable[x509.GeneralName],
775        organization: str | None = None,
776        crl_url: str | None = None,
777    ) -> CertStoreEntry:
778        """
779        commonname: Common name for the generated certificate. Must be a
780        valid, plain-ASCII, IDNA-encoded domain name.
781
782        sans: A list of Subject Alternate Names.
783
784        organization: Organization name for the generated certificate.
785
786        crl_url: URL of CRL distribution point
787        """
788        sans = _fix_legacy_sans(sans)
789
790        potential_keys: list[TCertId] = []
791        if commonname:
792            potential_keys.extend(self.asterisk_forms(commonname))
793        for s in sans:
794            potential_keys.extend(self.asterisk_forms(s))
795        potential_keys.append("*")
796        potential_keys.append((commonname, sans))
797
798        name = next(filter(lambda key: key in self.certs, potential_keys), None)
799        if name:
800            entry = self.certs[name]
801        else:
802            entry = CertStoreEntry(
803                cert=dummy_cert(
804                    self.default_privatekey,
805                    self.default_ca._cert,
806                    commonname,
807                    sans,
808                    organization,
809                    crl_url,
810                ),
811                privatekey=self.default_privatekey,
812                chain_file=self.default_chain_file,
813                chain_certs=self.default_chain_certs,
814            )
815            self.certs[(commonname, sans)] = entry
816            self.expire(entry)
817
818        return entry
819
820
821def load_pem_private_key(
822    data: bytes, password: bytes | None
823) -> rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey:
824    """
825    like cryptography's load_pem_private_key, but silently falls back to not using a password
826    if the private key is unencrypted.
827    """
828    try:
829        return serialization.load_pem_private_key(data, password)  # type: ignore
830    except TypeError:
831        if password is not None:
832            return load_pem_private_key(data, None)
833        raise
class Cert(mitmproxy.coretypes.serializable.Serializable):
153class Cert(serializable.Serializable):
154    """Representation of a (TLS) certificate."""
155
156    _cert: x509.Certificate
157
158    def __init__(self, cert: x509.Certificate):
159        assert isinstance(cert, x509.Certificate)
160        self._cert = cert
161
162    def __eq__(self, other):
163        return self.fingerprint() == other.fingerprint()
164
165    def __repr__(self):
166        altnames = [str(x.value) for x in self.altnames]
167        return f"<Cert(cn={self.cn!r}, altnames={altnames!r})>"
168
169    def __hash__(self):
170        return self._cert.__hash__()
171
172    @classmethod
173    def from_state(cls, state):
174        return cls.from_pem(state)
175
176    def get_state(self):
177        return self.to_pem()
178
179    def set_state(self, state):
180        self._cert = x509.load_pem_x509_certificate(state)
181
182    @classmethod
183    def from_pem(cls, data: bytes) -> "Cert":
184        cert = x509.load_pem_x509_certificate(data)  # type: ignore
185        return cls(cert)
186
187    def to_pem(self) -> bytes:
188        return self._cert.public_bytes(serialization.Encoding.PEM)
189
190    @classmethod
191    def from_pyopenssl(self, x509: OpenSSL.crypto.X509) -> "Cert":
192        return Cert(x509.to_cryptography())
193
194    @deprecated("Use `to_cryptography` instead.")
195    def to_pyopenssl(self) -> OpenSSL.crypto.X509:  # pragma: no cover
196        return OpenSSL.crypto.X509.from_cryptography(self._cert)
197
198    def to_cryptography(self) -> x509.Certificate:
199        return self._cert
200
201    def public_key(self) -> CertificatePublicKeyTypes:
202        return self._cert.public_key()
203
204    def fingerprint(self) -> bytes:
205        return self._cert.fingerprint(hashes.SHA256())
206
207    @property
208    def issuer(self) -> list[tuple[str, str]]:
209        return _name_to_keyval(self._cert.issuer)
210
211    @property
212    def notbefore(self) -> datetime.datetime:
213        try:
214            # type definitions haven't caught up with new API yet.
215            return self._cert.not_valid_before_utc  # type: ignore
216        except AttributeError:  # pragma: no cover
217            # cryptography < 42.0
218            return self._cert.not_valid_before.replace(tzinfo=datetime.UTC)
219
220    @property
221    def notafter(self) -> datetime.datetime:
222        try:
223            return self._cert.not_valid_after_utc  # type: ignore
224        except AttributeError:  # pragma: no cover
225            return self._cert.not_valid_after.replace(tzinfo=datetime.UTC)
226
227    def has_expired(self) -> bool:
228        if sys.version_info < (3, 11):  # pragma: no cover
229            return datetime.datetime.now(datetime.UTC) > self.notafter
230        return datetime.datetime.now(datetime.UTC) > self.notafter
231
232    @property
233    def subject(self) -> list[tuple[str, str]]:
234        return _name_to_keyval(self._cert.subject)
235
236    @property
237    def serial(self) -> int:
238        return self._cert.serial_number
239
240    @property
241    def is_ca(self) -> bool:
242        constraints: x509.BasicConstraints
243        try:
244            constraints = self._cert.extensions.get_extension_for_class(
245                x509.BasicConstraints
246            ).value
247            return constraints.ca
248        except x509.ExtensionNotFound:
249            return False
250
251    @property
252    def keyinfo(self) -> tuple[str, int]:
253        public_key = self._cert.public_key()
254        if isinstance(public_key, rsa.RSAPublicKey):
255            return "RSA", public_key.key_size
256        if isinstance(public_key, dsa.DSAPublicKey):
257            return "DSA", public_key.key_size
258        if isinstance(public_key, ec.EllipticCurvePublicKey):
259            return f"EC ({public_key.curve.name})", public_key.key_size
260        return (
261            public_key.__class__.__name__.replace("PublicKey", "").replace("_", ""),
262            getattr(public_key, "key_size", -1),
263        )  # pragma: no cover
264
265    @property
266    def cn(self) -> str | None:
267        attrs = self._cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
268        if attrs:
269            return cast(str, attrs[0].value)
270        return None
271
272    @property
273    def organization(self) -> str | None:
274        attrs = self._cert.subject.get_attributes_for_oid(
275            x509.NameOID.ORGANIZATION_NAME
276        )
277        if attrs:
278            return cast(str, attrs[0].value)
279        return None
280
281    @property
282    def altnames(self) -> x509.GeneralNames:
283        """
284        Get all SubjectAlternativeName DNS altnames.
285        """
286        try:
287            sans = self._cert.extensions.get_extension_for_class(
288                x509.SubjectAlternativeName
289            ).value
290        except x509.ExtensionNotFound:
291            return x509.GeneralNames([])
292        else:
293            return x509.GeneralNames(sans)
294
295    @property
296    def crl_distribution_points(self) -> list[str]:
297        try:
298            ext = self._cert.extensions.get_extension_for_class(
299                x509.CRLDistributionPoints
300            ).value
301        except x509.ExtensionNotFound:
302            return []
303        else:
304            return [
305                dist_point.full_name[0].value
306                for dist_point in ext
307                if dist_point.full_name
308                and isinstance(dist_point.full_name[0], x509.UniformResourceIdentifier)
309            ]

Representation of a (TLS) certificate.

Cert(cert: cryptography.hazmat.bindings._rust.x509.Certificate)
158    def __init__(self, cert: x509.Certificate):
159        assert isinstance(cert, x509.Certificate)
160        self._cert = cert
@classmethod
def from_pem(cls, data: bytes) -> Cert:
182    @classmethod
183    def from_pem(cls, data: bytes) -> "Cert":
184        cert = x509.load_pem_x509_certificate(data)  # type: ignore
185        return cls(cert)
def to_pem(self) -> bytes:
187    def to_pem(self) -> bytes:
188        return self._cert.public_bytes(serialization.Encoding.PEM)
@classmethod
def from_pyopenssl(self, x509: OpenSSL.crypto.X509) -> Cert:
190    @classmethod
191    def from_pyopenssl(self, x509: OpenSSL.crypto.X509) -> "Cert":
192        return Cert(x509.to_cryptography())
@deprecated('Use `to_cryptography` instead.')
def to_pyopenssl(self) -> OpenSSL.crypto.X509:
194    @deprecated("Use `to_cryptography` instead.")
195    def to_pyopenssl(self) -> OpenSSL.crypto.X509:  # pragma: no cover
196        return OpenSSL.crypto.X509.from_cryptography(self._cert)
def to_cryptography(self) -> cryptography.hazmat.bindings._rust.x509.Certificate:
198    def to_cryptography(self) -> x509.Certificate:
199        return self._cert
def public_key( self) -> cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey | cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey | cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey | cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey | cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA44PublicKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA65PublicKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA87PublicKey | cryptography.hazmat.primitives.asymmetric.mlkem.MLKEM768PublicKey | cryptography.hazmat.primitives.asymmetric.mlkem.MLKEM1024PublicKey | cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey | cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey:
201    def public_key(self) -> CertificatePublicKeyTypes:
202        return self._cert.public_key()
def fingerprint(self) -> bytes:
204    def fingerprint(self) -> bytes:
205        return self._cert.fingerprint(hashes.SHA256())
issuer: list[tuple[str, str]]
207    @property
208    def issuer(self) -> list[tuple[str, str]]:
209        return _name_to_keyval(self._cert.issuer)
notbefore: datetime.datetime
211    @property
212    def notbefore(self) -> datetime.datetime:
213        try:
214            # type definitions haven't caught up with new API yet.
215            return self._cert.not_valid_before_utc  # type: ignore
216        except AttributeError:  # pragma: no cover
217            # cryptography < 42.0
218            return self._cert.not_valid_before.replace(tzinfo=datetime.UTC)
notafter: datetime.datetime
220    @property
221    def notafter(self) -> datetime.datetime:
222        try:
223            return self._cert.not_valid_after_utc  # type: ignore
224        except AttributeError:  # pragma: no cover
225            return self._cert.not_valid_after.replace(tzinfo=datetime.UTC)
def has_expired(self) -> bool:
227    def has_expired(self) -> bool:
228        if sys.version_info < (3, 11):  # pragma: no cover
229            return datetime.datetime.now(datetime.UTC) > self.notafter
230        return datetime.datetime.now(datetime.UTC) > self.notafter
subject: list[tuple[str, str]]
232    @property
233    def subject(self) -> list[tuple[str, str]]:
234        return _name_to_keyval(self._cert.subject)
serial: int
236    @property
237    def serial(self) -> int:
238        return self._cert.serial_number
is_ca: bool
240    @property
241    def is_ca(self) -> bool:
242        constraints: x509.BasicConstraints
243        try:
244            constraints = self._cert.extensions.get_extension_for_class(
245                x509.BasicConstraints
246            ).value
247            return constraints.ca
248        except x509.ExtensionNotFound:
249            return False
keyinfo: tuple[str, int]
251    @property
252    def keyinfo(self) -> tuple[str, int]:
253        public_key = self._cert.public_key()
254        if isinstance(public_key, rsa.RSAPublicKey):
255            return "RSA", public_key.key_size
256        if isinstance(public_key, dsa.DSAPublicKey):
257            return "DSA", public_key.key_size
258        if isinstance(public_key, ec.EllipticCurvePublicKey):
259            return f"EC ({public_key.curve.name})", public_key.key_size
260        return (
261            public_key.__class__.__name__.replace("PublicKey", "").replace("_", ""),
262            getattr(public_key, "key_size", -1),
263        )  # pragma: no cover
cn: str | None
265    @property
266    def cn(self) -> str | None:
267        attrs = self._cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
268        if attrs:
269            return cast(str, attrs[0].value)
270        return None
organization: str | None
272    @property
273    def organization(self) -> str | None:
274        attrs = self._cert.subject.get_attributes_for_oid(
275            x509.NameOID.ORGANIZATION_NAME
276        )
277        if attrs:
278            return cast(str, attrs[0].value)
279        return None
altnames: cryptography.x509.extensions.GeneralNames
281    @property
282    def altnames(self) -> x509.GeneralNames:
283        """
284        Get all SubjectAlternativeName DNS altnames.
285        """
286        try:
287            sans = self._cert.extensions.get_extension_for_class(
288                x509.SubjectAlternativeName
289            ).value
290        except x509.ExtensionNotFound:
291            return x509.GeneralNames([])
292        else:
293            return x509.GeneralNames(sans)

Get all SubjectAlternativeName DNS altnames.

crl_distribution_points: list[str]
295    @property
296    def crl_distribution_points(self) -> list[str]:
297        try:
298            ext = self._cert.extensions.get_extension_for_class(
299                x509.CRLDistributionPoints
300            ).value
301        except x509.ExtensionNotFound:
302            return []
303        else:
304            return [
305                dist_point.full_name[0].value
306                for dist_point in ext
307                if dist_point.full_name
308                and isinstance(dist_point.full_name[0], x509.UniformResourceIdentifier)
309            ]
built with pdocpdoc logo