Skip to content

Commit 3ac4609

Browse files
authored
use async implementation for default api (#73)
* use async implementation for default api (putObject and uploadPart)
1 parent 9a96e71 commit 3ac4609

File tree

6 files changed

+37
-76
lines changed

6 files changed

+37
-76
lines changed

src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
55
import software.amazon.awssdk.awscore.exception.AwsServiceException;
6+
import software.amazon.awssdk.core.async.AsyncRequestBody;
67
import software.amazon.awssdk.core.exception.SdkClientException;
78
import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
89
import software.amazon.awssdk.core.sync.RequestBody;
910
import software.amazon.awssdk.core.sync.ResponseTransformer;
11+
import software.amazon.awssdk.services.s3.S3AsyncClient;
1012
import software.amazon.awssdk.services.s3.S3Client;
1113
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest;
1214
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadResponse;
@@ -48,8 +50,10 @@
4850
import java.util.ArrayList;
4951
import java.util.List;
5052
import java.util.Map;
53+
import java.util.concurrent.CompletableFuture;
5154
import java.util.concurrent.ExecutionException;
5255
import java.util.concurrent.ExecutorService;
56+
import java.util.concurrent.Executors;
5357
import java.util.concurrent.Future;
5458
import java.util.function.Consumer;
5559

@@ -126,13 +130,14 @@ public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBod
126130
throw new S3EncryptionClientException("Exception while performing Multipart Upload PutObject", e);
127131
}
128132
}
129-
130133
PutEncryptedObjectPipeline pipeline = PutEncryptedObjectPipeline.builder()
131-
.s3Client(_wrappedClient)
134+
.s3AsyncClient(S3AsyncClient.create())
132135
.cryptoMaterialsManager(_cryptoMaterialsManager)
133136
.secureRandom(_secureRandom)
134137
.build();
135-
return pipeline.putObject(putObjectRequest, requestBody);
138+
139+
CompletableFuture<PutObjectResponse> futurePut = pipeline.putObject(putObjectRequest, AsyncRequestBody.fromInputStream(requestBody.contentStreamProvider().newStream(), requestBody.optionalContentLength().orElse(-1L), Executors.newSingleThreadExecutor()));
140+
return futurePut.join();
136141
}
137142

138143
@Override

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ public void onNext(ByteBuffer byteBuffer) {
3636
if (amountToReadFromByteBuffer > 0) {
3737
byte[] buf = BinaryUtils.copyBytesFrom(byteBuffer, amountToReadFromByteBuffer);
3838
outputBuffer = cipher.update(buf, 0, amountToReadFromByteBuffer);
39+
if (outputBuffer == null && amountToReadFromByteBuffer < cipher.getBlockSize()) {
40+
// The underlying data is too short to fill in the block cipher
41+
// This is true at the end of the file, so complete to get the final
42+
// bytes
43+
this.onComplete();
44+
}
3945
wrappedSubscriber.onNext(ByteBuffer.wrap(outputBuffer));
4046
} else {
4147
// Do nothing

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

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

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44
import software.amazon.awssdk.awscore.exception.AwsServiceException;
5+
import software.amazon.awssdk.core.async.AsyncRequestBody;
56
import software.amazon.awssdk.core.exception.SdkClientException;
67
import software.amazon.awssdk.core.sync.RequestBody;
8+
import software.amazon.awssdk.services.s3.S3AsyncClient;
79
import software.amazon.awssdk.services.s3.S3Client;
810
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest;
911
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadResponse;
@@ -28,10 +30,12 @@
2830
import java.util.Collections;
2931
import java.util.HashMap;
3032
import java.util.Map;
33+
import java.util.concurrent.Executors;
3134

3235
public class MultipartUploadObjectPipeline {
3336

3437
final private S3Client _s3Client;
38+
final private S3AsyncClient _s3AsyncClient;
3539
final private CryptographicMaterialsManager _cryptoMaterialsManager;
3640
final private MultipartContentEncryptionStrategy _contentEncryptionStrategy;
3741
final private ContentMetadataEncodingStrategy _contentMetadataEncodingStrategy;
@@ -42,6 +46,7 @@ public class MultipartUploadObjectPipeline {
4246

4347
private MultipartUploadObjectPipeline(Builder builder) {
4448
this._s3Client = builder._s3Client;
49+
this._s3AsyncClient = S3AsyncClient.create(); // TODO plumbing
4550
this._cryptoMaterialsManager = builder._cryptoMaterialsManager;
4651
this._contentEncryptionStrategy = builder._contentEncryptionStrategy;
4752
this._contentMetadataEncodingStrategy = builder._contentMetadataEncodingStrategy;
@@ -76,38 +81,37 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ
7681
throws AwsServiceException, SdkClientException {
7782
final AlgorithmSuite algorithmSuite = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF;
7883
final int blockSize = algorithmSuite.cipherBlockSizeBytes();
79-
final String uploadId = request.uploadId();
8084
final long partSize = requestBody.optionalContentLength().orElse(-1L);
8185
final int cipherTagLength = isLastPart ? algorithmSuite.cipherTagLengthBytes() : 0;
86+
final long ciphertextLength = partSize + cipherTagLength;
8287
final boolean partSizeMultipleOfCipherBlockSize = 0 == (partSize % blockSize);
88+
8389
if (!isLastPart && !partSizeMultipleOfCipherBlockSize) {
84-
throw new S3EncryptionClientException(
85-
"Invalid part size: part sizes for encrypted multipart uploads must be multiples "
86-
+ "of the cipher block size ("
87-
+ blockSize
88-
+ ") with the exception of the last part.");
90+
throw new S3EncryptionClientException("Invalid part size: part sizes for encrypted multipart uploads must " +
91+
"be multiples of the cipher block size (" + blockSize + ") with the exception of the last part.");
8992
}
93+
94+
final String uploadId = request.uploadId();
9095
final MultipartUploadContext uploadContext = _multipartUploadContexts.get(uploadId);
9196
if (uploadContext == null) {
92-
throw new S3EncryptionClientException(
93-
"No client-side information available on upload ID " + uploadId);
97+
throw new S3EncryptionClientException("No client-side information available on upload ID " + uploadId);
9498
}
9599
final UploadPartResponse response;
96100
// Checks the parts are uploaded in series
97101
uploadContext.beginPartUpload(request.partNumber());
98102
Cipher cipher = uploadContext.getCipher();
99103
try {
100-
final InputStream cipherInputStream = new AuthenticatedCipherInputStream(requestBody.contentStreamProvider().newStream(), cipher, true, isLastPart);
104+
final AsyncRequestBody cipherAsyncRequestBody = new CipherAsyncRequestBody(cipher, AsyncRequestBody.fromInputStream(requestBody.contentStreamProvider().newStream(), request.contentLength(), Executors.newSingleThreadExecutor()), ciphertextLength);
101105
// The last part of the multipart upload will contain an extra
102106
// 16-byte mac
103107
if (isLastPart) {
104108
if (uploadContext.hasFinalPartBeenSeen()) {
105-
throw new S3EncryptionClientException(
106-
"This part was specified as the last part in a multipart upload, but a previous part was already marked as the last part. "
107-
+ "Only the last part of the upload should be marked as the last part.");
109+
throw new S3EncryptionClientException("This part was specified as the last part in a multipart " +
110+
"upload, but a previous part was already marked as the last part. Only the last part of the " +
111+
"upload should be marked as the last part.");
108112
}
109113
}
110-
response = _s3Client.uploadPart(request, RequestBody.fromInputStream(cipherInputStream, partSize + cipherTagLength));
114+
response = _s3AsyncClient.uploadPart(request, cipherAsyncRequestBody).join();
111115
} finally {
112116
uploadContext.endPartUpload();
113117
}

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

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44
import software.amazon.awssdk.core.async.AsyncRequestBody;
5-
import software.amazon.awssdk.core.sync.RequestBody;
65
import software.amazon.awssdk.services.s3.S3AsyncClient;
7-
import software.amazon.awssdk.services.s3.S3Client;
86
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
97
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
108
import software.amazon.encryption.s3.materials.CryptographicMaterialsManager;
@@ -18,22 +16,18 @@
1816

1917
public class PutEncryptedObjectPipeline {
2018

21-
final private S3Client _s3Client;
2219
final private S3AsyncClient _s3AsyncClient;
2320
final private CryptographicMaterialsManager _cryptoMaterialsManager;
2421
final private AsyncContentEncryptionStrategy _asyncContentEncryptionStrategy;
25-
final private ContentEncryptionStrategy _contentEncryptionStrategy;
2622
final private ContentMetadataEncodingStrategy _contentMetadataEncodingStrategy;
2723

2824
public static Builder builder() {
2925
return new Builder();
3026
}
3127

3228
private PutEncryptedObjectPipeline(Builder builder) {
33-
this._s3Client = builder._s3Client;
3429
this._s3AsyncClient = builder._s3AsyncClient;
3530
this._cryptoMaterialsManager = builder._cryptoMaterialsManager;
36-
this._contentEncryptionStrategy = builder._contentEncryptionStrategy;
3731
this._asyncContentEncryptionStrategy = builder._asyncContentEncryptionStrategy;
3832
this._contentMetadataEncodingStrategy = builder._contentMetadataEncodingStrategy;
3933
}
@@ -53,52 +47,23 @@ public CompletableFuture<PutObjectResponse> putObject(PutObjectRequest request,
5347
return _s3AsyncClient.putObject(encryptedPutRequest, encryptedContent.getAsyncCiphertext());
5448
}
5549

56-
public PutObjectResponse putObject(PutObjectRequest request, RequestBody requestBody) {
57-
EncryptionMaterialsRequest.Builder requestBuilder = EncryptionMaterialsRequest.builder()
58-
.s3Request(request)
59-
.plaintextLength(requestBody.optionalContentLength().orElse(-1L));
60-
61-
EncryptionMaterials materials = _cryptoMaterialsManager.getEncryptionMaterials(requestBuilder.build());
62-
63-
EncryptedContent encryptedContent = _contentEncryptionStrategy.encryptContent(materials, requestBody.contentStreamProvider().newStream());
64-
65-
Map<String, String> metadata = new HashMap<>(request.metadata());
66-
metadata = _contentMetadataEncodingStrategy.encodeMetadata(materials, encryptedContent.getNonce(), metadata);
67-
request = request.toBuilder().metadata(metadata).build();
68-
69-
return _s3Client.putObject(request, RequestBody.fromInputStream(encryptedContent.getCiphertext(), encryptedContent.getCiphertextLength()));
70-
}
71-
7250
public static class Builder {
73-
private S3Client _s3Client;
7451
private S3AsyncClient _s3AsyncClient;
7552
private CryptographicMaterialsManager _cryptoMaterialsManager;
7653
private SecureRandom _secureRandom;
7754
private AsyncContentEncryptionStrategy _asyncContentEncryptionStrategy;
78-
private ContentEncryptionStrategy _contentEncryptionStrategy;
7955
private final ContentMetadataEncodingStrategy _contentMetadataEncodingStrategy = ContentMetadataStrategy.OBJECT_METADATA;
8056

8157

8258
private Builder() {
8359
}
8460

85-
/**
86-
* Note that this does NOT create a defensive clone of S3Client. Any modifications made to the wrapped
87-
* S3Client will be reflected in this Builder.
88-
*/
89-
@SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Pass mutability into wrapping client")
90-
public Builder s3Client(S3Client s3Client) {
91-
this._s3Client = s3Client;
92-
return this;
93-
}
94-
9561
/**
9662
* Note that this does NOT create a defensive clone of S3Client. Any modifications made to the wrapped
9763
* S3Client will be reflected in this Builder.
9864
*/
9965
@SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Pass mutability into wrapping client")
10066
public Builder s3AsyncClient(S3AsyncClient s3AsyncClient) {
101-
// TODO: This needs similar "onlyOneOrNull" logic
10267
this._s3AsyncClient = s3AsyncClient;
10368
return this;
10469
}
@@ -115,12 +80,6 @@ public Builder secureRandom(SecureRandom secureRandom) {
11580

11681
public PutEncryptedObjectPipeline build() {
11782
// Default to AesGcm since it is the only active (non-legacy) content encryption strategy
118-
if (_contentEncryptionStrategy == null) {
119-
_contentEncryptionStrategy = StreamingAesGcmContentStrategy
120-
.builder()
121-
.secureRandom(_secureRandom)
122-
.build();
123-
}
12483
if (_asyncContentEncryptionStrategy == null) {
12584
_asyncContentEncryptionStrategy = StreamingAesGcmContentStrategy
12685
.builder()

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import java.security.GeneralSecurityException;
1616
import java.security.SecureRandom;
1717

18-
public class StreamingAesGcmContentStrategy implements ContentEncryptionStrategy, ContentDecryptionStrategy, AsyncContentEncryptionStrategy, MultipartContentEncryptionStrategy {
18+
public class StreamingAesGcmContentStrategy implements ContentDecryptionStrategy, AsyncContentEncryptionStrategy, MultipartContentEncryptionStrategy {
1919

2020
final private SecureRandom _secureRandom;
2121

@@ -27,14 +27,6 @@ public static Builder builder() {
2727
return new Builder();
2828
}
2929

30-
@Override
31-
public EncryptedContent encryptContent(EncryptionMaterials materials, InputStream content) {
32-
final byte[] nonce = new byte[materials.algorithmSuite().nonceLengthBytes()];
33-
final Cipher cipher = prepareCipher(materials, nonce);
34-
final InputStream ciphertext = new AuthenticatedCipherInputStream(content, cipher);
35-
return new EncryptedContent(nonce, ciphertext, materials.getCiphertextLength());
36-
}
37-
3830
@Override
3931
public EncryptedContent initMultipartEncryption(EncryptionMaterials materials) {
4032
final byte[] nonce = new byte[materials.algorithmSuite().nonceLengthBytes()];
@@ -45,6 +37,11 @@ public EncryptedContent initMultipartEncryption(EncryptionMaterials materials) {
4537

4638
@Override
4739
public EncryptedContent encryptContent(EncryptionMaterials materials, AsyncRequestBody content) {
40+
if (materials.getPlaintextLength() > AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherMaxContentLengthBytes()) {
41+
throw new S3EncryptionClientException("The contentLength of the object you are attempting to encrypt exceeds" +
42+
"the maximum length allowed for GCM encryption.");
43+
}
44+
4845
final byte[] nonce = new byte[materials.algorithmSuite().nonceLengthBytes()];
4946
final Cipher cipher = prepareCipher(materials, nonce);
5047

0 commit comments

Comments
 (0)