Skip to content

Commit 6817455

Browse files
Merge branch 'main' into check-null-manifest-assertions
2 parents 962d5d0 + 1ee1367 commit 6817455

File tree

7 files changed

+336
-26
lines changed

7 files changed

+336
-26
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.opentdf.platform;
2+
3+
import io.opentdf.platform.sdk.Config;
4+
import io.opentdf.platform.sdk.NanoTDF;
5+
import io.opentdf.platform.sdk.SDK;
6+
import io.opentdf.platform.sdk.SDKBuilder;
7+
8+
import java.io.ByteArrayInputStream;
9+
import java.io.FileInputStream;
10+
import java.io.FileOutputStream;
11+
import java.io.IOException;
12+
import java.nio.ByteBuffer;
13+
import java.nio.charset.StandardCharsets;
14+
import java.security.NoSuchAlgorithmException;
15+
16+
public class DecryptCollectionExample {
17+
public static void main(String[] args) throws IOException, NanoTDF.NanoTDFMaxSizeLimit, NanoTDF.UnsupportedNanoTDFFeature, NanoTDF.InvalidNanoTDFConfig, NoSuchAlgorithmException, InterruptedException {
18+
String clientId = "opentdf-sdk";
19+
String clientSecret = "secret";
20+
String platformEndpoint = "localhost:8080";
21+
22+
SDKBuilder builder = new SDKBuilder();
23+
SDK sdk = builder.platformEndpoint(platformEndpoint)
24+
.clientSecret(clientId, clientSecret).useInsecurePlaintextConnection(true)
25+
.build();
26+
27+
var kasInfo = new Config.KASInfo();
28+
kasInfo.URL = "http://localhost:8080/kas";
29+
30+
31+
// Convert String to InputStream
32+
NanoTDF nanoTDFClient = new NanoTDF(true);
33+
34+
for (int i = 0; i < 50; i++) {
35+
FileInputStream fis = new FileInputStream(String.format("out/my.%d_ciphertext", i));
36+
nanoTDFClient.readNanoTDF(ByteBuffer.wrap(fis.readAllBytes()), System.out, sdk.getServices().kas());
37+
fis.close();
38+
}
39+
40+
}
41+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.opentdf.platform;
2+
3+
import io.opentdf.platform.sdk.Config;
4+
import io.opentdf.platform.sdk.NanoTDF;
5+
import io.opentdf.platform.sdk.SDK;
6+
import io.opentdf.platform.sdk.SDKBuilder;
7+
8+
import java.io.ByteArrayInputStream;
9+
import java.io.FileNotFoundException;
10+
import java.io.FileOutputStream;
11+
import java.io.IOException;
12+
import java.nio.ByteBuffer;
13+
import java.nio.charset.StandardCharsets;
14+
import java.security.NoSuchAlgorithmException;
15+
16+
public class EncryptCollectionExample {
17+
public static void main(String[] args) throws IOException, NanoTDF.NanoTDFMaxSizeLimit, NanoTDF.UnsupportedNanoTDFFeature, NanoTDF.InvalidNanoTDFConfig, NoSuchAlgorithmException, InterruptedException {
18+
String clientId = "opentdf-sdk";
19+
String clientSecret = "secret";
20+
String platformEndpoint = "localhost:8080";
21+
22+
SDKBuilder builder = new SDKBuilder();
23+
SDK sdk = builder.platformEndpoint(platformEndpoint)
24+
.clientSecret(clientId, clientSecret).useInsecurePlaintextConnection(true)
25+
.build();
26+
27+
var kasInfo = new Config.KASInfo();
28+
kasInfo.URL = "http://localhost:8080/kas";
29+
30+
var tdfConfig = Config.newNanoTDFConfig(
31+
Config.withNanoKasInformation(kasInfo),
32+
Config.witDataAttributes("https://example.com/attr/attr1/value/value1"),
33+
Config.withCollection()
34+
);
35+
36+
String str = "Hello, World!";
37+
38+
// Convert String to InputStream
39+
var in = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
40+
NanoTDF nanoTDFClient = new NanoTDF();
41+
42+
for (int i = 0; i < 50; i++) {
43+
FileOutputStream fos = new FileOutputStream(String.format("out/my.%d_ciphertext", i));
44+
nanoTDFClient.createNanoTDF(ByteBuffer.wrap(str.getBytes()), fos, tdfConfig,
45+
sdk.getServices().kas());
46+
}
47+
48+
}
49+
}

sdk/src/main/java/io/opentdf/platform/sdk/Config.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN;
44
import io.opentdf.platform.sdk.nanotdf.ECCMode;
5+
import io.opentdf.platform.sdk.nanotdf.Header;
56
import io.opentdf.platform.sdk.nanotdf.NanoTDFType;
67
import io.opentdf.platform.sdk.nanotdf.SymmetricAndPayloadConfig;
78

89
import io.opentdf.platform.policy.Value;
10+
import org.bouncycastle.oer.its.ieee1609dot2.HeaderInfo;
911

1012
import java.util.*;
13+
import java.util.concurrent.atomic.AtomicInteger;
1114
import java.util.function.Consumer;
1215

1316
/**
@@ -20,6 +23,7 @@ public class Config {
2023
public static final int DEFAULT_SEGMENT_SIZE = 2 * 1024 * 1024; // 2mb
2124
public static final String KAS_PUBLIC_KEY_PATH = "/kas_public_key";
2225
public static final String DEFAULT_MIME_TYPE = "application/octet-stream";
26+
public static final int MAX_COLLECTION_ITERATION = (1 << 24) - 1;
2327

2428
public enum TDFFormat {
2529
JSONFormat,
@@ -248,6 +252,7 @@ public static class NanoTDFConfig {
248252
public SymmetricAndPayloadConfig config;
249253
public List<String> attributes;
250254
public List<KASInfo> kasInfoList;
255+
public CollectionConfig collectionConfig;
251256

252257
public NanoTDFConfig() {
253258
this.eccMode = new ECCMode();
@@ -262,6 +267,7 @@ public NanoTDFConfig() {
262267

263268
this.attributes = new ArrayList<>();
264269
this.kasInfoList = new ArrayList<>();
270+
this.collectionConfig = new CollectionConfig(false);
265271
}
266272
}
267273

@@ -273,6 +279,12 @@ public static NanoTDFConfig newNanoTDFConfig(Consumer<NanoTDFConfig>... options)
273279
return config;
274280
}
275281

282+
public static Consumer<NanoTDFConfig> withCollection() {
283+
return (NanoTDFConfig config) -> {
284+
config.collectionConfig = new CollectionConfig(true);
285+
};
286+
}
287+
276288
public static Consumer<NanoTDFConfig> witDataAttributes(String... attributes) {
277289
return (NanoTDFConfig config) -> {
278290
Collections.addAll(config.attributes, attributes);
@@ -304,4 +316,60 @@ public static Consumer<NanoTDFConfig> withEllipticCurve(String curve) {
304316
public static Consumer<NanoTDFConfig> WithECDSAPolicyBinding() {
305317
return (NanoTDFConfig config) -> config.eccMode.setECDSABinding(false);
306318
}
319+
320+
public static class HeaderInfo {
321+
private final Header header;
322+
private final AesGcm key;
323+
private final int iteration;
324+
325+
public HeaderInfo(Header header,AesGcm key, int iteration) {
326+
this.header = header;
327+
this.key = key;
328+
this.iteration = iteration;
329+
}
330+
331+
public Header getHeader() {
332+
return header;
333+
}
334+
335+
public int getIteration() {
336+
return iteration;
337+
}
338+
339+
public AesGcm getKey() {
340+
return key;
341+
}
342+
}
343+
344+
public static class CollectionConfig {
345+
private int iterationCounter;
346+
private HeaderInfo headerInfo;
347+
public final boolean useCollection;
348+
private Boolean updatedHeaderInfo;
349+
350+
351+
public CollectionConfig(boolean useCollection) {
352+
this.useCollection = useCollection;
353+
}
354+
355+
public synchronized HeaderInfo getHeaderInfo() throws InterruptedException {
356+
int iteration = iterationCounter;
357+
iterationCounter = (iterationCounter + 1) % MAX_COLLECTION_ITERATION;
358+
359+
if (iteration == 0) {
360+
updatedHeaderInfo = false;
361+
return null;
362+
}
363+
while (!updatedHeaderInfo) {
364+
this.wait();
365+
}
366+
return new HeaderInfo(headerInfo.getHeader(), headerInfo.getKey(), iteration);
367+
}
368+
369+
public synchronized void updateHeaderInfo(HeaderInfo headerInfo) {
370+
this.headerInfo = headerInfo;
371+
updatedHeaderInfo = true;
372+
this.notifyAll();
373+
}
374+
}
307375
}

sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.io.IOException;
66
import java.io.OutputStream;
77
import java.nio.ByteBuffer;
8+
import java.nio.ByteOrder;
89
import java.nio.charset.StandardCharsets;
910
import java.security.*;
1011
import java.util.*;
@@ -31,6 +32,19 @@ public class NanoTDF {
3132
private static final int kIvPadding = 9;
3233
private static final int kNanoTDFIvSize = 3;
3334
private static final byte[] kEmptyIV = new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
35+
private final CollectionStore collectionStore;
36+
37+
public NanoTDF() {
38+
this(new CollectionStore.NoOpCollectionStore());
39+
}
40+
41+
public NanoTDF(boolean collectionStoreEnabled) {
42+
this(collectionStoreEnabled ? new CollectionStoreImpl() : null);
43+
}
44+
45+
public NanoTDF(CollectionStore collectionStore) {
46+
this.collectionStore = collectionStore;
47+
}
3448

3549
public static class NanoTDFMaxSizeLimit extends Exception {
3650
public NanoTDFMaxSizeLimit(String errorMessage) {
@@ -50,19 +64,16 @@ public InvalidNanoTDFConfig(String errorMessage) {
5064
}
5165
}
5266

53-
public int createNanoTDF(ByteBuffer data, OutputStream outputStream,
54-
Config.NanoTDFConfig nanoTDFConfig,
55-
SDK.KAS kas) throws IOException, NanoTDFMaxSizeLimit, InvalidNanoTDFConfig,
56-
NoSuchAlgorithmException, UnsupportedNanoTDFFeature {
57-
58-
int nanoTDFSize = 0;
59-
Gson gson = new GsonBuilder().create();
60-
61-
int dataSize = data.limit();
62-
if (dataSize > kMaxTDFSize) {
63-
throw new NanoTDFMaxSizeLimit("exceeds max size for nano tdf");
67+
private Config.HeaderInfo getHeaderInfo(Config.NanoTDFConfig nanoTDFConfig, SDK.KAS kas)
68+
throws InvalidNanoTDFConfig, UnsupportedNanoTDFFeature, NoSuchAlgorithmException, InterruptedException {
69+
if (nanoTDFConfig.collectionConfig.useCollection) {
70+
Config.HeaderInfo headerInfo = nanoTDFConfig.collectionConfig.getHeaderInfo();
71+
if (headerInfo != null) {
72+
return headerInfo;
73+
}
6474
}
6575

76+
Gson gson = new GsonBuilder().create();
6677
if (nanoTDFConfig.kasInfoList.isEmpty()) {
6778
throw new InvalidNanoTDFConfig("kas url is missing");
6879
}
@@ -120,9 +131,32 @@ public int createNanoTDF(ByteBuffer data, OutputStream outputStream,
120131
header.setPayloadConfig(nanoTDFConfig.config);
121132
header.setEphemeralKey(compressedPubKey);
122133
header.setKasLocator(kasURL);
123-
124134
header.setPolicyInfo(policyInfo);
125135

136+
Config.HeaderInfo headerInfo = new Config.HeaderInfo(header, gcm, 0);
137+
if (nanoTDFConfig.collectionConfig.useCollection) {
138+
nanoTDFConfig.collectionConfig.updateHeaderInfo(headerInfo);
139+
}
140+
141+
return headerInfo;
142+
}
143+
144+
public int createNanoTDF(ByteBuffer data, OutputStream outputStream,
145+
Config.NanoTDFConfig nanoTDFConfig,
146+
SDK.KAS kas) throws IOException, NanoTDFMaxSizeLimit, InvalidNanoTDFConfig,
147+
NoSuchAlgorithmException, UnsupportedNanoTDFFeature, InterruptedException {
148+
int nanoTDFSize = 0;
149+
150+
int dataSize = data.limit();
151+
if (dataSize > kMaxTDFSize) {
152+
throw new NanoTDFMaxSizeLimit("exceeds max size for nano tdf");
153+
}
154+
155+
Config.HeaderInfo headerKeyPair = getHeaderInfo(nanoTDFConfig, kas);
156+
Header header = headerKeyPair.getHeader();
157+
AesGcm gcm = headerKeyPair.getKey();
158+
int iteration = headerKeyPair.getIteration();
159+
126160
int headerSize = header.getTotalSize();
127161
ByteBuffer bufForHeader = ByteBuffer.allocate(headerSize);
128162
header.writeIntoBuffer(bufForHeader);
@@ -132,13 +166,21 @@ public int createNanoTDF(ByteBuffer data, OutputStream outputStream,
132166
nanoTDFSize += headerSize;
133167
logger.debug("createNanoTDF header length {}", headerSize);
134168

169+
int authTagSize = SymmetricAndPayloadConfig.sizeOfAuthTagForCipher(nanoTDFConfig.config.getCipherType());
135170
// Encrypt the data
136171
byte[] actualIV = new byte[kIvPadding + kNanoTDFIvSize];
137-
do {
138-
byte[] iv = new byte[kNanoTDFIvSize];
139-
SecureRandom.getInstanceStrong().nextBytes(iv);
140-
System.arraycopy(iv, 0, actualIV, kIvPadding, iv.length);
141-
} while (Arrays.equals(actualIV, kEmptyIV)); // if match, we need to retry to prevent key + iv reuse with the policy
172+
if (nanoTDFConfig.collectionConfig.useCollection) {
173+
ByteBuffer b = ByteBuffer.allocate(4);
174+
b.order(ByteOrder.LITTLE_ENDIAN);
175+
b.putInt(iteration);
176+
System.arraycopy(b.array(), 0, actualIV, kIvPadding, kNanoTDFIvSize);
177+
} else {
178+
do {
179+
byte[] iv = new byte[kNanoTDFIvSize];
180+
SecureRandom.getInstanceStrong().nextBytes(iv);
181+
System.arraycopy(iv, 0, actualIV, kIvPadding, iv.length);
182+
} while (Arrays.equals(actualIV, kEmptyIV)); // if match, we need to retry to prevent key + iv reuse with the policy
183+
}
142184

143185
byte[] cipherData = gcm.encrypt(actualIV, authTagSize, data.array(), data.arrayOffset(), dataSize);
144186

@@ -157,23 +199,30 @@ public int createNanoTDF(ByteBuffer data, OutputStream outputStream,
157199
return nanoTDFSize;
158200
}
159201

202+
160203
public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream,
161204
SDK.KAS kas) throws IOException {
162205

163206
Header header = new Header(nanoTDF);
207+
CollectionKey cachedKey = collectionStore.getKey(header);
208+
byte[] key = cachedKey.getKey();
164209

165-
// create base64 encoded
166-
byte[] headerData = new byte[header.getTotalSize()];
167-
header.writeIntoBuffer(ByteBuffer.wrap(headerData));
168-
String base64HeaderData = Base64.getEncoder().encodeToString(headerData);
210+
// perform unwrap is not in collectionStore;
211+
if (key == null) {
212+
// create base64 encoded
213+
byte[] headerData = new byte[header.getTotalSize()];
214+
header.writeIntoBuffer(ByteBuffer.wrap(headerData));
215+
String base64HeaderData = Base64.getEncoder().encodeToString(headerData);
169216

170-
logger.debug("readNanoTDF header length {}", headerData.length);
217+
logger.debug("readNanoTDF header length {}", headerData.length);
171218

172-
String kasUrl = header.getKasLocator().getResourceUrl();
219+
String kasUrl = header.getKasLocator().getResourceUrl();
173220

174-
byte[] key = kas.unwrapNanoTDF(header.getECCMode().getEllipticCurveType(),
175-
base64HeaderData,
176-
kasUrl);
221+
key = kas.unwrapNanoTDF(header.getECCMode().getEllipticCurveType(),
222+
base64HeaderData,
223+
kasUrl);
224+
collectionStore.store(header, new CollectionKey(key));
225+
}
177226

178227
byte[] payloadLengthBuf = new byte[4];
179228
nanoTDF.get(payloadLengthBuf, 1, 3);
@@ -213,4 +262,15 @@ PolicyObject createPolicyObject(List<String> attributes) {
213262
}
214263
return policyObject;
215264
}
265+
266+
public static class CollectionKey {
267+
private final byte[] key;
268+
269+
public CollectionKey(byte[] key) {
270+
this.key = key;
271+
}
272+
protected byte[] getKey() {
273+
return key;
274+
}
275+
}
216276
}

0 commit comments

Comments
 (0)