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 b7c4c4e2..0a1f4a46 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -304,6 +304,7 @@ public static class NanoTDFConfig { public List attributes; public List kasInfoList; public CollectionConfig collectionConfig; + public NanoTDFType.PolicyType policyType; public NanoTDFConfig() { this.eccMode = new ECCMode(); @@ -319,6 +320,7 @@ public NanoTDFConfig() { this.attributes = new ArrayList<>(); this.kasInfoList = new ArrayList<>(); this.collectionConfig = new CollectionConfig(false); + this.policyType = NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED; } } @@ -372,6 +374,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 0179a619..e0b41549 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java @@ -111,21 +111,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); } @@ -155,6 +161,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 cee551b3..5e428c79 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java @@ -10,6 +10,7 @@ import io.opentdf.platform.sdk.Config.NanoTDFReaderConfig; import java.nio.charset.StandardCharsets; + import org.apache.commons.io.output.ByteArrayOutputStream; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -311,6 +312,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 15add755..08c6b8aa 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java @@ -77,21 +77,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