Meerkat Testing CT Log
A fully RFC 6962-compliant Certificate Transparency log for testing.
Accepts precertificate chains, returns cryptographically valid SCTs, and persists every entry in a
real Merkle tree â get-entries and get-proof-by-hash are fully implemented.
Each request is served by a randomly selected identity from a pool of 10 fake log operators â
so SCTs look like they came from different logs.
Available Log Identities
One identity is selected at random per add-pre-chain request. The returned SCT's id field identifies which log "signed" it. All identities share a single API endpoint and a single global Merkle tree.
| Log Name | Operator | Log ID (base64) | MMD |
|---|---|---|---|
| Meerkat Bousannoun CT 2025 | Bousannoun Certificate Transparency | iwnHdSIcKx8Ghz4kbh8q6pXRm1W8R4ELzhB+zQsYcCs= | 24 h |
| Chalhoub CT 2026h2 | Ganfous Errdaief GMBH | QbqhGVKRrVFvtBi5Me9culXIlDi/9a7JupvFgD6bWfU= | 24 h |
| Meerkat Dhibi CT 2026h1 | Dhibi Digital Trust | fH1x8dROT9Kw0LwQMvc4xYjq7PJMyzypx4yZjy58efM= | 24 h |
| Meerkat Farhoud CT 2025 | Farhoud CT Authority | hvxltxKBcrAnEDZHWgf6nMYy07hkovBzcnZg2WBSD68= | 24 h |
| Meerkat Habhoub CT 2026 | Habhoub Certificate Logs | N4FkhYpvMuQqv/rBV+ifYqOB2M516xKaivOkxHdzHVk= | 24 h |
| Finally the ninth CT LOG | Harhour GMBH | nb4XK1iOYM2Lk+khiyBUXuycsZcqzV5WpQG4ifKluuA= | 24 h |
| Meerkat Kablouti CT 2025h1 | Kablouti Certificate Services | hTHSlyqT8WJPI8sY3+w8rATgCEhtul+mdijguxKrOt4= | 24 h |
| Meerkat Karkoub CT 2025h2 | Karkoub Trust Infrastructure | Y/QMWnvgbCTNukIqMBNwqx8BH3V4NyA2/TyklEhpFH8= | 24 h |
| Meerkat Sal7ouf CT 2026h1 | Sal7ouf Digital Logs | bzp3sBRXyfGvWrmAJtkBhiCPhUFlW65qSRgce7ghsSY= | 24 h |
| Meerkat Sardouk CT 2025h2 | Sardouk Log Services | B9HDwAipkqj3Rc8AB0cfPPxCwro9whPdosOHWztOovM= | 24 h |
API Base URL: https://thameur.org/ct/v1 ¡
Log ID = SHA-256(SubjectPublicKeyInfo DER of the log's ECDSA P-256 public key)
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /ct/v1/add-pre-chain | Submit a precertificate chain. Returns a signed SCT and persists the entry. Primary endpoint. |
| GET | /ct/v1/get-sth | Returns a signed tree head reflecting the current Merkle tree size and root hash. |
| GET | /ct/v1/get-roots | Returns base64 DER of accepted root and issuing CA certificates. |
| GET | /ct/v1/get-entries | Returns persisted log entries for a given leaf index range. |
| GET | /ct/v1/get-proof-by-hash | Returns a Merkle audit proof for a given leaf hash. |
POST /ct/v1/add-pre-chain
Accepts a JSON body containing the precertificate chain (RFC 6962 §4.1). Validates the CT poison extension, strips it from the TBSCertificate, computes the issuer key hash, selects a random log identity, signs the SCT, and persists the entry in the Merkle tree.
Request body:
Request fields:
| Field | Type | Notes |
|---|---|---|
| chain[0] | required | Base64-encoded DER precertificate. Must contain the CT poison extension (OID 1.3.6.1.4.1.11129.2.4.3, critical). |
| chain[1] | optional | Base64-encoded DER issuing CA certificate. Used to compute the issuer_key_hash. If absent, the Meerkat Issuing CA cert is used. |
| chain[2âŚN] | optional | Additional intermediate CA certificates up to the accepted root (max 10 total). Stored in extra_data for get-entries. |
Success response (HTTP 200):
GET /ct/v1/get-sth
Returns a freshly signed Signed Tree Head. The tree_size and sha256_root_hash reflect the current state of the Merkle tree across all persisted entries. A randomly selected log key signs the STH on each call.
GET /ct/v1/get-roots
GET /ct/v1/get-entries?start=<n>&end=<m>
Returns persisted log entries for leaf indices [start, end] inclusive (0-indexed). Maximum 1 000 entries per request.
| Parameter | Type | Notes |
|---|---|---|
| start | required | First leaf index to return (0-based). |
| end | required | Last leaf index to return (inclusive). Clamped to start + 999. |
GET /ct/v1/get-proof-by-hash?hash=<base64>&tree_size=<n>
Returns a Merkle audit proof (RFC 6962 §4.5) for the leaf identified by its hash.
| Parameter | Type | Notes |
|---|---|---|
| hash | required | Base64 of SHA-256(0x00 || MerkleTreeLeaf) â the leaf hash. |
| tree_size | optional | Snapshot tree size to prove against. Defaults to current tree size. |
Verify the proof with the STH root hash: reconstruct the path hash from leaf â root by applying SHA-256(0x01 || left || right) at each level, and compare the final hash against sha256_root_hash from get-sth.
Merkle Tree (RFC 6962 §2.1)
All entries share a single global tree regardless of which log identity signed each SCT. Leaf hashes and interior node hashes follow the RFC 6962 / RFC 6979 conventions:
| Node type | Hash computation |
|---|---|
| Empty tree | SHA-256("") |
| Leaf node | SHA-256(0x00 || MerkleTreeLeaf) â stored as leaf_hash |
| Interior node | SHA-256(0x01 || left_child || right_child) |
SCT & Signed Data Structure
The signature field contains a base64-encoded DigitallySigned struct (RFC 6962 §3.2). The signed data for a precert_entry is identical to the MerkleTreeLeaf wire format â both start with 0x00 0x00 (version + signature_type / leaf_type):
| Field | Size | Value |
|---|---|---|
| version / leaf_type | 1 + 1 byte | 0x00 0x00 (v1, certificate_timestamp / timestamped_entry) |
| timestamp | 8 bytes | uint64 big-endian milliseconds since epoch |
| entry_type | 2 bytes | 0x00 0x01 (precert_entry) |
| issuer_key_hash | 32 bytes | SHA-256 of issuer SubjectPublicKeyInfo DER |
| tbs_certificate length | 3 bytes | uint24 big-endian byte count of the TBS |
| tbs_certificate | variable | DER TBSCertificate with CT poison extension removed |
| extensions length | 2 bytes | 0x00 0x00 (no extensions) |
Error Responses
All errors return JSON with error_code (HTTP status) and error_message.
| HTTP | Cause |
|---|---|
| 400 | Missing or malformed chain field; base64 decode failure; chain[0] is not a valid X.509 certificate; CT poison extension not present; invalid hash or start/end parameters; leaf not within tree_size snapshot. |
| 404 | Unknown endpoint; hash not found in the log. |
| 405 | Wrong HTTP method (e.g. GET on add-pre-chain). |
| 503 | No active CT log identities with keys â issue keys from the admin panel. |
| 500 | DER parsing failure; ECDSA signing error. |
Integration Guide
Step 1 â Issue a precertificate
Use the Meerkat Certificate Factory "Issue Precertificate" button, or issue one via your own CA pipeline with the CT poison extension (OID 1.3.6.1.4.1.11129.2.4.3, critical, value = ASN.1 NULL 05 00).
Step 2 â Submit to add-pre-chain
Step 3 â Verify inclusion with get-proof-by-hash
The leaf hash is SHA-256(0x00 || leaf_input) where leaf_input is the MerkleTreeLeaf bytes from the SCT. Pass the base64 leaf hash to get-proof-by-hash, then reconstruct the root from the audit path and compare against get-sth.
Embed the SCT in a certificate extension
The SCT can be embedded in the final certificate as a SignedCertificateTimestampList (OID 1.3.6.1.4.1.11129.2.4.2), or delivered via a TLS extension, or via OCSP stapling. Encoding of the SCT list is described in RFC 6962 §3.3.
Technical Notes
- Randomisation: Each
add-pre-chainrequest picks one of 10 ECDSA P-256 key pairs at random. Theidin the SCT uniquely identifies which log "signed" it. The global Merkle tree is shared across all identities. - Persistence: Every submitted precertificate is stored in the database.
get-entriesreturns the actual MerkleTreeLeaf and PrecertChainEntry for each stored entry.get-sthreflects the real tree size and Merkle root. - Poison stripping: The TBSCertificate submitted in the chain has the CT poison extension (OID 1.3.6.1.4.1.11129.2.4.3) removed before it is hashed into the signed data, per RFC 6962 §3.2.
- IssuerKeyHash: Computed as SHA-256 of the SubjectPublicKeyInfo DER of the issuing CA certificate (chain[1], or the Meerkat Issuing CA if absent).
- CORS: All endpoints respond with
Access-Control-Allow-Origin: *for browser-based testing.