Skip to content

Commit b643bc8

Browse files
akareddy04Anirav Kareddy
andauthored
MaterialsDescription Support for Keyrings (#467)
* I created the MaterialsDescription class to help identify AES + RSA keys for reEncryptInstructionFile feature Co-authored-by: Anirav Kareddy <aniravk@amazon.com>
1 parent 99077dc commit b643bc8

File tree

13 files changed

+645
-59
lines changed

13 files changed

+645
-59
lines changed

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ public class ContentMetadata {
1515

1616
private final EncryptedDataKey _encryptedDataKey;
1717
private final String _encryptedDataKeyAlgorithm;
18-
private final Map<String, String> _encryptedDataKeyContext;
18+
19+
/**
20+
* This field stores either encryption context or material description.
21+
* We use a single field to store both in order to maintain backwards
22+
* compatibility with V2, which treated both as the same.
23+
*/
24+
private final Map<String, String> _encryptionContextOrMatDesc;
1925

2026
private final byte[] _contentIv;
2127
private final String _contentCipher;
@@ -27,7 +33,7 @@ private ContentMetadata(Builder builder) {
2733

2834
_encryptedDataKey = builder._encryptedDataKey;
2935
_encryptedDataKeyAlgorithm = builder._encryptedDataKeyAlgorithm;
30-
_encryptedDataKeyContext = builder._encryptedDataKeyContext;
36+
_encryptionContextOrMatDesc = builder._encryptionContextOrMatDesc;
3137

3238
_contentIv = builder._contentIv;
3339
_contentCipher = builder._contentCipher;
@@ -51,14 +57,15 @@ public String encryptedDataKeyAlgorithm() {
5157
return _encryptedDataKeyAlgorithm;
5258
}
5359

60+
5461
/**
5562
* Note that the underlying implementation uses a Collections.unmodifiableMap which is
5663
* immutable.
5764
*/
5865
@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "False positive; underlying"
5966
+ " implementation is immutable")
6067
public Map<String, String> encryptedDataKeyContext() {
61-
return _encryptedDataKeyContext;
68+
return _encryptionContextOrMatDesc;
6269
}
6370

6471
public byte[] contentIv() {
@@ -85,7 +92,7 @@ public static class Builder {
8592

8693
private EncryptedDataKey _encryptedDataKey;
8794
private String _encryptedDataKeyAlgorithm;
88-
private Map<String, String> _encryptedDataKeyContext;
95+
private Map<String, String> _encryptionContextOrMatDesc;
8996

9097
private byte[] _contentIv;
9198
private String _contentCipher;
@@ -111,8 +118,8 @@ public Builder encryptedDataKeyAlgorithm(String encryptedDataKeyAlgorithm) {
111118
return this;
112119
}
113120

114-
public Builder encryptedDataKeyContext(Map<String, String> encryptedDataKeyContext) {
115-
_encryptedDataKeyContext = Collections.unmodifiableMap(encryptedDataKeyContext);
121+
public Builder encryptionContextOrMatDesc(Map<String, String> encryptionContextOrMatDesc) {
122+
_encryptionContextOrMatDesc = Collections.unmodifiableMap(encryptionContextOrMatDesc);
116123
return this;
117124
}
118125

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ private ContentMetadata readFromMap(Map<String, String> metadata, GetObjectRespo
136136
.keyProviderInfo(keyProviderInfo.getBytes(StandardCharsets.UTF_8))
137137
.build();
138138

139-
// Get encrypted data key encryption context
140-
final Map<String, String> encryptionContext = new HashMap<>();
139+
// Get encrypted data key encryption context or materials description (depending on the keyring)
140+
final Map<String, String> encryptionContextOrMatDesc = new HashMap<>();
141141
// The V2 client treats null value here as empty, do the same to avoid incompatibility
142142
String jsonEncryptionContext = metadata.getOrDefault(MetadataKeyConstants.ENCRYPTED_DATA_KEY_CONTEXT, "{}");
143143
// When the encryption context contains non-US-ASCII characters,
@@ -149,7 +149,7 @@ private ContentMetadata readFromMap(Map<String, String> metadata, GetObjectRespo
149149
JsonNode objectNode = parser.parse(decodedJsonEncryptionContext);
150150

151151
for (Map.Entry<String, JsonNode> entry : objectNode.asObject().entrySet()) {
152-
encryptionContext.put(entry.getKey(), entry.getValue().asString());
152+
encryptionContextOrMatDesc.put(entry.getKey(), entry.getValue().asString());
153153
}
154154
} catch (Exception e) {
155155
throw new RuntimeException(e);
@@ -161,7 +161,7 @@ private ContentMetadata readFromMap(Map<String, String> metadata, GetObjectRespo
161161
return ContentMetadata.builder()
162162
.algorithmSuite(algorithmSuite)
163163
.encryptedDataKey(edk)
164-
.encryptedDataKeyContext(encryptionContext)
164+
.encryptionContextOrMatDesc(encryptionContextOrMatDesc)
165165
.contentIv(iv)
166166
.contentRange(contentRange)
167167
.build();

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,16 @@ private Map<String, String> addMetadataToMap(Map<String, String> map, Encryption
8080

8181
try (JsonWriter jsonWriter = JsonWriter.create()) {
8282
jsonWriter.writeStartObject();
83-
for (Map.Entry<String, String> entry : materials.encryptionContext().entrySet()) {
84-
jsonWriter.writeFieldName(entry.getKey()).writeValue(entry.getValue());
83+
if (!materials.encryptionContext().isEmpty() && materials.materialsDescription().isEmpty()) {
84+
for (Map.Entry<String, String> entry : materials.encryptionContext().entrySet()) {
85+
jsonWriter.writeFieldName(entry.getKey()).writeValue(entry.getValue());
86+
}
87+
} else if (materials.encryptionContext().isEmpty() && !materials.materialsDescription().isEmpty()) {
88+
for (Map.Entry<String, String> entry : materials.materialsDescription().entrySet()) {
89+
jsonWriter.writeFieldName(entry.getKey()).writeValue(entry.getValue());
90+
}
8591
}
8692
jsonWriter.writeEndObject();
87-
8893
String jsonEncryptionContext = new String(jsonWriter.getBytes(), StandardCharsets.UTF_8);
8994
metadata.put(MetadataKeyConstants.ENCRYPTED_DATA_KEY_CONTEXT, jsonEncryptionContext);
9095
} catch (JsonWriter.JsonGenerationException e) {

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* This keyring can wrap keys with the active keywrap algorithm and
2121
* unwrap with the active and legacy algorithms for AES keys.
2222
*/
23-
public class AesKeyring extends S3Keyring {
23+
public class AesKeyring extends RawKeyring {
2424

2525
private static final String KEY_ALGORITHM = "AES";
2626

@@ -88,6 +88,11 @@ public boolean isLegacy() {
8888
return false;
8989
}
9090

91+
@Override
92+
public EncryptionMaterials modifyMaterials(EncryptionMaterials materials) {
93+
return modifyMaterialsForRawKeyring(materials);
94+
}
95+
9196
@Override
9297
public String keyProviderInfo() {
9398
return KEY_PROVIDER_INFO;
@@ -98,13 +103,6 @@ public EncryptionMaterials generateDataKey(EncryptionMaterials materials) {
98103
return defaultGenerateDataKey(materials);
99104
}
100105

101-
@Override
102-
public EncryptionMaterials modifyMaterials(EncryptionMaterials materials) {
103-
warnIfEncryptionContextIsPresent(materials);
104-
105-
return materials;
106-
}
107-
108106
@Override
109107
public byte[] encryptDataKey(SecureRandom secureRandom,
110108
EncryptionMaterials materials)
@@ -177,7 +175,7 @@ protected Map<String, DecryptDataKeyStrategy> decryptDataKeyStrategies() {
177175
return decryptDataKeyStrategies;
178176
}
179177

180-
public static class Builder extends S3Keyring.Builder<AesKeyring, Builder> {
178+
public static class Builder extends RawKeyring.Builder<AesKeyring, Builder> {
181179
private SecretKey _wrappingKey;
182180

183181
private Builder() {
@@ -199,7 +197,6 @@ public Builder wrappingKey(final SecretKey wrappingKey) {
199197
_wrappingKey = wrappingKey;
200198
return builder();
201199
}
202-
203200
public AesKeyring build() {
204201
return new AesKeyring(this);
205202
}

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

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

55
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
66
import software.amazon.awssdk.services.s3.model.S3Request;
7+
import software.amazon.encryption.s3.S3EncryptionClientException;
78
import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
89
import software.amazon.encryption.s3.internal.CipherMode;
910
import software.amazon.encryption.s3.internal.CipherProvider;
@@ -27,6 +28,7 @@ final public class EncryptionMaterials implements CryptographicMaterials {
2728
// Additional information passed into encrypted that is required on decryption as well
2829
// Should NOT contain sensitive information
2930
private final Map<String, String> _encryptionContext;
31+
private final MaterialsDescription _materialsDescription;
3032

3133
private final List<EncryptedDataKey> _encryptedDataKeys;
3234
private final byte[] _plaintextDataKey;
@@ -43,6 +45,7 @@ private EncryptionMaterials(Builder builder) {
4345
this._cryptoProvider = builder._cryptoProvider;
4446
this._plaintextLength = builder._plaintextLength;
4547
this._ciphertextLength = _plaintextLength + _algorithmSuite.cipherTagLengthBytes();
48+
this._materialsDescription = builder._materialsDescription;
4649
}
4750

4851
static public Builder builder() {
@@ -101,6 +104,9 @@ public Provider cryptoProvider() {
101104
return _cryptoProvider;
102105
}
103106

107+
public MaterialsDescription materialsDescription() {
108+
return _materialsDescription;
109+
}
104110
@Override
105111
public CipherMode cipherMode() {
106112
return CipherMode.ENCRYPT;
@@ -119,6 +125,7 @@ public Builder toBuilder() {
119125
.encryptedDataKeys(_encryptedDataKeys)
120126
.plaintextDataKey(_plaintextDataKey)
121127
.cryptoProvider(_cryptoProvider)
128+
.materialsDescription(_materialsDescription)
122129
.plaintextLength(_plaintextLength);
123130
}
124131

@@ -132,6 +139,7 @@ static public class Builder {
132139
private byte[] _plaintextDataKey = null;
133140
private long _plaintextLength = -1;
134141
private Provider _cryptoProvider = null;
142+
private MaterialsDescription _materialsDescription = MaterialsDescription.builder().build();
135143

136144
private Builder() {
137145
}
@@ -145,7 +153,12 @@ public Builder algorithmSuite(AlgorithmSuite algorithmSuite) {
145153
_algorithmSuite = algorithmSuite;
146154
return this;
147155
}
148-
156+
public Builder materialsDescription(MaterialsDescription materialsDescription) {
157+
_materialsDescription = materialsDescription == null
158+
? MaterialsDescription.builder().build()
159+
: materialsDescription;
160+
return this;
161+
}
149162
public Builder encryptionContext(Map<String, String> encryptionContext) {
150163
_encryptionContext = encryptionContext == null
151164
? Collections.emptyMap()
@@ -175,6 +188,9 @@ public Builder plaintextLength(long plaintextLength) {
175188
}
176189

177190
public EncryptionMaterials build() {
191+
if (!_materialsDescription.isEmpty() && !_encryptionContext.isEmpty()) {
192+
throw new S3EncryptionClientException("MaterialsDescription and EncryptionContext cannot both be set!");
193+
}
178194
return new EncryptionMaterials(this);
179195
}
180196
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.amazon.encryption.s3.materials;
5+
6+
import java.util.Collection;
7+
import java.util.Collections;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.Set;
11+
12+
/**
13+
* This class is used to provide key-value pairs that describe the key material used with the Keyring, specifically for AES and RSA Keyrings.
14+
* This will be useful during the re-encryption of instruction file.
15+
* The stored Materials Description is immutable once created.
16+
*/
17+
public class MaterialsDescription implements Map<String, String> {
18+
private final Map<String, String> materialsDescription;
19+
20+
private MaterialsDescription(Builder builder) {
21+
this.materialsDescription = Collections.unmodifiableMap(new HashMap<>(builder.materialsDescription));
22+
}
23+
public static Builder builder() {
24+
return new Builder();
25+
}
26+
public Map<String, String> getMaterialsDescription() {
27+
return this.materialsDescription;
28+
}
29+
30+
@Override
31+
public int size() {
32+
return materialsDescription.size();
33+
}
34+
35+
@Override
36+
public boolean isEmpty() {
37+
return materialsDescription.isEmpty();
38+
}
39+
40+
@Override
41+
public boolean containsKey(Object key) {
42+
return materialsDescription.containsKey(key);
43+
}
44+
45+
@Override
46+
public boolean containsValue(Object value) {
47+
return materialsDescription.containsValue(value);
48+
}
49+
50+
@Override
51+
public String get(Object key) {
52+
return materialsDescription.get(key);
53+
}
54+
55+
@Override
56+
public String put(String key, String value) {
57+
throw new UnsupportedOperationException("This map is immutable");
58+
}
59+
60+
@Override
61+
public String remove(Object key) {
62+
return materialsDescription.remove(key);
63+
}
64+
65+
@Override
66+
public void putAll(Map<? extends String, ? extends String> m) {
67+
throw new UnsupportedOperationException("This map is immutable");
68+
}
69+
70+
@Override
71+
public void clear() {
72+
materialsDescription.clear();
73+
}
74+
75+
@Override
76+
public Set<String> keySet() {
77+
return materialsDescription.keySet();
78+
}
79+
80+
@Override
81+
public Collection<String> values() {
82+
return materialsDescription.values();
83+
}
84+
85+
@Override
86+
public Set<Entry<String, String>> entrySet() {
87+
return materialsDescription.entrySet();
88+
}
89+
90+
91+
public static class Builder {
92+
private final Map<String, String> materialsDescription = new HashMap<>();
93+
public Builder put(String key, String value) {
94+
if (key == null || value == null) {
95+
throw new IllegalArgumentException("Key and value must not be null");
96+
}
97+
materialsDescription.put(key, value);
98+
return this;
99+
}
100+
public Builder putAll(Map<String, String> description) {
101+
if (description == null) {
102+
throw new IllegalArgumentException("Description must not be null");
103+
}
104+
materialsDescription.putAll(description);
105+
return this;
106+
}
107+
public MaterialsDescription build() {
108+
return new MaterialsDescription(this);
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)