diff --git a/README.md b/README.md index 64395ab..00d4b3a 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,16 @@ Data tile from a local file (with issuer resolution): ctfetch --monitoring-url https://halloumi2026h1.mon.ct.ipng.ch tile.bin +issuer ``` +## Hash tiles vs data tiles + +A Static CT log stores two kinds of tiles: + +**Data tiles** (`/tile/data/...`) contain the actual log entries — DER-encoded certificates and precertificates along with their metadata (leaf index, timestamp, chain fingerprints, etc.). These are what `ctfetch` parses into structured JSON. The output modifiers `+sct`, `+issuer`, and `+ctlog` all operate on data tiles. + +**Hash tiles** (`/tile/N/...`, where N is a tree level ≥ 0) contain the internal nodes of the Merkle tree — rows of raw 32-byte SHA-256 hashes used for inclusion and consistency proofs. There are no certificates in a hash tile; `ctfetch` outputs only the list of hashes. Using `+sct`, `+issuer`, or `+ctlog` with a hash tile is an error. + +The tree is organised so that level 0 hashes cover individual leaves (each is `SHA-256(0x00 || MerkleTreeLeaf)`), and each higher level hashes pairs of nodes from the level below. The tile URL encodes the level: `/tile/0/...` is level 0, `/tile/1/...` is level 1, and so on. + ## Output modifiers | Modifier | Description | diff --git a/internal/utils/utils.go b/internal/utils/utils.go index ac5aab1..b56398f 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -211,7 +211,7 @@ func DumpAllEntries(tileData []byte, opts Options) (*DumpResult, error) { if err != nil { // If it fails, try as hash tile fmt.Fprintf(os.Stderr, "Not a data tile, trying as hash tile...\n") - return dumpHashTile(tileData) + return dumpHashTile(tileData, opts) } return result, nil } @@ -237,7 +237,11 @@ func dumpDataTile(tileData []byte, opts Options) (*DumpResult, error) { }, nil } -func dumpHashTile(tileData []byte) (*DumpResult, error) { +func dumpHashTile(tileData []byte, opts Options) (*DumpResult, error) { + if opts.ShowSCT || opts.ShowIssuer || opts.ShowCTLog { + return nil, fmt.Errorf("+sct, +issuer, and +ctlog are not valid for hash tiles (only data tiles contain certificates)") + } + const hashSize = 32 // SHA-256 hash size if len(tileData)%hashSize != 0 { diff --git a/tile.data b/tile.data new file mode 100644 index 0000000..a9f4215 Binary files /dev/null and b/tile.data differ