Skip to content

Commit 7f131e2

Browse files
authored
Merge pull request #1 from smswz/main
Create AES Keyring and skeleton of S3 client
2 parents 204a111 + 2d2cf62 commit 7f131e2

17 files changed

+887
-108
lines changed

aws-java-sdk-s3/pom.xml

Lines changed: 0 additions & 103 deletions
This file was deleted.

pom.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
<modelVersion>4.0.0</modelVersion>
66

77
<groupId>software.amazon.encryption</groupId>
8-
<artifactId>s3-client</artifactId>
8+
<artifactId>s3</artifactId>
99
<version>3.0-SNAPSHOT</version>
1010
<packaging>jar</packaging>
1111

1212
<name>AWS S3 Encryption Client</name>
1313
<description>The AWS S3 Encryption Client provides client-side encryption for S3</description>
14-
<url>https://github.com/aws/aws-s33c-java</url>
14+
<url>https://github.com/aws/aws-s3-encryption-client-java</url>
1515

1616
<licenses>
1717
<license>
@@ -51,7 +51,7 @@
5151
<dependency>
5252
<groupId>software.amazon.awssdk</groupId>
5353
<artifactId>bom</artifactId>
54-
<version>2.17.154</version>
54+
<version>2.17.204</version>
5555
<optional>true</optional>
5656
<type>pom</type>
5757
<scope>import</scope>
@@ -80,14 +80,14 @@
8080
<dependency>
8181
<groupId>software.amazon.awssdk</groupId>
8282
<artifactId>s3</artifactId>
83-
<version>2.17.154</version>
83+
<version>2.17.204</version>
8484
<optional>true</optional>
8585
</dependency>
8686

8787
<dependency>
8888
<groupId>software.amazon.awssdk</groupId>
8989
<artifactId>kms</artifactId>
90-
<version>2.17.154</version>
90+
<version>2.17.204</version>
9191
<optional>true</optional>
9292
</dependency>
9393
</dependencies>
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package software.amazon.encryption.s3;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.IOException;
5+
import java.nio.charset.StandardCharsets;
6+
import java.security.InvalidAlgorithmParameterException;
7+
import java.security.InvalidKeyException;
8+
import java.security.NoSuchAlgorithmException;
9+
import java.security.SecureRandom;
10+
import java.util.Base64;
11+
import java.util.Collections;
12+
import java.util.HashMap;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.Map.Entry;
16+
import javax.crypto.BadPaddingException;
17+
import javax.crypto.Cipher;
18+
import javax.crypto.IllegalBlockSizeException;
19+
import javax.crypto.NoSuchPaddingException;
20+
import javax.crypto.SecretKey;
21+
import javax.crypto.spec.GCMParameterSpec;
22+
import javax.crypto.spec.SecretKeySpec;
23+
import software.amazon.awssdk.awscore.exception.AwsServiceException;
24+
import software.amazon.awssdk.core.ResponseInputStream;
25+
import software.amazon.awssdk.core.exception.SdkClientException;
26+
import software.amazon.awssdk.core.sync.RequestBody;
27+
import software.amazon.awssdk.core.sync.ResponseTransformer;
28+
import software.amazon.awssdk.http.AbortableInputStream;
29+
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
30+
import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
31+
import software.amazon.awssdk.protocols.jsoncore.JsonWriter;
32+
import software.amazon.awssdk.protocols.jsoncore.JsonWriter.JsonGenerationException;
33+
import software.amazon.awssdk.services.s3.S3Client;
34+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
35+
import 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;
38+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
39+
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
40+
import software.amazon.awssdk.services.s3.model.S3Exception;
41+
import software.amazon.awssdk.utils.IoUtils;
42+
import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
43+
import software.amazon.encryption.s3.materials.DecryptionMaterials;
44+
import software.amazon.encryption.s3.materials.DecryptMaterialsRequest;
45+
import software.amazon.encryption.s3.materials.EncryptionMaterialsRequest;
46+
import software.amazon.encryption.s3.materials.EncryptedDataKey;
47+
import software.amazon.encryption.s3.materials.EncryptionMaterials;
48+
import software.amazon.encryption.s3.materials.MaterialsManager;
49+
50+
public class S3EncryptionClient implements S3Client {
51+
52+
private final S3Client _wrappedClient;
53+
private final MaterialsManager _materialsManager;
54+
55+
public S3EncryptionClient(S3Client client, MaterialsManager materialsManager) {
56+
_wrappedClient = client;
57+
_materialsManager = materialsManager;
58+
}
59+
60+
@Override
61+
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
65+
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();
123+
124+
return _wrappedClient.putObject(putObjectRequest, RequestBody.fromBytes(ciphertext));
125+
}
126+
127+
@Override
128+
public <T> T getObject(GetObjectRequest getObjectRequest, ResponseTransformer<GetObjectResponse, T> responseTransformer)
129+
throws NoSuchKeyException, InvalidObjectStateException, AwsServiceException, SdkClientException, S3Exception {
130+
131+
// TODO: This is proof-of-concept code and needs to be refactored
132+
133+
ResponseInputStream<GetObjectResponse> objectStream = _wrappedClient.getObject(getObjectRequest);
134+
byte[] output;
135+
try {
136+
output = IoUtils.toByteArray(objectStream);
137+
} catch (IOException e) {
138+
throw new RuntimeException(e);
139+
}
140+
141+
GetObjectResponse response = objectStream.response();
142+
Map<String, String> metadata = response.metadata();
143+
144+
// Build encrypted data key
145+
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");
148+
EncryptedDataKey edk = EncryptedDataKey.builder()
149+
.ciphertext(edkCiphertext)
150+
.keyProviderId(keyProviderId)
151+
.build();
152+
List<EncryptedDataKey> encryptedDataKeys = Collections.singletonList(edk);
153+
154+
// Get encryption context
155+
final Map<String, String> encryptionContext = new HashMap<>();
156+
final String jsonEncryptionContext = metadata.get("x-amz-matdesc");
157+
try {
158+
JsonNodeParser parser = JsonNodeParser.create();
159+
JsonNode objectNode = parser.parse(jsonEncryptionContext);
160+
161+
for (Map.Entry<String, JsonNode> entry : objectNode.asObject().entrySet()) {
162+
encryptionContext.put(entry.getKey(), entry.getValue().asString());
163+
}
164+
} catch (Exception e) {
165+
throw new RuntimeException(e);
166+
}
167+
168+
// Get decryption materials
169+
final String contentEncryptionAlgorithm = metadata.get("x-amz-cek-alg");
170+
AlgorithmSuite algorithmSuite = null;
171+
if (contentEncryptionAlgorithm.equals("AES/GCM/NoPadding")) {
172+
algorithmSuite = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF;;
173+
}
174+
175+
if (algorithmSuite == null) {
176+
throw new RuntimeException("Unknown content encryption algorithm: " + contentEncryptionAlgorithm);
177+
}
178+
179+
DecryptMaterialsRequest request = DecryptMaterialsRequest.builder()
180+
.algorithmSuite(algorithmSuite)
181+
.encryptedDataKeys(encryptedDataKeys)
182+
.encryptionContext(encryptionContext)
183+
.build();
184+
DecryptionMaterials materials = _materialsManager.decryptMaterials(request);
185+
186+
// Get content encryption information
187+
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"));
190+
final Cipher cipher;
191+
byte[] plaintext;
192+
try {
193+
cipher = Cipher.getInstance(contentEncryptionAlgorithm);
194+
cipher.init(Cipher.DECRYPT_MODE, contentKey, new GCMParameterSpec(tagLength, iv));
195+
plaintext = cipher.doFinal(output);
196+
} catch (NoSuchAlgorithmException
197+
| NoSuchPaddingException
198+
| InvalidAlgorithmParameterException
199+
| InvalidKeyException e) {
200+
throw new RuntimeException(e);
201+
} catch (IllegalBlockSizeException e) {
202+
throw new RuntimeException(e);
203+
} catch (BadPaddingException e) {
204+
throw new RuntimeException(e);
205+
}
206+
207+
try {
208+
return responseTransformer.transform(response,
209+
AbortableInputStream.create(new ByteArrayInputStream(plaintext)));
210+
} catch (Exception e) {
211+
throw new RuntimeException(e);
212+
}
213+
}
214+
215+
@Override
216+
public String serviceName() {
217+
return _wrappedClient.serviceName();
218+
}
219+
220+
@Override
221+
public void close() {
222+
_wrappedClient.close();
223+
}
224+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package software.amazon.encryption.s3;
2+
3+
import software.amazon.awssdk.core.exception.SdkClientException;
4+
5+
public class S3EncryptionClientException extends SdkClientException {
6+
7+
public S3EncryptionClientException(String message) {
8+
super(SdkClientException.builder()
9+
.message(message));
10+
}
11+
12+
public S3EncryptionClientException(String message, Throwable cause) {
13+
super(SdkClientException.builder()
14+
.message(message)
15+
.cause(cause));
16+
}
17+
}

0 commit comments

Comments
 (0)