1+ package software .amazon .encryption .s3 .examples ;
2+
3+ import software .amazon .awssdk .core .ResponseBytes ;
4+ import software .amazon .awssdk .core .sync .RequestBody ;
5+ import software .amazon .awssdk .services .s3 .S3Client ;
6+ import software .amazon .awssdk .services .s3 .model .Delete ;
7+ import software .amazon .awssdk .services .s3 .model .GetObjectResponse ;
8+ import software .amazon .awssdk .services .s3 .model .ObjectIdentifier ;
9+ import software .amazon .awssdk .services .s3 .model .PutObjectRequest ;
10+ import software .amazon .encryption .s3 .S3EncryptionClient ;
11+ import software .amazon .encryption .s3 .S3EncryptionClientException ;
12+ import software .amazon .encryption .s3 .materials .PartialRsaKeyPair ;
13+
14+ import java .security .KeyPair ;
15+ import java .security .KeyPairGenerator ;
16+ import java .security .NoSuchAlgorithmException ;
17+ import java .util .Set ;
18+ import java .util .stream .Collectors ;
19+ import java .util .stream .Stream ;
20+
21+ import static org .junit .jupiter .api .Assertions .assertEquals ;
22+ import static org .junit .jupiter .api .Assertions .fail ;
23+
24+ public class PartialKeyPairExample {
25+
26+ private static final String OBJECT_CONTENT = "Hello, world!" ;
27+
28+ // Use unique object keys for each example
29+ private static final String PUBLIC_AND_PRIVATE_KEY_OBJECT_KEY = "PublicAndPrivateKeyTestObject" ;
30+ private static final String PUBLIC_KEY_OBJECT_KEY = "PublicKeyTestObject" ;
31+ private static final String PRIVATE_KEY_OBJECT_KEY = "PrivateKeyTestObject" ;
32+
33+ private static final Set <ObjectIdentifier > PARTIAL_KEY_PAIR_EXAMPLE_OBJECT_KEYS = Stream
34+ .of (PUBLIC_AND_PRIVATE_KEY_OBJECT_KEY , PUBLIC_KEY_OBJECT_KEY , PRIVATE_KEY_OBJECT_KEY )
35+ .map (k -> ObjectIdentifier .builder ().key (k ).build ())
36+ .collect (Collectors .toSet ());
37+
38+ // This example generates a new key. In practice, you would
39+ // retrieve your key from an existing keystore.
40+ private static final KeyPair RSA_KEY_PAIR = retrieveRsaKeyPair ();
41+
42+ public static void main (final String [] args ) {
43+ final String bucket = args [0 ];
44+
45+ useBothPublicAndPrivateKey (bucket );
46+ useOnlyPublicKey (bucket );
47+ useOnlyPrivateKey (bucket );
48+ cleanup (bucket );
49+ }
50+
51+ public static void useBothPublicAndPrivateKey (final String bucket ) {
52+ // Instantiate the S3 Encryption Client to encrypt and decrypt
53+ // by specifying an RSA wrapping key pair with the rsaKeyPair builder
54+ // parameter.
55+ // This means that the S3 Encryption Client can perform both encrypt and decrypt operations
56+ // as part of the S3 putObject and getObject operations.
57+ S3Client s3Client = S3EncryptionClient .builder ()
58+ .rsaKeyPair (RSA_KEY_PAIR )
59+ .build ();
60+
61+ // Call putObject to encrypt the object and upload it to S3
62+ s3Client .putObject (PutObjectRequest .builder ()
63+ .bucket (bucket )
64+ .key (PUBLIC_AND_PRIVATE_KEY_OBJECT_KEY )
65+ .build (), RequestBody .fromString (OBJECT_CONTENT ));
66+
67+ // Call getObject to retrieve and decrypt the object from S3
68+ ResponseBytes <GetObjectResponse > objectResponse = s3Client .getObjectAsBytes (builder -> builder
69+ .bucket (bucket )
70+ .key (PUBLIC_AND_PRIVATE_KEY_OBJECT_KEY ));
71+ String output = objectResponse .asUtf8String ();
72+
73+ // Verify that the decrypted object matches the original plaintext object
74+ assertEquals (OBJECT_CONTENT , output , "Decrypted response does not match original plaintext!" );
75+
76+ // Close the client
77+ s3Client .close ();
78+ }
79+
80+ static void useOnlyPublicKey (final String bucket ) {
81+ // Instantiate the S3 Encryption client to encrypt by specifying the
82+ // public key from an RSA key pair with the PartialKeyPair object.
83+ // When you specify the public key alone, all GetObject calls will fail
84+ // because the private key is required to decrypt.
85+ S3Client s3Client = S3EncryptionClient .builder ()
86+ .rsaKeyPair (new PartialRsaKeyPair (null , RSA_KEY_PAIR .getPublic ()))
87+ .build ();
88+
89+ // Call putObject to encrypt the object and upload it to S3
90+ s3Client .putObject (PutObjectRequest .builder ()
91+ .bucket (bucket )
92+ .key (PUBLIC_KEY_OBJECT_KEY )
93+ .build (), RequestBody .fromString (OBJECT_CONTENT ));
94+
95+ // Attempt to call getObject to retrieve and decrypt the object from S3.
96+ try {
97+ s3Client .getObjectAsBytes (builder -> builder
98+ .bucket (bucket )
99+ .key (PUBLIC_KEY_OBJECT_KEY ));
100+ fail ("Expected exception! No private key provided for decryption." );
101+ } catch (final S3EncryptionClientException exception ) {
102+ // This is expected; the s3Client cannot successfully call getObject
103+ // when instantiated with a public key.
104+ }
105+
106+ // Close the client
107+ s3Client .close ();
108+ }
109+
110+ static void useOnlyPrivateKey (final String bucket ) {
111+
112+ // Instantiate the S3 Encryption client to decrypt by specifying the
113+ // private key from an RSA key pair with the PartialRsaKeyPair object.
114+ // When you specify the private key alone, all PutObject calls will
115+ // fail because the public key is required to encrypt.
116+ S3Client s3ClientPrivateKeyOnly = S3EncryptionClient .builder ()
117+ .rsaKeyPair (new PartialRsaKeyPair (RSA_KEY_PAIR .getPrivate (), null ))
118+ .build ();
119+
120+ // Attempt to call putObject to encrypt the object and upload it to S3
121+ try {
122+ s3ClientPrivateKeyOnly .putObject (PutObjectRequest .builder ()
123+ .bucket (bucket )
124+ .key (PRIVATE_KEY_OBJECT_KEY )
125+ .build (), RequestBody .fromString (OBJECT_CONTENT ));
126+ fail ("Expected exception! No public key provided for encryption." );
127+ } catch (final S3EncryptionClientException exception ) {
128+ // This is expected; the s3Client cannot successfully call putObject
129+ // when instantiated with a private key.
130+ }
131+
132+ // Instantiate a new S3 Encryption client with a public key in order
133+ // to successfully call PutObject so that the client which only has
134+ // a private key can call GetObject on a valid S3 Object.
135+ S3Client s3ClientPublicKeyOnly = S3EncryptionClient .builder ()
136+ .rsaKeyPair (new PartialRsaKeyPair (null , RSA_KEY_PAIR .getPublic ()))
137+ .build ();
138+
139+ // Call putObject to encrypt the object and upload it to S3
140+ s3ClientPublicKeyOnly .putObject (PutObjectRequest .builder ()
141+ .bucket (bucket )
142+ .key (PRIVATE_KEY_OBJECT_KEY )
143+ .build (), RequestBody .fromString (OBJECT_CONTENT ));
144+
145+ // Call getObject to retrieve and decrypt the object from S3
146+ ResponseBytes <GetObjectResponse > objectResponse = s3ClientPrivateKeyOnly .getObjectAsBytes (builder -> builder
147+ .bucket (bucket )
148+ .key (PRIVATE_KEY_OBJECT_KEY ));
149+ String output = objectResponse .asUtf8String ();
150+
151+ // Verify that the decrypted object matches the original plaintext object
152+ assertEquals (OBJECT_CONTENT , output , "The decrypted response does not match the original plaintext!" );
153+
154+ // Close the clients
155+ s3ClientPublicKeyOnly .close ();
156+ s3ClientPrivateKeyOnly .close ();
157+ }
158+
159+ public static void cleanup (final String bucket ) {
160+ // The S3 Encryption client is not required when deleting encrypted
161+ // objects, use the S3 Client.
162+ final S3Client s3Client = S3Client .builder ().build ();
163+ final Delete delete = Delete .builder ()
164+ .objects (PARTIAL_KEY_PAIR_EXAMPLE_OBJECT_KEYS )
165+ .build ();
166+ s3Client .deleteObjects (builder -> builder
167+ .bucket (bucket )
168+ .delete (delete )
169+ .build ());
170+
171+ // Close the client
172+ s3Client .close ();
173+ }
174+
175+ private static KeyPair retrieveRsaKeyPair () {
176+ try {
177+ KeyPairGenerator keyPairGen = KeyPairGenerator .getInstance ("RSA" );
178+ keyPairGen .initialize (2048 );
179+ return keyPairGen .generateKeyPair ();
180+ } catch (final NoSuchAlgorithmException exception ) {
181+ // This should be impossible, wrap with a runtime exception
182+ throw new RuntimeException (exception );
183+ }
184+ }
185+
186+ }
0 commit comments