Skip to content

Commit 3bbb9fb

Browse files
author
Anirav Kareddy
committed
Added examples of re-encryption of instruction files via RSA + AES keyrings
1 parent 5b7b3ae commit 3bbb9fb

File tree

1 file changed

+300
-0
lines changed

1 file changed

+300
-0
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.encryption.s3.examples;
4+
5+
import software.amazon.awssdk.core.ResponseBytes;
6+
import software.amazon.awssdk.core.sync.RequestBody;
7+
import software.amazon.awssdk.services.s3.S3Client;
8+
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
9+
import software.amazon.encryption.s3.S3EncryptionClient;
10+
import software.amazon.encryption.s3.S3EncryptionClientException;
11+
import software.amazon.encryption.s3.internal.InstructionFileConfig;
12+
import software.amazon.encryption.s3.internal.ReEncryptInstructionFileRequest;
13+
import software.amazon.encryption.s3.internal.ReEncryptInstructionFileResponse;
14+
import software.amazon.encryption.s3.materials.AesKeyring;
15+
import software.amazon.encryption.s3.materials.MaterialsDescription;
16+
import software.amazon.encryption.s3.materials.PartialRsaKeyPair;
17+
import software.amazon.encryption.s3.materials.RsaKeyring;
18+
19+
import javax.crypto.KeyGenerator;
20+
import javax.crypto.SecretKey;
21+
import java.security.KeyPair;
22+
import java.security.KeyPairGenerator;
23+
import java.security.NoSuchAlgorithmException;
24+
import java.security.PrivateKey;
25+
import java.security.PublicKey;
26+
import java.security.SecureRandom;
27+
28+
import static org.junit.jupiter.api.Assertions.assertEquals;
29+
import static org.junit.jupiter.api.Assertions.assertTrue;
30+
import static software.amazon.encryption.s3.S3EncryptionClient.withCustomInstructionFileSuffix;
31+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix;
32+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject;
33+
34+
public class ReEncryptionInstructionFileExample {
35+
36+
/**
37+
* Generates a 256-bit AES key for encryption/decryption operations.
38+
*
39+
* @return A SecretKey instance for AES operations
40+
* @throws NoSuchAlgorithmException if AES algorithm is not available
41+
*/
42+
private static SecretKey generateAesKey() throws NoSuchAlgorithmException {
43+
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
44+
keyGen.init(256);
45+
return keyGen.generateKey();
46+
}
47+
48+
/**
49+
* Generates a 2048-bit RSA key pair for encryption/decryption operations.
50+
*
51+
* @return A KeyPair instance for RSA operations
52+
* @throws NoSuchAlgorithmException if RSA algorithm is not available
53+
*/
54+
private static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException {
55+
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
56+
keyPairGen.initialize(2048);
57+
return keyPairGen.generateKeyPair();
58+
}
59+
60+
public static void main(final String[] args) throws NoSuchAlgorithmException {
61+
final String bucket = args[0];
62+
simpleAesKeyringReEncryptInstructionFile(bucket);
63+
simpleRsaKeyringReEncryptInstructionFile(bucket);
64+
}
65+
66+
/**
67+
* This example demonstrates re-encrypting the encrypted data key in an instruction file with a new AES wrapping key.
68+
* The other cryptographic parameters in the instruction file such as the IV and wrapping algorithm remain unchanged.
69+
*
70+
* @param bucket The name of the Amazon S3 bucket to perform operations on.
71+
* @throws NoSuchAlgorithmException if AES algorithm is not available
72+
*/
73+
public static void simpleAesKeyringReEncryptInstructionFile(final String bucket) throws NoSuchAlgorithmException {
74+
// Set up the S3 object key and content to be encrypted
75+
final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-test");
76+
final String input = "Testing re-encryption of instruction file with AES Keyring";
77+
78+
// Generate the original AES key for initial encryption
79+
SecretKey originalAesKey = generateAesKey();
80+
81+
// Create the original AES keyring with materials description
82+
AesKeyring oldKeyring = AesKeyring.builder()
83+
.wrappingKey(originalAesKey)
84+
.secureRandom(new SecureRandom())
85+
.materialsDescription(MaterialsDescription.builder()
86+
.put("version", "1.0")
87+
.put("rotated", "no")
88+
.build())
89+
.build();
90+
91+
// Create a default S3 client for instruction file operations
92+
S3Client wrappedClient = S3Client.create();
93+
94+
// Create the S3 Encryption Client with instruction file support enabled
95+
// The client can perform both putObject and getObject operations using the original AES key
96+
S3EncryptionClient originalClient = S3EncryptionClient.builder()
97+
.keyring(oldKeyring)
98+
.instructionFileConfig(InstructionFileConfig.builder()
99+
.instructionFileClient(wrappedClient)
100+
.enableInstructionFilePutObject(true)
101+
.build())
102+
.build();
103+
104+
// Upload both the encrypted object and instruction file to the specified bucket in S3
105+
originalClient.putObject(builder -> builder
106+
.bucket(bucket)
107+
.key(objectKey)
108+
.build(), RequestBody.fromString(input));
109+
110+
// Generate a new AES key for re-encryption (rotating wrapping key)
111+
SecretKey newAesKey = generateAesKey();
112+
113+
// Create a new keyring with the new AES key and updated materials description
114+
AesKeyring newKeyring = AesKeyring.builder()
115+
.wrappingKey(newAesKey)
116+
.secureRandom(new SecureRandom())
117+
.materialsDescription(MaterialsDescription.builder()
118+
.put("version", "2.0")
119+
.put("rotated", "yes")
120+
.build())
121+
.build();
122+
123+
// Create the re-encryption of instruction file request to re-encrypt the encrypted data key with the new wrapping key
124+
// This updates the instruction file without touching the encrypted object
125+
ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder()
126+
.bucket(bucket)
127+
.key(objectKey)
128+
.newKeyring(newKeyring)
129+
.build();
130+
131+
// Perform the re-encryption of the instruction file
132+
ReEncryptInstructionFileResponse response = originalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest);
133+
134+
// Verify that the original client can no longer decrypt the object
135+
// This proves that the instruction file has been successfully re-encrypted
136+
try {
137+
originalClient.getObjectAsBytes(builder -> builder
138+
.bucket(bucket)
139+
.key(objectKey)
140+
.build());
141+
throw new RuntimeException("Original client should not be able to decrypt the object in S3 post re-encryption of instruction file!");
142+
} catch (S3EncryptionClientException e) {
143+
assertTrue(e.getMessage().contains("Unable to AES/GCM unwrap"));
144+
}
145+
146+
// Create a new client with the rotated AES key
147+
S3EncryptionClient newClient = S3EncryptionClient.builder()
148+
.keyring(newKeyring)
149+
.instructionFileConfig(InstructionFileConfig.builder()
150+
.instructionFileClient(wrappedClient)
151+
.enableInstructionFilePutObject(true)
152+
.build())
153+
.build();
154+
155+
// Verify that the new client can successfully decrypt the object
156+
// This proves that the instruction file has been successfully re-encrypted
157+
ResponseBytes<GetObjectResponse> decryptedObject = newClient.getObjectAsBytes(builder -> builder
158+
.bucket(bucket)
159+
.key(objectKey)
160+
.build());
161+
162+
// Assert that the decrypted object's content matches the original input
163+
assertEquals(input, decryptedObject.asUtf8String());
164+
165+
// Call deleteObject to delete the object from given S3 Bucket
166+
deleteObject(bucket, objectKey, originalClient);
167+
}
168+
169+
/**
170+
* This example demonstrates generating a custom instruction file to enable access to encrypted object by a third party.
171+
* This enables secure sharing of encrypted objects without sharing private keys.
172+
*
173+
* @param bucket The name of the Amazon S3 bucket to perform operations on.
174+
* @throws NoSuchAlgorithmException if RSA algorithm is not available
175+
*/
176+
public static void simpleRsaKeyringReEncryptInstructionFile(final String bucket) throws NoSuchAlgorithmException {
177+
// Set up the S3 object key and content to be encrypted
178+
final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-test");
179+
final String input = "Testing re-encryption of instruction file with RSA Keyring";
180+
181+
// Generate RSA key pair for the original client
182+
KeyPair clientRsaKeyPair = generateRsaKeyPair();
183+
PublicKey clientPublicKey = clientRsaKeyPair.getPublic();
184+
PrivateKey clientPrivateKey = clientRsaKeyPair.getPrivate();
185+
186+
// Create a partial RSA key pair for the client's keyring
187+
PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder()
188+
.publicKey(clientPublicKey)
189+
.privateKey(clientPrivateKey)
190+
.build();
191+
192+
// Create the client's RSA keyring with materials description
193+
RsaKeyring clientKeyring = RsaKeyring.builder()
194+
.wrappingKeyPair(clientPartialRsaKeyPair)
195+
.secureRandom(new SecureRandom())
196+
.materialsDescription(MaterialsDescription.builder()
197+
.put("isOwner", "yes")
198+
.put("access-level", "admin")
199+
.build())
200+
.build();
201+
202+
// Create a default S3 client for instruction file operations
203+
S3Client wrappedClient = S3Client.create();
204+
205+
// Create the S3 Encryption Client with instruction file support enabled
206+
// The client can perform both putObject and getObject operations using RSA keyring
207+
S3EncryptionClient client = S3EncryptionClient.builder()
208+
.keyring(clientKeyring)
209+
.instructionFileConfig(InstructionFileConfig.builder()
210+
.instructionFileClient(wrappedClient)
211+
.enableInstructionFilePutObject(true)
212+
.build())
213+
.build();
214+
215+
// Upload both the encrypted object and instruction file to the specified bucket in S3
216+
client.putObject(builder -> builder
217+
.bucket(bucket)
218+
.key(objectKey)
219+
.build(), RequestBody.fromString(input));
220+
221+
// Generate a new RSA key pair for the third party customer
222+
KeyPair thirdPartyKeyPair = generateRsaKeyPair();
223+
PublicKey thirdPartyPublicKey = thirdPartyKeyPair.getPublic();
224+
PrivateKey thirdPartyPrivateKey = thirdPartyKeyPair.getPrivate();
225+
226+
// Create a partial RSA key pair for the third party's keyring
227+
PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder()
228+
.publicKey(thirdPartyPublicKey)
229+
.privateKey(thirdPartyPrivateKey)
230+
.build();
231+
232+
// Create the third party's RSA keyring with updated materials description
233+
RsaKeyring thirdPartyKeyring = RsaKeyring.builder()
234+
.wrappingKeyPair(thirdPartyPartialRsaKeyPair)
235+
.secureRandom(new SecureRandom())
236+
.materialsDescription(MaterialsDescription.builder()
237+
.put("isOwner", "no")
238+
.put("access-level", "user")
239+
.build())
240+
.build();
241+
242+
// Create the re-encryption request that will generate a new instruction file specifically for third party access
243+
// This new instruction file will use a custom suffix and contain the data key encrypted with the third party's public key
244+
ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder()
245+
.bucket(bucket)
246+
.key(objectKey)
247+
.instructionFileSuffix("third-party-access-instruction-file") // Custom instruction file suffix for third party
248+
.newKeyring(thirdPartyKeyring)
249+
.build();
250+
251+
// Perform the re-encryption operation to create the new instruction file
252+
// This creates a new instruction file without modifying the original encrypted object
253+
ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = client.reEncryptInstructionFile(reEncryptInstructionFileRequest);
254+
255+
// Create the third party's S3 Encryption Client
256+
S3EncryptionClient thirdPartyClient = S3EncryptionClient.builder()
257+
.keyring(thirdPartyKeyring)
258+
.secureRandom(new SecureRandom())
259+
.instructionFileConfig(InstructionFileConfig.builder()
260+
.instructionFileClient(wrappedClient)
261+
.enableInstructionFilePutObject(true)
262+
.build())
263+
.build();
264+
265+
// Verify that the original client can still decrypt the object in the specified bucket in S3 using the default instruction file
266+
ResponseBytes<GetObjectResponse> clientDecryptedObject = client.getObjectAsBytes(builder -> builder
267+
.bucket(bucket)
268+
.key(objectKey)
269+
.build());
270+
271+
// Assert that the decrypted object's content matches the original input
272+
assertEquals(input, clientDecryptedObject.asUtf8String());
273+
274+
// Verify that the third party cannot decrypt the object in the specified bucket in S3 using the default instruction file
275+
try {
276+
ResponseBytes<GetObjectResponse> thirdPartyDecryptObject = thirdPartyClient.getObjectAsBytes(builder -> builder
277+
.bucket(bucket)
278+
.key(objectKey)
279+
.build());
280+
throw new RuntimeException("Third party client should not be able to decrypt the object in S3 using the default instruction file!");
281+
} catch (S3EncryptionClientException e) {
282+
assertTrue(e.getMessage().contains("Unable to RSA-OAEP-SHA1 unwrap"));
283+
}
284+
285+
// Verify that the third party can decrypt the object in the specified bucket in S3 using their custom instruction file
286+
// This demonstrates successful secure sharing of encrypted data
287+
ResponseBytes<GetObjectResponse> thirdPartyDecryptedObject = thirdPartyClient.getObjectAsBytes(builder -> builder
288+
.bucket(bucket)
289+
.key(objectKey)
290+
.overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file"))
291+
.build());
292+
293+
// Assert that the decrypted object's content matches the original input
294+
assertEquals(input, thirdPartyDecryptedObject.asUtf8String());
295+
296+
// Call deleteObject to delete the object from given S3 Bucket
297+
deleteObject(bucket, objectKey, client);
298+
}
299+
300+
}

0 commit comments

Comments
 (0)