diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb4179..0aebe1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas ## [Unreleased] +## [0.4.0] - 2026-10-22 + +### Security + +- Built with Go 1.27.3 (GO-2026-6809). `crypto/x509` now distrusts compromised intermediate certificates, and the + trust check uses it. + ### Added - **Classical strength for every detected algorithm** ([PRD-010](docs/product/PRD-010-classical-strength.md), @@ -56,12 +63,12 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas 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. + the three things to fix first, each with one chip saying when, the rest folded under a line that counts them, 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 + revocation, protocol features, and every algorithm by class. Every TLS version starts folded to one line that + says whether it should be on and how its suites rate. 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. @@ -78,6 +85,9 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas ### Changed +- **Breaking (API):** the evaluation is now `eval/12`, and stored scans are re-evaluated on start. The `band` field + uses the new hygiene band names (below), and a section's `status` can be `unknown` (revocation, when nothing + vouches for the certificate). Integrations that match on band names or on the set of statuses must be updated. - **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. @@ -111,10 +121,6 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas - an untrusted root is described as a private CA or a public root since removed, not only as a private one; - a composite DH modulus is critical, and a DH prime that is not a safe prime is low; - at equal bits, the weakest classical strength picks a disallowed algorithm (3DES) over an allowed one. -- **Fix first** shows the three things to fix first, each with one chip saying when, and folds the rest under one - line that counts them by severity. -- **Every TLS version on Configuration starts folded**, its head saying whether it should be on and how its suites - rate; version heads, suite rows and Fix first rows line up. - **SSH evaluation (eval/11; stored scans are re-evaluated on start):** - a truncated MAC is as strong as its tag: hmac-sha1-96 is 96 bits, not 128 (NIST SP 800-107 Rev. 1); - the next steps name OpenSSH releases only to a server that identifies as OpenSSH; @@ -123,16 +129,10 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas - **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. -### Security - -- Built with Go 1.27.3 (GO-2026-6809). `crypto/x509` now distrusts compromised intermediate certificates, and the - trust check uses it. - ## [0.3.0] - 2026-10-17 ### Changed @@ -874,7 +874,8 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas - **Storage:** SQLite only. Up to 50 snapshots of up to 32 MiB are kept per machine; there is no overall storage budget yet. -[Unreleased]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.3.0...HEAD +[Unreleased]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.4.0...HEAD +[0.4.0]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.1.1...v0.2.0 [0.1.1]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.1.0...v0.1.1 diff --git a/README.md b/README.md index 5b430f7..b6fd3c3 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,11 @@ rules. Systems are ranked for migration by exposure and by how long their data m **Scanners that show their work.** The TLS scanner is its own wire implementation: every protocol from SSLv2 to TLS 1.3, every cipher suite and key-exchange group (including `X25519MLKEM768`), the signature schemes (ML-DSA -included), the certificate chain against the bundled root stores, OCSP stapling, CT and HSTS. Its 55 checks each say -what they found or that they passed, why it matters, what to do and their sources; the TLS hygiene score (0 to 100) -shows its arithmetic. The SSH auditor reads what a server offers, negotiates as OpenSSH 10.6 would (ML-KEM hybrid +included), the certificate chain against the bundled root stores, OCSP stapling, CT and HSTS. It reads the certificate +a server really serves even when only legacy suites reach it, names a root whose private key is public, and says +*unknown* for what it cannot know (revocation, without contacting the CA) instead of guessing. Its 58 checks each +say what they found or that they passed, why it matters, what to do and their sources; the TLS hygiene score (0 to +100) shows its arithmetic. The SSH auditor reads what a server offers, negotiates as OpenSSH 10.6 would (ML-KEM hybrid included), verifies every host key, and never logs in. It judges the server against a cited baseline and writes the `sshd_config` lines that would make it meet it. Both are checked against real servers: OpenSSL for trust, OpenSSH 9.2 and 10.0 for SSH. @@ -67,7 +69,7 @@ included), verifies every host key, and never logs in. It judges the server agai installing anything; CBOMs from other tools can be imported. TLS endpoints, SSH servers and machines merge into one CycloneDX 1.7 CBOM, with PDF reports for each endpoint and for the whole inventory, and the OMB M-23-02 items per system. -| A TLS endpoint: verdict first, then every check | STARTTLS services, the same depth | +| A TLS endpoint: where it stands, then the three things to fix first | STARTTLS services, the same depth | |---|---| | ![A TLS endpoint report: readiness L3, the TLS hygiene score, what to fix](docs/img/report-hybrid.png) | ![An SMTP endpoint scanned through STARTTLS](docs/img/report-starttls.png) | | **SSH servers: readiness and the baseline, never a score** | **The `sshd_config` lines that meet the baseline** | @@ -77,9 +79,10 @@ 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. | +| **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; DH parameters tested for primality. STARTTLS for SMTP, IMAP, POP3, FTP, XMPP, LDAP and PostgreSQL. 58 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`). 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. | +| **One page to check a setup** | Each report has a Summary (readiness, hygiene, the weakest strength and the certificate as figures, then the three things to fix first) and a Configuration that reads in a reviewer's order: every TLS version folded to one line that says whether it should be on and how its suites rate, opening to its suites part by part; the certificate chain; key exchange and signatures; revocation; protocol features; every algorithm by class. SSH reports read the same way, ending with the baseline and its `sshd_config` lines. | | **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. | diff --git a/deploy/cipherhorizon.container b/deploy/cipherhorizon.container index 8736285..1eef5c9 100644 --- a/deploy/cipherhorizon.container +++ b/deploy/cipherhorizon.container @@ -23,7 +23,7 @@ After=network-online.target [Container] ContainerName=cipherhorizon -Image=ghcr.io/offsetsecurity/cipherhorizon:0.3.0 +Image=ghcr.io/offsetsecurity/cipherhorizon:0.4.0 PublishPort=127.0.0.1:8080:8080 Volume=cipherhorizon-data:/data # Another port: set it here and in PublishPort (the health check follows it), e.g. diff --git a/docs/engineering/DISTRIBUTION.md b/docs/engineering/DISTRIBUTION.md index 79aa806..4f351d0 100644 --- a/docs/engineering/DISTRIBUTION.md +++ b/docs/engineering/DISTRIBUTION.md @@ -92,7 +92,7 @@ tar -xzf cipherhorizon_0.1.0_linux_amd64.tar.gz docker run -d --name cipherhorizon -p 8080:8080 \ -e CIPHERHORIZON_TOKEN="$(openssl rand -hex 24)" \ -v cipherhorizon-data:/data \ - ghcr.io/offsetsecurity/cipherhorizon:0.3.0 + ghcr.io/offsetsecurity/cipherhorizon:0.4.0 # container, built locally (docker-compose.yml at the repository root) echo "CIPHERHORIZON_TOKEN=$(openssl rand -hex 24)" > .env @@ -136,10 +136,10 @@ gh attestation verify cipherhorizon_0.1.0_linux_amd64.tar.gz --repo offsetsecuri **Container image:** ```sh -cosign verify ghcr.io/offsetsecurity/cipherhorizon:0.3.0 \ +cosign verify ghcr.io/offsetsecurity/cipherhorizon:0.4.0 \ --certificate-identity-regexp '^https://github.com/offsetsecurity/cipherhorizon/\.github/workflows/release\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com -gh attestation verify oci://ghcr.io/offsetsecurity/cipherhorizon:0.3.0 --repo offsetsecurity/cipherhorizon +gh attestation verify oci://ghcr.io/offsetsecurity/cipherhorizon:0.4.0 --repo offsetsecurity/cipherhorizon ``` Air-gapped users: the archives are self-contained, so download on a connected machine, verify there, and move diff --git a/docs/engineering/INSTALL.md b/docs/engineering/INSTALL.md index 258cb40..1d114c1 100644 --- a/docs/engineering/INSTALL.md +++ b/docs/engineering/INSTALL.md @@ -120,7 +120,7 @@ header lists the commands). The log goes to `~/Library/Logs/CipherHorizon/cipher ```sh docker run -d --name cipherhorizon --restart unless-stopped \ -p 127.0.0.1:8080:8080 -v cipherhorizon-data:/data \ - ghcr.io/offsetsecurity/cipherhorizon:0.3.0 + ghcr.io/offsetsecurity/cipherhorizon:0.4.0 ``` Or, from a checkout, `docker compose up -d` ([docker-compose.yml](../../docker-compose.yml): read-only diff --git a/docs/img/endpoints.png b/docs/img/endpoints.png index 94dfc0a..ac2e320 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 966836e..49c3043 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 1532b23..70c56a5 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 9654e38..d748376 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 ad681a8..4974748 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 7cd4e50..9bd363e 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 4508a62..65d0aee 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 1a7e56c..1715478 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 62679a2..88695aa 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 e11e330..815053e 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 e11f716..a90bff4 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 eb7d567..35a4441 100644 --- a/docs/methodology.md +++ b/docs/methodology.md @@ -206,17 +206,19 @@ Every algorithm belongs to one class, from its CycloneDX primitive, and has a ro 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. +Configuration ends with this list (*Every algorithm, by class*: a lane per class, a bits bar per algorithm, the weakest +ringed); 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, +Every certificate in a report's certificate chain 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 +an older scan gains it without a rescan. In order, it shows: the chain it was served in, its validity (days +left on a lifetime bar), the public key and the issuer's signature as bits bars (the signature is the weaker of its hash +and the issuer's key) with their post-quantum status, the names it is valid for and what it may be used for (an +elliptic-curve key that claims key encipherment is marked, RFC 8813), every extension with its criticality and decoded +value (one this program does not decode is shown as OID and hex), the identifiers to copy (SHA-256, the SPKI +fingerprint, the serial, and SHA-1 as an identifier only) and the PEM, folded, 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. diff --git a/docs/ssh-scanning.md b/docs/ssh-scanning.md index ad06c86..2424299 100644 --- a/docs/ssh-scanning.md +++ b/docs/ssh-scanning.md @@ -86,7 +86,12 @@ not on the list, so they should be off. A server whose lists couldn't be read is 1. **A post-quantum hybrid key exchange is offered** (RFC 10042, RFC 9941). 2. **Strict key exchange is offered.** This is OpenSSH's countermeasure to Terrapin (CVE-2023-48795), in OpenSSH 9.6 and later. -3. **RSA host keys are at least 3072 bits** (NIST SP 800-57 Part 1: 128-bit security). +3. **RSA host keys are at least 3072 bits** (NIST SP 800-57 Part 1: 128-bit security). On a server with no RSA host + key this rule *does not apply*, rather than reading as met. + +A truncated MAC is rated by its tag: `hmac-sha1-96` is 96 bits, not HMAC-SHA-1's 128 (NIST SP 800-107 Rev. 1). The +next steps and the changes name OpenSSH releases only to a server that identifies as OpenSSH; any other server is told +what to offer. **What it leaves out, and why:** diff --git a/docs/tls-scanning.md b/docs/tls-scanning.md index d38dfbb..c675c55 100644 --- a/docs/tls-scanning.md +++ b/docs/tls-scanning.md @@ -21,11 +21,11 @@ CSVs), not from what a library happens to implement. | **Cipher suites** | Offer every candidate, note the server's pick, remove it, ask again until the server refuses. Yields the full set for each version. | | **Preference** | Adjacent pairs offered in reverse order. Same pick regardless of order → `server` (and ranks are reported); follows the client → `client`; both → `mixed`. No ranking is ever invented. | | **TLS 1.3 groups, incl. post-quantum** | A hello with an **empty `key_share`**: a supporting server must answer with a HelloRetryRequest that *names* its chosen group (and cipher suite). No key material is generated. Repeating with the pick removed enumerates every group, including `X25519MLKEM768`, the other ML-KEM hybrids, pure ML-KEM and the obsolete Kyber drafts. | -| **TLS ≤1.2 key exchange** | ECDHE curves the same way, from the ServerKeyExchange; DHE prime size and a fingerprint of the prime. | +| **TLS ≤1.2 key exchange** | ECDHE curves the same way, from the ServerKeyExchange; the DHE prime's size, a fingerprint of it, and whether it is prime and a safe prime (Miller-Rabin with 20 bases plus Baillie-PSW). | | **Signature schemes (TLS 1.3)** | One hello per scheme (ECDSA P-256/384/521, RSA-PSS, Ed25519, Ed448, ML-DSA-44/65/87), each offering only that scheme and carrying real key shares, so the server must pick a certificate before it answers: a ServerHello means it holds a key for that scheme. A control hello offering only a private-use code point must be refused; a server that answers it does not choose by scheme, and the stage is inconclusive. Every TLS 1.3 hello also offers ML-DSA, so a server whose only certificate is ML-DSA is seen. 15 connections. | | **Server features** | Read from hello extensions: secure renegotiation, extended master secret, tickets, encrypt-then-MAC, OCSP stapling; TLS compression; `TLS_FALLBACK_SCSV` enforcement. | -| **Certificates & "what a real client gets"** | A full handshake with Go's `crypto/tls` (TLS 1.3 encrypts the Certificate message, so raw probes cannot see it), plus ECDSA-only and RSA-only TLS 1.2 handshakes to surface dual certificates, plus one handshake **without SNI** for a named target. Each chain is kept exactly as served (the DER of every certificate, in order), with the stapled OCSP response and the SCTs from the TLS extension. Trust is decided in evaluation, not here (see below). | -| **Revocation & transparency** | No extra connection: the stapled OCSP response and the SCTs ride on the certificate handshakes. No OCSP responder, CRL or CT log is contacted. | +| **Certificates & "what a real client gets"** | A full handshake with Go's `crypto/tls` (TLS 1.3 encrypts the Certificate message, so raw probes cannot see it), plus ECDSA-only and RSA-only TLS 1.2 handshakes to surface dual certificates, plus one handshake **without SNI** for a named target. A server whose only suites `crypto/tls` will not offer (3DES, RC4, NULL, small DH), or that keeps an RSA certificate for clients that offer only RSA, has its chain read instead from the plaintext Certificate message of the raw TLS 1.2-and-earlier hellos the suite enumeration already sends (source `raw-tlsv1.2`). Each chain is kept exactly as served (the DER of every certificate, in order), with the stapled OCSP response and the SCTs from the TLS extension. The no-SNI chain is described, never taken for the endpoint's certificate. Trust is decided in evaluation, not here (see below). | +| **Revocation & transparency** | No extra connection: the stapled OCSP response and the SCTs ride on the certificate handshakes. No OCSP responder, CRL or CT log is contacted, so without a verified stapled response saying *good* the report says the revocation status is **unknown**, never *good*. | | **HTTP** | One `GET /` over a fresh TLS connection; the `Strict-Transport-Security` header is parsed (RFC 6797 §6.1). On by default; `--http-checks=false` turns it off. | Each stage is recorded in the report (`stages[]`): `done`, `skipped` (with a reason code: `option-off`, @@ -44,13 +44,13 @@ when a CA bundle or the reference data changes, without a rescan. The stored rep | What | How | |---|---| -| **Chain as served** | Analysed in served order: certificates out of order (TLS 1.2 requires the order, TLS 1.3 tolerates it), a root, duplicate or unrelated certificate sent, size in bytes. A **missing intermediate** is reported when only the leaf was served and its issuer is in no store; the AIA URL the certificate names is shown, never fetched. When the served path already ends at a CA certificate, the unknown issuer is reported as an unknown (often private) root instead. | +| **Chain as served** | Analysed in served order: certificates out of order (TLS 1.2 requires the order, TLS 1.3 tolerates it), a root, duplicate or unrelated certificate sent, size in bytes. A **missing intermediate** is reported when the served path stops at a certificate that names where its issuer can be downloaded (AIA caIssuers, as public CAs' leaves do) and that issuer is in no store; the URL is shown, never fetched. When the path already ends at a CA certificate, or the leaf names no issuer URL (a leaf signed straight by a root), the issuer is reported as a root the stores do not hold: a private CA, or a public root since removed. A chain under a root whose private key is public (Superfish, eDellRoot, DSDTestProvider) is reported as such, critical. | | **Trust per store** | Path validation against bundled, dated snapshots of the **Mozilla** and **Chrome** root stores (CCADB), each reported separately, with the stores' own constraints: Mozilla's *distrust for TLS after*, Chrome's `sct_not_after`, program name constraints. Only the served certificates are intermediates: no AIA fetching, no system pool. A **private CA bundle** (server-wide `--ca-bundle FILE`, and per system on the system page) adds a "your CA bundle" row. The machine's own store is an optional extra (`--os-trust` on the CLI), off by default because some OS verifiers fetch missing intermediates from the network. | | **Names** | The host against the DNS and IP SANs (never the CN, RFC 9525), stating which SAN matched. | | **Lifetime** | Against the limit in force on the issue date (SC-081v3: 398 days, 200 from 2026-03-15, 100 from 2027-03-15, 47 from 2029-03-15), for publicly trusted chains only. The expiry warning scales with the lifetime. | | **OCSP staple** | Decoded by our own bounded parser: response and certificate status, revocation time and reason, this/next update, responder; the signature is checked against the issuer or a delegated responder; the response must be for this certificate (serial and issuer hashes). | | **CT** | SCTs from all three places (embedded, TLS extension, OCSP), each with its log named from the bundled CT log list. Signatures are not verified in this version. | -| **DH groups** | The DHE prime fingerprint is matched against the RFC 2409, 3526, 5114 and 7919 groups. | +| **DH groups** | The DHE prime fingerprint is matched against the RFC 2409, 3526, 5114 and 7919 groups. A modulus that is not prime is critical; a prime that is not a safe prime and not a published group is low (small subgroups exist). | | **Attack names** | Published attacks are labels on the finding that enables them (FREAK, Logjam, DROWN, POODLE, BEAST, SWEET32, CRIME, LUCKY13, RC4), never second findings. A key served on an SSLv2 endpoint anywhere in the estate is reported on every endpoint that serves it (DROWN). Active-only attacks (Heartbleed, CCS injection, ROBOT, Ticketbleed, padding oracles) are listed as *not tested by design*. | The reference lists, their sources, dates and licences are in [reference-data.md](reference-data.md). @@ -115,8 +115,11 @@ rather than an invented one. OID when parseable, but a server presenting only such a chain will not yield a certificate result yet. - DH: the prime is matched against the RFC-published groups only; widely shipped non-RFC defaults are not recognised (no licence-clear list of them exists). -- Revocation and CT are judged from what the server sends: no live OCSP/CRL lookup, no SCT signature check, no - count against a browser CT policy. These are planned as options, off by default. +- Revocation and CT are judged from what the server sends: no live OCSP/CRL lookup (so revocation reads *unknown* + unless a stapled response vouches for the certificate), no SCT signature check, no count against a browser CT + policy. These are planned as options, off by default. +- A scan made before 0.4.0 of a server Go's TLS client cannot reach recorded only the no-SNI certificate; scan it + again to read the served one. - Behavioural vulnerability probes (Heartbleed, ROBOT, CCS injection) are not implemented; when they are, they will be off by default. diff --git a/web/static/js/views/help.js b/web/static/js/views/help.js index 89fb9ef..0afc84a 100644 --- a/web/static/js/views/help.js +++ b/web/static/js/views/help.js @@ -81,6 +81,7 @@ export async function render(root) { h('p', { class: 'callout' }, h('b', null, 'Scan only systems you own or are authorised to test. '), 'A scan makes a few hundred short TLS handshakes to its target and contacts nothing else.')), sec('ssh', 'Scan an SSH server', h('p', null, 'Type ', code('ssh://bastion.example.com'), ' or an address on port 22, or use ', h('b', null, 'SSH servers → Add SSH servers'), '. The report shows readiness, whether the server meets the SSH baseline (and the ', code('sshd_config'), ' lines that would make it), every check, the CBOM and history.'), + h('p', null, h('b', null, 'Reading an SSH report. '), 'The Summary shows readiness, the baseline, the weakest classical strength and the host key, then ', h('i', null, 'Fix first'), ', whose first line is meeting the baseline. Configuration reads in a reviewer’s order: what a current client gets, the version and software, key exchange, host keys (a card per key), ciphers, MACs and compression, and the baseline. Every algorithm is a row with its bits bar, its post-quantum mark for key exchange and host keys, and whether the baseline allows it; a row that should be off says why beside the mark.'), h('p', null, h('b', null, 'What is sent. '), 'One connection reads the server’s identification and its algorithm lists. One more negotiates as OpenSSH 10.6 would and, when it can, runs the key exchange to the server’s reply and checks its signature. Then one key exchange per host key type fetches each key. Every connection ends before any key is used: no login, no channel, never a password.'), h('p', null, h('b', null, 'Readiness. '), 'L1 when no post-quantum hybrid key exchange is offered; L3 when OpenSSH 10.6’s client gets one (', code('mlkem768x25519-sha256'), ', or ', code('sntrup761x25519-sha512'), ', which is noted as not a NIST standard); L4 with a post-quantum host key; L0 for an SSH-1-only server.'), @@ -126,7 +127,7 @@ export async function render(root) { h('dd', null, b.allowed[k].map((e, i) => [i ? ', ' : '', code(e.name)]))])))) : []), h('p', { class: 'text-2' }, 'Every entry’s source and the OpenSSH version that first offers it: ', code('cipherhorizon ssh baselines'), '. The baseline is this product’s own; BSI TR-02102-4 is planned as a second one.')), sec('limits', 'What is and is not checked', - h('p', null, 'Every protocol from SSLv2 to TLS 1.3, every cipher suite and key-exchange group, the certificate chain against the bundled root stores, OCSP stapling, Certificate Transparency, and session features: 55 checks, each listed on the endpoint’s page as found, passed or not applicable.'), + h('p', null, 'Every protocol from SSLv2 to TLS 1.3, every cipher suite and key-exchange group, the certificate chain against the bundled root stores, OCSP stapling, Certificate Transparency, and session features: 58 checks, each listed on the endpoint’s page as found, passed or not applicable. Revocation is shown as status unknown unless the server staples a response that vouches for the certificate: this product never contacts the CA.'), h('p', null, h('b', null, 'One HTTP request. '), 'Every check exchanges only TLS handshake messages except one: after the handshake, a plain ', code('GET /'), ' is sent to read the ', code('Strict-Transport-Security'), ' header (at most 8 KiB, no body kept, no redirect followed). This server has it ', h('b', null, a.httpChecks === false ? 'off' : 'on'), '; ', code('--http-checks=false'), ' turns it off.'),