从 Let’s Encrypt 证书透明度日志自动提取域名

几天前,Let’s Encrypt 进入公开测试。在撰写本文时,已颁发了近 12 万张证书,包括 TechOverflow 的证书。

我非常喜欢 Let’s Encrypt 服务,我相信它可能真正改变人们对 HTTPS 加密的看法。然而,用他们的证书保护你的域名时有一个很少被提及的副作用。

Let’s Encrypt 在 crt.sh 发布证书透明度日志。然而,这种透明度并非没有副作用:crt.sh 实际上发布了。

换句话说,当你在 Let’s Encrypt 等服务上为域名颁发证书时,通过不在任何地方发布其(子)域名来对公众隐藏站点将不起作用。

为了演示,我快速拼凑了一个脚本,它会自动获取可定义数量的 crt.sh ID 并打印出它们的域名。它将从 crt.sh 数据库中存在的最新 Let’s Encrypt 证书开始。

像这样使用它来获取 1000 张证书:

usage.sh
$ python3 letsencrypt-domains.py 1000

注意 1000 个域名不一定对应 1000 个域名:一方面,人们在习惯 Let’s Encrypt 的机制时有时会重新颁发证书。另一方面,一张证书可能包含多个域名。

letsencrypt-domains.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
从证书透明度日志自动提取域名

从 crt.sh 透明度日志提取由 Let's Encrypt 颁发的证书的域名

仅供演示用途
"""
__copyright__ = "Copyright (c) 2015 Uli Köhler"
__license__ = "Apache License v2.0"
__version__ = "1.0.0"

import requests
import re
import itertools
from multiprocessing import Pool


domainRegex = re.compile(r"DNS:([A-Za-z0-9\.]+)<BR>")
certLinkRegex = re.compile(r'<A href="\?id=(\d+)">')


def fetchDomainsByID(i):
    "按 ID 获取 crt.sh 域名列表"
    response = requests.get("https://crt.sh/?id={0}".format(i))
    return domainRegex.findall(response.text)


def findLatestCACRTSHID(caid):
    """
    对于给定的 crt.sh CA ID,查找
    """
    response = requests.get("https://crt.sh/?Identity=%25&iCAID={0}".format(caid))
    return int(certLinkRegex.search(response.text).group(1))

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('n', type=int, default=100, help='要尝试的证书 ID 数量')
    parser.add_argument('-p', default=32, type=int, help='并行 IO 线程数')
    parser.add_argument('-q', '--quiet', action="store_true", help='抑制信息消息')
    args = parser.parse_args()

    pool = Pool(args.p)
    start = findLatestCACRTSHID(7395)
    n = args.n

    if not args.quiet:
        print("从 {1} 开始获取 {0} 个 crt.sh ID".format(n, start))

    # 使用池获取并提取域名
    domainLists = pool.map(fetchDomainsByID,
                           range(start, start - n, -1))
    domains = sorted(set(itertools.chain(*domainLists)))

    # 打印域名
    for domain in domains:
        print(domain)

Check out similar posts by category: Linux