Skip to content

Commit 95b3b5a

Browse files
committed
Merge branch 'main' of github.com:aws/amazon-s3-encryption-client-java into inst-file-put
2 parents fd64f1c + 647c809 commit 95b3b5a

File tree

8 files changed

+200
-19
lines changed

8 files changed

+200
-19
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [3.3.1](https://github.com/aws/aws-s3-encryption-client-java/compare/v3.3.0...v3.3.1) (2025-01-24)
4+
5+
### Fixes
6+
7+
* KMS Dependency is required ([44e9886](https://github.com/aws/aws-s3-encryption-client-java/commit/44e988677505bdc207936d0a981a0ca67dfd1a8a))
8+
* treat null matdesc as empty ([#448](https://github.com/aws/aws-s3-encryption-client-java/issues/448)) ([bcd711e](https://github.com/aws/aws-s3-encryption-client-java/commit/bcd711eb65707bac99775a011e3e159c6e3a79e6))
9+
* unbounded streams are not supported ([#422](https://github.com/aws/aws-s3-encryption-client-java/issues/422)) ([034bb89](https://github.com/aws/aws-s3-encryption-client-java/commit/034bb89ae5933b4d56e9892f19bb1e8f1f27010e))
10+
311
## [3.3.0](https://github.com/aws/aws-s3-encryption-client-java/compare/v3.2.3...v3.3.0) (2024-10-30)
412

513
### Features

pom.xml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>software.amazon.encryption.s3</groupId>
88
<artifactId>amazon-s3-encryption-client-java</artifactId>
9-
<version>3.3.0</version>
9+
<version>3.3.1</version>
1010
<packaging>jar</packaging>
1111

1212
<name>Amazon S3 Encryption Client</name>
@@ -56,7 +56,7 @@
5656
<dependency>
5757
<groupId>software.amazon.awssdk</groupId>
5858
<artifactId>bom</artifactId>
59-
<version>2.28.28</version>
59+
<version>2.30.38</version>
6060
<optional>true</optional>
6161
<type>pom</type>
6262
<scope>import</scope>
@@ -68,21 +68,21 @@
6868
<dependency>
6969
<groupId>software.amazon.awssdk</groupId>
7070
<artifactId>s3</artifactId>
71-
<version>2.28.28</version>
71+
<version>2.30.38</version>
7272
</dependency>
7373

7474
<dependency>
7575
<groupId>software.amazon.awssdk</groupId>
7676
<artifactId>kms</artifactId>
77-
<version>2.28.28</version>
77+
<version>2.30.38</version>
7878
</dependency>
7979

8080
<!-- Used when enableMultipartPutObject is configured -->
8181
<dependency>
8282
<groupId>software.amazon.awssdk.crt</groupId>
8383
<artifactId>aws-crt</artifactId>
8484
<optional>true</optional>
85-
<version>0.31.3</version>
85+
<version>0.36.3</version>
8686
</dependency>
8787

8888
<dependency>
@@ -163,7 +163,7 @@
163163
<dependency>
164164
<groupId>software.amazon.awssdk</groupId>
165165
<artifactId>sts</artifactId>
166-
<version>2.28.28</version>
166+
<version>2.30.38</version>
167167
<optional>true</optional>
168168
<scope>test</scope>
169169
</dependency>

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

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,65 @@
66

77
public class S3EncryptionClientException extends SdkClientException {
88

9+
private S3EncryptionClientException(Builder b) {
10+
super(b);
11+
}
12+
913
public S3EncryptionClientException(String message) {
10-
super(SdkClientException.builder()
14+
super(S3EncryptionClientException.builder()
1115
.message(message));
1216
}
1317

1418
public S3EncryptionClientException(String message, Throwable cause) {
15-
super(SdkClientException.builder()
19+
super(S3EncryptionClientException.builder()
1620
.message(message)
1721
.cause(cause));
1822
}
23+
24+
@Override
25+
public Builder toBuilder() {
26+
return new BuilderImpl(this);
27+
}
28+
29+
public static Builder builder() {
30+
return new BuilderImpl();
31+
}
32+
33+
public interface Builder extends SdkClientException.Builder {
34+
@Override
35+
Builder message(String message);
36+
37+
@Override
38+
Builder cause(Throwable cause);
39+
40+
@Override
41+
S3EncryptionClientException build();
42+
}
43+
44+
protected static final class BuilderImpl extends SdkClientException.BuilderImpl implements Builder {
45+
46+
protected BuilderImpl() {
47+
}
48+
49+
protected BuilderImpl(S3EncryptionClientException ex) {
50+
super(ex);
51+
}
52+
53+
@Override
54+
public Builder message(String message) {
55+
this.message = message;
56+
return this;
57+
}
58+
59+
@Override
60+
public Builder cause(Throwable cause) {
61+
this.cause = cause;
62+
return this;
63+
}
64+
65+
@Override
66+
public S3EncryptionClientException build() {
67+
return new S3EncryptionClientException(this);
68+
}
69+
}
1970
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import org.reactivestreams.Subscriber;
66
import software.amazon.awssdk.core.async.AsyncRequestBody;
7+
import software.amazon.encryption.s3.S3EncryptionClientException;
78
import software.amazon.encryption.s3.materials.CryptographicMaterials;
89

910
import java.nio.ByteBuffer;
@@ -35,7 +36,9 @@ public CipherAsyncRequestBody(final AsyncRequestBody wrappedAsyncRequestBody, fi
3536

3637
@Override
3738
public void subscribe(Subscriber<? super ByteBuffer> subscriber) {
38-
wrappedAsyncRequestBody.subscribe(new CipherSubscriber(subscriber, contentLength().orElse(-1L), materials, iv));
39+
wrappedAsyncRequestBody.subscribe(new CipherSubscriber(subscriber,
40+
contentLength().orElseThrow(() -> new S3EncryptionClientException("Unbounded streams are currently not supported.")),
41+
materials, iv));
3942
}
4043

4144
@Override

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ private ContentMetadata readFromMap(Map<String, String> metadata, GetObjectRespo
138138

139139
// Get encrypted data key encryption context
140140
final Map<String, String> encryptionContext = new HashMap<>();
141-
final String jsonEncryptionContext = metadata.get(MetadataKeyConstants.ENCRYPTED_DATA_KEY_CONTEXT);
141+
// The V2 client treats null value here as empty, do the same to avoid incompatibility
142+
String jsonEncryptionContext = metadata.getOrDefault(MetadataKeyConstants.ENCRYPTED_DATA_KEY_CONTEXT, "{}");
142143
// When the encryption context contains non-US-ASCII characters,
143144
// the S3 server applies an esoteric encoding to the object metadata.
144145
// Reverse that, to allow decryption.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public CompletableFuture<PutObjectResponse> putObject(PutObjectRequest request,
5151
contentLength = request.contentLength();
5252
}
5353
} else {
54-
contentLength = requestBody.contentLength().orElse(-1L);
54+
contentLength = requestBody.contentLength().orElseThrow(() -> new S3EncryptionClientException("Unbounded streams are currently not supported."));
5555
}
5656

5757
if (contentLength > AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherMaxContentLengthBytes()) {

src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import software.amazon.awssdk.services.s3.S3Client;
2626
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
2727
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
28+
import software.amazon.awssdk.services.s3.model.MetadataDirective;
2829
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
2930
import software.amazon.encryption.s3.internal.InstructionFileConfig;
3031

@@ -168,6 +169,7 @@ public void AesGcmV2toV3() {
168169
// Cleanup
169170
deleteObject(BUCKET, objectKey, v3Client);
170171
v3Client.close();
172+
171173
}
172174

173175
@Test
@@ -905,4 +907,62 @@ public void AesWrapV1toV3FailsWhenLegacyModeDisabled() {
905907
deleteObject(BUCKET, objectKey, v3Client);
906908
v3Client.close();
907909
}
910+
911+
@Test
912+
public void nullMaterialDescriptionV3() {
913+
final String objectKey = appendTestSuffix("null-matdesc-v3");
914+
915+
// V2 Client
916+
EncryptionMaterialsProvider materialsProvider =
917+
new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY));
918+
AmazonS3EncryptionV2 v2Client = AmazonS3EncryptionClientV2.encryptionBuilder()
919+
.withEncryptionMaterialsProvider(materialsProvider)
920+
.build();
921+
922+
// V3 Client
923+
S3Client v3Client = S3EncryptionClient.builder()
924+
.aesKey(AES_KEY)
925+
.build();
926+
927+
// Asserts
928+
final String input = "AesGcmWithNullMatDesc";
929+
v2Client.putObject(BUCKET, objectKey, input);
930+
931+
ResponseBytes<GetObjectResponse> objectResponse = v3Client.getObjectAsBytes(builder -> builder
932+
.bucket(BUCKET)
933+
.key(objectKey));
934+
String output = objectResponse.asUtf8String();
935+
assertEquals(input, output);
936+
937+
// Now remove MatDesc - this must be done via CopyObject
938+
final String copyKey = objectKey + "copied";
939+
Map<String, String> modMd = new HashMap<>(objectResponse.response().metadata());
940+
modMd.remove("x-amz-meta-x-amz-matdesc");
941+
modMd.remove("x-amz-matdesc");
942+
v3Client.copyObject(builder -> builder
943+
.sourceBucket(BUCKET)
944+
.destinationBucket(BUCKET)
945+
.sourceKey(objectKey)
946+
.destinationKey(copyKey)
947+
.metadataDirective(MetadataDirective.REPLACE)
948+
.metadata(modMd)
949+
.build());
950+
951+
// V2
952+
String v2CopyOut = v2Client.getObjectAsString(BUCKET, copyKey);
953+
assertEquals(input, v2CopyOut);
954+
955+
// V3
956+
ResponseBytes<GetObjectResponse> objectResponseCopy = v3Client.getObjectAsBytes(builder -> builder
957+
.bucket(BUCKET)
958+
.key(copyKey));
959+
String outputCopy = objectResponseCopy.asUtf8String();
960+
assertEquals(input, outputCopy);
961+
962+
// Cleanup
963+
deleteObject(BUCKET, objectKey, v3Client);
964+
deleteObject(BUCKET, copyKey, v3Client);
965+
v3Client.close();
966+
967+
}
908968
}

src/test/java/software/amazon/encryption/s3/S3EncryptionClientStreamTest.java

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717
import software.amazon.awssdk.core.ResponseInputStream;
1818
import software.amazon.awssdk.core.async.AsyncRequestBody;
1919
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
20+
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
2021
import software.amazon.awssdk.core.sync.RequestBody;
2122
import software.amazon.awssdk.services.s3.S3AsyncClient;
2223
import software.amazon.awssdk.services.s3.S3Client;
2324
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
2425
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
2526
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
2627
import software.amazon.awssdk.utils.IoUtils;
27-
import software.amazon.encryption.s3.utils.BoundedStreamBufferer;
2828
import software.amazon.encryption.s3.utils.BoundedInputStream;
29+
import software.amazon.encryption.s3.utils.BoundedStreamBufferer;
2930
import software.amazon.encryption.s3.utils.MarkResetBoundedZerosInputStream;
3031
import software.amazon.encryption.s3.utils.S3EncryptionClientTestResources;
3132

@@ -135,6 +136,63 @@ public void ordinaryInputStreamV3Encrypt() throws IOException {
135136
v3Client.close();
136137
}
137138

139+
@Test
140+
public void ordinaryInputStreamV3UnboundedAsync() {
141+
try (S3AsyncClient s3AsyncEncryptionClient = S3AsyncEncryptionClient.builder().aesKey(AES_KEY).build()) {
142+
final String objectKey = appendTestSuffix("ordinaryInputStreamV3UnboundedAsync");
143+
BlockingInputStreamAsyncRequestBody body =
144+
AsyncRequestBody.forBlockingInputStream(null);
145+
try {
146+
s3AsyncEncryptionClient.putObject(r -> r.bucket(BUCKET).key(objectKey), body);
147+
fail("Expected exception!");
148+
} catch (S3EncryptionClientException exception) {
149+
// expected
150+
assertTrue(exception.getMessage().contains("Unbounded streams are currently not supported"));
151+
}
152+
}
153+
}
154+
155+
@Test
156+
public void ordinaryInputStreamV3UnboundedMultipartAsync() {
157+
try (S3AsyncClient s3AsyncEncryptionClient = S3AsyncEncryptionClient.builder()
158+
.aesKey(AES_KEY)
159+
.enableMultipartPutObject(true)
160+
.build()) {
161+
final String objectKey = appendTestSuffix("ordinaryInputStreamV3UnboundedAsync");
162+
BlockingInputStreamAsyncRequestBody body =
163+
AsyncRequestBody.forBlockingInputStream(null);
164+
try {
165+
s3AsyncEncryptionClient.putObject(r -> r.bucket(BUCKET).key(objectKey), body);
166+
fail("Expected exception!");
167+
} catch (S3EncryptionClientException exception) {
168+
// expected
169+
assertTrue(exception.getMessage().contains("Unbounded streams are currently not supported"));
170+
}
171+
}
172+
}
173+
174+
@Test
175+
public void ordinaryInputStreamV3UnboundedCrt() {
176+
try (S3AsyncClient s3CrtAsyncClient = S3AsyncClient.crtCreate()) {
177+
try (S3AsyncClient s3AsyncEncryptionClient = S3AsyncEncryptionClient.builder()
178+
.aesKey(AES_KEY)
179+
.enableMultipartPutObject(true)
180+
.wrappedClient(s3CrtAsyncClient)
181+
.build()) {
182+
final String objectKey = appendTestSuffix("ordinaryInputStreamV3UnboundedCrt");
183+
BlockingInputStreamAsyncRequestBody body =
184+
AsyncRequestBody.forBlockingInputStream(null);
185+
try {
186+
s3AsyncEncryptionClient.putObject(r -> r.bucket(BUCKET).key(objectKey), body);
187+
fail("Expected exception!");
188+
} catch (S3EncryptionClientException exception) {
189+
// expected
190+
assertTrue(exception.getMessage().contains("Unbounded streams are currently not supported"));
191+
}
192+
}
193+
}
194+
}
195+
138196
@Test
139197
public void ordinaryInputStreamV3Decrypt() throws IOException {
140198
final String objectKey = appendTestSuffix("ordinaryInputStreamV3Decrypt");
@@ -274,9 +332,9 @@ public void customSetBufferSizeWithLargeObject() throws IOException {
274332
final long fileSizeExceedingDefaultLimit = 1024 * 1024 * 32 + 1;
275333
final InputStream largeObjectStream = new BoundedInputStream(fileSizeExceedingDefaultLimit);
276334
v3ClientWithBuffer32MiB.putObject(PutObjectRequest.builder()
277-
.bucket(BUCKET)
278-
.key(objectKey)
279-
.build(), RequestBody.fromInputStream(largeObjectStream, fileSizeExceedingDefaultLimit));
335+
.bucket(BUCKET)
336+
.key(objectKey)
337+
.build(), RequestBody.fromInputStream(largeObjectStream, fileSizeExceedingDefaultLimit));
280338

281339
largeObjectStream.close();
282340

@@ -327,9 +385,9 @@ public void customSetBufferSizeWithLargeObjectAsyncClient() throws IOException {
327385
final InputStream largeObjectStream = new BoundedInputStream(fileSizeExceedingDefaultLimit);
328386
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
329387
CompletableFuture<PutObjectResponse> futurePut = v3ClientWithBuffer32MiB.putObject(PutObjectRequest.builder()
330-
.bucket(BUCKET)
331-
.key(objectKey)
332-
.build(), AsyncRequestBody.fromInputStream(largeObjectStream, fileSizeExceedingDefaultLimit, singleThreadExecutor));
388+
.bucket(BUCKET)
389+
.key(objectKey)
390+
.build(), AsyncRequestBody.fromInputStream(largeObjectStream, fileSizeExceedingDefaultLimit, singleThreadExecutor));
333391

334392
futurePut.join();
335393
largeObjectStream.close();
@@ -387,7 +445,7 @@ public void delayedAuthModeWithLargeObject() throws IOException {
387445
assertThrows(S3EncryptionClientException.class, () -> v3Client.getObjectAsBytes(builder -> builder
388446
.bucket(BUCKET)
389447
.key(objectKey)));
390-
448+
391449
S3Client v3ClientWithDelayedAuth = S3EncryptionClient.builder()
392450
.aesKey(AES_KEY)
393451
.enableDelayedAuthenticationMode(true)

0 commit comments

Comments
 (0)