Summary
While integrating OpenTDFKit for encrypting user content as TDF files, I encountered several areas where developer experience could be improved, particularly around StandardTDF encryption/decryption workflows.
Current Implementation Challenges
1. Key Unwrapping After KAS Rewrap
The KASRewrapClient.rewrapStandardTDF method returns StandardTDFKASRewrapResult with wrapped keys, but there's no clear documentation or helper method for unwrapping these keys to obtain the symmetric key.
Current Code:
let rewrapResult = try await rewrapClient.rewrapStandardTDF(
manifest: container.manifest,
clientPublicKeyPEM: clientPublicKeyPEM
)
// How to unwrap rewrapResult.wrappedKeys to get SymmetricKey?
let (_, rewrappedKeyData) = rewrapResult.wrappedKeys.first!
// Need guidance on proper ECDH key derivation here
let symmetricKey = SymmetricKey(data: rewrappedKeyData) // Is this correct?
Suggested Improvement:
- Add a helper method or documentation explaining the key derivation process
- Example:
StandardTDFCrypto.deriveSymmetricKey(from: rewrappedKeyData, using: clientPrivateKey)
2. StandardTDFDecryptor Limitations
The StandardTDFDecryptor has methods for file-based decryption but lacks a convenient end-to-end method that handles KAS rewrap automatically.
Current Options:
decryptFile(inputURL:outputURL:symmetricKey:) - Requires pre-obtained symmetric key
decryptFile(inputURL:outputURL:privateKeyPEM:) - Works with local private key but not KAS
Suggested Improvement:
Add a method that handles the full KAS rewrap flow:
public func decryptFile(
inputURL: URL,
outputURL: URL,
kasURL: URL,
oauthToken: String
) async throws
// Or even simpler:
public func decrypt(
container: StandardTDFContainer,
kasClient: KASRewrapClient
) async throws -> Data
3. KAS Public Key Fetching
Fetching the KAS public key requires manual URL construction and JSON parsing. This is a common operation that could be simplified.
Current Code:
let url = URL(string: "\(kasEndpoint)/v2/kas_public_key")!
let (data, _) = try await URLSession.shared.data(from: url)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let publicKey = json["publicKey"] as? String
Suggested Improvement:
Add a static helper method:
public extension StandardTDFKasInfo {
static func fetchPublicKey(from kasURL: URL) async throws -> String
}
4. DER to PEM Conversion
Converting key representations for KAS is a common need but not provided by the library.
Current Code:
private func convertDERToPEM(_ derData: Data, type: String) throws -> String {
let base64 = derData.base64EncodedString(options: [.lineLength64Characters, .endLineWithLineFeed])
return "-----BEGIN \(type)-----\n\(base64)-----END \(type)-----\n"
}
Suggested Improvement:
Add to StandardTDFCrypto or a KeyConversion utility:
public extension StandardTDFCrypto {
static func pemRepresentation(of publicKey: P256.KeyAgreement.PublicKey) -> String
}
5. Documentation Gaps
Missing Documentation:
- End-to-end example of StandardTDF encryption with KAS
- Complete decrypt workflow including KAS rewrap
- Key derivation process after rewrap
- Policy structure examples with real-world use cases (e.g., user access control)
Suggested Additions:
- Add "Getting Started" guide for StandardTDF with KAS
- Document the full encrypt/decrypt cycle
- Include code examples in README
- API documentation for
StandardTDFKASRewrapResult
6. Policy Builder Ergonomics
Creating policy JSON manually is error-prone and verbose.
Current Code:
let policyJSON = """
{
"uuid": "\(UUID().uuidString)",
"body": {
"dataAttributes": [],
"dissem": ["\(ownerId)"]
}
}
""".data(using: .utf8)!
let policy = try StandardTDFPolicy(json: policyJSON)
Suggested Improvement:
Add a policy builder:
let policy = StandardTDFPolicy.Builder()
.withUUID(UUID().uuidString)
.withDissemination([ownerId])
.withDataAttributes([])
.build()
Suggested New APIs
High-Level Encryption/Decryption
// Simplified encryption/decryption client
public struct StandardTDFClient {
public init(kasURL: URL, oauthToken: String)
public func encrypt(
plaintext: Data,
policy: StandardTDFPolicy,
mimeType: String?
) async throws -> StandardTDFContainer
public func decrypt(
container: StandardTDFContainer
) async throws -> Data
}
Key Management Helpers
public extension StandardTDFCrypto {
// Unwrap symmetric key after KAS rewrap
static func unwrapSymmetricKey(
rewrappedKey: Data,
clientPrivateKey: P256.KeyAgreement.PrivateKey,
kasPublicKey: P256.KeyAgreement.PublicKey?
) throws -> SymmetricKey
}
Implementation Context
Use Case: iOS app encrypting user-generated content as TDF files
- Files stored locally in Documents directory
- Shared via iOS share sheet or web links
- iOS 18.0+ deployment target
Current Status: Successfully building and integrating, but required several workarounds and assumptions about key derivation.
Priority
Medium-High - These improvements would significantly reduce integration friction for new OpenTDFKit users.
Workarounds Applied
- Manual key unwrapping (may be incorrect for production)
- Custom KAS public key fetching
- Custom DER-to-PEM conversion
- Manual policy JSON construction
- Direct use of
KASRewrapClient instead of higher-level APIs
Additional Context
The library is very powerful and well-structured at the low level, but lacks some convenience APIs for common workflows. Adding these would make OpenTDFKit more accessible to developers who want secure TDF encryption without deep cryptographic knowledge.
Thank you for maintaining this library! Happy to contribute PRs for any of these improvements if there's interest.
Summary
While integrating OpenTDFKit for encrypting user content as TDF files, I encountered several areas where developer experience could be improved, particularly around StandardTDF encryption/decryption workflows.
Current Implementation Challenges
1. Key Unwrapping After KAS Rewrap
The
KASRewrapClient.rewrapStandardTDFmethod returnsStandardTDFKASRewrapResultwith wrapped keys, but there's no clear documentation or helper method for unwrapping these keys to obtain the symmetric key.Current Code:
Suggested Improvement:
StandardTDFCrypto.deriveSymmetricKey(from: rewrappedKeyData, using: clientPrivateKey)2. StandardTDFDecryptor Limitations
The
StandardTDFDecryptorhas methods for file-based decryption but lacks a convenient end-to-end method that handles KAS rewrap automatically.Current Options:
decryptFile(inputURL:outputURL:symmetricKey:)- Requires pre-obtained symmetric keydecryptFile(inputURL:outputURL:privateKeyPEM:)- Works with local private key but not KASSuggested Improvement:
Add a method that handles the full KAS rewrap flow:
3. KAS Public Key Fetching
Fetching the KAS public key requires manual URL construction and JSON parsing. This is a common operation that could be simplified.
Current Code:
Suggested Improvement:
Add a static helper method:
4. DER to PEM Conversion
Converting key representations for KAS is a common need but not provided by the library.
Current Code:
Suggested Improvement:
Add to StandardTDFCrypto or a KeyConversion utility:
5. Documentation Gaps
Missing Documentation:
Suggested Additions:
StandardTDFKASRewrapResult6. Policy Builder Ergonomics
Creating policy JSON manually is error-prone and verbose.
Current Code:
Suggested Improvement:
Add a policy builder:
Suggested New APIs
High-Level Encryption/Decryption
Key Management Helpers
Implementation Context
Use Case: iOS app encrypting user-generated content as TDF files
Current Status: Successfully building and integrating, but required several workarounds and assumptions about key derivation.
Priority
Medium-High - These improvements would significantly reduce integration friction for new OpenTDFKit users.
Workarounds Applied
KASRewrapClientinstead of higher-level APIsAdditional Context
The library is very powerful and well-structured at the low level, but lacks some convenience APIs for common workflows. Adding these would make OpenTDFKit more accessible to developers who want secure TDF encryption without deep cryptographic knowledge.
Thank you for maintaining this library! Happy to contribute PRs for any of these improvements if there's interest.