tdf

package
v0.10.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2025 License: BSD-3-Clause-Clear Imports: 19 Imported by: 0

Documentation

Overview

Experimental: This package is EXPERIMENTAL and may change or be removed at any time Package tdf provides experimental streaming TDF (Trusted Data Format) creation capabilities.

Experimental Status

This package is EXPERIMENTAL and its API is subject to change in future releases. It is designed for advanced use cases requiring fine-grained control over TDF creation with streaming support for large datasets.

For most use cases, prefer the stable SDK-level TDF creation APIs.

Overview

The tdf package enables streaming creation of TDF files with support for:

  • Variable-length segments that can arrive out-of-order
  • Cryptographic assertions and integrity verification
  • Custom attribute-based access controls
  • Memory-efficient processing of large datasets
  • ZIP archive generation with proper central directory structures

Basic Usage

ctx := context.Background()

// Create a new TDF writer
writer, err := tdf.NewWriter(ctx)
if err != nil {
	log.Fatal(err)
}
defer writer.Close()

// Write data segments (can be out-of-order)
data1 := []byte("First segment")
_, err = writer.WriteSegment(ctx, 0, data1)
if err != nil {
	log.Fatal(err)
}

data2 := []byte("Second segment")
_, err = writer.WriteSegment(ctx, 1, data2)
if err != nil {
	log.Fatal(err)
}

// Finalize with attributes and options
finalBytes, manifest, err := writer.Finalize(ctx,
	WithAttributeValues(attributes),
	WithPayloadMimeType("text/plain"),
	WithEncryptedMetadata("sensitive metadata"),
)
if err != nil {
	log.Fatal(err)
}

Initial Attributes and Default KAS at Writer Creation

Callers can provide initial attributes and a default KAS when constructing the writer. If Finalize options omit these, the writer-level values are used. Finalize-specified values always take precedence.

attrs := []*policy.Value{ /* ... */ }
kasKey := &policy.SimpleKasKey{ /* ... */ }
writer, err := tdf.NewWriter(ctx,
	tdf.WithInitialAttributes(attrs),
	tdf.WithDefaultKASForWriter(kasKey),
)
if err != nil {
	log.Fatal(err)
}
// Later, Finalize without attributes/KAS uses the initial values.

Segment Overrides at Finalize (Contiguous Prefix)

You can restrict finalization to a contiguous prefix of written segments using `WithSegments([]int{0, 1, ..., K})`. Indices must start at 0 with no gaps or duplicates, and no segments may have been written beyond K.

// Write segments 0 and 1
_, _ = writer.WriteSegment(ctx, 0, []byte("part-0"))
_, _ = writer.WriteSegment(ctx, 1, []byte("part-1"))

// Finalize keeping the prefix [0,1]
finalBytes, manifest, err := writer.Finalize(ctx,
	tdf.WithSegments([]int{0, 1}),
)
if err != nil {
	log.Fatal(err)
}

If all segments should be kept, `WithSegments([0..N-1])` is equivalent to the default behavior and is optional.

Advanced Features

The package supports advanced TDF features including:

  • Custom cryptographic assertions with JWT-based integrity
  • Encrypted metadata storage within key access objects
  • Multiple integrity algorithm support (HS256, GMAC)
  • ZIP64 format support for large files
  • Memory-optimized segment processing

Architecture

The TDF writer uses a two-layer architecture:

  1. TDF Layer (tdf.Writer): Handles encryption, assertions, and TDF protocol logic
  2. Archive Layer (internal/archive2): Manages ZIP file structure and segment assembly

This separation enables independent optimization of cryptographic operations and file format handling.

Thread Safety

Writers are safe for concurrent use with proper external synchronization. Individual WriteSegment calls must be serialized, but multiple writers can operate independently.

Performance Characteristics

The implementation is optimized for:

  • Linear time complexity O(n) for n segments
  • Memory usage independent of write order (no payload buffering)
  • CRC aggregation via combine over per-segment CRCs
  • Minimal allocation patterns for high-throughput scenarios

Current benchmarks (100 segments, 1KB each):

  • Sequential: ~240μs/op, ~530KB memory/op
  • Out-of-order: Similar performance due to combine-based CRC approach

Compatibility

TDF files created by this package are compatible with:

  • OpenTDF SDK readers (LoadTDF)
  • OpenTDF platform services
  • Standard ZIP tools (for archive structure inspection)
  • TDF specification version 4.3.0

Error Handling

The package uses structured error reporting with operation context:

  • ErrAlreadyFinalized: Writer has been finalized
  • ErrInvalidSegmentIndex: Invalid segment index provided
  • ErrSegmentAlreadyWritten: Duplicate segment index

All errors include sufficient context for debugging and recovery.

Index

Examples

Constants

View Source
const (
	// SystemMetadataAssertionID is the standard ID for system metadata assertions
	SystemMetadataAssertionID = "system-metadata"
	// SystemMetadataSchemaV1 defines the schema version for system metadata
	SystemMetadataSchemaV1 = "system-metadata-v1"
)
View Source
const (
	// HS256 uses HMAC-SHA256 for integrity verification.
	// This is the default and most widely supported algorithm.
	HS256 = iota
	// GMAC uses Galois Message Authentication Code for integrity verification.
	// Provides better performance with AES-GCM but requires hardware support for optimal speed.
	GMAC
)
View Source
const (
	// The latest version of TDF Spec currently targeted by the SDK.
	// By default, new files will conform to this version of the spec
	// and, where possible, older versions will still be readable.
	TDFSpecVersion = "4.3.0"

	// The three-part semantic version number of this SDK
	Version = "0.6.0" // x-release-please-version
)

Variables

View Source
var (
	// ErrAlreadyFinalized is returned when attempting operations on a finalized writer
	ErrAlreadyFinalized = errors.New("tdf is already finalized")
	// ErrInvalidSegmentIndex is returned for negative segment indices
	ErrInvalidSegmentIndex = errors.New("invalid segment index")
	// ErrSegmentAlreadyWritten is returned when trying to write to an existing segment index
	ErrSegmentAlreadyWritten = errors.New("segment already written")
)

Functions

This section is empty.

Types

type AppliesToState

type AppliesToState string

AppliesToState indicates when the assertion is relevant in the TDF lifecycle.

This determines whether the assertion should be processed before or after decryption, enabling different handling patterns:

  • Encrypted: Process before decryption (e.g., access logging)
  • Unencrypted: Process after decryption (e.g., content filtering)
const (
	// Encrypted means the assertion should be processed before payload decryption.
	// Used for access control, audit logging, and pre-processing requirements.
	Encrypted AppliesToState = "encrypted"
	// Unencrypted means the assertion should be processed after payload decryption.
	// Used for content analysis, post-processing, and data handling requirements.
	Unencrypted AppliesToState = "unencrypted"
)

func (AppliesToState) String

func (ats AppliesToState) String() string

String returns the string representation of the applies to state.

type Assertion

type Assertion struct {
	ID             string         `json:"id"`
	Type           AssertionType  `json:"type"`
	Scope          Scope          `json:"scope"`
	AppliesToState AppliesToState `json:"appliesToState,omitempty"`
	Statement      Statement      `json:"statement"`
	Binding        Binding        `json:"binding,omitempty"`
}

Assertion represents a cryptographically signed assertion in the TDF manifest.

Assertions provide integrity verification and handling instructions that are cryptographically bound to the TDF. They cannot be modified or copied to another TDF without detection due to the cryptographic binding.

The assertion structure includes:

  • Metadata: ID, type, scope, and state applicability
  • Statement: The actual assertion content in structured format
  • Binding: Cryptographic signature ensuring integrity

Assertions are verified during TDF reading to ensure they haven't been tampered with since TDF creation.

func (Assertion) GetHash

func (a Assertion) GetHash() ([]byte, error)

GetHash returns the hash of the assertion in hex format.

func (*Assertion) Sign

func (a *Assertion) Sign(hash, sig string, key AssertionKey) error

Sign signs the assertion with the given hash and signature using the key. It returns an error if the signing fails. The assertion binding is updated with the method and the signature.

func (Assertion) Verify

func (a Assertion) Verify(key AssertionKey) (string, string, error)

Verify checks the binding signature of the assertion and returns the hash and the signature. It returns an error if the verification fails.

type AssertionConfig

type AssertionConfig struct {
	ID             string         `validate:"required"`
	Type           AssertionType  `validate:"required"`
	Scope          Scope          `validate:"required"`
	AppliesToState AppliesToState `validate:"required"`
	Statement      Statement
	SigningKey     AssertionKey
}

AssertionConfig defines an assertion to be included in the TDF during creation.

AssertionConfig extends Assertion with a signing key, enabling creation of cryptographically signed assertions. The signing key is used during TDF creation but is not stored in the final TDF.

Required fields:

  • ID: Unique identifier for the assertion
  • Type: The kind of assertion (BaseAssertion, HandlingAssertion)
  • Scope: What the assertion applies to (PayloadScope, TrustedDataObjScope)
  • AppliesToState: When the assertion is relevant (Encrypted, Unencrypted)
  • Statement: The assertion content and metadata

Optional fields:

  • SigningKey: Custom signing key (defaults to DEK with HS256)

Example:

assertion := AssertionConfig{
	ID:             "retention-policy",
	Type:           HandlingAssertion,
	Scope:          PayloadScope,
	AppliesToState: Unencrypted,
	Statement: Statement{
		Format: "json",
		Schema: "retention-v1",
		Value:  `{"retain_days": 90, "auto_delete": true}`,
	},
}

type AssertionKey

type AssertionKey struct {
	// Alg specifies the cryptographic algorithm for this key
	Alg AssertionKeyAlg
	// Key contains the actual key material (type depends on algorithm)
	Key interface{}
}

AssertionKey represents a cryptographic key for signing and verifying assertions.

The key can be either RSA or HMAC-based depending on the algorithm:

  • RS256: Key should be an RSA private key (*rsa.PrivateKey or jwk.Key)
  • HS256: Key should be a byte slice containing the shared secret

Example usage:

// HMAC key using TDF's Data Encryption Key
hmacKey := AssertionKey{
	Alg: AssertionKeyAlgHS256,
	Key: dek, // 32-byte AES key
}

// RSA key for public key scenarios
rsaKey := AssertionKey{
	Alg: AssertionKeyAlgRS256,
	Key: privateKey, // *rsa.PrivateKey
}

func (AssertionKey) Algorithm

func (k AssertionKey) Algorithm() AssertionKeyAlg

Algorithm returns the cryptographic algorithm of the key.

func (AssertionKey) IsEmpty

func (k AssertionKey) IsEmpty() bool

IsEmpty returns true if the key has no algorithm or key material configured. Used to check if a default signing key should be used instead.

type AssertionKeyAlg

type AssertionKeyAlg string

AssertionKeyAlg represents the cryptographic algorithm for assertion signing keys.

Different algorithms provide different security and compatibility characteristics:

  • RS256: RSA-based signatures, widely supported, good for public key scenarios
  • HS256: HMAC-based signatures, simpler, good for shared key scenarios
const (
	// AssertionKeyAlgRS256 uses RSA-SHA256 for assertion signatures.
	// Suitable when assertions need to be verified by parties without access to signing keys.
	AssertionKeyAlgRS256 AssertionKeyAlg = "RS256"
	// AssertionKeyAlgHS256 uses HMAC-SHA256 for assertion signatures.
	// More efficient, suitable when the same key used for TDF encryption can sign assertions.
	AssertionKeyAlgHS256 AssertionKeyAlg = "HS256"
)

func (AssertionKeyAlg) String

func (a AssertionKeyAlg) String() string

String returns the string representation of the algorithm.

type AssertionType

type AssertionType string

AssertionType represents the category of assertion being made.

Different assertion types serve different purposes in TDF handling:

  • HandlingAssertion: Instructions for data processing, retention, deletion
  • BaseAssertion: General-purpose assertions including metadata, audit info
const (
	// HandlingAssertion provides instructions for data handling and processing.
	// Examples: retention policies, deletion schedules, processing requirements
	HandlingAssertion AssertionType = "handling"
	// BaseAssertion is a general-purpose assertion type for metadata and other content.
	// Examples: audit information, system metadata, custom business logic
	BaseAssertion AssertionType = "other"
)

func (AssertionType) String

func (at AssertionType) String() string

String returns the string representation of the assertion type.

type AssertionVerificationKeys

type AssertionVerificationKeys struct {
	// Default key to use if the key for the assertion ID is not found.
	DefaultKey AssertionKey
	// Map of assertion ID to key.
	Keys map[string]AssertionKey
}

AssertionVerificationKeys represents the verification keys for assertions.

func (AssertionVerificationKeys) Get

func (k AssertionVerificationKeys) Get(assertionID string) (AssertionKey, error)

Get returns the key for the given assertion ID or the default key if the key is not found. If the default key is not set, it returns an empty key.

func (AssertionVerificationKeys) IsEmpty

func (k AssertionVerificationKeys) IsEmpty() bool

IsEmpty returns true if the default key and the keys map are empty.

type BaseConfig

type BaseConfig struct{}

BaseConfig provides common configuration foundation for TDF operations. Currently empty but reserved for future common configuration options.

type Binding

type Binding struct {
	// Method used to bind the assertion. (e.g. jws)
	Method string `json:"method,omitempty"`
	// Signature of the assertion.
	Signature string `json:"signature,omitempty"`
}

Binding enforces cryptographic integrity of the assertion. So the can't be modified or copied to another tdf.

type BindingMethod

type BindingMethod string

BindingMethod represents the cryptographic method used to bind assertions to the TDF.

The binding method ensures assertions cannot be modified or transferred to other TDFs without detection.

const (
	// JWS (JSON Web Signature) is the standard method for assertion binding.
	// Uses JWT-based cryptographic signatures for tamper detection.
	JWS BindingMethod = "jws"
)

func (BindingMethod) String

func (bm BindingMethod) String() string

String returns the string representation of the binding method.

type EncryptedMetadata

type EncryptedMetadata struct {
	Cipher string `json:"ciphertext"`
	Iv     string `json:"iv"`
}

type EncryptionInformation

type EncryptionInformation struct {
	KeyAccessType        string      `json:"type"`
	Policy               string      `json:"policy"`
	KeyAccessObjs        []KeyAccess `json:"keyAccess"`
	Method               Method      `json:"method"`
	IntegrityInformation `json:"integrityInformation"`
}

type FinalizeResult

type FinalizeResult struct {
	Data          []byte    `json:"data"`          // Final TDF bytes (manifest + metadata)
	Manifest      *Manifest `json:"manifest"`      // Complete manifest object
	TotalSegments int       `json:"totalSegments"` // Number of segments written
	TotalSize     int64     `json:"totalSize"`     // Total plaintext size
	EncryptedSize int64     `json:"encryptedSize"` // Total encrypted size
}

FinalizeResult contains the complete TDF creation result

type IntegrityAlgorithm

type IntegrityAlgorithm int

IntegrityAlgorithm specifies the cryptographic algorithm used for integrity verification.

Different algorithms provide different security and performance characteristics:

  • HS256: HMAC-SHA256, widely supported, good balance of security and performance
  • GMAC: Galois Message Authentication Code, faster but requires AES-GCM support

The algorithm choice affects both segment-level and root-level integrity verification.

func (IntegrityAlgorithm) String

func (i IntegrityAlgorithm) String() string

String returns the string representation of the integrity algorithm. Used for manifest generation and protocol compatibility.

type IntegrityInformation

type IntegrityInformation struct {
	RootSignature           `json:"rootSignature"`
	SegmentHashAlgorithm    string    `json:"segmentHashAlg"`
	DefaultSegmentSize      int64     `json:"segmentSizeDefault"`
	DefaultEncryptedSegSize int64     `json:"encryptedSegmentSizeDefault"`
	Segments                []Segment `json:"segments"`
}

type KeyAccess

type KeyAccess struct {
	KeyType            string      `json:"type"`
	KasURL             string      `json:"url"`
	Protocol           string      `json:"protocol"`
	WrappedKey         string      `json:"wrappedKey"`
	PolicyBinding      interface{} `json:"policyBinding"`
	EncryptedMetadata  string      `json:"encryptedMetadata,omitempty"`
	KID                string      `json:"kid,omitempty"`
	SplitID            string      `json:"sid,omitempty"`
	SchemaVersion      string      `json:"schemaVersion,omitempty"`
	EphemeralPublicKey string      `json:"ephemeralPublicKey,omitempty"`
}

type Manifest

type Manifest struct {
	EncryptionInformation `json:"encryptionInformation"`
	Payload               `json:"payload"`
	Assertions            []Assertion `json:"assertions,omitempty"`
	TDFVersion            string      `json:"schemaVersion,omitempty"`
}

type Method

type Method struct {
	Algorithm    string `json:"algorithm"`
	IV           string `json:"iv"`
	IsStreamable bool   `json:"isStreamable"`
}

type Option

type Option[T any] func(T)

Option is a functional option pattern for configuring TDF operations.

This generic type allows type-safe configuration of different TDF components:

  • Option[*WriterConfig] for Writer configuration
  • Option[*WriterFinalizeConfig] for Finalize operation configuration
  • Option[*ReaderConfig] for future Reader configuration

Example usage:

writer, err := NewWriter(ctx, WithIntegrityAlgorithm(GMAC))
finalBytes, manifest, err := writer.Finalize(ctx, WithPayloadMimeType("text/plain"))

func WithAssertions

func WithAssertions(assertions ...AssertionConfig) Option[*WriterFinalizeConfig]

WithAssertions includes cryptographic assertions in the TDF.

Assertions provide additional integrity verification and can include handling instructions, metadata, or custom verification logic. Each assertion is cryptographically signed to prevent tampering.

Common assertion types:

  • BaseAssertion ("other"): General-purpose assertions
  • HandlingAssertion: Data handling and processing instructions

Each assertion includes:

  • Statement: The assertion content (JSON format)
  • Scope: What the assertion applies to (payload, TDF object)
  • Signing key: For cryptographic verification

Example:

assertion := AssertionConfig{
	ID: "handling-instruction",
	Type: HandlingAssertion,
	Scope: PayloadScope,
	AppliesToState: Unencrypted,
	Statement: Statement{
		Format: "json",
		Schema: "handling-v1",
		Value: `{"retention_days": 90}`,
	},
}
finalBytes, manifest, err := writer.Finalize(ctx, WithAssertions(assertion))

func WithAttributeValues

func WithAttributeValues(values []*policy.Value) Option[*WriterFinalizeConfig]

WithAttributeValues sets the data attributes for access control.

Data attributes define who can access the TDF based on attribute-based access control (ABAC) policies. Each attribute represents a requirement that must be satisfied for access.

Attributes typically include:

  • Classification levels (SECRET, TOP_SECRET, etc.)
  • Organizational units (HR, Finance, Engineering)
  • Clearance levels (Level_1, Level_2, etc.)
  • Custom business attributes

Each attribute must have associated Key Access Server information that defines how the attribute is validated and which KAS controls access.

Example:

attributes := []*policy.Value{
	{
		Fqn: "https://company.com/attr/classification/value/secret",
		Grants: []*policy.KeyAccessServer{kasConfig},
	},
}
finalBytes, manifest, err := writer.Finalize(ctx, WithAttributeValues(attributes))

func WithDefaultKAS

func WithDefaultKAS(kas *policy.SimpleKasKey) Option[*WriterFinalizeConfig]

WithDefaultKAS sets the default Key Access Server for attribute resolution.

The KAS is used when attributes don't specify their own key access servers. This simplifies configuration when all attributes use the same KAS instance.

The provided KAS configuration includes:

  • URI: The KAS endpoint URL
  • Public key information for key wrapping
  • Algorithm specification (typically RSA-2048)

Example:

kasKey := &policy.SimpleKasKey{
	KasUri: "https://kas.example.com",
	PublicKey: &policy.SimpleKasPublicKey{
		Algorithm: policy.Algorithm_ALGORITHM_RSA_2048,
		Kid: "kas-key-1",
		Pem: kasPublicKeyPEM,
	},
}
finalBytes, manifest, err := writer.Finalize(ctx, WithDefaultKAS(kasKey))

func WithDefaultKASForWriter

func WithDefaultKASForWriter(kas *policy.SimpleKasKey) Option[*WriterConfig]

WithDefaultKASForWriter sets the default KAS on the Writer at creation time.

This default KAS is used by Finalize() if no default KAS is provided via Finalize options. Finalize-specified default KAS always takes precedence.

func WithEncryptedMetadata

func WithEncryptedMetadata(metadata string) Option[*WriterFinalizeConfig]

WithEncryptedMetadata includes encrypted metadata in the TDF.

The metadata is encrypted and stored within key access objects, making it accessible only after successful attribute-based access control validation. This is useful for storing sensitive information about the data that should only be visible to authorized users.

The metadata is encrypted using the same key management as the payload data, ensuring consistent access controls.

Example:

finalBytes, manifest, err := writer.Finalize(ctx,
	WithEncryptedMetadata("classification: secret"),
)

func WithExcludeVersionFromManifest

func WithExcludeVersionFromManifest(exclude bool) Option[*WriterFinalizeConfig]

WithExcludeVersionFromManifest controls version information in the manifest.

When set to true, excludes TDF specification version information from the manifest. This may be needed for compatibility with older TDF readers that don't expect version fields.

Generally should be left as default (false) unless specific compatibility requirements exist.

Example:

// For compatibility with legacy readers
finalBytes, manifest, err := writer.Finalize(ctx,
	WithExcludeVersionFromManifest(true),
)

func WithInitialAttributes

func WithInitialAttributes(values []*policy.Value) Option[*WriterConfig]

WithInitialAttributes sets data attributes on the Writer at creation time.

These attributes are used by Finalize() if no attributes are provided via Finalize options. Finalize-specified attributes always take precedence.

func WithIntegrityAlgorithm

func WithIntegrityAlgorithm(algo IntegrityAlgorithm) Option[*WriterConfig]

WithIntegrityAlgorithm sets the algorithm for root integrity signature calculation.

The root integrity algorithm is used to generate a signature over all segment hashes, providing verification that the complete TDF has not been tampered with.

Algorithm options:

  • HS256: HMAC-SHA256 (default) - widely supported, secure
  • GMAC: Galois Message Authentication Code - faster with hardware acceleration

Example:

writer, err := NewWriter(ctx, WithIntegrityAlgorithm(GMAC))

func WithPayloadMimeType

func WithPayloadMimeType(mimeType string) Option[*WriterFinalizeConfig]

WithPayloadMimeType sets the MIME type for the TDF payload.

The MIME type helps readers understand how to process the decrypted content. Common values include:

  • "application/octet-stream" (default) - binary data
  • "text/plain" - plain text files
  • "application/json" - JSON data
  • "image/jpeg", "video/mp4", etc. - media files

Example:

finalBytes, manifest, err := writer.Finalize(ctx,
	WithPayloadMimeType("application/json"),
)

func WithSegmentIntegrityAlgorithm

func WithSegmentIntegrityAlgorithm(algo IntegrityAlgorithm) Option[*WriterConfig]

WithSegmentIntegrityAlgorithm sets the algorithm for individual segment hash calculation.

The segment integrity algorithm is used to calculate a hash for each individual segment, enabling verification of segment-level integrity independent of the complete file. This is particularly useful for streaming scenarios where segments may be processed independently.

The segment algorithm can differ from the root algorithm to optimize for different processing patterns:

  • Use GMAC for segments if processing many small segments (better performance)
  • Use HS256 for root signature for broader compatibility

Example:

// Fast segment processing with compatible root signature
writer, err := NewWriter(ctx,
	WithSegmentIntegrityAlgorithm(GMAC),  // Fast segment hashing
	WithIntegrityAlgorithm(HS256),        // Compatible root signature
)

func WithSegments

func WithSegments(indices []int) Option[*WriterFinalizeConfig]

WithSegments restricts finalization to the provided segment indices and order. The order provided is used as the logical payload order. Indices may be sparse but must refer to segments that were written. When omitted, all present indices are used in ascending order.

type Payload

type Payload struct {
	Type        string `json:"type"`
	URL         string `json:"url"`
	Protocol    string `json:"protocol"`
	MimeType    string `json:"mimeType"`
	IsEncrypted bool   `json:"isEncrypted"`
}

type Policy

type Policy struct {
	UUID string     `json:"uuid"`
	Body PolicyBody `json:"body"`
}

type PolicyAttribute

type PolicyAttribute struct {
	Attribute   string `json:"attribute"`
	DisplayName string `json:"displayName"`
	IsDefault   bool   `json:"isDefault"`
	PubKey      string `json:"pubKey"`
	KasURL      string `json:"kasURL"`
}

type PolicyBinding

type PolicyBinding struct {
	Alg  string `json:"alg"`
	Hash string `json:"hash"`
}

type PolicyBody

type PolicyBody struct {
	DataAttributes []PolicyAttribute `json:"dataAttributes"`
	Dissem         []string          `json:"dissem"`
}

type Reader

type Reader struct{}

type ReaderConfig

type ReaderConfig struct {
	BaseConfig
}

ReaderConfig contains configuration options for TDF Reader creation. Reserved for future reader configuration options.

type RootSignature

type RootSignature struct {
	Algorithm string `json:"alg"`
	Signature string `json:"sig"`
}

type Scope

type Scope string

Scope defines what component of the TDF the assertion applies to.

Scope determines which part of the TDF structure the assertion governs:

  • TrustedDataObjScope: Assertion applies to the entire TDF object
  • PayloadScope: Assertion applies only to the encrypted payload data
const (
	// TrustedDataObjScope indicates the assertion applies to the complete TDF object.
	// This includes manifest, key access objects, and payload.
	TrustedDataObjScope Scope = "tdo"
	// PayloadScope indicates the assertion applies only to the payload data.
	// This is the most common scope for data handling assertions.
	PayloadScope Scope = "payload"
)

func (Scope) String

func (s Scope) String() string

String returns the string representation of the scope.

type Segment

type Segment struct {
	Hash          string `json:"hash"`
	Size          int64  `json:"segmentSize"`
	EncryptedSize int64  `json:"encryptedSegmentSize"`
}

type SegmentResult

type SegmentResult struct {
	Data          []byte `json:"data"`          // Encrypted segment bytes (for streaming)
	Index         int    `json:"index"`         // Segment index
	Hash          string `json:"hash"`          // Base64-encoded integrity hash
	PlaintextSize int64  `json:"plaintextSize"` // Original data size
	EncryptedSize int64  `json:"encryptedSize"` // Encrypted data size
	CRC32         uint32 `json:"crc32"`         // CRC32 checksum
}

SegmentResult contains the result of writing a segment

type Statement

type Statement struct {
	// Format describes the payload encoding format. (e.g. json)
	Format string `json:"format,omitempty" validate:"required"`
	// Schema describes the schema of the payload. (e.g. tdf)
	Schema string `json:"schema,omitempty" validate:"required"`
	// Value is the payload of the assertion.
	Value string `json:"value,omitempty"  validate:"required"`
}

Statement includes information applying to the scope of the assertion. It could contain rights, handling instructions, or general metadata.

func (*Statement) UnmarshalJSON

func (s *Statement) UnmarshalJSON(data []byte) error

type Writer

type Writer struct {
	// WriterConfig embeds configuration options for the TDF writer
	WriterConfig
	// contains filtered or unexported fields
}

Writer provides streaming TDF creation with out-of-order segment support.

The Writer enables creation of TDF files by writing individual segments that can arrive in any order. It handles encryption, integrity verification, and proper ZIP archive structure generation.

Key features:

  • Variable-length segments with sparse index support
  • Out-of-order segment writing without buffering payloads
  • Memory-efficient handling through segment cleanup
  • Cryptographic assertions and integrity verification
  • Custom attribute-based access controls

Thread safety: Writers require external synchronization for concurrent access. Each WriteSegment call must be serialized, but multiple Writers can operate independently.

Example usage:

writer, err := NewWriter(ctx, WithIntegrityAlgorithm(HS256))
if err != nil {
	return err
}
defer writer.Close()

// Write segments (can be out-of-order)
_, err = writer.WriteSegment(ctx, 1, []byte("second"))
_, err = writer.WriteSegment(ctx, 0, []byte("first"))

// Finalize with attributes
finalBytes, manifest, err := writer.Finalize(ctx, WithAttributeValues(attrs))
Example

ExampleWriter demonstrates basic TDF creation with the experimental streaming writer.

package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"log"

	"github.com/opentdf/platform/protocol/go/policy"
	"github.com/opentdf/platform/sdk/experimental/tdf"
)

func main() {
	ctx := context.Background()

	// Create a new TDF writer with default settings
	writer, err := tdf.NewWriter(ctx)
	if err != nil {
		log.Println(err)
		return
	}

	// Write segments (can be out-of-order)
	_, err = writer.WriteSegment(ctx, 0, []byte("First part of the data"))
	if err != nil {
		log.Println(err)
		return
	}

	_, err = writer.WriteSegment(ctx, 1, []byte("Second part of the data"))
	if err != nil {
		log.Println(err)
		return
	}

	// Finalize the TDF
	// This example finalizes without attributes, relying on a default KAS.
	mockKasKey := newMockKasKey("https://kas.example.com")

	fmt.Println("TDF writer created and 2 segments written.")
	finalizeResult, err := writer.Finalize(ctx, tdf.WithDefaultKAS(mockKasKey))
	if err != nil {
		log.Println(err)
		return
	}

	fmt.Printf("TDF finalized with %d key access object(s).\n", len(finalizeResult.Manifest.EncryptionInformation.KeyAccessObjs))
}

// newMockKasKey generates a new RSA key pair and returns a SimpleKasKey
// containing the public key, for use in tests without a real KAS.
func newMockKasKey(kasURL string) *policy.SimpleKasKey {

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatalf("Failed to generate RSA key: %v", err)
	}

	publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	if err != nil {
		log.Fatalf("Failed to marshal public key: %v", err)
	}

	publicKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: publicKeyBytes,
	})

	return &policy.SimpleKasKey{
		KasUri: kasURL,
		PublicKey: &policy.SimpleKasPublicKey{
			Algorithm: policy.Algorithm_ALGORITHM_RSA_2048,
			Kid:       "test-kid",
			Pem:       string(publicKeyPEM),
		},
	}
}
Output:

TDF writer created and 2 segments written.
TDF finalized with 1 key access object(s).
Example (LargeFile)

ExampleWriter_largeFile demonstrates efficient handling of large files through segmentation.

package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"log"

	"github.com/opentdf/platform/protocol/go/policy"
	"github.com/opentdf/platform/sdk/experimental/tdf"
)

func main() {
	ctx := context.Background()

	// Configure for large file processing
	writer, err := tdf.NewWriter(ctx,
		tdf.WithSegmentIntegrityAlgorithm(tdf.GMAC), // Faster for many segments
	)
	if err != nil {
		log.Println(err)
		return
	}

	// Simulate processing a large file in 1MB segments
	totalSegments := 2         // Keep it small for an example
	segmentSize := 1024 * 1024 // 1MB segments

	for i := 0; i < totalSegments; i++ {
		// In practice, this data would come from file reading, network, etc.
		segmentData := make([]byte, segmentSize)

		_, err := writer.WriteSegment(ctx, i, segmentData)
		if err != nil {
			log.Println(err)
			return
		}

		fmt.Printf("Processed segment %d: %d bytes\n", i, len(segmentData))
	}

	// Finalize large file
	mockKasKey := newMockKasKey("https://kas.example.com")

	fmt.Printf("All %d segments processed.\n", totalSegments)
	finalizeResult, err := writer.Finalize(ctx,
		tdf.WithPayloadMimeType("application/octet-stream"),
		tdf.WithDefaultKAS(mockKasKey),
	)
	if err != nil {
		log.Println(err)
	}

	fmt.Printf("Finalized large file with %d segment(s).\n", len(finalizeResult.Manifest.EncryptionInformation.IntegrityInformation.Segments))
}

// newMockKasKey generates a new RSA key pair and returns a SimpleKasKey
// containing the public key, for use in tests without a real KAS.
func newMockKasKey(kasURL string) *policy.SimpleKasKey {

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatalf("Failed to generate RSA key: %v", err)
	}

	publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	if err != nil {
		log.Fatalf("Failed to marshal public key: %v", err)
	}

	publicKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: publicKeyBytes,
	})

	return &policy.SimpleKasKey{
		KasUri: kasURL,
		PublicKey: &policy.SimpleKasPublicKey{
			Algorithm: policy.Algorithm_ALGORITHM_RSA_2048,
			Kid:       "test-kid",
			Pem:       string(publicKeyPEM),
		},
	}
}
Output:

Processed segment 0: 1048576 bytes
Processed segment 1: 1048576 bytes
All 2 segments processed.
Finalized large file with 2 segment(s).
Example (OutOfOrder)

ExampleWriter_outOfOrder demonstrates out-of-order segment writing for streaming scenarios.

package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"log"

	"github.com/opentdf/platform/protocol/go/policy"
	"github.com/opentdf/platform/sdk/experimental/tdf"
)

func main() {
	ctx := context.Background()

	writer, err := tdf.NewWriter(ctx)
	if err != nil {
		log.Println(err)
		return
	}

	// Simulate segments arriving out-of-order (e.g., from parallel processing)
	segments := map[int][]byte{
		0: []byte("Beginning of file"),
		1: []byte("Middle section with important data"),
		2: []byte("End of file"),
	}

	// Write segments as they arrive (out-of-order)
	writeOrder := []int{2, 0, 1} // End, beginning, middle

	for _, index := range writeOrder {
		data := segments[index]
		_, err := writer.WriteSegment(ctx, index, data)
		if err != nil {
			log.Println(err)
			return
		}
		fmt.Printf("Wrote segment %d\n", index)
	}

	// Finalize - segments will be properly ordered in the final TDF
	mockKasKey := newMockKasKey("https://kas.example.com")

	fmt.Println("All out-of-order segments written.")
	finalizeResult, err := writer.Finalize(ctx,
		tdf.WithPayloadMimeType("text/plain"),
		tdf.WithDefaultKAS(mockKasKey),
	)
	if err != nil {
		log.Println(err)
	}

	fmt.Printf("Finalized TDF with %d segment(s).\n", len(finalizeResult.Manifest.EncryptionInformation.IntegrityInformation.Segments))
}

// newMockKasKey generates a new RSA key pair and returns a SimpleKasKey
// containing the public key, for use in tests without a real KAS.
func newMockKasKey(kasURL string) *policy.SimpleKasKey {

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatalf("Failed to generate RSA key: %v", err)
	}

	publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	if err != nil {
		log.Fatalf("Failed to marshal public key: %v", err)
	}

	publicKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: publicKeyBytes,
	})

	return &policy.SimpleKasKey{
		KasUri: kasURL,
		PublicKey: &policy.SimpleKasPublicKey{
			Algorithm: policy.Algorithm_ALGORITHM_RSA_2048,
			Kid:       "test-kid",
			Pem:       string(publicKeyPEM),
		},
	}
}
Output:

Wrote segment 2
Wrote segment 0
Wrote segment 1
All out-of-order segments written.
Finalized TDF with 3 segment(s).
Example (WithAssertions)

ExampleWriter_withAssertions demonstrates TDF creation with cryptographic assertions.

package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"log"

	"github.com/opentdf/platform/protocol/go/policy"
	"github.com/opentdf/platform/sdk/experimental/tdf"
)

func main() {
	ctx := context.Background()

	writer, err := tdf.NewWriter(ctx)
	if err != nil {
		log.Println(err)
		return
	}

	// Write data segment
	data := []byte("Data with retention requirements")
	_, err = writer.WriteSegment(ctx, 0, data)
	if err != nil {
		log.Println(err)
		return
	}

	// Create a handling assertion for data retention
	retentionAssertion := tdf.AssertionConfig{
		ID:             "retention-policy",
		Type:           tdf.HandlingAssertion,
		Scope:          tdf.PayloadScope,
		AppliesToState: tdf.Unencrypted,
		Statement: tdf.Statement{
			Format: "json",
			Schema: "retention-policy-v1",
			Value:  `{"retain_days": 90, "auto_delete": true, "archive_required": false}`,
		},
	}

	// Create a metadata assertion
	metadataAssertion := tdf.AssertionConfig{
		ID:             "audit-info",
		Type:           tdf.BaseAssertion,
		Scope:          tdf.TrustedDataObjScope,
		AppliesToState: tdf.Encrypted,
		Statement: tdf.Statement{
			Format: "json",
			Schema: "audit-v1",
			Value:  `{"created_by": "system", "purpose": "compliance_report"}`,
		},
	}

	// Finalize with assertions
	mockKasKey := newMockKasKey("https://kas.example.com")

	fmt.Printf("TDF configured with %d assertions.\n", 2)
	finalizeResult, err := writer.Finalize(ctx,
		tdf.WithAssertions(retentionAssertion, metadataAssertion),
		tdf.WithPayloadMimeType("application/json"),
		tdf.WithDefaultKAS(mockKasKey),
	)
	if err != nil {
		log.Println(err)
	}

	fmt.Printf("TDF finalized with %d assertion(s).\n", len(finalizeResult.Manifest.Assertions))
}

// newMockKasKey generates a new RSA key pair and returns a SimpleKasKey
// containing the public key, for use in tests without a real KAS.
func newMockKasKey(kasURL string) *policy.SimpleKasKey {

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatalf("Failed to generate RSA key: %v", err)
	}

	publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	if err != nil {
		log.Fatalf("Failed to marshal public key: %v", err)
	}

	publicKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: publicKeyBytes,
	})

	return &policy.SimpleKasKey{
		KasUri: kasURL,
		PublicKey: &policy.SimpleKasPublicKey{
			Algorithm: policy.Algorithm_ALGORITHM_RSA_2048,
			Kid:       "test-kid",
			Pem:       string(publicKeyPEM),
		},
	}
}
Output:

TDF configured with 2 assertions.
TDF finalized with 2 assertion(s).
Example (WithAttributes)

ExampleWriter_withAttributes demonstrates TDF creation with attribute-based access control.

package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"log"

	"github.com/opentdf/platform/protocol/go/policy"
	"github.com/opentdf/platform/sdk/experimental/tdf"
)

func main() {
	ctx := context.Background()

	// Create writer with custom integrity algorithm
	writer, err := tdf.NewWriter(ctx, tdf.WithIntegrityAlgorithm(tdf.GMAC))
	if err != nil {
		log.Println(err)
		return
	}

	// Write sensitive data
	sensitiveData := []byte("Confidential business information")
	_, err = writer.WriteSegment(ctx, 0, sensitiveData)
	if err != nil {
		log.Println(err)
		return
	}

	// Create a mock KAS key to embed in the attributes.
	mockKasKey := newMockKasKey("https://kas.example.com")

	// Define access control attributes
	attributes := []*policy.Value{
		{
			Attribute: &policy.Attribute{
				Namespace: &policy.Namespace{
					Name: "company.com",
				},
				Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF,
			},
			Fqn: "https://company.com/attr/classification/value/confidential",
			// In real usage, this would be populated by the policy service.
			// For this example, we embed the key directly.
			KasKeys: []*policy.SimpleKasKey{mockKasKey},
		},
		{
			Attribute: &policy.Attribute{
				Namespace: &policy.Namespace{
					Name: "company.com",
				},
				Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
			},
			Fqn: "https://company.com/attr/department/value/finance",
			// Multiple attributes can share the same KAS.
			KasKeys: []*policy.SimpleKasKey{mockKasKey},
		},
	}

	fmt.Printf("TDF with %d attributes configured.\n", len(attributes))
	finalizeResult, err := writer.Finalize(ctx,
		tdf.WithAttributeValues(attributes),
		tdf.WithPayloadMimeType("text/plain"),
		tdf.WithEncryptedMetadata("Document ID: FIN-2024-001"),
	)
	if err != nil {
		log.Println(err)
	}

	fmt.Printf("TDF finalized with %d key access object(s) for %d attribute(s).\n", len(finalizeResult.Manifest.EncryptionInformation.KeyAccessObjs), len(attributes))
}

// newMockKasKey generates a new RSA key pair and returns a SimpleKasKey
// containing the public key, for use in tests without a real KAS.
func newMockKasKey(kasURL string) *policy.SimpleKasKey {

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatalf("Failed to generate RSA key: %v", err)
	}

	publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	if err != nil {
		log.Fatalf("Failed to marshal public key: %v", err)
	}

	publicKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: publicKeyBytes,
	})

	return &policy.SimpleKasKey{
		KasUri: kasURL,
		PublicKey: &policy.SimpleKasPublicKey{
			Algorithm: policy.Algorithm_ALGORITHM_RSA_2048,
			Kid:       "test-kid",
			Pem:       string(publicKeyPEM),
		},
	}
}
Output:

TDF with 2 attributes configured.
TDF finalized with 1 key access object(s) for 2 attribute(s).

func NewWriter

func NewWriter(_ context.Context, opts ...Option[*WriterConfig]) (*Writer, error)

NewWriter creates a new experimental TDF Writer with streaming support.

The writer is initialized with secure defaults:

  • HS256 integrity algorithms for both root and segment verification
  • AES-256-GCM encryption for all segments
  • Dynamic segment expansion supporting sparse indices
  • Memory-efficient segment processing

Configuration options can be provided to customize:

  • Integrity algorithm selection (HS256, GMAC)
  • Segment integrity algorithm (independent of root algorithm)

The writer generates a unique Data Encryption Key (DEK) and initializes the underlying archive writer for ZIP structure management.

Returns an error if:

  • DEK generation fails (cryptographic entropy issues)
  • AES-GCM cipher initialization fails (invalid key)
  • Archive writer creation fails (resource constraints)

Example:

// Default configuration
writer, err := NewWriter(ctx)

// Custom integrity algorithms
writer, err := NewWriter(ctx,
	WithIntegrityAlgorithm(GMAC),
	WithSegmentIntegrityAlgorithm(HS256),
)

func (*Writer) Finalize

func (w *Writer) Finalize(ctx context.Context, opts ...Option[*WriterFinalizeConfig]) (*FinalizeResult, error)

func (*Writer) GetManifest

func (w *Writer) GetManifest(ctx context.Context, opts ...Option[*WriterFinalizeConfig]) (*Manifest, error)

GetManifest returns the current manifest snapshot.

Behavior:

  • If Finalize has completed, this returns the finalized manifest.
  • If called before Finalize, this returns a stub manifest synthesized from the writer's current state (segments present so far, algorithm selections, and payload defaults). This pre-finalize manifest is not complete and must not be used for verification; it is provided for informational or client-side pre-calculation purposes only.

No logging is performed; callers should consult this documentation for the caveat about pre-finalize state.

func (*Writer) WriteSegment

func (w *Writer) WriteSegment(ctx context.Context, index int, data []byte) (*SegmentResult, error)

WriteSegment encrypts and writes a data segment at the specified index.

Segments can be written in any order and will be properly assembled during finalization. Each segment is independently encrypted with AES-256-GCM and has its integrity hash calculated for verification.

Parameters:

  • ctx: Context for cancellation and timeout control
  • index: Zero-based segment index (must be non-negative, sparse indices supported)
  • data: Raw data to encrypt and store in this segment

Returns the encrypted segment bytes that should be stored/uploaded, and any error. The returned bytes include ZIP structure elements and can be assembled in any order.

The function performs:

  1. Input validation (index >= 0, writer not finalized, no duplicate segments)
  2. AES-256-GCM encryption of the segment data
  3. HMAC signature calculation for integrity verification
  4. ZIP archive segment creation through the archive layer

Memory optimization: Uses sparse storage to avoid O(n²) memory growth for high or non-contiguous segment indices.

Error conditions:

  • ErrAlreadyFinalized: Writer has been finalized, no more segments accepted
  • ErrInvalidSegmentIndex: Negative index provided
  • ErrSegmentAlreadyWritten: Segment index already contains data
  • Context cancellation: If ctx.Done() is signaled
  • Encryption errors: AES-GCM operation failures
  • Archive errors: ZIP structure creation failures

Example:

// Write segments out-of-order
segment1, err := writer.WriteSegment(ctx, 1, []byte("second part"))
segment0, err := writer.WriteSegment(ctx, 0, []byte("first part"))

// Store/upload segment bytes (e.g., to S3)
uploadToS3(segment0, "part-000")
uploadToS3(segment1, "part-001")

type WriterConfig

type WriterConfig struct {
	BaseConfig
	// contains filtered or unexported fields
}

WriterConfig contains configuration options for TDF Writer creation.

The configuration controls cryptographic algorithms and processing behavior:

  • integrityAlgorithm: Algorithm for root integrity signature calculation
  • segmentIntegrityAlgorithm: Algorithm for individual segment hash calculation

These can be set independently to optimize for different security/performance requirements.

type WriterFinalizeConfig

type WriterFinalizeConfig struct {
	// contains filtered or unexported fields
}

WriterFinalizeConfig contains configuration options for TDF finalization.

This configuration controls the final TDF structure and access controls:

  • Key access server configuration for attribute-based access
  • Data attributes defining access policies
  • Cryptographic assertions for additional integrity/handling instructions
  • Metadata and content type specifications

All fields have sensible defaults and are optional unless specific access controls or metadata are required.

Directories

Path Synopsis
Experimental: This package is EXPERIMENTAL and may change or be removed at any time Package keysplit provides key splitting functionality for TDF (Trusted Data Format) encryption.
Experimental: This package is EXPERIMENTAL and may change or be removed at any time Package keysplit provides key splitting functionality for TDF (Trusted Data Format) encryption.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL