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:
- TDF Layer (tdf.Writer): Handles encryption, assertions, and TDF protocol logic
- 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 ¶
- Constants
- Variables
- type AppliesToState
- type Assertion
- type AssertionConfig
- type AssertionKey
- type AssertionKeyAlg
- type AssertionType
- type AssertionVerificationKeys
- type BaseConfig
- type Binding
- type BindingMethod
- type EncryptedMetadata
- type EncryptionInformation
- type FinalizeResult
- type IntegrityAlgorithm
- type IntegrityInformation
- type KeyAccess
- type Manifest
- type Method
- type Option
- func WithAssertions(assertions ...AssertionConfig) Option[*WriterFinalizeConfig]
- func WithAttributeValues(values []*policy.Value) Option[*WriterFinalizeConfig]
- func WithDefaultKAS(kas *policy.SimpleKasKey) Option[*WriterFinalizeConfig]
- func WithDefaultKASForWriter(kas *policy.SimpleKasKey) Option[*WriterConfig]
- func WithEncryptedMetadata(metadata string) Option[*WriterFinalizeConfig]
- func WithExcludeVersionFromManifest(exclude bool) Option[*WriterFinalizeConfig]
- func WithInitialAttributes(values []*policy.Value) Option[*WriterConfig]
- func WithIntegrityAlgorithm(algo IntegrityAlgorithm) Option[*WriterConfig]
- func WithPayloadMimeType(mimeType string) Option[*WriterFinalizeConfig]
- func WithSegmentIntegrityAlgorithm(algo IntegrityAlgorithm) Option[*WriterConfig]
- func WithSegments(indices []int) Option[*WriterFinalizeConfig]
- type Payload
- type Policy
- type PolicyAttribute
- type PolicyBinding
- type PolicyBody
- type Reader
- type ReaderConfig
- type RootSignature
- type Scope
- type Segment
- type SegmentResult
- type Statement
- type Writer
- func (w *Writer) Finalize(ctx context.Context, opts ...Option[*WriterFinalizeConfig]) (*FinalizeResult, error)
- func (w *Writer) GetManifest(ctx context.Context, opts ...Option[*WriterFinalizeConfig]) (*Manifest, error)
- func (w *Writer) WriteSegment(ctx context.Context, index int, data []byte) (*SegmentResult, error)
- type WriterConfig
- type WriterFinalizeConfig
Examples ¶
Constants ¶
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" )
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 )
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 ¶
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.
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 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 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 Policy ¶
type Policy struct {
UUID string `json:"uuid"`
Body PolicyBody `json:"body"`
}
type PolicyAttribute ¶
type PolicyBinding ¶
type PolicyBody ¶
type PolicyBody struct {
DataAttributes []PolicyAttribute `json:"dataAttributes"`
Dissem []string `json:"dissem"`
}
type ReaderConfig ¶
type ReaderConfig struct {
BaseConfig
}
ReaderConfig contains configuration options for TDF Reader creation. Reserved for future reader configuration options.
type RootSignature ¶
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" )
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 ¶
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 ¶
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 ¶
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:
- Input validation (index >= 0, writer not finalized, no duplicate segments)
- AES-256-GCM encryption of the segment data
- HMAC signature calculation for integrity verification
- 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.
Source Files
¶
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. |