Security/v1 1 canonicalization#3
Merged
Merged
Conversation
Audit-identified protocol issues that require a coordinated wire-format
break across all reference implementations:
- v not in signed payload (downgrade vector)
- kid not in signed payload (cross-key swap)
- no domain separator (cross-protocol signature lift)
- float canonicalization underspecified (NaN, +/-0, denormals)
- string canonicalization underspecified (NFC only for source)
- extra value types underspecified
- timestamp format underspecified
- unknown top-level fields permitted
- size limits absent
Spec (docs/spec.md):
- Bump protocol version field to 2; the wire format is not compatible
with v1.
- Domain separator: signed_bytes = b"vectorpin/v2\x00" || canonical_json
(13-byte tag, exact).
- Canonical header now includes EVERY field except sig — v, kid, and
all others are signed.
- NFC normalization mandatory for every string-typed field; control
chars and bidi overrides rejected.
- Vectors with NaN/Inf rejected at sign time; +0.0 and -0.0 distinct.
- extra is strictly map<string, string>; non-string values reject.
- ts must match exactly /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.
- Unknown top-level fields rejected by parsers.
- §4.3 size limits MUST: 64 KiB pin, 32 extra entries, etc.
- §5 verifier failure mode list expanded with KEY_EXPIRED,
PARSE_ERROR, RECORD/COLLECTION/TENANT_MISMATCH.
- §12 new: explicit migration notes from v1 to v2.
Python (src/vectorpin/, tests/):
- PinHeader carries kid; canonicalize() prepends DOMAIN_TAG and emits
sorted-keys NFC-normalized JSON over all fields including v and kid.
- Signer rejects NaN/Inf; formats ts strictly; NFC-normalizes inputs.
- Default Verifier accepts only v == 2.
- LegacyV1Verifier (opt-in) byte-for-byte preserves v1 canonicalization
for migration of existing pins.
- KeyEntry carries (valid_from, valid_until); KEY_EXPIRED fires when
pin.ts is outside the registered window.
- verify(...) accepts expected_record_id/collection_id/tenant_id and
enforces them per §5 step 8.
- 120 tests pass (4 new test files cover v2 canonicalization, legacy
v1 verification, replay protection, and the test-vector fixtures).
Rust (rust/vectorpin/):
- Mirror of the Python wire format; same DOMAIN_TAG bytes, same
canonicalization, same failure-mode taxonomy.
- Cross-language anchor: rust/vectorpin/tests/cross_lang.rs loads
testvectors/v2.json and asserts byte-for-byte equality of canonical
bytes, pin_json, and Ed25519 signatures against the Python
reference, for every fixture.
- VerifyOptions builder for replay-protection inputs.
- LegacyV1Verifier mirrors the Python opt-in path.
- 50 tests pass; clippy --all-targets -- -D warnings clean.
TypeScript (typescript/):
- Async signing/verifying API throughout (carried from P2 hardening
branch).
- DOMAIN_TAG enforced at module load. NFC normalization via built-in
String.normalize. No new dependencies.
- Cross-language anchor: typescript/test/cross-lang.test.ts asserts
byte-for-byte equality against testvectors/v2.json on canonical
bytes, pin_json, and Ed25519 signature.
- 83 tests pass; npm run typecheck clean.
Test vectors:
- testvectors/v2.json — 4 positive fixtures (f32, f64, model_hash,
extra+record_id).
- testvectors/negative_v2.json — 17 fixtures covering tampered vector,
source mismatch, model mismatch, wrong v, wrong kid, bit-flipped
sig, wrong sig length, unknown top-level field, non-string extra
value, NaN in vector, NFD source, fractional-seconds ts, offset ts,
lowercase t/z ts, record_id mismatch, oversize JSON.
- testvectors/v1.json and testvectors/negative_v1.json left as-is for
LegacyV1Verifier coverage.
- Deterministic seed; reproducible via scripts/generate_test_vectors.py.
Migration: v1 pins do not verify under the strict v2 verifier in any of
the three languages. Use LegacyV1Verifier (Python, Rust, TypeScript) to
read v1 pins during migration; re-sign with the v2 Signer to produce
new v2 pins.
main now contains the p3-spec additions (size limits, key revocation,
key distribution, expanded failure-mode taxonomy). The v2 spec on this
branch is a complete rewrite that already subsumes every p3-spec
addition with v2-aware language:
- §4.3 size limits — present in v2 spec verbatim
- §7 revocation with (valid_from, valid_until) — present in v2 spec
- §10 key distribution (fingerprint, transparency log, TOFU) — present
- §5 KEY_EXPIRED / RECORD/COLLECTION/TENANT_MISMATCH / PARSE_ERROR
failure codes — present, augmented by v2 wire-format requirements
Conflict resolved by taking ours (the v2 spec). All tests still pass:
Python 120, Rust 50 across targets, TypeScript 83.
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.
No description provided.