Live app: keyassay.vercel.app
Source: github.com/aniruddhaadak80/keyassay
Every TLS session an adversary records today becomes readable the day a cryptographically relevant quantum computer exists — if the key behind it has not been replaced by then.
The standard advice is to rotate your certificate. That does nothing for traffic already captured. The ciphertext is already in their hands, and a fresh certificate only protects traffic you emit after the migration.
So the question is not when a certificate expires. It is whether the key behind the traffic you have already emitted will still be unbroken in 2040 — the year by which anything captured today has to have become unreadable.
Keyassay answers that for a real endpoint, in about two seconds, and shows the arithmetic.
What it actually does
You type a hostname. The server opens a TCP connection to port 443, performs the handshake, and walks the chain the endpoint presents. No lookup tables, no API keys, no third-party scanner.
From that real certificate it reads:
- the leaf public key, its algorithm and its size, parsed out of the DER
- the signature algorithm OID, so an ML-DSA or SLH-DSA certificate is identified rather than guessed
- its Certificate Transparency history from crt.sh, which is how you notice a host has been quietly reissued
- the classical security level from NIST SP 800-57 equivalences
It then prices breaking that key with the published quantum estimates — Gidney and Ekerå's abstract circuit cost for factoring, and Gidney's 2025 revision — anchored to a stated physical-qubit count and scaled by a growth rate you set.
Nothing is hard-coded as an uncheckable string. The two papers the cost model rests on are re-fetched from arXiv at runtime and their returned titles compared against the ones the engine cites, so a superseded citation shows up as a failed check rather than a confident-sounding sentence.
The part I care about most: the horizon, not the expiry
The default policy exposes data until 2040. Drag that dial to 2035 and every stored assay is re-rated through the same engine.
The important detail: re-rating does not mutate the stored measurement. What was measured stays measured; the grade is a function of measurement plus horizon, so you can always answer "what did we actually know at the time?" by replaying the chain.
The certificate
Seven weighted factors, each with its measured value, its contribution and the published source it came from. The arithmetic reproduces exactly.
For a real EC P-256 leaf, the engine says: 128-bit classical equivalence, needs ~1.5M physical qubits and ~8.87e9 Toffoli gates, capability arrives around 2050, so a 2040 horizon is 10 years of margin.
Rotate the horizon to 2050 and the same host reads corroded.
What makes it more than a calculator
Nine MCP tools. An agent gets the same JSON-RPC surface: assay_host, list_assays, record_decision, verify_integrity, delete_assay and the rest. Mutating tools call the same service layer the UI does, so there is exactly one code path that can change state, and they are idempotent on a key.
A hash chain you can prove later. Every mutation appends to a per-entity SHA-384 chain over canonical JSON. Download the certificate, replay it in six months, confirm the grade was never quietly edited. Deleting a record writes a tombstone with its own event, so the chain still replays clean afterwards.
Exports a partner will actually accept. Self-contained HTML for a ticket, JSON for a machine, CSV for a spreadsheet — all carrying the seal and every citation.
Honest failure. A host that cannot be assayed says why, per source. arXiv being rate-limited is labelled as unverified, never silently presented as passing.
Three bugs worth writing down
A five-query page against a four-connection pool. /ledger fetched its page, its policy, and a three-query health probe in parallel. Under serverless concurrency that exhausted the pool and the page rendered the error boundary instead of its content. It only ever reproduced on a real deployment. It now issues two queries, and store health lives at /api/health where it belongs.
Never let a third party suspend your render. The literature panels were async server components streamed behind Suspense. From my machine arXiv answers 429 in milliseconds so nothing ever suspended; from a GitHub runner the request hangs, the boundaries stay pending, and React's RSC client fails the whole stream with an internal Expected static flag was missing error. The panels now render immediately and fetch /api/standards from the browser. A slow third party costs a late panel, never a dead page.
sslmode is not a boolean. The database adapter set ssl: { rejectUnauthorized: false } for any URL that did not say sslmode=disable, so it forced TLS onto a stock Postgres that had TLS switched off. CI answered 503 from /api/health for an entire run. It now parses the mode properly and otherwise lets the driver negotiate.
Honest limits
- The engine models the cost of breaking a public key. It cannot see implementation bugs, weak randomness, or operational mistakes.
- Break years are outputs of a stated growth assumption. They are not predictions and should never be quoted as one.
- Rate limiting is a per-process token bucket. On serverless that is a speed bump, not a control.
Stack
Next.js 16 App Router, strict TypeScript, Tailwind 4, Neon Postgres in production with embedded PGlite for zero-config local dev, Vitest and Playwright. 99 unit and integration tests, a live verifier that makes 77 HTTP assertions against the deployment, and a GitHub Actions pipeline that runs the browser journey against a real Postgres.
MIT licensed. Issues and pull requests welcome — I am working through Hacktoberfest Week 1 and would love review on the engine's factor weighting in particular.




Top comments (0)