"""Enumeracion OSINT pasiva de subdominios via Certificate Transparency (crt.sh). Funcion IMPURA: consulta los logs publicos de Certificate Transparency a traves de crt.sh y extrae los subdominios que han aparecido en certificados emitidos para el dominio. Es OSINT pasivo: no toca al dominio objetivo, solo consulta registros CT publicos. """ import os import sys sys.path.insert( 0, os.path.join(os.path.dirname(__file__), "..", "..", "functions") ) from infra.http_get_json import http_get_json # noqa: E402 def enum_subdomains_crtsh(dominio: str, timeout_s: float = 20.0) -> list: """Enumera subdominios de un dominio desde Certificate Transparency. Consulta ``https://crt.sh/?q=%25.&output=json`` (``%25`` = ``%``, el wildcard de crt.sh) usando ``http_get_json`` del registry. crt.sh devuelve un array JSON de certificados; de cada uno se toma el campo ``name_value`` (que puede contener varios nombres separados por saltos de linea, uno por SAN). Se separan, deduplican, se filtran los wildcards (``*.``) y se devuelve la lista ordenada de subdominios unicos. Args: dominio: Dominio base a enumerar (ej. ``"organic-machine.com"``). timeout_s: Segundos maximo de espera de la peticion HTTP (default 20). Returns: Lista ordenada de subdominios unicos (sin wildcards). Lista vacia si crt.sh no devuelve resultados. Raises: RuntimeError: Si el dominio esta vacio o la peticion HTTP falla. """ if not dominio or not dominio.strip(): raise RuntimeError("enum_subdomains_crtsh: dominio vacio") dom = dominio.strip().lower() # %25 es el wildcard '%' de crt.sh: busca cualquier nombre que termine en . url = f"https://crt.sh/?q=%25.{dom}&output=json" data = http_get_json(url, timeout=timeout_s) if not isinstance(data, list): raise RuntimeError( f"enum_subdomains_crtsh: respuesta crt.sh inesperada " f"(tipo {type(data).__name__})" ) found: set = set() for cert in data: if not isinstance(cert, dict): continue name_value = cert.get("name_value", "") for raw_name in name_value.split("\n"): name = raw_name.strip().lower() if not name: continue if name.startswith("*."): continue found.add(name) return sorted(found)