Skip to content

Improve NanoTDF Developer Experience #25

@arkavo-com

Description

@arkavo-com

Summary

While integrating NanoTDF encryption into an iOS app, several API design aspects could be improved to enhance developer experience.

Current Challenges

1. Failable Initializers Without Clear Documentation

ResourceLocator has a failable initializer but the conditions for failure aren't immediately clear from the API:

public init?(protocolEnum: ProtocolEnum, body: String, identifier: Data? = nil)

Impact: Developers need to handle optionals and understand validation rules (e.g., body length 1-255 bytes)

2. PublicKeyType Requires Manual PEM Parsing

When working with PEM-formatted public keys from a KAS endpoint, developers must manually:

  • Strip PEM headers/footers
  • Base64 decode
  • Parse DER representation
  • Convert to P256.KeyAgreement.PublicKey

Current code needed:

let base64String = pem
    .replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "")
    .replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
    .replacingOccurrences(of: "\n", with: "")
    .trimmingCharacters(in: .whitespacesAndNewlines)
guard let derData = Data(base64Encoded: base64String) else {
    throw Error()
}
let publicKey = try P256.KeyAgreement.PublicKey(derRepresentation: derData)

3. Module vs Instance Method Naming Collision

The global function createNanoTDF(kas:policy:plaintext:) can collide with instance methods, requiring:

let nanoTDF = try await OpenTDFKit.createNanoTDF(kas: kasMetadata, policy: &policy, plaintext: content)

4. Missing High-Level Decryption API

While encryption has createNanoTDF(), there's no equivalent high-level decryptNanoTDF() function that handles:

  • Parsing NanoTDF structure
  • KAS rewrap
  • Symmetric key derivation
  • Payload decryption

Developers must manually orchestrate these steps using low-level APIs.

Suggested Improvements

1. Throwing Initializers or Validation Helpers

// Option A: Throwing init
public init(protocolEnum: ProtocolEnum, body: String, identifier: Data? = nil) throws

// Option B: Static factory with clear errors
public static func create(protocolEnum: ProtocolEnum, body: String) throws -> ResourceLocator

2. PEM Parsing Helper

extension KasMetadata {
    public static func fromPEM(
        endpoint: String,
        publicKeyPEM: String,
        curve: Curve = .secp256r1
    ) throws -> KasMetadata
}

3. High-Level Decryption API

public func decryptNanoTDF(
    nanoTDF: Data,
    kasRewrapClient: KASRewrapClient
) async throws -> Data

4. Convenience Constructors

extension Policy {
    public static func embedded(
        json: String,
        ownerID: String? = nil
    ) throws -> Policy
}

Use Case

iOS app encrypting user-generated content with policy-based access control, using a remote KAS at https://kas.example.com/kas.

Priority

Medium - These are quality-of-life improvements, not blockers. Current API is functional but requires more boilerplate.

Related

  • NanoTDF specification compliance
  • iOS/mobile developer experience
  • API ergonomics for common use cases

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions