Skip to content

Commit 49b8846

Browse files
authored
Merge pull request #10 from imabhichow/main
Consolidate Metadata Strategies
2 parents 59bb8b1 + e4ab6d4 commit 49b8846

File tree

5 files changed

+118
-85
lines changed

5 files changed

+118
-85
lines changed
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package software.amazon.encryption.s3.internal;
22

3-
import java.util.Map;
3+
import software.amazon.awssdk.services.s3.S3Client;
4+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
45
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
56

67
@FunctionalInterface
78
public interface ContentMetadataDecodingStrategy {
8-
ContentMetadata decodeMetadata(GetObjectResponse response);
9+
ContentMetadata decodeMetadata(S3Client client, GetObjectRequest request, GetObjectResponse response);
910
}

src/main/java/software/amazon/encryption/s3/internal/S3ObjectMetadataStrategy.java renamed to src/main/java/software/amazon/encryption/s3/internal/ContentMetadataStrategy.java

Lines changed: 75 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
import java.util.HashMap;
66
import java.util.Map;
77
import java.util.Map.Entry;
8+
import software.amazon.awssdk.core.ResponseInputStream;
89
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
910
import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
1011
import software.amazon.awssdk.protocols.jsoncore.JsonWriter;
1112
import software.amazon.awssdk.protocols.jsoncore.JsonWriter.JsonGenerationException;
13+
import software.amazon.awssdk.services.s3.S3Client;
14+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
1215
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
1316
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
1417
import software.amazon.encryption.s3.S3EncryptionClientException;
@@ -17,55 +20,71 @@
1720
import software.amazon.encryption.s3.materials.EncryptionMaterials;
1821
import 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
}

src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.IOException;
55
import java.util.Collections;
66
import java.util.List;
7+
78
import software.amazon.awssdk.core.ResponseInputStream;
89
import software.amazon.awssdk.core.sync.ResponseTransformer;
910
import software.amazon.awssdk.http.AbortableInputStream;
@@ -42,14 +43,8 @@ public <T> T getObject(GetObjectRequest getObjectRequest,
4243
throw new RuntimeException(e);
4344
}
4445

45-
GetObjectResponse response = objectStream.response();
46-
47-
// TODO: Need to differentiate metadata decoding strategy here
48-
ContentMetadataDecodingStrategy contentMetadataDecodingStrategy = S3ObjectMetadataStrategy
49-
.builder()
50-
.build();
51-
52-
ContentMetadata contentMetadata = contentMetadataDecodingStrategy.decodeMetadata(response);
46+
GetObjectResponse getObjectResponse = objectStream.response();
47+
ContentMetadata contentMetadata = ContentMetadataStrategy.decode(_s3Client, getObjectRequest, getObjectResponse);
5348

5449
AlgorithmSuite algorithmSuite = contentMetadata.algorithmSuite();
5550
List<EncryptedDataKey> encryptedDataKeys = Collections.singletonList(contentMetadata.encryptedDataKey());
@@ -74,7 +69,7 @@ public <T> T getObject(GetObjectRequest getObjectRequest,
7469
byte[] plaintext = contentDecryptionStrategy.decryptContent(contentMetadata, materials, ciphertext);
7570

7671
try {
77-
return responseTransformer.transform(response,
72+
return responseTransformer.transform(getObjectResponse,
7873
AbortableInputStream.create(new ByteArrayInputStream(plaintext)));
7974
} catch (Exception e) {
8075
throw new S3EncryptionClientException("Unable to transform response.", e);

src/main/java/software/amazon/encryption/s3/internal/PutEncryptedObjectPipeline.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,7 @@ public static class Builder {
5353
AesGcmContentStrategy
5454
.builder()
5555
.build();
56-
private ContentMetadataEncodingStrategy _contentMetadataEncodingStrategy =
57-
S3ObjectMetadataStrategy
58-
.builder()
59-
.build();
56+
private ContentMetadataEncodingStrategy _contentMetadataEncodingStrategy = ContentMetadataStrategy.OBJECT_METADATA;
6057

6158
private Builder() {}
6259

src/test/java/S3EncryptionClientTest.java

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,8 @@
66
import com.amazonaws.services.s3.AmazonS3EncryptionClient;
77
import com.amazonaws.services.s3.AmazonS3EncryptionClientV2;
88
import com.amazonaws.services.s3.AmazonS3EncryptionV2;
9-
import com.amazonaws.services.s3.model.CryptoConfiguration;
10-
import com.amazonaws.services.s3.model.CryptoConfigurationV2;
11-
import com.amazonaws.services.s3.model.CryptoMode;
12-
import com.amazonaws.services.s3.model.EncryptedPutObjectRequest;
13-
import com.amazonaws.services.s3.model.EncryptionMaterials;
14-
import com.amazonaws.services.s3.model.EncryptionMaterialsProvider;
15-
import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;
16-
import com.amazonaws.services.s3.model.ObjectMetadata;
17-
import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider;
9+
import com.amazonaws.services.s3.model.*;
10+
1811
import java.io.ByteArrayInputStream;
1912
import java.nio.charset.StandardCharsets;
2013
import java.security.KeyPair;
@@ -119,12 +112,44 @@ public void AesWrapV1toV3() {
119112

120113
@Test
121114
public void AesGcmV2toV3() {
122-
final String BUCKET_KEY = "aes-gcm-v3-to-v2";
115+
final String BUCKET_KEY = "aes-gcm-v2-to-v3";
116+
117+
// V2 Client
118+
EncryptionMaterialsProvider materialsProvider =
119+
new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY));
120+
AmazonS3EncryptionV2 v2Client = AmazonS3EncryptionClientV2.encryptionBuilder()
121+
.withEncryptionMaterialsProvider(materialsProvider)
122+
.build();
123+
124+
// V3 Client
125+
S3Client v3Client = S3EncryptionClient.builder()
126+
.aesKey(AES_KEY)
127+
.build();
128+
129+
// Asserts
130+
final String input = "AesGcmV2toV3";
131+
v2Client.putObject(BUCKET, BUCKET_KEY, input);
132+
133+
ResponseBytes<GetObjectResponse> objectResponse = v3Client.getObjectAsBytes(
134+
GetObjectRequest.builder()
135+
.bucket(BUCKET)
136+
.key(BUCKET_KEY).build());
137+
String output = objectResponse.asUtf8String();
138+
assertEquals(input, output);
139+
}
140+
141+
@Test
142+
public void AesGcmV2toV3WithInstructionFile() {
143+
final String BUCKET_KEY = "aes-gcm-v2-to-v3-with-instruction-file";
123144

124145
// V2 Client
125146
EncryptionMaterialsProvider materialsProvider =
126147
new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY));
148+
CryptoConfigurationV2 cryptoConfig =
149+
new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption)
150+
.withStorageMode(CryptoStorageMode.InstructionFile);
127151
AmazonS3EncryptionV2 v2Client = AmazonS3EncryptionClientV2.encryptionBuilder()
152+
.withCryptoConfiguration(cryptoConfig)
128153
.withEncryptionMaterialsProvider(materialsProvider)
129154
.build();
130155

0 commit comments

Comments
 (0)