From 8f51b50b9b2543b2f22878869a3c7f0624dabef3 Mon Sep 17 00:00:00 2001 From: Paul Flynn Date: Tue, 17 Feb 2026 11:50:37 -0500 Subject: [PATCH 1/5] feat(wasm-host): add JVM WASM host for TDF encrypt round-trip validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new wasm-host Maven module that loads the TinyGo-built TDF encrypt WASM binary via Chicory (pure-Java WASM runtime), provides host crypto functions using Java SDK classes, and validates that WASM-produced TDFs decrypt correctly. Three JUnit 5 tests: - HS256 round-trip: encrypt → parse ZIP → unwrap DEK → AES-GCM decrypt - GMAC round-trip: verify segment hash equals GCM auth tag, then decrypt - Error handling: invalid PEM returns error via get_error export Co-Authored-By: Claude Opus 4.6 --- pom.xml | 1 + wasm-host/.gitignore | 2 + wasm-host/pom.xml | 77 ++++ .../io/opentdf/platform/wasm/WasmTdfTest.java | 423 ++++++++++++++++++ 4 files changed, 503 insertions(+) create mode 100644 wasm-host/.gitignore create mode 100644 wasm-host/pom.xml create mode 100644 wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java diff --git a/pom.xml b/pom.xml index e8f2e7be..baeb9ce1 100644 --- a/pom.xml +++ b/pom.xml @@ -299,6 +299,7 @@ sdk cmdline examples + wasm-host true diff --git a/wasm-host/.gitignore b/wasm-host/.gitignore new file mode 100644 index 00000000..3d48c456 --- /dev/null +++ b/wasm-host/.gitignore @@ -0,0 +1,2 @@ +target/ +*.wasm diff --git a/wasm-host/pom.xml b/wasm-host/pom.xml new file mode 100644 index 00000000..b5b4e42d --- /dev/null +++ b/wasm-host/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + + io.opentdf.platform + sdk-pom + 0.12.0 + + + wasm-host + io.opentdf.platform:wasm-host + JVM WASM host for TDF encrypt round-trip validation + + + 1.5.3 + + + + + + com.dylibso.chicory + runtime + ${chicory.version} + test + + + com.dylibso.chicory + wasi + ${chicory.version} + test + + + + + io.opentdf.platform + sdk + ${project.version} + test + + + + + com.google.code.gson + gson + 2.11.0 + test + + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + + maven-install-plugin + + true + + + + maven-deploy-plugin + + true + + + + + diff --git a/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java b/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java new file mode 100644 index 00000000..62e8103b --- /dev/null +++ b/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java @@ -0,0 +1,423 @@ +package io.opentdf.platform.wasm; + +import com.dylibso.chicory.runtime.HostFunction; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.runtime.Store; +import com.dylibso.chicory.wasi.WasiOptions; +import com.dylibso.chicory.wasi.WasiPreview1; +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.types.FunctionType; +import com.dylibso.chicory.wasm.types.ValType; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.opentdf.platform.sdk.AesGcm; +import io.opentdf.platform.sdk.AsymDecryption; +import io.opentdf.platform.sdk.AsymEncryption; +import io.opentdf.platform.sdk.CryptoUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Base64; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * JVM WASM host that loads a TinyGo-built TDF encrypt module via Chicory, + * provides host crypto using Java SDK classes, and validates round-trip + * encrypt/decrypt. + */ +public class WasmTdfTest { + + private static final long ERR_SENTINEL = 0xFFFFFFFFL; + private static final int ALG_HS256 = 0; + private static final int ALG_GMAC = 1; + + private Instance instance; + private String kasPubPEM; + private String kasPrivPEM; + private String lastError = ""; + + @BeforeEach + void setUp() throws Exception { + KeyPair kp = CryptoUtils.generateRSAKeypair(); + kasPubPEM = CryptoUtils.getRSAPublicKeyPEM(kp.getPublic()); + kasPrivPEM = CryptoUtils.getRSAPrivateKeyPEM(kp.getPrivate()); + + try (InputStream wasmStream = Objects.requireNonNull( + getClass().getClassLoader().getResourceAsStream("tdfcore.wasm"), + "tdfcore.wasm not found in test resources")) { + + var wasi = WasiPreview1.builder() + .withOptions(WasiOptions.builder().build()) + .build(); + + var store = new Store(); + store.addFunction(wasi.toHostFunctions()); + store.addFunction(cryptoHostFunctions()); + store.addFunction(ioHostFunctions()); + + instance = store.instantiate("tdfcore", Parser.parse(wasmStream)); + } + + // Initialize the TinyGo c-shared module + instance.export("_initialize").apply(); + } + + // ---- Host crypto functions ---- + + private HostFunction[] cryptoHostFunctions() { + return new HostFunction[]{ + new HostFunction( + "crypto", "random_bytes", + FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), + (inst, args) -> { + int outPtr = (int) args[0]; + int n = (int) args[1]; + byte[] bytes = new byte[n]; + new SecureRandom().nextBytes(bytes); + inst.memory().write(outPtr, bytes); + return new long[]{n}; + }), + + new HostFunction( + "crypto", "aes_gcm_encrypt", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + byte[] key = inst.memory().readBytes((int) args[0], (int) args[1]); + byte[] pt = inst.memory().readBytes((int) args[2], (int) args[3]); + AesGcm.Encrypted encrypted = new AesGcm(key).encrypt(pt); + byte[] result = encrypted.asBytes(); + inst.memory().write((int) args[4], result); + return new long[]{result.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "aes_gcm_decrypt", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + byte[] key = inst.memory().readBytes((int) args[0], (int) args[1]); + byte[] ct = inst.memory().readBytes((int) args[2], (int) args[3]); + byte[] decrypted = new AesGcm(key).decrypt(new AesGcm.Encrypted(ct)); + inst.memory().write((int) args[4], decrypted); + return new long[]{decrypted.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "hmac_sha256", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + byte[] key = inst.memory().readBytes((int) args[0], (int) args[1]); + byte[] data = inst.memory().readBytes((int) args[2], (int) args[3]); + byte[] hmac = CryptoUtils.CalculateSHA256Hmac(key, data); + inst.memory().write((int) args[4], hmac); + return new long[]{hmac.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "rsa_oaep_sha1_encrypt", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + String pubPEM = inst.memory().readString((int) args[0], (int) args[1]); + byte[] pt = inst.memory().readBytes((int) args[2], (int) args[3]); + byte[] encrypted = new AsymEncryption(pubPEM).encrypt(pt); + inst.memory().write((int) args[4], encrypted); + return new long[]{encrypted.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "rsa_oaep_sha1_decrypt", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + String privPEM = inst.memory().readString((int) args[0], (int) args[1]); + byte[] ct = inst.memory().readBytes((int) args[2], (int) args[3]); + byte[] decrypted = new AsymDecryption(privPEM).decrypt(ct); + inst.memory().write((int) args[4], decrypted); + return new long[]{decrypted.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "rsa_generate_keypair", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + // args[0] = bits (unused, always 2048) + KeyPair kp = CryptoUtils.generateRSAKeypair(); + byte[] privPEM = CryptoUtils.getRSAPrivateKeyPEM(kp.getPrivate()) + .getBytes(StandardCharsets.UTF_8); + byte[] pubPEM = CryptoUtils.getRSAPublicKeyPEM(kp.getPublic()) + .getBytes(StandardCharsets.UTF_8); + inst.memory().write((int) args[1], privPEM); + inst.memory().write((int) args[2], pubPEM); + inst.memory().writeI32((int) args[3], pubPEM.length); + return new long[]{privPEM.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "get_last_error", + FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), + (inst, args) -> { + if (lastError.isEmpty()) { + return new long[]{0}; + } + byte[] errBytes = lastError.getBytes(StandardCharsets.UTF_8); + int cap = (int) args[1]; + int len = Math.min(errBytes.length, cap); + inst.memory().write((int) args[0], Arrays.copyOf(errBytes, len)); + lastError = ""; + return new long[]{len}; + }) + }; + } + + private HostFunction[] ioHostFunctions() { + return new HostFunction[]{ + // read_input: return 0 (EOF) — not used during encrypt + new HostFunction( + "io", "read_input", + FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), + (inst, args) -> new long[]{0}), + + // write_output: no-op, return length — not used during encrypt + new HostFunction( + "io", "write_output", + FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), + (inst, args) -> new long[]{args[1]}) + }; + } + + // ---- Helpers ---- + + private long wasmMalloc(int size) { + long[] result = instance.export("tdf_malloc").apply((long) size); + return result[0]; + } + + private long allocAndWrite(byte[] data) { + long ptr = wasmMalloc(data.length); + instance.memory().write((int) ptr, data); + return ptr; + } + + private String getWasmError() { + long errBufPtr = wasmMalloc(4096); + long[] result = instance.export("get_error").apply(errBufPtr, 4096L); + int errLen = (int) result[0]; + if (errLen == 0) { + return ""; + } + return instance.memory().readString((int) errBufPtr, errLen); + } + + private byte[] wasmEncrypt(byte[] plaintext, int integrityAlg, int segIntegrityAlg) { + byte[] kasPubBytes = kasPubPEM.getBytes(StandardCharsets.UTF_8); + byte[] kasURLBytes = "https://kas.example.com".getBytes(StandardCharsets.UTF_8); + byte[] attrBytes = "https://example.com/attr/classification/value/secret" + .getBytes(StandardCharsets.UTF_8); + + long kasPubPtr = allocAndWrite(kasPubBytes); + long kasURLPtr = allocAndWrite(kasURLBytes); + long attrPtr = allocAndWrite(attrBytes); + long ptPtr = allocAndWrite(plaintext); + + int outCapacity = 1024 * 1024; + long outPtr = wasmMalloc(outCapacity); + + long[] result = instance.export("tdf_encrypt").apply( + kasPubPtr, (long) kasPubBytes.length, + kasURLPtr, (long) kasURLBytes.length, + attrPtr, (long) attrBytes.length, + ptPtr, (long) plaintext.length, + outPtr, (long) outCapacity, + (long) integrityAlg, (long) segIntegrityAlg + ); + + long resultLen = result[0]; + assertTrue(resultLen > 0, "WASM encrypt failed: " + getWasmError()); + + return instance.memory().readBytes((int) outPtr, (int) resultLen); + } + + private Map parseZip(byte[] zipBytes) throws Exception { + // Use ZipFile (central-directory based) instead of ZipInputStream because + // the TDF ZIP uses STORED entries with data descriptors, which ZipInputStream rejects. + Path tempFile = Files.createTempFile("tdf", ".zip"); + try { + Files.write(tempFile, zipBytes); + Map entries = new HashMap<>(); + try (ZipFile zf = new ZipFile(tempFile.toFile())) { + Enumeration e = zf.entries(); + while (e.hasMoreElements()) { + ZipEntry entry = e.nextElement(); + try (InputStream is = zf.getInputStream(entry)) { + entries.put(entry.getName(), is.readAllBytes()); + } + } + } + return entries; + } finally { + Files.deleteIfExists(tempFile); + } + } + + // ---- Tests ---- + + @Test + void testHS256RoundTrip() throws Exception { + byte[] plaintext = "Hello, TDF from JVM WASM host!".getBytes(StandardCharsets.UTF_8); + byte[] tdfBytes = wasmEncrypt(plaintext, ALG_HS256, ALG_HS256); + + // Parse ZIP + Map entries = parseZip(tdfBytes); + assertTrue(entries.containsKey("0.manifest.json"), "Missing manifest"); + assertTrue(entries.containsKey("0.payload"), "Missing payload"); + + // Parse manifest + String manifestJson = new String(entries.get("0.manifest.json"), StandardCharsets.UTF_8); + JsonObject manifest = JsonParser.parseString(manifestJson).getAsJsonObject(); + + // Verify manifest structure + assertEquals("4.3.0", manifest.get("schemaVersion").getAsString()); + + JsonObject encInfo = manifest.getAsJsonObject("encryptionInformation"); + assertEquals("AES-256-GCM", + encInfo.getAsJsonObject("method").get("algorithm").getAsString()); + + JsonObject intInfo = encInfo.getAsJsonObject("integrityInformation"); + assertEquals("HS256", intInfo.getAsJsonObject("rootSignature").get("alg").getAsString()); + assertEquals("HS256", intInfo.get("segmentHashAlg").getAsString()); + + // Unwrap DEK with our private key + String wrappedKeyB64 = encInfo.getAsJsonArray("keyAccess") + .get(0).getAsJsonObject().get("wrappedKey").getAsString(); + byte[] wrappedKey = Base64.getDecoder().decode(wrappedKeyB64); + byte[] dek = new AsymDecryption(kasPrivPEM).decrypt(wrappedKey); + + // Decrypt payload: [iv(12) || ciphertext || tag(16)] + byte[] payload = entries.get("0.payload"); + byte[] decrypted = new AesGcm(dek).decrypt(new AesGcm.Encrypted(payload)); + + assertArrayEquals(plaintext, decrypted); + } + + @Test + void testGMACRoundTrip() throws Exception { + byte[] plaintext = "GMAC integrity test from JVM".getBytes(StandardCharsets.UTF_8); + byte[] tdfBytes = wasmEncrypt(plaintext, ALG_HS256, ALG_GMAC); + + Map entries = parseZip(tdfBytes); + String manifestJson = new String(entries.get("0.manifest.json"), StandardCharsets.UTF_8); + JsonObject manifest = JsonParser.parseString(manifestJson).getAsJsonObject(); + + JsonObject encInfo = manifest.getAsJsonObject("encryptionInformation"); + JsonObject intInfo = encInfo.getAsJsonObject("integrityInformation"); + assertEquals("GMAC", intInfo.get("segmentHashAlg").getAsString()); + + // GMAC = last 16 bytes of ciphertext (the GCM auth tag) + byte[] payload = entries.get("0.payload"); + byte[] cipher = Arrays.copyOfRange(payload, 12, payload.length); + byte[] gmacTag = Arrays.copyOfRange(cipher, cipher.length - 16, cipher.length); + String expectedSegHash = Base64.getEncoder().encodeToString(gmacTag); + + String actualSegHash = intInfo.getAsJsonArray("segments") + .get(0).getAsJsonObject().get("hash").getAsString(); + assertEquals(expectedSegHash, actualSegHash); + + // Decrypt and verify round-trip + String wrappedKeyB64 = encInfo.getAsJsonArray("keyAccess") + .get(0).getAsJsonObject().get("wrappedKey").getAsString(); + byte[] dek = new AsymDecryption(kasPrivPEM).decrypt( + Base64.getDecoder().decode(wrappedKeyB64)); + byte[] decrypted = new AesGcm(dek).decrypt(new AesGcm.Encrypted(payload)); + + assertArrayEquals(plaintext, decrypted); + } + + @Test + void testErrorHandlingInvalidPEM() { + byte[] plaintext = "test".getBytes(StandardCharsets.UTF_8); + byte[] invalidPEM = "not-a-valid-pem".getBytes(StandardCharsets.UTF_8); + byte[] kasURLBytes = "https://kas.example.com".getBytes(StandardCharsets.UTF_8); + byte[] attrBytes = new byte[0]; + + long kasPubPtr = allocAndWrite(invalidPEM); + long kasURLPtr = allocAndWrite(kasURLBytes); + long attrPtr = wasmMalloc(1); // empty attrs need at least 1 byte allocation + long ptPtr = allocAndWrite(plaintext); + + int outCapacity = 1024 * 1024; + long outPtr = wasmMalloc(outCapacity); + + long[] result = instance.export("tdf_encrypt").apply( + kasPubPtr, (long) invalidPEM.length, + kasURLPtr, (long) kasURLBytes.length, + attrPtr, 0L, + ptPtr, (long) plaintext.length, + outPtr, (long) outCapacity, + (long) ALG_HS256, (long) ALG_HS256 + ); + + assertEquals(0, result[0], "Expected encrypt to fail with invalid PEM"); + + String error = getWasmError(); + assertFalse(error.isEmpty(), "Expected non-empty error message"); + } +} From 0f46d1fe2a4d69c6f4bdbd91b193d72184f424fc Mon Sep 17 00:00:00 2001 From: Paul Flynn Date: Tue, 17 Feb 2026 12:01:11 -0500 Subject: [PATCH 2/5] refactor(wasm-host): use ZipInputStream now that upstream zipstream fix landed Replace ZipFile (temp file + central directory) workaround with standard ZipInputStream, since opentdf/platform@7dd876e1 fixed the STORED entries with unnecessary data descriptors. Co-Authored-By: Claude Opus 4.6 --- .../io/opentdf/platform/wasm/WasmTdfTest.java | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java b/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java index 62e8103b..de9ee7e4 100644 --- a/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java +++ b/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java @@ -17,21 +17,19 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.security.KeyPair; import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; -import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -297,25 +295,14 @@ private byte[] wasmEncrypt(byte[] plaintext, int integrityAlg, int segIntegrityA } private Map parseZip(byte[] zipBytes) throws Exception { - // Use ZipFile (central-directory based) instead of ZipInputStream because - // the TDF ZIP uses STORED entries with data descriptors, which ZipInputStream rejects. - Path tempFile = Files.createTempFile("tdf", ".zip"); - try { - Files.write(tempFile, zipBytes); - Map entries = new HashMap<>(); - try (ZipFile zf = new ZipFile(tempFile.toFile())) { - Enumeration e = zf.entries(); - while (e.hasMoreElements()) { - ZipEntry entry = e.nextElement(); - try (InputStream is = zf.getInputStream(entry)) { - entries.put(entry.getName(), is.readAllBytes()); - } - } + Map entries = new HashMap<>(); + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + entries.put(entry.getName(), zis.readAllBytes()); } - return entries; - } finally { - Files.deleteIfExists(tempFile); } + return entries; } // ---- Tests ---- From a6f63945b8b9b7fcb9096825e7b3c5e8b1d6c945 Mon Sep 17 00:00:00 2001 From: Paul Flynn Date: Thu, 19 Feb 2026 15:43:06 -0500 Subject: [PATCH 3/5] feat(examples): add WASM encrypt/decrypt to cross-SDK benchmark Add Chicory 1.5.3 WASM runtime to the benchmark, providing 8 crypto host functions (AES-GCM, HMAC-SHA256, RSA-OAEP) and tdf_encrypt/ tdf_decrypt wrappers with OOM recovery. Uses local RSA keypair for DEK unwrap (no KAS needed). New --wasm-binary CLI flag. Co-Authored-By: Claude Opus 4.6 --- examples/pom.xml | 21 + .../opentdf/platform/BenchmarkCrossSDK.java | 577 ++++++++++++++++++ 2 files changed, 598 insertions(+) create mode 100644 examples/src/main/java/io/opentdf/platform/BenchmarkCrossSDK.java diff --git a/examples/pom.xml b/examples/pom.xml index cc18d0c8..c1d9ec74 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -68,6 +68,21 @@ commons-cli 1.4 + + org.apache.commons + commons-compress + 1.26.1 + + + com.dylibso.chicory + runtime + 1.5.3 + + + com.dylibso.chicory + wasi + 1.5.3 + org.apache.logging.log4j log4j-api @@ -207,6 +222,12 @@ io.opentdf.platform.ListSubjectMappings + + BenchmarkCrossSDK + + io.opentdf.platform.BenchmarkCrossSDK + + diff --git a/examples/src/main/java/io/opentdf/platform/BenchmarkCrossSDK.java b/examples/src/main/java/io/opentdf/platform/BenchmarkCrossSDK.java new file mode 100644 index 00000000..99f6b292 --- /dev/null +++ b/examples/src/main/java/io/opentdf/platform/BenchmarkCrossSDK.java @@ -0,0 +1,577 @@ +package io.opentdf.platform; + +import com.dylibso.chicory.runtime.HostFunction; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.runtime.Store; +import com.dylibso.chicory.wasi.WasiOptions; +import com.dylibso.chicory.wasi.WasiPreview1; +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.types.FunctionType; +import com.dylibso.chicory.wasm.types.ValType; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.opentdf.platform.sdk.*; +import org.apache.commons.cli.*; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class BenchmarkCrossSDK { + + private static final long ERR_SENTINEL = 0xFFFFFFFFL; + + // WASM state + private static Instance wasmInstance; + private static String lastError = ""; + private static boolean wasmOK = false; + private static String wasmBinaryPath; + + // RSA key pair for WASM encrypt/decrypt + private static String wasmPubPEM; + private static String wasmPrivPEM; + + public static void main(String[] args) throws Exception { + Options options = new Options(); + options.addOption(Option.builder("i") + .longOpt("iterations") + .hasArg() + .desc("Iterations per payload size to average (default: 5)") + .build()); + options.addOption(Option.builder("s") + .longOpt("sizes") + .hasArg() + .desc("Comma-separated payload sizes in bytes (default: 256,1024,16384,65536,262144,1048576)") + .build()); + options.addOption(Option.builder("e") + .longOpt("platform-endpoint") + .hasArg() + .desc("Platform endpoint (default: localhost:8080)") + .build()); + options.addOption(Option.builder() + .longOpt("client-id") + .hasArg() + .desc("OAuth client ID (default: opentdf)") + .build()); + options.addOption(Option.builder() + .longOpt("client-secret") + .hasArg() + .desc("OAuth client secret (default: secret)") + .build()); + options.addOption(Option.builder("a") + .longOpt("attribute") + .hasArg() + .desc("Data attribute (default: https://example.com/attr/attr1/value/value1)") + .build()); + options.addOption(Option.builder("w") + .longOpt("wasm-binary") + .hasArg() + .desc("Path to tdfcore.wasm (default: wasm-host/src/test/resources/tdfcore.wasm)") + .build()); + + CommandLineParser parser = new DefaultParser(); + CommandLine cmd = parser.parse(options, args); + + int iterations = Integer.parseInt(cmd.getOptionValue("iterations", "5")); + String sizesStr = cmd.getOptionValue("sizes", "256,1024,16384,65536,262144,1048576"); + String platformEndpoint = cmd.getOptionValue("platform-endpoint", "localhost:8080"); + String clientId = cmd.getOptionValue("client-id", "opentdf"); + String clientSecret = cmd.getOptionValue("client-secret", "secret"); + String attribute = cmd.getOptionValue("attribute", "https://example.com/attr/attr1/value/value1"); + wasmBinaryPath = cmd.getOptionValue("wasm-binary", "wasm-host/src/test/resources/tdfcore.wasm"); + + int[] sizes = parseSizes(sizesStr); + + // Setup SDK + SDKBuilder builder = new SDKBuilder(); + SDK sdk = builder.platformEndpoint(platformEndpoint) + .clientSecret(clientId, clientSecret) + .useInsecurePlaintextConnection(true) + .build(); + + String kasUrl = "http://" + platformEndpoint + "/kas"; + + var kasInfo = new Config.KASInfo(); + kasInfo.URL = kasUrl; + + SecureRandom random = new SecureRandom(); + + // Setup WASM runtime + RSA keypair + System.out.println("Initializing WASM runtime (Chicory)..."); + KeyPair kp = CryptoUtils.generateRSAKeypair(); + wasmPubPEM = CryptoUtils.getRSAPublicKeyPEM(kp.getPublic()); + wasmPrivPEM = CryptoUtils.getRSAPrivateKeyPEM(kp.getPrivate()); + try { + initWasm(wasmBinaryPath); + wasmOK = true; + System.out.println("WASM runtime initialized."); + } catch (Exception e) { + System.out.println("WASM init failed: " + e.getMessage()); + wasmOK = false; + } + + long[] encryptTimes = new long[sizes.length]; + long[] decryptTimes = new long[sizes.length]; + long[] wasmEncryptTimes = new long[sizes.length]; + long[] wasmDecryptTimes = new long[sizes.length]; + String[] wasmEncErrors = new String[sizes.length]; + String[] wasmDecErrors = new String[sizes.length]; + + for (int i = 0; i < sizes.length; i++) { + int size = sizes[i]; + byte[] payload = new byte[size]; + random.nextBytes(payload); + + System.out.printf("Benchmarking %s ...%n", formatSize(size)); + + // ── Native SDK encrypt ────────────────────────────────── + byte[] lastTdf = null; + long encTotal = 0; + for (int j = 0; j < iterations; j++) { + var tdfConfig = Config.newTDFConfig( + Config.withKasInformation(kasInfo), + Config.withDataAttributes(attribute), + Config.withAutoconfigure(false)); + var in = new ByteArrayInputStream(payload); + var out = new ByteArrayOutputStream(); + + long start = System.nanoTime(); + sdk.createTDF(in, out, tdfConfig); + encTotal += System.nanoTime() - start; + + lastTdf = out.toByteArray(); + } + encryptTimes[i] = encTotal / iterations; + + // ── WASM encrypt ──────────────────────────────────────── + byte[] wasmTdf = null; + if (wasmOK) { + try { + long wasmEncTotal = 0; + for (int j = 0; j < iterations; j++) { + long start = System.nanoTime(); + byte[] tdf = wasmEncrypt(payload, wasmPubPEM); + wasmEncTotal += System.nanoTime() - start; + wasmTdf = tdf; + } + wasmEncryptTimes[i] = wasmEncTotal / iterations; + } catch (Exception e) { + System.out.printf(" WASM encrypt failed: %s%n", e.getMessage()); + wasmEncErrors[i] = "OOM"; + reinitWasm(); + } + } else { + wasmEncErrors[i] = "N/A"; + } + + // ── Native SDK decrypt ────────────────────────────────── + long decTotal = 0; + for (int j = 0; j < iterations; j++) { + var channel = new SeekableInMemoryByteChannel(lastTdf); + var readerConfig = Config.newTDFReaderConfig(); + var decOut = new ByteArrayOutputStream(); + + long start = System.nanoTime(); + var reader = sdk.loadTDF(channel, readerConfig); + reader.readPayload(decOut); + decTotal += System.nanoTime() - start; + } + decryptTimes[i] = decTotal / iterations; + + // ── WASM decrypt ──────────────────────────────────────── + if (wasmTdf != null && wasmOK) { + try { + long wasmDecTotal = 0; + for (int j = 0; j < iterations; j++) { + long start = System.nanoTime(); + byte[] dek = unwrapDEKLocal(wasmTdf, wasmPrivPEM); + wasmDecrypt(wasmTdf, dek); + wasmDecTotal += System.nanoTime() - start; + } + wasmDecryptTimes[i] = wasmDecTotal / iterations; + } catch (Exception e) { + System.out.printf(" WASM decrypt failed: %s%n", e.getMessage()); + wasmDecErrors[i] = "OOM"; + reinitWasm(); + } + } else if (wasmEncErrors[i] != null) { + wasmDecErrors[i] = "N/A"; + } else if (!wasmOK) { + wasmDecErrors[i] = "N/A"; + } + } + + // Print results + System.out.println(); + System.out.println("# Cross-SDK Benchmark Results"); + System.out.printf("Platform: %s%n", platformEndpoint); + System.out.printf("Iterations: %d per size%n", iterations); + System.out.println(); + + System.out.println("## Encrypt"); + System.out.println("| Payload | Java SDK | WASM |"); + System.out.println("|---------|----------|------|"); + for (int i = 0; i < sizes.length; i++) { + String wasmCol = wasmEncErrors[i] != null ? wasmEncErrors[i] : fmtDurationMS(wasmEncryptTimes[i]); + System.out.printf("| %s | %s | %s |%n", formatSize(sizes[i]), fmtDurationMS(encryptTimes[i]), wasmCol); + } + + System.out.println(); + System.out.println("## Decrypt"); + System.out.println("| Payload | Java SDK* | WASM** |"); + System.out.println("|---------|-----------|--------|"); + for (int i = 0; i < sizes.length; i++) { + String wasmCol = wasmDecErrors[i] != null ? wasmDecErrors[i] : fmtDurationMS(wasmDecryptTimes[i]); + System.out.printf("| %s | %s | %s |%n", formatSize(sizes[i]), fmtDurationMS(decryptTimes[i]), wasmCol); + } + System.out.println("*Java SDK: includes KAS rewrap network latency"); + System.out.println("**WASM: includes local RSA-OAEP DEK unwrap (no network); in production the host would call KAS for rewrap"); + } + + // ── WASM lifecycle ────────────────────────────────────────────────── + + static void initWasm(String path) throws Exception { + lastError = ""; + try (InputStream wasmStream = new FileInputStream(path)) { + var wasi = WasiPreview1.builder() + .withOptions(WasiOptions.builder().build()) + .build(); + + var store = new Store(); + store.addFunction(wasi.toHostFunctions()); + store.addFunction(cryptoHostFunctions()); + store.addFunction(ioHostFunctions()); + + wasmInstance = store.instantiate("tdfcore", Parser.parse(wasmStream)); + } + wasmInstance.export("_initialize").apply(); + } + + static void reinitWasm() { + wasmInstance = null; + try { + initWasm(wasmBinaryPath); + wasmOK = true; + } catch (Exception e) { + System.out.printf(" WASM runtime reinit failed: %s%n", e.getMessage()); + wasmOK = false; + } + } + + // ── Host crypto functions ─────────────────────────────────────────── + + static HostFunction[] cryptoHostFunctions() { + return new HostFunction[]{ + new HostFunction( + "crypto", "random_bytes", + FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), + (inst, args) -> { + int outPtr = (int) args[0]; + int n = (int) args[1]; + byte[] bytes = new byte[n]; + new SecureRandom().nextBytes(bytes); + inst.memory().write(outPtr, bytes); + return new long[]{n}; + }), + + new HostFunction( + "crypto", "aes_gcm_encrypt", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + byte[] key = inst.memory().readBytes((int) args[0], (int) args[1]); + byte[] pt = inst.memory().readBytes((int) args[2], (int) args[3]); + AesGcm.Encrypted encrypted = new AesGcm(key).encrypt(pt); + byte[] result = encrypted.asBytes(); + inst.memory().write((int) args[4], result); + return new long[]{result.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "aes_gcm_decrypt", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + byte[] key = inst.memory().readBytes((int) args[0], (int) args[1]); + byte[] ct = inst.memory().readBytes((int) args[2], (int) args[3]); + byte[] decrypted = new AesGcm(key).decrypt(new AesGcm.Encrypted(ct)); + inst.memory().write((int) args[4], decrypted); + return new long[]{decrypted.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "hmac_sha256", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + byte[] key = inst.memory().readBytes((int) args[0], (int) args[1]); + byte[] data = inst.memory().readBytes((int) args[2], (int) args[3]); + byte[] hmac = CryptoUtils.CalculateSHA256Hmac(key, data); + inst.memory().write((int) args[4], hmac); + return new long[]{hmac.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "rsa_oaep_sha1_encrypt", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + String pubPEM = inst.memory().readString((int) args[0], (int) args[1]); + byte[] pt = inst.memory().readBytes((int) args[2], (int) args[3]); + byte[] encrypted = new AsymEncryption(pubPEM).encrypt(pt); + inst.memory().write((int) args[4], encrypted); + return new long[]{encrypted.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "rsa_oaep_sha1_decrypt", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + String privPEM = inst.memory().readString((int) args[0], (int) args[1]); + byte[] ct = inst.memory().readBytes((int) args[2], (int) args[3]); + byte[] decrypted = new AsymDecryption(privPEM).decrypt(ct); + inst.memory().write((int) args[4], decrypted); + return new long[]{decrypted.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "rsa_generate_keypair", + FunctionType.of( + List.of(ValType.I32, ValType.I32, ValType.I32, ValType.I32), + List.of(ValType.I32)), + (inst, args) -> { + try { + KeyPair kpGen = CryptoUtils.generateRSAKeypair(); + byte[] privPEM = CryptoUtils.getRSAPrivateKeyPEM(kpGen.getPrivate()) + .getBytes(StandardCharsets.UTF_8); + byte[] pubPEM = CryptoUtils.getRSAPublicKeyPEM(kpGen.getPublic()) + .getBytes(StandardCharsets.UTF_8); + inst.memory().write((int) args[1], privPEM); + inst.memory().write((int) args[2], pubPEM); + inst.memory().writeI32((int) args[3], pubPEM.length); + return new long[]{privPEM.length}; + } catch (Exception e) { + lastError = e.getMessage(); + return new long[]{ERR_SENTINEL}; + } + }), + + new HostFunction( + "crypto", "get_last_error", + FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), + (inst, args) -> { + if (lastError.isEmpty()) { + return new long[]{0}; + } + byte[] errBytes = lastError.getBytes(StandardCharsets.UTF_8); + int cap = (int) args[1]; + int len = Math.min(errBytes.length, cap); + inst.memory().write((int) args[0], Arrays.copyOf(errBytes, len)); + lastError = ""; + return new long[]{len}; + }) + }; + } + + static HostFunction[] ioHostFunctions() { + return new HostFunction[]{ + new HostFunction( + "io", "read_input", + FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), + (inst, args) -> new long[]{0}), + + new HostFunction( + "io", "write_output", + FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), + (inst, args) -> new long[]{args[1]}) + }; + } + + // ── WASM memory helpers ───────────────────────────────────────────── + + static long wasmMalloc(int size) { + long[] result = wasmInstance.export("tdf_malloc").apply((long) size); + return result[0]; + } + + static long allocAndWrite(byte[] data) { + long ptr = wasmMalloc(data.length); + wasmInstance.memory().write((int) ptr, data); + return ptr; + } + + static String getWasmError() { + long errBufPtr = wasmMalloc(4096); + long[] result = wasmInstance.export("get_error").apply(errBufPtr, 4096L); + int errLen = (int) result[0]; + if (errLen == 0) { + return ""; + } + return wasmInstance.memory().readString((int) errBufPtr, errLen); + } + + // ── WASM encrypt ──────────────────────────────────────────────────── + + static byte[] wasmEncrypt(byte[] plaintext, String kasPubPEM) throws Exception { + byte[] kasPubBytes = kasPubPEM.getBytes(StandardCharsets.UTF_8); + byte[] kasURLBytes = "https://kas.example.com".getBytes(StandardCharsets.UTF_8); + byte[] attrBytes = "https://example.com/attr/classification/value/secret" + .getBytes(StandardCharsets.UTF_8); + + long kasPubPtr = allocAndWrite(kasPubBytes); + long kasURLPtr = allocAndWrite(kasURLBytes); + long attrPtr = allocAndWrite(attrBytes); + long ptPtr = allocAndWrite(plaintext); + + int outCapacity = plaintext.length * 2 + 65536; + long outPtr = wasmMalloc(outCapacity); + + long[] result = wasmInstance.export("tdf_encrypt").apply( + kasPubPtr, (long) kasPubBytes.length, + kasURLPtr, (long) kasURLBytes.length, + attrPtr, (long) attrBytes.length, + ptPtr, (long) plaintext.length, + outPtr, (long) outCapacity, + 0L, 0L, // HS256 for root + segment integrity + 0L // default segment size + ); + + long resultLen = result[0]; + if (resultLen == 0) { + String err = getWasmError(); + throw new Exception("WASM encrypt failed: " + (err.isEmpty() ? "unknown error" : err)); + } + + return wasmInstance.memory().readBytes((int) outPtr, (int) resultLen); + } + + // ── DEK unwrap ────────────────────────────────────────────────────── + + static byte[] unwrapDEKLocal(byte[] tdfBytes, String privPEM) throws Exception { + String manifestJson = null; + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(tdfBytes))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if ("0.manifest.json".equals(entry.getName())) { + manifestJson = new String(zis.readAllBytes(), StandardCharsets.UTF_8); + break; + } + } + } + if (manifestJson == null) { + throw new Exception("0.manifest.json not found in TDF ZIP"); + } + + JsonObject manifest = JsonParser.parseString(manifestJson).getAsJsonObject(); + JsonObject encInfo = manifest.getAsJsonObject("encryptionInformation"); + String wrappedKeyB64 = encInfo.getAsJsonArray("keyAccess") + .get(0).getAsJsonObject().get("wrappedKey").getAsString(); + byte[] wrappedKey = Base64.getDecoder().decode(wrappedKeyB64); + byte[] dek = new AsymDecryption(privPEM).decrypt(wrappedKey); + if (dek.length != 32) { + throw new Exception("DEK length: got " + dek.length + ", want 32"); + } + return dek; + } + + // ── WASM decrypt ──────────────────────────────────────────────────── + + static byte[] wasmDecrypt(byte[] tdfBytes, byte[] dek) throws Exception { + long tdfPtr = allocAndWrite(tdfBytes); + long dekPtr = allocAndWrite(dek); + + int outCapacity = tdfBytes.length; + long outPtr = wasmMalloc(outCapacity); + + long[] result = wasmInstance.export("tdf_decrypt").apply( + tdfPtr, (long) tdfBytes.length, + dekPtr, (long) dek.length, + outPtr, (long) outCapacity + ); + + long resultLen = result[0]; + if (resultLen == 0) { + String err = getWasmError(); + if (!err.isEmpty()) { + throw new Exception("WASM decrypt failed: " + err); + } + return new byte[0]; + } + + return wasmInstance.memory().readBytes((int) outPtr, (int) resultLen); + } + + // ── Utility methods ───────────────────────────────────────────────── + + static int[] parseSizes(String s) { + String[] parts = s.split(","); + int[] sizes = new int[parts.length]; + int count = 0; + for (String p : parts) { + p = p.trim(); + if (p.isEmpty()) continue; + sizes[count++] = Integer.parseInt(p); + } + if (count < sizes.length) { + int[] trimmed = new int[count]; + System.arraycopy(sizes, 0, trimmed, 0, count); + return trimmed; + } + return sizes; + } + + static String formatSize(int n) { + int mb = 1024 * 1024; + int kb = 1024; + if (n >= mb && n % mb == 0) { + return n / mb + " MB"; + } else if (n >= kb && n % kb == 0) { + return n / kb + " KB"; + } else { + return n + " B"; + } + } + + static String fmtDurationMS(long nanos) { + double ms = nanos / 1_000_000.0; + return String.format("%.1f ms", ms); + } +} From 46d3fa24de5d09025f92f2b69e8a8d1f9de3edae Mon Sep 17 00:00:00 2001 From: Paul Flynn Date: Thu, 19 Feb 2026 15:59:18 -0500 Subject: [PATCH 4/5] feat(wasm-host): rebuild tdfcore.wasm with tdf_decrypt export Rebuilt with TinyGo 0.37 in reactor mode (-buildmode=c-shared) from platform/sdk/experimental/tdf/wasm/ source. Now exports tdf_decrypt in addition to tdf_encrypt, enabling end-to-end WASM benchmarks. Binary size: 150 KB (down from 630 KB with updated TinyGo). Co-Authored-By: Claude Opus 4.6 --- wasm-host/src/test/resources/tdfcore.wasm | Bin 0 -> 149758 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 wasm-host/src/test/resources/tdfcore.wasm diff --git a/wasm-host/src/test/resources/tdfcore.wasm b/wasm-host/src/test/resources/tdfcore.wasm new file mode 100644 index 0000000000000000000000000000000000000000..311a564e480696c213cfd6ac850ea6247f058992 GIT binary patch literal 149758 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�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 ppX}h}=t!BXm3 Date: Mon, 23 Feb 2026 12:29:03 -0500 Subject: [PATCH 5/5] feat(wasm-host): streaming tdf_encrypt + 100MB benchmark support Update WASM host for the new streaming tdf_encrypt API that uses read_input/write_output host callbacks instead of flat buffers in WASM linear memory. Enables 100MB+ encrypt without OOM. WasmTdfTest: - Add real read_input/write_output host function implementations - Override proc_exit with ProcExitSignal + withStart(false) for TinyGo compatibility (proc_exit(0) after _start) - Switch parseZip to ZipFile (central-directory) for multi-segment TDF compatibility with data descriptors on STORED entries - Add testStreamingLargePayload (1MB, 64KB segments, round-trip) BenchmarkCrossSDK: - Same streaming I/O + proc_exit handling as WasmTdfTest - Switch unwrapDEKLocal to ZipFile for multi-segment support - Auto-select segment size: 256KB for 1-10MB, 1MB for >10MB - Add 25ms estimated KAS rewrap latency to WASM decrypt for apples-to-apples comparison with SDK (which includes network) - Default sizes now include 10MB + 100MB - Use opentdf-sdk as default client ID Rebuild tdfcore.wasm with TinyGo (//export, not //go:wasmexport). Co-Authored-By: Claude Opus 4.6 --- .../opentdf/platform/BenchmarkCrossSDK.java | 149 ++++++++++---- .../io/opentdf/platform/wasm/WasmTdfTest.java | 181 +++++++++++++++--- wasm-host/src/test/resources/tdfcore.wasm | Bin 149758 -> 149903 bytes 3 files changed, 265 insertions(+), 65 deletions(-) diff --git a/examples/src/main/java/io/opentdf/platform/BenchmarkCrossSDK.java b/examples/src/main/java/io/opentdf/platform/BenchmarkCrossSDK.java index 99f6b292..d990e202 100644 --- a/examples/src/main/java/io/opentdf/platform/BenchmarkCrossSDK.java +++ b/examples/src/main/java/io/opentdf/platform/BenchmarkCrossSDK.java @@ -20,12 +20,20 @@ import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; +import java.nio.file.Files; import java.util.List; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; public class BenchmarkCrossSDK { + /** Thrown by our proc_exit override to halt _start without killing the module. */ + private static class ProcExitSignal extends RuntimeException { + final int exitCode; + ProcExitSignal(int code) { this.exitCode = code; } + } + private static final long ERR_SENTINEL = 0xFFFFFFFFL; // WASM state @@ -38,6 +46,11 @@ public class BenchmarkCrossSDK { private static String wasmPubPEM; private static String wasmPrivPEM; + // Streaming I/O state + private static byte[] pendingInput; + private static int inputOffset; + private static ByteArrayOutputStream outputBuffer; + public static void main(String[] args) throws Exception { Options options = new Options(); options.addOption(Option.builder("i") @@ -80,9 +93,9 @@ public static void main(String[] args) throws Exception { CommandLine cmd = parser.parse(options, args); int iterations = Integer.parseInt(cmd.getOptionValue("iterations", "5")); - String sizesStr = cmd.getOptionValue("sizes", "256,1024,16384,65536,262144,1048576"); + String sizesStr = cmd.getOptionValue("sizes", "256,1024,16384,65536,262144,1048576,10485760,104857600"); String platformEndpoint = cmd.getOptionValue("platform-endpoint", "localhost:8080"); - String clientId = cmd.getOptionValue("client-id", "opentdf"); + String clientId = cmd.getOptionValue("client-id", "opentdf-sdk"); String clientSecret = cmd.getOptionValue("client-secret", "secret"); String attribute = cmd.getOptionValue("attribute", "https://example.com/attr/attr1/value/value1"); wasmBinaryPath = cmd.getOptionValue("wasm-binary", "wasm-host/src/test/resources/tdfcore.wasm"); @@ -123,6 +136,7 @@ public static void main(String[] args) throws Exception { long[] wasmDecryptTimes = new long[sizes.length]; String[] wasmEncErrors = new String[sizes.length]; String[] wasmDecErrors = new String[sizes.length]; + String[] sdkDecErrors = new String[sizes.length]; for (int i = 0; i < sizes.length; i++) { int size = sizes[i]; @@ -151,13 +165,21 @@ public static void main(String[] args) throws Exception { encryptTimes[i] = encTotal / iterations; // ── WASM encrypt ──────────────────────────────────────── + // Auto-select segment size: 0 for <1MB, 256KB for 1-10MB, 1MB for >10MB + int segSize = 0; + if (size > 10 * 1024 * 1024) { + segSize = 1024 * 1024; + } else if (size >= 1024 * 1024) { + segSize = 256 * 1024; + } + byte[] wasmTdf = null; if (wasmOK) { try { long wasmEncTotal = 0; for (int j = 0; j < iterations; j++) { long start = System.nanoTime(); - byte[] tdf = wasmEncrypt(payload, wasmPubPEM); + byte[] tdf = wasmEncryptWithSegSize(payload, wasmPubPEM, segSize); wasmEncTotal += System.nanoTime() - start; wasmTdf = tdf; } @@ -173,17 +195,22 @@ public static void main(String[] args) throws Exception { // ── Native SDK decrypt ────────────────────────────────── long decTotal = 0; - for (int j = 0; j < iterations; j++) { - var channel = new SeekableInMemoryByteChannel(lastTdf); - var readerConfig = Config.newTDFReaderConfig(); - var decOut = new ByteArrayOutputStream(); - - long start = System.nanoTime(); - var reader = sdk.loadTDF(channel, readerConfig); - reader.readPayload(decOut); - decTotal += System.nanoTime() - start; + try { + for (int j = 0; j < iterations; j++) { + var channel = new SeekableInMemoryByteChannel(lastTdf); + var readerConfig = Config.newTDFReaderConfig(); + var decOut = new ByteArrayOutputStream(); + + long start = System.nanoTime(); + var reader = sdk.loadTDF(channel, readerConfig); + reader.readPayload(decOut); + decTotal += System.nanoTime() - start; + } + decryptTimes[i] = decTotal / iterations; + } catch (Exception e) { + System.out.printf(" SDK decrypt failed: %s%n", e.getMessage()); + sdkDecErrors[i] = "err"; } - decryptTimes[i] = decTotal / iterations; // ── WASM decrypt ──────────────────────────────────────── if (wasmTdf != null && wasmOK) { @@ -195,7 +222,8 @@ public static void main(String[] args) throws Exception { wasmDecrypt(wasmTdf, dek); wasmDecTotal += System.nanoTime() - start; } - wasmDecryptTimes[i] = wasmDecTotal / iterations; + // Add estimated KAS rewrap latency (25ms) for apples-to-apples comparison + wasmDecryptTimes[i] = wasmDecTotal / iterations + 25_000_000L; } catch (Exception e) { System.out.printf(" WASM decrypt failed: %s%n", e.getMessage()); wasmDecErrors[i] = "OOM"; @@ -228,11 +256,12 @@ public static void main(String[] args) throws Exception { System.out.println("| Payload | Java SDK* | WASM** |"); System.out.println("|---------|-----------|--------|"); for (int i = 0; i < sizes.length; i++) { + String sdkCol = sdkDecErrors[i] != null ? sdkDecErrors[i] : fmtDurationMS(decryptTimes[i]); String wasmCol = wasmDecErrors[i] != null ? wasmDecErrors[i] : fmtDurationMS(wasmDecryptTimes[i]); - System.out.printf("| %s | %s | %s |%n", formatSize(sizes[i]), fmtDurationMS(decryptTimes[i]), wasmCol); + System.out.printf("| %s | %s | %s |%n", formatSize(sizes[i]), sdkCol, wasmCol); } System.out.println("*Java SDK: includes KAS rewrap network latency"); - System.out.println("**WASM: includes local RSA-OAEP DEK unwrap (no network); in production the host would call KAS for rewrap"); + System.out.println("**WASM: local decrypt + estimated 25ms KAS rewrap latency"); } // ── WASM lifecycle ────────────────────────────────────────────────── @@ -244,14 +273,39 @@ static void initWasm(String path) throws Exception { .withOptions(WasiOptions.builder().build()) .build(); + // Override proc_exit so the module stays alive after _start. + java.util.ArrayList wasiFns = new java.util.ArrayList<>(); + for (HostFunction fn : wasi.toHostFunctions()) { + if (!"proc_exit".equals(fn.name())) { + wasiFns.add(fn); + } + } + wasiFns.add(new HostFunction( + "wasi_snapshot_preview1", "proc_exit", + FunctionType.of(List.of(ValType.I32), List.of()), + (inst, args) -> { + throw new ProcExitSignal((int) args[0]); + })); + var store = new Store(); - store.addFunction(wasi.toHostFunctions()); + store.addFunction(wasiFns.toArray(new HostFunction[0])); store.addFunction(cryptoHostFunctions()); store.addFunction(ioHostFunctions()); - wasmInstance = store.instantiate("tdfcore", Parser.parse(wasmStream)); + var module = Parser.parse(wasmStream); + wasmInstance = store.instantiate("tdfcore", importValues -> + Instance.builder(module) + .withImportValues(importValues) + .withStart(false) + .build()); + } + + // Call _start to init Go runtime. proc_exit(0) is expected. + try { + wasmInstance.export("_start").apply(); + } catch (ProcExitSignal e) { + if (e.exitCode != 0) throw new RuntimeException("WASM _start exited with code " + e.exitCode); } - wasmInstance.export("_initialize").apply(); } static void reinitWasm() { @@ -416,12 +470,30 @@ static HostFunction[] ioHostFunctions() { new HostFunction( "io", "read_input", FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), - (inst, args) -> new long[]{0}), + (inst, args) -> { + int bufPtr = (int) args[0]; + int bufCapacity = (int) args[1]; + if (pendingInput == null || inputOffset >= pendingInput.length) { + return new long[]{0}; // EOF + } + int remaining = pendingInput.length - inputOffset; + int toRead = Math.min(bufCapacity, remaining); + inst.memory().write(bufPtr, + Arrays.copyOfRange(pendingInput, inputOffset, inputOffset + toRead)); + inputOffset += toRead; + return new long[]{toRead}; + }), new HostFunction( "io", "write_output", FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), - (inst, args) -> new long[]{args[1]}) + (inst, args) -> { + int bufPtr = (int) args[0]; + int bufLen = (int) args[1]; + byte[] data = inst.memory().readBytes(bufPtr, bufLen); + outputBuffer.write(data, 0, bufLen); + return new long[]{bufLen}; + }) }; } @@ -451,6 +523,10 @@ static String getWasmError() { // ── WASM encrypt ──────────────────────────────────────────────────── static byte[] wasmEncrypt(byte[] plaintext, String kasPubPEM) throws Exception { + return wasmEncryptWithSegSize(plaintext, kasPubPEM, 0); + } + + static byte[] wasmEncryptWithSegSize(byte[] plaintext, String kasPubPEM, int segmentSize) throws Exception { byte[] kasPubBytes = kasPubPEM.getBytes(StandardCharsets.UTF_8); byte[] kasURLBytes = "https://kas.example.com".getBytes(StandardCharsets.UTF_8); byte[] attrBytes = "https://example.com/attr/classification/value/secret" @@ -459,19 +535,19 @@ static byte[] wasmEncrypt(byte[] plaintext, String kasPubPEM) throws Exception { long kasPubPtr = allocAndWrite(kasPubBytes); long kasURLPtr = allocAndWrite(kasURLBytes); long attrPtr = allocAndWrite(attrBytes); - long ptPtr = allocAndWrite(plaintext); - int outCapacity = plaintext.length * 2 + 65536; - long outPtr = wasmMalloc(outCapacity); + // Set up streaming I/O state + pendingInput = plaintext; + inputOffset = 0; + outputBuffer = new ByteArrayOutputStream(plaintext.length + 65536); long[] result = wasmInstance.export("tdf_encrypt").apply( kasPubPtr, (long) kasPubBytes.length, kasURLPtr, (long) kasURLBytes.length, attrPtr, (long) attrBytes.length, - ptPtr, (long) plaintext.length, - outPtr, (long) outCapacity, + (long) plaintext.length, // plaintextSize (i64) 0L, 0L, // HS256 for root + segment integrity - 0L // default segment size + (long) segmentSize ); long resultLen = result[0]; @@ -480,21 +556,26 @@ static byte[] wasmEncrypt(byte[] plaintext, String kasPubPEM) throws Exception { throw new Exception("WASM encrypt failed: " + (err.isEmpty() ? "unknown error" : err)); } - return wasmInstance.memory().readBytes((int) outPtr, (int) resultLen); + return outputBuffer.toByteArray(); } // ── DEK unwrap ────────────────────────────────────────────────────── static byte[] unwrapDEKLocal(byte[] tdfBytes, String privPEM) throws Exception { String manifestJson = null; - try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(tdfBytes))) { - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - if ("0.manifest.json".equals(entry.getName())) { - manifestJson = new String(zis.readAllBytes(), StandardCharsets.UTF_8); - break; + File tmp = File.createTempFile("tdf-bench-", ".zip"); + try { + Files.write(tmp.toPath(), tdfBytes); + try (ZipFile zf = new ZipFile(tmp)) { + ZipEntry entry = zf.getEntry("0.manifest.json"); + if (entry != null) { + try (InputStream is = zf.getInputStream(entry)) { + manifestJson = new String(is.readAllBytes(), StandardCharsets.UTF_8); + } } } + } finally { + tmp.delete(); } if (manifestJson == null) { throw new Exception("0.manifest.json not found in TDF ZIP"); diff --git a/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java b/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java index de9ee7e4..cf7e59a2 100644 --- a/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java +++ b/wasm-host/src/test/java/io/opentdf/platform/wasm/WasmTdfTest.java @@ -17,11 +17,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.KeyPair; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; @@ -29,12 +32,11 @@ import java.util.Map; import java.util.Objects; import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; +import java.util.zip.ZipFile; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -44,6 +46,12 @@ */ public class WasmTdfTest { + /** Thrown by our proc_exit override to halt _start without killing the module. */ + private static class ProcExitSignal extends RuntimeException { + final int exitCode; + ProcExitSignal(int code) { this.exitCode = code; } + } + private static final long ERR_SENTINEL = 0xFFFFFFFFL; private static final int ALG_HS256 = 0; private static final int ALG_GMAC = 1; @@ -53,6 +61,11 @@ public class WasmTdfTest { private String kasPrivPEM; private String lastError = ""; + // Streaming I/O state + private byte[] pendingInput; + private int inputOffset; + private ByteArrayOutputStream outputBuffer; + @BeforeEach void setUp() throws Exception { KeyPair kp = CryptoUtils.generateRSAKeypair(); @@ -67,16 +80,42 @@ void setUp() throws Exception { .withOptions(WasiOptions.builder().build()) .build(); + // Override proc_exit so the module stays alive after _start. + // TinyGo/Go wasip1 calls proc_exit(0) after main() — we throw + // ProcExitSignal to halt _start without closing the module. + List wasiFns = new ArrayList<>(); + for (HostFunction fn : wasi.toHostFunctions()) { + if (!"proc_exit".equals(fn.name())) { + wasiFns.add(fn); + } + } + wasiFns.add(new HostFunction( + "wasi_snapshot_preview1", "proc_exit", + FunctionType.of(List.of(ValType.I32), List.of()), + (inst, args) -> { + throw new ProcExitSignal((int) args[0]); + })); + var store = new Store(); - store.addFunction(wasi.toHostFunctions()); + store.addFunction(wasiFns.toArray(new HostFunction[0])); store.addFunction(cryptoHostFunctions()); store.addFunction(ioHostFunctions()); - instance = store.instantiate("tdfcore", Parser.parse(wasmStream)); + // Instantiate without auto-calling _start, then call it manually + var module = Parser.parse(wasmStream); + instance = store.instantiate("tdfcore", importValues -> + Instance.builder(module) + .withImportValues(importValues) + .withStart(false) + .build()); } - // Initialize the TinyGo c-shared module - instance.export("_initialize").apply(); + // Call _start to init runtime. proc_exit(0) is expected after main(). + try { + instance.export("_start").apply(); + } catch (ProcExitSignal e) { + if (e.exitCode != 0) throw new RuntimeException("WASM _start exited with code " + e.exitCode); + } } // ---- Host crypto functions ---- @@ -228,17 +267,33 @@ private HostFunction[] cryptoHostFunctions() { private HostFunction[] ioHostFunctions() { return new HostFunction[]{ - // read_input: return 0 (EOF) — not used during encrypt new HostFunction( "io", "read_input", FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), - (inst, args) -> new long[]{0}), + (inst, args) -> { + int bufPtr = (int) args[0]; + int bufCapacity = (int) args[1]; + if (pendingInput == null || inputOffset >= pendingInput.length) { + return new long[]{0}; // EOF + } + int remaining = pendingInput.length - inputOffset; + int toRead = Math.min(bufCapacity, remaining); + inst.memory().write(bufPtr, + Arrays.copyOfRange(pendingInput, inputOffset, inputOffset + toRead)); + inputOffset += toRead; + return new long[]{toRead}; + }), - // write_output: no-op, return length — not used during encrypt new HostFunction( "io", "write_output", FunctionType.of(List.of(ValType.I32, ValType.I32), List.of(ValType.I32)), - (inst, args) -> new long[]{args[1]}) + (inst, args) -> { + int bufPtr = (int) args[0]; + int bufLen = (int) args[1]; + byte[] data = inst.memory().readBytes(bufPtr, bufLen); + outputBuffer.write(data, 0, bufLen); + return new long[]{bufLen}; + }) }; } @@ -266,6 +321,10 @@ private String getWasmError() { } private byte[] wasmEncrypt(byte[] plaintext, int integrityAlg, int segIntegrityAlg) { + return wasmEncryptWithSegSize(plaintext, integrityAlg, segIntegrityAlg, 0); + } + + private byte[] wasmEncryptWithSegSize(byte[] plaintext, int integrityAlg, int segIntegrityAlg, int segmentSize) { byte[] kasPubBytes = kasPubPEM.getBytes(StandardCharsets.UTF_8); byte[] kasURLBytes = "https://kas.example.com".getBytes(StandardCharsets.UTF_8); byte[] attrBytes = "https://example.com/attr/classification/value/secret" @@ -274,35 +333,49 @@ private byte[] wasmEncrypt(byte[] plaintext, int integrityAlg, int segIntegrityA long kasPubPtr = allocAndWrite(kasPubBytes); long kasURLPtr = allocAndWrite(kasURLBytes); long attrPtr = allocAndWrite(attrBytes); - long ptPtr = allocAndWrite(plaintext); - int outCapacity = 1024 * 1024; - long outPtr = wasmMalloc(outCapacity); + // Set up streaming I/O state + pendingInput = plaintext; + inputOffset = 0; + outputBuffer = new ByteArrayOutputStream(plaintext.length + 65536); long[] result = instance.export("tdf_encrypt").apply( kasPubPtr, (long) kasPubBytes.length, kasURLPtr, (long) kasURLBytes.length, attrPtr, (long) attrBytes.length, - ptPtr, (long) plaintext.length, - outPtr, (long) outCapacity, - (long) integrityAlg, (long) segIntegrityAlg + (long) plaintext.length, // plaintextSize (i64) + (long) integrityAlg, (long) segIntegrityAlg, + (long) segmentSize ); long resultLen = result[0]; assertTrue(resultLen > 0, "WASM encrypt failed: " + getWasmError()); - return instance.memory().readBytes((int) outPtr, (int) resultLen); + byte[] output = outputBuffer.toByteArray(); + assertEquals(resultLen, output.length, "Output length mismatch"); + return output; } private Map parseZip(byte[] zipBytes) throws Exception { - Map entries = new HashMap<>(); - try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes))) { - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - entries.put(entry.getName(), zis.readAllBytes()); + // Use ZipFile (central-directory based) instead of ZipInputStream to + // handle data descriptors on STORED entries (multi-segment TDFs). + File tmp = File.createTempFile("tdf-test-", ".zip"); + try { + Files.write(tmp.toPath(), zipBytes); + Map entries = new HashMap<>(); + try (ZipFile zf = new ZipFile(tmp)) { + var it = zf.entries(); + while (it.hasMoreElements()) { + ZipEntry entry = it.nextElement(); + try (InputStream is = zf.getInputStream(entry)) { + entries.put(entry.getName(), is.readAllBytes()); + } + } } + return entries; + } finally { + tmp.delete(); } - return entries; } // ---- Tests ---- @@ -383,23 +456,23 @@ void testErrorHandlingInvalidPEM() { byte[] plaintext = "test".getBytes(StandardCharsets.UTF_8); byte[] invalidPEM = "not-a-valid-pem".getBytes(StandardCharsets.UTF_8); byte[] kasURLBytes = "https://kas.example.com".getBytes(StandardCharsets.UTF_8); - byte[] attrBytes = new byte[0]; long kasPubPtr = allocAndWrite(invalidPEM); long kasURLPtr = allocAndWrite(kasURLBytes); long attrPtr = wasmMalloc(1); // empty attrs need at least 1 byte allocation - long ptPtr = allocAndWrite(plaintext); - int outCapacity = 1024 * 1024; - long outPtr = wasmMalloc(outCapacity); + // Set up streaming I/O state + pendingInput = plaintext; + inputOffset = 0; + outputBuffer = new ByteArrayOutputStream(); long[] result = instance.export("tdf_encrypt").apply( kasPubPtr, (long) invalidPEM.length, kasURLPtr, (long) kasURLBytes.length, attrPtr, 0L, - ptPtr, (long) plaintext.length, - outPtr, (long) outCapacity, - (long) ALG_HS256, (long) ALG_HS256 + (long) plaintext.length, // plaintextSize (i64) + (long) ALG_HS256, (long) ALG_HS256, + 0L // default segment size ); assertEquals(0, result[0], "Expected encrypt to fail with invalid PEM"); @@ -407,4 +480,50 @@ void testErrorHandlingInvalidPEM() { String error = getWasmError(); assertFalse(error.isEmpty(), "Expected non-empty error message"); } + + @Test + void testStreamingLargePayload() throws Exception { + // 1MB payload with 64KB segments → 16 segments + int payloadSize = 1024 * 1024; + int segSize = 64 * 1024; + byte[] plaintext = new byte[payloadSize]; + new SecureRandom().nextBytes(plaintext); + + byte[] tdfBytes = wasmEncryptWithSegSize(plaintext, ALG_HS256, ALG_HS256, segSize); + + // Parse ZIP and verify structure + Map entries = parseZip(tdfBytes); + assertTrue(entries.containsKey("0.manifest.json"), "Missing manifest"); + assertTrue(entries.containsKey("0.payload"), "Missing payload"); + + // Verify segment count in manifest + String manifestJson = new String(entries.get("0.manifest.json"), StandardCharsets.UTF_8); + JsonObject manifest = JsonParser.parseString(manifestJson).getAsJsonObject(); + JsonObject encInfo = manifest.getAsJsonObject("encryptionInformation"); + JsonObject intInfo = encInfo.getAsJsonObject("integrityInformation"); + int segmentCount = intInfo.getAsJsonArray("segments").size(); + assertEquals(16, segmentCount, "Expected 16 segments for 1MB / 64KB"); + + // Unwrap DEK and decrypt each segment to verify round-trip + String wrappedKeyB64 = encInfo.getAsJsonArray("keyAccess") + .get(0).getAsJsonObject().get("wrappedKey").getAsString(); + byte[] dek = new AsymDecryption(kasPrivPEM).decrypt( + Base64.getDecoder().decode(wrappedKeyB64)); + + // Decrypt all segments and reassemble plaintext + byte[] payload = entries.get("0.payload"); + ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(payloadSize); + int offset = 0; + for (int i = 0; i < segmentCount; i++) { + long encSegSize = intInfo.getAsJsonArray("segments") + .get(i).getAsJsonObject().get("encryptedSegmentSize").getAsLong(); + byte[] segCt = Arrays.copyOfRange(payload, offset, offset + (int) encSegSize); + byte[] segPt = new AesGcm(dek).decrypt(new AesGcm.Encrypted(segCt)); + decryptedOut.write(segPt); + offset += (int) encSegSize; + } + + assertArrayEquals(plaintext, decryptedOut.toByteArray(), + "Decrypted plaintext must match original"); + } } diff --git a/wasm-host/src/test/resources/tdfcore.wasm b/wasm-host/src/test/resources/tdfcore.wasm index 311a564e480696c213cfd6ac850ea6247f058992..88c57eb9b36a33b4b7e07f5b1eab2d174d5a61a1 100644 GIT binary patch delta 45596 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