Skip to content

Commit 6194b64

Browse files
author
Anirav Kareddy
committed
wrote 2 comprehensive test cases regarding re-encryption of instruction files with AES + RSA keyrings and both of these tests pass
1 parent 3ffc4b2 commit 6194b64

File tree

1 file changed

+289
-0
lines changed

1 file changed

+289
-0
lines changed
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
package software.amazon.encryption.s3;
2+
3+
import org.junit.jupiter.api.BeforeAll;
4+
import org.junit.jupiter.api.Test;
5+
import software.amazon.awssdk.core.Response;
6+
import software.amazon.awssdk.core.ResponseBytes;
7+
import software.amazon.awssdk.core.sync.RequestBody;
8+
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
9+
import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
10+
import software.amazon.awssdk.services.s3.S3Client;
11+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
12+
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
13+
import software.amazon.encryption.s3.internal.InstructionFileConfig;
14+
import software.amazon.encryption.s3.internal.ReEncryptInstructionFileRequest;
15+
import software.amazon.encryption.s3.internal.ReEncryptInstructionFileResponse;
16+
import software.amazon.encryption.s3.materials.AesKeyring;
17+
import software.amazon.encryption.s3.materials.MaterialsDescription;
18+
import software.amazon.encryption.s3.materials.PartialRsaKeyPair;
19+
import software.amazon.encryption.s3.materials.RsaKeyring;
20+
21+
import javax.crypto.KeyGenerator;
22+
import javax.crypto.SecretKey;
23+
import java.security.KeyPair;
24+
import java.security.KeyPairGenerator;
25+
import java.security.NoSuchAlgorithmException;
26+
import java.security.PrivateKey;
27+
import java.security.PublicKey;
28+
import java.security.SecureRandom;
29+
30+
import static org.junit.jupiter.api.Assertions.assertEquals;
31+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
32+
import static org.junit.jupiter.api.Assertions.assertTrue;
33+
import static software.amazon.encryption.s3.S3EncryptionClient.withCustomInstructionFileSuffix;
34+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET;
35+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix;
36+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject;
37+
38+
public class S3EncryptionClientReEncryptInstructionFileTest {
39+
private static SecretKey AES_KEY;
40+
private static SecretKey AES_KEY_TWO;
41+
private static KeyPair RSA_KEY_PAIR;
42+
private static KeyPair THIRD_PARTY_RSA_KEY_PAIR;
43+
44+
@BeforeAll
45+
public static void setUp() throws NoSuchAlgorithmException {
46+
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
47+
keyGen.init(256);
48+
AES_KEY = keyGen.generateKey();
49+
AES_KEY_TWO = keyGen.generateKey();
50+
51+
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
52+
keyPairGen.initialize(2048);
53+
RSA_KEY_PAIR = keyPairGen.generateKeyPair();
54+
THIRD_PARTY_RSA_KEY_PAIR = keyPairGen.generateKeyPair();
55+
}
56+
57+
@Test
58+
public void testAesKeyringReEncryptInstructionFile() {
59+
AesKeyring oldKeyring = AesKeyring.builder()
60+
.wrappingKey(AES_KEY)
61+
.secureRandom(new SecureRandom())
62+
.materialsDescription(MaterialsDescription.builder()
63+
.put("rotated", "no")
64+
.build())
65+
.build();
66+
67+
S3Client wrappedClient = S3Client.create();
68+
S3EncryptionClient client = S3EncryptionClient.builder()
69+
.keyring(oldKeyring)
70+
.instructionFileConfig(InstructionFileConfig.builder()
71+
.instructionFileClient(wrappedClient)
72+
.enableInstructionFilePutObject(true)
73+
.build())
74+
.build();
75+
76+
final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-test");
77+
final String input = "Testing re-encryption of instruction file with AES Keyring";
78+
79+
client.putObject(builder -> builder
80+
.bucket(BUCKET)
81+
.key(objectKey)
82+
.build(), RequestBody.fromString(input));
83+
84+
ResponseBytes<GetObjectResponse> instructionFile = wrappedClient.getObjectAsBytes(builder -> builder
85+
.bucket(BUCKET)
86+
.key(objectKey + ".instruction")
87+
.build());
88+
89+
String instructionFileContent = instructionFile.asUtf8String();
90+
JsonNodeParser parser = JsonNodeParser.create();
91+
JsonNode instructionFileNode = parser.parse(instructionFileContent);
92+
93+
String originalIv = instructionFileNode.asObject().get("x-amz-iv").asString();
94+
String originalEncryptedDataKeyAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString();
95+
String originalEncryptedDataKey = instructionFileNode.asObject().get("x-amz-key-v2").asString();
96+
JsonNode originalMatDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString());
97+
assertEquals("no", originalMatDescNode.asObject().get("rotated").asString());
98+
99+
AesKeyring newKeyring = AesKeyring.builder()
100+
.wrappingKey(AES_KEY_TWO)
101+
.secureRandom(new SecureRandom())
102+
.materialsDescription(MaterialsDescription.builder()
103+
.put("rotated", "yes")
104+
.build())
105+
.build();
106+
107+
ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder()
108+
.bucket(BUCKET)
109+
.key(objectKey)
110+
.newKeyring(newKeyring)
111+
.build();
112+
113+
ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest);
114+
S3Client rotatedWrappedClient = S3Client.create();
115+
116+
S3EncryptionClient rotatedClient = S3EncryptionClient.builder()
117+
.keyring(newKeyring)
118+
.instructionFileConfig(InstructionFileConfig.builder()
119+
.instructionFileClient(rotatedWrappedClient)
120+
.enableInstructionFilePutObject(true)
121+
.build())
122+
.build();
123+
124+
try {
125+
client.getObjectAsBytes(GetObjectRequest.builder()
126+
.bucket(BUCKET)
127+
.key(objectKey)
128+
.build());
129+
throw new RuntimeException("Expected exception");
130+
} catch (S3EncryptionClientException e) {
131+
assertTrue(e.getMessage().contains("Unable to AES/GCM unwrap"));
132+
}
133+
ResponseBytes<GetObjectResponse> getResponse = rotatedClient.getObjectAsBytes(builder -> builder
134+
.bucket(BUCKET)
135+
.key(objectKey)
136+
.build());
137+
assertEquals(input, getResponse.asUtf8String());
138+
139+
ResponseBytes<GetObjectResponse> reEncryptedInstructionFile = rotatedWrappedClient.getObjectAsBytes(builder -> builder
140+
.bucket(BUCKET)
141+
.key(objectKey + ".instruction")
142+
.build());
143+
144+
String newInstructionFileContent = reEncryptedInstructionFile.asUtf8String();
145+
JsonNode newInstructionFileNode = parser.parse(newInstructionFileContent);
146+
147+
String postReEncryptionIv = newInstructionFileNode.asObject().get("x-amz-iv").asString();
148+
String postReEncryptionEncryptedDataKeyAlgorithm = newInstructionFileNode.asObject().get("x-amz-wrap-alg").asString();
149+
String postReEncryptionEncryptedDataKey = newInstructionFileNode.asObject().get("x-amz-key-v2").asString();
150+
JsonNode postReEncryptionMatDescNode = parser.parse(newInstructionFileNode.asObject().get("x-amz-matdesc").asString());
151+
152+
assertEquals("yes", postReEncryptionMatDescNode.asObject().get("rotated").asString());
153+
assertEquals(originalIv, postReEncryptionIv);
154+
assertEquals(originalEncryptedDataKeyAlgorithm, postReEncryptionEncryptedDataKeyAlgorithm);
155+
assertNotEquals(originalEncryptedDataKey, postReEncryptionEncryptedDataKey);
156+
157+
assertEquals(BUCKET, response.Bucket());
158+
assertEquals(objectKey, response.Key());
159+
assertEquals(".instruction", response.InstructionFileSuffix());
160+
161+
deleteObject(BUCKET, objectKey, client);
162+
}
163+
164+
@Test
165+
public void testRsaKeyringReEncryptInstructionFile() {
166+
PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic();
167+
PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate();
168+
169+
PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder()
170+
.publicKey(clientPublicKey)
171+
.privateKey(clientPrivateKey)
172+
.build();
173+
174+
RsaKeyring clientKeyring = RsaKeyring.builder()
175+
.wrappingKeyPair(clientPartialRsaKeyPair)
176+
.secureRandom(new SecureRandom())
177+
.materialsDescription(MaterialsDescription.builder()
178+
.put("isOwner", "yes")
179+
.put("access-level", "admin")
180+
.build())
181+
.build();
182+
183+
S3Client wrappedClient = S3Client.create();
184+
S3EncryptionClient client = S3EncryptionClient.builder()
185+
.keyring(clientKeyring)
186+
.instructionFileConfig(InstructionFileConfig.builder()
187+
.instructionFileClient(wrappedClient)
188+
.enableInstructionFilePutObject(true)
189+
.build())
190+
.build();
191+
192+
final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-test");
193+
final String input = "Testing re-encryption of instruction file with RSA Keyring";
194+
195+
client.putObject(builder -> builder
196+
.bucket(BUCKET)
197+
.key(objectKey)
198+
.build(), RequestBody.fromString(input));
199+
200+
PublicKey thirdPartyPublicKey = THIRD_PARTY_RSA_KEY_PAIR.getPublic();
201+
PrivateKey thirdPartyPrivateKey = THIRD_PARTY_RSA_KEY_PAIR.getPrivate();
202+
203+
PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder()
204+
.publicKey(thirdPartyPublicKey)
205+
.privateKey(thirdPartyPrivateKey)
206+
.build();
207+
208+
RsaKeyring thirdPartyKeyring = RsaKeyring.builder()
209+
.wrappingKeyPair(thirdPartyPartialRsaKeyPair)
210+
.secureRandom(new SecureRandom())
211+
.materialsDescription(MaterialsDescription.builder()
212+
.put("isOwner", "no")
213+
.put("access-level", "user")
214+
.build())
215+
.build();
216+
217+
ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder()
218+
.bucket(BUCKET)
219+
.key(objectKey)
220+
.instructionFileSuffix("third-party-access-instruction-file")
221+
.newKeyring(thirdPartyKeyring)
222+
.build();
223+
224+
S3EncryptionClient thirdPartyClient = S3EncryptionClient.builder()
225+
.keyring(thirdPartyKeyring)
226+
.secureRandom(new SecureRandom())
227+
.instructionFileConfig(InstructionFileConfig.builder()
228+
.instructionFileClient(wrappedClient)
229+
.enableInstructionFilePutObject(true)
230+
.build())
231+
.build();
232+
233+
ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = client.reEncryptInstructionFile(reEncryptInstructionFileRequest);
234+
235+
ResponseBytes<GetObjectResponse> clientInstructionFile= wrappedClient.getObjectAsBytes(builder -> builder
236+
.bucket(BUCKET)
237+
.key(objectKey + ".instruction")
238+
.build());
239+
240+
JsonNodeParser parser = JsonNodeParser.create();
241+
242+
String clientInstructionFileContent = clientInstructionFile.asUtf8String();
243+
244+
JsonNode clientInstructionFileNode = parser.parse(clientInstructionFileContent);
245+
String clientIv = clientInstructionFileNode.asObject().get("x-amz-iv").asString();
246+
String clientEncryptedDataKeyAlgorithm = clientInstructionFileNode.asObject().get("x-amz-wrap-alg").asString();
247+
String clientEncryptedDataKey = clientInstructionFileNode.asObject().get("x-amz-key-v2").asString();
248+
JsonNode clientMatDescNode = parser.parse(clientInstructionFileNode.asObject().get("x-amz-matdesc").asString());
249+
250+
assertEquals("yes", clientMatDescNode.asObject().get("isOwner").asString());
251+
assertEquals("admin", clientMatDescNode.asObject().get("access-level").asString());
252+
253+
ResponseBytes<GetObjectResponse> thirdPartyInstFile = wrappedClient.getObjectAsBytes(builder -> builder
254+
.bucket(BUCKET)
255+
.key(objectKey + ".third-party-access-instruction-file")
256+
.build());
257+
258+
String thirdPartyInstructionFileContent = thirdPartyInstFile.asUtf8String();
259+
JsonNode thirdPartyInstructionFileNode = parser.parse(thirdPartyInstructionFileContent);
260+
String thirdPartyIv = thirdPartyInstructionFileNode.asObject().get("x-amz-iv").asString();
261+
String thirdPartyEncryptedDataKeyAlgorithm = thirdPartyInstructionFileNode.asObject().get("x-amz-wrap-alg").asString();
262+
String thirdPartyEncryptedDataKey = thirdPartyInstructionFileNode.asObject().get("x-amz-key-v2").asString();
263+
JsonNode thirdPartyMatDescNode = parser.parse(thirdPartyInstructionFileNode.asObject().get("x-amz-matdesc").asString());
264+
assertEquals("no", thirdPartyMatDescNode.asObject().get("isOwner").asString());
265+
assertEquals("user", thirdPartyMatDescNode.asObject().get("access-level").asString());
266+
267+
assertEquals(clientIv, thirdPartyIv);
268+
assertEquals(clientEncryptedDataKeyAlgorithm, thirdPartyEncryptedDataKeyAlgorithm);
269+
assertNotEquals(clientEncryptedDataKey, thirdPartyEncryptedDataKey);
270+
271+
ResponseBytes<GetObjectResponse> thirdPartyDecryptedObject = thirdPartyClient.getObjectAsBytes(builder -> builder
272+
.bucket(BUCKET)
273+
.key(objectKey)
274+
.overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file"))
275+
.build());
276+
assertEquals(input, thirdPartyDecryptedObject.asUtf8String());
277+
278+
ResponseBytes<GetObjectResponse> clientDecryptedObject = client.getObjectAsBytes(builder -> builder
279+
.bucket(BUCKET)
280+
.key(objectKey)
281+
.build());
282+
assertEquals(input, clientDecryptedObject.asUtf8String());
283+
284+
assertEquals(BUCKET, reEncryptInstructionFileResponse.Bucket());
285+
assertEquals(objectKey, reEncryptInstructionFileResponse.Key());
286+
assertEquals(".third-party-access-instruction-file", reEncryptInstructionFileResponse.InstructionFileSuffix());
287+
288+
}
289+
}

0 commit comments

Comments
 (0)