|
9 | 9 | import com.amazonaws.services.s3.model.EncryptionMaterialsProvider; |
10 | 10 | import com.amazonaws.services.s3.model.KMSEncryptionMaterials; |
11 | 11 | import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider; |
| 12 | +import org.apache.commons.io.IOUtils; |
12 | 13 | import org.junit.jupiter.api.Test; |
13 | 14 | import software.amazon.awssdk.core.ResponseBytes; |
| 15 | +import software.amazon.awssdk.core.ResponseInputStream; |
14 | 16 | import software.amazon.awssdk.core.sync.RequestBody; |
| 17 | +import software.amazon.awssdk.regions.Region; |
15 | 18 | import software.amazon.awssdk.services.s3.S3Client; |
| 19 | +import software.amazon.awssdk.services.s3.model.CompletedPart; |
| 20 | +import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse; |
16 | 21 | import software.amazon.awssdk.services.s3.model.GetObjectResponse; |
17 | 22 | import software.amazon.awssdk.services.s3.model.NoSuchKeyException; |
| 23 | +import software.amazon.awssdk.services.s3.model.SdkPartType; |
| 24 | +import software.amazon.awssdk.services.s3.model.UploadPartRequest; |
| 25 | +import software.amazon.awssdk.services.s3.model.UploadPartResponse; |
18 | 26 | import software.amazon.encryption.s3.internal.InstructionFileConfig; |
| 27 | +import software.amazon.encryption.s3.utils.BoundedInputStream; |
19 | 28 |
|
20 | 29 | import javax.crypto.KeyGenerator; |
21 | 30 | import javax.crypto.SecretKey; |
22 | 31 |
|
| 32 | +import java.io.ByteArrayInputStream; |
| 33 | +import java.io.ByteArrayOutputStream; |
| 34 | +import java.io.IOException; |
| 35 | +import java.io.InputStream; |
23 | 36 | import java.security.KeyPair; |
24 | 37 | import java.security.KeyPairGenerator; |
25 | 38 | import java.security.NoSuchAlgorithmException; |
| 39 | +import java.util.ArrayList; |
| 40 | +import java.util.HashMap; |
| 41 | +import java.util.List; |
| 42 | +import java.util.Map; |
26 | 43 |
|
27 | 44 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 45 | +import static org.junit.jupiter.api.Assertions.assertNotNull; |
28 | 46 | import static org.junit.jupiter.api.Assertions.assertTrue; |
29 | 47 | import static org.junit.jupiter.api.Assertions.fail; |
| 48 | +import static software.amazon.encryption.s3.S3EncryptionClient.withAdditionalConfiguration; |
30 | 49 | import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; |
31 | 50 | import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ID; |
| 51 | +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.S3_REGION; |
32 | 52 | import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; |
33 | 53 | import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; |
34 | 54 |
|
@@ -290,4 +310,149 @@ public void testPutWithInstructionFileV3ToV2Rsa() throws NoSuchAlgorithmExceptio |
290 | 310 | deleteObject(BUCKET, objectKey, s3Client); |
291 | 311 | s3Client.close(); |
292 | 312 | } |
| 313 | + |
| 314 | + @Test |
| 315 | + public void testMultipartPutWithInstructionFile() throws IOException, NoSuchAlgorithmException { |
| 316 | + final String object_key = appendTestSuffix("test-multipart-put-instruction-file"); |
| 317 | + |
| 318 | + final long fileSizeLimit = 1024 * 1024 * 50; //50 MB |
| 319 | + final InputStream inputStream = new BoundedInputStream(fileSizeLimit); |
| 320 | + final InputStream objectStreamForResult = new BoundedInputStream(fileSizeLimit); |
| 321 | + |
| 322 | + S3Client wrappedClient = S3Client.create(); |
| 323 | + S3Client s3Client = S3EncryptionClient.builder() |
| 324 | + .instructionFileConfig(InstructionFileConfig.builder() |
| 325 | + .instructionFileClient(wrappedClient) |
| 326 | + .enableInstructionFilePutObject(true) |
| 327 | + .build()) |
| 328 | + .kmsKeyId(KMS_KEY_ID) |
| 329 | + .build(); |
| 330 | + |
| 331 | + Map<String, String> encryptionContext = new HashMap<>(); |
| 332 | + encryptionContext.put("test-key", "test-value"); |
| 333 | + |
| 334 | + |
| 335 | + s3Client.putObject(builder -> builder |
| 336 | + .bucket(BUCKET) |
| 337 | + .overrideConfiguration(withAdditionalConfiguration(encryptionContext)) |
| 338 | + .key(object_key), RequestBody.fromInputStream(inputStream, fileSizeLimit)); |
| 339 | + |
| 340 | + S3Client defaultClient = S3Client.create(); |
| 341 | + ResponseBytes<GetObjectResponse> directInstGetResponse = defaultClient.getObjectAsBytes(builder -> builder |
| 342 | + .bucket(BUCKET) |
| 343 | + .key(object_key + ".instruction") |
| 344 | + .build()); |
| 345 | + assertTrue(directInstGetResponse.response().metadata().containsKey("x-amz-crypto-instr-file")); |
| 346 | + |
| 347 | + ResponseInputStream<GetObjectResponse> getResponse = s3Client.getObject(builder -> builder |
| 348 | + .bucket(BUCKET) |
| 349 | + .overrideConfiguration(withAdditionalConfiguration(encryptionContext)) |
| 350 | + .key(object_key)); |
| 351 | + |
| 352 | + assertTrue(IOUtils.contentEquals(objectStreamForResult, getResponse)); |
| 353 | + |
| 354 | + deleteObject(BUCKET, object_key, s3Client); |
| 355 | + s3Client.close(); |
| 356 | + |
| 357 | + } |
| 358 | + |
| 359 | + @Test |
| 360 | + public void testLowLevelMultipartPutWithInstructionFile() throws NoSuchAlgorithmException, IOException { |
| 361 | + final String object_key = appendTestSuffix("test-low-level-multipart-put-instruction-file"); |
| 362 | + |
| 363 | + final long fileSizeLimit = 1024 * 1024 * 50; |
| 364 | + final int PART_SIZE = 10 * 1024 * 1024; |
| 365 | + final InputStream inputStream = new BoundedInputStream(fileSizeLimit); |
| 366 | + final InputStream objectStreamForResult = new BoundedInputStream(fileSizeLimit); |
| 367 | + |
| 368 | + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); |
| 369 | + keyPairGen.initialize(2048); |
| 370 | + KeyPair rsaKey = keyPairGen.generateKeyPair(); |
| 371 | + |
| 372 | + S3Client wrappedClient = S3Client.create(); |
| 373 | + |
| 374 | + S3Client v3Client = S3EncryptionClient.builder() |
| 375 | + .rsaKeyPair(rsaKey) |
| 376 | + .instructionFileConfig(InstructionFileConfig.builder() |
| 377 | + .instructionFileClient(wrappedClient) |
| 378 | + .enableInstructionFilePutObject(true) |
| 379 | + .build()) |
| 380 | + .enableDelayedAuthenticationMode(true) |
| 381 | + .build(); |
| 382 | + |
| 383 | + |
| 384 | + CreateMultipartUploadResponse initiateResult = v3Client.createMultipartUpload(builder -> |
| 385 | + builder.bucket(BUCKET).key(object_key)); |
| 386 | + |
| 387 | + List<CompletedPart> partETags = new ArrayList<>(); |
| 388 | + |
| 389 | + int bytesRead, bytesSent = 0; |
| 390 | + byte[] partData = new byte[PART_SIZE]; |
| 391 | + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| 392 | + int partsSent = 1; |
| 393 | + while ((bytesRead = inputStream.read(partData, 0, partData.length)) != -1) { |
| 394 | + outputStream.write(partData, 0, bytesRead); |
| 395 | + if (bytesSent < PART_SIZE) { |
| 396 | + bytesSent += bytesRead; |
| 397 | + continue; |
| 398 | + } |
| 399 | + UploadPartRequest uploadPartRequest = UploadPartRequest.builder() |
| 400 | + .bucket(BUCKET) |
| 401 | + .key(object_key) |
| 402 | + .uploadId(initiateResult.uploadId()) |
| 403 | + .partNumber(partsSent) |
| 404 | + .build(); |
| 405 | + |
| 406 | + final InputStream partInputStream = new ByteArrayInputStream(outputStream.toByteArray()); |
| 407 | + |
| 408 | + UploadPartResponse uploadPartResult = v3Client.uploadPart(uploadPartRequest, |
| 409 | + RequestBody.fromInputStream(partInputStream, partInputStream.available())); |
| 410 | + |
| 411 | + partETags.add(CompletedPart.builder() |
| 412 | + .partNumber(partsSent) |
| 413 | + .eTag(uploadPartResult.eTag()) |
| 414 | + .build()); |
| 415 | + outputStream.reset(); |
| 416 | + bytesSent = 0; |
| 417 | + partsSent++; |
| 418 | + } |
| 419 | + inputStream.close(); |
| 420 | + UploadPartRequest uploadPartRequest = UploadPartRequest.builder() |
| 421 | + .bucket(BUCKET) |
| 422 | + .key(object_key) |
| 423 | + .uploadId(initiateResult.uploadId()) |
| 424 | + .partNumber(partsSent) |
| 425 | + .sdkPartType(SdkPartType.LAST) |
| 426 | + .build(); |
| 427 | + final InputStream partInputStream = new ByteArrayInputStream(outputStream.toByteArray()); |
| 428 | + UploadPartResponse uploadPartResult = v3Client.uploadPart(uploadPartRequest, |
| 429 | + RequestBody.fromInputStream(partInputStream, partInputStream.available())); |
| 430 | + partETags.add(CompletedPart.builder() |
| 431 | + .partNumber(partsSent) |
| 432 | + .eTag(uploadPartResult.eTag()) |
| 433 | + .build()); |
| 434 | + v3Client.completeMultipartUpload(builder -> builder |
| 435 | + .bucket(BUCKET) |
| 436 | + .key(object_key) |
| 437 | + .uploadId(initiateResult.uploadId()) |
| 438 | + .multipartUpload(partBuilder -> partBuilder.parts(partETags))); |
| 439 | + |
| 440 | + S3Client defaultClient = S3Client.create(); |
| 441 | + ResponseBytes<GetObjectResponse> directInstGetResponse = defaultClient.getObjectAsBytes(builder -> builder |
| 442 | + .bucket(BUCKET) |
| 443 | + .key(object_key + ".instruction") |
| 444 | + .build()); |
| 445 | + assertTrue(directInstGetResponse.response().metadata().containsKey("x-amz-crypto-instr-file")); |
| 446 | + |
| 447 | + ResponseInputStream<GetObjectResponse> getResponse = v3Client.getObject(builder -> builder |
| 448 | + .bucket(BUCKET) |
| 449 | + .key(object_key)); |
| 450 | + |
| 451 | + assertTrue(IOUtils.contentEquals(objectStreamForResult, getResponse)); |
| 452 | + |
| 453 | + deleteObject(BUCKET, object_key, v3Client); |
| 454 | + v3Client.close(); |
| 455 | + } |
| 456 | + |
293 | 457 | } |
| 458 | + |
0 commit comments