Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
5842fe2
feat(sdk): add custom assertion provider interfaces for external signing
pflynn-virtru Sep 4, 2025
87d6d0d
lint
pflynn-virtru Sep 5, 2025
7e14f2c
lint
pflynn-virtru Sep 5, 2025
72e7207
refactor
pflynn-virtru Sep 11, 2025
7d5f435
feat(examples): improve assertion provider setup in decrypt command
pflynn-virtru Sep 11, 2025
15e708b
refactor(examples, sdk): simplify assertion provider setup in decrypt…
pflynn-virtru Sep 12, 2025
a67f4cd
refactor(sdk): remove new `assertion_binding.go` implementation
pflynn-virtru Sep 12, 2025
37807d6
refactor(sdk, examples): replace default signing providers with publi…
pflynn-virtru Sep 12, 2025
4092840
refactor(sdk): enhance assertion handling and validation flow
pflynn-virtru Sep 15, 2025
5b64102
refactor(sdk, examples): replace payload key mechanism with public ke…
pflynn-virtru Sep 15, 2025
f916d45
refactor(sdk, examples): replace `AssertionProviderFactory` with `Ass…
pflynn-virtru Sep 16, 2025
2716aa5
refactor(sdk): replace signing provider logic with direct key-based s…
pflynn-virtru Sep 16, 2025
4421a85
refactor(sdk, examples): simplify key-based assertion handling and va…
pflynn-virtru Sep 19, 2025
8aee9f4
refactor(sdk, examples): rename `WithAssertionProviderFactory` to `Wi…
pflynn-virtru Sep 24, 2025
5a230de
refactor(sdk, examples): replace builders with binders for assertion …
pflynn-virtru Oct 16, 2025
68d5517
refactor(sdk): introduce binder/validator pattern for custom assertio…
pflynn-virtru Oct 16, 2025
149e7c7
refactor(examples, docs): simplify RSA key handling and add troublesh…
pflynn-virtru Oct 16, 2025
19a2a2f
refactor(sdk, examples): remove deprecated provider interfaces and im…
pflynn-virtru Oct 16, 2025
5905d7b
refactor(sdk, docs): add dual-mode schema validation for system metad…
pflynn-virtru Oct 16, 2025
1e4fc20
Merge remote-tracking branch 'origin/main' into feature/assertion-pro…
pflynn-virtru Oct 16, 2025
e2cd790
refactor(sdk): add constant for manifest file name
pflynn-virtru Oct 16, 2025
3a26a9c
refactor(tests): simplify schema validation logic in assertion provid…
pflynn-virtru Oct 16, 2025
f5dc640
test(assertion_provider): make Bind tests parallel and improve contex…
pflynn-virtru Oct 16, 2025
561313b
test(assertion_provider): remove unused context import in tests
pflynn-virtru Oct 16, 2025
b87c557
refactor(sdk): streamline assertion handling with binder/validator pa…
pflynn-virtru Oct 16, 2025
2c65e07
refactor(sdk): add structured logging for assertion verification and …
pflynn-virtru Oct 16, 2025
67b977a
refactor(sdk): improve assertion verification and setup logic
pflynn-virtru Oct 16, 2025
ac85283
refactor(sdk): add custom JSON marshaling for `Assertion` and utility…
pflynn-virtru Oct 16, 2025
51000c4
refactor(sdk): enforce mandatory cryptographic bindings for assertions
pflynn-virtru Oct 17, 2025
fc1f881
refactor(sdk): add DEK-based auto-signing for assertions without expl…
pflynn-virtru Oct 17, 2025
4c17b41
refactor(sdk): enhance assertion verification mode handling and security
pflynn-virtru Oct 17, 2025
a39a258
refactor(sdk): refine `GetHash` implementation for assertions
pflynn-virtru Oct 17, 2025
5bd34f5
test(sdk): add comprehensive tests for assertion validation and verif…
pflynn-virtru Oct 17, 2025
5d22def
docs(adr): update to reflect custom assertion providers decision
pflynn-virtru Oct 17, 2025
5b489a1
test(sdk): improve parallelism and refactor tests for clarity
pflynn-virtru Oct 17, 2025
4379e93
refactor(sdk): remove regex-based validation and implement schema-bas…
pflynn-virtru Oct 17, 2025
02fddca
refactor(sdk): disable `assertionSchema` claim binding and update rel…
pflynn-virtru Oct 20, 2025
2f6bea9
refactor(sdk): revert `assertionSchema` binding to legacy schema and …
pflynn-virtru Oct 20, 2025
2382cf1
test(sdk): update tests to revert default system metadata schema to v1
pflynn-virtru Oct 20, 2025
8029a0d
refactor(sdk): update to use v2 schema for system metadata by default
pflynn-virtru Oct 20, 2025
968e63d
refactor(sdk): enhance schema handling, revert default to v1 for comp…
pflynn-virtru Oct 20, 2025
75994ab
refactor(sdk): clarify schema handling and improve test coverage
pflynn-virtru Oct 20, 2025
54a7441
refactor(sdk): add statement value to `KeyAssertionBinder` initializa…
pflynn-virtru Oct 21, 2025
df2cea0
refactor(sdk): remove `slog` logging and clean up legacy handling
pflynn-virtru Oct 21, 2025
e22cb61
refactor(examples): enhance assertion cryptographic binding and verif…
pflynn-virtru Oct 21, 2025
0ccb368
docs(assertions): fix syntax in JSON example
pflynn-virtru Oct 21, 2025
9a8e9d1
docs(adr): update custom assertion providers ADR with schema and vali…
pflynn-virtru Oct 21, 2025
7ea7e34
docs(examples): clarify encryption/decryption details and refine asse…
pflynn-virtru Oct 21, 2025
df618cb
Merge remote-tracking branch 'origin/main' into feature/assertion-pro…
pflynn-virtru Oct 22, 2025
44b1832
refactor(sdk): streamline assertion validation, add DEK fallback
pflynn-virtru Oct 22, 2025
4a4749a
refactor(sdk): standardize assertion signature handling and enhance c…
pflynn-virtru Oct 22, 2025
25e7a0e
refactor(sdk): standardize encoding format detection and assertion si…
pflynn-virtru Oct 22, 2025
9d07326
refactor(sdk): centralize assertion signature logic and streamline ma…
pflynn-virtru Oct 22, 2025
a3d0505
chore(sdk): add diagrams (#2840)
cshamrick Oct 28, 2025
683da83
refactor(sdk): introduce schema-aware marshaling for assertions, add …
pflynn-virtru Nov 10, 2025
5de4adc
Merge remote-tracking branch 'origin/main' into feature/assertion-pro…
pflynn-virtru Nov 10, 2025
bc1b8ce
lint
pflynn-virtru Nov 10, 2025
b079ac8
lint
pflynn-virtru Nov 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ coverage.lcov
sdk/nanotest1.ntdf

*.zip
sensitive.txt.tdf
keys/
/examples/sensitive.txt.ntdf
sensitive.txt.ntdf
traces/

# Cucumber / BDD log files
*.log
/examples/examples-cli
/examples/*.tdf
/examples/*.ntdf
235 changes: 235 additions & 0 deletions adr/decisions/2025-10-16-custom-assertion-providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# Custom Assertion Providers for OpenTDF SDK

- **Status**: implemented

## Context and Problem Statement

The OpenTDF SDK needs to support custom assertion signing and validation mechanisms to enable integration with hardware security modules, smart cards, and cloud key management services. Currently, the SDK only supports DEK-based (Data Encryption Key) assertion signing, which prevents integration with:

- Personal Identity Verification (PIV) cards
- Common Access Card (CAC)
- Hardware security modules (HSMs)
- Cloud-based key management services (KMS like AWS KMS, Azure Key Vault, Google Cloud KMS)
- Custom cryptographic implementations

**Problem**: How can we allow developers to provide their own signing and validation logic while maintaining compatibility with existing DEK-based assertion handling and ensuring security?

**Key constraints**:
- Must not break existing TDFs or SDK behavior
- Must maintain cryptographic security guarantees
- Must support cross-SDK interoperability (Java, JavaScript, Go)
- Must be simple enough for developers to implement custom providers

## Decision Drivers

- **Hardware Security Requirements**: Enterprise customers require PIV/CAC card support for government and regulated environments
- **Cloud-Native Architecture**: Modern deployments need cloud KMS integration (AWS, Azure, GCP)
- **Security Compliance**: Organizations need hardware-backed key operations that never expose private keys
- **Developer Experience**: Must be easy to implement custom providers without deep cryptographic expertise
- **Backward Compatibility**: Cannot break existing TDF files or SDK implementations
- **Cross-SDK Interoperability**: Must work with Java and JavaScript SDKs
- **Performance**: Minimal overhead for assertion verification in high-throughput scenarios

## Considered Options

1. **Single Unified Provider Interface** - One interface handling both signing and validation
2. **Binder/Validator Pattern** - Separate interfaces for signing (binder) and validation (validator)
3. **Factory-Based Approach** - Central factory creating providers based on configuration
4. **Plugin Architecture** - Dynamic loading of assertion providers from external modules

## Decision Outcome

**Chosen option**: "Binder/Validator Pattern" (Option 2)

We implement separate `AssertionBinder` (for signing) and `AssertionValidator` (for verification) interfaces with exact string matching for validator dispatch.

### Key Design Elements

**Interfaces**:
```go
type AssertionBinder interface {
// Bind creates and signs an assertion, binding it to the manifest.
// Use ShouldUseHexEncoding(m) for format compatibility.
Bind(ctx context.Context, manifest Manifest) (Assertion, error)
}

type AssertionValidator interface {
Schema() string // Returns schema URI or "*" for wildcard
Verify(ctx context.Context, assertion Assertion, reader Reader) error // Crypto check
Validate(ctx context.Context, assertion Assertion, reader Reader) error // Policy check
}
```

**Architecture**:
```
Assertion System
┌────────────┴────────────┐
│ │
Binders (Signing) Validators (Verification)
│ │
┌───────┴───────┐ ┌───────┴───────┐
│ │ │ │
Key-Based Custom Key-Based Custom
(RSA/EC) (HSM/KMS) (RSA/EC) (HSM/KMS)
│ │ │ │
Built-in External Built-in External
```

**Registration**:
- Binders: `sdk.WithAssertionBinder(binder)`
- Validators: `sdk.WithAssertionValidator(schema, validator)` using exact schema string matching (the `schema` parameter must exactly match the value returned by the validator's `Schema()` method)

### Rationale

**Why not Option 1 (Unified Provider)?**
- Coupling signing and validation logic together makes implementations more complex
- Many use cases only need validation (e.g., verifying signatures from external systems)
- Single interface would need 4+ methods, increasing implementation burden

**Why not Option 3 (Factory-Based)?**
- Adds indirection without clear benefit
- Makes it harder to test and mock providers
- Less flexible for runtime provider selection

**Why not Option 4 (Plugin Architecture)?**
- Over-engineered for the problem
- Adds deployment complexity
- Security concerns with dynamic code loading
- Most providers can be implemented as regular Go packages

**Why Option 2 (Binder/Validator) works best**:
- **Simplicity**: Single-method interfaces are easy to implement
- **Separation of Concerns**: Verify (crypto) vs Validate (policy) are distinct operations
- **Flexibility**: Schema-based validator dispatch enables mixed assertion types in one TDF
- **Efficiency**: Direct registration avoids factory overhead
- **Security**: Clear boundary between cryptographic verification and trust decisions

## Consequences

### Positive

- ✅ **Extensibility**: Supports any signing mechanism (HSM, cloud KMS, hardware tokens)
- ✅ **Simplicity**: Single-method interfaces are straightforward to implement
- ✅ **Flexibility**: Pattern-based dispatch supports mixed assertion types in one TDF
- ✅ **Efficiency**: Post-creation assertion binding without full decryption/re-encryption cycles
- ✅ **Security**: Cryptographic verification is independent from trust policy evaluation
- ✅ **Testability**: Easy to mock and test individual components
- ✅ **Backward Compatible**: Existing DEK-based assertions continue to work unchanged

### Negative

- ❌ **Learning Curve**: Developers must understand when to use binders vs validators
- ❌ **Schema Matching**: Validator schema strings must exactly match registration keys
- ❌ **Documentation Burden**: Need comprehensive examples for common scenarios (PIV/CAC, HSM, KMS)
- ❌ **Validation Complexity**: Two-phase validation (Verify + Validate) may be confusing initially

### Neutral

- ↔️ **API Surface**: Adds 2 new interfaces and 4 new option functions to SDK

## Pros and Cons of the Options

### Option 1: Single Unified Provider Interface

**Pros**:
- Single interface to implement
- Simpler conceptual model

**Cons**:
- Forces all providers to implement both signing and validation
- Harder to compose different signing/validation strategies
- Less flexible for read-only or write-only scenarios
- Larger interface increases implementation burden

### Option 2: Binder/Validator Pattern (CHOSEN)

**Pros**:
- Clear separation between signing and validation
- Single-method interfaces are easy to implement
- Flexible schema-based dispatch
- Independent implementation of crypto vs policy checks

**Cons**:
- Two interfaces to understand
- Schema strings must exactly match between registration and validator implementation

### Option 3: Factory-Based Approach

**Pros**:
- Centralized provider creation
- Could support configuration-based instantiation

**Cons**:
- Adds indirection layer
- Less flexible for runtime selection
- Harder to test
- Doesn't solve core problem better than Option 2

### Option 4: Plugin Architecture

**Pros**:
- Maximum flexibility for third-party providers
- Could support dynamic provider loading

**Cons**:
- Over-engineered for this use case
- Security concerns with dynamic code loading
- Deployment complexity
- Most providers can be standard Go packages

## More Information

### Cryptographic Binding Mechanism

**Standard Format**: `base64(aggregateHash + assertionHash)`

- Binds assertions to all payload segments via aggregateHash
- Cross-SDK compatible (Java, JS, Go)
- `ShouldUseHexEncoding(manifest)` determines legacy (hex) vs modern (raw bytes) encoding
- SDK provides `ComputeAssertionSignature()` helper for consistent implementation

### Verification Modes

The SDK supports three verification modes for different security/compatibility trade-offs:

| Mode | Unknown Assertions | Missing Keys | Missing Binding | Verification Failure | DEK Fallback |
|------------------------|--------------------|--------------|-----------------|----------------------|--------------|
| **PermissiveMode** | Skip + warn | Skip + warn | **FAIL** | Log + continue | Attempted |
| **FailFast (default)** | Skip + warn | **FAIL** | **FAIL** | **FAIL** | Attempted |
| **StrictMode** | **FAIL** | **FAIL** | **FAIL** | **FAIL** | Attempted |

**DEK Fallback Logic**: When no schema-specific validator exists and no explicit verification keys provided:
1. Attempt verification with DEK (payload key)
2. If JWT verification fails (wrong key) → Treat as unknown assertion (skip per mode)
3. If JWT succeeds but hash/binding fails → **FAIL** (tampering detected)
4. If verification succeeds → Assertion validated with DEK

This enables forward compatibility (new assertion types are skipped) while detecting tampering (DEK-signed assertions are validated).

**Recommendation**: Use `FailFast` for production (default), `PermissiveMode` only for development/testing, `StrictMode` for high-security environments.

### Security Considerations

1. **Mandatory Bindings**: All assertions MUST have cryptographic bindings - unsigned assertions are rejected immediately
2. **Key Management**: Private keys should remain in HSM/PIV/CAC and never be exposed
3. **Fail-Secure Validation**: Validators fail securely when keys are missing (not silently skip)
4. **Binding Integrity**: Assertion signature format binds to all payload segments via aggregateHash
5. **TDFVersion Spoofing**: `ShouldUseHexEncoding()` checks unprotected `TDFVersion` field - use ONLY for format detection, NOT security decisions. Always verify cryptographic bindings regardless of version.
6. **DEK Fallback Validation**: Assertions without schema-specific validators attempt DEK verification as fallback, enabling tampering detection while maintaining forward compatibility

### Implementation Requirements

**Custom Binders/Validators must**:
- Use `ShouldUseHexEncoding(manifest)` and `ComputeAssertionSignature()` for cross-SDK compatibility
- Compute `aggregateHash` from manifest segments during binding/verification (not pre-store)
- Verify cryptographic bindings in `Verify()`, enforce policy in `Validate()`

## Links

- [OpenTDF Specification](https://github.com/opentdf/spec)
- [PKCS#11 Specification](http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/os/pkcs11-base-v2.40-os.html)
- [X.509 Certificate Standard](https://www.itu.int/rec/T-REC-X.509)
- [PIV Card Specification (NIST SP 800-73-4)](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf)
- [PR #2687 - Implementation](https://github.com/opentdf/platform/pull/2687)
- [MADR Template](https://adr.github.io/madr/)
Loading
Loading