Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/tools/netwerk/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 13 kB image not shown  

Quelle  decode_ssl_tokens_cache.py

  Sprache: Python
 

#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.11"
# dependencies = ["brotli", "cryptography", "darkdetect", "humanize", "rich"]
# ///
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Decode and display the contents of an ssl_tokens_cache.bin file.

File format:
  magic:   4 bytes  b"STCF"
  version: 1 byte

Version 2:
  body:    zlib-compressed bincode-1.3 Vec<PersistedRecord>
  PersistedRecord contains separate token and cert-chain fields with raw DER.

Version 3:
  body:    bincode-1.3 Vec<PersistedRecord>
  checksum: 4-byte little-endian Adler-32 of body
  PersistedRecord contains a single compressed_payload field
  (token + cert info packed together and compressed).

Run with ``uv run decode_ssl_tokens_cache.py`` for automatic dependency
installation, or ``pip install brotli cryptography darkdetect humanize rich``
for plain Python.
"""

import argparse
import datetime
import io
import struct
import sys
import warnings
import zlib
from collections.abc import Callable
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import NamedTuple, TypeVar, cast

try:
    import darkdetect
    import humanize
    from rich import box as rich_box
    from rich.console import Console
    from rich.panel import Panel
    from rich.table import Table
    from rich.text import Text
    from rich.theme import Theme
except ImportError as e:
    sys.exit(
        f"Missing dependency: {e.name}\n"
        f"Run with: uv run {sys.argv[0]}\n"
        f"Or install manually: pip install darkdetect humanize rich"
    )

_DARK_THEME = Theme({"label""dim""cert""cyan""live""green"})
_LIGHT_THEME = Theme({"label""italic""cert""dark_cyan""live""dark_green"})

try:
    from cryptography import x509 as _x509
except ImportError:
    _x509 = None  # type: ignore[assignment]

try:
    import brotli as _brotli
except ImportError:
    _brotli = None  # type: ignore[assignment]

MAGIC = b"STCF"
SUPPORTED_VERSIONS = (23)

T = TypeVar("T")


def _adler32_le(data: bytes) -> bytes:
    return struct.pack("<I", zlib.adler32(data))


class DecodeError(Exception):
    pass


@dataclass
class Record:
    key: str
    expires: datetime.datetime
    token: bytes
    ev_status: int
    ct_status: int
    overridable_error: int
    server_cert: bytes
    succeeded_cert_chain: list[bytes] | None
    handshake_certs: list[bytes] | None
    built_in_root: bool | None
    size: int = 0


class _ParsedPayload(NamedTuple):
    token: bytes
    ev: int
    ct: int
    builtin_root: bool | None
    server_cert: bytes
    succeeded: list[bytes] | None
    handshake: list[bytes] | None


_EMPTY_PAYLOAD = _ParsedPayload(b""00None, b""NoneNone)


def _decompress_payload(data: bytes) -> bytes | None:
    """Decompress a record payload (4-byte LE original-size prefix + compressed body)."""
    if len(data) < 4:
        return None
    if _brotli is None:
        return None
    try:
        return _brotli.decompress(data[4:])
    except Exception:  # pylint: disable=broad-exception-caught
        return None


def _parse_payload(payload: bytes) -> _ParsedPayload:
    buf = io.BytesIO(payload)

    def read(n: int) -> bytes:
        chunk = buf.read(n)
        if len(chunk) != n:
            raise DecodeError("truncated payload")
        return chunk

    def u8() -> int:
        return struct.unpack_from("<B", read(1))[0]

    def u16() -> int:
        return struct.unpack_from("<H", read(2))[0]

    def u32() -> int:
        return struct.unpack_from("<I", read(4))[0]

    def read_bytes() -> bytes:
        return read(u32())

    def read_chain() -> list[bytes] | None:
        if not u8():
            return None
        return [read_bytes() for _ in range(u8())]

    token = read_bytes()
    ev = u8()
    ct = u16()
    u8()  # overridable_error — also stored at the PersistedRecord level; use that value
    builtin_tag = u8()
    builtin_root: bool | None = None if builtin_tag == 0 else (builtin_tag == 2)
    server_cert = read_bytes()
    succeeded = read_chain()
    handshake = read_chain()
    if buf.read(1):
        raise DecodeError("trailing bytes in payload")
    return _ParsedPayload(
        token, ev, ct, builtin_root, server_cert, succeeded, handshake
    )


class Reader:
    """Minimal bincode-1.3 (little-endian, u64 lengths) stream reader."""

    _U8 = struct.Struct("<B")
    _U16 = struct.Struct("<H")
    _U64 = struct.Struct("<Q")
    _I64 = struct.Struct("<q")

    def __init__(self, data: bytes, version: int = 2):
        self._buf = io.BytesIO(data)
        self._len = len(data)
        self._version = version

    def remaining(self) -> int:
        return self._len - self._buf.tell()

    def _read(self, n: int) -> bytes:
        chunk = self._buf.read(n)
        if len(chunk) != n:
            raise DecodeError(
                f"truncated: need {n} bytes at offset {self._buf.tell()}, "
                f"only {len(chunk)} available"
            )
        return chunk

    def _unpack(self, fmt: struct.Struct) -> int:
        (value,) = fmt.unpack(self._read(fmt.size))
        return cast(int, value)

    def u8(self) -> int:
        return self._unpack(self._U8)

    def u16(self) -> int:
        return self._unpack(self._U16)

    def u64(self) -> int:
        return self._unpack(self._U64)

    def i64(self) -> int:
        return self._unpack(self._I64)

    def read_bool(self) -> bool:
        return bool(self.u8())

    def bytes_vec(self) -> bytes:
        return self._read(self.u64())

    def vec_of_bytes(self) -> list[bytes]:
        return [self.bytes_vec() for _ in range(self.u64())]

    def option(self, f: Callable[[], T]) -> T | None:
        tag = self.u8()
        if not tag:
            return None
        if tag == 1:
            return f()
        raise DecodeError(f"invalid Option discriminant {tag}")

    @staticmethod
    def _make_record(
        key: str, expires: datetime.datetime, p: _ParsedPayload, overridable: int
    ) -> Record:
        return Record(
            key=key,
            expires=expires,
            token=p.token,
            ev_status=p.ev,
            ct_status=p.ct,
            overridable_error=overridable,
            server_cert=p.server_cert,
            succeeded_cert_chain=p.succeeded,
            handshake_certs=p.handshake,
            built_in_root=p.builtin_root,
        )

    def _record_v2(self, key: str, expires: datetime.datetime) -> Record:
        token = self.bytes_vec()
        ev = self.u8()
        ct = self.u16()
        overridable = self.u8()
        server_cert = self.bytes_vec()
        succeeded = self.option(self.vec_of_bytes)
        handshake = self.option(self.vec_of_bytes)
        builtin_root = self.option(self.read_bool)
        return self._make_record(
            key,
            expires,
            _ParsedPayload(
                token, ev, ct, builtin_root, server_cert, succeeded, handshake
            ),
            overridable,
        )

    def _record_v3(self, key: str, expires: datetime.datetime) -> Record:
        overridable = self.u8()
        raw_payload = self.bytes_vec()
        p = _EMPTY_PAYLOAD
        payload = _decompress_payload(raw_payload)
        if payload is not None:
            try:
                p = _parse_payload(payload)
            except DecodeError:
                pass
        return self._make_record(key, expires, p, overridable)

    def record(self) -> Record:
        start = self._buf.tell()
        self.u64()  # session-internal id, re-assigned on load; not displayed
        key = self.bytes_vec().decode("utf-8", errors="replace")
        prtime = self.i64()
        expires = datetime.datetime.fromtimestamp(
            prtime / 1e6, tz=datetime.timezone.utc
        )
        rec = (self._record_v3 if self._version >= 3 else self._record_v2)(key, expires)
        rec.size = self._buf.tell() - start
        return rec

    def records(self) -> list[Record]:
        return [self.record() for _ in range(self.u64())]


@lru_cache(maxsize=256)
def cert_subject(der: bytes) -> str:
    if _x509 is not None:
        try:
            return str(_x509.load_der_x509_certificate(der).subject.rfc4514_string())
        except Exception:  # pylint: disable=broad-exception-caught
            pass
    return f"<{len(der)} bytes DER>"


def _hexdump_text(data: bytes) -> Text:
    t = Text()
    for off in range(0, len(data), 16):
        chunk = data[off : off + 16]
        hex_part = " ".join(f"{b:02x}" for b in chunk)
        asc_part = "".join(chr(b) if 0x20 <= b < 0x7F else "." for b in chunk)
        t.append(f"{off:04x}", style="dim")
        t.append(f"  {hex_part:<47}  ")
        t.append(asc_part, style="dim")
        t.append("\n")
    return t


def _chain_text(chain: list[bytes], verbose: int) -> str:
    count = f"{len(chain)} cert(s)"
    if verbose >= 1:
        subjects = "  ".join(cert_subject(c) for c in chain)
        return f"{count}  {subjects}"
    return count


def print_record(
    idx: int, rec: Record, verbose: int, now: datetime.datetime, console: Console
) -> None:
    expired = rec.expires < now

    tbl = Table(box=None, show_header=False, show_edge=False, padding=(0100))
    tbl.add_column(style="label", min_width=11, no_wrap=True)
    tbl.add_column()

    rel = humanize.naturaltime(rec.expires, when=now)
    status = (
        Text("EXPIRED", style="bold red"if expired else Text("live", style="live")
    )
    tbl.add_row(
        "expires",
        Text.assemble(
            rec.expires.strftime("%Y-%m-%d %H:%M:%S UTC"),
            ("  """),
            (f"({rel})""dim"),
            "  ",
            status,
        ),
    )
    tbl.add_row("token", humanize.naturalsize(len(rec.token)))
    if verbose >= 2:
        tbl.add_row("", _hexdump_text(rec.token))

    flag_parts = [
        f"ev={rec.ev_status}",
        f"ct={rec.ct_status}",
        f"ovr_error={rec.overridable_error}",
        *([] if rec.built_in_root is None else [f"built_in_root={rec.built_in_root}"]),
    ]
    tbl.add_row("flags""  ".join(flag_parts))

    if rec.server_cert:
        tbl.add_row("server cert", Text(cert_subject(rec.server_cert), style="cert"))
    for label, chain in [
        ("chain", rec.succeeded_cert_chain),
        ("hs certs", rec.handshake_certs),
    ]:
        if chain is not None:
            tbl.add_row(label, _chain_text(chain, verbose))

    console.print(
        Panel(
            tbl,
            title=f"[bold][{idx}][/] {rec.key}  [dim]{humanize.naturalsize(rec.size)}[/]",
            title_align="left",
            box=rich_box.ROUNDED,
            border_style="red" if expired else "default",
        )
    )


def decode(path: str, verbose: int, console: Console) -> None:
    data = Path(path).read_bytes()

    if len(data) < 5:
        raise DecodeError("file too short to contain header")
    magic, version = data[:4], data[4]
    if magic != MAGIC:
        raise DecodeError(f"bad magic: expected {MAGIC!r}, got {magic!r}")
    if version not in SUPPORTED_VERSIONS:
        raise DecodeError(
            f"unsupported version {version} "
            f"(this script handles versions {SUPPORTED_VERSIONS})"
        )

    if version >= 3 and _brotli is None:
        warnings.warn(
            "brotli package not found; cert info will be unavailable for v3 records",
            stacklevel=2,
        )

    if version >= 3:
        if len(data) < 5 + 4:
            raise DecodeError("file too short to contain checksum")
        payload, stored_checksum = data[5:-4], data[-4:]
        computed = _adler32_le(payload)
        if computed != stored_checksum:
            raise DecodeError(
                f"checksum mismatch: file may be corrupt "
                f"(stored {stored_checksum.hex()}, computed {computed.hex()})"
            )
    else:
        try:
            payload = zlib.decompress(data[5:])
        except zlib.error as e:
            raise DecodeError(f"zlib decompression failed: {e}"from e

    reader = Reader(payload, version)
    records = reader.records()

    if reader.remaining():
        warnings.warn(
            f"{reader.remaining()} trailing bytes after records", stacklevel=2
        )

    now = datetime.datetime.now(datetime.timezone.utc)
    n_expired = sum(r.expires < now for r in records)

    summary = Table(box=None, show_header=False, show_edge=False, padding=(0100))
    summary.add_column(style="label")
    summary.add_column()
    summary.add_row("version", str(version))
    summary.add_row("records", f"{len(records)} [dim]({n_expired} expired)[/dim]")
    summary.add_row("body", humanize.naturalsize(len(payload)))
    console.print(
        Panel(
            summary, title=f"[bold]{path}[/]", title_align="left", box=rich_box.ROUNDED
        )
    )
    console.print()

    for i, rec in enumerate(records):
        print_record(i, rec, verbose, now, console)


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Decode an ssl_tokens_cache.bin file from a Firefox profile."
    )
    parser.add_argument("file", help="path to ssl_tokens_cache.bin")
    parser.add_argument(
        "-v",
        "--verbose",
        action="count",
        default=0,
        help="-v: show cert chain subjects; -vv: also hexdump token bytes",
    )
    parser.add_argument(
        "--no-color",
        action="store_true",
        help="disable color output",
    )
    args = parser.parse_args()

    con = (
        Console(no_color=True, theme=_LIGHT_THEME)
        if args.no_color
        else Console(theme=_DARK_THEME if darkdetect.isDark() else _LIGHT_THEME)
    )

    try:
        decode(args.file, args.verbose, con)
    except (DecodeError, OSError) as e:
        Console(stderr=True).print(f"[bold red]error:[/] {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()

Messung V0.5 in Prozent
C=90 H=92 G=90

¤ Dauer der Verarbeitung: 0.34 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.