Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 4 additions & 17 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
# Contributing

This is an individual proposal, not an IETF standard. Preserve version 1 while
reviewing version 2. The authoritative candidate is
`draft/draft-acosta-crypto-agility-manifest-02.xml`; its embedded normative schema
must equal `schema/crypto-agility-v2.schema.json`. Update both in the same PR.
Generate the Markdown rendering with `python -m tools.render_draft`.
This is an individual proposal, not an IETF standard. Preserve supported input compatibility and reject unsupported contract versions explicitly. The authoritative specification is `draft/draft-acosta-crypto-agility-manifest-02.xml`; its embedded normative schema must equal `schema/crypto-agility-v2.schema.json`. Update both in the same PR. Generate the Markdown rendering with `python -m tools.render_draft`.

Keep the core to discovery and evidence references. Reuse existing cryptographic
protocols. Distinguish declarations, authenticated assertions and independently
verified runtime behavior. Cite primary sources and separate normative dependencies
from background. Do not use em dashes or claim validation without evidence.
Keep the core to discovery and evidence references. Reuse existing cryptographic protocols. Distinguish declarations, authenticated assertions and independently verified runtime behavior. Cite primary sources and separate normative dependencies from background. Do not use em dashes or claim validation without evidence.

## Validation

Expand All @@ -21,14 +14,8 @@ python -m tools.render_draft --check
xml2rfc --text draft/draft-acosta-crypto-agility-manifest-02.xml --out /tmp/crypto-agility-02.txt
```

Run the independent consumer and live TLS checks required by CI. Required tests
must fail when their dependency or capability is absent, not silently skip.
Submission also requires review of current IETF author guidance, author contact,
revision availability, idnits output and any active submission deadline. Do not
submit or declare readiness while acceptance requirements remain unproven.
Run the independent consumer and live TLS checks required by CI. Required tests must fail when their dependency or capability is absent, not silently skip. Submission also requires review of current IETF author guidance, author contact, revision availability, idnits output and any active submission deadline. Submission status must reflect an observed Datatracker result. Authorize the submission explicitly, complete the current checks, and preserve the submission receipt; never infer publication from local rendering or upload preparation.

## Licensing

Code, schemas and examples use Apache-2.0. Draft contributions are subject to the
IETF Trust provisions indicated by `ipr="trust200902"` and BCP 78/79. Do not publish
secrets or private inventories as evidence.
Code, schemas and examples use Apache-2.0. Draft contributions are subject to the IETF Trust provisions indicated by `ipr="trust200902"` and BCP 78/79. Do not publish secrets or private inventories as evidence.
81 changes: 31 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,40 @@
# Crypto-Agility Manifest

An individual Internet-Draft for discovering declared cryptographic capabilities at
`/.well-known/crypto-agility.json`. This is a proposal, not an IETF standard.
A manifest is a claim. Neither a digest, a signature nor a compatibility result
proves that a system is secure or quantum resistant.

## Version 2 candidate

The [-02 XML](draft/draft-acosta-crypto-agility-manifest-02.xml) is authoritative for
the v2 candidate. The [readable draft](draft/draft-acosta-crypto-agility-manifest-02.md),
[JSON Schema](schema/crypto-agility-v2.schema.json) and
[example](examples/crypto-agility-v2.json) accompany it. Version 1 remains unchanged
for existing qScan producers. Unknown versions require an explicit consumer upgrade.

The core describes subjects, capabilities, device profiles, evidence and migration
status. It separates declarations, observations and independent verification.
Optional evidence collection and monitoring belong to implementations, not a new
cryptographic protocol. The draft does not authorize configuration changes.

## Reference tools

- `tools.verifier`: bounded schema and semantic assessment, with no implicit trust
- `tools.tls_probe`: real TLS 1.3 negotiation through OpenSSL, certificate and hostname
validation, negotiated group and peer signature reporting
- `tools.observatory_ingestion`: signed collector evidence, endpoint binding,
transcript digest, freshness and provenance validation
- `tools.attestation`: compact JWS/JWT verification with pinned issuer policy,
explicit audience and purpose, expiry and persistent replay rejection
- `tools.discovery`: bounded HTTPS discovery and conditional caching
- `tools.qscan`: deterministic offline adaptation of existing qScan v1 output
- `tools.monitor`: repeated assessment, regression alerts and protected local state

Ed25519 in the reference JWS profile is classical. A TLS hybrid key exchange does
not establish post-quantum authentication. Device compatibility is a conservative
comparison of declarations, not a device certification or fleet measurement.

## Validation
A discoverable JSON contract for declared cryptographic capabilities, migration plans and evidence references at `/.well-known/crypto-agility.json`. This is an individual Internet-Draft proposal, not an IETF standard. A declaration, digest, signature or compatibility result does not prove comprehensive security or quantum resistance.

## Current contract

The [authoritative RFCXML](draft/draft-acosta-crypto-agility-manifest-02.xml), [readable specification](draft/draft-acosta-crypto-agility-manifest-02.md), [JSON Schema](schema/crypto-agility-v2.schema.json), [minimal example](examples/minimal-v2.json) and [complete example](examples/crypto-agility-v2.json) define the contract with `version: 2`. The Internet-Draft revision suffix and the JSON contract version are separate identifiers. Consumers reject unsupported versions; the [qScan adapter](docs/qscan-integration.md) explicitly handles supported input with `version: 1` without treating source declarations as runtime evidence.

A manifest identifies one independently authorized subject. It contains generation and expiry timestamps, declared endpoint profiles, observations and migration records, with optional evidence, policies and exceptions. Evidence is bound to its exact bytes and subject. Declarations, authenticated collector statements and independent measurements are distinct assurance levels. Key establishment and authentication are assessed separately. The contract grants no authority to change a system.

## Reference components

| Component | Responsibility |
| --- | --- |
| `tools.verifier` | Bounded schema and semantic assessment; no implicit trust or passing security certification |
| `tools.tls_probe` | Actual TLS 1.3 negotiation with certificate and reference-identity validation |
| `tools.observatory_ingestion` | Authenticated collector provenance, endpoint, transcript and freshness checks |
| `tools.attestation` | Compact JWS verification, explicit issuer/key/subject/audience/purpose policy and durable replay rejection |
| `tools.discovery` | Allowlisted HTTPS retrieval, bounded conditional caching and exact response bytes |
| `tools.qscan` | Deterministic offline adaptation of supported qScan source evidence |
| `tools.monitor` | Repeated assessment, historical regression baseline and durable alert occurrences |
| `tools.consumer` | Immutable bundle selection, local trust/status enforcement and persistent supervision |
| `tools.kms_status` | Bounded authenticated internal key-status refresh using an existing identity |
| `tools.alert_delivery` | Persistent delivery intent, provider receipt verification and exact acknowledgment |

The reference signature profile supports Ed25519 and ML-DSA-65 under independently provisioned algorithm/key policies. Ed25519 is classical. A hybrid TLS exchange does not establish post-quantum certificate authentication. Declared compatibility is a conservative profile comparison, not representative device execution or fleet certification.

## Use and validation

Install `requirements.txt` and `npm ci --ignore-scripts`, then follow [implementation status](docs/implementation-status.md) for the complete required test environment. A deterministic example assessment is:

```sh
python -m pip install -r requirements.txt
npm ci --ignore-scripts
python -m unittest discover -s tests -v
python -m tools.verifier examples/crypto-agility-v2.json --now 2026-10-12T00:00:00Z
python -m tools.render_draft --check
xml2rfc --no-network --text draft/draft-acosta-crypto-agility-manifest-02.xml
```

The example intentionally reports fallback risks and the verifier exits 1.
Hosted CI requires actual hybrid TLS and pinned qScan interoperability; missing
runtimes are failures there. It retains logs, the exact tested commit and rendered
draft artifacts. See [implementation status](docs/implementation-status.md) for
acceptance evidence and [IETF readiness](docs/ietf-readiness.md) for submission gates.

No production deployment or Internet-Draft submission is implied by passing the
reference tests. In particular, legacy unsigned measurements cannot be relabeled
as authenticated evidence.
The example intentionally reports fallback risks and exits 1. Read [runtime evidence](docs/runtime-evidence.md), [signed observations](docs/signed-observations.md), [collector integration](docs/observatory-integration.md), [consumer deployment](docs/consumer-deployment.md) and the [security review](docs/security-review.md) before relying on evidence. Production acceptance and its limits are in [implementation status](docs/implementation-status.md); submission status and requirements are in [IETF readiness](docs/ietf-readiness.md).

Code is Apache-2.0. Internet-Drafts carry IETF Trust provisions (`trust200902`).
Author contact: `[email protected]`. See [CONTRIBUTING.md](CONTRIBUTING.md).
Code, schemas and examples use Apache-2.0. Internet-Drafts carry IETF Trust provisions (`trust200902`). Author contact: `[email protected]`. See [CONTRIBUTING.md](CONTRIBUTING.md).
30 changes: 5 additions & 25 deletions docs/change-feed-design.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,11 @@
# Discovery and optional refresh hints

The only interoperable discovery resource in this candidate is the current manifest
at `/.well-known/crypto-agility.json`. No custom feed format, hash chain, sequence
protocol, or push service is required. Those earlier proposals added state and trust
problems without demonstrating a benefit over conditional HTTP retrieval.
The discovery resource is the current manifest at `/.well-known/crypto-agility.json`. No custom feed format, hash chain, sequence protocol, or push service is required. Conditional HTTP retrieval provides bounded refresh without a second trust or sequencing protocol.

`tools.discovery.DiscoveryClient` uses an explicit origin allowlist, verified HTTPS,
public-address checking followed by a pinned-IP connection with hostname/SNI
validation, no proxies and no redirects. A worker deadline bounds DNS, TLS, response
headers and body reads together. Responses are limited to 1 MiB, uncompressed JSON,
16 levels of nesting and schema limits. Duplicate members and non-JSON numbers fail.
`tools.discovery.DiscoveryClient` uses an explicit origin allowlist, verified HTTPS, public-address checking followed by a pinned-IP connection with hostname/SNI validation, no proxies and no redirects. A worker deadline bounds DNS, TLS, response headers and body reads together. Responses are limited to 1 MiB, uncompressed JSON, 16 levels of nesting and schema limits. Duplicate members and non-JSON numbers fail.

The memory-only cache supports ETag/If-None-Match and Last-Modified/If-Modified-Since,
Cache-Control max-age, no-cache, no-store and Age/Date. Local max-age defaults to
300 seconds and cannot exceed 3600 seconds. There is at most one cached manifest
per configured origin, at most 256 origins. An expired or mismatched manifest is
rejected, including after 304. HTTP freshness never refreshes evidence timestamps:
the monitoring caller must reassess evidence and signatures on every evaluation.
The cache returns original bytes for exact snapshot digest binding.
The memory-only cache supports ETag/If-None-Match and Last-Modified/If-Modified-Since, Cache-Control max-age, no-cache, no-store and Age/Date. Local max-age defaults to 300 seconds and cannot exceed 3600 seconds. There is at most one cached manifest per configured origin, at most 256 origins. An expired or mismatched manifest is rejected, including after 304. HTTP freshness never refreshes evidence timestamps: the monitoring caller must reassess evidence and signatures on every evaluation. The cache returns original bytes for exact snapshot digest binding.

An operator can arrange a local authenticated notification to call
`fetch(configured_origin, expected_subject, force=True)`. The notification is only a
refresh hint. Its contents never supply a URL, security finding, or instruction.
Ordinary periodic polling recovers from lost, duplicated, reordered, or forged
notifications. A separate feed protocol should only be considered after an actual
interoperability use case demonstrates that HTTP validators are insufficient.
An operator can arrange a local authenticated notification to call `fetch(configured_origin, expected_subject, force=True)`. The notification is only a refresh hint. Its contents never supply a URL, security finding, or instruction. Ordinary periodic polling recovers from lost, duplicated, reordered, or forged notifications. A separate feed protocol should only be considered after an actual interoperability use case demonstrates that HTTP validators are insufficient.

Use `python -m unittest discover -s tests -p 'test_discovery.py' -v` for cache,
expiry, SSRF, redirect, malformed response, exact hostname validation and total
worker deadline tests. These tests do not claim production network availability.
Use `python -m unittest discover -s tests -p 'test_discovery.py' -v` for cache, expiry, SSRF, redirect, malformed response, exact hostname validation and total worker deadline tests. These tests do not claim production network availability.
4 changes: 2 additions & 2 deletions docs/consumer-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ An operator can replace the manually asserted `keyStatus` object with the follow

The independently provisioned withdrawal file contains `{"version":1,"withdrawn":false,"reason":"<operator decision>"}`. Changing `withdrawn` to `true` immediately fails the next consumer check; a missing/malformed file fails closed. The refresher never writes this override. Retain the local override independently of downloaded receipts, bundles and deployment releases.

Run `python -m tools.kms_status --policy /private/kms-policy.json --output /private/kms-status.json` in the existing authorized signing environment. The policy contains exactly `version: 1`, `keyArn`, `spkiSha256` and `ttlSeconds`. Use an explicit key ARN, never an alias. A 15-minute workflow interval with `ttlSeconds: 1800` leaves one refresh interval of margin; the maximum accepted receipt lifetime is one hour. Optional `--profile qk-attest` selects an already configured local profile. Actions should inherit its existing AWS authentication and omit the profile. No signing API is called and no credentials are read out, copied, exported to the consumer or installed on its host.
Run `python -m tools.kms_status --policy /private/kms-policy.json --output /private/kms-status.json` in the existing authorized signing environment. The policy contains exactly `version: 1`, `keyArn`, `spkiSha256` and `ttlSeconds`. Use an explicit key ARN, never an alias. A 15-minute workflow interval with `ttlSeconds: 1800` leaves one refresh interval of margin; the maximum accepted receipt lifetime is one hour. Optional `--profile existing-profile` selects an already configured local profile. Actions should inherit its existing AWS authentication and omit the profile. No signing API is called and no credentials are read out, copied, exported to the consumer or installed on its host.

The bounded CLI invokes authenticated `DescribeKey` and `GetPublicKey` against the fixed regional HTTPS KMS endpoint. It requires the exact ARN, enabled state, `ML_DSA_65`, `SIGN_VERIFY`, `ML_DSA_SHAKE_256`, valid ML-DSA-65 SPKI framing and the approved SHA-256 digest. AWS documents the SPKI and algorithm fields in [GetPublicKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html). Requests that fail or disagree replace the receipt with `unknown` and return a nonzero exit code. Transfer that unknown receipt too; do not conceal a disabled key by publishing only successful refreshes. Invalid local policy or an unwritable output path stops publication; an old receipt expires naturally and cannot remain valid indefinitely.

Expand All @@ -86,7 +86,7 @@ This internal policy establishes that the authorized refresh observed the expect

## Durable email delivery

The approved QuantaKrypto alert destination is `[email protected]`. Provision owner-private `delivery.json` with `{"version":1,"recipient":"[email protected]","sender":"quantakrypto <no-reply@quantakrypto.com>"}` after confirming the existing sender configuration. Honor a configured production `EMAIL_FROM` when provisioning this sender instead of assuming the default. The adapter uses the existing `RESEND_API_KEY` from its runtime environment; it does not create, copy or print the key. Configure the service to read the existing host environment in place using a reviewed host-specific `EnvironmentFile` drop-in or an existing launcher that passes only the needed variable. Protect that existing source file with owner-only permissions before activation. The public contact form and its automatic confirmation are not a monitor transport.
Obtain the operator-approved recipient and sender before activating delivery. Provision owner-private `delivery.json`, for example `{"version":1,"recipient":"[email protected]","sender":"Monitor <no-reply@example.org>"}`, using the actual approved addresses. Honor a configured production `EMAIL_FROM` when provisioning this sender instead of assuming the default. The adapter uses the existing `RESEND_API_KEY` from its runtime environment; it does not create, copy or print the key. Configure the service to read the existing host environment in place using a reviewed host-specific `EnvironmentFile` drop-in or an existing launcher that passes only the needed variable. Protect that existing source file with owner-only permissions before activation. The public contact form and its automatic confirmation are not a monitor transport.

Explicitly initialize delivery once with `python -m tools.alert_delivery --initialize-delivery --state-directory /absolute/private/state`, after initializing the consumer state. This adds `delivery.json` and its stable lock inside the persistent state directory. Initialization refuses previous or interrupted delivery state. Missing delivery state after initialization is an error; never delete it to retry a send. Run `python -m tools.alert_delivery --config /absolute/private/config/delivery.json --state-directory /absolute/private/state` from the existing mail runtime environment. The service/timer templates run independently from the offline consumer, so mail failures do not stop measurement assessment.

Expand Down
Loading