Implement IETF MTC CA#214
Draft
lukevalenta wants to merge 7 commits intomainfrom
Draft
Conversation
cb29508 to
9e0d790
Compare
8d2e9fb to
1600761
Compare
Closed
be8a89a to
9fd74be
Compare
…nary-format support
Introduces a new spec-level crate, separate from `ietf_mtc_api`, for the
`sign-subtree` cosigner endpoint. The endpoint is currently specified in
draft-ietf-plants-merkle-tree-certs-02 §C.2; the intent is for it to
migrate into a standalone C2SP specification (provisional URL
`c2sp.org/tlog-subtree-signature`), and this crate is positioned to ship
with that future spec.
Scope:
HTTP wire format (top-level module):
Server side (cosigner receiving the request):
- `SignSubtreeRequest`, `SubtreeNoteBody`
- `parse_sign_subtree_request`
- `serialize_sign_subtree_response`
Client side (requester):
- `serialize_subtree_note_body`
- `serialize_sign_subtree_request`
- `parse_sign_subtree_response`
Shared:
- `MAX_CONSISTENCY_PROOF_HASHES = 63`
- `TlogSubtreeSignatureError`
Binary signing format (`crypto` module, re-exported at crate root):
- `serialize_subtree_signature_input` — produce the `mtc-subtree/v1\n\0`
binary message a subtree signer actually signs (§5.4.1). Opaque-bytes
interface: `cosigner_id` and `log_id` are `&[u8]` so the crate doesn't
depend on any particular identity encoding (MTC uses BER-encoded
`TrustAnchorID`; future C2SP spec may use something else).
- `RawSigner` / `RawVerifier` traits: algorithm-agnostic abstraction
over "produce raw sig bytes for a message" / "check raw sig bytes for
a message". Concrete implementations (Ed25519, ML-DSA-44, ...) live
in downstream crates; today the only workspace ones are in
`ietf_mtc_api` (`MtcSigningKey` / `MtcVerifyingKey`).
- `sign_subtree(signer, cosigner_id, log_id, start, end, hash)` —
convenience wrapper that builds the binary input and hands it to the
signer.
- `SubtreeNoteVerifier<V: RawVerifier>` — a `signed_note::NoteVerifier`
that accepts a subtree signed note, reconstructs the binary signing
input from the note text body, and delegates to the wrapped
`RawVerifier`. Caller supplies `KeyName`, key ID, and identity bytes;
this crate does not prescribe name-format or key-ID conventions.
The label `mtc-subtree/v1` is kept as-is for wire compatibility with
current IETF MTC deployments. When the C2SP spec settles on a new label
(e.g. via [C2SP#237]'s `subtree/v1`), `serialize_subtree_signature_input`
will gain a second encoding and existing clients can migrate in lockstep.
Out of scope (stays in `ietf_mtc_api`):
- Multi-algorithm key enums (`MtcSigningKey` / `MtcVerifyingKey`). They
become `RawSigner` / `RawVerifier` impls in a follow-up commit.
- `TrustAnchorID` / `ID_RDNA_TRUSTANCHOR_ID` and the MTC `oid/…`
key-name scheme. The new crate takes an opaque `KeyName` + key ID.
- `MtcCosigner` and `ParsedMtcProof`. These remain MTC-CA-specific.
Tests: 21 unit tests (17 wire-format + 4 binary-format) pin the request /
response round-trip, malformed-input rejection, the `mtc-subtree/v1`
binary layout, and the `SubtreeNoteVerifier` happy-path + malformed-text
paths. The binary-format layout test mirrors the pattern used by
`SequenceMetadata::serialize_cache_entries` — any change to the format
would fail loudly and visibly.
The `parse_sign_subtree_request` doc comment carries forward a TODO from
the original `ietf_mtc_api::sign_subtree` module: the `\n\n` blank-line
separator between sections is ambiguous with the intra-note
text/signatures boundary. Two cleaner alternatives (length-prefixed
sections; TLS-binary framing) are sketched for upstream discussion.
Follow-ups:
- `lvalenta/ietf-mtc-v2` drops the duplicate parsers +
`serialize_mtc_subtree_signature_input` from `ietf_mtc_api`; its
`MtcCosigner::sign_subtree` becomes a thin wrapper over
`tlog_subtree_signature::sign_subtree`, and `MtcSubtreeNoteVerifier`
becomes a thin wrapper that fixes the MTC identity convention in
place around `SubtreeNoteVerifier`.
- `lvalenta/tlog-witness` gains a `/sign-subtree` endpoint using this
crate's parsers + `ietf_mtc_api` for the MTC signing semantics.
[C2SP#237]: C2SP/C2SP#237
Initial scaffolding for the IETF MTC implementation (draft-ietf-plants-merkle-tree-certs). Copied directly from bootstrap_mtc_api and bootstrap_mtc_worker as a starting point, incorporating the SequencerMetadata refactor; subsequent commits will remove bootstrap- specific functionality and implement draft-02 behaviour. Identifiers (crate names, paths, module names, types prefixed BootstrapMtc, constants prefixed BOOTSTRAP_MTC, kebab-case references) are renamed for the new crate; file contents otherwise match the bootstrap source byte-for-byte. The integration_tests crate gains tests/ietf_mtc_api.rs, which still uses the bootstrap_mtc_api client/fixtures until the strip commit introduces IETF- specific counterparts.
Removes bootstrap-specific code from ietf_mtc_api and ietf_mtc_worker and replaces it with IETF draft-ietf-plants-merkle-tree-certs-02 functionality: ietf_mtc_api: - AddEntryRequest: replace chain (Vec<Vec<u8>>) with csr (base64url DER) - build_pending_entry: parse PKCS#10 CSR, extract SAN extension - TbsCertificateLogEntry: no outer SEQUENCE wrapper (davidben-10); adds subject_public_key_info_algorithm field (plants-02) - encode_fields/decode_fields: manual DER encoding/decoding without #[derive(Sequence)] - serialize_landmark_relative_cert: renamed from serialize_signatureless_cert - Remove GetRootsResponse, validate_chain, validate_correspondence, etc. ietf_mtc_worker: - Remove ccadb_roots_cron, ct_logs_cron, dev-bootstrap-roots.pem - Remove ROOTS OnceLock, load_roots, sct_validator dep, get-roots route - add_entry: call build_pending_entry with CSR instead of validate_chain - IetfMtcSequenceMetadata::leaf_index() / timestamp() accessors used in frontend response construction (in place of tuple field access) - wrangler.jsonc: cleaned up for IETF worker - config/src/lib.rs: replace enable_sct_validation with version: DraftVersion - config.schema.json: replace enable_sct_validation with version field - config/Cargo.toml: add ietf_mtc_api dep (needed for DraftVersion type) integration_tests: - Add IetfMtcClient with CSR-based add_entry - Add make_ietf_mtc_csr fixture using x509-cert RequestBuilder - Update tests/ietf_mtc_api.rs to use IetfMtcClient and CSR submission - Add ietf_mtc_api to Cargo.toml READMEs updated for all four MTC crates.
Implements draft-ietf-plants-merkle-tree-certs §6.2 standalone MTC
certificates, where the signatureValue embeds a cosignature over the
subtree covering the leaf.
ietf_mtc_api:
- cosigner.rs: promote Ed25519-only cosigner to a multi-algorithm type:
- MtcSigningKey / MtcVerifyingKey enums with Ed25519 variant and an
MlDsa44 stub (unimplemented! until draft-03 / C2SP#237 align on the
subtree/v1 unified signature format)
- MtcCheckpointNoteVerifier / MtcSubtreeNoteVerifier: split the single
NoteVerifier into two types with distinct key IDs (mtc-checkpoint/v1
vs mtc-subtree/v1) and distinct note-body parsers, so each role is
self-describing instead of overloading a single verifier
- ParsedMtcProof: parse an MTCProof from a certificate's signatureValue
and verify its cosignature against a subtree hash / verifying key
- lib.rs: add SUBTREE_SIG_KEY_PREFIX / subtree_sig_key / SignedSubtree;
rename serialize_landmark_relative_cert -> serialize_mtc_cert and add a
cosignatures parameter (empty = landmark-relative, non-empty = standalone)
- AddEntryResponse: replace individual fields with a single certificate
field (DER-encoded standalone cert)
- relative_oid.rs: add from_ber_bytes and Debug/PartialEq/Eq/Hash derives
ietf_mtc_worker:
- IetfMtcSequenceMetadata gains old_tree_size / new_tree_size fields. The
frontend uses Subtree::split_interval(old, new) plus the leaf_index to
identify the single R2 key of the cached subtree signature, avoiding
the candidate-subtree enumeration used in bootstrap
- sequencer_do: checkpoint_callback signs and caches the covering
subtree(s) with sign_and_cache_batch_subtrees / sign_and_cache_landmark_subtrees
- cleaner_do: remove stale subtree signatures from R2
- frontend_worker: build_standalone_cert fetches the cached SignedSubtree,
computes the inclusion proof, and assembles a DER-encoded §6.2 standalone
cert via serialize_mtc_cert
- The /metadata endpoint now returns the cosigner's DER-encoded SPKI so
multi-algorithm clients can distinguish key types
tlog_tiles:
- evaluate_subtree_inclusion_proof: complement to
verify_subtree_inclusion_proof that returns the derived subtree hash
(so callers can compare it against an external commitment like a cosignature)
integration_tests:
- IetfMtcClient::get_signed_subtree fetches cached signatures from R2
- AddEntryResponse: replace field parsing with the certificate-only shape
- tests/ietf_mtc_api.rs: full draft-02 §7.2 standalone certificate
verification end-to-end
Implements the MlDsa44 variants that were previously stubs in MtcSigningKey / MtcVerifyingKey (unimplemented!). Keeps the same key ID scheme (mtc-checkpoint/v1 / mtc-subtree/v1 context strings) and the same mtc-subtree/v1 binary signing format as draft-02 specifies — no C2SP#237 (subtree/v1) changes. ietf_mtc_api: - MtcSigningKey::MlDsa44(ExpandedSigningKey<MlDsa44>) and MtcVerifyingKey::MlDsa44(VerifyingKey<MlDsa44>) - try_sign / verify / to_public_key_der routed through the ml-dsa crate's sign/verify APIs and RustCrypto PKCS#8 SPKI encoding ietf_mtc_worker: - parse_key_pair now dispatches on the PKCS#8 AlgorithmIdentifier OID (1.3.101.112 Ed25519 / 2.16.840.1.101.3.4.3.17 ML-DSA-44) so logs can be configured with either algorithm Dev / CI: - .dev.vars: replace dev1's Ed25519 key with a NIST FIPS 204 ML-DSA-44 test vector; drop the unused WITNESS_KEY_* entries left over from the bootstrap scaffold. dev2 stays on Ed25519 to exercise both paths. - config.dev.json: give dev1 the same short landmark_interval_secs as dev2 so the full test suite runs under either algorithm. - Integration CI: run the IETF MTC test job twice (IETF_MTC_LOG_NAME=dev1 then =dev2) to cover both algorithms per push. Integration tests: - fetch_verifying_key dispatches on the SPKI algorithm OID and constructs either an Ed25519 or ML-DSA-44 MtcVerifyingKey. - get_certificate_returns_valid_cert no longer skips on non-dev2 log names.
…rkers.dev deployment scripts/create-log.sh provisions the R2 bucket and uploads the signing key secret for a single log shard. ALGORITHM selects ed25519 or ml-dsa-44; the ML-DSA-44 path passes `-provparam ml-dsa.output_formats=seed-only` so the generated PEM is the compact 32-byte-seed PKCS#8 form that the worker's `ml-dsa-0.1.0-rc.8`-based PKCS#8 decoder expects (default OpenSSL output would be the expanded-key form which the worker would reject). Unlike the ct_worker create-log.sh this does not create a witness key — IETF MTC has no witness-key concept. scripts/test-deployment.sh is an end-to-end smoke test against a deployed worker: it generates a CSR (ed25519 or ML-DSA-44), submits it via /add-entry, fetches the returned standalone MTC certificate, then polls for a landmark-relative certificate for the same entry. Prints a summary (size, subject/issuer/validity, signatureAlgorithm OID) for each cert but does not verify the cosignatures or inclusion proofs — for that, run the Rust integration tests against the same BASE_URL. The README gains a Deployment section covering workers.dev (using the existing `dev` environment in `wrangler.jsonc`) that references both scripts, and pointing at the ct_worker docs for custom-domain deployments.
The `sign-subtree` wire-format parsers/serializers and the
`mtc-subtree/v1` binary signing layout live in the
`tlog_subtree_signature` crate (introduced earlier in this branch's
history). Refactor `ietf_mtc_api` to build on top of that crate rather
than duplicate it:
- `ietf_mtc_api::sign_subtree` becomes a thin re-export shim over
`tlog_subtree_signature`, re-exporting `SignSubtreeRequest`,
`SubtreeNoteBody`, `MAX_CONSISTENCY_PROOF_HASHES`,
`parse_sign_subtree_request` / `serialize_sign_subtree_response`
(server side) and `serialize_subtree_note_body` /
`serialize_sign_subtree_request` / `parse_sign_subtree_response`
(client side). All wire-format unit tests now live in the
`tlog_subtree_signature` crate.
- `MtcSubtreeNoteVerifier` is kept in this crate (it owns the
MTC-specific `oid/{id_rdna_trustanchor_id}.{log_id}` name format and
the `mtc-subtree/v1` key-ID context string) but delegates its
`NoteVerifier` impl to a wrapped
`tlog_subtree_signature::SubtreeNoteVerifier<MtcVerifyingKey>`.
Taking `&TrustAnchorID` parameters now so the constructor fits
pedantic-clippy without needless moves; no external callers yet.
- `MtcSigningKey` / `MtcVerifyingKey` implement the algorithm-agnostic
`tlog_subtree_signature::RawSigner` / `RawVerifier` traits so they
can plug directly into
`tlog_subtree_signature::{sign_subtree, SubtreeNoteVerifier}`.
- `MtcCosigner::sign_subtree` is now a one-liner over
`tlog_subtree_signature::sign_subtree`, eliminating the duplicated
`serialize_mtc_subtree_signature_input` helper from `cosigner.rs`.
`MtcCheckpointNoteVerifier::verify` and
`ParsedMtcProof::verify_cosignature` use
`tlog_subtree_signature::serialize_subtree_signature_input` as the
single source of truth for the `mtc-subtree/v1\n\0` binary layout.
Net effect: `ietf_mtc_api` gains a dep on `tlog_subtree_signature`
and loses several hundred lines of duplicated wire-format code and
tests. No public-API breakage besides `MtcSubtreeNoteVerifier::new`
switching from owned to borrowed `TrustAnchorID` arguments (no
external callers).
9fd74be to
b00c111
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the IETF MTC CA on top of the generic
tlog_subtree_signaturecrate from #221.ietf_mtc_apiandietf_mtc_workercrates (initially copied from thebootstrap_mtc counterparts, then stripped of bootstrap-specific functionality
and adapted for
draft-ietf-plants-merkle-tree-certs-02).Ed25519, and helper scripts for workers.dev deployment.
ietf_mtc_apionto Add tlog_subtree_signature crate with sign-subtree wire-format and binary-format support #221'stlog_subtree_signaturecrate:
MtcSigningKey/MtcVerifyingKeyimpl the crate'sRawSigner/RawVerifiertraits,MtcSubtreeNoteVerifierdelegates toSubtreeNoteVerifier, and the duplicatemtc-subtree/v1binary-formathelper in
ietf_mtc_api::cosigneris removed.Stacked on
tlog_subtree_signaturecrate this PR depends on.Testing
cargo clippy --workspace --all-targets -- -Dwarnings -Dclippy::pedantic,cargo test,cargo fmt --all --check,cargo macheteall pass locally.Integration test job
integration-ietf-mtcexercises both ML-DSA-44 andEd25519 logs against
wrangler dev.