Skip to content

Commit 4202371

Browse files
committed
Basic decrypt working
1 parent 0f40511 commit 4202371

File tree

5 files changed

+262
-4
lines changed

5 files changed

+262
-4
lines changed

src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package software.amazon.encryption.s3;
22

3+
import java.io.ByteArrayInputStream;
34
import java.io.IOException;
45
import java.nio.charset.Charset;
56
import java.nio.charset.StandardCharsets;
@@ -9,7 +10,9 @@
910
import java.security.SecureRandom;
1011
import java.security.spec.InvalidParameterSpecException;
1112
import java.util.Base64;
13+
import java.util.Collections;
1214
import java.util.HashMap;
15+
import java.util.List;
1316
import java.util.Map;
1417
import java.util.Map.Entry;
1518
import javax.crypto.BadPaddingException;
@@ -19,17 +22,29 @@
1922
import javax.crypto.SecretKey;
2023
import javax.crypto.spec.GCMParameterSpec;
2124
import javax.crypto.spec.IvParameterSpec;
25+
import javax.crypto.spec.SecretKeySpec;
2226
import software.amazon.awssdk.awscore.exception.AwsServiceException;
27+
import software.amazon.awssdk.core.ResponseInputStream;
2328
import software.amazon.awssdk.core.exception.SdkClientException;
2429
import software.amazon.awssdk.core.sync.RequestBody;
30+
import software.amazon.awssdk.core.sync.ResponseTransformer;
31+
import software.amazon.awssdk.http.AbortableInputStream;
32+
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
33+
import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
2534
import software.amazon.awssdk.protocols.jsoncore.JsonWriter;
2635
import software.amazon.awssdk.protocols.jsoncore.JsonWriter.JsonGenerationException;
2736
import software.amazon.awssdk.services.s3.S3Client;
37+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
38+
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
39+
import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
40+
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
2841
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
2942
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
3043
import software.amazon.awssdk.services.s3.model.S3Exception;
3144
import software.amazon.awssdk.utils.IoUtils;
45+
import software.amazon.encryption.s3.materials.DecryptionMaterials;
3246
import software.amazon.encryption.s3.materials.DefaultMaterialsManager;
47+
import software.amazon.encryption.s3.materials.DefaultMaterialsManager.DecryptionMaterialsRequest;
3348
import software.amazon.encryption.s3.materials.DefaultMaterialsManager.EncryptionMaterialsRequest;
3449
import software.amazon.encryption.s3.materials.EncryptedDataKey;
3550
import software.amazon.encryption.s3.materials.EncryptionMaterials;
@@ -111,6 +126,88 @@ public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBod
111126
return _wrappedClient.putObject(putObjectRequest, RequestBody.fromBytes(ciphertext));
112127
}
113128

129+
@Override
130+
public <T> T getObject(GetObjectRequest getObjectRequest, ResponseTransformer<GetObjectResponse, T> responseTransformer)
131+
throws NoSuchKeyException, InvalidObjectStateException, AwsServiceException, SdkClientException, S3Exception {
132+
ResponseInputStream<GetObjectResponse> objectStream = _wrappedClient.getObject(getObjectRequest);
133+
byte[] output;
134+
try {
135+
output = IoUtils.toByteArray(objectStream);
136+
} catch (IOException e) {
137+
throw new RuntimeException(e);
138+
}
139+
140+
GetObjectResponse response = objectStream.response();
141+
Map<String, String> metadata = response.metadata();
142+
143+
// Build encrypted data key
144+
Base64.Decoder decoder = Base64.getDecoder();
145+
byte[] edkCiphertext = decoder.decode(metadata.get("x-amz-key-v2"));
146+
String keyProviderId = metadata.get("x-amz-wrap-alg");
147+
EncryptedDataKey edk = EncryptedDataKey.builder()
148+
.ciphertext(edkCiphertext)
149+
.keyProviderId(keyProviderId)
150+
.build();
151+
List<EncryptedDataKey> encryptedDataKeys = Collections.singletonList(edk);
152+
153+
// Get encryption context
154+
final Map<String, String> encryptionContext = new HashMap<>();
155+
final String jsonEncryptionContext = metadata.get("x-amz-matdesc");
156+
try {
157+
JsonNodeParser parser = JsonNodeParser.create();
158+
JsonNode objectNode = parser.parse(jsonEncryptionContext);
159+
160+
for (Map.Entry<String, JsonNode> entry : objectNode.asObject().entrySet()) {
161+
encryptionContext.put(entry.getKey(), entry.getValue().asString());
162+
}
163+
} catch (Exception e) {
164+
throw new RuntimeException(e);
165+
}
166+
167+
// Get decryption materials
168+
final String contentEncryptionAlgorithm = metadata.get("x-amz-cek-alg");
169+
int algorithmSuiteId = 0;
170+
if (contentEncryptionAlgorithm.equals("AES/GCM/NoPadding")) {
171+
algorithmSuiteId = 0x0078;
172+
}
173+
174+
DecryptionMaterialsRequest request = new DecryptionMaterialsRequest();
175+
request.encryptionContext = encryptionContext;
176+
request.algorithmSuiteId = algorithmSuiteId;
177+
request.encryptedDataKeys = encryptedDataKeys;
178+
DecryptionMaterials materials = _materialsManager.getDecryptionMaterials(request);
179+
180+
// Get content encryption information
181+
SecretKey contentKey = new SecretKeySpec(materials.plaintextDataKey(), "AES");
182+
final int tagLength = Integer.parseInt(metadata.get("x-amz-tag-len"));
183+
byte[] iv = decoder.decode(metadata.get("x-amz-iv"));
184+
final Cipher cipher;
185+
byte[] plaintext;
186+
try {
187+
cipher = Cipher.getInstance(contentEncryptionAlgorithm);
188+
cipher.init(Cipher.DECRYPT_MODE, contentKey, new GCMParameterSpec(tagLength, iv));
189+
plaintext = cipher.doFinal(output);
190+
} catch (NoSuchAlgorithmException
191+
| NoSuchPaddingException
192+
| InvalidAlgorithmParameterException
193+
| InvalidKeyException e) {
194+
throw new RuntimeException(e);
195+
} catch (IllegalBlockSizeException e) {
196+
throw new RuntimeException(e);
197+
} catch (BadPaddingException e) {
198+
throw new RuntimeException(e);
199+
}
200+
201+
try {
202+
return responseTransformer.transform(response,
203+
AbortableInputStream.create(new ByteArrayInputStream(plaintext)));
204+
} catch (Exception e) {
205+
throw new RuntimeException(e);
206+
}
207+
208+
// return _wrappedClient.getObject(getObjectRequest, responseTransformer);
209+
}
210+
114211
@Override
115212
public String serviceName() {
116213
return _wrappedClient.serviceName();

src/main/java/software/amazon/encryption/s3/materials/AESKeyring.java

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public AESKeyring(SecretKey wrappingKey) {
2828
}
2929

3030
@Override
31-
public EncryptionMaterials OnEncrypt(EncryptionMaterials materials) {
31+
public EncryptionMaterials onEncrypt(EncryptionMaterials materials) {
3232
// TODO: handle a null plaintext data key
3333

3434
try {
@@ -39,7 +39,6 @@ public EncryptionMaterials OnEncrypt(EncryptionMaterials materials) {
3939
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_IN_BITS, iv);
4040

4141
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
42-
// TODO: should/can this be Cipher.WRAP_MODE?
4342
cipher.init(Cipher.ENCRYPT_MODE, _wrappingKey, gcmParameterSpec, secureRandom);
4443

4544
// this is the CONTENT encryption, not the wrapping encryption
@@ -69,4 +68,59 @@ public EncryptionMaterials OnEncrypt(EncryptionMaterials materials) {
6968
throw new UnsupportedOperationException("Unable to AES/GCM/NoPadding wrap", e);
7069
}
7170
}
71+
72+
@Override
73+
public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List<EncryptedDataKey> encryptedDataKeys) {
74+
/*
75+
ByteBuffer encryptedCekBuff = ByteBuffer.wrap(encryptedCek);
76+
// Split the IV from the front of the ciphertext
77+
byte[] iv = new byte[IV_LENGTH_IN_BYTES];
78+
byte[] taggedCek = new byte[encryptedCek.length - IV_LENGTH_IN_BYTES];
79+
encryptedCekBuff.get(iv);
80+
encryptedCekBuff.get(taggedCek);
81+
82+
Cipher cipher = this.cipherProvider.createCipher();
83+
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_IN_BITS, iv);
84+
try {
85+
cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
86+
cipher.updateAAD(this.cekAlgorithm.getBytes(StandardCharsets.UTF_8));
87+
return cipher.doFinal(taggedCek);
88+
} catch (Exception e) {
89+
throw failure(e, "An exception was thrown when attempting to decrypt the Content Encryption Key");
90+
}
91+
*/
92+
if (materials.plaintextDataKey() != null) {
93+
return materials;
94+
}
95+
96+
for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) {
97+
if (!encryptedDataKey.keyProviderId().equals(KEY_PROVIDER_ID)) {
98+
continue;
99+
}
100+
101+
byte[] encodedBytes = encryptedDataKey.ciphertext();
102+
byte[] iv = new byte[IV_LENGTH_IN_BYTES];
103+
byte[] ciphertext = new byte[encodedBytes.length - iv.length];
104+
105+
System.arraycopy(encodedBytes, 0, iv, 0, iv.length);
106+
System.arraycopy(encodedBytes, iv.length, ciphertext, 0, ciphertext.length);
107+
108+
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_IN_BITS, iv);
109+
try {
110+
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
111+
cipher.init(Cipher.DECRYPT_MODE, _wrappingKey, gcmParameterSpec);
112+
// this is the CONTENT encryption, not the wrapping encryption
113+
// TODO: get this from encryption context or preferably algorithm suite
114+
cipher.updateAAD("AES/GCM/NoPadding".getBytes(StandardCharsets.UTF_8));
115+
byte[] plaintext = cipher.doFinal(ciphertext);
116+
117+
return materials.toBuilder().plaintextDataKey(plaintext).build();
118+
} catch (Exception e) {
119+
// TODO: maybe this should fall through?
120+
throw new UnsupportedOperationException("Unable to AES/GCM/NoPadding unwrap", e);
121+
}
122+
}
123+
124+
return materials;
125+
}
72126
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package software.amazon.encryption.s3.materials;
2+
3+
import java.util.Collections;
4+
import java.util.List;
5+
import java.util.Map;
6+
import javax.crypto.SecretKey;
7+
import javax.crypto.spec.SecretKeySpec;
8+
9+
final public class DecryptionMaterials {
10+
11+
// Identifies what sort of crypto algorithms we want to use
12+
// In ESDK, this is an enum
13+
private final int _algorithmSuiteId;
14+
15+
// Additional information passed into encrypted that is required on decryption as well
16+
// Should NOT contain sensitive information
17+
private final Map<String, String> _encryptionContext;
18+
19+
private final byte[] _plaintextDataKey;
20+
21+
// Unused in here since signing is not supported
22+
// private final byte[] _signingKey;
23+
24+
25+
private DecryptionMaterials(Builder builder) {
26+
this._algorithmSuiteId = builder._algorithmSuiteId;
27+
this._encryptionContext = builder._encryptionContext;
28+
this._plaintextDataKey = builder._plaintextDataKey;
29+
}
30+
31+
static public Builder builder() {
32+
return new Builder();
33+
}
34+
35+
public int algorithmSuiteId() {
36+
return _algorithmSuiteId;
37+
}
38+
39+
public Map<String, String> encryptionContext() {
40+
return _encryptionContext;
41+
}
42+
43+
public byte[] plaintextDataKey() {
44+
return _plaintextDataKey;
45+
}
46+
47+
public SecretKey dataKey() {
48+
return new SecretKeySpec(_plaintextDataKey, "AES");
49+
}
50+
51+
public Builder toBuilder() {
52+
return new Builder()
53+
.algorithmSuiteId(_algorithmSuiteId)
54+
.encryptionContext(_encryptionContext)
55+
.plaintextDataKey(_plaintextDataKey);
56+
}
57+
58+
static public class Builder {
59+
60+
private int _algorithmSuiteId = -1;
61+
private Map<String, String> _encryptionContext = Collections.emptyMap();
62+
private byte[] _plaintextDataKey = null;
63+
64+
private Builder() {
65+
}
66+
67+
public Builder algorithmSuiteId(int id) {
68+
_algorithmSuiteId = id;
69+
return this;
70+
}
71+
72+
public Builder encryptionContext(Map<String, String> encryptionContext) {
73+
_encryptionContext = encryptionContext == null
74+
? Collections.emptyMap()
75+
: Collections.unmodifiableMap(encryptionContext);
76+
return this;
77+
}
78+
79+
public Builder plaintextDataKey(byte[] plaintextDataKey) {
80+
_plaintextDataKey = plaintextDataKey == null ? null : plaintextDataKey.clone();
81+
return this;
82+
}
83+
84+
public DecryptionMaterials build() {
85+
return new DecryptionMaterials(this);
86+
}
87+
}
88+
}

src/main/java/software/amazon/encryption/s3/materials/DefaultMaterialsManager.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.security.NoSuchAlgorithmException;
44
import java.security.SecureRandom;
5+
import java.util.List;
56
import java.util.Map;
67
import javax.crypto.KeyGenerator;
78
import javax.crypto.SecretKey;
@@ -25,7 +26,7 @@ public EncryptionMaterials getEncryptionMaterials(EncryptionMaterialsRequest req
2526
.plaintextDataKey(key.getEncoded())
2627
.build();
2728

28-
return _keyring.OnEncrypt(materials);
29+
return _keyring.onEncrypt(materials);
2930
}
3031

3132
private SecretKey generateDataKey() {
@@ -40,7 +41,22 @@ private SecretKey generateDataKey() {
4041
return generator.generateKey();
4142
}
4243

44+
public DecryptionMaterials getDecryptionMaterials(DecryptionMaterialsRequest request) {
45+
DecryptionMaterials materials = DecryptionMaterials.builder()
46+
.algorithmSuiteId(request.algorithmSuiteId)
47+
.encryptionContext(request.encryptionContext)
48+
.build();
49+
50+
return _keyring.onDecrypt(materials, request.encryptedDataKeys);
51+
}
52+
4353
public static class EncryptionMaterialsRequest {
4454
public Map<String, String> encryptionContext;
4555
}
56+
57+
public static class DecryptionMaterialsRequest {
58+
public int algorithmSuiteId;
59+
public List<EncryptedDataKey> encryptedDataKeys;
60+
public Map<String, String> encryptionContext;
61+
}
4662
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package software.amazon.encryption.s3.materials;
22

3+
import java.util.List;
4+
35
public interface Keyring {
4-
EncryptionMaterials OnEncrypt(final EncryptionMaterials materials);
6+
EncryptionMaterials onEncrypt(final EncryptionMaterials materials);
7+
DecryptionMaterials onDecrypt(final DecryptionMaterials materials, final List<EncryptedDataKey> encryptedDataKeys);
58
}

0 commit comments

Comments
 (0)