17
17
import java .util .Arrays ;
18
18
import java .util .Collections ;
19
19
import java .util .HashMap ;
20
+ import java .util .HashSet ;
20
21
import java .util .List ;
21
22
import java .util .Map ;
23
+ import java .util .Set ;
22
24
import java .util .regex .Matcher ;
23
25
import java .util .regex .Pattern ;
24
26
@@ -70,25 +72,88 @@ public class MetaStore extends ProviderStore {
70
72
private static final String DEFAULT_HASH_KEY = "N" ;
71
73
private static final String DEFAULT_RANGE_KEY = "V" ;
72
74
75
+ /** Default no-op implementation of {@link ExtraDataSupplier}. */
76
+ private static final EmptyExtraDataSupplier EMPTY_EXTRA_DATA_SUPPLIER
77
+ = new EmptyExtraDataSupplier ();
78
+
79
+ /** DDB fields that must be encrypted. */
80
+ private static final Set <String > ENCRYPTED_FIELDS ;
81
+ static {
82
+ final Set <String > tempEncryptedFields = new HashSet <>();
83
+ tempEncryptedFields .add (MATERIAL_TYPE_VERSION );
84
+ tempEncryptedFields .add (ENCRYPTION_KEY_FIELD );
85
+ tempEncryptedFields .add (ENCRYPTION_ALGORITHM_FIELD );
86
+ tempEncryptedFields .add (INTEGRITY_KEY_FIELD );
87
+ tempEncryptedFields .add (INTEGRITY_ALGORITHM_FIELD );
88
+ ENCRYPTED_FIELDS = tempEncryptedFields ;
89
+ }
90
+
73
91
private final Map <String , ExpectedAttributeValue > doesNotExist ;
92
+ private final Set <String > doNotEncrypt ;
74
93
private final String tableName ;
75
94
private final AmazonDynamoDB ddb ;
76
95
private final DynamoDBEncryptor encryptor ;
77
96
private final EncryptionContext ddbCtx ;
97
+ private final ExtraDataSupplier extraDataSupplier ;
98
+
99
+ /**
100
+ * Provides extra data that should be persisted along with the standard material data.
101
+ */
102
+ public interface ExtraDataSupplier {
78
103
104
+ /**
105
+ * Gets the extra data attributes for the specified material name.
106
+ *
107
+ * @param materialName material name.
108
+ * @param version version number.
109
+ * @return plain text of the extra data.
110
+ */
111
+ Map <String , AttributeValue > getAttributes (final String materialName , final long version );
112
+
113
+ /**
114
+ * Gets the extra data field names that should be signed only but not encrypted.
115
+ *
116
+ * @return signed only fields.
117
+ */
118
+ Set <String > getSignedOnlyFieldNames ();
119
+ }
120
+
121
+ /**
122
+ * Create a new MetaStore with specified table name.
123
+ *
124
+ * @param ddb Interface for accessing DynamoDB.
125
+ * @param tableName DynamoDB table name for this {@link MetaStore}.
126
+ * @param encryptor used to perform crypto operations on the record attributes.
127
+ */
79
128
public MetaStore (final AmazonDynamoDB ddb , final String tableName ,
80
129
final DynamoDBEncryptor encryptor ) {
130
+ this (ddb , tableName , encryptor , EMPTY_EXTRA_DATA_SUPPLIER );
131
+ }
132
+
133
+ /**
134
+ * Create a new MetaStore with specified table name and extra data supplier.
135
+ *
136
+ * @param ddb Interface for accessing DynamoDB.
137
+ * @param tableName DynamoDB table name for this {@link MetaStore}.
138
+ * @param encryptor used to perform crypto operations on the record attributes
139
+ * @param extraDataSupplier provides extra data that should be stored along with the material.
140
+ */
141
+ public MetaStore (final AmazonDynamoDB ddb , final String tableName ,
142
+ final DynamoDBEncryptor encryptor , final ExtraDataSupplier extraDataSupplier ) {
81
143
this .ddb = checkNotNull (ddb , "ddb must not be null" );
82
144
this .tableName = checkNotNull (tableName , "tableName must not be null" );
83
145
this .encryptor = checkNotNull (encryptor , "encryptor must not be null" );
146
+ this .extraDataSupplier = checkNotNull (extraDataSupplier , "extraDataSupplier must not be null" );
84
147
85
- ddbCtx = new EncryptionContext .Builder ().withTableName (this .tableName )
148
+ this . ddbCtx = new EncryptionContext .Builder ().withTableName (this .tableName )
86
149
.withHashKeyName (DEFAULT_HASH_KEY ).withRangeKeyName (DEFAULT_RANGE_KEY ).build ();
87
150
88
151
final Map <String , ExpectedAttributeValue > tmpExpected = new HashMap <String , ExpectedAttributeValue >();
89
152
tmpExpected .put (DEFAULT_HASH_KEY , new ExpectedAttributeValue ().withExists (false ));
90
153
tmpExpected .put (DEFAULT_RANGE_KEY , new ExpectedAttributeValue ().withExists (false ));
91
154
doesNotExist = Collections .unmodifiableMap (tmpExpected );
155
+
156
+ this .doNotEncrypt = getSignedOnlyFields (extraDataSupplier );
92
157
}
93
158
94
159
@ Override
@@ -105,10 +170,8 @@ public EncryptionMaterialsProvider getProvider(final String materialName, final
105
170
106
171
@ Override
107
172
public EncryptionMaterialsProvider getOrCreate (final String materialName , final long nextId ) {
108
- final SecretKeySpec encryptionKey = new SecretKeySpec (Utils .getRandom (32 ), DEFAULT_ENCRYPTION );
109
- final SecretKeySpec integrityKey = new SecretKeySpec (Utils .getRandom (32 ), DEFAULT_INTEGRITY );
110
- final Map <String , AttributeValue > ciphertext = conditionalPut (encryptKeys (materialName ,
111
- nextId , encryptionKey , integrityKey ));
173
+ final Map <String , AttributeValue > plaintext = createMaterialItem (materialName , nextId );
174
+ final Map <String , AttributeValue > ciphertext = conditionalPut (getEncryptedText (plaintext ));
112
175
return decryptProvider (ciphertext );
113
176
}
114
177
@@ -145,9 +208,10 @@ public long getVersionFromMaterialDescription(final Map<String, String> descript
145
208
146
209
/**
147
210
* This API retrieves the intermediate keys from the source region and replicates it in the target region.
148
- * @param materialName
149
- * @param version
150
- * @param targetMetaStore
211
+ *
212
+ * @param materialName material name of the encryption material.
213
+ * @param version version of the encryption material.
214
+ * @param targetMetaStore target MetaStore where the encryption material to be stored.
151
215
*/
152
216
public void replicate (final String materialName , final long version , final MetaStore targetMetaStore ) {
153
217
try {
@@ -168,8 +232,14 @@ public void replicate(final String materialName, final long version, final MetaS
168
232
//Item already present.
169
233
}
170
234
}
235
+
171
236
/**
172
237
* Creates a DynamoDB Table with the correct properties to be used with a ProviderStore.
238
+ *
239
+ * @param ddb interface for accessing DynamoDB
240
+ * @param tableName name of table that stores the meta data of the material.
241
+ * @param provisionedThroughput required provisioned throughput of the this table.
242
+ * @return result of create table request.
173
243
*/
174
244
public static CreateTableResult createTable (final AmazonDynamoDB ddb , final String tableName ,
175
245
final ProvisionedThroughput provisionedThroughput ) {
@@ -181,6 +251,44 @@ public static CreateTableResult createTable(final AmazonDynamoDB ddb, final Stri
181
251
182
252
}
183
253
254
+ /**
255
+ * Empty extra data supplier. This default class is intended to simplify the default
256
+ * implementation of {@link MetaStore}.
257
+ */
258
+ private static class EmptyExtraDataSupplier implements ExtraDataSupplier {
259
+ @ Override
260
+ public Map <String , AttributeValue > getAttributes (String materialName , long version ) {
261
+ return Collections .emptyMap ();
262
+ }
263
+
264
+ @ Override
265
+ public Set <String > getSignedOnlyFieldNames () {
266
+ return Collections .emptySet ();
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Get a set of fields that must be signed but not encrypted.
272
+ *
273
+ * @param extraDataSupplier extra data supplier that is used to return sign only field names.
274
+ * @return fields that must be signed.
275
+ */
276
+ private static Set <String > getSignedOnlyFields (final ExtraDataSupplier extraDataSupplier ) {
277
+ final Set <String > signedOnlyFields = extraDataSupplier .getSignedOnlyFieldNames ();
278
+ for (final String signedOnlyField : signedOnlyFields ) {
279
+ if (ENCRYPTED_FIELDS .contains (signedOnlyField )) {
280
+ throw new IllegalArgumentException (signedOnlyField + " must be encrypted" );
281
+ }
282
+ }
283
+
284
+ // fields that should not be encrypted
285
+ final Set <String > doNotEncryptFields = new HashSet <>();
286
+ doNotEncryptFields .add (DEFAULT_HASH_KEY );
287
+ doNotEncryptFields .add (DEFAULT_RANGE_KEY );
288
+ doNotEncryptFields .addAll (signedOnlyFields );
289
+ return Collections .unmodifiableSet (doNotEncryptFields );
290
+ }
291
+
184
292
private Map <String , AttributeValue > conditionalPut (final Map <String , AttributeValue > item ) {
185
293
try {
186
294
final PutItemRequest put = new PutItemRequest ().withTableName (tableName ).withItem (item )
@@ -201,19 +309,29 @@ private Map<String, AttributeValue> ddbGet(final Map<String, AttributeValue> ddb
201
309
.withKey (ddbKey )).getItem ();
202
310
}
203
311
204
- private Map <String , AttributeValue > encryptKeys (final String name , final long version ,
205
- final SecretKeySpec encryptionKey , final SecretKeySpec integrityKey ) {
312
+ /**
313
+ * Build an material item for a given material name and version with newly generated
314
+ * encryption and integrity keys.
315
+ *
316
+ * @param materialName material name.
317
+ * @param version version of the material.
318
+ * @return newly generated plaintext material item.
319
+ */
320
+ private Map <String , AttributeValue > createMaterialItem (final String materialName , final long version ) {
321
+ final SecretKeySpec encryptionKey = new SecretKeySpec (Utils .getRandom (32 ), DEFAULT_ENCRYPTION );
322
+ final SecretKeySpec integrityKey = new SecretKeySpec (Utils .getRandom (32 ), DEFAULT_INTEGRITY );
323
+
206
324
final Map <String , AttributeValue > plaintext = new HashMap <String , AttributeValue >();
207
- plaintext .put (DEFAULT_HASH_KEY , new AttributeValue ().withS (name ));
325
+ plaintext .put (DEFAULT_HASH_KEY , new AttributeValue ().withS (materialName ));
208
326
plaintext .put (DEFAULT_RANGE_KEY , new AttributeValue ().withN (Long .toString (version )));
209
327
plaintext .put (MATERIAL_TYPE_VERSION , new AttributeValue ().withS ("0" ));
210
- plaintext .put (ENCRYPTION_KEY_FIELD ,
211
- new AttributeValue ().withB (ByteBuffer .wrap (encryptionKey .getEncoded ())));
328
+ plaintext .put (ENCRYPTION_KEY_FIELD , new AttributeValue ().withB (ByteBuffer .wrap (encryptionKey .getEncoded ())));
212
329
plaintext .put (ENCRYPTION_ALGORITHM_FIELD , new AttributeValue ().withS (encryptionKey .getAlgorithm ()));
213
- plaintext
214
- .put (INTEGRITY_KEY_FIELD , new AttributeValue ().withB (ByteBuffer .wrap (integrityKey .getEncoded ())));
330
+ plaintext .put (INTEGRITY_KEY_FIELD , new AttributeValue ().withB (ByteBuffer .wrap (integrityKey .getEncoded ())));
215
331
plaintext .put (INTEGRITY_ALGORITHM_FIELD , new AttributeValue ().withS (integrityKey .getAlgorithm ()));
216
- return getEncryptedText (plaintext );
332
+ plaintext .putAll (extraDataSupplier .getAttributes (materialName , version ));
333
+
334
+ return plaintext ;
217
335
}
218
336
219
337
private EncryptionMaterialsProvider decryptProvider (final Map <String , AttributeValue > item ) {
@@ -237,19 +355,31 @@ private EncryptionMaterialsProvider decryptProvider(final Map<String, AttributeV
237
355
buildDescription (plaintext ));
238
356
}
239
357
240
- private Map <String , AttributeValue > getPlainText (Map <String , AttributeValue > item ) {
358
+ /**
359
+ * Decrypts attributes in the ciphertext item using {@link DynamoDBEncryptor}.
360
+ * except the attribute names specified in doNotEncrypt.
361
+ * @param ciphertext the ciphertext to be decrypted.
362
+ * @throws AmazonClientException when failed to decrypt material item.
363
+ * @return decrypted item.
364
+ */
365
+ private Map <String , AttributeValue > getPlainText (final Map <String , AttributeValue > ciphertext ) {
241
366
try {
242
- return encryptor .decryptAllFieldsExcept (item ,
243
- ddbCtx , DEFAULT_HASH_KEY , DEFAULT_RANGE_KEY );
367
+ return encryptor .decryptAllFieldsExcept (ciphertext , ddbCtx , doNotEncrypt );
244
368
} catch (final GeneralSecurityException e ) {
245
369
throw new AmazonClientException (e );
246
370
}
247
371
}
248
372
373
+ /**
374
+ * Encrypts attributes in the plaintext item using {@link DynamoDBEncryptor}.
375
+ * except the attribute names specified in doNotEncrypt.
376
+ *
377
+ * @throws AmazonClientException when failed to encrypt material item.
378
+ * @param plaintext plaintext to be encrypted.
379
+ */
249
380
private Map <String , AttributeValue > getEncryptedText (Map <String , AttributeValue > plaintext ) {
250
381
try {
251
- return encryptor .encryptAllFieldsExcept (plaintext , ddbCtx , DEFAULT_HASH_KEY ,
252
- DEFAULT_RANGE_KEY );
382
+ return encryptor .encryptAllFieldsExcept (plaintext , ddbCtx , doNotEncrypt );
253
383
} catch (final GeneralSecurityException e ) {
254
384
throw new AmazonClientException (e );
255
385
}
0 commit comments