refactor(sdk): de-duplicate ChunkedWriter onto existing sdk helpers - #3923
Conversation
ChunkedWriter shipped with its own copies of the key-wrapping, metadata-encryption and policy-binding helpers that sdk/tdf.go already provides in the same package. Delete the copies and delegate to createKeyAccess, which also picks up two fixes the copies were missing: - EC key access objects were emitted with type "eccWrapped", but the KAS dispatches on "ec-wrapped" (service/kas/access/rewrap.go), so those TDFs could never be rewrapped. - The EC path XOR'd the DEK with the HKDF output instead of AES-GCM encrypting it, which is what the KAS unwrap path expects. Neither was caught before because experimental/tdf/reader.go is a stub and the chunked tests' fake KAS is RSA-only. To make the delegation possible, extract createPolicyBinding and reuse integrityAlgorithmString in tdf.go so both paths share one implementation, and add KASPublicKey.toKASInfo to bridge the splitter's key descriptor to createKeyAccess. Also hoist the policy binding and metadata encryption out of the per-KAS loop: both key on the split share rather than the KAS, so an OR-group was re-encrypting identical metadata once per URL for nothing. buildChunkedPolicy is left alone on purpose; sharing createPolicyObject would flip zero-attribute policies from [] to null and the policy UUID from v4 to v1. Adds TestChunkedKAOShape and TestChunkedECKeyAccess, the latter unwrapping the EC-wrapped DEK exactly as the KAS does. Both fail before this change. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
… sdk sdk/experimental/tdf carried its own copies of the manifest and assertion types, substantively identical to the sdk ones. Replace them with type aliases so the two packages are interchangeable at compile time, the implementation lives in one place, and downstream importers keep the exact same API. examples/cmd/benchmark_experimental.go, the only in-repo consumer, builds unchanged. Fix the EC bugs at their origin here too, so nothing is left behind for a later search to find: wrapKeyWithEC emitted "eccWrapped" rather than the "ec-wrapped" the KAS dispatches on, and XOR'd the DEK with the HKDF output instead of AES-GCM encrypting it. The existing EC tests asserted the wrong key type; they now assert the right one and additionally unwrap the DEK the way service/kas/access/rewrap.go does, which is what would have caught the envelope bug. Also replace the remaining wrap-scheme and protocol string literals with named constants. Not aliased, deliberately: - IntegrityAlgorithm: sdk's is itself an alias for int, so it cannot carry the String() method this package exports. - Policy/PolicyBody/PolicyAttribute: sdk's PolicyObject declares Body as an anonymous struct over an unexported element type, so there is nothing to alias to until those are named and exported in sdk. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
WithChunkedExcludeVersion omitted schemaVersion from the manifest, but the segment and root signatures were computed with the hex flag hardcoded to false. Readers derive isLegacyTDF from the absence of schemaVersion alone (tdf.go:988), so the option produced a manifest that claimed to predate 4.3.0 while carrying 4.3.0-style singly-encoded signatures -- unverifiable by legacy readers and by this SDK's own reader. Add WithChunkedTargetMode, which reuses the existing isLessThanSemver / hexSemverThreshold pair from tdf.go and sets useHex and excludeVersion together, mirroring the mainline WithTargetMode. The flag lives on the writer rather than the finalize config because segment signatures are written during WriteSegment, before Finalize options are seen. Finalize now rejects the inconsistent combination with ErrChunkedVersionHexMismatch instead of emitting a TDF no reader can verify.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
|
|
xtest cross-SDK run against the tip of the stack ( Per the plan, I grepped the job logs rather than trusting the check mark, since both fixes here are only reachable end-to-end through the stack. EC key access ( plus the Legacy hex / doubly-encoded signatures — That test writes a Worth noting for reviewers: the 16 skips, none related: 12 DPoP (Keycloak realm not configured for DPoP; js SDK lacks
|
Purpose
Intermediate PR in the stack
main ← #3782 ← **this** ← #3865 ← #3921.#3782 introduced
ChunkedWriterwith its own private copies of crypto helpers that alreadyexisted in
sdk/tdf.go(same package) and insdk/experimental/tdf/. This PR collapses thatthree-way duplication before #3865 rewrites
CreateTDFon top ofChunkedWriter— so thatrewrite has one implementation to build on instead of two to reconcile.
Duplication was located with
dupl(AST/token-based, so it sees through the renameswrapKeyWithEC→chunkedWrapKeyWithEC).jscpdreported only 1.05% exact clones; that gapis itself the signal that this was a paraphrased copy rather than a literal one, and it's why
CI never flagged it (
duplis not in.golangci.yaml's enabled linters).Commits
refactor(sdk): reuse tdf.go crypto helpers in ChunkedWriterDeletes
chunkedEncryptMetadata,chunkedWrapKeyWith{EC,KEM,RSA,PublicKey}, andchunkedCreatePolicyBinding, routing key access construction through the existingcreateKeyAccess/encryptMetadata/createPolicyBinding. Also hoists the policy bindingand metadata encryption out of the per-KAS loop, which had been re-encrypting identical
metadata once per URL in an OR-group.
refactor(sdk): point experimental/tdf manifest and assertion types at sdkReplaces the manifest and assertion types in
sdk/experimental/tdfwith aliases onto theirsdkcounterparts. Source-compatible —experimental/tdfis exported public API withlikely downstream consumers, so nothing is deleted from its surface.
IntegrityAlgorithmkeeps its own defined type because
sdk's is= intand cannot carry theString()method.fix(sdk): support legacy hex signatures in ChunkedWriter— see below.Bug fixes
EC key access was unusable. Copied verbatim from
experimental/tdf/key_access.go,ChunkedWriteremitted key type"eccWrapped"(the KAS only accepts"ec-wrapped",rewrap.go:713) and XOR-wrapped the DEK with the HKDF output where the unwrap path expectsAES-GCM. EC-KAS TDFs from
ChunkedWritercould not be rewrapped. It went unnoticed becauseexperimental/tdf/reader.gois a stub andchunked_test.go's fake KAS is RSA-only. Fixed inboth packages;
TestChunkedECKeyAccesscovers it and fails on the pre-fix code.WithChunkedExcludeVersionproduced unreadable TDFs. The option omittedschemaVersion—which is exactly how a reader detects a pre-4.3.0 TDF and switches to expecting hex-then-base64
signatures — while both signature sites hardcoded
isLegacyTDF: false. Every such TDF failedroot signature verification.
The two settings have to travel together, and
useHexis consumed duringWriteSegment, longbefore a
Finalizeoption is seen. So this addsWithChunkedTargetMode(mode string)atconstruction, mirroring mainline
WithTargetModeand reusing the package's existingisLessThanSemver/hexSemverThreshold.WithChunkedExcludeVersionon its own now fails withErrChunkedVersionHexMismatchrather than emitting a TDF no reader can verify.Legacy hex readers are still deployed, so the doubly-encoded form remains fully supported —
TestChunkedLegacyTargetModeround-trips a4.2.2-mode TDF and asserts the root and everysegment signature decode to 64 bytes.
Testing
make lint— clean apart from the pre-existingSA1019atsdk/kas_client_test.go:188make test— passes acrosssdk,otdfctl,serviceTestChunkedECKeyAccess,TestChunkedKAOShape,TestChunkedLegacyTargetMode,TestChunkedCurrentTargetMode,TestChunkedExcludeVersionRequiresLegacyMode,TestChunkedTargetModeInvalidChunkedWriteris only reachable end-to-end through refactor(sdk): rewrite CreateTDF on top of ChunkedWriter #3865 + fix(cli): stream encrypt and decrypt instead of buffering whole payload #3921, so cross-SDK xtest is runagainst the tip of the stack.
Follow-ups (not in scope here)
duplforsdk/in.golangci.yaml— it would have blocked the original PR.SplitResult.KASPublicKeyscould bemap[string]KASInfo, deleting the exportedKASPublicKeyentirely. That's a public-contract change to the newKeySplitterinterfaceand belongs in its own PR.
sdk/experimental/tdfdeprecated in its godoc pointing atthe
sdkequivalents.