You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于主机名拦截TLS连接并动态提供自定义证书?

实现mitmproxy动态TLS证书生成插件

核心思路

要实现基于主机名动态生成证书并拦截TLS连接,需挂钩mitmproxy的tls_start_client事件——该事件在mitmproxy准备向客户端发送ServerHello前触发,刚好可以替换默认证书。我们将用cryptography生成证书,基于mitmproxy的根CA签名,确保客户端信任。

完整插件代码

from mitmproxy import ctx
from mitmproxy.connection import Client
from mitmproxy.tls import TlsData
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
import datetime
import ssl

class DynamicCertGenerator:
    def __init__(self):
        # 缓存已生成的证书,避免重复计算
        self.cert_cache = {}
        # 加载mitmproxy的根CA证书和私钥
        root_cert_entry = ctx.options.certs[0]
        self.ca_cert = x509.load_pem_x509_certificate(
            root_cert_entry.certfile.read_bytes(),
            default_backend()
        )
        self.ca_key = serialization.load_pem_private_key(
            root_cert_entry.keyfile.read_bytes(),
            password=None,
            backend=default_backend()
        )

    def generate_cert(self, hostname: str) -> bytes:
        # 优先从缓存获取证书
        if hostname in self.cert_cache:
            return self.cert_cache[hostname]
        
        # 生成服务器RSA私钥
        server_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
            backend=default_backend()
        )

        # 构建证书主体与颁发者信息
        subject_issuer = x509.Name([
            x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "CA"),
            x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, "mitmproxy Dynamic Cert"),
            x509.NameAttribute(NameOID.COMMON_NAME, hostname),
        ])

        # 构建X509证书
        cert_builder = x509.CertificateBuilder()
        cert = (
            cert_builder.subject_name(subject_issuer)
            .issuer_name(subject_issuer)
            .public_key(server_key.public_key())
            .serial_number(x509.random_serial_number())
            .not_valid_before(datetime.datetime.utcnow())
            .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
            .add_extension(
                x509.SubjectAlternativeName([x509.DNSName(hostname)]),
                critical=False
            )
            .sign(self.ca_key, hashes.SHA256(), default_backend())
        )

        # 将证书和私钥打包为PEM格式
        cert_pem = cert.public_bytes(serialization.Encoding.PEM)
        key_pem = server_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        )
        combined_pem = key_pem + cert_pem
        
        self.cert_cache[hostname] = combined_pem
        return combined_pem

    def tls_start_client(self, data: TlsData):
        # 获取客户端请求的SNI主机名
        client: Client = data.context.client
        sni_hostname = client.sni

        if not sni_hostname:
            # 无SNI的请求,沿用mitmproxy默认逻辑
            return

        # 生成对应主机名的证书
        cert_pem = self.generate_cert(sni_hostname)

        # 加载证书为SSLContext格式,供mitmproxy使用
        ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        ssl_ctx.load_cert_chain(
            certfile=None,
            cert_data=cert_pem,
            keyfile=None,
            key_data=cert_pem.split(b"-----END PRIVATE KEY-----")[0] + b"-----END PRIVATE KEY-----"
        )

        # 替换客户端连接的mitmcert,mitmproxy会用此证书完成TLS握手
        client.mitmcert = ssl_ctx.get_cert_chain()

addons = [DynamicCertGenerator()]

关键细节说明

  1. 证书缓存:避免重复生成相同主机名的证书,大幅提升性能。
  2. 根CA依赖:动态证书必须由mitmproxy的根CA签名,否则客户端会提示证书不可信。你可以使用mitmproxy默认生成的CA,或启动时用--certs参数指定自定义CA。
  3. SNI获取:通过client.sni获取客户端TLS握手时发送的主机名,这是TLS阶段唯一能拿到主机名的方式。
  4. 证书格式:将生成的私钥和证书打包为PEM格式,适配mitmproxymitmcert属性所需的SSLContext证书链格式。

使用与验证

  1. 将代码保存为dynamic_certs.py
  2. 启动mitmproxy:mitmproxy -s dynamic_certs.py
  3. 配置客户端使用mitmproxy作为代理,并信任mitmproxy的根CA(可通过mitmproxy证书向导导出)
  4. 访问任意HTTPS网站,mitmproxy会自动生成对应域名的证书并完成拦截

内容的提问来源于stack exchange,提问作者Cyb3rCr0wCC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 21:07:28