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