Skip to main content

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:


Quick Start

Initialize a client and run an end-to-end encrypt/decrypt in one block.

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"

CreateTDF

Encrypts a plaintext payload and writes a TDF to the provided output destination.

Signature

func (s SDK) CreateTDF(writer io.Writer, reader io.ReadSeeker, opts ...TDFOption) (*TDFObject, error)

Parameters

ParameterRequiredDescription
Output destinationRequiredWhere the encrypted TDF bytes are written.
Plaintext sourceRequiredThe data to encrypt. See the signature tab for your language for the expected type.
ConfigurationRequired*Encryption options. A KAS endpoint must be specified unless autoconfigure is enabled. See Encrypt Options.

Returns

(*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.

Errors

ErrorCause
KAS unreachableThe KAS could not be contacted to wrap the encryption key.
No KAS configuredNo KAS was provided and autoconfigure is disabled or could not resolve one.
Write failureAn I/O error occurred writing to out.

Example

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)
}

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

func (s SDK) LoadTDF(reader io.ReadSeeker, opts ...TDFReaderOption) (*Reader, error)

Parameters

ParameterRequiredDescription
Encrypted TDF sourceRequiredThe TDF to open. See the signature tab for your language for the expected type. For JavaScript, source accepts a buffer (Uint8Array) or stream (ReadableStream).
ConfigurationOptionalDecryption options. Defaults allow any KAS endpoint. See Decrypt Options.

Returns

(*Reader, error) — A TDF reader object. See TDF Reader for available methods.

Errors

ErrorCause
Invalid TDFThe input is not a valid TDF. Use IsValidTdf to pre-validate.
KAS unreachableThe KAS referenced in the manifest cannot be contacted.
Access deniedThe caller's credentials do not satisfy the TDF policy.
KAS not allowlistedThe TDF references a KAS endpoint not on the allowlist.

Example

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())

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

func IsValidTdf(reader io.ReadSeeker) (bool, error)

This is a package-level function in the sdk package, not a method on the client.

Parameters

ParameterRequiredDescription
reader / channelRequiredThe data to inspect. The stream position is reset after the check, so the same reader can be passed directly to LoadTDF.

Returns

(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.

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

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)

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

ParameterRequiredDescription
ctxRequiredContext for the operation, used for cancellation and deadlines.
optsRequiredAt minimum, sdk.WithTDFs(tdfs...) must be provided.

sdk.WithTDFs accepts one or more *sdk.BulkTDF structs:

FieldTypeDescription
Readerio.ReadSeekerSource of the encrypted TDF.
Writerio.WriterDestination for the decrypted plaintext.
TriggeredObligationsRequiredObligationsPopulated 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

  • BulkDecrypt returns error. If individual TDFs fail, extract per-TDF errors using sdk.FromBulkErrors(err).
  • PrepareBulkDecrypt returns (*BulkDecryptPrepared, error). Call prepared.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.

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)

Metadata

Returns the plaintext metadata string attached at encryption time. Returns nil / empty string if no metadata was set.

func (r *Reader) UnencryptedMetadata() ([]byte, error)
import "fmt"

meta, err := tdfReader.UnencryptedMetadata()
fmt.Printf("Metadata: %s\n", meta)

Policy

Returns the PolicyObject containing the UUID, data attributes, and dissemination list.

func (r *Reader) Policy() (PolicyObject, error)
import "fmt"

policy, err := tdfReader.Policy()
fmt.Printf("Policy UUID: %s\n", policy.UUID)

Data Attributes

Returns the list of data attribute FQN strings attached to the TDF policy.

func (r *Reader) DataAttributes() ([]string, error)
import "fmt"

attrs, err := tdfReader.DataAttributes()
for _, a := range attrs {
fmt.Println(a)
}

Manifest

Returns the raw TDF manifest, including encryption information, key access objects, and assertions.

func (r *Reader) Manifest() Manifest
import "fmt"

manifest := tdfReader.Manifest()
fmt.Printf("TDF version: %s\n", manifest.TDFVersion)

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

func (r *Reader) Obligations(ctx context.Context) (RequiredObligations, error)

RequiredObligations is a struct with a single field:

type RequiredObligations struct {
FQNs []string
}

Example

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)
}

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:

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",
}),
)

Per-read override — applies to a single TDF:

reader, err := client.LoadTDF(
bytes.NewReader(ciphertextBytes),
sdk.WithTDFFulfillableObligationFQNs([]string{
"https://example.com/obl/drm/value/watermarking",
}),
)

Full workflow — declare what you can fulfill, then read what is required:

// 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)
}
}

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

FieldGoJavaRequiredDescription
URLURL stringString URLRequiredThe KAS endpoint URL.
AlgorithmAlgorithm stringString AlgorithmOptionalWrapping key algorithm (e.g. "ec:secp256r1"). Defaults to "rsa:2048" if empty.
KIDKID stringString KIDOptionalKey identifier on the KAS, used when the KAS hosts multiple keys.
PublicKeyPublicKey stringString PublicKeyOptionalPEM-encoded public key. If empty, the SDK fetches it from the KAS.
DefaultDefault boolOptionalIf true, this KAS is used as the default for encrypt calls when no KAS is explicitly specified.

Example

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),
)

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

FieldGoJavaDescription
UUIDUUID stringString uuidUnique identifier for this policy.
Body.DataAttributes[]attributeObjectList<AttributeObject>Data attribute FQNs governing access. Each entry has an Attribute (FQN string) and KasURL.
Body.Dissem[]stringList<String>Dissemination list — entity identifiers explicitly granted access.

Example

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)
}

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

FieldTypeDescription
TDFVersionstringTDF spec version (e.g. "4.3.0"). Serialized as schemaVersion in the manifest JSON.
EncryptionInformationEncryptionInformationEncryption method, key access objects, and integrity information.
PayloadPayloadMetadata about the encrypted payload.
Assertions[]AssertionCryptographically bound or signed statements attached to the TDF. Empty if none were added at encrypt time.

EncryptionInformation Fields

FieldTypeDescription
KeyAccessObjs[]KeyAccessOne entry per KAS holding a key grant. Split TDFs have multiple entries.
Method.AlgorithmstringEncryption algorithm (e.g. "AES-256-GCM").
PolicystringBase64-encoded policy object. Use tdfReader.Policy() to decode.

KeyAccess Fields

FieldTypeDescription
KasURLstringURL of the KAS holding the key grant.
KeyTypestringKey access type ("wrapped" or "remote").
SplitIDstringKey split identifier. Entries sharing the same ID share a key segment; different IDs represent independent splits.
KIDstringKey identifier on the KAS, if set.

Payload Fields

FieldTypeDescription
MimeTypestringMIME type of the plaintext payload. Empty string if not set at encrypt time.
IsEncryptedboolAlways true in a valid TDF.
ProtocolstringProtocol 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.

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.

Validating attributes before encrypt

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.

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
},
),
)

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.

import "github.com/opentdf/platform/sdk"

manifest, err := client.CreateTDF(buf, str,
sdk.WithDataAttributes("https://example.com/attr/clearance/value/executive"),
sdk.WithAutoconfigure(true),
)
note

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.

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.


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.

import "github.com/opentdf/platform/sdk"

manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithMetaData(`{"owner":"alice","sensitivity":"high"}`),
)

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.

import "github.com/opentdf/platform/sdk"

manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithMimeType("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.

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),
)

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.

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
},
}

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.

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.

KAS support required

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.

import "github.com/opentdf/platform/sdk"

manifest, err := client.CreateTDF(buf, str,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithTargetMode("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.

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",
}),
)

Ignore KAS Allowlist

Disable the KAS allowlist check entirely. The SDK will contact any KAS referenced in the TDF manifest without restriction.

Security consideration

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.

import (
"bytes"

"github.com/opentdf/platform/sdk"
)

tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithIgnoreAllowlist(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.

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),
)

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.

warning

Disabling assertion verification removes a tamper-detection layer. Only use this when you have explicitly verified the TDF's integrity through another mechanism.

import (
"bytes"

"github.com/opentdf/platform/sdk"
)

tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithDisableAssertionVerification(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.

import (
"bytes"

"github.com/opentdf/platform/lib/ocrypto"
"github.com/opentdf/platform/sdk"
)

tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithSessionKeyType(ocrypto.EC256Key),
)

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.

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",
}),
)

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.

import (
"bytes"

"github.com/opentdf/platform/sdk"
)

tdfReader, err := client.LoadTDF(
bytes.NewReader(encryptedBytes),
sdk.WithMaxManifestSize(1 * 1024 * 1024), // 1 MB limit
)

Experimental: Streaming Writer

Experimental API

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: NewWriter takes a *policy.SimpleKasKey directly and does not require a full sdk.SDK client

TDFs produced by NewWriter are fully compatible with the standard LoadTDF.

Import

import "github.com/opentdf/platform/sdk/experimental/tdf"

Basic usage

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)
}

Out-of-order segments

Segments are written by index and assembled in index order at Finalize time regardless of write order:

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)
}

Key options

OptionDescription
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 on NewWriter. 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 to result.Manifest from Finalize).
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.

Result types

SegmentResult

Returned by each WriteSegment call.

FieldTypeDescription
TDFDataio.ReaderReader for the encrypted segment bytes (nonce + ciphertext + ZIP structures). Stream or store this before calling Finalize.
IndexintThe segment index that was written.
HashstringBase64-encoded integrity hash for this segment.
PlaintextSizeint64Size of the original (unencrypted) data in bytes.
EncryptedSizeint64Size of the encrypted data in bytes.

FinalizeResult

Returned by Finalize.

FieldTypeDescription
Data[]byteManifest JSON and ZIP footer bytes. Does not contain the encrypted payload — segment data is returned by each WriteSegment call via TDFData.
Manifest*ManifestThe complete TDF manifest including key access objects, integrity information, and assertions.
TotalSegmentsintNumber of segments written.
TotalSizeint64Total plaintext bytes across all segments.
EncryptedSizeint64Total encrypted bytes across all segments.

For the full API reference, see the pkg.go.dev documentation.