Skip to content

Commit 898c55e

Browse files
committed
wip - instruction file puts
1 parent 8e51328 commit 898c55e

File tree

5 files changed

+114
-63
lines changed

5 files changed

+114
-63
lines changed
Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,74 @@
1-
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2-
// SPDX-License-Identifier: Apache-2.0
31
package software.amazon.encryption.s3.internal;
42

3+
import software.amazon.awssdk.protocols.jsoncore.JsonWriter;
54
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest;
65
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
6+
import software.amazon.encryption.s3.S3EncryptionClientException;
7+
import software.amazon.encryption.s3.materials.EncryptedDataKey;
78
import software.amazon.encryption.s3.materials.EncryptionMaterials;
89

9-
public interface ContentMetadataEncodingStrategy {
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.Base64;
12+
import java.util.HashMap;
13+
import java.util.Map;
1014

11-
PutObjectRequest encodeMetadata(EncryptionMaterials materials, byte[] iv, PutObjectRequest putObjectRequest);
12-
CreateMultipartUploadRequest encodeMetadata(EncryptionMaterials materials, byte[] iv, CreateMultipartUploadRequest createMultipartUploadRequest);
15+
public class ContentMetadataEncodingStrategy {
1316

17+
private static final Base64.Encoder ENCODER = Base64.getEncoder();
18+
private final InstructionFileConfig _instructionFileConfig;
19+
20+
public ContentMetadataEncodingStrategy(InstructionFileConfig instructionFileConfig) {
21+
_instructionFileConfig = instructionFileConfig;
22+
}
23+
24+
public PutObjectRequest encodeMetadata(EncryptionMaterials materials, byte[] iv, PutObjectRequest putObjectRequest) {
25+
if (_instructionFileConfig.isInstructionFilePutEnabled()) {
26+
// TODO: serialize inst file as string
27+
final String metadataString = metadataToString(materials, iv);
28+
_instructionFileConfig.putInstructionFile(putObjectRequest, "");
29+
// the original object is returned as-is
30+
return putObjectRequest;
31+
} else {
32+
Map<String, String> newMetadata = addMetadataToMap(putObjectRequest.metadata(), materials, iv);
33+
return putObjectRequest.toBuilder()
34+
.metadata(newMetadata)
35+
.build();
36+
}
37+
}
38+
39+
public CreateMultipartUploadRequest encodeMetadata(EncryptionMaterials materials, byte[] iv, CreateMultipartUploadRequest createMultipartUploadRequest) {
40+
Map<String, String> newMetadata = addMetadataToMap(createMultipartUploadRequest.metadata(), materials, iv);
41+
return createMultipartUploadRequest.toBuilder()
42+
.metadata(newMetadata)
43+
.build();
44+
}
45+
46+
private String metadataToString(EncryptionMaterials materials, byte[] iv) {
47+
// this is just the metadata map serialized as JSON
48+
return "";
49+
}
50+
51+
private Map<String, String> addMetadataToMap(Map<String, String> map, EncryptionMaterials materials, byte[] iv) {
52+
Map<String, String> metadata = new HashMap<>(map);
53+
EncryptedDataKey edk = materials.encryptedDataKeys().get(0);
54+
metadata.put(MetadataKeyConstants.ENCRYPTED_DATA_KEY_V2, ENCODER.encodeToString(edk.encryptedDatakey()));
55+
metadata.put(MetadataKeyConstants.CONTENT_IV, ENCODER.encodeToString(iv));
56+
metadata.put(MetadataKeyConstants.CONTENT_CIPHER, materials.algorithmSuite().cipherName());
57+
metadata.put(MetadataKeyConstants.CONTENT_CIPHER_TAG_LENGTH, Integer.toString(materials.algorithmSuite().cipherTagLengthBits()));
58+
metadata.put(MetadataKeyConstants.ENCRYPTED_DATA_KEY_ALGORITHM, new String(edk.keyProviderInfo(), StandardCharsets.UTF_8));
59+
60+
try (JsonWriter jsonWriter = JsonWriter.create()) {
61+
jsonWriter.writeStartObject();
62+
for (Map.Entry<String, String> entry : materials.encryptionContext().entrySet()) {
63+
jsonWriter.writeFieldName(entry.getKey()).writeValue(entry.getValue());
64+
}
65+
jsonWriter.writeEndObject();
66+
67+
String jsonEncryptionContext = new String(jsonWriter.getBytes(), StandardCharsets.UTF_8);
68+
metadata.put(MetadataKeyConstants.ENCRYPTED_DATA_KEY_CONTEXT, jsonEncryptionContext);
69+
} catch (JsonWriter.JsonGenerationException e) {
70+
throw new S3EncryptionClientException("Cannot serialize encryption context to JSON.", e);
71+
}
72+
return metadata;
73+
}
1474
}

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package software.amazon.encryption.s3.internal;
22

33
import software.amazon.awssdk.core.ResponseInputStream;
4+
import software.amazon.awssdk.core.async.AsyncRequestBody;
45
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
6+
import software.amazon.awssdk.core.sync.RequestBody;
57
import software.amazon.awssdk.services.s3.S3AsyncClient;
68
import software.amazon.awssdk.services.s3.S3Client;
79
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
810
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
11+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
12+
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
913
import software.amazon.encryption.s3.S3EncryptionClientException;
1014

1115
/**
@@ -22,6 +26,7 @@ private InstructionFileConfig(final Builder builder) {
2226
_clientType = builder._clientType;
2327
_s3Client = builder._s3Client;
2428
_s3AsyncClient = builder._s3AsyncClient;
29+
_enableInstructionFilePut = builder._enableInstructionFilePut;
2530
}
2631

2732
public static Builder builder() {
@@ -34,6 +39,30 @@ public enum InstructionFileClientType {
3439
ASYNC
3540
}
3641

42+
boolean isInstructionFilePutEnabled() {
43+
return _enableInstructionFilePut;
44+
}
45+
46+
PutObjectResponse putInstructionFile(PutObjectRequest request, String instructionFileContent) {
47+
// This shouldn't happen in practice because the metadata strategy will evaluate
48+
// if instruction file Puts are enabled before calling this method; check again anyway for robustness
49+
if (!_enableInstructionFilePut) {
50+
throw new S3EncryptionClientException("Enable Instruction File Put must be set to true in order to call PutObject with an instruction file!");
51+
}
52+
switch (_clientType) {
53+
case SYNCHRONOUS:
54+
return _s3Client.putObject(request, RequestBody.fromString(instructionFileContent));
55+
case ASYNC:
56+
return _s3AsyncClient.putObject(request, AsyncRequestBody.fromString(instructionFileContent)).join();
57+
case DISABLED:
58+
// this should never happen because we check enablePut first
59+
throw new S3EncryptionClientException("Instruction File has been disabled!");
60+
default:
61+
// this should never happen
62+
throw new S3EncryptionClientException("Unknown Instruction File Type");
63+
}
64+
}
65+
3766
ResponseInputStream<GetObjectResponse> getInstructionFile(GetObjectRequest request) {
3867
switch (_clientType) {
3968
case SYNCHRONOUS:
@@ -65,6 +94,7 @@ public static class Builder {
6594
private boolean _disableInstructionFile;
6695
private S3AsyncClient _s3AsyncClient;
6796
private S3Client _s3Client;
97+
private boolean _enableInstructionFilePut;
6898

6999
/**
70100
* When set to true, the S3 Encryption Client will not attempt to get instruction files.
@@ -76,6 +106,11 @@ public Builder disableInstructionFile(boolean disableInstructionFile) {
76106
return this;
77107
}
78108

109+
public Builder enableInstructionFilePutObject(boolean enableInstructionFilePutObject) {
110+
_enableInstructionFilePut = enableInstructionFilePutObject;
111+
return this;
112+
}
113+
79114
/**
80115
* Sets the S3 client to use to retrieve instruction files.
81116
* @param instructionFileClient
@@ -95,13 +130,17 @@ public Builder instructionFileAsyncClient(S3AsyncClient instructionFileAsyncClie
95130
_s3AsyncClient = instructionFileAsyncClient;
96131
return this;
97132
}
133+
98134
public InstructionFileConfig build() {
99135
if ((_s3AsyncClient != null || _s3Client != null) && _disableInstructionFile) {
100136
throw new S3EncryptionClientException("Instruction Files have been disabled but a client has been passed!");
101137
}
102138
if (_disableInstructionFile) {
103139
// We know both clients are null, so carry on.
104140
this._clientType = InstructionFileClientType.DISABLED;
141+
if (_enableInstructionFilePut) {
142+
throw new S3EncryptionClientException("Instruction Files must be enabled to enable Instruction Files for PutObject.");
143+
}
105144
return new InstructionFileConfig(this);
106145
}
107146
if (_s3Client != null && _s3AsyncClient != null) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public void putLocalObject(RequestBody requestBody, String uploadId, OutputStrea
216216
public static class Builder {
217217
private final Map<String, MultipartUploadMaterials> _multipartUploadMaterials =
218218
Collections.synchronizedMap(new HashMap<>());
219-
private final ContentMetadataEncodingStrategy _contentMetadataEncodingStrategy = new ObjectMetadataEncodingStrategy();
219+
private final ContentMetadataEncodingStrategy _contentMetadataEncodingStrategy = new ContentMetadataEncodingStrategy();
220220
private S3AsyncClient _s3AsyncClient;
221221
private CryptographicMaterialsManager _cryptoMaterialsManager;
222222
private SecureRandom _secureRandom;

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

Lines changed: 0 additions & 56 deletions
This file was deleted.

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ public static class Builder {
8181
private CryptographicMaterialsManager _cryptoMaterialsManager;
8282
private SecureRandom _secureRandom;
8383
private AsyncContentEncryptionStrategy _asyncContentEncryptionStrategy;
84-
private final ContentMetadataEncodingStrategy _contentMetadataEncodingStrategy = new ObjectMetadataEncodingStrategy();
84+
private InstructionFileConfig _instructionFileConfig;
85+
private ContentMetadataEncodingStrategy _contentMetadataEncodingStrategy;
8586

8687
private Builder() {
8788
}
@@ -106,6 +107,11 @@ public Builder secureRandom(SecureRandom secureRandom) {
106107
return this;
107108
}
108109

110+
public Builder instructionFileConfig(InstructionFileConfig instructionFileConfig) {
111+
this._instructionFileConfig = instructionFileConfig;
112+
return this;
113+
}
114+
109115
public PutEncryptedObjectPipeline build() {
110116
// Default to AesGcm since it is the only active (non-legacy) content encryption strategy
111117
if (_asyncContentEncryptionStrategy == null) {
@@ -114,6 +120,8 @@ public PutEncryptedObjectPipeline build() {
114120
.secureRandom(_secureRandom)
115121
.build();
116122
}
123+
_contentMetadataEncodingStrategy = new ContentMetadataEncodingStrategy(_instructionFileConfig);
124+
117125
return new PutEncryptedObjectPipeline(this);
118126
}
119127
}

0 commit comments

Comments
 (0)