55import java .io .IOException ;
66import java .io .OutputStream ;
77import java .nio .ByteBuffer ;
8+ import java .nio .ByteOrder ;
89import java .nio .charset .StandardCharsets ;
910import java .security .*;
1011import 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