Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ public static class NanoTDFConfig {
public List<String> attributes;
public List<KASInfo> kasInfoList;
public CollectionConfig collectionConfig;
public NanoTDFType.PolicyType policyType;

public NanoTDFConfig() {
this.eccMode = new ECCMode();
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -372,6 +374,10 @@ public static Consumer<NanoTDFConfig> WithECDSAPolicyBinding(boolean enable) {
return (NanoTDFConfig config) -> config.eccMode.setECDSABinding(enable);
}

public static Consumer<NanoTDFConfig> withPolicyType(NanoTDFType.PolicyType policyType) {
return (NanoTDFConfig config) -> config.policyType = policyType;
}

public static class NanoTDFReaderConfig {
Set<String> kasAllowlist;
boolean ignoreKasAllowlist;
Expand Down
32 changes: 22 additions & 10 deletions sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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();
Expand Down
33 changes: 33 additions & 0 deletions sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -311,6 +312,38 @@ void collection() throws Exception {
assertThat(header).isNotEqualTo(newHeader);
}

@Test
public void testNanoTDFWithPlainTextPolicy() throws Exception {
List<String> 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<Config.KASInfo>();
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);
Expand Down
31 changes: 19 additions & 12 deletions sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
}