Skip to content

Commit be394d1

Browse files
authored
Merge pull request #2 from smswz/kms
Working KMS keyring
2 parents 7f131e2 + 7236e40 commit be394d1

File tree

3 files changed

+168
-2
lines changed

3 files changed

+168
-2
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package software.amazon.encryption.s3.internal;
2+
3+
import software.amazon.awssdk.core.ApiName;
4+
5+
/**
6+
* Provides the information for the ApiName APIs for the AWS SDK
7+
*/
8+
public class ApiNameVersion {
9+
public static final String API_NAME = "AwsS3Encrypt";
10+
11+
public static final String API_VERSION_UNKNOWN = "unknown";
12+
13+
public static ApiName apiNameWithVersion() {
14+
return ApiName.builder()
15+
.name(API_NAME)
16+
.version(apiVersion())
17+
.build();
18+
}
19+
20+
private static String apiVersion() {
21+
// TODO: Use a resources file akin to ESDK to populate this
22+
return API_VERSION_UNKNOWN;
23+
}
24+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public EncryptionMaterials onEncrypt(EncryptionMaterials materials) {
7474
.encryptedDataKeys(encryptedDataKeys)
7575
.build();
7676
} catch (Exception e) {
77-
throw new S3EncryptionClientException("Unable to " + CIPHER_ALGORITHM + " wrap", e);
77+
throw new S3EncryptionClientException("Unable to " + KEY_PROVIDER_ID + " wrap", e);
7878
}
7979
}
8080

@@ -107,7 +107,7 @@ public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List<E
107107

108108
return materials.toBuilder().plaintextDataKey(plaintext).build();
109109
} catch (Exception e) {
110-
throw new S3EncryptionClientException("Unable to " + CIPHER_ALGORITHM + " unwrap", e);
110+
throw new S3EncryptionClientException("Unable to " + KEY_PROVIDER_ID + " unwrap", e);
111111
}
112112
}
113113

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package software.amazon.encryption.s3.materials;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.TreeMap;
6+
import javax.crypto.SecretKey;
7+
import software.amazon.awssdk.core.ApiName;
8+
import software.amazon.awssdk.core.SdkBytes;
9+
import software.amazon.awssdk.services.kms.KmsClient;
10+
import software.amazon.awssdk.services.kms.model.DecryptRequest;
11+
import software.amazon.awssdk.services.kms.model.DecryptResponse;
12+
import software.amazon.awssdk.services.kms.model.EncryptRequest;
13+
import software.amazon.awssdk.services.kms.model.EncryptResponse;
14+
import software.amazon.encryption.s3.S3EncryptionClientException;
15+
import software.amazon.encryption.s3.internal.ApiNameVersion;
16+
17+
/**
18+
* AESKeyring will call to KMS to wrap the data key used to encrypt content.
19+
*/
20+
public class KMSContextKeyring implements Keyring {
21+
22+
private static final String KEY_PROVIDER_ID = "kms+context";
23+
24+
private static final String ENCRYPTION_CONTEXT_ALGORITHM_KEY = "aws:x-amz-cek-alg";
25+
26+
private static final ApiName API_NAME = ApiNameVersion.apiNameWithVersion();
27+
28+
private final KmsClient _kmsClient;
29+
private final String _wrappingKeyId;
30+
private final DataKeyGenerator _dataKeyGenerator;
31+
32+
public KMSContextKeyring(Builder builder) {
33+
_kmsClient = builder._kmsClient;
34+
_wrappingKeyId = builder._wrappingKeyId;
35+
_dataKeyGenerator = builder._dataKeyGenerator;
36+
}
37+
38+
public static Builder builder() {
39+
return new Builder();
40+
}
41+
42+
@Override
43+
public EncryptionMaterials onEncrypt(EncryptionMaterials materials) {
44+
if (materials.plaintextDataKey() == null) {
45+
SecretKey dataKey = _dataKeyGenerator.generateDataKey(materials.algorithmSuite());
46+
materials = materials.toBuilder()
47+
.plaintextDataKey(dataKey.getEncoded())
48+
.build();
49+
}
50+
51+
if (materials.encryptionContext().containsKey(ENCRYPTION_CONTEXT_ALGORITHM_KEY)) {
52+
throw new S3EncryptionClientException(ENCRYPTION_CONTEXT_ALGORITHM_KEY + " is a reserved key for the S3 encryption client");
53+
}
54+
55+
TreeMap<String, String> encryptionContext = new TreeMap<>(materials.encryptionContext());
56+
encryptionContext.put(ENCRYPTION_CONTEXT_ALGORITHM_KEY, materials.algorithmSuite().cipherName());
57+
58+
try {
59+
EncryptRequest request = EncryptRequest.builder()
60+
.keyId(_wrappingKeyId)
61+
.encryptionContext(encryptionContext)
62+
.plaintext(SdkBytes.fromByteArray(materials.plaintextDataKey()))
63+
.overrideConfiguration(builder -> builder.addApiName(API_NAME))
64+
.build();
65+
66+
EncryptResponse response = _kmsClient.encrypt(request);
67+
byte[] ciphertext = response.ciphertextBlob().asByteArray();
68+
69+
EncryptedDataKey encryptedDataKey = EncryptedDataKey.builder()
70+
.keyProviderId(KEY_PROVIDER_ID)
71+
.ciphertext(ciphertext)
72+
.build();
73+
74+
List<EncryptedDataKey> encryptedDataKeys = new ArrayList<>(materials.encryptedDataKeys());
75+
encryptedDataKeys.add(encryptedDataKey);
76+
77+
return materials.toBuilder()
78+
.encryptionContext(encryptionContext)
79+
.encryptedDataKeys(encryptedDataKeys)
80+
.build();
81+
} catch (Exception e) {
82+
throw new S3EncryptionClientException("Unable to " + KEY_PROVIDER_ID + " wrap", e);
83+
}
84+
}
85+
86+
@Override
87+
public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List<EncryptedDataKey> encryptedDataKeys) {
88+
if (materials.plaintextDataKey() != null) {
89+
return materials;
90+
}
91+
92+
for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) {
93+
if (!encryptedDataKey.keyProviderId().equals(KEY_PROVIDER_ID)) {
94+
continue;
95+
}
96+
97+
try {
98+
DecryptRequest request = DecryptRequest.builder()
99+
.keyId(_wrappingKeyId)
100+
.encryptionContext(materials.encryptionContext())
101+
.ciphertextBlob(SdkBytes.fromByteArray(encryptedDataKey.ciphertext()))
102+
.overrideConfiguration(builder -> builder.addApiName(API_NAME))
103+
.build();
104+
105+
DecryptResponse response = _kmsClient.decrypt(request);
106+
107+
return materials.toBuilder().plaintextDataKey(response.plaintext().asByteArray()).build();
108+
} catch (Exception e) {
109+
throw new S3EncryptionClientException("Unable to " + KEY_PROVIDER_ID + " unwrap", e);
110+
}
111+
}
112+
113+
return materials;
114+
}
115+
116+
public static class Builder {
117+
private KmsClient _kmsClient;
118+
private String _wrappingKeyId;
119+
private DataKeyGenerator _dataKeyGenerator = new DefaultDataKeyGenerator();
120+
121+
private Builder() {}
122+
123+
public Builder kmsClient(KmsClient kmsClient) {
124+
_kmsClient = kmsClient;
125+
return this;
126+
}
127+
128+
public Builder wrappingKeyId(String wrappingKeyId) {
129+
_wrappingKeyId = wrappingKeyId;
130+
return this;
131+
}
132+
133+
public Builder dataKeyGenerator(DataKeyGenerator dataKeyGenerator) {
134+
_dataKeyGenerator = dataKeyGenerator;
135+
return this;
136+
}
137+
138+
public KMSContextKeyring build() {
139+
return new KMSContextKeyring(this);
140+
}
141+
}
142+
}

0 commit comments

Comments
 (0)