Skip to content

Commit 0f40511

Browse files
committed
Working and V2 compatible encrypt.
1 parent 204a111 commit 0f40511

File tree

11 files changed

+431
-108
lines changed

11 files changed

+431
-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: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package software.amazon.encryption.s3;
2+
3+
import java.io.IOException;
4+
import java.nio.charset.Charset;
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.security.spec.InvalidParameterSpecException;
11+
import java.util.Base64;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
import java.util.Map.Entry;
15+
import javax.crypto.BadPaddingException;
16+
import javax.crypto.Cipher;
17+
import javax.crypto.IllegalBlockSizeException;
18+
import javax.crypto.NoSuchPaddingException;
19+
import javax.crypto.SecretKey;
20+
import javax.crypto.spec.GCMParameterSpec;
21+
import javax.crypto.spec.IvParameterSpec;
22+
import software.amazon.awssdk.awscore.exception.AwsServiceException;
23+
import software.amazon.awssdk.core.exception.SdkClientException;
24+
import software.amazon.awssdk.core.sync.RequestBody;
25+
import software.amazon.awssdk.protocols.jsoncore.JsonWriter;
26+
import software.amazon.awssdk.protocols.jsoncore.JsonWriter.JsonGenerationException;
27+
import software.amazon.awssdk.services.s3.S3Client;
28+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
29+
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
30+
import software.amazon.awssdk.services.s3.model.S3Exception;
31+
import software.amazon.awssdk.utils.IoUtils;
32+
import software.amazon.encryption.s3.materials.DefaultMaterialsManager;
33+
import software.amazon.encryption.s3.materials.DefaultMaterialsManager.EncryptionMaterialsRequest;
34+
import software.amazon.encryption.s3.materials.EncryptedDataKey;
35+
import software.amazon.encryption.s3.materials.EncryptionMaterials;
36+
37+
public class S3EncryptionClient implements S3Client {
38+
39+
private final S3Client _wrappedClient;
40+
private final DefaultMaterialsManager _materialsManager;
41+
42+
public S3EncryptionClient(S3Client client, DefaultMaterialsManager materialsManager) {
43+
_wrappedClient = client;
44+
_materialsManager = materialsManager;
45+
}
46+
47+
@Override
48+
public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBody requestBody)
49+
throws AwsServiceException, SdkClientException, S3Exception {
50+
51+
// Get content encryption key
52+
EncryptionMaterials materials = _materialsManager.getEncryptionMaterials(new EncryptionMaterialsRequest());
53+
SecretKey contentKey = materials.dataKey();
54+
// Encrypt content
55+
byte[] iv = new byte[12]; // default GCM IV length
56+
new SecureRandom().nextBytes(iv);
57+
58+
final String contentEncryptionAlgorithm = "AES/GCM/NoPadding";
59+
final Cipher cipher;
60+
try {
61+
cipher = Cipher.getInstance(contentEncryptionAlgorithm);
62+
//GCMParameterSpec defaultSpec = cipher.getParameters().getParameterSpec(GCMParameterSpec.class);
63+
cipher.init(Cipher.ENCRYPT_MODE, contentKey, new GCMParameterSpec(128, iv));
64+
} catch (NoSuchAlgorithmException
65+
| NoSuchPaddingException
66+
| InvalidAlgorithmParameterException
67+
| InvalidKeyException e) {
68+
throw new RuntimeException(e);
69+
}/* catch (InvalidParameterSpecException e) {
70+
throw new RuntimeException(e);
71+
}*/
72+
73+
byte[] ciphertext;
74+
try {
75+
byte[] input = IoUtils.toByteArray(requestBody.contentStreamProvider().newStream());
76+
ciphertext = cipher.doFinal(input);
77+
} catch (IOException e) {
78+
throw new RuntimeException(e);
79+
} catch (IllegalBlockSizeException e) {
80+
throw new RuntimeException(e);
81+
} catch (BadPaddingException e) {
82+
throw new RuntimeException(e);
83+
}
84+
85+
// Save content metadata into request
86+
Base64.Encoder encoder = Base64.getEncoder();
87+
Map<String,String> metadata = new HashMap<>(putObjectRequest.metadata());
88+
EncryptedDataKey edk = materials.encryptedDataKeys().get(0);
89+
metadata.put("x-amz-key-v2", encoder.encodeToString(edk.ciphertext()));
90+
metadata.put("x-amz-iv", encoder.encodeToString(iv));
91+
metadata.put("x-amz-matdesc", /* TODO: JSON encoded */ "{}");
92+
metadata.put("x-amz-cek-alg", contentEncryptionAlgorithm);
93+
metadata.put("x-amz-tag-len", /* TODO: take from algo suite */ "128");
94+
metadata.put("x-amz-wrap-alg", edk.keyProviderId());
95+
96+
try (JsonWriter jsonWriter = JsonWriter.create()) {
97+
jsonWriter.writeStartObject();
98+
for (Entry<String,String> entry : materials.encryptionContext().entrySet()) {
99+
jsonWriter.writeFieldName(entry.getKey()).writeValue(entry.getValue());
100+
}
101+
jsonWriter.writeEndObject();
102+
103+
String jsonEncryptionContext = new String(jsonWriter.getBytes(), StandardCharsets.UTF_8);
104+
metadata.put("x-amz-matdesc", jsonEncryptionContext);
105+
} catch (JsonGenerationException e) {
106+
throw new RuntimeException(e);
107+
}
108+
109+
putObjectRequest = putObjectRequest.toBuilder().metadata(metadata).build();
110+
111+
return _wrappedClient.putObject(putObjectRequest, RequestBody.fromBytes(ciphertext));
112+
}
113+
114+
@Override
115+
public String serviceName() {
116+
return _wrappedClient.serviceName();
117+
}
118+
119+
@Override
120+
public void close() {
121+
_wrappedClient.close();
122+
}
123+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package software.amazon.encryption.s3;public class TracerBullet {
2+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package software.amazon.encryption.s3.algorithms;
2+
3+
4+
public enum AlgorithmSuite {
5+
// TODO: fill in these values
6+
// ALG_AES_256_GCM_IV12_TAG16_NO_KDF(12, 16, Constants.GCM_MAX_CONTENT_LEN, "AES", 32, 0x0078, "AES", 32),
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package software.amazon.encryption.s3.algorithms;
2+
3+
class Constants {
4+
/** Maximum length of the content that can be encrypted in GCM mode. */
5+
public static final long GCM_MAX_CONTENT_LEN = (1L << 36) - 32;
6+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package software.amazon.encryption.s3.materials;
2+
3+
import java.nio.charset.StandardCharsets;
4+
import java.security.NoSuchAlgorithmException;
5+
import java.security.SecureRandom;
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import javax.crypto.Cipher;
9+
import javax.crypto.NoSuchPaddingException;
10+
import javax.crypto.SecretKey;
11+
import javax.crypto.spec.GCMParameterSpec;
12+
13+
public class AESKeyring implements Keyring {
14+
15+
private static final String KEY_PROVIDER_ID = "AES/GCM";
16+
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
17+
private static final int IV_LENGTH_IN_BYTES = 12;
18+
private static final int TAG_LENGTH_IN_BYTES = 16;
19+
private static final int TAG_LENGTH_IN_BITS = TAG_LENGTH_IN_BYTES * 8;
20+
private final SecretKey _wrappingKey;
21+
22+
public AESKeyring(SecretKey wrappingKey) {
23+
if (!wrappingKey.getAlgorithm().equals("AES")) {
24+
// TODO: throw?
25+
}
26+
27+
_wrappingKey = wrappingKey;
28+
}
29+
30+
@Override
31+
public EncryptionMaterials OnEncrypt(EncryptionMaterials materials) {
32+
// TODO: handle a null plaintext data key
33+
34+
try {
35+
SecureRandom secureRandom = new SecureRandom();
36+
37+
byte[] iv = new byte[IV_LENGTH_IN_BYTES];
38+
secureRandom.nextBytes(iv);
39+
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_IN_BITS, iv);
40+
41+
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
42+
// TODO: should/can this be Cipher.WRAP_MODE?
43+
cipher.init(Cipher.ENCRYPT_MODE, _wrappingKey, gcmParameterSpec, secureRandom);
44+
45+
// this is the CONTENT encryption, not the wrapping encryption
46+
// TODO: get this from encryption context or preferably algorithm suite
47+
cipher.updateAAD("AES/GCM/NoPadding".getBytes(StandardCharsets.UTF_8));
48+
49+
// The encrypted data key is the IV prepended to the ciphertext
50+
iv = cipher.getIV();
51+
byte[] ciphertext = cipher.doFinal(materials.plaintextDataKey());
52+
53+
byte[] encodedBytes = new byte[iv.length + ciphertext.length];
54+
System.arraycopy(iv, 0, encodedBytes, 0, iv.length);
55+
System.arraycopy(ciphertext, 0, encodedBytes, iv.length, ciphertext.length);
56+
57+
EncryptedDataKey encryptedDataKey = EncryptedDataKey.builder()
58+
.keyProviderId(KEY_PROVIDER_ID)
59+
.ciphertext(encodedBytes)
60+
.build();
61+
62+
List<EncryptedDataKey> encryptedDataKeys = new ArrayList<>(materials.encryptedDataKeys());
63+
encryptedDataKeys.add(encryptedDataKey);
64+
65+
return materials.toBuilder()
66+
.encryptedDataKeys(encryptedDataKeys)
67+
.build();
68+
} catch (Exception e) {
69+
throw new UnsupportedOperationException("Unable to AES/GCM/NoPadding wrap", e);
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)