55import java .util .HashMap ;
66import java .util .Map ;
77import java .util .Map .Entry ;
8+ import software .amazon .awssdk .core .ResponseInputStream ;
89import software .amazon .awssdk .protocols .jsoncore .JsonNode ;
910import software .amazon .awssdk .protocols .jsoncore .JsonNodeParser ;
1011import software .amazon .awssdk .protocols .jsoncore .JsonWriter ;
1112import software .amazon .awssdk .protocols .jsoncore .JsonWriter .JsonGenerationException ;
13+ import software .amazon .awssdk .services .s3 .S3Client ;
14+ import software .amazon .awssdk .services .s3 .model .GetObjectRequest ;
1215import software .amazon .awssdk .services .s3 .model .GetObjectResponse ;
1316import software .amazon .awssdk .services .s3 .model .PutObjectRequest ;
1417import software .amazon .encryption .s3 .S3EncryptionClientException ;
1720import software .amazon .encryption .s3 .materials .EncryptionMaterials ;
1821import software .amazon .encryption .s3 .materials .S3Keyring ;
1922
20- /**
21- * This stores encryption metadata in the S3 object metadata.
22- * The name is not a typo
23- */
24- public class S3ObjectMetadataStrategy implements ContentMetadataEncodingStrategy ,
25- ContentMetadataDecodingStrategy {
26- private final Base64 .Encoder _encoder ;
27- private final Base64 .Decoder _decoder ;
28-
29- private S3ObjectMetadataStrategy (Builder builder ) {
30- this ._encoder = builder ._encoder ;
31- this ._decoder = builder ._decoder ;
32- }
23+ public abstract class ContentMetadataStrategy implements ContentMetadataEncodingStrategy , ContentMetadataDecodingStrategy {
24+
25+ private static final Base64 .Encoder ENCODER = Base64 .getEncoder ();
26+ private static final Base64 .Decoder DECODER = Base64 .getDecoder ();
27+
28+ public static final ContentMetadataDecodingStrategy INSTRUCTION_FILE = new ContentMetadataDecodingStrategy () {
29+
30+ private static final String FILE_SUFFIX = ".instruction" ;
31+
32+ @ Override
33+ public ContentMetadata decodeMetadata (S3Client client , GetObjectRequest getObjectRequest , GetObjectResponse response ) {
34+ GetObjectRequest instructionGetObjectRequest = GetObjectRequest .builder ()
35+ .bucket (getObjectRequest .bucket ())
36+ .key (getObjectRequest .key () + FILE_SUFFIX )
37+ .build ();
38+ ResponseInputStream <GetObjectResponse > instruction = client .getObject (
39+ instructionGetObjectRequest );
3340
34- public static Builder builder () { return new Builder (); }
35-
36- @ Override
37- public PutObjectRequest encodeMetadata (
38- EncryptionMaterials materials ,
39- EncryptedContent encryptedContent ,
40- PutObjectRequest request ) {
41- Map <String ,String > metadata = new HashMap <>(request .metadata ());
42- EncryptedDataKey edk = materials .encryptedDataKeys ().get (0 );
43- metadata .put (MetadataKeyConstants .ENCRYPTED_DATA_KEY_V2 , _encoder .encodeToString (edk .ciphertext ()));
44- metadata .put (MetadataKeyConstants .CONTENT_NONCE , _encoder .encodeToString (encryptedContent .nonce ));
45- metadata .put (MetadataKeyConstants .CONTENT_CIPHER , materials .algorithmSuite ().cipherName ());
46- metadata .put (MetadataKeyConstants .CONTENT_CIPHER_TAG_LENGTH , Integer .toString (materials .algorithmSuite ().cipherTagLengthBits ()));
47- metadata .put (MetadataKeyConstants .ENCRYPTED_DATA_KEY_ALGORITHM , new String (edk .keyProviderInfo (), StandardCharsets .UTF_8 ));
48-
49- try (JsonWriter jsonWriter = JsonWriter .create ()) {
50- jsonWriter .writeStartObject ();
51- for (Entry <String ,String > entry : materials .encryptionContext ().entrySet ()) {
52- jsonWriter .writeFieldName (entry .getKey ()).writeValue (entry .getValue ());
41+ Map <String , String > metadata = new HashMap <>();
42+ JsonNodeParser parser = JsonNodeParser .create ();
43+ JsonNode objectNode = parser .parse (instruction );
44+ for (Map .Entry <String , JsonNode > entry : objectNode .asObject ().entrySet ()) {
45+ metadata .put (entry .getKey (), entry .getValue ().asString ());
5346 }
54- jsonWriter .writeEndObject ();
5547
56- String jsonEncryptionContext = new String (jsonWriter .getBytes (), StandardCharsets .UTF_8 );
57- metadata .put (MetadataKeyConstants .ENCRYPTED_DATA_KEY_CONTEXT , jsonEncryptionContext );
58- } catch (JsonGenerationException e ) {
59- throw new S3EncryptionClientException ("Cannot serialize encryption context to JSON." , e );
48+ return ContentMetadataStrategy .readFromMap (metadata );
6049 }
50+ };
51+
52+ public static final ContentMetadataStrategy OBJECT_METADATA = new ContentMetadataStrategy () {
53+
54+ @ Override
55+ public PutObjectRequest encodeMetadata (EncryptionMaterials materials ,
56+ EncryptedContent encryptedContent , PutObjectRequest request ) {
57+ Map <String ,String > metadata = new HashMap <>(request .metadata ());
58+ EncryptedDataKey edk = materials .encryptedDataKeys ().get (0 );
59+ metadata .put (MetadataKeyConstants .ENCRYPTED_DATA_KEY_V2 , ENCODER .encodeToString (edk .ciphertext ()));
60+ metadata .put (MetadataKeyConstants .CONTENT_NONCE , ENCODER .encodeToString (encryptedContent .nonce ));
61+ metadata .put (MetadataKeyConstants .CONTENT_CIPHER , materials .algorithmSuite ().cipherName ());
62+ metadata .put (MetadataKeyConstants .CONTENT_CIPHER_TAG_LENGTH , Integer .toString (materials .algorithmSuite ().cipherTagLengthBits ()));
63+ metadata .put (MetadataKeyConstants .ENCRYPTED_DATA_KEY_ALGORITHM , new String (edk .keyProviderInfo (), StandardCharsets .UTF_8 ));
64+
65+ try (JsonWriter jsonWriter = JsonWriter .create ()) {
66+ jsonWriter .writeStartObject ();
67+ for (Entry <String ,String > entry : materials .encryptionContext ().entrySet ()) {
68+ jsonWriter .writeFieldName (entry .getKey ()).writeValue (entry .getValue ());
69+ }
70+ jsonWriter .writeEndObject ();
6171
62- return request .toBuilder ().metadata (metadata ).build ();
63- }
72+ String jsonEncryptionContext = new String (jsonWriter .getBytes (), StandardCharsets .UTF_8 );
73+ metadata .put (MetadataKeyConstants .ENCRYPTED_DATA_KEY_CONTEXT , jsonEncryptionContext );
74+ } catch (JsonGenerationException e ) {
75+ throw new S3EncryptionClientException ("Cannot serialize encryption context to JSON." , e );
76+ }
6477
65- @ Override
66- public ContentMetadata decodeMetadata (GetObjectResponse response ) {
67- Map <String , String > metadata = response .metadata ();
78+ return request .toBuilder ().metadata (metadata ).build ();
79+ }
6880
81+ @ Override
82+ public ContentMetadata decodeMetadata (S3Client client , GetObjectRequest request , GetObjectResponse response ) {
83+ return ContentMetadataStrategy .readFromMap (response .metadata ());
84+ }
85+ };
86+
87+ private static ContentMetadata readFromMap (Map <String , String > metadata ) {
6988 // Get algorithm suite
7089 final String contentEncryptionAlgorithm = metadata .get (MetadataKeyConstants .CONTENT_CIPHER );
7190 AlgorithmSuite algorithmSuite ;
@@ -89,7 +108,7 @@ public ContentMetadata decodeMetadata(GetObjectResponse response) {
89108 switch (algorithmSuite ) {
90109 case ALG_AES_256_CBC_IV16_NO_KDF :
91110 // Extract encrypted data key ciphertext
92- edkCiphertext = _decoder .decode (metadata .get (MetadataKeyConstants .ENCRYPTED_DATA_KEY_V1 ));
111+ edkCiphertext = DECODER .decode (metadata .get (MetadataKeyConstants .ENCRYPTED_DATA_KEY_V1 ));
93112
94113 // Hardcode the key provider id to match what V1 does
95114 keyProviderInfo = "AES" ;
@@ -105,7 +124,7 @@ public ContentMetadata decodeMetadata(GetObjectResponse response) {
105124 }
106125
107126 // Extract encrypted data key ciphertext and provider id
108- edkCiphertext = _decoder .decode (metadata .get (MetadataKeyConstants .ENCRYPTED_DATA_KEY_V2 ));
127+ edkCiphertext = DECODER .decode (metadata .get (MetadataKeyConstants .ENCRYPTED_DATA_KEY_V2 ));
109128 keyProviderInfo = metadata .get (MetadataKeyConstants .ENCRYPTED_DATA_KEY_ALGORITHM );
110129
111130 break ;
@@ -136,7 +155,7 @@ public ContentMetadata decodeMetadata(GetObjectResponse response) {
136155 }
137156
138157 // Get content nonce
139- byte [] nonce = _decoder .decode (metadata .get (MetadataKeyConstants .CONTENT_NONCE ));
158+ byte [] nonce = DECODER .decode (metadata .get (MetadataKeyConstants .CONTENT_NONCE ));
140159
141160 return ContentMetadata .builder ()
142161 .algorithmSuite (algorithmSuite )
@@ -146,22 +165,18 @@ public ContentMetadata decodeMetadata(GetObjectResponse response) {
146165 .build ();
147166 }
148167
149- public static class Builder {
150- private Base64 .Encoder _encoder = Base64 .getEncoder ();
151- private Base64 .Decoder _decoder = Base64 .getDecoder ();
152-
153- public Builder base64Encoder (Base64 .Encoder encoder ) {
154- this ._encoder = encoder ;
155- return this ;
156- }
157-
158- public Builder base64Decoder (Base64 .Decoder decoder ) {
159- this ._decoder = decoder ;
160- return this ;
168+ public static ContentMetadata decode (S3Client client , GetObjectRequest request , GetObjectResponse response ) {
169+ Map <String , String > metadata = response .metadata ();
170+ ContentMetadataDecodingStrategy strategy ;
171+ if (metadata != null
172+ && metadata .containsKey (MetadataKeyConstants .CONTENT_NONCE )
173+ && (metadata .containsKey (MetadataKeyConstants .ENCRYPTED_DATA_KEY_V1 )
174+ || metadata .containsKey (MetadataKeyConstants .ENCRYPTED_DATA_KEY_V2 ))) {
175+ strategy = OBJECT_METADATA ;
176+ } else {
177+ strategy = INSTRUCTION_FILE ;
161178 }
162179
163- public S3ObjectMetadataStrategy build () {
164- return new S3ObjectMetadataStrategy (this );
165- }
180+ return strategy .decodeMetadata (client , request , response );
166181 }
167182}
0 commit comments