Skip to content

Commit 69e193a

Browse files
authored
Merge pull request #9 from smswz/request-encrypt-context
Add request-scoped encryption context
2 parents 49b8846 + ef8bc2a commit 69e193a

18 files changed

+248
-161
lines changed

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package software.amazon.encryption.s3;
22

33
import java.security.KeyPair;
4-
import java.security.SecureRandom;
4+
import java.util.Map;
5+
import java.util.function.Consumer;
56
import javax.crypto.SecretKey;
7+
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
68
import software.amazon.awssdk.awscore.exception.AwsServiceException;
79
import software.amazon.awssdk.core.exception.SdkClientException;
10+
import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
811
import software.amazon.awssdk.core.sync.RequestBody;
912
import software.amazon.awssdk.core.sync.ResponseTransformer;
1013
import software.amazon.awssdk.services.s3.S3Client;
@@ -16,28 +19,40 @@
1619
import software.amazon.encryption.s3.internal.PutEncryptedObjectPipeline;
1720
import software.amazon.encryption.s3.materials.AesKeyring;
1821
import software.amazon.encryption.s3.materials.CryptographicMaterialsManager;
19-
import software.amazon.encryption.s3.materials.DataKeyGenerator;
2022
import software.amazon.encryption.s3.materials.DefaultCryptoMaterialsManager;
21-
import software.amazon.encryption.s3.materials.DefaultDataKeyGenerator;
2223
import software.amazon.encryption.s3.materials.Keyring;
2324
import software.amazon.encryption.s3.materials.KmsKeyring;
2425
import software.amazon.encryption.s3.materials.RsaKeyring;
2526

27+
/**
28+
* This client is a drop-in replacement for the S3 client. It will automatically encrypt objects
29+
* on putObject and decrypt objects on getObject using the provided encryption key(s).
30+
*/
2631
public class S3EncryptionClient implements S3Client {
2732

33+
// Used for request-scoped encryption contexts for supporting keys
34+
public static final ExecutionAttribute<Map<String,String>> ENCRYPTION_CONTEXT = new ExecutionAttribute<>("EncryptionContext");
35+
2836
private final S3Client _wrappedClient;
2937
private final CryptographicMaterialsManager _cryptoMaterialsManager;
38+
private final boolean _enableLegacyModes;
3039

3140
private S3EncryptionClient(Builder builder) {
3241
_wrappedClient = builder._wrappedClient;
3342
_cryptoMaterialsManager = builder._cryptoMaterialsManager;
34-
// TODO: store _enableLegacyModes and pass onto pipeline
43+
_enableLegacyModes = builder._enableLegacyModes;
3544
}
3645

3746
public static Builder builder() {
3847
return new Builder();
3948
}
4049

50+
// Helper function to attach encryption contexts to a request
51+
public static Consumer<AwsRequestOverrideConfiguration.Builder> withAdditionalEncryptionContext(Map<String, String> encryptionContext) {
52+
return builder ->
53+
builder.putExecutionAttribute(S3EncryptionClient.ENCRYPTION_CONTEXT, encryptionContext);
54+
}
55+
4156
@Override
4257
public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBody requestBody)
4358
throws AwsServiceException, SdkClientException {
@@ -58,6 +73,7 @@ public <T> T getObject(GetObjectRequest getObjectRequest,
5873
GetEncryptedObjectPipeline pipeline = GetEncryptedObjectPipeline.builder()
5974
.s3Client(_wrappedClient)
6075
.cryptoMaterialsManager(_cryptoMaterialsManager)
76+
.enableLegacyModes(_enableLegacyModes)
6177
.build();
6278

6379
return pipeline.getObject(getObjectRequest, responseTransformer);

src/main/java/software/amazon/encryption/s3/internal/ContentMetadata.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,6 @@
77

88
public class ContentMetadata {
99

10-
/*
11-
public static final String ENCRYPTED_DATA_KEY = "x-amz-key-v2";
12-
// This is the name of the keyring/algorithm e.g. AES/GCM or kms+context
13-
public static final String ENCRYPTED_DATA_KEY_ALGORITHM = "x-amz-wrap-alg";
14-
public static final String ENCRYPTED_DATA_KEY_CONTEXT = "x-amz-matdesc";
15-
16-
public static final String CONTENT_NONCE = "x-amz-iv";
17-
// This is usually an actual Java cipher e.g. AES/GCM/NoPadding
18-
public static final String CONTENT_CIPHER = "x-amz-cek-alg";
19-
public static final String CONTENT_CIPHER_TAG_LENGTH = "x-amz-tag-len";
20-
*/
21-
2210
private final AlgorithmSuite _algorithmSuite;
2311

2412
private final EncryptedDataKey _encryptedDataKey;

src/main/java/software/amazon/encryption/s3/internal/CryptoMaterialsManagerKeyStrategy.java

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

src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,24 @@
2020
import software.amazon.encryption.s3.materials.DecryptionMaterials;
2121
import software.amazon.encryption.s3.materials.EncryptedDataKey;
2222

23+
/**
24+
* This class will determine the necessary mechanisms to decrypt objects returned from S3.
25+
* Due to supporting various legacy modes, this is not a predefined pipeline like
26+
* PutEncryptedObjectPipeline. There are several branches in this graph that are determined as more
27+
* information is available from the returned object.
28+
*/
2329
public class GetEncryptedObjectPipeline {
2430

25-
final private S3Client _s3Client;
26-
final private CryptographicMaterialsManager _cryptoMaterialsManager;
31+
private final S3Client _s3Client;
32+
private final CryptographicMaterialsManager _cryptoMaterialsManager;
33+
private final boolean _enableLegacyModes;
2734

2835
public static Builder builder() { return new Builder(); }
2936

3037
private GetEncryptedObjectPipeline(Builder builder) {
3138
this._s3Client = builder._s3Client;
3239
this._cryptoMaterialsManager = builder._cryptoMaterialsManager;
40+
this._enableLegacyModes = builder._enableLegacyModes;
3341
}
3442

3543
public <T> T getObject(GetObjectRequest getObjectRequest,
@@ -47,15 +55,20 @@ public <T> T getObject(GetObjectRequest getObjectRequest,
4755
ContentMetadata contentMetadata = ContentMetadataStrategy.decode(_s3Client, getObjectRequest, getObjectResponse);
4856

4957
AlgorithmSuite algorithmSuite = contentMetadata.algorithmSuite();
58+
if (!_enableLegacyModes && algorithmSuite.isLegacy()) {
59+
throw new S3EncryptionClientException("Enable legacy modes to use legacy content encryption: " + algorithmSuite.cipherName());
60+
}
61+
5062
List<EncryptedDataKey> encryptedDataKeys = Collections.singletonList(contentMetadata.encryptedDataKey());
5163

52-
DecryptMaterialsRequest request = DecryptMaterialsRequest.builder()
64+
DecryptMaterialsRequest materialsRequest = DecryptMaterialsRequest.builder()
65+
.s3Request(getObjectRequest)
5366
.algorithmSuite(algorithmSuite)
5467
.encryptedDataKeys(encryptedDataKeys)
5568
.encryptionContext(contentMetadata.encryptedDataKeyContext())
5669
.build();
5770

58-
DecryptionMaterials materials = _cryptoMaterialsManager.decryptMaterials(request);
71+
DecryptionMaterials materials = _cryptoMaterialsManager.decryptMaterials(materialsRequest);
5972

6073
ContentDecryptionStrategy contentDecryptionStrategy = null;
6174
switch (algorithmSuite) {
@@ -79,6 +92,7 @@ public <T> T getObject(GetObjectRequest getObjectRequest,
7992
public static class Builder {
8093
private S3Client _s3Client;
8194
private CryptographicMaterialsManager _cryptoMaterialsManager;
95+
private boolean _enableLegacyModes;
8296

8397
private Builder() {}
8498

@@ -92,6 +106,11 @@ public Builder cryptoMaterialsManager(CryptographicMaterialsManager cryptoMateri
92106
return this;
93107
}
94108

109+
public Builder enableLegacyModes(boolean enableLegacyModes) {
110+
this._enableLegacyModes = enableLegacyModes;
111+
return this;
112+
}
113+
95114
public GetEncryptedObjectPipeline build() {
96115
return new GetEncryptedObjectPipeline(this);
97116
}

src/main/java/software/amazon/encryption/s3/internal/KeyUnwrapStrategy.java

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

src/main/java/software/amazon/encryption/s3/internal/PutEncryptedObjectPipeline.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ private PutEncryptedObjectPipeline(Builder builder) {
2929
}
3030

3131
public PutObjectResponse putObject(PutObjectRequest request, RequestBody requestBody) {
32-
EncryptionMaterials materials = _cryptoMaterialsManager.getEncryptionMaterials(
33-
EncryptionMaterialsRequest.builder()
34-
.build());
32+
EncryptionMaterialsRequest.Builder requestBuilder = EncryptionMaterialsRequest.builder()
33+
.s3Request(request);
34+
35+
EncryptionMaterials materials = _cryptoMaterialsManager.getEncryptionMaterials(requestBuilder.build());
3536

3637
byte[] input;
3738
try {
39+
// TODO: this needs to be a stream and not a byte[]
3840
input = IoUtils.toByteArray(requestBody.contentStreamProvider().newStream());
3941
} catch (IOException e) {
4042
throw new S3EncryptionClientException("Cannot read input.", e);
@@ -49,6 +51,7 @@ public PutObjectResponse putObject(PutObjectRequest request, RequestBody request
4951
public static class Builder {
5052
private S3Client _s3Client;
5153
private CryptographicMaterialsManager _cryptoMaterialsManager;
54+
// Default to AesGcm since it is the only active (non-legacy) content encryption strategy
5255
private ContentEncryptionStrategy _contentEncryptionStrategy =
5356
AesGcmContentStrategy
5457
.builder()

src/main/java/software/amazon/encryption/s3/legacy/internal/AesCbcContentStrategy.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.security.InvalidAlgorithmParameterException;
44
import java.security.InvalidKeyException;
55
import java.security.NoSuchAlgorithmException;
6-
import java.security.SecureRandom;
76
import javax.crypto.BadPaddingException;
87
import javax.crypto.Cipher;
98
import javax.crypto.IllegalBlockSizeException;

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ public String keyProviderInfo() {
3838
}
3939

4040
@Override
41-
public byte[] decryptDataKey(DecryptionMaterials materials, EncryptedDataKey encryptedDataKey) throws GeneralSecurityException {
41+
public byte[] decryptDataKey(DecryptionMaterials materials, byte[] encryptedDataKey) throws GeneralSecurityException {
4242
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
4343
cipher.init(Cipher.DECRYPT_MODE, _wrappingKey);
4444

45-
return cipher.doFinal(encryptedDataKey.ciphertext());
45+
return cipher.doFinal(encryptedDataKey);
4646
}
4747
};
4848

@@ -62,11 +62,11 @@ public String keyProviderInfo() {
6262
}
6363

6464
@Override
65-
public byte[] decryptDataKey(DecryptionMaterials materials, EncryptedDataKey encryptedDataKey) throws GeneralSecurityException {
65+
public byte[] decryptDataKey(DecryptionMaterials materials, byte[] encryptedDataKey) throws GeneralSecurityException {
6666
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
6767
cipher.init(Cipher.UNWRAP_MODE, _wrappingKey);
6868

69-
Key plaintextKey = cipher.unwrap(encryptedDataKey.ciphertext(), CIPHER_ALGORITHM, Cipher.SECRET_KEY);
69+
Key plaintextKey = cipher.unwrap(encryptedDataKey, CIPHER_ALGORITHM, Cipher.SECRET_KEY);
7070
return plaintextKey.getEncoded();
7171
}
7272
};
@@ -113,16 +113,16 @@ public byte[] encryptDataKey(SecureRandom secureRandom,
113113
}
114114

115115
@Override
116-
public byte[] decryptDataKey(DecryptionMaterials materials, EncryptedDataKey encryptedDataKey) throws GeneralSecurityException {
117-
byte[] encodedBytes = encryptedDataKey.ciphertext();
116+
public byte[] decryptDataKey(DecryptionMaterials materials, byte[] encryptedDataKey) throws GeneralSecurityException {
117+
byte[] encodedBytes = encryptedDataKey;
118118
byte[] nonce = new byte[NONCE_LENGTH_BYTES];
119119
byte[] ciphertext = new byte[encodedBytes.length - nonce.length];
120120

121121
System.arraycopy(encodedBytes, 0, nonce, 0, nonce.length);
122122
System.arraycopy(encodedBytes, nonce.length, ciphertext, 0, ciphertext.length);
123123

124124
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_BITS, nonce);
125-
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
125+
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
126126
cipher.init(Cipher.DECRYPT_MODE, _wrappingKey, gcmParameterSpec);
127127

128128
AlgorithmSuite algorithmSuite = materials.algorithmSuite();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ public interface DecryptDataKeyStrategy {
77

88
String keyProviderInfo();
99

10-
byte[] decryptDataKey(DecryptionMaterials materials, EncryptedDataKey encryptedDataKey)
10+
byte[] decryptDataKey(DecryptionMaterials materials, byte[] encryptedDataKey)
1111
throws GeneralSecurityException;
1212
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
import java.util.Collections;
44
import java.util.List;
55
import java.util.Map;
6+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
67
import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
78

89
public class DecryptMaterialsRequest {
910

11+
private final GetObjectRequest _s3Request;
1012
private final AlgorithmSuite _algorithmSuite;
1113
private final List<EncryptedDataKey> _encryptedDataKeys;
1214
private final Map<String, String> _encryptionContext;
1315

1416
private DecryptMaterialsRequest(Builder builder) {
17+
this._s3Request = builder._s3Request;
1518
this._algorithmSuite = builder._algorithmSuite;
1619
this._encryptedDataKeys = builder._encryptedDataKeys;
1720
this._encryptionContext = builder._encryptionContext;
@@ -21,6 +24,10 @@ static public Builder builder() {
2124
return new Builder();
2225
}
2326

27+
public GetObjectRequest s3Request() {
28+
return _s3Request;
29+
}
30+
2431
public AlgorithmSuite algorithmSuite() {
2532
return _algorithmSuite;
2633
}
@@ -35,13 +42,19 @@ public Map<String, String> encryptionContext() {
3542

3643
static public class Builder {
3744

45+
public GetObjectRequest _s3Request = null;
3846
private AlgorithmSuite _algorithmSuite = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF;
3947
private Map<String, String> _encryptionContext = Collections.emptyMap();
4048
private List<EncryptedDataKey> _encryptedDataKeys = Collections.emptyList();
4149

4250
private Builder() {
4351
}
4452

53+
public Builder s3Request(GetObjectRequest s3Request) {
54+
_s3Request = s3Request;
55+
return this;
56+
}
57+
4558
public Builder algorithmSuite(AlgorithmSuite algorithmSuite) {
4659
_algorithmSuite = algorithmSuite;
4760
return this;

0 commit comments

Comments
 (0)