diff --git a/code_samples/tdf/assertion_examples.mdx b/code_samples/tdf/assertion_examples.mdx index 16b287f4..e830c0f4 100644 --- a/code_samples/tdf/assertion_examples.mdx +++ b/code_samples/tdf/assertion_examples.mdx @@ -56,7 +56,7 @@ The system metadata assertion uses ID `"system-metadata"`, schema `"system-metad ### End-to-End Example -Create a signed assertion at encrypt time, then verify it at decrypt time. +Create a signed assertion at encrypt time, then verify it at decrypt time. Uses `LoadTDF` directly rather than the [Decrypt Helpers](/sdks/tdf#decrypt-helpers) (`DecryptBytes`/`DecryptTo`/`DecryptFile`), since this example reads `tdfReader.Manifest().Assertions` — the helpers return only the plaintext, with no access to manifest data. diff --git a/docs/sdks/quickstart/go.mdx b/docs/sdks/quickstart/go.mdx index e8ccceea..898e4aac 100644 --- a/docs/sdks/quickstart/go.mdx +++ b/docs/sdks/quickstart/go.mdx @@ -65,6 +65,7 @@ package main import ( "bytes" + "context" "log" "strings" @@ -122,18 +123,13 @@ func main() { // Decrypt data log.Println("\nšŸ”“ Decrypting TDF...") - tdfReader, err := client.LoadTDF(bytes.NewReader(encryptedBuffer.Bytes())) + decryptedBytes, err := client.DecryptBytes(context.Background(), encryptedBuffer.Bytes()) if err != nil { log.Fatalf("āŒ Decryption failed: %v", err) } - var decryptedBuffer bytes.Buffer - if _, err = tdfReader.WriteTo(&decryptedBuffer); err != nil { - log.Fatalf("āŒ Failed to read decrypted data: %v", err) - } - log.Println("āœ… Data successfully decrypted") - log.Printf("šŸ“¤ Decrypted content:\n\n%s\n", decryptedBuffer.String()) + log.Printf("šŸ“¤ Decrypted content:\n\n%s\n", string(decryptedBytes)) log.Println("\nšŸŽ‰ Quickstart complete!") } @@ -405,7 +401,10 @@ In production applications, you'll often need to persist encrypted TDFs to disk - **Archive encrypted data**: Store TDFs in backup systems or long-term storage with their access policies intact ```go -import "os" +import ( + "context" + "os" +) // After encryption err = os.WriteFile("encrypted.tdf", encryptedBuffer.Bytes(), 0644) @@ -413,15 +412,16 @@ if err != nil { log.Fatalf("Failed to save TDF: %v", err) } -// Later, load from file -tdfData, err := os.ReadFile("encrypted.tdf") +// Later, decrypt directly from the file — DecryptFile streams from disk +// rather than buffering the ciphertext or plaintext in memory. +err = client.DecryptFile(context.Background(), "encrypted.tdf", "decrypted.txt") if err != nil { - log.Fatalf("Failed to read TDF: %v", err) + log.Fatalf("Failed to decrypt TDF: %v", err) } - -tdfReader, err := client.LoadTDF(bytes.NewReader(tdfData)) ``` +`DecryptFile` is one of three decrypt convenience helpers — see [Decrypt Helpers](/sdks/tdf#decrypt-helpers) for `DecryptBytes` and `DecryptTo`, which work directly on in-memory ciphertext instead of a file path. + ### Handle Large Files with Streaming For large files, use file I/O instead of in-memory buffers: @@ -626,19 +626,13 @@ func main() { log.Printf("āœ… TDF loaded from encrypted.tdf") // 7. Decrypt the data - tdfReader, err := client.LoadTDF(bytes.NewReader(tdfData)) - if err != nil { - log.Fatalf("Failed to load TDF: %v", err) - } - - var decryptedBuffer bytes.Buffer - _, err = tdfReader.WriteTo(&decryptedBuffer) + decryptedBytes, err := client.DecryptBytes(context.Background(), tdfData) if err != nil { log.Fatalf("Failed to decrypt: %v", err) } log.Printf("āœ… Data successfully decrypted") - log.Printf("šŸ“¤ Decrypted content: %s", decryptedBuffer.String()) + log.Printf("šŸ“¤ Decrypted content: %s", string(decryptedBytes)) } ``` diff --git a/docs/sdks/tdf.mdx b/docs/sdks/tdf.mdx index a0c04ae6..dbac7d94 100644 --- a/docs/sdks/tdf.mdx +++ b/docs/sdks/tdf.mdx @@ -19,6 +19,7 @@ This page covers the core TDF operations: - **[CreateTDF](#createtdf)** — encrypt a payload and write a TDF - **[LoadTDF](#loadtdf)** — open a TDF and access the plaintext +- **[Decrypt Helpers](#decrypt-helpers)** — `DecryptBytes`/`DecryptTo`/`DecryptFile`: one-call convenience wrappers over `LoadTDF` + `WriteTo` - **[IsValidTdf](#isvalidtdf)** — check whether a stream is a valid TDF without decrypting - **[BulkDecrypt](#bulkdecrypt)** — decrypt multiple TDFs in one call - **[TDF Reader](#tdf-reader)** — methods on the reader object returned by `LoadTDF` @@ -496,6 +497,104 @@ Rejects with `Error` if the TDF is invalid, the KAS is unreachable, access is de --- +## Decrypt Helpers + +Go-only convenience wrappers over `LoadTDF` + `Reader.WriteTo` for the common case of decrypting straight to plaintext. All three take a `ctx context.Context` that governs their KAS rewrap request, and accept the same `opts ...TDFReaderOption` as `LoadTDF` — see [Decrypt Options](#decrypt-options) for the full list. + +:::tip Why use these instead of LoadTDF directly? +`LoadTDF` returns a reader — useful when you need to stream a very large payload or read manifest data (attributes, assertions) before writing out the payload. If you just want the plaintext, `DecryptBytes`, `DecryptTo`, and `DecryptFile` collapse `LoadTDF` + `WriteTo` into a single call. +::: + +### DecryptBytes + +Decrypts a TDF payload held in memory and returns the plaintext. Rejects payloads over 1 GiB up front, before buffering anything, since the full plaintext is held in memory — use `DecryptTo` or `DecryptFile` for larger payloads, since both stream to their destination instead of buffering. + +**Signature** + + + +```go +func (s SDK) DecryptBytes(ctx context.Context, ciphertext []byte, opts ...TDFReaderOption) ([]byte, error) +``` + +**Example** + +```go +plaintext, err := client.DecryptBytes(ctx, ciphertext) +if err != nil { + log.Fatal(err) +} +fmt.Println(string(plaintext)) +``` + +### DecryptTo + +Decrypts a TDF payload held in memory and writes the plaintext to any `io.Writer` — a file, `os.Stdout`, an HTTP response, etc. + +**Signature** + + + +```go +func (s SDK) DecryptTo(ctx context.Context, out io.Writer, ciphertext []byte, opts ...TDFReaderOption) error +``` + +**Example** + +```go +err := client.DecryptTo(ctx, os.Stdout, ciphertext) +if err != nil { + log.Fatal(err) +} +``` + +### DecryptFile + +Decrypts the TDF at `inputPath` and writes the plaintext to `outputPath`. Streams directly from disk rather than buffering the whole ciphertext or plaintext in memory. + +**Signature** + + + +```go +func (s SDK) DecryptFile(ctx context.Context, inputPath, outputPath string, opts ...TDFReaderOption) error +``` + +**Example** + +```go +err := client.DecryptFile(ctx, "secret.tdf", "secret.txt") +if err != nil { + log.Fatal(err) +} +``` + +Plaintext is written to a temp file next to `outputPath` and only renamed into place once decryption fully succeeds — a pre-existing `outputPath` is never touched, truncated, or deleted if decryption fails partway. If `outputPath` already exists, it's moved aside and restored automatically if the final rename fails, so a failed decrypt never destroys a file that was already there. `inputPath` and `outputPath` must not refer to the same file (including via a hard link); `DecryptFile` rejects that case up front. + +**Errors** + +`DecryptBytes`, `DecryptTo`, and `DecryptFile` all wrap failures in one of two sentinels, so callers can branch on which stage failed regardless of which helper they called: + +| Error | Sentinel | Cause | +|-------|----------|-------| +| Pre-decrypt failure | `sdk.ErrTDFNotDecryptable` | The input itself isn't decryptable — corrupt/invalid TDF bytes, a rejected `TDFReaderOption`, or a schema validation failure. Retrying the same input won't help. | +| Payload too large (`DecryptBytes` only) | `sdk.ErrTDFNotDecryptable` | Plaintext exceeds the 1 GiB in-memory limit. Use `DecryptTo`, `DecryptFile`, or `LoadTDF` directly instead. | +| Decrypt-time failure | `sdk.ErrTDFDecryptFailed` | Failed during decryption itself — most commonly a KAS rewrap failure (not entitled), but also writer errors or payload integrity errors. | + +Both are testable with `errors.Is`, and the underlying `LoadTDF`/`WriteTo` error stays reachable through the same chain via `errors.As`. Every failure from all three helpers wraps one of these two sentinels: + +```go +plaintext, err := client.DecryptBytes(ctx, ciphertext) +switch { +case errors.Is(err, sdk.ErrTDFNotDecryptable): + // Pre-decrypt: the input itself isn't decryptable. Retrying won't help. +case errors.Is(err, sdk.ErrTDFDecryptFailed): + // Decrypt-time: most commonly a KAS rewrap failure (not entitled), but also writer errors for DecryptTo/DecryptFile. +} +``` + +--- + ## IsValidTdf Checks whether a byte stream contains a valid TDF without decrypting it. Specifically, it validates the ZIP container structure and parses the embedded manifest JSON against the TDF schema. No key access, HMAC verification, or payload decryption is performed. The stream position is restored after the check.