-
Notifications
You must be signed in to change notification settings - Fork 1
Description
[CRITICAL] Implement OpenTDF-based Key Management for Content Protection
Priority
🚨 P0 (Critical - Blocker)
Description
Currently, the FairPlay integration uses a hardcoded zero-filled placeholder content key, which makes the DRM system non-functional for actual content protection. This issue proposes implementing a comprehensive OpenTDF-based key management architecture that integrates policy management via smart contracts, external key services, and OpenTDF SDK functionality.
Location: src/modules/media_api.rs:449-456
// 5. TODO: Extract content key (DEK) from policy/storage
let content_key = vec![0x00u8; 16]; // PLACEHOLDER: Replace with actual DEK retrievalSecurity Impact
- All FairPlay-protected content currently uses the same zero-filled key
- Any attacker can decrypt all content
- Violates DRM security requirements
- Potential legal/compliance issues with content providers
- CVSS Score: 9.8 (Critical)
Background & Motivation
Why OpenTDF Instead of AWS KMS?
OpenTDF Advantages:
- Open Standards: OpenTDF is an open-source standard for data protection, ensuring interoperability and avoiding vendor lock-in
- Policy-Driven Security: Attribute-Based Access Control (ABAC) policies provide fine-grained, dynamic access control
- Smart Contract Integration: Policies can be managed via blockchain smart contracts within the Arkavo ecosystem
- Decentralized Architecture: Aligns with Arkavo's decentralized approach to content protection
- Cost Efficiency: No per-request fees or vendor-specific pricing
- Flexibility: Can integrate with multiple key storage backends (HSM, KMS, distributed key management)
- Compliance: Built-in support for data sovereignty and regulatory compliance requirements
Arkavo Integration Benefits:
- Seamless integration with existing Arkavo smart contract infrastructure
- Unified policy management across TDF3 and FairPlay protocols
- Consistent security model across all DRM implementations
- Enhanced auditability through blockchain-based policy tracking
Architecture Overview
The proposed architecture consists of three integrated components:
┌─────────────────────────────────────────────────────────────┐
│ Arkavo Media DRM │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ FairPlay/TDF3 │◄────►│ OpenTDF SDK │ │
│ │ Protocol Layer │ │ Integration │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Policy Evaluation Engine │ │
│ │ (Smart Contract + Local Cache) │ │
│ └──────────────────────────────────────────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Smart Contract │ │ OpenTDF KAS │ │
│ │ Policy Store │ │ (Key Access │ │
│ │ (Blockchain) │ │ Service) │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │
└─────────────────────────────────────┼──────────────────────┘
│
▼
┌──────────────────┐
│ Key Storage │
│ Backend │
│ (HSM/KMS/Redis) │
└──────────────────┘
Component 1: OpenTDF Policy Management via Smart Contracts
Overview
Implement policy management using Arkavo's smart contract infrastructure to provide decentralized, auditable, and flexible access control.
Technical Requirements
-
Smart Contract Policy Schema
pub struct ContentPolicy { pub asset_id: String, pub policy_id: String, pub attributes: Vec<Attribute>, pub dissem: Vec<String>, // Dissemination controls pub expiration: Option<i64>, // Unix timestamp pub max_uses: Option<u32>, pub geo_restrictions: Vec<String>, // ISO 3166-1 alpha-2 codes pub device_restrictions: Vec<DeviceType>, pub created_at: i64, pub updated_at: i64, pub version: u32, } pub struct Attribute { pub namespace: String, pub name: String, pub value: String, } pub enum DeviceType { Mobile, Desktop, TV, Web, }
-
Policy Evaluation Interface
#[async_trait] pub trait PolicyEvaluator: Send + Sync { /// Evaluate policy for content access async fn evaluate_policy( &self, asset_id: &str, user_attributes: &[Attribute], context: &AccessContext, ) -> Result<PolicyDecision, PolicyError>; /// Get policy for asset async fn get_policy( &self, asset_id: &str, ) -> Result<ContentPolicy, PolicyError>; /// Update policy (requires authorization) async fn update_policy( &self, policy: ContentPolicy, signature: &[u8], ) -> Result<(), PolicyError>; } pub struct AccessContext { pub user_id: String, pub client_ip: String, pub geo_region: Option<String>, pub device_type: DeviceType, pub timestamp: i64, } pub struct PolicyDecision { pub allowed: bool, pub reason: Option<String>, pub key_access_info: Option<KeyAccessInfo>, }
-
Smart Contract Integration
- Use existing Arkavo smart contract infrastructure
- Implement policy CRUD operations via contract calls
- Cache policies locally with TTL for performance
- Subscribe to policy update events from blockchain
Implementation Details
pub struct SmartContractPolicyManager {
contract_client: Arc<ContractClient>,
policy_cache: Arc<RwLock<LruCache<String, CachedPolicy>>>,
cache_ttl: Duration,
}
impl SmartContractPolicyManager {
pub async fn new(
contract_address: String,
cache_size: usize,
cache_ttl: Duration,
) -> Result<Self, PolicyError> {
let contract_client = ContractClient::connect(contract_address).await?;
Ok(Self {
contract_client: Arc::new(contract_client),
policy_cache: Arc::new(RwLock::new(LruCache::new(cache_size))),
cache_ttl,
})
}
async fn fetch_policy_from_contract(
&self,
asset_id: &str,
) -> Result<ContentPolicy, PolicyError> {
// Call smart contract to retrieve policy
let policy_data = self.contract_client
.call_method("getPolicy", &[asset_id])
.await?;
// Deserialize policy from contract response
let policy: ContentPolicy = serde_json::from_slice(&policy_data)?;
Ok(policy)
}
}
#[async_trait]
impl PolicyEvaluator for SmartContractPolicyManager {
async fn evaluate_policy(
&self,
asset_id: &str,
user_attributes: &[Attribute],
context: &AccessContext,
) -> Result<PolicyDecision, PolicyError> {
// Get policy (from cache or contract)
let policy = self.get_policy(asset_id).await?;
// Evaluate ABAC rules
let allowed = self.evaluate_abac_rules(
&policy,
user_attributes,
context,
)?;
if !allowed {
return Ok(PolicyDecision {
allowed: false,
reason: Some("Policy evaluation failed".to_string()),
key_access_info: None,
});
}
// Generate key access information
let key_access_info = KeyAccessInfo {
kas_url: self.get_kas_url(&policy)?,
policy_binding: self.create_policy_binding(&policy)?,
};
Ok(PolicyDecision {
allowed: true,
reason: None,
key_access_info: Some(key_access_info),
})
}
async fn get_policy(
&self,
asset_id: &str,
) -> Result<ContentPolicy, PolicyError> {
// Check cache first
{
let cache = self.policy_cache.read().await;
if let Some(cached) = cache.get(asset_id) {
if !cached.is_expired() {
return Ok(cached.policy.clone());
}
}
}
// Fetch from contract
let policy = self.fetch_policy_from_contract(asset_id).await?;
// Update cache
{
let mut cache = self.policy_cache.write().await;
cache.put(
asset_id.to_string(),
CachedPolicy {
policy: policy.clone(),
cached_at: Utc::now(),
},
);
}
Ok(policy)
}
async fn update_policy(
&self,
policy: ContentPolicy,
signature: &[u8],
) -> Result<(), PolicyError> {
// Verify signature
self.verify_policy_update_signature(&policy, signature)?;
// Update contract
self.contract_client
.call_method("updatePolicy", &[
serde_json::to_vec(&policy)?,
signature.to_vec(),
])
.await?;
// Invalidate cache
{
let mut cache = self.policy_cache.write().await;
cache.pop(&policy.asset_id);
}
Ok(())
}
}Component 2: OpenTDF KAS (Key Access Service) Integration
Overview
Implement integration with OpenTDF Key Access Service for secure key retrieval and management.
Technical Requirements
-
KAS Client Interface
#[async_trait] pub trait KasClient: Send + Sync { /// Request key from KAS async fn request_key( &self, request: KeyRequest, ) -> Result<KeyResponse, KasError>; /// Rewrap key (for key rotation) async fn rewrap_key( &self, request: RewrapRequest, ) -> Result<RewrapResponse, KasError>; /// Health check async fn health_check(&self) -> Result<(), KasError>; } pub struct KeyRequest { pub policy_binding: Vec<u8>, pub client_public_key: Vec<u8>, pub algorithm: String, pub user_attributes: Vec<Attribute>, } pub struct KeyResponse { pub wrapped_key: Vec<u8>, pub policy: Vec<u8>, pub algorithm: String, }
-
OpenTDF KAS Implementation
pub struct OpenTdfKasClient { kas_url: String, http_client: reqwest::Client, public_key: P256PublicKey, private_key: SecretKey, } impl OpenTdfKasClient { pub async fn new( kas_url: String, cert_path: Option<PathBuf>, ) -> Result<Self, KasError> { // Generate or load key pair let private_key = SecretKey::random(&mut OsRng); let public_key = P256PublicKey::from(&private_key); // Configure HTTP client with TLS let mut http_client_builder = reqwest::Client::builder() .timeout(Duration::from_secs(30)) .pool_max_idle_per_host(10); if let Some(cert) = cert_path { let cert_bytes = std::fs::read(cert)?; let cert = reqwest::Certificate::from_pem(&cert_bytes)?; http_client_builder = http_client_builder.add_root_certificate(cert); } let http_client = http_client_builder.build()?; Ok(Self { kas_url, http_client, public_key, private_key, }) } } #[async_trait] impl KasClient for OpenTdfKasClient { async fn request_key( &self, request: KeyRequest, ) -> Result<KeyResponse, KasError> { // Construct KAS request per OpenTDF spec let kas_request = serde_json::json!({ "keyAccess": { "type": "wrapped", "url": self.kas_url, "protocol": "kas", "wrappedKey": base64::encode(&request.policy_binding), }, "policy": base64::encode(&request.policy_binding), "clientPublicKey": base64::encode(&request.client_public_key), "algorithm": request.algorithm, }); // Send request to KAS let response = self.http_client .post(format!("{}/v2/rewrap", self.kas_url)) .json(&kas_request) .send() .await?; if !response.status().is_success() { return Err(KasError::RequestFailed( response.status(), response.text().await?, )); } let kas_response: serde_json::Value = response.json().await?; // Extract wrapped key let wrapped_key = base64::decode( kas_response["entityWrappedKey"] .as_str() .ok_or(KasError::InvalidResponse)?, )?; Ok(KeyResponse { wrapped_key, policy: request.policy_binding, algorithm: request.algorithm, }) } async fn rewrap_key( &self, request: RewrapRequest, ) -> Result<RewrapResponse, KasError> { // Implement key rewrapping for rotation let rewrap_request = serde_json::json!({ "signedRequestToken": request.signed_token, }); let response = self.http_client .post(format!("{}/v2/rewrap", self.kas_url)) .json(&rewrap_request) .send() .await?; let rewrap_response: serde_json::Value = response.json().await?; Ok(RewrapResponse { wrapped_key: base64::decode( rewrap_response["entityWrappedKey"] .as_str() .ok_or(KasError::InvalidResponse)?, )?, }) } async fn health_check(&self) -> Result<(), KasError> { let response = self.http_client .get(format!("{}/healthz", self.kas_url)) .send() .await?; if response.status().is_success() { Ok(()) } else { Err(KasError::Unhealthy) } } }
-
Key Storage Backend
#[async_trait] pub trait KeyStorage: Send + Sync { /// Store encrypted key async fn store_key( &self, asset_id: &str, encrypted_key: &[u8], metadata: KeyMetadata, ) -> Result<(), StorageError>; /// Retrieve encrypted key async fn get_key( &self, asset_id: &str, ) -> Result<(Vec<u8>, KeyMetadata), StorageError>; /// Delete key async fn delete_key( &self, asset_id: &str, ) -> Result<(), StorageError>; } pub struct KeyMetadata { pub created_at: i64, pub algorithm: String, pub key_version: u32, pub policy_version: u32, }
Component 3: OpenTDF SDK Integration
Overview
Implement OpenTDF SDK functionality within Arkavo for TDF3 creation, encryption, and decryption.
Technical Requirements
-
TDF3 Operations
pub struct OpenTdfSdk { kas_client: Arc<dyn KasClient>, policy_evaluator: Arc<dyn PolicyEvaluator>, } impl OpenTdfSdk { pub async fn create_tdf( &self, plaintext: &[u8], policy: ContentPolicy, ) -> Result<Vec<u8>, TdfError> { // Generate DEK let dek = self.generate_dek()?; // Encrypt content with DEK let ciphertext = self.encrypt_content(plaintext, &dek)?; // Wrap DEK with KAS public key let wrapped_dek = self.wrap_dek(&dek, &policy).await?; // Create TDF3 manifest let manifest = self.create_manifest( &wrapped_dek, &policy, ciphertext.len(), )?; // Assemble TDF3 package let tdf = self.assemble_tdf(&manifest, &ciphertext)?; Ok(tdf) } pub async fn decrypt_tdf( &self, tdf: &[u8], user_attributes: &[Attribute], ) -> Result<Vec<u8>, TdfError> { // Parse TDF3 package let (manifest, ciphertext) = self.parse_tdf(tdf)?; // Extract policy let policy = self.extract_policy(&manifest)?; // Evaluate policy let decision = self.policy_evaluator .evaluate_policy( &policy.asset_id, user_attributes, &AccessContext::current(), ) .await?; if !decision.allowed { return Err(TdfError::PolicyDenied( decision.reason.unwrap_or_default(), )); } // Unwrap DEK via KAS let dek = self.unwrap_dek(&manifest, user_attributes).await?; // Decrypt content let plaintext = self.decrypt_content(&ciphertext, &dek)?; Ok(plaintext) } async fn wrap_dek( &self, dek: &[u8], policy: &ContentPolicy, ) -> Result<Vec<u8>, TdfError> { // Create policy binding let policy_binding = self.create_policy_binding(policy)?; // Request key wrapping from KAS let request = KeyRequest { policy_binding, client_public_key: self.get_public_key()?, algorithm: "AES-256-GCM".to_string(), user_attributes: vec![], }; let response = self.kas_client.request_key(request).await?; Ok(response.wrapped_key) } async fn unwrap_dek( &self, manifest: &TdfManifest, user_attributes: &[Attribute], ) -> Result<Vec<u8>, TdfError> { // Extract wrapped key from manifest let wrapped_key = &manifest.encryption_information.key_access[0].wrapped_key; // Request key unwrapping from KAS let request = KeyRequest { policy_binding: manifest.encryption_information.policy.clone(), client_public_key: self.get_public_key()?, algorithm: manifest.encryption_information.method.algorithm.clone(), user_attributes: user_attributes.to_vec(), }; let response = self.kas_client.request_key(request).await?; // Unwrap DEK using ECDH let dek = self.perform_ecdh_unwrap(&response.wrapped_key)?; Ok(dek) } }
-
Integration with FairPlay Handler
pub async fn get_content_key_opentdf( &self, asset_id: &str, user_attributes: &[Attribute], context: &AccessContext, ) -> Result<Vec<u8>, KeyRetrievalError> { // Evaluate policy let decision = self.policy_evaluator .evaluate_policy(asset_id, user_attributes, context) .await?; if !decision.allowed { return Err(KeyRetrievalError::PolicyDenied( decision.reason.unwrap_or_default(), )); } // Get key access info from policy decision let key_access_info = decision.key_access_info .ok_or(KeyRetrievalError::NoKeyAccessInfo)?; // Request key from KAS let key_request = KeyRequest { policy_binding: key_access_info.policy_binding, client_public_key: self.get_public_key()?, algorithm: "AES-128-GCM".to_string(), user_attributes: user_attributes.to_vec(), }; let key_response = self.kas_client .request_key(key_request) .await?; // Unwrap key let dek = self.unwrap_key(&key_response.wrapped_key)?; // Validate key if dek.len() != 16 { return Err(KeyRetrievalError::InvalidKeySize); } if dek.iter().all(|&b| b == 0) { return Err(KeyRetrievalError::InvalidKey); } Ok(dek) }
Dependencies & Prerequisites
External Dependencies
- OpenTDF Rust SDK (if available) or implement OpenTDF protocol
- Smart Contract Client Library (existing Arkavo infrastructure)
- Cryptographic Libraries:
p256for ECDH operationsaes-gcmfor content encryptionhkdffor key derivationsha2for hashing
Infrastructure Requirements
-
OpenTDF KAS Deployment:
- Deploy OpenTDF KAS service (Docker/Kubernetes)
- Configure TLS certificates
- Set up key storage backend (HSM, KMS, or Redis)
-
Smart Contract Deployment:
- Deploy policy management contracts
- Configure contract access permissions
- Set up event listeners for policy updates
-
Configuration:
[opentdf] kas_url = "https://kas.arkavo.com" kas_public_key_path = "./config/kas_public_key.pem" kas_cert_path = "./config/kas_cert.pem" [policy] contract_address = "0x..." cache_size = 1000 cache_ttl_seconds = 300 [key_storage] backend = "redis" # or "hsm", "kms" redis_url = "redis://localhost:6379"
Implementation Plan
Phase 1: Core Infrastructure (Week 1-2)
- Implement
PolicyEvaluatortrait andSmartContractPolicyManager - Implement
KasClienttrait andOpenTdfKasClient - Set up policy caching mechanism
- Add configuration management
Phase 2: OpenTDF SDK Integration (Week 2-3)
- Implement TDF3 creation and decryption
- Implement key wrapping/unwrapping
- Integrate with existing crypto module
- Add ECDH operations
Phase 3: FairPlay Integration (Week 3-4)
- Replace placeholder key retrieval in
media_api.rs - Integrate policy evaluation with key requests
- Add user attribute extraction from session
- Implement key validation
Phase 4: Testing & Validation (Week 4-5)
- Unit tests for all components
- Integration tests with mock KAS
- End-to-end tests with real KAS
- Performance testing (< 10ms overhead target)
- Security testing
Phase 5: Documentation & Deployment (Week 5-6)
- Architecture documentation
- API documentation
- Deployment guides
- Operational runbooks
- Security audit
Acceptance Criteria
Functional Requirements
- Policy evaluation via smart contracts working
- KAS integration functional for key requests
- OpenTDF SDK operations (create/decrypt TDF) working
- FairPlay integration using OpenTDF keys
- Per-content unique keys supported
- Key rotation mechanism implemented
- Runtime validation prevents zero-filled keys
Non-Functional Requirements
- Key retrieval latency < 10ms (cached policies)
- Key retrieval latency < 50ms (uncached policies)
- Policy cache hit rate > 90%
- Support for 1000+ concurrent key requests
- Zero downtime during key rotation
Testing Requirements
- Unit test coverage > 80%
- Integration tests with mock KAS passing
- End-to-end tests with real KAS passing
- Performance tests meeting latency targets
- Security tests passing (no zero-filled keys, proper validation)
Documentation Requirements
- Architecture Decision Record (ADR) created
- API documentation complete
- Deployment guide written
- Operational runbook created
- Security audit report completed
Security Requirements
- All keys encrypted at rest
- TLS for all KAS communication
- Policy signatures verified
- Audit logging for all key access
- No sensitive data in logs
Testing Strategy
Unit Tests
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_policy_evaluation_allowed() {
let policy_manager = create_mock_policy_manager();
let user_attrs = vec![
Attribute {
namespace: "https://arkavo.com".to_string(),
name: "role".to_string(),
value: "premium".to_string(),
},
];
let decision = policy_manager
.evaluate_policy("asset-123", &user_attrs, &mock_context())
.await
.unwrap();
assert!(decision.allowed);
}
#[tokio::test]
async fn test_kas_key_request() {
let kas_client = create_mock_kas_client();
let request = KeyRequest {
policy_binding: vec![1, 2, 3],
client_public_key: vec![4, 5, 6],
algorithm: "AES-256-GCM".to_string(),
user_attributes: vec![],
};
let response = kas_client.request_key(request).await.unwrap();
assert!(!response.wrapped_key.is_empty());
}
#[tokio::test]
async fn test_zero_filled_key_rejected() {
let sdk = create_test_sdk();
let zero_key = vec![0u8; 16];
let result = sdk.validate_key(&zero_key);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), KeyRetrievalError::InvalidKey));
}
}Integration Tests
#[tokio::test]
#[ignore] // Requires KAS service
async fn test_end_to_end_key_retrieval() {
let config = load_test_config();
let sdk = OpenTdfSdk::new(config).await.unwrap();
// Create test policy
let policy = create_test_policy();
// Request key
let key = sdk.get_content_key_opentdf(
"test-asset",
&test_user_attributes(),
&test_context(),
).await.unwrap();
// Validate key
assert_eq!(key.len(), 16);
assert!(!key.iter().all(|&b| b == 0));
}Performance Considerations
Optimization Strategies
-
Policy Caching:
- LRU cache with configurable TTL
- Cache invalidation on policy updates
- Target: 90%+ cache hit rate
-
Connection Pooling:
- Reuse HTTP connections to KAS
- Connection pool size: 10-20 per instance
-
Async Operations:
- All I/O operations async
- Parallel policy evaluation when possible
-
Key Prefetching:
- Prefetch keys for popular content
- Background refresh before expiration
Performance Targets
- Policy evaluation (cached): < 1ms
- Policy evaluation (uncached): < 50ms
- KAS key request: < 30ms
- Total key retrieval: < 50ms (P95)
Security Considerations
Threat Model
- Key Exposure: Keys must never be logged or exposed in plaintext
- Policy Bypass: Policy evaluation must be tamper-proof
- Replay Attacks: Prevent reuse of old policy decisions
- Man-in-the-Middle: All KAS communication over TLS
Security Controls
- Encryption at Rest: All keys encrypted in storage
- Encryption in Transit: TLS 1.3 for all network communication
- Access Control: Policy-based access to keys
- Audit Logging: All key access logged with user context
- Key Rotation: Automated key rotation every 90 days
- Zero-Knowledge: KAS cannot decrypt content
Monitoring & Observability
Metrics to Track
pub struct KeyManagementMetrics {
pub key_requests_total: Counter,
pub key_request_duration: Histogram,
pub policy_cache_hits: Counter,
pub policy_cache_misses: Counter,
pub kas_errors: Counter,
pub policy_evaluation_duration: Histogram,
}Alerts
- High Error Rate: > 1% key request failures
- High Latency: P95 > 100ms
- Low Cache Hit Rate: < 80%
- KAS Unavailable: Health check failures
Logging
// Structured logging for key access
log::info!(
target: "key_access",
user_id = %user_id,
asset_id = %asset_id,
policy_version = policy.version,
decision = %decision.allowed,
latency_ms = latency,
"Key access request"
);Migration Strategy
Phase 1: Parallel Operation
- Deploy OpenTDF infrastructure alongside existing system
- Route 10% of traffic to new system
- Monitor metrics and errors
- Gradually increase traffic
Phase 2: Full Migration
- Route 100% of new sessions to OpenTDF
- Maintain backward compatibility for existing sessions
- Migrate existing keys to new system
Phase 3: Cleanup
- Remove placeholder key code
- Remove old key management code
- Update documentation
Rollback Plan
Rollback Triggers
- Error rate > 5%
- Latency P95 > 200ms
- KAS unavailability > 5 minutes
- Critical security issue discovered
Rollback Procedure
- Route traffic back to placeholder (temporary)
- Investigate root cause
- Fix issues in staging
- Redeploy with fixes
Related Issues & PRs
- PR FairPlay SDK 26 #25 - FairPlay SDK 26 Integration
- Issue [HIGH] Refactor vendor SDK management to external dependency #27 - Vendor SDK management
- Issue [HIGH] Add comprehensive unit test coverage for FairPlay integration #28 - Unit test coverage
- Issue [HIGH] Implement health checks and graceful shutdown for production readiness #30 - Health checks and graceful shutdown