Parent: #28 · Track A (cert-side) · Depends on PR 1 (Rust SPKI OIDs) · Independent of PRs 2/3
Scope
New opt-in validator that walks the certificate chain and emits per-cert PQ posture. Useful during the staged migration period when leaf, intermediate, and root rotate independently.
Returns
```python
{
"chain_length": 3,
"links": [
{"depth": 0, "subject": "...", "key_alg": "ml-dsa-65", "sig_alg": "ml-dsa-65", "is_pq": True},
{"depth": 1, "subject": "...", "key_alg": "rsaEncryption", "sig_alg": "sha256WithRSAEncryption", "is_pq": False},
{"depth": 2, "subject": "...", "key_alg": "rsaEncryption", "sig_alg": "sha256WithRSAEncryption", "is_pq": False},
],
"summary": {"leaf_pq": True, "intermediate_pq": False, "root_pq": False},
"is_valid": True, # or another threshold — see open question
}
```
Open question for the PR
What makes is_valid: True? Three options:
- Leaf PQ is enough (current proposal — matches the realistic 2026 migration order)
- Full chain PQ
- Configurable threshold via validator user-arg
Default to (1) and discuss in PR review.
Doc note (required)
Chains terminating at public trust anchors will report is_pq: False at the root for the foreseeable future. This is expected, not a bug — call it out in the docstring and the docs page.
Files to touch
certmonitor/validators/pq_chain.py (new) — model after chain.py
- certmonitor/validators/init.py — register in
VALIDATORS only
- Docs page +
mkdocs.yml entry
tests/test_validators/test_pq_chain.py (new)
Tests
- All-classical chain (leaf, intermediate, root all RSA)
- Hybrid migration: PQ leaf, classical intermediate + root
- Fully PQ chain (synthetic — won't exist in the wild)
- Single-cert chain (self-signed)
- Empty chain → error dict
Definition of Done
Parent: #28 · Track A (cert-side) · Depends on PR 1 (Rust SPKI OIDs) · Independent of PRs 2/3
Scope
New opt-in validator that walks the certificate chain and emits per-cert PQ posture. Useful during the staged migration period when leaf, intermediate, and root rotate independently.
Returns
```python
{
"chain_length": 3,
"links": [
{"depth": 0, "subject": "...", "key_alg": "ml-dsa-65", "sig_alg": "ml-dsa-65", "is_pq": True},
{"depth": 1, "subject": "...", "key_alg": "rsaEncryption", "sig_alg": "sha256WithRSAEncryption", "is_pq": False},
{"depth": 2, "subject": "...", "key_alg": "rsaEncryption", "sig_alg": "sha256WithRSAEncryption", "is_pq": False},
],
"summary": {"leaf_pq": True, "intermediate_pq": False, "root_pq": False},
"is_valid": True, # or another threshold — see open question
}
```
Open question for the PR
What makes
is_valid: True? Three options:Default to (1) and discuss in PR review.
Doc note (required)
Chains terminating at public trust anchors will report
is_pq: Falseat the root for the foreseeable future. This is expected, not a bug — call it out in the docstring and the docs page.Files to touch
certmonitor/validators/pq_chain.py(new) — model after chain.pyVALIDATORSonlymkdocs.ymlentrytests/test_validators/test_pq_chain.py(new)Tests
Definition of Done
make cicleanmkdocs.ymlwith the trust-anchor caveat explicitly called outdevelopfromfeat/pq-chain-validatorDEFAULT_VALIDATORS)