TDF
A TDF (Trusted Data Format) wraps a payload in a cryptographic envelope that binds access control policy directly to the data. The TDF contains an encrypted payload, a manifest describing the policy and key access configuration, and optional assertions.
This page covers the core TDF operations:
- CreateTDF — encrypt a payload and write a TDF
- LoadTDF — open a TDF and access the plaintext
- IsValidTdf — check whether a stream is a valid TDF without decrypting
- BulkDecrypt — decrypt multiple TDFs in one call
- TDF Reader — methods on the reader object returned by
LoadTDF - Encrypt Options — full option reference for
CreateTDF - Decrypt Options — full option reference for
LoadTDF - Type Reference —
KASInfo,PolicyObject,Manifest - Experimental: Streaming Writer — segment-based API for large files and out-of-order assembly
Quick Start
Initialize a client and run an end-to-end encrypt/decrypt in one block.
- Go
- Java
- JavaScript
import (
"bytes"
"fmt"
"log"
"strings"
"github.com/opentdf/platform/sdk"
)
client, err := sdk.New(
"https://platform.example.com",
sdk.WithClientCredentials("client-id", "client-secret", nil),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Encrypt
var buf bytes.Buffer
_, err = client.CreateTDF(&buf, strings.NewReader("hello, world"),
sdk.WithKasInformation(sdk.KASInfo{URL: "https://platform.example.com"}),
)
if err != nil {
log.Fatal(err)
}
// Decrypt
tdfReader, err := client.LoadTDF(bytes.NewReader(buf.Bytes()))
if err != nil {
log.Fatal(err)
}
var plaintext bytes.Buffer
if _, err = tdfReader.WriteTo(&plaintext); err != nil {
log.Fatal(err)
}
fmt.Println(plaintext.String()) // "hello, world"
import io.opentdf.platform.sdk.SDK;
import io.opentdf.platform.sdk.SDKBuilder;
import io.opentdf.platform.sdk.Config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
SDK sdk = SDKBuilder.newBuilder()
.platformEndpoint("https://platform.example.com")
.clientSecret("client-id", "client-secret")
.build();
// Encrypt
var kasInfo = new Config.KASInfo();
kasInfo.URL = "https://platform.example.com/kas";
var config = Config.newTDFConfig(Config.withKasInformation(kasInfo));
var input = new ByteArrayInputStream("hello, world".getBytes(StandardCharsets.UTF_8));
var output = new ByteArrayOutputStream();
sdk.createTDF(input, output, config);
// Decrypt
Path tmp = Files.createTempFile("example", ".tdf");
Files.write(tmp, output.toByteArray());
try (FileChannel ch = FileChannel.open(tmp, StandardOpenOption.READ)) {
var reader = sdk.loadTDF(ch, Config.newTDFReaderConfig());
var result = new ByteArrayOutputStream();
reader.readPayload(result);
System.out.println(result.toString(StandardCharsets.UTF_8)); // "hello, world"
}
import { OpenTDF, AuthProviders } from '@opentdf/sdk';
const authProvider = await AuthProviders.clientSecretAuthProvider({
clientId: 'client-id',
clientSecret: 'client-secret',
oidcOrigin: 'https://platform.example.com/auth/realms/opentdf',
});
const client = new OpenTDF({ authProvider, platformUrl: 'https://platform.example.com' });
// Encrypt
const enc = new TextEncoder();
const tdfStream = await client.createTDF({
source: { type: 'buffer', location: enc.encode('hello, world') },
defaultKASEndpoint: 'https://platform.example.com/kas',
});
// Decrypt
const encrypted = new Uint8Array(await new Response(tdfStream).arrayBuffer());
const stream = await client.read({
source: { type: 'buffer', location: encrypted },
});
console.log(await new Response(stream).text()); // "hello, world"
CreateTDF
Encrypts a plaintext payload and writes a TDF to the provided output destination.
Signature
- Go
- Java
- JavaScript
func (s SDK) CreateTDF(writer io.Writer, reader io.ReadSeeker, opts ...TDFOption) (*TDFObject, error)
Manifest SDK.createTDF(InputStream plaintext, OutputStream out, Config.TDFConfig config)
throws SDKException, IOException
async createTDF(options: CreateTDFOptions): Promise<DecoratedStream>
Parameters
| Parameter | Required | Description |
|---|---|---|
| Output destination | Required | Where the encrypted TDF bytes are written. |
| Plaintext source | Required | The data to encrypt. See the signature tab for your language for the expected type. |
| Configuration | Required* | Encryption options. A KAS endpoint must be specified unless autoconfigure is enabled. See Encrypt Options. |
Returns
- Go
- Java
- JavaScript
(*TDFObject, error) — On success, a TDFObject with a .Manifest() method returning the Manifest and a .Size() method returning the encrypted byte count. Returns a non-nil error on failure.
Manifest — The manifest written to the TDF, containing key access and encryption metadata. Encrypted bytes are written to the output stream.
Promise<DecoratedStream> — A decorated stream of encrypted TDF bytes. Pipe or consume it like any ReadableStream.
Errors
- Go
- Java
- JavaScript
| Error | Cause |
|---|---|
| KAS unreachable | The KAS could not be contacted to wrap the encryption key. |
| No KAS configured | No KAS was provided and autoconfigure is disabled or could not resolve one. |
| Write failure | An I/O error occurred writing to out. |
| Exception | Cause |
|---|---|
SDKException | KAS unreachable, policy invalid, or the SDK could not resolve a KAS from the attribute service. |
IOException | I/O failure reading from the input stream or writing to the output stream. |
Rejects with Error if the KAS is unreachable, authentication fails, or the source data cannot be read.
Example
- Go
- Java
- JavaScript
import (
"bytes"
"log"
"strings"
"github.com/opentdf/platform/sdk"
)
var buf bytes.Buffer
manifest, err := client.CreateTDF(&buf, strings.NewReader("Sensitive data"),
sdk.WithKasInformation(sdk.KASInfo{URL: "https://kas.example.com"}),
)
if err != nil {
log.Fatal(err)
}
import io.opentdf.platform.sdk.Config;
import io.opentdf.platform.sdk.Manifest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
var kasInfo = new Config.KASInfo();
kasInfo.URL = "https://kas.example.com";
var config = Config.newTDFConfig(Config.withKasInformation(kasInfo));
var in = new ByteArrayInputStream("Sensitive data".getBytes(StandardCharsets.UTF_8));
var out = new ByteArrayOutputStream();
Manifest manifest = sdk.createTDF(in, out, config);
const tdf = await client.createTDF({
source: { type: 'buffer', location: new TextEncoder().encode('Sensitive data') },
defaultKASEndpoint: 'https://kas.example.com',
});
const encrypted = await new Response(tdf).bytes();
See Encrypt Options for the full list of configuration options.
LoadTDF
Opens an encrypted TDF and returns a TDF reader that provides access to the plaintext payload and manifest data.
Signature
- Go
- Java
- JavaScript
func (s SDK) LoadTDF(reader io.ReadSeeker, opts ...TDFReaderOption) (*Reader, error)
TDF.Reader SDK.loadTDF(SeekableByteChannel channel, Config.TDFReaderConfig config) throws SDKException, IOException
// Returns the plaintext payload directly:
async read(options: ReadOptions): Promise<DecoratedStream>
// Returns a reader for lazy inspection or decryption:
open(options: ReadOptions): TDFReader
Parameters
| Parameter | Required | Description |
|---|---|---|
| Encrypted TDF source | Required | The TDF to open. See the signature tab for your language for the expected type. For JavaScript, source accepts a buffer (Uint8Array) or stream (ReadableStream). |
| Configuration | Optional | Decryption options. Defaults allow any KAS endpoint. See Decrypt Options. |
Returns
- Go
- Java
- JavaScript
(*Reader, error) — A TDF reader object. See TDF Reader for available methods.
TDF.Reader — A reader object. See TDF Reader for available methods.
read()→Promise<DecoratedStream>— A stream of plaintext bytes with.metadataattached.open()→TDFReader— A lazy reader with.decrypt(),.manifest(), and.attributes()methods.
Errors
- Go
- Java
- JavaScript
| Error | Cause |
|---|---|
| Invalid TDF | The input is not a valid TDF. Use IsValidTdf to pre-validate. |
| KAS unreachable | The KAS referenced in the manifest cannot be contacted. |
| Access denied | The caller's credentials do not satisfy the TDF policy. |
| KAS not allowlisted | The TDF references a KAS endpoint not on the allowlist. |
Java throws IOException for all failure modes. Inspect e.getMessage() to distinguish between access denied, unreachable KAS, and malformed TDF — the message text reflects the underlying cause.
Rejects with Error if the TDF is invalid, the KAS is unreachable, access is denied, or the endpoint is not allowlisted.
Example
- Go
- Java
- JavaScript
import (
"bytes"
"fmt"
"log"
)
tdfReader, err := client.LoadTDF(bytes.NewReader(encryptedBytes))
if err != nil {
log.Fatal(err)
}
var plaintext bytes.Buffer
if _, err = tdfReader.WriteTo(&plaintext); err != nil {
log.Fatal(err)
}
fmt.Println(plaintext.String())
import io.opentdf.platform.sdk.Config;
import io.opentdf.platform.sdk.TDF;
import java.io.ByteArrayOutputStream;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;
try (var channel = FileChannel.open(tdfPath, StandardOpenOption.READ)) {
TDF.Reader reader = sdk.loadTDF(channel, Config.newTDFReaderConfig());
var output = new ByteArrayOutputStream();
reader.readPayload(output);
System.out.println(output.toString(StandardCharsets.UTF_8));
}
const stream = await client.read({
source: { type: 'buffer', location: encryptedBytes },
});
const text = await new Response(stream).text();
console.log(text);
See Decrypt Options for the full list of configuration options. See TDF Reader for all methods on the reader object. See PolicyObject and Manifest Object for the types returned by reader methods.
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.
Signature
- Go
- Java
func IsValidTdf(reader io.ReadSeeker) (bool, error)
This is a package-level function in the sdk package, not a method on the client.
static boolean SDK.isTDF(SeekableByteChannel channel)
Parameters
| Parameter | Required | Description |
|---|---|---|
reader / channel | Required | The data to inspect. The stream position is reset after the check, so the same reader can be passed directly to LoadTDF. |
Returns
- Go
- Java
(bool, error) — true if the data is a valid TDF, false if it is not. A non-nil error indicates an I/O failure, not an invalid TDF.
boolean — true if the channel contains a valid TDF, false otherwise.
Errors
A non-nil error (Go) or IOException (Java) indicates an I/O failure reading the stream — not that the TDF is invalid. An invalid TDF returns false with no error.
Example
- Go
- Java
import (
"bytes"
"fmt"
"log"
"github.com/opentdf/platform/sdk"
)
reader := bytes.NewReader(data)
valid, err := sdk.IsValidTdf(reader)
if err != nil {
log.Fatal(err)
}
if !valid {
fmt.Println("Not a valid TDF")
return
}
// reader position is reset — pass directly to LoadTDF
tdfReader, err := client.LoadTDF(reader)
import io.opentdf.platform.sdk.Config;
import io.opentdf.platform.sdk.SDK;
import io.opentdf.platform.sdk.TDF;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
try (var channel = FileChannel.open(tdfPath, StandardOpenOption.READ)) {
if (!SDK.isTDF(channel)) {
System.out.println("Not a valid TDF");
return;
}
}
// Open a fresh channel for decryption
try (var channel = FileChannel.open(tdfPath, StandardOpenOption.READ)) {
TDF.Reader reader = sdk.loadTDF(channel, Config.newTDFReaderConfig());
// ...
}
BulkDecrypt
Decrypts multiple TDFs in a single operation, batching KAS key rewrap requests to reduce round-trip overhead.
Signature
func (s SDK) BulkDecrypt(ctx context.Context, opts ...BulkDecryptOption) error
func (s SDK) PrepareBulkDecrypt(ctx context.Context, opts ...BulkDecryptOption) (*BulkDecryptPrepared, error)
Parameters
| Parameter | Required | Description |
|---|---|---|
ctx | Required | Context for the operation, used for cancellation and deadlines. |
opts | Required | At minimum, sdk.WithTDFs(tdfs...) must be provided. |
sdk.WithTDFs accepts one or more *sdk.BulkTDF structs:
| Field | Type | Description |
|---|---|---|
Reader | io.ReadSeeker | Source of the encrypted TDF. |
Writer | io.Writer | Destination for the decrypted plaintext. |
TriggeredObligations | RequiredObligations | Populated after rewrap with obligation FQNs required by this TDF. See Obligations in bulk decrypt. |
Use sdk.WithBulkKasAllowlist([]string{...}) to restrict which KAS endpoints may be contacted.
Returns
BulkDecryptreturnserror. If individual TDFs fail, extract per-TDF errors usingsdk.FromBulkErrors(err).PrepareBulkDecryptreturns(*BulkDecryptPrepared, error). Callprepared.BulkDecrypt(ctx)to execute after inspecting the prepared request.
Errors
Individual TDF failures are collected into a single bulk error. Use sdk.FromBulkErrors(err) to extract a []error slice, indexed by TDF position.
Example
import (
"bytes"
"context"
"fmt"
"github.com/opentdf/platform/sdk"
)
ctx := context.Background()
tdfs := []*sdk.BulkTDF{
{Reader: bytes.NewReader(tdf1Bytes), Writer: &buf1},
{Reader: bytes.NewReader(tdf2Bytes), Writer: &buf2},
}
err := client.BulkDecrypt(ctx,
sdk.WithTDFs(tdfs...),
sdk.WithBulkKasAllowlist([]string{"https://kas.example.com"}),
)
if err != nil {
if errs, ok := sdk.FromBulkErrors(err); ok {
for i, e := range errs {
if e != nil {
fmt.Printf("TDF %d failed: %v\n", i, e)
}
}
}
}
To inspect the prepared request before executing:
import (
"context"
"log"
"github.com/opentdf/platform/sdk"
)
ctx := context.Background()
prepared, err := client.PrepareBulkDecrypt(ctx, sdk.WithTDFs(tdfs...))
if err != nil {
log.Fatal(err)
}
err = prepared.BulkDecrypt(ctx)
TDF Reader
The reader returned by LoadTDF provides methods to read the plaintext payload and inspect manifest and policy data without a second decrypt call, useful for routing, auditing, or display.
Payload
Write the full plaintext to any destination, or read incrementally.
- Go
- Java
- JavaScript
func (r *Reader) WriteTo(w io.Writer) (int64, error)
func (r *Reader) Read(p []byte) (int, error) // implements io.Reader
import "bytes"
var plaintext bytes.Buffer
_, err := tdfReader.WriteTo(&plaintext)
void reader.readPayload(OutputStream out) throws SDK.SegmentSignatureMismatch, IOException
import java.io.ByteArrayOutputStream;
var output = new ByteArrayOutputStream();
reader.readPayload(output);
Use client.read() for direct payload access, or .decrypt() on a TDFReader:
const stream = await reader.decrypt();
const text = await new Response(stream).text();
Metadata
Returns the plaintext metadata string attached at encryption time. Returns nil / empty string if no metadata was set.
- Go
- Java
- JavaScript
func (r *Reader) UnencryptedMetadata() ([]byte, error)
import "fmt"
meta, err := tdfReader.UnencryptedMetadata()
fmt.Printf("Metadata: %s\n", meta)
String reader.getMetadata()
String metadata = reader.getMetadata();
.metadata on the DecoratedStream returned by client.read():
const stream = await client.read({ source: { type: 'buffer', location: encryptedBytes } });
const metadata = await stream.metadata;
Policy
Returns the PolicyObject containing the UUID, data attributes, and dissemination list.
- Go
- Java
- JavaScript
func (r *Reader) Policy() (PolicyObject, error)
import "fmt"
policy, err := tdfReader.Policy()
fmt.Printf("Policy UUID: %s\n", policy.UUID)
PolicyObject reader.readPolicyObject()
import io.opentdf.platform.sdk.PolicyObject;
PolicyObject policy = reader.readPolicyObject();
System.out.println("Policy UUID: " + policy.uuid);
const manifest = await reader.manifest();
// Policy is at manifest.encryptionInformation.policy
Data Attributes
Returns the list of data attribute FQN strings attached to the TDF policy.
- Go
- Java
- JavaScript
func (r *Reader) DataAttributes() ([]string, error)
import "fmt"
attrs, err := tdfReader.DataAttributes()
for _, a := range attrs {
fmt.Println(a)
}
import io.opentdf.platform.sdk.PolicyObject;
PolicyObject policy = reader.readPolicyObject();
for (var attr : policy.body.dataAttributes) {
System.out.println(attr.attribute);
}
async attributes(): Promise<string[]>
const attrs = await reader.attributes();
Manifest
Returns the raw TDF manifest, including encryption information, key access objects, and assertions.
- Go
- Java
- JavaScript
func (r *Reader) Manifest() Manifest
import "fmt"
manifest := tdfReader.Manifest()
fmt.Printf("TDF version: %s\n", manifest.TDFVersion)
Manifest reader.getManifest()
Manifest manifest = reader.getManifest();
System.out.println("TDF version: " + manifest.tdfVersion);
async manifest(): Promise<Manifest>
const manifest = await reader.manifest();
Obligations
Obligations are PDP-to-PEP directives embedded in a TDF that specify additional controls the consuming application must enforce (for example: apply a watermark, prevent download). See Obligations for the policy concept.
Reading obligations from a TDF
Returns the obligation FQNs that the platform requires the consuming application to enforce before granting access to this TDF's payload. If obligations have not yet been fetched, calling this method triggers a rewrap request to the KAS (without decrypting the payload). Subsequent calls return the cached result.
Signature
- Go
- JavaScript
func (r *Reader) Obligations(ctx context.Context) (RequiredObligations, error)
RequiredObligations is a struct with a single field:
type RequiredObligations struct {
FQNs []string
}
obligations(): Promise<RequiredObligations>
RequiredObligations is an object with a single field:
type RequiredObligations = {
fqns: string[];
};
Example
- Go
- JavaScript
import (
"bytes"
"context"
"log"
"github.com/opentdf/platform/sdk"
)
reader, err := client.LoadTDF(bytes.NewReader(ciphertextBytes))
if err != nil {
log.Fatal(err)
}
obligations, err := reader.Obligations(context.Background())
if err != nil {
log.Fatal(err)
}
for _, fqn := range obligations.FQNs {
log.Printf("Required obligation: %s", fqn)
}
import { OpenTDF } from '@opentdf/sdk';
const client = new OpenTDF({ authProvider, platformUrl: 'http://localhost:8080' });
const reader = client.open({ source: ciphertextSource });
const obligations = await reader.obligations();
for (const fqn of obligations.fqns) {
console.log(`Required obligation: ${fqn}`);
}
If the TDF carries no obligations, the FQNs field is an empty slice/array and no error is returned.
Declaring fulfillable obligations
Tell the platform which obligation FQNs your application is prepared to honor. The platform uses this list when deciding whether to surface obligation requirements in a rewrap response.
There are two levels of configuration:
SDK-level default — applies to every LoadTDF / read call:
- Go
- JavaScript
client, err := sdk.New(
"https://platform.example.com",
sdk.WithClientCredentials("client-id", "client-secret", nil),
sdk.WithFulfillableObligationFQNs([]string{
"https://example.com/obl/drm/value/watermarking",
"https://example.com/obl/drm/value/no-download",
}),
)
// Not available at SDK init in JavaScript.
// Use per-read fulfillableObligationFQNs instead (see below).
Per-read override — applies to a single TDF:
- Go
- JavaScript
reader, err := client.LoadTDF(
bytes.NewReader(ciphertextBytes),
sdk.WithTDFFulfillableObligationFQNs([]string{
"https://example.com/obl/drm/value/watermarking",
}),
)
const reader = client.open({
source: ciphertextSource,
fulfillableObligationFQNs: [
'https://example.com/obl/drm/value/watermarking',
],
});
Full workflow — declare what you can fulfill, then read what is required:
- Go
- JavaScript
// 1. Declare fulfillable obligations at SDK init time (see above).
// 2. Load the TDF.
reader, err := client.LoadTDF(bytes.NewReader(ciphertextBytes))
if err != nil {
log.Fatal(err)
}
// 3. Read required obligations (triggers rewrap if not yet populated).
obligations, err := reader.Obligations(context.Background())
if err != nil {
log.Fatal(err)
}
// 4. Enforce each required obligation before processing the payload.
for _, fqn := range obligations.FQNs {
if err := enforce(fqn); err != nil {
log.Fatalf("Cannot fulfill obligation %s: %v", fqn, err)
}
}
// 1. Set fulfillable obligations at open time.
const reader = client.open({
source: ciphertextSource,
fulfillableObligationFQNs: [
'https://example.com/obl/drm/value/watermarking',
'https://example.com/obl/drm/value/no-download',
],
});
// 2. Read required obligations (triggers decrypt if not yet called).
const obligations = await reader.obligations();
// 3. Enforce each required obligation before processing the payload.
for (const fqn of obligations.fqns) {
await enforce(fqn);
}
Obligations in bulk decrypt
When using BulkDecrypt, each BulkTDF result includes a TriggeredObligations field populated after the rewrap phase. Use PrepareBulkDecrypt to inspect obligations before decrypting payloads:
tdfs := []*sdk.BulkTDF{
{Reader: bytes.NewReader(tdf1Bytes), Writer: &buf1},
{Reader: bytes.NewReader(tdf2Bytes), Writer: &buf2},
}
// Prepare: rewrap all TDFs and populate TriggeredObligations (no payload decryption yet).
prepared, err := client.PrepareBulkDecrypt(ctx, sdk.WithTDFs(tdfs...))
if err != nil {
log.Fatal(err)
}
// Inspect obligations before decrypting.
for i, tdf := range tdfs {
if tdf.Error != nil {
log.Printf("TDF %d rewrap failed: %v", i, tdf.Error)
continue
}
for _, fqn := range tdf.TriggeredObligations.FQNs {
log.Printf("TDF %d requires: %s", i, fqn)
}
}
// Decrypt payloads.
if err := prepared.BulkDecrypt(ctx); err != nil {
log.Fatal(err)
}
Type Reference
The following types are returned by or passed to the methods above.
KASInfo
KASInfo is the input type passed to WithKasInformation (Go) or used to build a Config.KASInfo (Java). It identifies a KAS endpoint and the key configuration to use when wrapping the data encryption key.
Fields
| Field | Go | Java | Required | Description |
|---|---|---|---|---|
| URL | URL string | String URL | Required | The KAS endpoint URL. |
| Algorithm | Algorithm string | String Algorithm | Optional | Wrapping key algorithm (e.g. "ec:secp256r1"). Defaults to "rsa:2048" if empty. |
| KID | KID string | String KID | Optional | Key identifier on the KAS, used when the KAS hosts multiple keys. |
| PublicKey | PublicKey string | String PublicKey | Optional | PEM-encoded public key. If empty, the SDK fetches it from the KAS. |
| Default | Default bool | — | Optional | If true, this KAS is used as the default for encrypt calls when no KAS is explicitly specified. |
Example
- Go
- Java
import "github.com/opentdf/platform/sdk"
kas := sdk.KASInfo{
URL: "https://kas.example.com",
Algorithm: "ec:secp256r1",
KID: "my-key-1",
}
manifest, err := client.CreateTDF(&buf, plaintext,
sdk.WithKasInformation(kas),
)
import io.opentdf.platform.sdk.Config;
var kasInfo = new Config.KASInfo();
kasInfo.URL = "https://kas.example.com";
kasInfo.Algorithm = "ec:secp256r1";
kasInfo.KID = "my-key-1";
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfo)
);
PolicyObject
PolicyObject is returned by tdfReader.Policy() (Go) and reader.readPolicyObject() (Java). It contains the decoded access control policy embedded in the TDF — including the UUID, the list of data attribute FQNs, and the dissemination list.
Fields
| Field | Go | Java | Description |
|---|---|---|---|
| UUID | UUID string | String uuid | Unique identifier for this policy. |
| Body.DataAttributes | []attributeObject | List<AttributeObject> | Data attribute FQNs governing access. Each entry has an Attribute (FQN string) and KasURL. |
| Body.Dissem | []string | List<String> | Dissemination list — entity identifiers explicitly granted access. |
Example
- Go
- Java
- JavaScript
import (
"fmt"
"github.com/opentdf/platform/sdk"
)
policy, err := tdfReader.Policy()
if err != nil {
log.Fatal(err)
}
fmt.Println("Policy UUID:", policy.UUID)
for _, attr := range policy.Body.DataAttributes {
fmt.Printf(" Attribute: %s (KAS: %s)\n", attr.Attribute, attr.KasURL)
}
for _, entity := range policy.Body.Dissem {
fmt.Println(" Dissem:", entity)
}
import io.opentdf.platform.sdk.Config;
import io.opentdf.platform.sdk.PolicyObject;
import io.opentdf.platform.sdk.TDF;
PolicyObject policy = reader.readPolicyObject();
System.out.println("Policy UUID: " + policy.uuid);
for (var attr : policy.body.dataAttributes) {
System.out.println(" Attribute: " + attr.attribute + " (KAS: " + attr.kasURL + ")");
}
for (var entity : policy.body.dissem) {
System.out.println(" Dissem: " + entity);
}
The policy is embedded in the manifest. Use reader.manifest() on a TDFReader from client.open():
const manifest = await reader.manifest();
const policy = JSON.parse(atob(manifest.encryptionInformation.policy));
console.log('Policy UUID:', policy.uuid);
console.log('Attributes:', policy.body.dataAttributes);
Manifest Object
The Manifest type is returned by CreateTDF and accessible via tdfReader.Manifest(). It describes the full TDF structure — encryption method, key access configuration, payload metadata, and any attached assertions — without requiring decryption.
Top-Level Fields
| Field | Type | Description |
|---|---|---|
TDFVersion | string | TDF spec version (e.g. "4.3.0"). Serialized as schemaVersion in the manifest JSON. |
EncryptionInformation | EncryptionInformation | Encryption method, key access objects, and integrity information. |
Payload | Payload | Metadata about the encrypted payload. |
Assertions | []Assertion | Cryptographically bound or signed statements attached to the TDF. Empty if none were added at encrypt time. |
EncryptionInformation Fields
| Field | Type | Description |
|---|---|---|
KeyAccessObjs | []KeyAccess | One entry per KAS holding a key grant. Split TDFs have multiple entries. |
Method.Algorithm | string | Encryption algorithm (e.g. "AES-256-GCM"). |
Policy | string | Base64-encoded policy object. Use tdfReader.Policy() to decode. |
KeyAccess Fields
| Field | Type | Description |
|---|---|---|
KasURL | string | URL of the KAS holding the key grant. |
KeyType | string | Key access type ("wrapped" or "remote"). |
SplitID | string | Key split identifier. Entries sharing the same ID share a key segment; different IDs represent independent splits. |
KID | string | Key identifier on the KAS, if set. |
Payload Fields
| Field | Type | Description |
|---|---|---|
MimeType | string | MIME type of the plaintext payload. Empty string if not set at encrypt time. |
IsEncrypted | bool | Always true in a valid TDF. |
Protocol | string | Protocol identifier (e.g. "zip"). |
Example
import (
"fmt"
"github.com/opentdf/platform/sdk"
)
manifest := tdfReader.Manifest()
fmt.Println("TDF version:", manifest.TDFVersion)
fmt.Println("Algorithm:", manifest.EncryptionInformation.Method.Algorithm)
fmt.Println("MIME type:", manifest.Payload.MimeType)
for _, ka := range manifest.EncryptionInformation.KeyAccessObjs {
fmt.Printf("KAS: %s (split: %s)\n", ka.KasURL, ka.SplitID)
}
for _, a := range manifest.Assertions {
fmt.Printf("Assertion %s [%s]: %s\n", a.ID, a.Type, a.Statement.Value)
}
Encrypt Options
The following options can be passed to the encrypt call to control how the TDF is constructed.
Data Attributes
Attach one or more attribute value FQNs to the TDF policy. Access to the data will be governed by these attributes — only entities that hold matching attribute values will be permitted to decrypt.
- Go
- Java
- JavaScript
import "github.com/opentdf/platform/sdk"
manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithDataAttributes(
"https://example.com/attr/clearance/value/executive",
"https://example.com/attr/department/value/engineering",
),
)
Multiple calls to WithDataAttributes are additive.
import io.opentdf.platform.sdk.Config;
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfo),
Config.withDataAttributes(
"https://example.com/attr/clearance/value/executive",
"https://example.com/attr/department/value/engineering"
)
);
sdk.createTDF(inputStream, outputStream, config);
To pass fully resolved Value objects instead of FQN strings, use Config.withDataAttributeValues(Value...).
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
defaultKASEndpoint: 'https://kas.example.com',
attributes: [
'https://example.com/attr/clearance/value/executive',
'https://example.com/attr/department/value/engineering',
],
});
Use validateAttributes to confirm that all FQNs exist on the platform before creating a TDF. This prevents silent policy mismatches.
KAS Configuration
Specify which Key Access Server (KAS) will hold the key grants for this TDF. KAS is the service that enforces access policy by controlling who can unwrap the data encryption key. The KAS URL must be reachable by anyone who needs to decrypt the file.
- Go
- Java
- JavaScript
import "github.com/opentdf/platform/sdk"
manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(
sdk.KASInfo{
URL: "https://kas.example.com",
Algorithm: "ec:secp256r1", // optional; defaults to rsa:2048
},
),
)
import io.opentdf.platform.sdk.Config;
var kasInfo = new Config.KASInfo();
kasInfo.URL = "https://kas.example.com";
kasInfo.Algorithm = "ec:secp256r1"; // optional; defaults to rsa:2048
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfo)
);
sdk.createTDF(inputStream, outputStream, config);
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
defaultKASEndpoint: 'https://kas.example.com',
attributes: ['https://example.com/attr/clearance/value/executive'],
});
The defaultKASEndpoint is used when autoconfigure is false or when no KAS is resolved from the attribute policy.
Autoconfigure
When enabled, the SDK queries the platform's attribute service to automatically determine which KAS endpoints should hold grants for the given data attributes. This eliminates the need to hardcode KAS URLs per encrypt call.
- Go
- Java
- JavaScript
import "github.com/opentdf/platform/sdk"
manifest, err := client.CreateTDF(buf, str,
sdk.WithDataAttributes("https://example.com/attr/clearance/value/executive"),
sdk.WithAutoconfigure(true),
)
import io.opentdf.platform.sdk.Config;
Config.TDFConfig config = Config.newTDFConfig(
Config.withDataAttributes("https://example.com/attr/clearance/value/executive"),
Config.withAutoconfigure(true)
);
sdk.createTDF(inputStream, outputStream, config);
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
attributes: ['https://example.com/attr/clearance/value/executive'],
autoconfigure: true,
});
Autoconfigure requires the SDK to be connected to a platform with the attribute service running. If the platform is unavailable or the attribute has no KAS binding, the encrypt call will fail.
Key Splitting
Split the data encryption key across multiple KAS instances. The key can only be reconstructed when all participating KAS endpoints respond. This is used for multi-party access control or defence-in-depth key custody.
- Go
- Java
- JavaScript
Provide multiple KASInfo entries with distinct URL values. The SDK splits the key across all provided KAS endpoints.
import "github.com/opentdf/platform/sdk"
manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(
sdk.KASInfo{URL: "https://kas-a.example.com"},
sdk.KASInfo{URL: "https://kas-b.example.com"},
),
)
To share a key segment (i.e., not split), provide the same URL more than once or use WithAutoconfigure.
Use Config.withSplitPlan to declare explicit key-split steps. Each step names a KAS URL and a split ID.
import io.opentdf.platform.sdk.Autoconfigure;
import io.opentdf.platform.sdk.Config;
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfoA, kasInfoB),
Config.withSplitPlan(
new Autoconfigure.KeySplitStep("https://kas-a.example.com", "split-1"),
new Autoconfigure.KeySplitStep("https://kas-b.example.com", "split-2")
)
);
sdk.createTDF(inputStream, outputStream, config);
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
splitPlan: [
{ kas: 'https://kas-a.example.com', sid: 'split-1' },
{ kas: 'https://kas-b.example.com', sid: 'split-2' },
],
});
Steps with the same sid share a key segment. Steps with different sid values represent independent splits that are all required for decryption.
Metadata
Attach a plaintext metadata string to the TDF. Metadata is stored unencrypted in the TDF manifest and is readable by anyone with access to the file — do not store sensitive values here. Common uses include audit labels, content identifiers, or application-specific tags.
- Go
- Java
- JavaScript
import "github.com/opentdf/platform/sdk"
manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithMetaData(`{"owner":"alice","sensitivity":"high"}`),
)
import io.opentdf.platform.sdk.Config;
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfo),
Config.withMetaData("{\"owner\":\"alice\",\"sensitivity\":\"high\"}")
);
sdk.createTDF(inputStream, outputStream, config);
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
defaultKASEndpoint: 'https://kas.example.com',
metadata: { owner: 'alice', sensitivity: 'high' },
});
The metadata field is deprecated in the JavaScript SDK. Use assertionConfigs for bound, verifiable metadata instead.
MIME Type
Declare the content type of the plaintext payload. This is stored in the TDF manifest and can help receiving applications handle the decrypted content correctly.
- Go
- Java
- JavaScript
import "github.com/opentdf/platform/sdk"
manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithMimeType("application/pdf"),
)
import io.opentdf.platform.sdk.Config;
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfo),
Config.withMimeType("application/pdf")
);
sdk.createTDF(inputStream, outputStream, config);
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
defaultKASEndpoint: 'https://kas.example.com',
mimeType: 'application/pdf',
});
Segment Size
TDF payloads are split into encrypted segments. The segment size controls the trade-off between memory use and seek performance during decryption. The default is 2 MB. Larger segments reduce overhead for streaming workloads; smaller segments allow faster random access.
- Go
- Java
- JavaScript
import "github.com/opentdf/platform/sdk"
const oneMB = 1 * 1024 * 1024
manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithSegmentSize(oneMB),
)
import io.opentdf.platform.sdk.Config;
int oneMB = 1 * 1024 * 1024;
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfo),
Config.withSegmentSize(oneMB)
);
sdk.createTDF(inputStream, outputStream, config);
const oneMB = 1 * 1024 * 1024;
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
defaultKASEndpoint: 'https://kas.example.com',
windowSize: oneMB,
});
Assertions
Assertions are signed statements attached to the TDF that carry structured metadata — audit labels, handling instructions, content identifiers — and can be cryptographically verified on decrypt.
- Go
- Java
- JavaScript
import "github.com/opentdf/platform/sdk"
assertionCfg := sdk.AssertionConfig{
ID: "assertion-1",
Type: sdk.HandlingAssertion,
Scope: sdk.TrustedDataObj,
AppliesToState: sdk.Unencrypted,
Statement: sdk.Statement{
Format: "application/json",
Value: `{"owner":"alice","sensitivity":"high"}`,
},
}
manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithAssertions(assertionCfg),
)
To sign the assertion so it can be verified on decrypt, add a SigningKey:
import (
"crypto/rsa"
"github.com/opentdf/platform/sdk"
)
assertionCfg := sdk.AssertionConfig{
ID: "assertion-1",
Type: sdk.HandlingAssertion,
Scope: sdk.TrustedDataObj,
AppliesToState: sdk.Unencrypted,
Statement: sdk.Statement{
Format: "application/json",
Value: `{"owner":"alice","sensitivity":"high"}`,
},
SigningKey: sdk.AssertionKey{
Alg: sdk.AssertionKeyAlgRS256,
Key: rsaPrivateKey, // *rsa.PrivateKey
},
}
import io.opentdf.platform.sdk.AssertionConfig;
import io.opentdf.platform.sdk.Config;
var statement = new AssertionConfig.Statement();
statement.format = "application/json";
statement.value = "{\"owner\":\"alice\",\"sensitivity\":\"high\"}";
var assertionCfg = new AssertionConfig();
assertionCfg.id = "assertion-1";
assertionCfg.type = AssertionConfig.Type.HandlingAssertion;
assertionCfg.scope = AssertionConfig.Scope.TrustedDataObj;
assertionCfg.appliesToState = AssertionConfig.AppliesToState.Unencrypted;
assertionCfg.statement = statement;
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfo),
Config.withAssertionConfig(assertionCfg)
);
sdk.createTDF(inputStream, outputStream, config);
To sign the assertion, set assertionCfg.signingKey:
import io.opentdf.platform.sdk.AssertionConfig;
var signingKey = new AssertionConfig.AssertionKey(
AssertionConfig.AssertionKeyAlg.RS256,
rsaPrivateKey // java.security.PrivateKey
);
assertionCfg.signingKey = signingKey;
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
defaultKASEndpoint: 'https://kas.example.com',
assertionConfigs: [
{
id: 'assertion-1',
type: 'HandlingAssertion',
scope: 'TrustedDataObj',
appliesToState: 'Unencrypted',
statement: {
format: 'application/json',
value: '{"owner":"alice","sensitivity":"high"}',
},
},
],
});
To sign the assertion, add a signers map keyed by assertion ID:
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
defaultKASEndpoint: 'https://kas.example.com',
assertionConfigs: [
{
id: 'assertion-1',
type: 'HandlingAssertion',
scope: 'TrustedDataObj',
appliesToState: 'Unencrypted',
statement: { format: 'application/json', value: '{"owner":"alice"}' },
},
],
signers: {
'assertion-1': rsaPrivateKey, // CryptoKey (RS256)
},
});
Signed assertions can be verified on decrypt using Assertion Verification Keys.
Wrapping Key Algorithm
When a TDF is created, the SDK generates a random symmetric Data Encryption Key (DEK) to encrypt the payload. The DEK is then asymmetrically encrypted ("wrapped") using the KAS's public key, so that only the KAS can unwrap it during decryption. This option controls which asymmetric algorithm is used for that wrapping step.
The default is rsa:2048 — RSA with a 2048-bit key. EC (elliptic curve) algorithms such as ec:secp256r1 offer equivalent security with smaller key material stored in the TDF manifest and faster wrap/unwrap operations.
- Go
- Java
- JavaScript
import (
"github.com/opentdf/platform/lib/ocrypto"
"github.com/opentdf/platform/sdk"
)
manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithWrappingKeyAlg(ocrypto.EC256Key),
)
Valid values: ocrypto.RSA2048Key, ocrypto.EC256Key, ocrypto.EC384Key, ocrypto.EC521Key.
import io.opentdf.platform.sdk.Config;
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfo),
Config.WithWrappingKeyAlg(KeyType.EC256Key)
);
sdk.createTDF(inputStream, outputStream, config);
Valid values: KeyType.RSA2048Key, KeyType.EC256Key, KeyType.EC384Key, KeyType.EC521Key.
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
defaultKASEndpoint: 'https://kas.example.com',
wrappingKeyAlgorithm: 'ec:secp256r1',
});
Valid values: 'rsa:2048', 'rsa:4096', 'ec:secp256r1', 'ec:secp384r1', 'ec:secp521r1'.
The KAS must support the chosen algorithm. Check your platform's KAS public key endpoint to confirm which algorithms are available.
TDF Spec Version
Target a specific TDF specification version. Use this to maintain compatibility with older decoders or to opt in to newer features. The current default is 4.3.0.
- Go
- Java
- JavaScript
import "github.com/opentdf/platform/sdk"
manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithTargetMode("4.3.0"),
)
import io.opentdf.platform.sdk.Config;
Config.TDFConfig config = Config.newTDFConfig(
Config.withKasInformation(kasInfo),
Config.withTargetMode("4.3.0")
);
sdk.createTDF(inputStream, outputStream, config);
const tdf = await client.createTDF({
source: { type: 'buffer', location: data },
defaultKASEndpoint: 'https://kas.example.com',
tdfSpecVersion: '4.3.0',
});
Valid values: '4.2.2', '4.3.0'.
Decrypt Options
The following options can be passed to the decrypt call to control how the TDF is opened and validated.
KAS Allowlist
Restrict decryption to only contact KAS endpoints on an explicit allowlist. If the TDF references a KAS not on the list, decryption will fail. This is a security control to prevent credential exfiltration to a rogue KAS.
- Go
- Java
- JavaScript
import (
"bytes"
"github.com/opentdf/platform/sdk"
)
tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithKasAllowlist([]string{
"https://kas.example.com",
"https://kas-backup.example.com",
}),
)
import io.opentdf.platform.sdk.Config;
import io.opentdf.platform.sdk.TDF;
Config.TDFReaderConfig readerConfig = Config.newTDFReaderConfig(
Config.WithKasAllowlist(
"https://kas.example.com",
"https://kas-backup.example.com"
)
);
TDF.Reader reader = sdk.loadTDF(fileChannel, readerConfig);
const plaintext = await client.read({
source: { type: 'buffer', location: encryptedBytes },
allowedKASEndpoints: [
'https://kas.example.com',
'https://kas-backup.example.com',
],
});
Ignore KAS Allowlist
Disable the KAS allowlist check entirely. The SDK will contact any KAS referenced in the TDF manifest without restriction.
Only use this in controlled environments (e.g., integration tests, airgapped deployments where all KAS endpoints are trusted). In production, the allowlist is an important defence against credential forwarding attacks.
- Go
- Java
- JavaScript
import (
"bytes"
"github.com/opentdf/platform/sdk"
)
tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithIgnoreAllowlist(true),
)
import io.opentdf.platform.sdk.Config;
import io.opentdf.platform.sdk.TDF;
Config.TDFReaderConfig readerConfig = Config.newTDFReaderConfig(
Config.WithIgnoreKasAllowlist(true)
);
TDF.Reader reader = sdk.loadTDF(fileChannel, readerConfig);
const plaintext = await client.read({
source: { type: 'buffer', location: encryptedBytes },
ignoreAllowlist: true,
});
Assertion Verification Keys
Provide public keys used to verify signed assertions embedded in the TDF. If an assertion was signed during encryption, you must supply the corresponding verification key here or verification will fail.
- Go
- Java
- JavaScript
import (
"bytes"
"github.com/opentdf/platform/sdk"
)
verificationKeys := sdk.AssertionVerificationKeys{
Keys: map[string]sdk.AssertionKey{
"assertion-1": {
Alg: sdk.AssertionKeyAlgRS256,
Key: rsaPublicKey, // *rsa.PublicKey
},
},
}
tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithAssertionVerificationKeys(verificationKeys),
)
import io.opentdf.platform.sdk.AssertionConfig;
import io.opentdf.platform.sdk.Config;
import io.opentdf.platform.sdk.TDF;
import java.util.Map;
var assertionKey = new AssertionConfig.AssertionKey(
AssertionConfig.AssertionKeyAlg.RS256,
rsaPublicKey
);
var verificationKeys = new Config.AssertionVerificationKeys();
verificationKeys.keys = Map.of("assertion-1", assertionKey);
Config.TDFReaderConfig readerConfig = Config.newTDFReaderConfig(
Config.withAssertionVerificationKeys(verificationKeys)
);
TDF.Reader reader = sdk.loadTDF(fileChannel, readerConfig);
const plaintext = await client.read({
source: { type: 'buffer', location: encryptedBytes },
assertionVerificationKeys: {
keys: {
'assertion-1': {
alg: 'RS256',
key: rsaPublicKey, // CryptoKey
},
},
},
});
Disable Assertion Verification
Skip cryptographic verification of all assertions in the TDF. The assertions will still be read and returned, but their signatures will not be checked.
Disabling assertion verification removes a tamper-detection layer. Only use this when you have explicitly verified the TDF's integrity through another mechanism.
- Go
- Java
- JavaScript
import (
"bytes"
"github.com/opentdf/platform/sdk"
)
tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithDisableAssertionVerification(true),
)
import io.opentdf.platform.sdk.Config;
import io.opentdf.platform.sdk.TDF;
Config.TDFReaderConfig readerConfig = Config.newTDFReaderConfig(
Config.withDisableAssertionVerification(true)
);
TDF.Reader reader = sdk.loadTDF(fileChannel, readerConfig);
const plaintext = await client.read({
source: { type: 'buffer', location: encryptedBytes },
noVerify: true,
});
Session Key Type
During decryption, the SDK generates a short-lived (ephemeral) asymmetric key pair and sends the public half to the KAS. The KAS uses it to securely return the unwrapped Data Encryption Key back to the SDK. This option controls the algorithm used for that ephemeral key pair.
The default is rsa:2048. Use ec:secp256r1 (or another EC variant) for smaller messages and faster key exchange. Must match an algorithm supported by the KAS.
- Go
- Java
- JavaScript
import (
"bytes"
"github.com/opentdf/platform/lib/ocrypto"
"github.com/opentdf/platform/sdk"
)
tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithSessionKeyType(ocrypto.EC256Key),
)
import io.opentdf.platform.sdk.Config;
import io.opentdf.platform.sdk.TDF;
Config.TDFReaderConfig readerConfig = Config.newTDFReaderConfig(
Config.WithSessionKeyType(KeyType.EC256Key)
);
TDF.Reader reader = sdk.loadTDF(fileChannel, readerConfig);
const plaintext = await client.read({
source: { type: 'buffer', location: encryptedBytes },
wrappingKeyAlgorithm: 'ec:secp256r1',
});
Fulfillable Obligations
Declare which obligation FQNs the calling application is prepared to fulfil. The platform may attach obligations to an access decision — if an obligation is not declared as fulfillable, decryption may be denied.
- Go
- Java
- JavaScript
import (
"bytes"
"github.com/opentdf/platform/sdk"
)
tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithTDFFulfillableObligationFQNs([]string{
"https://example.com/obl/audit/value/log",
"https://example.com/obl/watermark/value/apply",
}),
)
Fulfillable obligations are not yet supported in the Java SDK TDF reader config. Configure them at the SDK level via SDKBuilder if available, or handle obligation enforcement in your application logic after reading the policy object.
const plaintext = await client.read({
source: { type: 'buffer', location: encryptedBytes },
fulfillableObligationFQNs: [
'https://example.com/obl/audit/value/log',
'https://example.com/obl/watermark/value/apply',
],
});
Max Manifest Size
Limit the maximum size of the TDF manifest that the SDK will parse. This is a defence against malformed or malicious TDFs with abnormally large manifests.
- Go
- Java
- JavaScript
import (
"bytes"
"github.com/opentdf/platform/sdk"
)
tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithMaxManifestSize(1 * 1024 * 1024), // 1 MB limit
)
Not available in the Java SDK. The manifest size limit is not configurable; the SDK accepts any well-formed manifest.
Not available in the JavaScript SDK. The manifest size limit is not configurable; the SDK accepts any well-formed manifest.
Experimental: Streaming Writer
github.com/opentdf/platform/sdk/experimental/tdf is experimental — its API may change or be removed without notice. Do not use it in production without accepting that risk.
The standard CreateTDF API takes a single io.ReadSeeker and writes the TDF in one call. For most use cases this is the right choice.
The experimental tdf.NewWriter API uses a segment-based model where you write indexed segments — potentially out of order — and call Finalize when done. Use it when:
- Encrypting large files in chunks: process data segment-by-segment without loading it all into memory
- Out-of-order assembly: segments arrive from parallel workers or a network stream and must be assembled in order
- You already have the KAS public key:
NewWritertakes a*policy.SimpleKasKeydirectly and does not require a fullsdk.SDKclient
TDFs produced by NewWriter are fully compatible with the standard LoadTDF.
Import
- Go
- Java
- JavaScript
import "github.com/opentdf/platform/sdk/experimental/tdf"
Not available in the Java SDK.
Not available in the JavaScript SDK.
Basic usage
- Go
- Java
- JavaScript
import (
"context"
"log"
"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/sdk/experimental/tdf"
)
ctx := context.Background()
writer, err := tdf.NewWriter(ctx)
if err != nil {
log.Fatal(err)
}
segResult, err := writer.WriteSegment(ctx, 0, []byte("first chunk"))
if err != nil {
log.Fatal(err)
}
segResult, err = writer.WriteSegment(ctx, 1, []byte("second chunk"))
if err != nil {
log.Fatal(err)
}
_ = segResult // stream or store segResult.TDFData before calling Finalize
// kasPublicKeyPEM holds the PEM-encoded RSA public key for your KAS instance.
// Load this from a file, environment variable, or configuration source.
kasPublicKeyPEM := `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`
kasKey := &policy.SimpleKasKey{
KasUri: "https://kas.example.com",
PublicKey: &policy.SimpleKasPublicKey{
Algorithm: policy.Algorithm_ALGORITHM_RSA_2048,
Kid: "my-key-id",
Pem: kasPublicKeyPEM,
},
}
result, err := writer.Finalize(ctx,
tdf.WithDefaultKAS(kasKey),
tdf.WithPayloadMimeType("text/plain"),
)
if err != nil {
log.Fatal(err)
}
Not available in the Java SDK.
Not available in the JavaScript SDK.
Out-of-order segments
Segments are written by index and assembled in index order at Finalize time regardless of write order:
- Go
- Java
- JavaScript
chunk0 := []byte("segment 0 data")
chunk1 := []byte("segment 1 data")
chunk2 := []byte("segment 2 data")
// Segments arrive from parallel workers — write in any order
segResult, err := writer.WriteSegment(ctx, 2, chunk2)
if err != nil {
log.Fatal(err)
}
segResult, err = writer.WriteSegment(ctx, 0, chunk0)
if err != nil {
log.Fatal(err)
}
segResult, err = writer.WriteSegment(ctx, 1, chunk1)
if err != nil {
log.Fatal(err)
}
_ = segResult // stream or store segResult.TDFData before calling Finalize
// Finalize assembles them as segment 0, 1, 2
result, err := writer.Finalize(ctx, tdf.WithDefaultKAS(kasKey))
if err != nil {
log.Fatal(err)
}
Not available in the Java SDK.
Not available in the JavaScript SDK.
Key options
| Option | Description |
|---|---|
tdf.WithIntegrityAlgorithm(algorithm) | Root integrity algorithm. Can be tdf.HS256 (default) or tdf.GMAC. |
tdf.WithSegmentIntegrityAlgorithm(tdf.GMAC) | Per-segment hash algorithm — GMAC is faster for many small segments |
tdf.WithAttributeValues(values) | Data attribute *policy.Value slice for ABAC |
tdf.WithDefaultKAS(kasKey) | Default KAS for key wrapping (Finalize option) |
tdf.WithEncryptedMetadata(string) | Metadata encrypted inside the key access objects |
tdf.WithPayloadMimeType(string) | MIME type of the payload content |
tdf.WithSegments([]int) | Restrict Finalize to a specific contiguous segment prefix |
Inspecting the manifest before finalization
GetManifest returns a snapshot of the TDF manifest at any point during the writer's lifecycle:
- Before
Finalize: returns a stub manifest built from the current writer state — segments written so far, algorithm selections, initial attributes and KAS if set onNewWriter. Use it to pre-calculate the expected manifest size or inspect the anticipated structure. This stub is not suitable for verification. - After
Finalize: returns the finalized manifest (identical toresult.ManifestfromFinalize).
- Go
- Java
- JavaScript
import "encoding/json"
// After writing segments but before Finalize, inspect the expected manifest size.
stub, err := writer.GetManifest(ctx)
if err != nil {
log.Fatal(err)
}
manifestJSON, err := json.Marshal(stub)
if err != nil {
log.Fatal(err)
}
expectedManifestSize := len(manifestJSON)
// Use expectedManifestSize to pre-allocate buffers, set Content-Length headers, etc.
Not available in the Java SDK.
Not available in the JavaScript SDK.
Result types
SegmentResult
Returned by each WriteSegment call.
| Field | Type | Description |
|---|---|---|
TDFData | io.Reader | Reader for the encrypted segment bytes (nonce + ciphertext + ZIP structures). Stream or store this before calling Finalize. |
Index | int | The segment index that was written. |
Hash | string | Base64-encoded integrity hash for this segment. |
PlaintextSize | int64 | Size of the original (unencrypted) data in bytes. |
EncryptedSize | int64 | Size of the encrypted data in bytes. |
FinalizeResult
Returned by Finalize.
| Field | Type | Description |
|---|---|---|
Data | []byte | Manifest JSON and ZIP footer bytes. Does not contain the encrypted payload — segment data is returned by each WriteSegment call via TDFData. |
Manifest | *Manifest | The complete TDF manifest including key access objects, integrity information, and assertions. |
TotalSegments | int | Number of segments written. |
TotalSize | int64 | Total plaintext bytes across all segments. |
EncryptedSize | int64 | Total encrypted bytes across all segments. |
For the full API reference, see the pkg.go.dev documentation.