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:


Setup

JavaScript examples use client credentials for simplicity

The JavaScript SDK is designed for browser applications. The examples on this page use clientCredentialsTokenProvider because it's self-contained and easy to follow, but it requires a client secret and must not be used in browser code.

In production browser apps, complete an OIDC login flow to obtain a refresh token, then use refreshTokenProvider(). See the Authentication Decision Guide for help choosing the right method.

Initialize an SDK client — all examples on this page require it. See Authentication for full details including DPoP key binding. Here is an end-to-end encrypt/decrypt to get started:

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

ParameterTypeDescription
writerio.WriterWhere the encrypted TDF bytes are written.
readerio.ReadSeekerThe plaintext data to encrypt.
opts...TDFOptionEncryption options. At minimum, a KAS endpoint must be specified (e.g., sdk.WithKasInformation()). See Encrypt Options.

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.

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 the output destination.

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

ParameterTypeDescription
readerio.ReadSeekerThe encrypted TDF to open.
opts...TDFReaderOptionOptional decryption settings. See Decrypt Options.

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.

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.

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.

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)

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.


BulkDecrypt

Decrypts multiple TDFs in a single operation, batching KAS key rewrap requests to reduce round-trip overhead.

Why use BulkDecrypt?

Calling LoadTDF in a loop issues a separate KAS rewrap request per TDF. BulkDecrypt batches all rewrap requests by KAS endpoint so that N TDFs targeting the same KAS require only one round-trip instead of N. Use it whenever you need to decrypt more than a handful of TDFs.

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.

Options

OptionDescription
WithTDFs(tdfs...)(Required) The TDFs to decrypt.
WithBulkKasAllowlist([]string{...})Restrict which KAS endpoints may be contacted. Only listed URLs are allowed; requests to unlisted KAS endpoints fail with an error per TDF. If omitted and a KAS registry is configured, the SDK uses the registry as the allowlist.
WithBulkIgnoreAllowlist(true)Bypass KAS allowlist checks entirely. A warning is logged for each KAS URL contacted. Use only for testing or fully trusted environments.
WithTDFType(tdfType)Specify the TDF format explicitly. Currently only sdk.Standard (TDF3 / ZIP-based) is supported. If omitted, the SDK auto-detects the format from each reader.
WithTDF3DecryptOptions(opts...)Pass additional reader options (e.g., session key, assertion verification) through to each TDF3 decryptor. These are the same options accepted by LoadTDF.

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

PrepareBulkDecrypt splits the operation into two phases — rewrap (network) and decrypt (CPU) — so you can act between them:

  • Inspect obligations — after PrepareBulkDecrypt, each BulkTDF has its TriggeredObligations populated. Check these before decrypting payloads (see Obligations in bulk decrypt).
  • Conditional decryption — skip TDFs that don't meet your criteria without wasting CPU on decryption.
  • Monitoring — separate the network-bound rewrap phase from the CPU-bound decryption phase for progress reporting or latency attribution.
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)

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

BulkDecrypt does not fail fast — it attempts every TDF and collects individual failures into a single BulkErrors value. Use sdk.FromBulkErrors(err) to extract the underlying []error slice:

  • Each element corresponds to a TDF by position in the original WithTDFs call.
  • nil entries indicate success; non-nil entries describe why that TDF failed.
  • Individual errors are also written to the Error field on each *BulkTDF struct, so you can inspect failures directly on the input slice after the call returns.

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

Session Encryption

The session encryption key is used to encrypt responses from the Key Access Server (KAS) back to your client during the rewrap flow. This is an advanced option for scenarios where you need to control the encryption key lifecycle — for example, reusing the same key across multiple TDF operations in a session.

If not provided, the SDK generates a session key automatically.

import (
"crypto/rand"
"crypto/rsa"
)

// Generate or load your own RSA key pair
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}

client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("id", "secret", nil),
sdk.WithSessionEncryptionRSA(privateKey),
)

Assertions

Assertions are signed statements attached to a TDF that carry structured metadata alongside the encrypted payload. Use them to embed handling instructions, audit labels, retention policies, or any application-specific metadata that should travel with the data and be cryptographically verifiable on decrypt.

Every assertion is bound to the TDF's data encryption key (DEK) by default, preventing it from being removed or copied to a different TDF. You can optionally sign assertions with your own RSA key for independent verification.

Assertion Types

ConstantValuePurpose
HandlingAssertion"handling"Data handling and processing instructions — e.g., retention policies, access restrictions, DLP rules.
BaseAssertion"other"General-purpose metadata — e.g., audit labels, content identifiers, provenance records.

Scopes

ConstantValueDescription
TrustedDataObjScope"tdo"Assertion applies to the entire TDF object.
PayloadScope"payload"Assertion applies only to the encrypted payload.

AppliesToState

ConstantValueWhen to process
Encrypted"encrypted"Process before decryption — useful for access-control checks, audit logging, or routing decisions that don't require seeing the plaintext.
Unencrypted"unencrypted"Process after decryption — useful for content filtering, retention enforcement, or any logic that inspects the plaintext.

Statement

Each assertion carries a Statement with three fields:

FieldDescription
FormatPayload encoding format (e.g., "application/json", "text/plain").
SchemaSchema identifier for the value (e.g., "retention-policy-v1").
ValueThe assertion payload as a string — typically a JSON object.

Signing and Verification

By default, every assertion is bound to the TDF using the DEK with HMAC-SHA256 (HS256). This binding ensures the assertion cannot be tampered with or detached, but verification requires the DEK — which means only entities that can decrypt the TDF can verify the binding.

To enable independent verification (where a verifier doesn't need the DEK), sign the assertion with an RSA private key (RS256) at encrypt time and provide the corresponding public key at decrypt time.

AlgorithmConstantKey typeUse case
HMAC-SHA256AssertionKeyAlgHS256Symmetric (byte slice / DEK)Default binding — tamper detection for anyone who can decrypt.
RSA-SHA256AssertionKeyAlgRS256RSA key pair or crypto.SignerIndependent verification — any holder of the public key can verify.

System Metadata

The SDKs can automatically attach a system metadata assertion containing information about the environment that created the TDF — spec version, creation timestamp, SDK version, OS, and architecture. This is useful for audit trails and debugging without requiring application-level code.

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

manifest, err := client.CreateTDF(&buf, plaintext,
sdk.WithKasInformation(sdk.KASInfo{URL: kasURL}),
sdk.WithSystemMetadataAssertion(),
)

To inspect or customize the config before passing it to WithAssertions:

cfg, err := sdk.GetSystemMetadataAssertionConfig()
// cfg.Statement.Value contains JSON like:
// {"tdf_spec_version":"4.3.0","creation_date":"2026-03-31T12:00:00Z",
// "operating_system":"linux","sdk_version":"Go-0.3.4",
// "go_version":"go1.23.0","architecture":"amd64"}

The system metadata assertion uses ID "system-metadata", schema "system-metadata-v1", type BaseAssertion, scope PayloadScope, and state Unencrypted.

End-to-End Example

Create a signed assertion at encrypt time, then verify it at decrypt time.

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"io"
"log"

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

// Generate an RSA key pair for assertion signing.
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}

// --- Encrypt with a signed assertion ---

assertionCfg := sdk.AssertionConfig{
ID: "handling-1",
Type: sdk.HandlingAssertion,
Scope: sdk.TrustedDataObjScope,
AppliesToState: sdk.Unencrypted,
Statement: sdk.Statement{
Format: "application/json",
Schema: "retention-policy-v1",
Value: `{"retain_until":"2027-01-01","classification":"internal"}`,
},
SigningKey: sdk.AssertionKey{
Alg: sdk.AssertionKeyAlgRS256,
Key: rsaKey,
},
}

var buf bytes.Buffer
_, err = client.CreateTDF(&buf, plaintext,
sdk.WithKasInformation(sdk.KASInfo{URL: kasURL}),
sdk.WithAssertions(assertionCfg),
)
if err != nil {
log.Fatal(err)
}

// --- Decrypt and verify the assertion ---

verificationKeys := sdk.AssertionVerificationKeys{
Keys: map[string]sdk.AssertionKey{
"handling-1": {
Alg: sdk.AssertionKeyAlgRS256,
Key: &rsaKey.PublicKey,
},
},
}

tdfReader, err := client.LoadTDF(
bytes.NewReader(buf.Bytes()),
sdk.WithAssertionVerificationKeys(verificationKeys),
)
if err != nil {
log.Fatal(err) // Verification failure surfaces here.
}

decrypted, err := io.ReadAll(tdfReader)
if err != nil {
log.Fatal(err)
}

// Access assertions from the manifest.
for _, a := range tdfReader.Manifest().Assertions {
log.Printf("Assertion %s [%s]: %s", a.ID, a.Type, a.Statement.Value)
}

See the Encrypt Options and Decrypt Options sections for the full option reference, including per-option details for WithAssertions and WithAssertionVerificationKeys.


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

AssertionConfig

AssertionConfig is the input type passed to WithAssertions when creating a TDF. It defines the assertion content, classification, and optional signing key.

Fields

FieldGoJavaRequiredDescription
IDID stringString idYesUnique identifier for the assertion. Used to look up verification keys on decrypt.
TypeType AssertionTypeType typeYesHandlingAssertion ("handling") or BaseAssertion ("other").
ScopeScope ScopeScope scopeYesTrustedDataObjScope ("tdo") or PayloadScope ("payload").
AppliesToStateAppliesToState AppliesToStateAppliesToState appliesToStateYesEncrypted or Unencrypted.
StatementStatement StatementStatement statementNoThe assertion content. See Statement (type).
SigningKeySigningKey AssertionKeyAssertionKey signingKeyNoCustom signing key. If empty, the assertion is bound with the DEK using HS256. See AssertionKey.

Assertion

Assertion is the signed form stored in the TDF manifest. Accessible via tdfReader.Manifest().Assertions after decryption.

Fields

FieldTypeDescription
IDstringAssertion identifier (matches the AssertionConfig.ID used at encrypt time).
TypeAssertionType"handling" or "other".
ScopeScope"tdo" or "payload".
AppliesToStateAppliesToState"encrypted" or "unencrypted".
StatementStatementThe assertion content.
BindingBindingCryptographic binding — Method ("jws") and Signature (JWS token).

Statement (type)

Fields

FieldTypeDescription
FormatstringPayload encoding format (e.g., "application/json", "text/plain").
SchemastringSchema identifier for the value (e.g., "retention-policy-v1").
ValuestringThe assertion payload — typically a JSON object serialized as a string.

AssertionKey

Fields

FieldTypeDescription
AlgAssertionKeyAlgAssertionKeyAlgRS256 ("RS256") for RSA-SHA256, or AssertionKeyAlgHS256 ("HS256") for HMAC-SHA256.
Keyinterface{} / ObjectFor RS256: an RSA private key (encrypt) or public key (verify). For HS256: a byte slice / shared secret. Supports crypto.Signer for hardware-backed keys.

AssertionVerificationKeys

Passed to WithAssertionVerificationKeys when loading a TDF. Maps assertion IDs to the public keys used to verify their signatures.

Fields

FieldTypeDescription
DefaultKeyAssertionKeyFallback key used when no ID-specific key is found.
Keysmap[string]AssertionKeyMap of assertion ID to verification key.

Encrypt Options

The following options can be passed to the encrypt call to control how the TDF is constructed.


WithDataAttributes

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.


WithMetadata

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

WithAssertions

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.TrustedDataObjScope,
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.TrustedDataObjScope,
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.


System Metadata Assertion

Automatically attach a system metadata assertion containing TDF spec version, creation timestamp, SDK version, OS, and architecture. Useful for audit trails and debugging.

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

manifest, err := client.CreateTDF(&buf, plaintext,
sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
sdk.WithSystemMetadataAssertion(),
)

The assertion uses ID "system-metadata", schema "system-metadata-v1", type BaseAssertion, scope PayloadScope, and state Unencrypted. It is bound with the DEK (HS256) by default.


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.