From d75b0c335b43f80b83d4a406ef16562988964d76 Mon Sep 17 00:00:00 2001 From: sujan kota Date: Wed, 21 May 2025 16:11:15 -0400 Subject: [PATCH 1/2] feat(sdk): add nanotdf plaintext policy --- .../java/io/opentdf/platform/sdk/Config.java | 6 ++++ .../java/io/opentdf/platform/sdk/NanoTDF.java | 32 +++++++++++------ .../io/opentdf/platform/sdk/NanoTDFTest.java | 34 +++++++++++++++++++ .../io/opentdf/platform/sdk/TDFE2ETest.java | 31 ++++++++++------- 4 files changed, 81 insertions(+), 22 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java index 8b527bd3..68e63f08 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -308,6 +308,7 @@ public static class NanoTDFConfig { public List attributes; public List kasInfoList; public CollectionConfig collectionConfig; + public NanoTDFType.PolicyType policyType; public NanoTDFConfig() { this.eccMode = new ECCMode(); @@ -323,6 +324,7 @@ public NanoTDFConfig() { this.attributes = new ArrayList<>(); this.kasInfoList = new ArrayList<>(); this.collectionConfig = new CollectionConfig(false); + this.policyType = NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED; } } @@ -376,6 +378,10 @@ public static Consumer WithECDSAPolicyBinding(boolean enable) { return (NanoTDFConfig config) -> config.eccMode.setECDSABinding(enable); } + public static Consumer withPolicyType(NanoTDFType.PolicyType policyType) { + return (NanoTDFConfig config) -> config.policyType = policyType; + } + public static class NanoTDFReaderConfig { Set kasAllowlist; boolean ignoreKasAllowlist; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java index 5b7bcf99..ddebf865 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java @@ -116,21 +116,27 @@ private Config.HeaderInfo getHeaderInfo(Config.NanoTDFConfig nanoTDFConfig) thro logger.debug("createNanoTDF policy object - {}", policyObjectAsStr); - AesGcm gcm = new AesGcm(key); - byte[] policyObjectAsBytes = policyObjectAsStr.getBytes(StandardCharsets.UTF_8); - int authTagSize = SymmetricAndPayloadConfig.sizeOfAuthTagForCipher(nanoTDFConfig.config.getCipherType()); - byte[] encryptedPolicy = gcm.encrypt(kEmptyIV, authTagSize, policyObjectAsBytes, 0, policyObjectAsBytes.length); - + // Set policy body and embed in header, either as plain text or encrypted + final byte[] policyBody; PolicyInfo policyInfo = new PolicyInfo(); - byte[] encryptedPolicyWithoutIV = Arrays.copyOfRange(encryptedPolicy, kEmptyIV.length, encryptedPolicy.length); - policyInfo.setEmbeddedEncryptedTextPolicy(encryptedPolicyWithoutIV); + AesGcm gcm = new AesGcm(key); + if (nanoTDFConfig.policyType == NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT) { + policyBody = policyObjectAsStr.getBytes(StandardCharsets.UTF_8); + policyInfo.setEmbeddedPlainTextPolicy(policyBody); + } else { + byte[] policyObjectAsBytes = policyObjectAsStr.getBytes(StandardCharsets.UTF_8); + int authTagSize = SymmetricAndPayloadConfig.sizeOfAuthTagForCipher(nanoTDFConfig.config.getCipherType()); + byte[] encryptedPolicy = gcm.encrypt(kEmptyIV, authTagSize, policyObjectAsBytes, 0, policyObjectAsBytes.length); + policyBody = Arrays.copyOfRange(encryptedPolicy, kEmptyIV.length, encryptedPolicy.length); + policyInfo.setEmbeddedEncryptedTextPolicy(policyBody); + } + // Set policy binding (GMAC) if (nanoTDFConfig.eccMode.isECDSABindingEnabled()) { throw new UnsupportedNanoTDFFeature("ECDSA policy binding is not support"); } else { - byte[] hash = digest.digest(encryptedPolicyWithoutIV); - byte[] gmac = Arrays.copyOfRange(hash, hash.length - kNanoTDFGMACLength, - hash.length); + byte[] hash = digest.digest(policyBody); + byte[] gmac = Arrays.copyOfRange(hash, hash.length - kNanoTDFGMACLength, hash.length); policyInfo.setPolicyBinding(gmac); } @@ -160,6 +166,12 @@ public int createNanoTDF(ByteBuffer data, OutputStream outputStream, throw new NanoTDFMaxSizeLimit("exceeds max size for nano tdf"); } + // check the policy type, support only embedded policy + if (nanoTDFConfig.policyType != NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT && + nanoTDFConfig.policyType != NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED) { + throw new UnsupportedNanoTDFFeature("unsupported policy type"); + } + Config.HeaderInfo headerKeyPair = getHeaderInfo(nanoTDFConfig); Header header = headerKeyPair.getHeader(); AesGcm gcm = headerKeyPair.getKey(); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java index d8e6e397..0f186e0c 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java @@ -12,6 +12,8 @@ import io.opentdf.platform.sdk.nanotdf.Header; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import java.nio.charset.StandardCharsets; + +import io.opentdf.platform.sdk.nanotdf.PolicyInfo; import org.apache.commons.io.output.ByteArrayOutputStream; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -313,6 +315,38 @@ void collection() throws Exception { assertThat(header).isNotEqualTo(newHeader); } + @Test + public void testNanoTDFWithPlainTextPolicy() throws Exception { + List sampleAttributes = List.of("https://example.com/attr/Classification/value/S"); + String sampleKasUrl = "https://api.example.com/kas"; + byte[] sampleData = "test-policy".getBytes(StandardCharsets.UTF_8); + + var kasInfos = new ArrayList(); + var kasInfo = new Config.KASInfo(); + kasInfo.URL = sampleKasUrl; + kasInfo.PublicKey = kasPublicKey; + kasInfo.KID = KID; + kasInfos.add(kasInfo); + + Config.NanoTDFConfig config = Config.newNanoTDFConfig( + Config.withNanoKasInformation(kasInfos.toArray(new Config.KASInfo[0])), + Config.witDataAttributes(sampleAttributes.toArray(new String[0])), + Config.withPolicyType(NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT) + ); + + ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); + NanoTDF nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasRegistryService).build()); + nanoTDF.createNanoTDF(ByteBuffer.wrap(sampleData), tdfOutputStream, config); + + byte[] tdfData = tdfOutputStream.toByteArray(); + Header header = new Header(ByteBuffer.wrap(tdfData)); + String policyJson = new String(header.getPolicyInfo().getEmbeddedPlainTextPolicy(), StandardCharsets.UTF_8); + + assertThat(policyJson) + .as("Policy JSON should contain the expected attribute") + .contains(sampleAttributes.get(0)); + } + private ByteBuffer getHeaderBuffer(ByteBuffer input, NanoTDF nanoTDF, Config.NanoTDFConfig config) throws Exception { ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); nanoTDF.createNanoTDF(input, tdfOutputStream, config); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java index ea79d77a..8345217b 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java @@ -78,21 +78,28 @@ public void createAndDecryptNanoTDF() throws Exception { var kasInfo = new Config.KASInfo(); kasInfo.URL = "http://localhost:8080"; - Config.NanoTDFConfig config = Config.newNanoTDFConfig( - Config.withNanoKasInformation(kasInfo) - ); + for (NanoTDFType.PolicyType policyType : List.of( + NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT, + NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED)) { - String plainText = "text"; - ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); + Config.NanoTDFConfig config = Config.newNanoTDFConfig( + Config.withNanoKasInformation(kasInfo), + Config.withPolicyType(policyType) + ); - NanoTDF ntdf = new NanoTDF(sdk); - ntdf.createNanoTDF(ByteBuffer.wrap(plainText.getBytes()), tdfOutputStream, config); + String plainText = "text"; + ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); - byte[] nanoTDFBytes = tdfOutputStream.toByteArray(); - ByteArrayOutputStream plainTextStream = new ByteArrayOutputStream(); - ntdf.readNanoTDF(ByteBuffer.wrap(nanoTDFBytes), plainTextStream); + NanoTDF ntdf = new NanoTDF(sdk); + ntdf.createNanoTDF(ByteBuffer.wrap(plainText.getBytes()), tdfOutputStream, config); - String out = new String(plainTextStream.toByteArray(), "UTF-8"); - assertThat(out).isEqualTo("text"); + byte[] nanoTDFBytes = tdfOutputStream.toByteArray(); + ByteArrayOutputStream plainTextStream = new ByteArrayOutputStream(); + ntdf.readNanoTDF(ByteBuffer.wrap(nanoTDFBytes), plainTextStream, + Config.newNanoTDFReaderConfig(Config.WithNanoIgnoreKasAllowlist(true))); + + String out = new String(plainTextStream.toByteArray(), StandardCharsets.UTF_8); + assertThat(out).isEqualTo("text"); + } } } \ No newline at end of file From 6a221d9c611e81227bcd43758794f973326ef7a7 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Tue, 27 May 2025 11:23:00 -0400 Subject: [PATCH 2/2] that went away --- sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java index c9271ede..5e428c79 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java @@ -11,7 +11,6 @@ import java.nio.charset.StandardCharsets; -import io.opentdf.platform.sdk.nanotdf.PolicyInfo; import org.apache.commons.io.output.ByteArrayOutputStream; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test;