100 lines
8.5 KiB
Markdown
100 lines
8.5 KiB
Markdown
# AERE Quantum Security Control Plane
|
|
|
|
From "we found the problem" to "this is exactly what changes, in which order, what we can migrate automatically, how the rest is
|
|
fixed on your server, and how that is proven". Node.js 24 with OpenSSL 3.5, no dependencies. It composes tools that exist beside it:
|
|
the cryptographic inventory (`crypto-inventory/`), the TLS readiness scanner (`readiness/`), the PQ Gateway, KMS and PKI
|
|
(`pq-gateway/`, `pq-kms/`, `pq-pki/`), the verification layer (`verify-layer/`) and the AIP-23 envelope builder (`proof-kinds/`).
|
|
|
|
Code comments, internal names, test names and file names are in Romanian; command lines, JSON fields, messages and this
|
|
documentation are in English (the data format was translated on 2026-09-29, version 2 of each format).
|
|
|
|
## 1. Plan: `control-plane.mjs`, `plan-migrare.mjs`
|
|
|
|
node control-plane.mjs --code <dir> [--scan scan.json] [--json]
|
|
|
|
Runs the inventory on a code directory and, optionally, takes a scan of a hostname (the JSON of the readiness scanner), and writes a
|
|
prioritized plan. Every quantum-vulnerable asset becomes an action:
|
|
|
|
{ ref, asset, problem, current, target, method: "auto-aere" | "manual" | "blocked", product: "gateway" | "kms" | "pki" | null,
|
|
how, cn?, primitive?, urgency: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW", blocker, location, source }
|
|
|
|
Harvest-now-decrypt-later applies to key exchange and encrypted data, **not to signatures** (a signature is public; it is not
|
|
harvested), so a classical key exchange on a public endpoint is the only CRITICAL case, and a classical signature is a future
|
|
forgery risk. The plan is honest about blockers: a public WebPKI certificate cannot be reissued post-quantum today (no public CA
|
|
issues one), so it is `blocked` with the reason written, not `auto`. The scan findings are classified by a closed table on the ids
|
|
the scanner actually emits (the test derives them from the scanner's source and fails on an id without a classification).
|
|
|
|
## 2. Execution: `executa-migrare.mjs`
|
|
|
|
node executa-migrare.mjs --plan plan.json --out <dir> [--consent ref1,ref2|all] [--execute]
|
|
[--gw-cert c.pem --gw-key k.pem --gw-upstream http://h:p [--gw-mode hybrid-only|hybrid-preferred] [--gw-listen 127.0.0.1:8443] [--gw-keep]]
|
|
[--kms-url http://127.0.0.1:8420 --kms-token-file f] [--pki-dir ca --pki-ca issuing --pki-roots ca/root.crt] (AERE_PKI_PASSPHRASE)
|
|
|
|
Nothing runs without consent **per action**, and the default is a dry run (`--execute` starts the work). Each `auto-aere` action is
|
|
done through the matching product and judged by a **measurement afterwards**, not by an exit code:
|
|
|
|
| product | what it does | the proof afterwards | rollback |
|
|
|---|---|---|---|
|
|
| `gateway` | starts the PQ Gateway in front of the service and writes its configuration for your service manager | a client offering only `X25519MLKEM768` completes a TLS 1.3 handshake; a second, classical-only client says whether classical clients are still accepted (`hybrid-preferred`) or refused (`hybrid-only`) | the gateway is stopped |
|
|
| `kms` | creates the hybrid key (X25519 + ML-KEM-768) or rotates it if it exists; the name comes only from the `ref` (`mig-...`) | `latest_version` and the fingerprint read back from the KMS | older versions stay decryptable; nothing is deleted |
|
|
| `pki` | issues an ML-DSA-65 certificate from your PQ CA for a `cn` checked to be a host name (no wildcard) | `verifyChain` up to the root | files written in part are removed |
|
|
|
|
Every action is a record in a hash chain `sha256(seq | prev | record)` in `execution.json` (`records[]`, each `{ seq, prev, record,
|
|
hash }`; verdicts `OK`, `FAILED`, `DRY-RUN`, `SKIPPED-no-consent`). The console and anyone else can check it again; a changed or
|
|
removed record is caught. It does not change your DNS, ports or firewall (moving traffic to the gateway stays with you), does not
|
|
issue public WebPKI certificates, and does not delete keys.
|
|
|
|
## 3. Remediation: `remediere.mjs`
|
|
|
|
node remediere.mjs recipe --plan plan.json --profile nginx|apache|haproxy|node|go [--json]
|
|
node remediere.mjs verify --plan plan.json --scan after.json [--applied-at 2026-09-28T10:00:00Z] [--out dir]
|
|
|
|
The actions no AERE product can do (the `manual` ones: TLS 1.3 missing, TLS 1.2 accepted, the hybrid group not preferred, HSTS, the
|
|
certificate) live in your server's configuration, which we do not touch. The **recipe** gives, for your server software, the exact
|
|
fragment, where it goes, the measurable precondition (the OpenSSL or Go version and the command that shows it) and the configuration
|
|
check to run before reloading; fragments are generated from one structured form (groups, minimum version, HSTS), and certificate
|
|
problems get operational steps. The **proof**: after you apply it, a new scan judges each action `RESOLVED` (the defect is gone AND the
|
|
required property is measured), `UNRESOLVED`, or `UNMEASURED` (the scan failed, is of another host, or is not from after the time you
|
|
applied the change: `--applied-at`, otherwise the plan's `generatedAt`; with neither, everything is `UNMEASURED`). A domain in the
|
|
plan that is not a host name gets no recipe, so no command to copy is built with it. What is measured by us is written on every
|
|
recipe (`measured`): the `node` profile end to end; for nginx, Apache and HAProxy only that OpenSSL 3.5 accepts the group list (the
|
|
directive comes from their documentation); Go from the 1.24 release notes, not measured.
|
|
|
|
## 4. Compliance report: `raport-conformitate.mjs`
|
|
|
|
import { complianceReport, verifyReport } from './raport-conformitate.mjs';
|
|
const { report, envelope, evidenceHash } = complianceReport({ plan, organization, date: '2026-09-28', risk: 'high', cnsa: true, cnsaCategories });
|
|
|
|
For every action: which regimes apply (NIST IR 8547 initial public draft; the EU coordinated roadmap of 2025-06-23; optionally
|
|
CNSA 2.0), each deadline and the days left on the report's date, and whether the asset is exposed today (key exchange: yes;
|
|
signature: no; a key of unknown use: not known). Each regime carries its source, the source's date and the date it was read; CNSA 2.0
|
|
is read from secondary sources, and says so. An action the report cannot classify is counted, and then the report cannot say that
|
|
there is no vulnerable algorithm. The report is bound to an AIP-23 `compliance` envelope whose `evidenceHash` is the digest of the
|
|
canonical report. It is not a certification and not a legal opinion, and it covers only what was inventoried and scanned.
|
|
|
|
## 5. Console: `consola.mjs`, `consola-web/`
|
|
|
|
node consola.mjs --bundle bundle.json [--plan plan.json] [--execution execution.json] [--json]
|
|
|
|
One state of a deployment: the audit chain of the verification layer recomputed from its entries, the integrity of every AIP-23
|
|
envelope recomputed, the plan folded in, and the execution chain checked again. The console does not trust what a bundle says about
|
|
itself (`chainOk: true` over a modified log is reported as a suspect self-report). `consola-web/index.html` renders that output; it
|
|
computes nothing in the browser, and a bundle pasted there directly is shown as `UNCHECKED`.
|
|
|
|
## Tests
|
|
|
|
bash probele-b1.sh # all seven below, each VERDE / ROSU / STRICAT (in the development repository only)
|
|
node proba-plan-migrare.mjs # 30: real certificates and keys (openssl) through the real inventory; the scanner's ids derived from its source
|
|
node proba-control-plane.mjs # 9: the command line end to end, an all-post-quantum tree gives an empty plan
|
|
node proba-executa-migrare.mjs # 30: on real products started locally (KMS, PQ CA, gateway), with the attacks of the review
|
|
node proba-remediere.mjs # 33: real TLS servers on 127.0.0.1 scanned by the readiness scanner, before and after the recipe
|
|
node control-negativ-remediere.mjs # 7 of 7 guards broken in a copy, each turns the test red
|
|
node proba-raport-conformitate.mjs # 27: on the real outputs of the inventory and the scanner; the envelope through the AIP-23 verifier
|
|
node control-negativ-raport-conformitate.mjs # 3 plantings, each turns the named check red
|
|
node proba-consola.mjs # 8: a good bundle and four negative controls
|
|
node proba-consola-web.mjs # 26: the viewer in a real Chromium, phone (390 px) and desktop (1440 px); needs playwright-core and a Chromium
|
|
node control-negativ-consola-web.mjs # 8 defects planted in a copy of the page, each turns its named check red
|
|
|
|
The report test runs the envelope through the AIP-23 reference verifier (`AERE_VERIFY_PROOF=<verify-proof.mjs from aere-node>`).
|
|
No third party has reviewed any of this.
|