-
Notifications
You must be signed in to change notification settings - Fork 1
v0.49.0: decision records + automatic cadence anchoring #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
01ea786
fix(registry): trim MCP manifest descriptions to the 100-char registr…
vaaraio 2f0fd79
feat(attestation): signed decision-record envelope for the server-exe…
vaaraio 7b5ce1a
test-vectors(receipt): add replay-with-field-substitution negative case
vaaraio 3e589df
test(timeanchor): add opt-in live RFC 3161 TSA round-trip
vaaraio 90c4a67
feat(audit): automatic cadence anchoring, fail-open with chained gap …
vaaraio 0d17d68
docs(sep): drop content-addressed-receipt attribution; time anchor is…
vaaraio 0da8823
release(v0.49.0): decision records, auto cadence anchoring, negative …
vaaraio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| """Emit and verify-signature for decision-record envelopes. | ||
|
|
||
| Internal module. Public surface is in ``vaara.attestation.decision``. | ||
|
|
||
| Reuses the SEP-2787 canonicalization (RFC 8785 JCS) and signing stack | ||
| (HS256 / ES256 / RS256) unchanged. The only new wire shape is the | ||
| envelope layout; the cryptographic primitives are shared so a verifier | ||
| that already handles SEP-2787 signatures handles decision records with | ||
| no new crypto code. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any, Optional | ||
|
|
||
| from vaara.attestation._decision_types import ( | ||
| DecisionDerived, | ||
| DecisionRecord, | ||
| IssuerAsserted, | ||
| decision_to_dict, | ||
| ) | ||
| from vaara.attestation._receipt_types import BackLink, back_link_to_dict, receipt_asserted_to_dict | ||
| from vaara.attestation._sep2787_canonical import ( | ||
| canonical_json, | ||
| new_nonce, | ||
| now_iso8601, | ||
| ) | ||
| from vaara.attestation._sep2787_signing import ( | ||
| sign_es256, | ||
| sign_hs256, | ||
| sign_rs256, | ||
| verify_es256, | ||
| verify_hs256, | ||
| verify_rs256, | ||
| ) | ||
| from vaara.attestation._sep2787_types import ( | ||
| VALID_ALGS, | ||
| Algorithm, | ||
| AttestationError, | ||
| ) | ||
|
|
||
|
|
||
| def _signing_payload( | ||
| *, | ||
| version: int, | ||
| alg: Algorithm, | ||
| back_link: BackLink, | ||
| decision_derived: DecisionDerived, | ||
| issuer_asserted: IssuerAsserted, | ||
| ) -> bytes: | ||
| """JCS-canonical encoding of the decision blocks, signature excluded.""" | ||
| body = { | ||
| "version": version, | ||
| "alg": alg, | ||
| "backLink": back_link_to_dict(back_link), | ||
| "decisionDerived": decision_to_dict(decision_derived), | ||
| "issuerAsserted": receipt_asserted_to_dict(issuer_asserted), | ||
| } | ||
| return canonical_json(body) | ||
|
|
||
|
|
||
| def emit_decision_record( | ||
| *, | ||
| back_link: BackLink, | ||
| decision_derived: DecisionDerived, | ||
| iss: str, | ||
| sub: str, | ||
| secret_version: str, | ||
| alg: Algorithm, | ||
| signing_material: Any, | ||
| nonce: Optional[str] = None, | ||
| iat: Optional[str] = None, | ||
| version: int = 1, | ||
| ) -> DecisionRecord: | ||
| """Build, JCS-canonicalize, and sign a DecisionRecord envelope. | ||
|
|
||
| ``back_link`` joins the decision to the SEP-2787 attestation it | ||
| governs (build it with ``make_back_link``). ``decision_derived`` | ||
| carries the verdict, its risk basis, and the decision time. Any | ||
| float in the risk basis is rejected at the JCS boundary; the risk | ||
| fields MUST be decimal strings. | ||
|
|
||
| ``signing_material`` is either a bytes shared secret (HS256) or a | ||
| private-key object from ``cryptography.hazmat`` (ES256 / RS256). | ||
| """ | ||
| if alg not in VALID_ALGS: | ||
| raise AttestationError(f"unsupported alg: {alg!r}") | ||
| if not back_link.attestation_digest.startswith("sha256:"): | ||
| raise AttestationError( | ||
| "backLink.attestationDigest MUST be a 'sha256:' digest" | ||
| ) | ||
| if not back_link.attestation_nonce: | ||
| raise AttestationError("backLink.attestationNonce MUST be non-empty") | ||
|
|
||
| issuer_asserted = IssuerAsserted( | ||
| iss=iss, | ||
| sub=sub, | ||
| iat=iat or now_iso8601(), | ||
| nonce=nonce or new_nonce(), | ||
| secret_version=secret_version, | ||
| alg=alg, | ||
| ) | ||
|
|
||
| payload = _signing_payload( | ||
| version=version, | ||
| alg=alg, | ||
| back_link=back_link, | ||
| decision_derived=decision_derived, | ||
| issuer_asserted=issuer_asserted, | ||
| ) | ||
|
|
||
| if alg == "HS256": | ||
| if not isinstance(signing_material, (bytes, bytearray)): | ||
| raise AttestationError("HS256 requires bytes shared_secret") | ||
| signature_hex = sign_hs256(payload, shared_secret=bytes(signing_material)) | ||
| elif alg == "ES256": | ||
| signature_hex = sign_es256(payload, private_key=signing_material) | ||
| elif alg == "RS256": | ||
| signature_hex = sign_rs256(payload, private_key=signing_material) | ||
| else: | ||
| raise AttestationError(f"unreachable alg: {alg!r}") | ||
|
|
||
| return DecisionRecord( | ||
| version=version, | ||
| alg=alg, | ||
| back_link=back_link, | ||
| decision_derived=decision_derived, | ||
| issuer_asserted=issuer_asserted, | ||
| signature=signature_hex, | ||
| ) | ||
|
|
||
|
|
||
| def verify_decision_signature( | ||
| record: DecisionRecord, | ||
| *, | ||
| verifying_material: Any, | ||
| ) -> bool: | ||
| """Verify the decision-record signature only. | ||
|
|
||
| Returns True iff the signature matches the JCS-canonical encoding of | ||
| the record blocks under ``verifying_material``. Back-link and pairing | ||
| checks are composed separately via ``verify_decision_back_link`` and | ||
| ``records_paired``; a decision record is a durable record so there is | ||
| no TTL to enforce. | ||
|
|
||
| ``verifying_material`` is either a bytes shared secret (HS256) or a | ||
| public-key object from ``cryptography.hazmat`` (ES256 / RS256). | ||
| """ | ||
| payload = _signing_payload( | ||
| version=record.version, | ||
| alg=record.alg, | ||
| back_link=record.back_link, | ||
| decision_derived=record.decision_derived, | ||
| issuer_asserted=record.issuer_asserted, | ||
| ) | ||
|
|
||
| if record.alg == "HS256": | ||
| if not isinstance(verifying_material, (bytes, bytearray)): | ||
| return False | ||
| return verify_hs256( | ||
| payload, | ||
| signature_hex=record.signature, | ||
| shared_secret=bytes(verifying_material), | ||
| ) | ||
| if record.alg == "ES256": | ||
| return verify_es256( | ||
| payload, | ||
| signature_hex=record.signature, | ||
| public_key=verifying_material, | ||
| ) | ||
| if record.alg == "RS256": | ||
| return verify_rs256( | ||
| payload, | ||
| signature_hex=record.signature, | ||
| public_key=verifying_material, | ||
| ) | ||
| return False | ||
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate the verdict before signing.
emit_decision_record()trustsdecision_derived.decision, but thatLiteral[...]is only static typing. Today a caller can signDecisionDerived(decision="deny", ...), producing a record thatparse_decision_record()rejects later. Please enforce the same verdict check here before canonicalization.Suggested fix
from vaara.attestation._decision_types import ( DecisionDerived, DecisionRecord, IssuerAsserted, + VALID_VERDICTS, decision_to_dict, ) @@ if alg not in VALID_ALGS: raise AttestationError(f"unsupported alg: {alg!r}") + if decision_derived.decision not in VALID_VERDICTS: + raise AttestationError( + f"invalid decision verdict {decision_derived.decision!r}" + ) if not back_link.attestation_digest.startswith("sha256:"): raise AttestationError( "backLink.attestationDigest MUST be a 'sha256:' digest" )🤖 Prompt for AI Agents