tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

crtshToIdentifyingStruct.py (4883B)


      1 #!/usr/bin/env python3
      2 #
      3 # This Source Code Form is subject to the terms of the Mozilla Public
      4 # License, v. 2.0. If a copy of the MPL was not distributed with this
      5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      6 
      7 """
      8 This utility takes a series of https://crt.sh/ identifiers and writes to
      9 stdout all of those certs' distinguished name or SPKI fields in hex, with an
     10 array of all those. You'll need to post-process this list to handle any
     11 duplicates.
     12 
     13 Requires Python 3.
     14 """
     15 
     16 import argparse
     17 import io
     18 import re
     19 import sys
     20 
     21 import requests
     22 from cryptography import x509
     23 from cryptography.hazmat.backends import default_backend
     24 from cryptography.hazmat.primitives import hashes
     25 from cryptography.x509.oid import NameOID
     26 from pyasn1.codec.der import decoder, encoder
     27 from pyasn1_modules import pem, rfc5280
     28 
     29 assert sys.version_info >= (3, 2), "Requires Python 3.2 or later"
     30 
     31 
     32 def hex_string_for_struct(bytes):
     33    return [f"0x{x:02X}" for x in bytes]
     34 
     35 
     36 def hex_string_human_readable(bytes):
     37    return [f"{x:02X}" for x in bytes]
     38 
     39 
     40 def nameOIDtoString(oid):
     41    if oid == NameOID.COUNTRY_NAME:
     42        return "C"
     43    if oid == NameOID.COMMON_NAME:
     44        return "CN"
     45    if oid == NameOID.LOCALITY_NAME:
     46        return "L"
     47    if oid == NameOID.ORGANIZATION_NAME:
     48        return "O"
     49    if oid == NameOID.ORGANIZATIONAL_UNIT_NAME:
     50        return "OU"
     51    raise Exception(f"Unknown OID: {oid}")
     52 
     53 
     54 def print_block(pemData, identifierType="DN", crtshId=None):
     55    substrate = pem.readPemFromFile(io.StringIO(pemData.decode("utf-8")))
     56    cert, _ = decoder.decode(substrate, asn1Spec=rfc5280.Certificate())
     57    octets = None
     58 
     59    if identifierType == "DN":
     60        der_subject = encoder.encode(cert["tbsCertificate"]["subject"])
     61        octets = hex_string_for_struct(der_subject)
     62    elif identifierType == "SPKI":
     63        der_spki = encoder.encode(cert["tbsCertificate"]["subjectPublicKeyInfo"])
     64        octets = hex_string_for_struct(der_spki)
     65    else:
     66        raise Exception("Unknown identifier type: " + identifierType)
     67 
     68    cert = x509.load_pem_x509_certificate(pemData, default_backend())
     69    common_name = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0]
     70    block_name = "CA{}{}".format(
     71        re.sub(r"[-:=_. ]", "", common_name.value), identifierType
     72    )
     73 
     74    fingerprint = hex_string_human_readable(cert.fingerprint(hashes.SHA256()))
     75 
     76    dn_parts = [f"/{nameOIDtoString(part.oid)}={part.value}" for part in cert.subject]
     77    distinguished_name = "".join(dn_parts)
     78 
     79    print(f"// {distinguished_name}")
     80    print("// SHA256 Fingerprint: " + ":".join(fingerprint[:16]))
     81    print("//                     " + ":".join(fingerprint[16:]))
     82    if crtshId:
     83        print(f"// https://crt.sh/?id={crtshId} (crt.sh ID={crtshId})")
     84    print(f"static const uint8_t {block_name}[{len(octets)}] = " + "{")
     85 
     86    while len(octets) > 0:
     87        print("  " + ", ".join(octets[:13]) + ",")
     88        octets = octets[13:]
     89 
     90    print("};")
     91    print()
     92 
     93    return block_name
     94 
     95 
     96 if __name__ == "__main__":
     97    parser = argparse.ArgumentParser()
     98    parser.add_argument(
     99        "-spki",
    100        action="store_true",
    101        help="Create a list of subject public key info fields",
    102    )
    103    parser.add_argument(
    104        "-dn",
    105        action="store_true",
    106        help="Create a list of subject distinguished name fields",
    107    )
    108    parser.add_argument("-listname", help="Name of the final DataAndLength block")
    109    parser.add_argument(
    110        "certId", nargs="+", help="A list of PEM files on disk or crt.sh IDs"
    111    )
    112    args = parser.parse_args()
    113 
    114    if not args.dn and not args.spki:
    115        parser.print_help()
    116        raise Exception("You must select either DN or SPKI matching")
    117 
    118    blocks = []
    119 
    120    print(
    121        "// Script from security/manager/tools/crtshToIdentifyingStruct/"
    122        + "crtshToIdentifyingStruct.py"
    123    )
    124    print("// Invocation: {}".format(" ".join(sys.argv)))
    125    print()
    126 
    127    identifierType = None
    128    if args.dn:
    129        identifierType = "DN"
    130    else:
    131        identifierType = "SPKI"
    132 
    133    for certId in args.certId:
    134        # Try a local file first, then crt.sh
    135        try:
    136            with open(certId, "rb") as pemFile:
    137                blocks.append(
    138                    print_block(pemFile.read(), identifierType=identifierType)
    139                )
    140        except OSError:
    141            r = requests.get(f"https://crt.sh/?d={certId}")
    142            r.raise_for_status()
    143            blocks.append(
    144                print_block(r.content, crtshId=certId, identifierType=identifierType)
    145            )
    146 
    147    print("static const DataAndLength " + args.listname + "[]= {")
    148    for structName in blocks:
    149        if len(structName) < 33:
    150            print("  { " + f"{structName}, sizeof({structName}) " + "},")
    151        else:
    152            print("  { " + f"{structName},")
    153            print(f"    sizeof({structName})" + " },")
    154    print("};")