Repository navigation
feat(sdk): plaintext byte-range decrypt in the SDK and CLI (DSPX-5116) - #1093
Draft
dmihalcik-virtru wants to merge 9 commits into
Draft
dmihalcik-virtru wants to merge 9 commits into
dmihalcik-virtru wants to merge 9 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
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 |
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
sliceAndDecrypt located each segment in the batch buffer with `encryptedOffset % slice[0].encryptedOffset`, which is only correct for batches that start at segment 0. Spans that start elsewhere, which plaintext range reads produce, misplaced later segments and failed the segment hash check. Subtract the batch's first offset instead. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Add a `range: { offset, length? }` decrypt option (ReadOptions,
DecryptParams / DecryptParamsBuilder.withRange, DecryptConfiguration).
The range is mapped onto covering segments with a binary search over
the segments' plaintext sizes; only those segments are fetched, each is
still checked against its root-signed hash, and the output is trimmed
to exactly the requested bytes. Prefetch is bounded by the range, and
the batch size is capped at the number of covering segments.
Offsets and lengths must be non-negative safe integers, so offsets above
2^32 work. An omitted length reads to the end, a span past the end is
cut short, and an offset at or past the end is a ConfigurationError.
Unsatisfiable ranges, and files whose segments lack plaintext sizes, are
refused before the KAS rewrap. The root signature is still verified
before any plaintext is emitted.
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
`decrypt <file-or-url> --offset <N> --length <M>` decrypts only that plaintext span, via the SDK's range option. Values must be plain decimal safe integers; --length omitted reads to the end, --offset omitted starts at 0, and an offset at or past the end is an error. Bad flags fail before any I/O. Inputs to decrypt and inspect now go through a source chosen by location: http(s) URLs use the SDK's remote source (fromUrl, HTTP Range requests), and local files use positioned reads on a file handle instead of fs.openAsBlob, whose size wraps at 4 GiB on some Node releases. Encrypt input is unchanged. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…ntics (DSPX-5116) Range positions no longer come from summing the manifest's recorded segment sizes, which the root signature does not cover: a keyless editor could shrink or inflate an earlier segment (padding the payload to match) and move an authentic segment to another offset. planPlaintextRange replaces plaintextRangeChunks: - Uniform layout (all segments but the last record size S and the cipher's ciphertext size for S; the last is in (0, S]): segment i starts at i * S. At least one non-last segment is always decrypted, adding segment n-2 when only the last covers the range, and every decrypted segment's authenticated length must equal its recorded size (IntegrityError otherwise), which confirms S. - Any other layout: decrypt from segment 0 and drop output before the offset. The output stream now cancels the batch stream once the range is delivered, so this stops reading after the range. Add PlaintextRangeError (extends ConfigurationError, exported from @opentdf/sdk) for malformed and unsatisfiable ranges. Follow Blob.slice: an offset equal to the plaintext size returns empty output with no payload reads, an empty plaintext reads as empty at offset 0, and only an offset past the end is an error. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…emantics (DSPX-5116) A PlaintextRangeError from the SDK (such as an offset past the end of the plaintext) is printed as a CLI error and exits with status 1. An offset equal to the plaintext size now prints nothing and succeeds. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…slice semantics (DSPX-5116) Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…SPX-5116) #1089 landed a positioned-read file chunker that also detects files changing mid-read and closes its handle. Use it for decrypt ranges and drop this branch's duplicate fromLocalFile/tdfSource. Signed-off-by: Dave Mihalcik <david.mihalcik@virtru.com>
dmihalcik-virtru
force-pushed
the
DSPX-5116
branch
from
October 16, 2026 18:19
0613d04 to
ef92bef
Compare
|
This branch has not been deployed
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.



Part of DSPX-4886. Builds on #1092 (DSPX-4654, bounded prefetch) and #1089 (DSPX-5160, local file reads without
openAsBlob), both now onmain.What
range: { offset, length? }option, with the same semantics asBlob.slice. It's available onReadOptionsandDecryptParamsBuilder.withRange().PlaintextRangeError(a subclass ofConfigurationError, exported from@opentdf/sdk). It covers both a malformed range and an offset past the end.decrypt <file-or-url> --offset N --length M.fromUrl.fileAsSource/openFileChunkerfrom fix(cli): read file input by position instead of openAsBlob (DSPX-5160) #1089, so ranges past 4 GiB work. This PR adds no file-reading code of its own.sliceAndDecryptfound segments within a batch using%, which is only correct for batches starting at segment 0. It now subtracts the batch's first offset. This is in its own commit, with a regression test.spec/DSPX-5116.md.Risk: crypto / integrity
Byte positions in a range read have to be authenticated, because an attacker can edit the manifest and payload, though not the root-signed hash list.
Tests reproduce the attacks: shrinking a segment, inflating it and padding the payload, and relabelling the layout. The residual assumption and the full threat model are in the spec's Security section. Please review that part closely.
How to test
cd lib && npm test. The new tests aredecrypt-range.spec.ts,unit/plaintext-range.spec.tsand thesliceAndDecryptcases inunit/tdf.spec.ts. They cover offsets above 2^32 against a sparse archive, a counting chunker that checks only the planned bytes are read, tamper cases, and comparison with a full decrypt.cd cli && npm test(source.spec.ts: flag parsing, URL detection, range errors as CLI errors), plus theopentdf.batsrange cases. The large-file reads are covered by fix(cli): read file input by position instead of openAsBlob (DSPX-5160) #1089'sfile-chunker.spec.ts.opentdf decrypt <tdf-or-url> --offset 1000 --length 100.