Skip to content

Commit 5e41470

Browse files
akareddy04Anirav Kareddy
andauthored
feat: Enforce Rotation Functionality for ReEncrypt-feature (#474)
Implemented a stronger level of validation for reEncryptInstructionFile where, when enabled, the client will attempt to decrypt the re-encrypted instruction file with the old key material and throw an exception when decryption succeeds. This is a stronger level of validation that the wrapping key has been rotated than the standard assertion that the materials descriptions are different. --------- Co-authored-by: Anirav Kareddy <[email protected]>
1 parent 675256f commit 5e41470

File tree

5 files changed

+1792
-7
lines changed

5 files changed

+1792
-7
lines changed

src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public static void simpleAesKeyringReEncryptInstructionFile(final String bucket)
123123
.bucket(bucket)
124124
.key(objectKey)
125125
.newKeyring(newKeyring)
126+
.enforceRotation(true)
126127
.build();
127128

128129
// Perform the re-encryption of the instruction file
@@ -239,6 +240,7 @@ public static void simpleRsaKeyringReEncryptInstructionFile(final String bucket)
239240
.bucket(bucket)
240241
.key(objectKey)
241242
.newKeyring(newKeyring)
243+
.enforceRotation(true)
242244
.build();
243245

244246
// Perform the re-encryption of the instruction file

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ public static Consumer<AwsRequestOverrideConfiguration.Builder> withAdditionalCo
207207
* Key rotation scenarios:
208208
* - Legacy to V3: Can rotate same wrapping key from legacy wrapping algorithms to fully supported wrapping algorithms
209209
* - Within V3: When rotating the wrapping key, the new keyring must be different from the current keyring
210+
* - Enforce Rotation: When enabled, ensures old keyring cannot decrypt data encrypted by new keyring
210211
*
211212
* @param reEncryptInstructionFileRequest the request containing bucket, object key, new keyring, and optional instruction file suffix
212213
* @return ReEncryptInstructionFileResponse containing the bucket, object key, and instruction file suffix used
@@ -257,6 +258,11 @@ public ReEncryptInstructionFileResponse reEncryptInstructionFile(ReEncryptInstru
257258
throw new S3EncryptionClientException("New keyring must have new materials description!");
258259
}
259260

261+
// If enforceRotation is set to true, ensure that the old keyring cannot decrypt the newly encrypted data key
262+
if (reEncryptInstructionFileRequest.enforceRotation()) {
263+
enforceRotation(encryptedMaterials, request);
264+
}
265+
260266
//Create or update instruction file with the re-encrypted metadata while preserving IV
261267
ContentMetadataEncodingStrategy encodeStrategy = new ContentMetadataEncodingStrategy(_instructionFileConfig);
262268
encodeStrategy.encodeMetadata(encryptedMaterials, iv, PutObjectRequest.builder()
@@ -265,8 +271,23 @@ public ReEncryptInstructionFileResponse reEncryptInstructionFile(ReEncryptInstru
265271
.build(), reEncryptInstructionFileRequest.instructionFileSuffix());
266272

267273
return new ReEncryptInstructionFileResponse(reEncryptInstructionFileRequest.bucket(),
268-
reEncryptInstructionFileRequest.key(), reEncryptInstructionFileRequest.instructionFileSuffix());
274+
reEncryptInstructionFileRequest.key(), reEncryptInstructionFileRequest.instructionFileSuffix(), reEncryptInstructionFileRequest.enforceRotation());
275+
276+
}
269277

278+
private void enforceRotation(EncryptionMaterials newEncryptionMaterials, GetObjectRequest request) {
279+
try {
280+
DecryptionMaterials decryptedMaterials = this._cryptoMaterialsManager.decryptMaterials(
281+
DecryptMaterialsRequest.builder()
282+
.algorithmSuite(newEncryptionMaterials.algorithmSuite())
283+
.encryptedDataKeys(newEncryptionMaterials.encryptedDataKeys())
284+
.s3Request(request)
285+
.build()
286+
);
287+
} catch (S3EncryptionClientException e) {
288+
return;
289+
}
290+
throw new S3EncryptionClientException("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key");
270291
}
271292

272293
/**

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ public class ReEncryptInstructionFileRequest {
1919
private final String key;
2020
private final RawKeyring newKeyring;
2121
private final String instructionFileSuffix;
22+
private final boolean enforceRotation;
2223

2324
private ReEncryptInstructionFileRequest(Builder builder) {
2425
bucket = builder.bucket;
2526
key = builder.key;
2627
newKeyring = builder.newKeyring;
2728
instructionFileSuffix = builder.instructionFileSuffix;
29+
enforceRotation = builder.enforceRotation;
2830
}
2931

3032
/**
@@ -55,6 +57,11 @@ public String instructionFileSuffix() {
5557
return instructionFileSuffix;
5658
}
5759

60+
/**
61+
* @return whether to enforce rotation for the re-encrypted instruction file
62+
*/
63+
public boolean enforceRotation() { return enforceRotation; }
64+
5865
/**
5966
* Creates a builder that can be used to configure and create a {@link ReEncryptInstructionFileRequest}
6067
*
@@ -72,6 +79,7 @@ public static class Builder {
7279
private String key;
7380
private RawKeyring newKeyring;
7481
private String instructionFileSuffix = DEFAULT_INSTRUCTION_FILE_SUFFIX;
82+
private boolean enforceRotation = false;
7583

7684
/**
7785
* Sets the S3 bucket name for the re-encryption of instruction file.
@@ -120,6 +128,20 @@ public Builder instructionFileSuffix(String instructionFileSuffix) {
120128
return this;
121129
}
122130

131+
/**
132+
* Sets whether to enforce rotation for the re-encrypted instruction file.
133+
* When enabled, the client will attempt to decrypt the re-encrypted instruction file with the old key material and
134+
* throw an exception when decryption succeeds. This is a stronger level of validation that the wrapping key has been
135+
* rotated than the standard assertion that the materials descriptions are different.
136+
*
137+
* @param enforceRotation whether to enforce rotation
138+
* @return a reference to this object so that method calls can be chained together.
139+
*/
140+
public Builder enforceRotation(boolean enforceRotation) {
141+
this.enforceRotation = enforceRotation;
142+
return this;
143+
}
144+
123145
/**
124146
* Validates and builds the ReEncryptInstructionFileRequest according
125147
* to the configuration options passed to the Builder object.

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,27 @@
44

55
/**
66
* Response object returned after re-encrypting an instruction file in S3.
7-
* Contains the S3 bucket name, object key, and instruction file suffix used for the re-encrypted instruction file
7+
* Contains the S3 bucket name, object key, instruction file suffix, and rotation enforcement status for the re-encrypted instruction file
88
*/
99
public class ReEncryptInstructionFileResponse {
1010
private final String bucket;
1111
private final String key;
1212
private final String instructionFileSuffix;
13+
private final boolean enforceRotation;
1314

1415
/**
1516
* Creates a new ReEncryptInstructionFileResponse object with the specified parameters.
1617
*
1718
* @param bucket the S3 bucket containing the re-encrypted instruction file
1819
* @param key the S3 object key of the encrypted object in S3
1920
* @param instructionFileSuffix the suffix used for the instruction file
21+
* @param enforceRotation whether rotation was enforced for the re-encrypted instruction file
2022
*/
21-
public ReEncryptInstructionFileResponse(String bucket, String key, String instructionFileSuffix) {
23+
public ReEncryptInstructionFileResponse(String bucket, String key, String instructionFileSuffix, boolean enforceRotation) {
2224
this.bucket = bucket;
2325
this.key = key;
2426
this.instructionFileSuffix = instructionFileSuffix.substring(1);
27+
this.enforceRotation = enforceRotation;
2528
}
2629

2730
/**
@@ -38,6 +41,13 @@ public String key() {
3841
return key;
3942
}
4043

44+
/**
45+
* @return whether rotation was enforced for the re-encrypted instruction file
46+
*/
47+
public boolean enforceRotation() {
48+
return enforceRotation;
49+
}
50+
4151
/**
4252
* @return the instruction file suffix used for the instruction file
4353
*/

0 commit comments

Comments
 (0)