feat(seal): selectable backends, a local sealer, and profile split - #106
Open
LKSNDRTMLKV wants to merge 14 commits into
Open
feat(seal): selectable backends, a local sealer, and profile split#106LKSNDRTMLKV wants to merge 14 commits into
LKSNDRTMLKV wants to merge 14 commits into
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 97 |
| Duplication | 17 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
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.
Sealing stops being locked to one provider in the composition root, gains a development backend that works without a vendor account, and starts answering two questions it previously could not.
Closes #105, closes #107. Advances #96 — see the comment there for what remains (a second real provider, gated on vendor sandbox access, not on wiring).
What changed
Backends sit behind a
SealBackendtrait.QtspSealAdapterholds aBox<dyn SealBackend>and does nothing but forward the port's three calls and collapseSealErrorintoDppErrorat the boundary. Each backend module constructs its own, so every wiring site readsQtspSealAdapter::new(...)and the adapter names none of them.SealProvideris deliberately not#[non_exhaustive]: adding a backend should break every wiring site rather than fall into a_arm, which is how a node silently seals with something other than what it was asked for.Provider errors moved into the provider's module.
EideasyErrorowns the variants that only mean something against one contract — the five-minute HMAC skew window, the rate-limit wording, the status field that can sayERRORinside a 200.SealErrorkeepsConfig,Transport,Unsupportedand aBackend(String)for a failure only the backend can describe. Nothing outside the crate ever matched on the variants.A local development backend (
SEAL_PROVIDER=local). Real detached CMSSignedDataunder a generated P-256 key, persisted between runs. Not qualified and cannot become so — the certificate is on no EU Trusted List — which the node states structurally by resolving it to theGhosttrust tier, so production refuses to boot on it while the envelope still drains.It verifies its own seals, and is the only backend that does.
verify()is now the trait's default and it refuses; previously the refusal was an arm in the adapter's match that a new backend would silently have missed. The hosted backend keeps refusing, for independence rather than tooling: a verdict issued by the node that bought the seal attests nothing a relying party should accept. Neither objection applies to the local backend — its seals make no trust claim beyond "this key signed this digest".Making that possible needed a format fix. The first implementation signed the digest directly with
signedAttrsabsent, leaving the sealed digest in neither the envelope nor the seal — andSealPort::verifyis handed only the envelope. It was unverifiable by construction. The seal now carriescontentTypeandmessageDigestinsignedAttrsand signs over their DERSET OFencoding per RFC 5652 §5.4, which is what CAdES requires and what makes the envelope self-checking.The seal route reports staleness.
sealedPayloadHashandcoverage(current/superseded/unknown). The envelope records no preimage, but the outbox row that bought the seal does and is never deleted — so a passport re-published after sealing is detectable with a lookup and a string comparison, no AdES tooling. It is the node's record of what it asked to seal, not proof of what the CAdES covers; the validator's extracted digest is the cross-check.unknownis deliberately distinct fromsuperseded— branding a backup-restored seal stale on a missing row is the same error in the opposite direction.Breaking
NODE_PROFILE=productionnow refuses aSandboxtrust tier, not only a ghost, andNODE_PROFILE=sandboxis new. A production node asserts its passports are backed by real authorities; admitting a sandbox tier made that untrue. Migration note in the changelog.Not in this PR
SealVerificationmodels a three-valued result as a boolean (SealVerification models a three-valued result as a boolean dpp-core#131). The uncheckable-but-not-invalid case already shows up here, in miniature: a seal with nosignedAttrsis refused with an explicit message rather than reportedvalid: false.Verification
just checkgreen locally throughout; CI green including the Postgres and testcontainers tiers. The three newsealed_digestintegration tests could not run locally (no Docker) and passed in CI.