diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4f35e..f35a528 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,9 +33,58 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas - **SSH reports** (page and PDF): a strength for every key exchange, host key (by the presented key's size), cipher and MAC. - **CBOM:** `classicalSecurityLevel` on every algorithm with a strength (1.6 and 1.7). +- **Every algorithm by class: asymmetric, symmetric, hash** ([PRD-011](docs/product/PRD-011-algorithm-classes-and-flow.md), #20). + - A TLS report's Configuration ends with *Every algorithm, by class*: one lane per class, each algorithm with its + role, a bits bar, its post-quantum status and the suites or certificates that use it, the weakest ringed. + - Every suite row shows its parts (cipher, key exchange, signature, MAC, key-derivation hash) as cells with their + bits, the weakest ringed. + - The Inventory filters algorithms by class (`?class=`, a segmented control), and its *Kind* says the class. + - The report's CBOM tab has a *Classical strength* column. + - The PDF and `tls scan --details` list the algorithms by class, and the CLI lists each suite's parts. + - API: the evaluation (`eval/10`) carries `algorithms`, and every suite part carries its `class`. +- **Open any certificate and read the whole X.509 document** ([PRD-012](docs/product/PRD-012-certificate-viewer.md), #21). + - Every certificate in the chain and the CBOM tab opens its page. + - The page shows the chain it was served in, its validity, the key and the issuer's signature with their strengths + and post-quantum status, its names and uses, every extension with its criticality and decoded value, the + identifiers to copy, and the PEM (folded) to copy or download. + - It is decoded from the DER the scan kept (`GET /api/v1/certificates/{sha256}/x509`), so earlier scans have it + too. + +- **Instruments, not paragraphs: the report, the certificate page and the Inventory are rebuilt** + ([PRD-013](docs/product/PRD-013-visual-rebuild.md), [UX-DESIGN-011](docs/product/UX-DESIGN-011-instruments-not-paragraphs.md)). + One set of shared instruments draws every quantity the same way on every page: a 0 to 256 bits bar over the + strength bands, a strength chip, a part cell, a post-quantum glyph, a status tile, a validity bar, the hygiene + gauge and the readiness stepper. Each says in words what it shows. + - **Summary:** four figures (readiness, hygiene, the weakest classical strength, the certificate), *Fix first* as + one numbered line per problem, one bar of every check's result, and a tile per area that opens it. + - **Configuration** is one place to check the whole setup, in a reviewer's order: protocols and cipher suites + (what a current client gets, then each version with its groups and its suites in the server's order), the + certificate chain (each card opens its certificate; a missing issuer is drawn), key exchange and signatures, + revocation, protocol features, and every algorithm by class. Each version folds to one line when all its + suites are Strong, and opens when it needs a look. A row of chips at the top jumps to each part, and + each part ends with the issues found in it. HSTS shows the header as sent. + - **Inventory:** algorithms *By class* and *By classical strength* (`?strength=`: broken, below-112, 112, 128, + 192, 256, not-rated), each bar opening its list; the list is weakest first, with a bits bar. + - **SSH reports:** the algorithm tables use the bits bar and a post-quantum column. + - **Scanner:** a TLS scan records whether the server negotiates the heartbeat extension (RFC 6520). + - Every number and claim on these screens was checked against its source + ([CRYPTO-REVIEW-001](docs/product/CRYPTO-REVIEW-001-visual-rebuild.md)). ### Changed +- **The TLS hygiene bands have new names:** Excellent, Good, Fair, Poor, Failing (they were Strong, Sound, Fair, + Weak, Broken). *Strong* and *Weak* now mean only a suite's strength (UX-REVIEW-010, #22). The scores and the + thresholds are unchanged. Stored scans are migrated, and the API's `band` field uses the new names. +- **Each suite gains the hash that derives its keys as a part**, rated in that use (SP 800-57 Table 3). With NIST's + numbers it changes no word. +- **Signature algorithms are rated as their hash and their key together**, so `SHA256withRSA` from an imported CBOM + now reads 128 bits instead of *not rated*. +- **The report's Summary:** + - the line under *N things to fix* names the first one; + - the *Cipher suites* row names the weakest suite and its bits; + - the Overview's TLS endpoints tile keeps to its count and the exposed figure. +- **The Inventory explains every *not rated* strength** on the row. + - **Suite strength words follow the weakest-part rule**, so some suites read differently: - a suite authenticated by an RSA-2048 certificate (112 bits) is now *Acceptable*, not *Strong*; - a suite with a 1024-bit DH prime is *Weak*. @@ -45,6 +94,9 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas ### Fixed - **Single-DES suites (56-bit key) are Insecure, not Weak** (#18). +- **The TLS hygiene figure on a report's Summary opened *About this report*.** It now opens how the score was worked + out, on Weaknesses. +- **The browser journey retries a browser that does not start**, instead of failing the whole run. - **A running scan's connection count could go backwards.** Concurrent probes reported counts out of order, and progress writes could land out of order. The count now only moves forward, and writes are serialised. This was found by a CI run of the workflow test. diff --git a/README.md b/README.md index 23a783b..5b430f7 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,8 @@ CycloneDX 1.7 CBOM, with PDF reports for each endpoint and for the whole invento |---|---| | **TLS endpoints** | Every protocol from SSLv2 to TLS 1.3, every cipher suite and key-exchange group (hybrids such as `X25519MLKEM768` included), the TLS 1.3 signature schemes (ML-DSA included), the certificate chain against the Mozilla and Chrome root stores or your own CA, OCSP stapling, Certificate Transparency and HSTS. STARTTLS for SMTP, IMAP, POP3, FTP, XMPP, LDAP and PostgreSQL. 55 cited checks and the TLS hygiene score. | | **SSH servers** | Identification and algorithm lists, negotiation as OpenSSH 10.6 would (the ML-KEM hybrid included), every host key fetched and verified (Ed25519, ECDSA, RSA, ML-DSA and the composite `ssh-mldsa44-ed25519`), 18 cited checks, and the CipherHorizon SSH baseline 2026 with the `sshd_config` lines to meet it. It never logs in. | -| **Classical strength** | Every algorithm, key and cipher suite in bits, computed with NIST's own methods (the GNFS estimate for RSA and DH, half the order for curves, SP 800-57 for ciphers and hashes). A suite is as strong as its weakest part and names it, on the report, the PDF, the CLI, the Inventory, SSH reports and the CBOM (`classicalSecurityLevel`). | +| **Classical strength** | Every algorithm, key and cipher suite in bits, computed with NIST's own methods (the GNFS estimate for RSA and DH, half the order for curves, SP 800-57 for ciphers and hashes). A suite is as strong as its weakest part and names it, on the report, the PDF, the CLI, the Inventory, SSH reports and the CBOM (`classicalSecurityLevel`). Every algorithm is classed as asymmetric, symmetric or hash; each report shows every suite part by part and every algorithm by class, drawn as bits bars on one scale. | +| **Certificates** | Open any certificate in a chain to read the whole X.509 document: its chain, validity, key and signature with their strengths, names and uses, every extension decoded, fingerprints, and the PEM to copy or download. | | **Post-quantum readiness** | L0 to L4 for every TLS endpoint and SSH server, each protocol by its own stated rules, with harvest-now-decrypt-later exposure. | | **Machines** | Two readable inventory scripts (PowerShell, shell) list a machine's cryptographic software and certificates; nothing is installed and no key material is read. | | **Inventory** | TLS endpoints, SSH servers, machines and imported CBOMs merged into one CycloneDX 1.7 (or 1.6) CBOM: every algorithm with its OID where one exists (the exact certificate signature algorithm, ECDH on each named curve, legacy ciphers such as 3DES and RC4), certificates with their serial and SHA-256 fingerprint. | diff --git a/docs/img/endpoints.png b/docs/img/endpoints.png index aedb69c..94dfc0a 100644 Binary files a/docs/img/endpoints.png and b/docs/img/endpoints.png differ diff --git a/docs/img/inventory.png b/docs/img/inventory.png index a5bb814..966836e 100644 Binary files a/docs/img/inventory.png and b/docs/img/inventory.png differ diff --git a/docs/img/overview-dark.png b/docs/img/overview-dark.png index 550a513..1532b23 100644 Binary files a/docs/img/overview-dark.png and b/docs/img/overview-dark.png differ diff --git a/docs/img/overview.png b/docs/img/overview.png index ea60093..9654e38 100644 Binary files a/docs/img/overview.png and b/docs/img/overview.png differ diff --git a/docs/img/report-hybrid-dark.png b/docs/img/report-hybrid-dark.png index bc5bb37..ad681a8 100644 Binary files a/docs/img/report-hybrid-dark.png and b/docs/img/report-hybrid-dark.png differ diff --git a/docs/img/report-hybrid.png b/docs/img/report-hybrid.png index 8e75790..7cd4e50 100644 Binary files a/docs/img/report-hybrid.png and b/docs/img/report-hybrid.png differ diff --git a/docs/img/report-legacy.png b/docs/img/report-legacy.png index 28eac1b..4508a62 100644 Binary files a/docs/img/report-legacy.png and b/docs/img/report-legacy.png differ diff --git a/docs/img/report-ssh-baseline.png b/docs/img/report-ssh-baseline.png index eba09f4..1a7e56c 100644 Binary files a/docs/img/report-ssh-baseline.png and b/docs/img/report-ssh-baseline.png differ diff --git a/docs/img/report-ssh.png b/docs/img/report-ssh.png index 241c509..62679a2 100644 Binary files a/docs/img/report-ssh.png and b/docs/img/report-ssh.png differ diff --git a/docs/img/report-starttls.png b/docs/img/report-starttls.png index 1e2aa01..e11e330 100644 Binary files a/docs/img/report-starttls.png and b/docs/img/report-starttls.png differ diff --git a/docs/img/ssh-servers.png b/docs/img/ssh-servers.png index dd2027a..e11f716 100644 Binary files a/docs/img/ssh-servers.png and b/docs/img/ssh-servers.png differ diff --git a/docs/methodology.md b/docs/methodology.md index 571ee1d..c6053df 100644 --- a/docs/methodology.md +++ b/docs/methodology.md @@ -72,15 +72,19 @@ traced to a finding and every finding carries its sources. | Band | Score | |---|---| - | **Strong** | 90 to 100 | - | **Sound** | 75 to 89 | + | **Excellent** | 90 to 100 | + | **Good** | 75 to 89 | | **Fair** | 55 to 74 | - | **Weak** | 30 to 54 | - | **Broken** | 0 to 29 | + | **Poor** | 30 to 54 | + | **Failing** | 0 to 29 | | **Not rated** | no protocol version answered, so there is nothing to rate | + Until PRD-011 the bands were Strong, Sound, Fair, Weak and Broken. They were renamed because *Strong* and *Weak* + are suite-strength words, so one word meant two things on the same page (UX-REVIEW-010 U-1). Stored scans are + migrated, and an evaluation stored with an old name is read as the new one. + **Post-quantum findings are never counted** (category `pqc`). Readiness answers that question, so a well-configured -endpoint that has not yet adopted post-quantum key exchange scores Strong on hygiene and L1 on readiness, and it is +endpoint that has not yet adopted post-quantum key exchange scores Excellent on hygiene and L1 on readiness, and it is not marked down twice. The result lists each deduction (rule ID, title, severity, points), any ceiling that changed the outcome, and what is @@ -136,7 +140,7 @@ takes every finding except info, orders it by severity, then by whether it stops untrusted, name mismatch, missing intermediate, no serverAuth, Must-Staple missing, revoked), then by section order, and shows the first three distinct rules. -## Classical strength (eval/9, PRD-010) +## Classical strength (eval/9, PRD-010; classes and the hash part eval/10, PRD-011) Every algorithm the product lists has a classical security strength in bits, computed with NIST's own methods. Where no number applies, it says why instead: a cited break, or no rating. The derivation, with NIST's tables @@ -148,14 +152,18 @@ The code is `internal/algo/strength.go` and `strength_of.go`. | RSA modulus, finite-field DH prime | General Number Field Sieve: E(L) = (1.923·∛(L·ln 2)·∛((ln(L·ln 2))²) − 4.69)/ln 2, rounded to the nearest multiple of eight, at most 256. 1024 → 80, 2048 → 112, 3072 → 128, 4096 → 152, 8192 → 200 | FIPS 140-3 IG D.B; SP 800-56B Rev. 2 Appendix D, Table 4; SP 800-56A Rev. 3, Table 26 | | Elliptic curves | Half the bit length of the base point's order, taken to the nearest strength SP 800-186 assigns (80, 112, 128, 192, 224, 256): P-256 and Curve25519 → 128, P-384 → 192, Curve448 → 224, P-521 → 256 | SP 800-186 §3 and Table 1 | | Symmetric ciphers | AES-k → k; 3DES (3TDEA) → 112, disallowed for encryption after 2023; DES, RC4, RC2 → broken. Ciphers NIST does not rate (ChaCha20, Camellia, ARIA, SEED, …) get the bound an exhaustive key search sets, labelled as such | SP 800-57 Part 1 Rev. 5 Table 2; SP 800-131A Rev. 2; RFC 7465 | -| Hashes and MACs | Signatures, where collision resistance is needed: SHA-256 → 128, SHA-384 → 192, SHA-512 → 256; SHA-1 and MD5 → broken. HMAC: SHA-1 → 128, SHA-256 and longer → 256 | SP 800-57 Part 1 Table 3 | +| Hashes and MACs | Signatures, where collision resistance is needed: SHA-256 → 128, SHA-384 → 192, SHA-512 → 256; SHA-1 and MD5 → broken. HMAC, a KDF or a PRF (the TLS PRF, HKDF): SHA-1 → 128, SHA-256 and longer → 256 | SP 800-57 Part 1 Table 3 | +| Signature algorithms | The weaker of the hash (collision strength) and the signer's key, in every spelling (ECDSA-SHA256, SHA256-RSA, sha256WithRSAEncryption, SHA256withRSA). Without the key's size, the hash, and the basis says the key was not given | SP 800-57 Part 1 Tables 2 and 3 | | Post-quantum algorithms | The classical equivalent of their NIST category: ML-KEM-512/768/1024 → 128/192/256, ML-DSA-44/65/87 → 128/192/256, SLH-DSA by its level | FIPS 203, 204, 205 | | Hybrids | Secure while either part is, so their stronger part: X25519MLKEM768 → 192 | RFC 10024, RFC 10042 | **A TLS cipher suite is as strong as its weakest part.** Its parts are the bulk cipher; the key exchange (the weakest group the server accepts for that version, the DH prime it sent, or, for RSA key transport, the certificate -key); the authentication (the weakest end-entity key the server presented that the suite can use); and, for a -non-AEAD suite, the MAC. +key); the authentication (the weakest end-entity key the server presented that the suite can use); for a non-AEAD +suite, the MAC; and the hash that derives its keys: HKDF's in TLS 1.3, the PRF's in TLS 1.2 (SHA-256 unless the suite +names SHA-384, RFC 5246 §5), the MD5 and SHA-1 pair in TLS 1.0 and 1.1 (secure while either is, RFC 2246 §5, so +128). The hash part is rated in that use; with NIST's numbers it never lowers a suite today, but it counts like any +other part (PRD-011 OD-1), so a later change to the numbers needs no new rule. | Word | When | |---|---| @@ -168,6 +176,33 @@ An RSA-2048 certificate (112 bits) makes every suite it authenticates *Acceptabl and the owner's decision OD-1. Each suite states its weakest part and the reason for its word. The words are this product's own; the numbers are NIST's. +### Classes (PRD-011) + +Every algorithm belongs to one class, from its CycloneDX primitive, and has a role within it: + +| Class | Primitives | Roles | +|---|---|---| +| **Asymmetric** | `pke`, `signature`, `key-agree`, `kem`, `combiner` (hybrids) | key exchange, signature | +| **Symmetric** | `ae`, `block-cipher`, `stream-cipher`, `mac` | cipher, MAC | +| **Hash** | `hash` | hash (HKDF), hash (PRF), hash (signature) | + +A TLS evaluation lists every algorithm the endpoint uses (`algorithms` in the API): its class, role, strength and +post-quantum status, the suites or certificates that use it, and the weakest of each class. A hash is listed once per +use, because its strength depends on the use: SHA-256 deriving keys is 256 bits, signing it is 128. The report's +Configuration opens with this list (*Algorithms in use*); the PDF, `tls scan --details` and the Inventory's *Class* +filter show the same classes. + +### One certificate, decoded (PRD-012) + +Every certificate in a report's *Chain as served*, its quick look and the CBOM tab opens the certificate's page, +`#/certificates/{sha256}`. The page is decoded from the DER the scan kept (`GET /api/v1/certificates/{sha256}/x509`), so +an older scan gains it without a rescan. In order, it shows: identity, names, the public key (with its classical +strength and post-quantum status), the signature (the weaker of its hash and the issuer's key), every extension with +its criticality and decoded value (one this program does not decode is shown as OID and hex), the fingerprints (SHA-1 +as an identifier only) and the PEM, to copy or download. A certificate known only from a machine inventory or an +imported CBOM keeps no DER; its page shows the fields that source gave and says so. Every value comes from the +certificate and is rendered as text. + ## SSH SSH servers share the readiness scale and the findings model. They are **not scored**: an SSH server is judged against diff --git a/docs/product/CRYPTO-REVIEW-001-visual-rebuild.md b/docs/product/CRYPTO-REVIEW-001-visual-rebuild.md new file mode 100644 index 0000000..22c0ef6 --- /dev/null +++ b/docs/product/CRYPTO-REVIEW-001-visual-rebuild.md @@ -0,0 +1,97 @@ +# CRYPTO-REVIEW-001 · Every number and claim on the rebuilt screens (PRD-013) + +Date: 2026-10-21. Reviewer role: cryptographer (PRD-013 REQ-VIS-12). Build: branch `feat/classes-and-x509`. +Scope: what the Summary, Configuration, the certificate page, the Inventory and the SSH tables *show*: each +number, word, glyph and sentence, checked against the evaluation it is drawn from and against the source that +evaluation cites. The evaluation's own rules were reviewed for PRD-010 and PRD-011; they are re-read here only +where a screen states them. + +Data: the CI journey's demo estate (TLS targets 9440 to 9459, SSH targets 9460 to 9467, the imported CBOMs and +the machine inventories), as the screens render it. Agreement between screen and API is checked by the journey +(E2E-STR-1, E2E-CLS-1, E2E-CLS-2, E2E-VIS-3, E2E-VIS-4, E2E-VIS-6, E2E-XV-2); this review checks that what the API +says is right. + +## Verdict + +**Signed off**, with the four defects below fixed in this branch and three notes left as they are, each with its +reason. No number on the rebuilt screens disagrees with its cited source. + +## Numbers checked against their sources + +| Shown | Value | Source | Holds | +|---|---|---|---| +| RSA and finite-field DH | 1024 → 80, 2048 → 112, 3072 → 128, 7680 → 192, 15360 → 256 | NIST SP 800-57 Part 1 Rev. 5, Table 2 | yes | +| Elliptic curves | P-256, X25519, Ed25519 → 128; P-384 → 192; P-521 → 256 (half the base-point order) | SP 800-57 Table 2; SP 800-186 Table 1 | yes | +| Hybrid groups | X25519MLKEM768 and SecP256r1MLKEM768 → 192; SecP384r1MLKEM1024 → 256 | the stronger component: breaking a hybrid needs both components broken (draft-ietf-tls-ecdhe-mlkem); ML-KEM-768 is category 3 (FIPS 203) | yes | +| ML-KEM, ML-DSA, SLH-DSA | 512/44/128x → 128; 768/65/192x → 192; 1024/87/256x → 256 | NIST PQC security categories 1, 3, 5 (2 for ML-DSA-44, rated 128 as its SHA-256 collision bound), FIPS 203, 204, 205 | yes | +| AES, ChaCha20 | by key: 128, 192, 256 | SP 800-57 Table 2 | yes | +| 3DES | 112, disallowed | SP 800-131A Rev. 2 (disallowed for encryption after 2023); 64-bit block (SWEET32) makes its suites Weak | yes | +| RC4, export, NULL, single DES, anonymous | broken | RFC 7465; RFC 9325 §4.1 | yes | +| Hashes in key derivation | SHA-256 and SHA-384 (HKDF, PRF) → 256 | SP 800-57 Table 3, KDF column | yes | +| Hashes in a signature, and alone | SHA-256 → 128, SHA-384 → 192, SHA-512 → 256 (collision resistance); SHA-1 and MD5 broken | SP 800-57 Table 3; SP 800-131A | yes | +| A signature | the weaker of its hash and its key: ECDSA-SHA256 on P-256 → 128; SHA256withRSA on RSA-2048 → 112 | SP 800-57 §5.6.1.2 | yes | +| HMAC | HMAC-SHA-1 → 128, HMAC-SHA-256 and -512 → 256; HMAC-MD5 not rated | SP 800-57 Table 3, HMAC column; RFC 6151 | yes | +| Suite word | Insecure under 80 or a broken part; Weak 80 to 111, a 64-bit block or no forward secrecy; Acceptable 112 to 127 or not AEAD; Strong otherwise | PRD-010 §2, from RFC 9325 §4.1 and §4.2 | yes: every suite on six reports agrees (E2E-STR-1) | +| Bands of the bits bar | red < 80, amber 80 to 111, grey 112 to 127, green ≥ 128 | the same edges as the suite word | yes, and Help states them | +| Readiness L0 to L4 | the level stepper fills to the evaluation's level | PRD-004 readiness model | yes (E2E-VIS-4) | +| Certificate validity | days left, of the lifetime; "today" placed where today falls | the certificate's notBefore and notAfter | yes, after fix 4 | +| Fingerprints | SHA-256 of the DER; SPKI SHA-256; SHA-1 marked "an identifier for searching, never for trust" | the DER (E2E-XV-3 hashes the PEM shown) | yes | + +## Claims checked + +- **"A suite is as strong as its weakest part, ringed."** True of the rule (PRD-010 §2) and of the screen: the ringed + cell is the evaluation's `weakest` part on every suite of the journey's reports (E2E-CLS-2). When two parts tie, + the first in column order is ringed, which is the part the evaluation names. +- **"A current client gets TLS 1.3 + suite + group."** The banner is the scan's negotiated handshake (the default + client hello), not a guess. Correct. +- **Off versions "as they should be".** Only said when TLS 1.2 or 1.3 is on; SSL 2, SSL 3, TLS 1.0 and 1.1 off is + right per RFC 8996. TLS 1.3 off says "should be on" (RFC 9325 §3.1.1 SHOULD); after fix 2, so does TLS 1.2 when + neither modern version is on (RFC 9325 §3.1.1 MUST). +- **Post-quantum glyphs.** A shield with a check (quantum-safe) on ML-KEM, ML-DSA, SLH-DSA, the hybrids, AES and + SHA-2; a shield with a cross on RSA, ECDH, ECDSA, EdDSA and finite-field DH. AES-128 as quantum-safe follows NIST + IR 8547 (AES-128 stays approved; Grover gives no practical break) and the cited classification the CBOM carries. +- **Heartbeat.** "Negotiated" when the server echoes the extension (RFC 6520), with the note that nothing needs it; + the probe does not send a heartbeat request, so it never claims Heartbleed is present or absent (that stays + *Not tested* under Weaknesses). 0-RTT says *Not probed yet*: correct, nothing sends early data. +- **Revocation.** "The CA's responder and CRL are not contacted" is true of the scanner: it reads a stapled response + and the URLs the certificate names, and never fetches them. +- **The chain.** "No trusted root" and "Missing intermediate" follow the PKI view's own diagnosis (RFC 5280 path + building against the bundled stores and the system's CA bundle); the dashed card says what to do. +- **HSTS.** The header is shown as sent. After fix 3, an invalid header says browsers ignore it (RFC 6797 §8.1), + instead of reading as `max-age 0`. + +## Defects found and fixed in this branch + +1. **"Finite-field DH: not offered" next to an accepted `TLS_DH_anon` suite.** The scan accepted a DH suite but kept + no prime for it (the report's `dh` is empty), so the screen must not say the opposite of the suite list. The chip now says *offered · + prime not recorded*, and keeps *not offered* for when no DH suite was accepted. +2. **TLS 1.2 off, with neither modern version on, showed only "!"**. It now says *should be on*, as TLS 1.3 does. +3. **An invalid HSTS header read as "max-age 0: switched off".** A header with problems (duplicate directives, no + max-age, bad syntax) is ignored by user agents. The tile now says so, in the problem tone, and shows the header. +4. **The certificate page put "today" mid-way under the validity bar** whatever the date, which reads as half the + lifetime used. It is now placed where today falls (and left out near either end, where the dates are). +5. **An EC key with *Key encipherment* passed silently.** An elliptic-curve key cannot encipher; RFC 8813 §3 says a CA + must not assert it for one. The usage chip is now marked *not for an EC key*, with the reason in its tooltip. + (The demo's self-signed `localhost` certificate asserts it.) +6. **"Not a quantum question" on algorithms no source classified.** The Inventory's `unknown` status (HMAC, 3DES, RC4, + MD5, and `SHA256withRSA` from an imported CBOM) showed the dot with that tooltip, which is wrong for RSA. *Not + classified* now has its own glyph (a question mark) and the class bars' legend says *not classified*. + +## Notes left as they are + +- **TLS 1.3 suites are rated with the weakest group the server accepts.** In TLS 1.3 the suite does not fix the + group; a client that offers only X25519 gets X25519 from a server that prefers X25519MLKEM768. Rating with the + weakest accepted group is the honest worst case, and the group chips beside each version show which groups those + are, with their bits. Kept. +- **SSL 3 suites name their MAC "HMAC-MD5" and "HMAC-SHA-1".** SSL 3's MAC is the pre-HMAC construction of + RFC 6101 §5.2.3.1, not HMAC. Its strength is shown as not rated (MD5) or by its hash (SHA-1), and the version is + marked *should be off* regardless, so no verdict changes. Renaming would change the CBOM's algorithm names; left + for the catalogue owner, noted here. +- **The Inventory rates a hash alone by collision resistance** (SHA-256 → 128), while Configuration rates the same + hash deriving keys at 256. Both are right for their use; Help says which is which, and each figure's tooltip + names its basis. + +## Sign-off + +The numbers and claims on the rebuilt screens are correct against their sources, with fixes 1 to 6 in this branch. +Signed off for PRD-013 REQ-VIS-12, cryptography. diff --git a/docs/product/ENH-002-algorithm-classes.md b/docs/product/ENH-002-algorithm-classes.md new file mode 100644 index 0000000..d6eb70b --- /dev/null +++ b/docs/product/ENH-002-algorithm-classes.md @@ -0,0 +1,107 @@ +# ENH-002 · Every algorithm by class (asymmetric, symmetric, hash), with its strength + +Status: decisions taken by the owner 2026-10-21 (§5: OD-1 and OD-2 as recommended); to be baselined as PRD-011. Raised by the owner: "the cipher, its strength and its +classification (asymmetric, hashing, symmetric) would be needed, so that you can show it." Reviewed against +`feat/classical-strength` (PR #19, PRD-010). + +## 1. Short answer + +**The model has the parts, but no screen shows them.** PRD-010 rates every cipher suite by its weakest part, and +the evaluation already stores each part: cipher, key exchange, authentication and MAC, each with its bits. The +report shows one word and the weakest part only ("Strong · 128 bits · x25519"). No surface answers the questions a +cryptography reviewer asks first: +- which **asymmetric** algorithms does this endpoint use (key exchange, signatures), and how strong is each? +- which **symmetric** ciphers, and how strong is each? +- which **hashes**, and for what? + +Hashes are missing from the model too: +- a TLS 1.3 suite's hash (SHA-384 in `TLS_AES_256_GCM_SHA384`, used by HKDF) is not a part; +- a TLS 1.2 AEAD suite's PRF hash is not a part; +- the certificate's signature hash (SHA-256 in ECDSA-SHA256) is not rated anywhere: the Inventory shows + `SHA256withRSA` as *not rated*. + +## 2. Evidence + +| # | Where | Today | Gap | +|---|---|---|---| +| C-1 | Report › Configuration › suite table | Strength word, bits and the weakest part | The other parts, each part's class, and its strength are in the API (`suiteStrength[].parts`) but are never drawn | +| C-2 | Report, any tab | There is no "algorithms this endpoint uses" view | A reviewer has to read the suite names to find out that the endpoint uses RSA, ECDHE, AES-GCM and SHA-384 | +| C-3 | Model (`internal/eval/strength.go`) | Parts: cipher, key exchange, authentication, MAC | No PRF/HKDF hash part; no signature hash on the certificate | +| C-4 | Inventory › Algorithms | A *Kind* column (block cipher, signature, hash…) | No class grouping or filter: the list cannot show "every symmetric cipher" or "every hash" | +| C-5 | Inventory | `SHA256withRSA`, `ecdsa-with-SHA256` | A composite signature algorithm is rated only when the CBOM names its key; it is never split into hash and key | +| C-6 | CBOM tab | Name, Type, Detail, Post-quantum | No classical strength (the CBOM file has `classicalSecurityLevel`, the table does not show it) | +| C-7 | PDF and CLI | Suite word and weakest part | Same gap as C-1 | + +## 3. Proposal + +### 3.1 One class vocabulary +Each primitive gets one of three classes, and a role within it: + +| Class | Roles | Examples | +|---|---|---| +| **Asymmetric** | key exchange (KEM, key agreement, key transport), signature | ECDHE secp256r1, X25519, ML-KEM-768, RSA key transport, ECDSA P-256, RSA-2048, ML-DSA-65 | +| **Symmetric** | bulk cipher (AEAD, block, stream), MAC | AES-256-GCM, ChaCha20-Poly1305, 3DES, RC4, HMAC-SHA-256 | +| **Hash** | PRF or KDF, signature hash, MAC hash, fingerprint | SHA-384 (HKDF), SHA-256 (in ECDSA-SHA256), SHA-1 | + +The class comes from the CycloneDX `primitive` already in the catalogue (`ae`, `block-cipher`, `kem`, +`signature`…), so nothing is classified twice. "Fingerprint" hashes are shown but never rated: we compute them +ourselves. + +### 3.2 The model +- Add a **hash part** to every suite: + - the PRF hash in TLS 1.2; + - the HKDF hash in TLS 1.3; + - SP 800-57 Table 3 rates a hash in HMAC, KDF and RBG use separately from its collision strength: SHA-1 ≥128, + SHA-256 and SHA-384 ≥256. + + This part never lowers a suite today: NIST rates SHA-1 at ≥128 for HMAC and KDF use. It is shown because a reviewer + needs to see it, and so that one later rule change touches the model only. +- **Signature algorithm = hash + key.** `ECDSA-SHA256` is rated as the weaker of its hash's collision strength + (Table 3) and the key's strength. A certificate whose key we know (any TLS scan) is always rated. An imported + `SHA256withRSA` with no key size is rated by its hash, and says that the key was not given. +- `algo.ClassOf(name)` returns class and role. Every catalogue entry gets one, and a test enforces it. + +### 3.3 Surfaces +1. **Report › Configuration › a new "Algorithms in use" card**, above the suites: + - three groups (Asymmetric, Symmetric, Hash); + - each row gives: the algorithm, its role, its classical strength, its post-quantum status, and where it is used + (which versions or suites, or the certificate); + - the weakest row in each group is marked. + + This is the view a reviewer reads first. +2. **The suite row expands** to its parts: one line per part with class, name and strength, the weakest marked. It + replaces the single "weakest" hint, which stays as the collapsed summary. +3. **Inventory › Algorithms:** a *Class* filter (Asymmetric, Symmetric, Hash) next to *Only quantum-vulnerable*, + with the class shown in the *Kind* column ("Asymmetric · signature"). +4. **CBOM tab:** a *Classical strength* column, from `classicalSecurityLevel`. +5. **PDF and `tls scan --details`:** the same "Algorithms in use" table, and each suite's parts. +6. **SSH report:** the existing tables are already one per role; each gets its class in the heading + ("Key exchange methods · asymmetric"). +7. **API:** the evaluation gains `algorithms[]` ({name, class, role, strength, pqc, usedBy[]}), and each suite part + gains `class`. + +## 4. Requirements (draft for PRD-011) + +| ID | Requirement | Test | +|---|---|---| +| REQ-CLS-1 | `algo.ClassOf` gives every catalogue entry a class and a role; none is left unclassified | `TestEveryAlgorithmHasAClass` | +| REQ-CLS-2 | Each suite gains its hash part (PRF or HKDF), rated by SP 800-57 Table 3 for that use | `TestSuiteHasItsHashPart` | +| REQ-CLS-3 | A signature algorithm is rated as the weaker of its hash (collision strength) and its key; a known key is always used | `TestSignatureAlgorithmIsHashAndKey` | +| REQ-CLS-4 | The evaluation carries `algorithms[]`, one per distinct algorithm, with class, role, strength, post-quantum status and the suites or certificate that use it | `TestEvaluationListsAlgorithmsByClass` | +| REQ-CLS-5 | The report's *Algorithms in use* card, grouped by class, agrees row for row with the API | e2e `E2E-CLS-1` | +| REQ-CLS-6 | A suite row expands to its parts, the weakest marked | e2e `E2E-CLS-2` | +| REQ-CLS-7 | The Inventory's *Class* filter; the CBOM tab's *Classical strength* column | e2e `E2E-CLS-3`, `TestInventoryFiltersByClass` | +| REQ-CLS-8 | The PDF and the CLI show the same table and parts | `TestPDFListsAlgorithmsByClass`, `TestDetailsShowSuiteParts` | +| REQ-CLS-9 | Help and the methodology explain the three classes and the hash rule | `report:docs/methodology.md` | + +## 5. Decisions (owner, 2026-10-21) + +- **OD-1: does the hash part count toward a suite's word? Decided: yes, as a part.** It never decides a word with + today's NIST numbers, but one rule is easier to explain than an exception. +- **OD-2: where does the card sit? Decided: Configuration, first card.** The Summary stays the short answer (what + to fix), and gains one line naming the weakest suite and its bits, which links to the card (UX-REVIEW-010 U-3). + +## 6. Size + +Medium: one model change (a hash part, and the signature split), one new evaluation list, and four surfaces. It +builds on PRD-010's model and tests; no new standard is needed. diff --git a/docs/product/ENH-003-certificate-viewer.md b/docs/product/ENH-003-certificate-viewer.md new file mode 100644 index 0000000..9d69ed6 --- /dev/null +++ b/docs/product/ENH-003-certificate-viewer.md @@ -0,0 +1,108 @@ +# ENH-003 · Open any certificate and read the whole X.509 document + +Status: decisions taken by the owner 2026-10-21 (§5: OD-1 and OD-2 as recommended); to be baselined as PRD-012. Raised by the owner: "the certs are just shown as a chain; clicking +one should show more details of the X.509 document." Reviewed against `feat/classical-strength` (PR #19). + +## 1. Short answer + +**The data is there; the screens hide it, or do not have it.** +- The scanner keeps every certificate as served: the DER bytes and about 30 parsed fields, every extension + included (`internal/tlsprobe/report.go`, `CertInfo`). +- What a user sees is far less: + - the report's *Chain as served* is a list that cannot be clicked; + - its details sit in a collapsed *Validity, names and every field* panel, which shows 14 fields; + - the Inventory’s certificate page shows 14. +- Neither shows the extensions, their criticality or their values, or the key identifiers, the revocation + pointers, the policies, the constraints or the PEM. +- The panel's title promises "every field", which it does not show. + +## 2. Evidence + +| # | Where | Today | Gap | +|---|---|---|---| +| X-1 | Report › Configuration › *Chain as served* | Position, subject, role, key, expiry | Not clickable; no way from a node to its details | +| X-2 | Report › *Validity, names and every field* | Subject, issuer, key, key OID, signature, signature OID, serial, validity, type, SHA-256, SANs, must-staple, SCT | **Missing although recorded:** key usage, extended key usage, basic constraints (path length), name constraints, policies and validation type, AKI/SKI, OCSP and CA-issuer URLs, CRL points, RSA exponent, SPKI SHA-256, RSASSA-PSS parameters, every extension with its criticality, the PEM | +| X-3 | Inventory › Certificate | 14 fields, findings, where it was seen | No link to the scan report or the chain it was served in; same gaps as X-2 | +| X-4 | Both | Key shown as "ECDSA 256-bit (P-256)" | No classical strength or post-quantum status on the certificate's own page (PRD-010 has both) | +| X-5 | Model | Extensions recorded as OID, name, critical | The **values** of extensions we do not model (an unknown private extension, the SCT list) are not kept, though the DER is | +| X-6 | Export | Certificates CSV and JSON for the whole scan | No PEM for one certificate, which is what an engineer pastes into `openssl x509 -text` | + +## 3. Proposal + +### 3.1 One certificate page, reached from everywhere +The certificate page (`#/certificates/{sha256}`) becomes the single place a certificate is read: +- every node of *Chain as served* is a link to it; +- so is every certificate in the Inventory, Findings and the CBOM tab. + +When the link comes from a report, the page names that scan ("served by modern-hybrid, position 0 of 2") and links +back to it. Back returns to the chain. + +### 3.2 What the page shows, in the order an engineer reads it + +1. **Summary line:** + - who it is for and who issued it; + - the days left; + - trust per store (when reached from a scan); + - its findings. +2. **Identity:** subject, issuer (a link to the issuer's page when we hold it), serial, version, validity, lifetime + and validation type (DV, OV or EV). +3. **Names:** SANs by type (DNS, IP, e-mail, URI), wildcards, and whether the CN is the only name. +4. **Public key:** + - algorithm, size or curve, and the RSA exponent; + - SPKI SHA-256 (for pinning); + - the **classical strength** and the **post-quantum status**, both from PRD-010 and the catalogue. +5. **Signature:** algorithm, OID, PSS parameters, and its strength (the weaker of the hash and the issuer's key; + ENH-002 §3.2). +6. **Extensions, every one:** + - name, OID, a *critical* mark, and the decoded value; + - covers key usage, EKU, basic constraints with the path length, name constraints, AKI and SKI, AIA (OCSP and CA + issuers), CRL distribution points, certificate policies, the SCT list (log and time) and must-staple; + - an extension we do not decode is shown as its OID and its hex value, never dropped. +7. **Fingerprints:** SHA-256, plus SHA-1 marked as an identifier only. +8. **Raw:** + - the PEM, with *Copy* and *Download .pem*; + - the `openssl x509 -text -noout` line to inspect it locally. +9. **Where it was seen**, as today. + +### 3.3 Server +- `GET /api/v1/certificates/{sha256}/x509` decodes the stored DER on request: the fields above, every extension + with its value, and the PEM. It is read only. + - Nothing new is stored, so old scans gain the view without a rescan, as long as they kept the DER (tls-report/2 + and later). + - Earlier reports show the parsed fields they have and say the raw certificate was not kept. +- The decode is the standard library's `x509.ParseCertificate`, plus a small table of extension decoders. It is + fuzzed: the DER comes from the network. + +### 3.4 Report changes +- *Chain as served*: each node is a link, with a *details* affordance and a keyboard focus ring. +- The collapsed *Validity, names and every field* panel is renamed *Quick look*, since it is not every field, and + points to the full page. + +## 4. Requirements (draft for PRD-012) + +| ID | Requirement | Test | +|---|---|---| +| REQ-X509-1 | `GET /api/v1/certificates/{sha256}/x509` returns every field of §3.2, every extension with criticality and value, and the PEM; an unknown extension is returned as OID and hex | `TestX509DecodeShowsEveryExtension` (a corpus of real and crafted certificates) | +| REQ-X509-2 | The decoder never panics or hangs on hostile DER | `FuzzX509Decode` | +| REQ-X509-3 | Every node in *Chain as served*, and every certificate link in the app, opens the certificate page; Back returns | e2e `E2E-X509-1` | +| REQ-X509-4 | The page shows the sections of §3.2 in that order, with the key's classical strength and post-quantum status | e2e `E2E-X509-2` | +| REQ-X509-5 | *Copy* and *Download .pem* give a PEM that `x509.ParseCertificate` reads back to the same SHA-256 | `TestPEMRoundTrip`, e2e `E2E-X509-3` | +| REQ-X509-6 | A report without DER says so and shows what it has | `TestX509WithoutDERSaysSo` | +| REQ-X509-7 | All values render as text: certificates carry attacker-chosen strings (the existing hostile-subject fixture) | e2e `E2E-X509-4` | +| REQ-X509-8 | The page is usable at 375 px and with the keyboard alone | e2e `E2E-X509-5` | + +## 5. Decisions (owner, 2026-10-21) + +- **OD-1: a full page or a side panel? Decided: a full page.** It can be linked to and shared, Back works, and it + is the page the Inventory already has. A side panel would be a second place that shows the same thing. +- **OD-2: show the SHA-1 fingerprint? Decided: yes, labelled as an identifier only**, because that is what people + still search for in CA portals and browsers. + +## 6. Size + +Medium: +- one read-only API endpoint with a decoder and a fuzz test; +- one page extended; +- links from four places. + +No scanner change, and no stored-data change. diff --git a/docs/product/PRD-011-algorithm-classes-and-flow.md b/docs/product/PRD-011-algorithm-classes-and-flow.md new file mode 100644 index 0000000..2876062 --- /dev/null +++ b/docs/product/PRD-011-algorithm-classes-and-flow.md @@ -0,0 +1,46 @@ +# PRD-011 · Every algorithm by class, and a flow that explains itself + +Status: baselined 2026-10-21. The owner approved [ENH-002](ENH-002-algorithm-classes.md) and +[UX-REVIEW-010](UX-REVIEW-010-flow-and-self-explanation.md) with every open decision as recommended: +- the hash part counts in the weakest-part rule; +- the *Algorithms in use* card goes first on Configuration; +- the TLS hygiene bands are renamed, so no word belongs to two scales. + +Issues #20 and #22. Status per requirement: [RTM](../program/RTM.md). Results: QA-REPORT-011. + +## 1. Goals + +| # | Goal | Measure | +|---|---|---| +| G-1 | A reviewer sees every algorithm an endpoint uses, grouped as asymmetric, symmetric and hash, each with its strength | One card answers "what crypto does this endpoint use, and how strong is each piece?" | +| G-2 | A suite's rating can be checked part by part | Every part is shown, with its class, strength and source | +| G-3 | No word means two things | The hygiene bands and the suite words share no word | + +Non-goals: new findings, or a change to the TLS hygiene score's numbers. + +## 2. Classes + +| Class | CycloneDX primitives | Roles | +|---|---|---| +| asymmetric | `pke`, `signature`, `key-agree`, `kem`, `combiner` | key exchange, signature | +| symmetric | `ae`, `block-cipher`, `stream-cipher`, `mac` | cipher, MAC | +| hash | `hash` | hash (PRF, HKDF or signature) | + +## 3. Requirements + +| ID | Requirement | Acceptance | Test | Pri | +|---|---|---|---|---| +| **REQ-CLS-1** | **The class model.** `algo.ClassOf` gives every catalogue entry, and every name this program derives (RSA-2048, ECDSA-P256, the group names, the signature spellings), a class and a role from §2. | No catalogue entry is unclassified | `TestEveryAlgorithmHasAClass` | P0 | +| **REQ-CLS-2** | **Hash part.** Every suite gains its hash part: HKDF in TLS 1.3, the PRF in TLS 1.2 (SHA-256 unless the suite names SHA-384), MD5 and SHA-1 combined in TLS 1.0 and 1.1. It is rated in its KDF use (SP 800-57 Table 3), and it counts in the weakest-part rule (OD-1). | Every registry suite has a hash part; no word changes on today's demo targets | `TestSuiteHasItsHashPart`, `TestEverySuiteInTheRegistryIsRated` | P0 | +| **REQ-CLS-3** | **Signature algorithms.** A signature algorithm is split into its hash and its key: `ECDSA-SHA256`, `SHA256-RSA`, `sha256WithRSAEncryption` and `SHA256withRSA` are one algorithm. It is rated as the weaker of the hash's collision strength and the key's strength; with no key size, the hash's strength and a basis saying the key was not given. | `SHA256withRSA` is no longer *not rated* | `TestSignatureAlgorithmIsHashAndKey` | P0 | +| **REQ-CLS-4** | **API.** The evaluation carries `algorithms[]`, one per distinct algorithm, with its class, role, strength, post-quantum status and what uses it (versions, suites, the certificate). Each suite part gains `class`. `eval.Version` becomes `eval/10`. | | `TestEvaluationListsAlgorithmsByClass` | P0 | +| **REQ-CLS-5** | **Algorithms in use.** The first card on Configuration groups the endpoint's algorithms by class, each row with its role, strength, post-quantum status and uses, the weakest per class marked. It agrees row for row with the API. | | `script:testing/e2e/classes-phase.mjs#E2E-CLS-1` | P0 | +| **REQ-CLS-6** | **Suite parts.** A suite's strength cell opens its parts: class, name and strength, the weakest marked. | | `script:testing/e2e/classes-phase.mjs#E2E-CLS-2` | P0 | +| **REQ-CLS-7** | **Inventory and CBOM tab.** The Inventory's algorithm list has a *Class* filter and says the class in *Kind*; the report's CBOM tab has a *Classical strength* column. | | `TestInventoryFiltersByClass`, `script:testing/e2e/classes-phase.mjs#E2E-CLS-3` | P1 | +| **REQ-CLS-8** | **PDF and CLI.** The PDF has the *Algorithms in use* table; `tls scan --details` lists it and each suite's parts. | | `TestPDFListsAlgorithmsByClass`, `TestDetailsShowAlgorithmsByClass` | P1 | +| **REQ-UXF-1** | **Hygiene bands renamed** (UX-REVIEW-010 U-1): Excellent (90+), Good (75+), Fair (55+), Poor (30+), Failing. A stored scan's old band (Strong, Sound, Weak, Broken) is migrated in the database and mapped when an old evaluation is read. | No screen, API answer or PDF shows an old band name | `TestBandsHaveTheirOwnWords`, `TestOldBandsAreMigrated` | P0 | +| **REQ-UXF-2** | **Summary headline** (U-2): the line under "N things to fix" names the first thing to fix; the post-quantum sentence stays in its tile. | | `script:testing/e2e/classes-phase.mjs#E2E-UXF-1` | P1 | +| **REQ-UXF-3** | **Summary strength** (U-3): the *Cipher suites* row names the weakest suite, its word and bits, and links to *Algorithms in use*. | | `script:testing/e2e/classes-phase.mjs#E2E-UXF-1` | P1 | +| **REQ-UXF-4** | **Reasons and links** (U-6, U-7): a *not rated* Inventory cell says why; the hygiene tile links to Help › TLS hygiene. | | `script:testing/e2e/classes-phase.mjs#E2E-UXF-2` | P2 | +| **REQ-UXF-5** | **Overview tile** (U-8): the TLS endpoints tile shows the count and the exposed figure only. | | `script:testing/e2e/classes-phase.mjs#E2E-UXF-2` | P2 | +| **REQ-CLS-9** | **Documentation.** The methodology gives the classes and the hash rule; Help explains the card and the new band words; the CHANGELOG names the rename. | | `report:docs/methodology.md` | P1 | diff --git a/docs/product/PRD-012-certificate-viewer.md b/docs/product/PRD-012-certificate-viewer.md new file mode 100644 index 0000000..24acd8a --- /dev/null +++ b/docs/product/PRD-012-certificate-viewer.md @@ -0,0 +1,30 @@ +# PRD-012 · Open any certificate and read the whole X.509 document + +Status: baselined 2026-10-21. The owner approved [ENH-003](ENH-003-certificate-viewer.md) with both open decisions +as recommended: +- a full page, not a side panel; +- the SHA-1 fingerprint shown, labelled as an identifier only. + +Issue #21. Status per requirement: [RTM](../program/RTM.md). Results: QA-REPORT-011. + +## 1. Goals + +| # | Goal | Measure | +|---|---|---| +| G-1 | Every certificate the product shows can be opened | Every chain node and certificate link leads to one page | +| G-2 | That page is the whole X.509 document | Every field and every extension the DER holds, decoded or as OID and hex | +| G-3 | Nothing new is stored, and old scans gain the view | Decoded from the DER on request | + +## 2. Requirements + +| ID | Requirement | Acceptance | Test | Pri | +|---|---|---|---|---| +| **REQ-XV-1** | **Decode.** `GET /api/v1/certificates/{sha256}/x509` returns, from the stored DER, every field of ENH-003 §3.2: identity, names, public key with its classical strength and post-quantum status, signature with its strength, every extension (name, OID, critical, decoded value), fingerprints and the PEM. An extension it does not decode is OID and hex. | Matches `openssl x509 -text` on the test corpus | `TestX509DecodeShowsEveryExtension` | P0 | +| **REQ-XV-2** | **Hostile input.** The decoder never panics or hangs on any DER. | | `FuzzX509Decode` | P0 | +| **REQ-XV-3** | **Where the DER comes from.** The scan the page was opened from (`?scan=`), else the newest endpoint scan that saw it; only endpoint scans keep DER. A certificate known only from a report without DER (before tls-report/2), a machine inventory or an imported CBOM shows the fields it has, and says the raw certificate was not kept. | | `TestX509WithoutDERSaysSo` | P1 | +| **REQ-XV-4** | **Reached from everywhere.** Every node of *Chain as served*, the quick-look cards, and the CBOM tab's certificates link to `#/certificates/{sha256}`. When reached from a scan, the page names the scan and the chain position and links back. | Back returns to the chain | `script:testing/e2e/x509-phase.mjs#E2E-XV-1` | P0 | +| **REQ-XV-5** | **The page.** The sections of ENH-003 §3.2, in that order. The panel that said *every field* is renamed *Quick look*. | | `script:testing/e2e/x509-phase.mjs#E2E-XV-2` | P0 | +| **REQ-XV-6** | **PEM.** *Copy PEM* and *Download .pem* give a PEM that parses back to the same SHA-256. | | `TestPEMRoundTrip`, `script:testing/e2e/x509-phase.mjs#E2E-XV-3` | P1 | +| **REQ-XV-7** | **Text only.** Every value is rendered as text: certificates carry attacker-chosen strings. | The hostile-subject fixture runs no script | `script:testing/e2e/x509-phase.mjs#E2E-XV-4` | P0 | +| **REQ-XV-8** | **Phone and keyboard.** The page works at 375 px without sideways scroll, and every link and button is reachable by Tab. | | `script:testing/e2e/x509-phase.mjs#E2E-XV-5` | P1 | +| **REQ-XV-9** | **Documentation.** Help and the API reference describe the page and the endpoint. | | `report:docs/methodology.md` | P2 | diff --git a/docs/product/PRD-013-visual-rebuild.md b/docs/product/PRD-013-visual-rebuild.md new file mode 100644 index 0000000..b470a4b --- /dev/null +++ b/docs/product/PRD-013-visual-rebuild.md @@ -0,0 +1,24 @@ +# PRD-013 · The visual rebuild: Summary, Configuration, certificates, Inventory + +Status: baselined 2026-10-21, approved by the owner ("let's build it"). Design: +[UX-DESIGN-011](UX-DESIGN-011-instruments-not-paragraphs.md). It replaces the screens PRD-011 and PRD-012 first +shipped; their data and APIs stay. Sign-offs: the cryptographer's review (CRYPTO-REVIEW-001) and the PM's. +Results: QA-REPORT-012. + +## Requirements + +| ID | Requirement | Acceptance | Test | Pri | +|---|---|---|---|---| +| **REQ-VIS-1** | **Shared instruments** (`viz.js`, UX-DESIGN-011 §2): bits bar and axis, strength chip, part cell, post-quantum glyph, status tile, lifetime bar, gauge, level stepper, stack bar, jump row. Each one has a text alternative. | One scale and one set of colours everywhere | `TestInstrumentsAreSharedAndLabelled`, `script:testing/e2e/visual-phase.mjs#E2E-VIS-1` | P0 | +| **REQ-VIS-2** | **Summary** (§3.1): instruments, Fix first, the checks bar, area tiles; no coverage sentence and no "why it matters" groups. | Each tile opens its section | `script:testing/e2e/visual-phase.mjs#E2E-VIS-2` | P0 | +| **REQ-VIS-3** | **Configuration, protocols and suites** (§3.2.1): the negotiated banner; each version on with its groups and its suites in server order, one row each with five part cells and the strength chip; versions off on one line. | The rows equal the report's suites in rank order, and the cells equal the evaluation's parts | `script:testing/e2e/visual-phase.mjs#E2E-VIS-3`, `script:testing/e2e/strength-phase.mjs#E2E-STR-1` | P0 | +| **REQ-VIS-4** | **Certificate chain card** (§3.2.2): every certificate served opens its page; a missing issuer or an untrusted root is a dashed card that says what to do; trust per store and the name match beside it. | | `script:testing/e2e/x509-phase.mjs#E2E-XV-1` | P0 | +| **REQ-VIS-5** | **Key exchange, revocation and features** (§3.2.3 to 5), as chip rows and status tiles, each saying what was found and whether that is right. | Every feature the scan records has a tile; one it did not probe says "not probed" | `script:testing/e2e/visual-phase.mjs#E2E-VIS-4` | P0 | +| **REQ-VIS-6** | **The class map** (§3.2.6): one lane per class, each named once; a bits bar per algorithm on one axis; the weakest ringed; it agrees with the API. A sticky jump row leads to every section. | | `script:testing/e2e/classes-phase.mjs#E2E-CLS-1` | P0 | +| **REQ-VIS-7** | **Certificate page** (§3.3), in that order, agreeing with the API; PEM collapsed. | | `script:testing/e2e/x509-phase.mjs#E2E-XV-2` | P0 | +| **REQ-VIS-8** | **Inventory** (§3.4): the class bars and the strength histogram open filtered lists; the list is weakest first with a bits bar. A `strength=` filter (broken, below-112, 112, 128, 192, 256, not-rated). | | `TestInventoryFiltersByStrength`, `script:testing/e2e/visual-phase.mjs#E2E-VIS-5` | P1 | +| **REQ-VIS-9** | **Heartbeat probe.** The scan records whether the server negotiates the heartbeat extension (RFC 6520) and its Features tile shows it. Early data (0-RTT) stays *not probed*, said so on the tile. | | `TestHeartbeatIsProbed` | P2 | +| **REQ-VIS-10** | **SSH report** algorithm tables use the bits bar and the post-quantum glyph. | | `script:testing/e2e/visual-phase.mjs#E2E-VIS-6` | P2 | +| **REQ-VIS-11** | **Fit and access** (§4): no sideways scroll at 1440, 1100, 860 and 375 px; every control reachable by Tab; dark mode; contrast AA. | | `script:testing/e2e/visual-phase.mjs#E2E-VIS-7` | P0 | +| **REQ-VIS-12** | **Sign-off.** A cryptographer checks every number and claim the new screens show, against its source (CRYPTO-REVIEW-001); the PM signs off the walk (QA-REPORT-012). | | `report:docs/product/CRYPTO-REVIEW-001-visual-rebuild.md` | P0 | +| **REQ-VIS-13** | **Versions fold** (owner request, 2026-10-21): each TLS version that is on is open only when it needs a look (an old version on, a suite below Strong, or the only version); folded, its head says how its suites rate; one button shows or hides every version and says which it will do. | A reviewer sees first what there is to do | `script:testing/e2e/visual-phase.mjs#E2E-VIS-FOLD` | P1 | diff --git a/docs/product/UX-DESIGN-011-instruments-not-paragraphs.md b/docs/product/UX-DESIGN-011-instruments-not-paragraphs.md new file mode 100644 index 0000000..8b6b5a9 --- /dev/null +++ b/docs/product/UX-DESIGN-011-instruments-not-paragraphs.md @@ -0,0 +1,154 @@ +# UX-DESIGN-011 · Instruments, not paragraphs + +Status: approved by the owner 2026-10-21 ("let's build it"), from the concept canvas +() and its three review comments. It answers +[UX-REVIEW-010](UX-REVIEW-010-flow-and-self-explanation.md) and the owner's verdict on the first PRD-011 screens: +"very texty… double symmetric writing with text and bullet points". The data and the story are settled. This +document is the layout, the order and the visual encodings. Requirements: [PRD-013](PRD-013-visual-rebuild.md). +It works inside [DESIGN.md](../../DESIGN.md): Offset theme, flat, one accent, symbol + word + colour. + +## 1. Principles for this rebuild + +1. **Encode first, explain on demand.** Every fact that is a quantity is drawn: bits on a scale, days on a + timeline, a score on a gauge, counts as a stacked bar. Its source and reasoning go in the element's tooltip + (`title`) or one click away, never in a paragraph above it. +2. **One label per thing.** A class, a version or a state is named once, in the place a reader looks for it. No + pill that repeats a heading, and no sentence that repeats a table. +3. **Order by the reviewer's question.** Each screen follows the order an experienced reviewer checks in, and a + sticky jump row shows every section with its state. +4. **Every state is a tile:** a symbol, a word and a colour (✓ good, ! needs attention, ✕ problem, – does not apply + or not probed), with the finding in its sub-line. +5. **The same instrument means the same thing everywhere.** The bits bar on the Configuration, the certificate page, + the Inventory and the SSH report is one component with one scale. + +## 2. Components (`web/static/js/viz.js`) + +| Component | Draws | Rules | +|---|---|---| +| `bitsBar(strength, opts)` | A track 0 to 256 bits with four hard bands (below 80 broken, 80 to 111 weak, 112 to 127 acceptable, 128 and up strong), a fill to the value in its band's ink, the value in mono to its right, and an optional "weakest" ring | A *broken* status fills the track with a hatched red. *Not rated* draws an empty track and the words "not rated". `title` holds the basis. `role="img"` with an aria-label in words. | +| `bitsAxis()` | The tick row (0, 80, 112, 128, 192, 256) for a column of bars | Shared by every column that uses bars. | +| `strengthChip(word)` | Strong, Acceptable, Weak or Insecure, as a tinted chip | It is the only place a suite's word appears. | +| `partCell(part, weakest)` | One heat cell: the bits in mono on its band's tint, a ring when it is the weakest, "–" when the part is not used (AEAD has no MAC) | `title` = part name and strength. | +| `pqGlyph(status)` | A shield with a check (quantum-safe), a shield with a cross (quantum-vulnerable), a middle dot (no quantum question) | Always has an accessible name. | +| `statusTile(name, state, tone)` | A small bordered tile: a glyph, a bold name and a one-line state | `tone` is good, attention, problem or na. | +| `lifetimeBar(notBefore, notAfter, now)` | Days used against the lifetime, with a "today" mark | Green while more than 30 days are left, amber within 30, red when expired. | +| `gauge(score, band)` | A 180° arc 0 to 100 in the band's colour, the score, and the band word | Used once, for TLS hygiene. | +| `levelStepper(level)` | Five segments L0 to L4, filled up to the level | Used for readiness. | +| `stackBar(segments)` | A proportional bar of counts, with a legend of counts | Coverage of checks, the Inventory's classes. | +| `jumpNav(sections)` | A sticky row of chips, each with a state dot, that scrolls to its section | Configuration only. | + +## 3. Screens + +### 3.1 Report › Summary (TLS) + +1. **Instruments.** Four tiles in one band: + - readiness (level and stepper); + - TLS hygiene (gauge, and the ceiling that capped it, if any); + - the weakest classical strength (bits bar and the algorithm that sets it); + - the certificate (days left, lifetime bar, a trust chip per store). + + Each tile opens its section. +2. **Fix first.** A numbered list, worst first: a severity chip, the title and the one-line *what to do*, with + *Evidence* on the right. The grouping by "why it matters" goes; the why is in the evidence. +3. **Checks.** A stacked bar (problem, to note, passed, could not run, does not apply) with its counts. It replaces + the coverage sentence. +4. **By area.** One status tile per area: the area and its one-line verdict. It replaces the list of sentences. + +### 3.2 Report › Configuration (TLS): the one-stop check + +A sticky jump row, then, in a reviewer's order: + +1. **Protocols and suites.** + - The negotiated connection as a banner: version + suite + group, post-quantum marked. + - For each version that is on, a header: name, the number of suites, who picks the order, forward secrecy, and + its groups as chips (post-quantum marked). + - Under it, its suites in the server's order, one row each: + - rank; + - full IANA name, with a fact line (key exchange, AEAD or CBC, IANA status); + - the five part cells: cipher, key exchange, signature, MAC, hash; + - the strength chip. + - Versions that are off share one line of ✓ chips. A version that is on but should be off gets a red header + ("should be off"). +2. **Certificate chain.** A ribbon of cards, leaf first, each opening the certificate's page. Each card shows: + - role; + - common name; + - key and signature strength chips; + - self-signed or not; + - a lifetime bar; + - names (on the leaf). + + A missing intermediate or untrusted root is drawn as a dashed card that says what to do. Trust per store and + the name match are chips beside the title. +3. **Key exchange and signatures.** Labelled rows of chips: + - groups per version, in preference order; + - finite-field DH and its prime; + - TLS 1.3 signature schemes; + - certificate key types. + + Readiness (level, stepper and the three dimensions) is a compact row here, not a panel of paragraphs. +4. **Revocation and transparency.** Status tiles: OCSP stapling, must-staple, OCSP and CRL pointers, SCTs (counted, + with the logs in the tooltip). +5. **Features.** Status tiles: secure renegotiation, extended master secret, downgrade protection, compression, + encrypt-then-MAC, session tickets, ALPN, client certificates, heartbeat. Each says what was found and whether + that is right. HSTS joins here. +6. **Everything above, by class.** The crypto map: + - one lane per class (asymmetric, symmetric, hash), each named once; + - a row per algorithm: name and role, a bits bar on the shared axis, the post-quantum glyph, and its uses; + - the weakest of each class ringed. +7. *About this report*, collapsed, as today. + +The vulnerabilities list stays on Weaknesses. + +### 3.3 One certificate + +Top to bottom: +1. **Chain ribbon:** this certificate highlighted, its issuer and root linked, a root that was not sent shown dashed. +2. **Header:** common name (display face), organisation and issuer, chips for type, validation and version, then + Copy PEM and Download .pem. +3. **Validity:** a lifetime bar with the dates and "today". +4. **Two meters,** public key and the issuer's signature: a bits bar, the post-quantum glyph, and the basis in one + muted line. +5. **Names and uses,** as chips: names typed by colour (DNS, wildcard, IP, e-mail, URI); key usage and EKU with a + *critical* flag. +6. **Every extension,** as a definition list: name and OID, then the values as chips or mono. An undecoded one says + so. +7. **Identifiers:** + - SHA-256 and SPKI fingerprints, serial, and SHA-1 (identifier only); + - each with a copy button. +8. **PEM,** collapsed. +9. **Seen on,** as linked cards, then the findings. + +### 3.4 Inventory › Algorithms + +1. **By class:** for each class, its count and a stacked bar by post-quantum status. Each opens the list filtered + to that class. +2. **By classical strength:** a histogram (broken, 80, 112, 128, 192, 256, not rated). Each column opens the list + filtered to it (a new `strength=` filter). +3. **Controls:** search, a segmented class control (All, Asymmetric, Symmetric, Hash), *Only quantum-vulnerable*, + and the source. +4. **The list, weakest first:** name, class and role, a bits bar with its words, the post-quantum glyph, and where + it was seen. Not-rated rows give their reason in the tooltip and a muted sub-line. + +### 3.5 SSH report + +The algorithm tables use `bitsBar` and `pqGlyph` in place of the words column. No other change. + +## 4. Responsive and accessible + +- Measured at 1440, 1100 and 860 px wide, at 680 px high, and at 375 px. +- Wide tables (suite rows, the Inventory list) scroll inside their own box; the page never scrolls sideways. +- Ribbons scroll sideways inside themselves. +- Every instrument has a text alternative (aria-label in words). +- Every link and button is reachable by Tab; status is never colour alone. +- Dark mode uses the existing tokens. +- Text meets 4.5:1 (`TestTokenContrast` and the journey's contrast checks). + +## 5. What goes + +- The class pills. +- The "Every algorithm this endpoint uses…" and legend paragraphs. +- The fact-sentence bullets on Configuration. +- The `Cipher suites` and `Key exchange` fact cards. +- The separate part-by-part table. +- The long label/value cards on the certificate page. +- The Inventory's unsorted long table on top. diff --git a/docs/product/UX-REVIEW-010-flow-and-self-explanation.md b/docs/product/UX-REVIEW-010-flow-and-self-explanation.md new file mode 100644 index 0000000..e3c20f4 --- /dev/null +++ b/docs/product/UX-REVIEW-010-flow-and-self-explanation.md @@ -0,0 +1,47 @@ +# UX-REVIEW-010 · Does the flow explain itself? + +Date: 2026-10-21. Asked by the owner: "Is the flow of the UI good? Is it self-explaining?" Walked as a first-time +reviewer on `feat/classical-strength` (PR #19), on the e2e journey's data. The path: Overview → scan → TLS report +(Summary, Configuration) → a certificate → Inventory. + +## Verdict + +**The first level explains itself; the second level does not yet.** +- **What works:** + - The path from scan to the report's Summary to *what to fix* is clear: one scan box, one headline, the action + items first, every check with its source. + - Navigation is plain (Results / Plan & share), with breadcrumbs everywhere. + - TLS and SSH reports have the same tabs. +- **Where a reader gets stuck:** + - one level down, two scales share words; + - the Summary does not say how strong the crypto is; + - the details a reviewer wants (the algorithm parts, the certificate) are hidden or missing. + +## Findings + +| # | Sev | Screen | What a first-time reader meets | Recommendation | +|---|---|---|---|---| +| U-1 | **High** | Overview, report, Help | **Two scales share words.** The TLS hygiene score's bands are Strong, Sound, Fair, Weak, Broken. PRD-010's suite strength is Strong, Acceptable, Weak, Insecure. "Weak" and "Strong" mean different things one tab apart, and the Overview says "0 rated Strong or Sound". | Give each scale its own words (OD-1). | +| U-2 | Medium | Report › Summary | A red headline "1 thing to fix" is followed first by good news ("Post-quantum: … recorded traffic is protected"). The thing to fix (an untrusted chain) only appears in the next card. | The line under the headline names the thing to fix; the post-quantum sentence moves to the L3 tile, which already says it. | +| U-3 | Medium | Report › Summary | The Summary never says how strong the classical crypto is. *Cipher suites* reads "8 suites accepted: 6 AEAD, 2 CBC". | The row says the weakest suite and its bits ("weakest: Acceptable, 112 bits, RSA-2048 key"), linking to ENH-002's *Algorithms in use*. | +| U-4 | Medium | Report › Configuration | Each suite shows its weakest part only; the classes (asymmetric, symmetric, hash) are never named. | ENH-002. | +| U-5 | Medium | Report › Certificates | The chain cannot be clicked, and the panel called *every field* shows 14 of the recorded fields. | ENH-003. | +| U-6 | Low | Inventory › Algorithms | Generic names (RSA, ECDH, AES, DH) read *not rated* with no reason on the row; the reason (size not known) is only in the API. | Show the reason as the cell's hint: "not rated · size not given". | +| U-7 | Low | Report › Summary | The TLS hygiene tile ("74 Fair") does not say what moves it, or link to how it is worked out. | The tile links to Help › TLS hygiene, as the strength legend now does. | +| U-8 | Low | Overview › TLS endpoints tile | "6 scanned, 1 failed; 0 rated Strong or Sound; 4 exposed to harvest-now-decrypt-later" is four facts in one tile. | Keep the count and one fact (exposed); the rest belongs on the TLS endpoints page. | + +## Decision (owner, 2026-10-21) + +- **OD-1: which scale changes its words? Decided: rename the hygiene bands; keep the suite words.** The new bands + are Excellent (90+), Good (75+), Fair (55+), Poor (30+) and Failing (below 30). Old stored band names are mapped + when a scan is read, and the CHANGELOG says so in words. + - *Why:* the suite words (Strong, Acceptable, Weak, Insecure) are close to NIST SP 800-131A's own usage + ("acceptable", "disallowed"), and they have just shipped in PRD-010. + - *Cost:* the API's band names change, so the change carries a read-time mapping of old names, a test for it and + a CHANGELOG note. + - *Rejected:* renaming the suite words, and dropping them in favour of bits only. + +## Plan + +U-1, U-2, U-3, U-6, U-7 and U-8 are small. They go together as one UX fix (PRD-011's UI part), after OD-1. U-4 is +ENH-002 and U-5 is ENH-003. diff --git a/docs/program/QA-REPORT-011.md b/docs/program/QA-REPORT-011.md new file mode 100644 index 0000000..7569770 --- /dev/null +++ b/docs/program/QA-REPORT-011.md @@ -0,0 +1,89 @@ +# QA-REPORT-011 · Algorithm classes, the flow, and the certificate viewer (PRD-011, PRD-012) + +Date: 2026-10-21. Build: branch `feat/classes-and-x509` on top of `ee275af` (PR #19, PRD-010). Requirements: +[PRD-011](../product/PRD-011-algorithm-classes-and-flow.md), [PRD-012](../product/PRD-012-certificate-viewer.md), +[RTM](RTM.md) §PRD-011 and §PRD-012. Specs: [ENH-002](../product/ENH-002-algorithm-classes.md), +[ENH-003](../product/ENH-003-certificate-viewer.md), [UX-REVIEW-010](../product/UX-REVIEW-010-flow-and-self-explanation.md). + +## Result + +| Gate | Command | Result | +|---|---|---| +| Unit and integration | `go test ./...` | passed on the data layer (commit cb2f165); the final build is in [QA-REPORT-012](QA-REPORT-012.md) | +| Traceability | `TestRequirementsTraceability` (in the above) | 23 new rows (14 PRD-011, 9 PRD-012), all done, each with evidence | +| Lint | `golangci-lint run ./...`, and with `GOOS=linux` | 0 issues, 0 issues | +| Fuzz | `FuzzX509Decode`, 120 s in WSL (the Windows engine stalls on ASN.1) | 6,071,367 inputs, no crash or hang | +| Decoder against OpenSSL | a demo target's certificate through `openssl x509 -text` and `x509view.Decode` | same extensions, criticality, names and SHA-256 | +| UI scripts parse | `node --check` on every module | all parse | +| Browser journey | `testing/e2e/journey.mjs` (CI setup) | 224 passed, 3 failed: the 3 were first-pass screens the owner then rejected, rebuilt by PRD-013 ([QA-REPORT-012](QA-REPORT-012.md)) | +| Screen walk | below | superseded: see below | + +## What the new checks cover + +- **Classes (REQ-CLS-1).** Every catalogue entry has a class, from its CycloneDX primitive. The names this program + derives (RSA-2048, ECDSA-P256, the group names, every signature spelling) have one too. +- **The hash part (REQ-CLS-2).** Every registry suite gets its hash part, at every version that can carry it: + - HKDF's hash in TLS 1.3; + - the PRF's in TLS 1.2 (SHA-256 unless the suite names SHA-384); + - the MD5 and SHA-1 pair in TLS 1.0 and 1.1. + + In no case does the hash part decide a word. It can tie, and the first part in order is then named. +- **Signatures (REQ-CLS-3).** + - The Go, OID and Java spellings each split into hash and key. + - The weaker of the two decides: SHA256-RSA with an RSA-2048 key is 112, ECDSA-SHA256 with a P-384 key is 128. + - `SHA256withRSA` with no key is now 128. +- **The list (REQ-CLS-4, 5).** E2E-CLS-1 compares every algorithm the API lists, on six endpoints, with the card's + row under its class: role, strength and weakest mark. +- **Parts (REQ-CLS-6).** E2E-CLS-2 opens a suite and checks one line per API part, each with a class, one marked + weakest. +- **Inventory and CBOM tab (REQ-CLS-7).** `?class=hash` lists hashes only, a bad class is refused, and the CBOM tab + shows bits. +- **PDF and CLI (REQ-CLS-8).** Every algorithm the evaluation lists appears in the PDF, under its class word. The + CLI prints the classes and each suite's parts. +- **The bands (REQ-UXF-1).** + - No band shares a word with suite strength (every score 0 to 100 is checked). + - A v14 database's bands are migrated, and the scores are untouched. + - Old names in stored evaluations read and rank as the new ones. + - E2E-UXF-2 finds no old band name on the TLS endpoints page. +- **The Summary (REQ-UXF-2 to 5).** E2E-UXF-1 and E2E-UXF-2 cover: + - the first-fix line; + - the weakest suite; + - the hygiene figure opening the score breakdown; + - the not-rated reasons; + - the Overview tile. +- **The certificate viewer (REQ-XV-1 to 9).** + - `TestX509DecodeShowsEveryExtension` uses a generated chain that carries every decoded extension, plus an unknown + private one, which must come back as OID and hex. + - The SCT is named from the bundled CT log list. + - The endpoint test decodes a real scan's certificate with and without `?scan=`, and checks the no-DER answer. + - E2E-XV-1 to 5 check, in order: + - every chain node links to its page, and Back returns; + - the section order agrees with the API; + - the PEM's SHA-256 is the fingerprint; + - the hostile demo certificate (markup in its subject, names and URLs, and a `javascript:` OCSP URL) stays text, + with no link made from it; + - 375 px with no sideways scroll, and every control reachable by Tab. + +## Defects found by QA and fixed before release + +| # | Found by | Defect | Fix | +|---|---|---|---| +| Q1 | `TestSignatureAlgorithmIsHashAndKey` | With hyphens removed, `SHA384` starts with `SHA3`, so SHA-384 signatures were read as SHA3-84. | SHA-3 is recognised by its length too. | +| Q2 | `TestEvaluationListsAlgorithmsByClass` | A hybrid group's post-quantum status depended on the report's `hybrid` flag. A report without the flag listed X25519MLKEM768 with no status. | The status falls back to the catalogue name. | +| Q3 | `TestPDFListsAlgorithmsByClass` | The PDF's narrow columns broke "Asymmetric" and "quantum-vulnerable" across lines. | Wider columns; versions written "TLS 1.2". | +| Q4 | Screen walk (UX-REVIEW-010 U-7) | **Pre-existing.** The TLS hygiene figure on a report's Summary opened *About this report* (scan time, connections), not the score breakdown. | It opens Weaknesses at *How the TLS hygiene score was worked out*. | +| Q5 | `TestX509DecodeShowsEveryExtension` | Go 1.27 writes and reads certificate policies through `Policies`. The decoder read `PolicyIdentifiers`, which misses OIDs whose arcs exceed an int. | The decoder reads `Policies`. | +| Q6 | CI on PR #19, and a local run | The headless browser sometimes never opened its DevTools port (after an update), and the whole journey failed. | The journey waits 30 s, then starts a fresh browser on a new port, once. | + +## Test changes, and why each is not a weakening + +- **E2E-05.3** now expects the SSLv3 endpoint's band to be *Failing*: the band is renamed, and the threshold is + unchanged. +- **The registry sweep in `TestSuiteHasItsHashPart`** pairs each suite only with the versions that can carry it. + AEAD and SHA-2 MAC suites need TLS 1.2, and SCSVs are signalling values that are never accepted. The pairs it + leaves out can never be negotiated. +- **`TestEvaluationCarriesStrength`** compares the version with `eval.Version` instead of a fixed `eval/9`. + +## Screen walk + +The first-pass screens of PRD-011 and PRD-012 (the Algorithms in use card, the suite part list, the certificate page as tables) were rejected by the owner as too texty before they shipped. PRD-013 rebuilt them as instruments; the walk of the screens that ship, at every width and in dark mode, is in [QA-REPORT-012](QA-REPORT-012.md). The data layer this report covers is unchanged by that rebuild. diff --git a/docs/program/QA-REPORT-012.md b/docs/program/QA-REPORT-012.md new file mode 100644 index 0000000..2ce51e5 --- /dev/null +++ b/docs/program/QA-REPORT-012.md @@ -0,0 +1,114 @@ +# QA-REPORT-012 · Instruments, not paragraphs (PRD-013) + +Date: 2026-10-21. Build: branch `feat/classes-and-x509` (PRD-011, PRD-012 and PRD-013 together) on top of `ee275af`. +Requirements: [PRD-013](../product/PRD-013-visual-rebuild.md), [RTM](RTM.md) §PRD-013. Design: +[UX-DESIGN-011](../product/UX-DESIGN-011-instruments-not-paragraphs.md). Cryptography: +[CRYPTO-REVIEW-001](../product/CRYPTO-REVIEW-001-visual-rebuild.md). The data layer is in +[QA-REPORT-011](QA-REPORT-011.md). + +## Result + +| Gate | Command | Result | +|---|---|---| +| Unit and integration | `go test ./...` | pass | +| Traceability | `TestRequirementsTraceability` | 13 new rows (REQ-VIS-1 to 13), all done, each with evidence | +| Instruments | `TestInstrumentsAreSharedAndLabelled` | every instrument exported from `viz.js` with a text alternative; the five rebuilt views draw with it and keep no screen-local drawing | +| Copy rules | `TestUICopyRules` | pass (three en-dash glyphs in `viz.js` replaced by `·`) | +| Lint | `golangci-lint run ./...`, and with `GOOS=linux` | 0 issues, 0 issues | +| UI scripts parse | `node --check` on every module | all parse | +| Browser journey | `testing/e2e/journey.mjs`, the CI setup (fresh database, fresh demo targets) | **235 passed, 0 failed** (with E2E-VIS-FOLD) | +| Screen walk | below | done, 14 defects found and fixed | +| Cryptography | CRYPTO-REVIEW-001 | signed off, 6 defects fixed, 3 notes kept | + +## The new checks (visual-phase.mjs) + +- **E2E-VIS-1** On Summary, Configuration, the Inventory, an SSH report and a certificate page, every bits bar, + post-quantum glyph, status tile, validity bar, stepper, stack bar and gauge says in words what it shows. +- **E2E-VIS-2** The Summary has its four figures, Fix first, a checks bar whose segments add up to the checks run, + eight area tiles and no prose blocks; an area tile opens its part of Configuration, in view. +- **E2E-VIS-3** On the report with the most suites, each version that is on lists exactly the report's suites in the + server's order, five part cells each and the evaluation's word, under the negotiated banner. +- **E2E-VIS-4** Every feature tile is there and in the scan's state; 0-RTT says not probed; the revocation tiles; the + readiness level and its stepper agree with the evaluation. +- **E2E-VIS-5** The Inventory's strength histogram adds up to the list, the class bars and three columns open lists + of exactly their count, and the list is weakest first. +- **E2E-VIS-6** Every rated SSH algorithm's bar says the evaluation's strength; key exchange and host keys carry the + post-quantum glyph. +- **E2E-VIS-7** Summary, Configuration, the Inventory and the certificate page at 1440, 1100, 860 and 375 px: no + sideways scroll, every control reachable by Tab; instrument text at 4.5:1 or better in light and dark. (Checked by + hand that the contrast check measures: 166 text elements on one Configuration, lowest 4.71:1.) + +- **E2E-VIS-FOLD** (REQ-VIS-13, the owner's request after the PR opened) On the hybrid target, a TLS version is open + only when it needs a look (an old version on, a suite below Strong, or the only version); a folded one says + *Strong · all N*; the button shows or hides every version and its label follows the versions as they are + opened by hand (the first build kept a stale label: fixed). + +## Checks rewritten for the new structure, and why each is not a weakening + +| Check | Was | Now | +|---|---|---| +| E2E-05.2 | grouped action items with "What to do" and "Why it matters" | each Fix first line numbered, with what to do, why in its tooltip and an Evidence link; titles unique | +| E2E-R.1, R.3 | a version matrix with Enabled/Disabled words | protocols first; every version shown (on as a block with its suites, off on one line); an old version on says *should be off*, and at least one such version is checked | +| E2E-F.8 | purpose lines ≤ 15 words | six sections, asides ≤ 25 words (an aside now carries a link), How we tested last | +| T1-E2E.4 | three summary tiles | four (the weakest classical strength added) | +| T1-E2E.7 | purpose line per card | the six parts in the reviewer's order, each headed, and the jump row leading to each with its state dot | +| T1-E2E.8, .12 | trust table rows | trust chips, *your CA bundle ✓* in the good tone; the missing issuer card | +| T1-E2E.13 | HSTS header in `#sec-http` | in the Features part, still rendered as text | +| T1-E2E.14 | check rows fold to two columns | area tiles fold to at most two | +| T1-E2E.15 | "OCSP response was stapled", "not looked up" | the OCSP stapling tile, the OCSP URL, "not contacted" | +| T1-E2E.17 | five detail cards, no finding card | no finding card in Configuration (stricter selector), the certificate part names its issue | +| E2E-PQS.1 | "post-quantum (ML-DSA-65)" in text | an accepted post-quantum scheme chip that names mldsa65 and ML-DSA-65 | +| E2E-STR-1 | groups have any bits | **stronger:** every group chip's bits equal the evaluation's for that group | +| E2E-CLS-1 | the class card first, rows matched by `includes` | lanes matched by class name exactly (the old check let *asymmetric* match *symmetric*); the bar's label equals the API's strength and weakest mark | +| E2E-CLS-2 | a suite's part list | **stronger:** every suite on the report, each part the API gives present with its bits, the ringed cell is the API's weakest | +| E2E-CLS-3 | a Class `