Skip to content

Commit e37bcf1

Browse files
committed
Working KMS keyring
1 parent 7f131e2 commit e37bcf1

File tree

2 files changed

+140
-2
lines changed

2 files changed

+140
-2
lines changed

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: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package software.amazon.encryption.s3.materials;
2+
3+
import java.security.SecureRandom;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
import java.util.TreeMap;
7+
import javax.crypto.SecretKey;
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.materials.AESKeyring.Builder;
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 final KmsClient _kmsClient;
27+
private final String _wrappingKeyId;
28+
private final DataKeyGenerator _dataKeyGenerator;
29+
30+
public KMSContextKeyring(Builder builder) {
31+
_kmsClient = builder._kmsClient;
32+
_wrappingKeyId = builder._wrappingKeyId;
33+
_dataKeyGenerator = builder._dataKeyGenerator;
34+
}
35+
36+
public static Builder builder() {
37+
return new Builder();
38+
}
39+
40+
@Override
41+
public EncryptionMaterials onEncrypt(EncryptionMaterials materials) {
42+
if (materials.plaintextDataKey() == null) {
43+
SecretKey dataKey = _dataKeyGenerator.generateDataKey(materials.algorithmSuite());
44+
materials = materials.toBuilder()
45+
.plaintextDataKey(dataKey.getEncoded())
46+
.build();
47+
}
48+
49+
if (materials.encryptionContext().containsKey(ENCRYPTION_CONTEXT_ALGORITHM_KEY)) {
50+
throw new IllegalStateException(ENCRYPTION_CONTEXT_ALGORITHM_KEY + " is a reserved key for the S3 encryption client");
51+
}
52+
53+
TreeMap<String, String> encryptionContext = new TreeMap<>(materials.encryptionContext());
54+
encryptionContext.put(ENCRYPTION_CONTEXT_ALGORITHM_KEY, materials.algorithmSuite().cipherName());
55+
56+
try {
57+
EncryptRequest request = EncryptRequest.builder()
58+
.keyId(_wrappingKeyId)
59+
.encryptionContext(encryptionContext)
60+
.plaintext(SdkBytes.fromByteArray(materials.plaintextDataKey()))
61+
.build();
62+
63+
EncryptResponse response = _kmsClient.encrypt(request);
64+
byte[] ciphertext = response.ciphertextBlob().asByteArray();
65+
66+
EncryptedDataKey encryptedDataKey = EncryptedDataKey.builder()
67+
.keyProviderId(KEY_PROVIDER_ID)
68+
.ciphertext(ciphertext)
69+
.build();
70+
71+
List<EncryptedDataKey> encryptedDataKeys = new ArrayList<>(materials.encryptedDataKeys());
72+
encryptedDataKeys.add(encryptedDataKey);
73+
74+
return materials.toBuilder()
75+
.encryptionContext(encryptionContext)
76+
.encryptedDataKeys(encryptedDataKeys)
77+
.build();
78+
} catch (Exception e) {
79+
throw new UnsupportedOperationException("Unable to " + KEY_PROVIDER_ID + " wrap", e);
80+
}
81+
}
82+
83+
@Override
84+
public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List<EncryptedDataKey> encryptedDataKeys) {
85+
if (materials.plaintextDataKey() != null) {
86+
return materials;
87+
}
88+
89+
for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) {
90+
if (!encryptedDataKey.keyProviderId().equals(KEY_PROVIDER_ID)) {
91+
continue;
92+
}
93+
94+
try {
95+
DecryptRequest request = DecryptRequest.builder()
96+
.keyId(_wrappingKeyId)
97+
.encryptionContext(materials.encryptionContext())
98+
.ciphertextBlob(SdkBytes.fromByteArray(encryptedDataKey.ciphertext()))
99+
.build();
100+
101+
DecryptResponse response = _kmsClient.decrypt(request);
102+
103+
return materials.toBuilder().plaintextDataKey(response.plaintext().asByteArray()).build();
104+
} catch (Exception e) {
105+
throw new UnsupportedOperationException("Unable to " + KEY_PROVIDER_ID + " unwrap", e);
106+
}
107+
}
108+
109+
return materials;
110+
}
111+
112+
public static class Builder {
113+
private KmsClient _kmsClient;
114+
private String _wrappingKeyId;
115+
private DataKeyGenerator _dataKeyGenerator = new DefaultDataKeyGenerator();
116+
117+
private Builder() {}
118+
119+
public Builder kmsClient(KmsClient kmsClient) {
120+
_kmsClient = kmsClient;
121+
return this;
122+
}
123+
124+
public Builder wrappingKeyId(String wrappingKeyId) {
125+
_wrappingKeyId = wrappingKeyId;
126+
return this;
127+
}
128+
129+
public Builder dataKeyGenerator(DataKeyGenerator dataKeyGenerator) {
130+
_dataKeyGenerator = dataKeyGenerator;
131+
return this;
132+
}
133+
134+
public KMSContextKeyring build() {
135+
return new KMSContextKeyring(this);
136+
}
137+
}
138+
}

0 commit comments

Comments
 (0)