Running the OpenTDF Java SDK on Android #3489
arkavo-com
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
We just shipped our app's Android client on top of the OpenTDF Java SDK (
io.opentdf.platform:sdk0.15.0). Content is encrypted to standard (ZIP/Archive) TDF on the device — same wire format on iOS and Android, so a TDF created on one decrypts on the other. The Java SDK isn't officially marketed as an Android target, but with a few build-side accommodations it works end-to-end on a real device against an OpenTDF platform deployment.Sharing the notes that weren't obvious from the README, in case they help anyone else.
What worked out of the box
SDKBuilder.newBuilder().platformEndpoint(...).clientSecret(id, secret).useInsecurePlaintextConnection(dev).build()— same setup as a JVM client.sdk.createTDF(InputStream, OutputStream, TDFConfig)for encryption — output is real standard TDF (ZIP local-file-header magicPK\x03\x04is the round-trip canary).KasClient/OauthTokenSourcewe had on 0.7.5.The four Android-specific things we had to do
1. Strip server-side transitive deps. The 0.15.0 POM pulls a JVM-server toolchain that won't dex (or duplicates Firebase's google common protos). Our
implementation(libs.opentdf.sdk)exclusion block:Important: keep
com.google.api.grpc:proto-google-common-protos. The SDK needs it at runtime to deserializeRewrapResponse— we excluded it once and got cryptic failures inside rewrap.Also
packaging.resources.excludesfor duplicate BouncyCastle OSGI manifests:2. Kotlin metadata version skew. The SDK jar ships Kotlin 2.1 metadata even though its public API is Java. Our Compose build is on 1.9.x. Fix:
The 2.1 stdlib it pulls in is binary-compatible; this just silences the compile-time metadata-version assertion.
3.
TDF.Readeris package-private.SDK.loadTDF(...)returns aTDF.Reader, but the enclosingio.opentdf.platform.sdk.TDFclass isn't exported. From Kotlin you can't even name the return type without it being public. We solved it with a 25-line same-package Java shim:Would be nice to have
byte[] SDK.decrypt(SeekableByteChannel, TDFReaderConfig)or to makeTDF.Readerpublic — happy to PR if there's appetite.4. In-memory
SeekableByteChannelfor decrypt.loadTDFneeds seekable input (standard TDF is a ZIP). On mobile we don't want to spill the ciphertext to disk just to read it. A ~30-line read-onlyByteArraySeekableByteChannelover aByteArrayis enough —position(),read(ByteBuffer),size(), and throw on write/truncate.Recipient encoding (attribute-based access control on a mobile client)
The interesting product question: how do you express "Alice and Bob can read this" without putting their emails in the TDF manifest? We use a single attribute namespace and, per recipient, an attribute value that is the SHA-256 of
email.lowercase().trim():Then
Config.withDataAttributes(*fqns)at encrypt time. The platform-side subject mapping has to hash the incoming Keycloak email claim the same way before matching — that's the only contract. We pinned the transform with a known SHA-256 vector in a unit test, because changing it after release would silently de-entitle every existing artifact.Release-parity guards
Because the dev defaults (
opentdf/secret,http://localhost:8080, plaintext) are baked intoBuildConfigfor ergonomic local dev, we added a Gradle task that failsassembleReleaseif any of them survive into a release build. The exact dev secret is in the public README, so leaking it into an APK would be embarrassing.Limitations / open questions
TDF.Readeris the only sharp edge left — would prefer not to keep the bridge if there's a sanctioned API.Happy to share the full
build.gradle.ktsexclusions block or theTdfDecryptBridgeshim if anyone wants to lift them.All reactions