22
33import java .io .ByteArrayInputStream ;
44import java .io .IOException ;
5- import java .nio .charset .StandardCharsets ;
65import java .security .InvalidAlgorithmParameterException ;
76import java .security .InvalidKeyException ;
87import java .security .NoSuchAlgorithmException ;
9- import java .security .SecureRandom ;
108import java .util .Base64 ;
119import java .util .Collections ;
1210import java .util .HashMap ;
1311import java .util .List ;
1412import java .util .Map ;
15- import java .util .Map .Entry ;
1613import javax .crypto .BadPaddingException ;
1714import javax .crypto .Cipher ;
1815import javax .crypto .IllegalBlockSizeException ;
2825import software .amazon .awssdk .http .AbortableInputStream ;
2926import software .amazon .awssdk .protocols .jsoncore .JsonNode ;
3027import software .amazon .awssdk .protocols .jsoncore .JsonNodeParser ;
31- import software .amazon .awssdk .protocols .jsoncore .JsonWriter ;
32- import software .amazon .awssdk .protocols .jsoncore .JsonWriter .JsonGenerationException ;
3328import software .amazon .awssdk .services .s3 .S3Client ;
3429import software .amazon .awssdk .services .s3 .model .GetObjectRequest ;
3530import software .amazon .awssdk .services .s3 .model .GetObjectResponse ;
36- import software .amazon .awssdk .services .s3 .model .InvalidObjectStateException ;
37- import software .amazon .awssdk .services .s3 .model .NoSuchKeyException ;
3831import software .amazon .awssdk .services .s3 .model .PutObjectRequest ;
3932import software .amazon .awssdk .services .s3 .model .PutObjectResponse ;
40- import software .amazon .awssdk .services .s3 .model .S3Exception ;
4133import software .amazon .awssdk .utils .IoUtils ;
4234import software .amazon .encryption .s3 .algorithms .AlgorithmSuite ;
43- import software .amazon .encryption .s3 .materials .DecryptionMaterials ;
35+ import software .amazon .encryption .s3 .internal .MetadataKey ;
36+ import software .amazon .encryption .s3 .internal .PutEncryptedObjectPipeline ;
4437import software .amazon .encryption .s3 .materials .DecryptMaterialsRequest ;
45- import software .amazon .encryption .s3 .materials .EncryptionMaterialsRequest ;
38+ import software .amazon .encryption .s3 .materials .DecryptionMaterials ;
4639import software .amazon .encryption .s3 .materials .EncryptedDataKey ;
47- import software .amazon .encryption .s3 .materials .EncryptionMaterials ;
4840import software .amazon .encryption .s3 .materials .MaterialsManager ;
4941
5042public class S3EncryptionClient implements S3Client {
5143
5244 private final S3Client _wrappedClient ;
5345 private final MaterialsManager _materialsManager ;
5446
55- public S3EncryptionClient (S3Client client , MaterialsManager materialsManager ) {
56- _wrappedClient = client ;
57- _materialsManager = materialsManager ;
47+ private S3EncryptionClient (Builder builder ) {
48+ _wrappedClient = builder ._wrappedClient ;
49+ _materialsManager = builder ._materialsManager ;
50+ }
51+
52+ public static Builder builder () {
53+ return new Builder ();
5854 }
5955
6056 @ Override
6157 public PutObjectResponse putObject (PutObjectRequest putObjectRequest , RequestBody requestBody )
62- throws AwsServiceException , SdkClientException , S3Exception {
63-
64- // TODO: This is proof-of-concept code and needs to be refactored
58+ throws AwsServiceException , SdkClientException {
6559
66- // Get content encryption key
67- EncryptionMaterials materials = _materialsManager .getEncryptionMaterials (EncryptionMaterialsRequest .builder ()
68- .build ());
69- SecretKey contentKey = materials .dataKey ();
70- // Encrypt content
71- byte [] iv = new byte [12 ]; // default GCM IV length
72- new SecureRandom ().nextBytes (iv );
73-
74- final String contentEncryptionAlgorithm = "AES/GCM/NoPadding" ;
75- final Cipher cipher ;
76- try {
77- cipher = Cipher .getInstance (contentEncryptionAlgorithm );
78- cipher .init (Cipher .ENCRYPT_MODE , contentKey , new GCMParameterSpec (128 , iv ));
79- } catch (NoSuchAlgorithmException
80- | NoSuchPaddingException
81- | InvalidAlgorithmParameterException
82- | InvalidKeyException e ) {
83- throw new RuntimeException (e );
84- }
85-
86- byte [] ciphertext ;
87- try {
88- byte [] input = IoUtils .toByteArray (requestBody .contentStreamProvider ().newStream ());
89- ciphertext = cipher .doFinal (input );
90- } catch (IOException e ) {
91- throw new RuntimeException (e );
92- } catch (IllegalBlockSizeException e ) {
93- throw new RuntimeException (e );
94- } catch (BadPaddingException e ) {
95- throw new RuntimeException (e );
96- }
97-
98- // Save content metadata into request
99- Base64 .Encoder encoder = Base64 .getEncoder ();
100- Map <String ,String > metadata = new HashMap <>(putObjectRequest .metadata ());
101- EncryptedDataKey edk = materials .encryptedDataKeys ().get (0 );
102- metadata .put ("x-amz-key-v2" , encoder .encodeToString (edk .ciphertext ()));
103- metadata .put ("x-amz-iv" , encoder .encodeToString (iv ));
104- metadata .put ("x-amz-matdesc" , /* TODO: JSON encoded */ "{}" );
105- metadata .put ("x-amz-cek-alg" , contentEncryptionAlgorithm );
106- metadata .put ("x-amz-tag-len" , /* TODO: take from algo suite */ "128" );
107- metadata .put ("x-amz-wrap-alg" , edk .keyProviderId ());
108-
109- try (JsonWriter jsonWriter = JsonWriter .create ()) {
110- jsonWriter .writeStartObject ();
111- for (Entry <String ,String > entry : materials .encryptionContext ().entrySet ()) {
112- jsonWriter .writeFieldName (entry .getKey ()).writeValue (entry .getValue ());
113- }
114- jsonWriter .writeEndObject ();
115-
116- String jsonEncryptionContext = new String (jsonWriter .getBytes (), StandardCharsets .UTF_8 );
117- metadata .put ("x-amz-matdesc" , jsonEncryptionContext );
118- } catch (JsonGenerationException e ) {
119- throw new RuntimeException (e );
120- }
121-
122- putObjectRequest = putObjectRequest .toBuilder ().metadata (metadata ).build ();
60+ PutEncryptedObjectPipeline pipeline = PutEncryptedObjectPipeline .builder ()
61+ .s3Client (_wrappedClient )
62+ .materialsManager (_materialsManager )
63+ .build ();
12364
124- return _wrappedClient .putObject (putObjectRequest , RequestBody . fromBytes ( ciphertext ) );
65+ return pipeline .putObject (putObjectRequest , requestBody );
12566 }
12667
12768 @ Override
128- public <T > T getObject (GetObjectRequest getObjectRequest , ResponseTransformer <GetObjectResponse , T > responseTransformer )
129- throws NoSuchKeyException , InvalidObjectStateException , AwsServiceException , SdkClientException , S3Exception {
69+ public <T > T getObject (GetObjectRequest getObjectRequest ,
70+ ResponseTransformer <GetObjectResponse , T > responseTransformer )
71+ throws AwsServiceException , SdkClientException {
13072
13173 // TODO: This is proof-of-concept code and needs to be refactored
13274
133- ResponseInputStream <GetObjectResponse > objectStream = _wrappedClient .getObject (getObjectRequest );
75+ ResponseInputStream <GetObjectResponse > objectStream = _wrappedClient .getObject (
76+ getObjectRequest );
13477 byte [] output ;
13578 try {
13679 output = IoUtils .toByteArray (objectStream );
@@ -143,8 +86,8 @@ public <T> T getObject(GetObjectRequest getObjectRequest, ResponseTransformer<Ge
14386
14487 // Build encrypted data key
14588 Base64 .Decoder decoder = Base64 .getDecoder ();
146- byte [] edkCiphertext = decoder .decode (metadata .get ("x-amz-key-v2" ));
147- String keyProviderId = metadata .get ("x-amz-wrap-alg" );
89+ byte [] edkCiphertext = decoder .decode (metadata .get (MetadataKey . ENCRYPTED_DATA_KEY ));
90+ String keyProviderId = metadata .get (MetadataKey . ENCRYPTED_DATA_KEY_ALGORITHM );
14891 EncryptedDataKey edk = EncryptedDataKey .builder ()
14992 .ciphertext (edkCiphertext )
15093 .keyProviderId (keyProviderId )
@@ -153,7 +96,7 @@ public <T> T getObject(GetObjectRequest getObjectRequest, ResponseTransformer<Ge
15396
15497 // Get encryption context
15598 final Map <String , String > encryptionContext = new HashMap <>();
156- final String jsonEncryptionContext = metadata .get ("x-amz-matdesc" );
99+ final String jsonEncryptionContext = metadata .get (MetadataKey . ENCRYPTED_DATA_KEY_CONTEXT );
157100 try {
158101 JsonNodeParser parser = JsonNodeParser .create ();
159102 JsonNode objectNode = parser .parse (jsonEncryptionContext );
@@ -166,14 +109,15 @@ public <T> T getObject(GetObjectRequest getObjectRequest, ResponseTransformer<Ge
166109 }
167110
168111 // Get decryption materials
169- final String contentEncryptionAlgorithm = metadata .get ("x-amz-cek-alg" );
112+ final String contentEncryptionAlgorithm = metadata .get (MetadataKey . CONTENT_CIPHER );
170113 AlgorithmSuite algorithmSuite = null ;
171114 if (contentEncryptionAlgorithm .equals ("AES/GCM/NoPadding" )) {
172- algorithmSuite = AlgorithmSuite .ALG_AES_256_GCM_IV12_TAG16_NO_KDF ;;
115+ algorithmSuite = AlgorithmSuite .ALG_AES_256_GCM_IV12_TAG16_NO_KDF ;
173116 }
174117
175118 if (algorithmSuite == null ) {
176- throw new RuntimeException ("Unknown content encryption algorithm: " + contentEncryptionAlgorithm );
119+ throw new RuntimeException (
120+ "Unknown content encryption algorithm: " + contentEncryptionAlgorithm );
177121 }
178122
179123 DecryptMaterialsRequest request = DecryptMaterialsRequest .builder ()
@@ -185,8 +129,8 @@ public <T> T getObject(GetObjectRequest getObjectRequest, ResponseTransformer<Ge
185129
186130 // Get content encryption information
187131 SecretKey contentKey = new SecretKeySpec (materials .plaintextDataKey (), "AES" );
188- final int tagLength = Integer .parseInt (metadata .get ("x-amz-tag-len" ));
189- byte [] iv = decoder .decode (metadata .get ("x-amz-iv" ));
132+ final int tagLength = Integer .parseInt (metadata .get (MetadataKey . CONTENT_CIPHER_TAG_LENGTH ));
133+ byte [] iv = decoder .decode (metadata .get (MetadataKey . CONTENT_NONCE ));
190134 final Cipher cipher ;
191135 byte [] plaintext ;
192136 try {
@@ -221,4 +165,25 @@ public String serviceName() {
221165 public void close () {
222166 _wrappedClient .close ();
223167 }
168+
169+ public static class Builder {
170+ private S3Client _wrappedClient = S3Client .builder ().build ();
171+ private MaterialsManager _materialsManager ;
172+
173+ private Builder () {}
174+
175+ public Builder wrappedClient (S3Client wrappedClient ) {
176+ this ._wrappedClient = wrappedClient ;
177+ return this ;
178+ }
179+
180+ public Builder materialsManager (MaterialsManager materialsManager ) {
181+ this ._materialsManager = materialsManager ;
182+ return this ;
183+ }
184+
185+ public S3EncryptionClient build () {
186+ return new S3EncryptionClient (this );
187+ }
188+ }
224189}
0 commit comments