Skip to content

Commit 113b784

Browse files
authored
Merge pull request #3 from smswz/main
Add RSA Keyring.
2 parents be394d1 + b681bd9 commit 113b784

File tree

3 files changed

+178
-10
lines changed

3 files changed

+178
-10
lines changed

src/main/java/software/amazon/encryption/s3/materials/AESKeyring.java renamed to src/main/java/software/amazon/encryption/s3/materials/AesGcmKeyring.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
/**
1414
* AESKeyring will use an AES key to wrap the data key used to encrypt content.
1515
*/
16-
public class AESKeyring implements Keyring {
16+
public class AesGcmKeyring implements Keyring {
1717

1818
private static final String KEY_ALGORITHM = "AES";
1919
private static final String KEY_PROVIDER_ID = "AES/GCM";
@@ -26,7 +26,7 @@ public class AESKeyring implements Keyring {
2626
private final SecureRandom _secureRandom;
2727
private final DataKeyGenerator _dataKeyGenerator;
2828

29-
private AESKeyring(Builder builder) {
29+
private AesGcmKeyring(Builder builder) {
3030
_wrappingKey = builder._wrappingKey;
3131
_secureRandom = builder._secureRandom;
3232
_dataKeyGenerator = builder._dataKeyGenerator;
@@ -81,7 +81,7 @@ public EncryptionMaterials onEncrypt(EncryptionMaterials materials) {
8181
@Override
8282
public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List<EncryptedDataKey> encryptedDataKeys) {
8383
if (materials.plaintextDataKey() != null) {
84-
return materials;
84+
throw new S3EncryptionClientException("Decryption materials already contains a plaintext data key.");
8585
}
8686

8787
for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) {
@@ -139,8 +139,8 @@ public Builder dataKeyGenerator(DataKeyGenerator dataKeyGenerator) {
139139
return this;
140140
}
141141

142-
public AESKeyring build() {
143-
return new AESKeyring(this);
142+
public AesGcmKeyring build() {
143+
return new AesGcmKeyring(this);
144144
}
145145
}
146146
}

src/main/java/software/amazon/encryption/s3/materials/KMSContextKeyring.java renamed to src/main/java/software/amazon/encryption/s3/materials/KmsContextKeyring.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
/**
1818
* AESKeyring will call to KMS to wrap the data key used to encrypt content.
1919
*/
20-
public class KMSContextKeyring implements Keyring {
20+
public class KmsContextKeyring implements Keyring {
2121

2222
private static final String KEY_PROVIDER_ID = "kms+context";
2323

@@ -29,7 +29,7 @@ public class KMSContextKeyring implements Keyring {
2929
private final String _wrappingKeyId;
3030
private final DataKeyGenerator _dataKeyGenerator;
3131

32-
public KMSContextKeyring(Builder builder) {
32+
public KmsContextKeyring(Builder builder) {
3333
_kmsClient = builder._kmsClient;
3434
_wrappingKeyId = builder._wrappingKeyId;
3535
_dataKeyGenerator = builder._dataKeyGenerator;
@@ -86,7 +86,7 @@ public EncryptionMaterials onEncrypt(EncryptionMaterials materials) {
8686
@Override
8787
public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List<EncryptedDataKey> encryptedDataKeys) {
8888
if (materials.plaintextDataKey() != null) {
89-
return materials;
89+
throw new S3EncryptionClientException("Decryption materials already contains a plaintext data key.");
9090
}
9191

9292
for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) {
@@ -135,8 +135,8 @@ public Builder dataKeyGenerator(DataKeyGenerator dataKeyGenerator) {
135135
return this;
136136
}
137137

138-
public KMSContextKeyring build() {
139-
return new KMSContextKeyring(this);
138+
public KmsContextKeyring build() {
139+
return new KmsContextKeyring(this);
140140
}
141141
}
142142
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package software.amazon.encryption.s3.materials;
2+
3+
import java.nio.charset.StandardCharsets;
4+
import java.security.Key;
5+
import java.security.KeyPair;
6+
import java.security.SecureRandom;
7+
import java.security.spec.MGF1ParameterSpec;
8+
import java.util.ArrayList;
9+
import java.util.Arrays;
10+
import java.util.List;
11+
import javax.crypto.Cipher;
12+
import javax.crypto.SecretKey;
13+
import javax.crypto.spec.OAEPParameterSpec;
14+
import javax.crypto.spec.PSource.PSpecified;
15+
import javax.crypto.spec.SecretKeySpec;
16+
import software.amazon.encryption.s3.S3EncryptionClientException;
17+
18+
/**
19+
* RsaOaepKeyring will use an RSA public key to wrap the data key used to encrypt content.
20+
*/
21+
public class RsaOaepKeyring implements Keyring {
22+
23+
private static final String KEY_ALGORITHM = "RSA";
24+
private static final String KEY_PROVIDER_ID = "RSA-OAEP-SHA1";
25+
private static final String CIPHER_ALGORITHM = "RSA/ECB/OAEPPadding";
26+
private static final String DIGEST_NAME = "SHA-1";
27+
private static final String MGF_NAME = "MGF1";
28+
private static final MGF1ParameterSpec MGF_PARAMETER_SPEC = new MGF1ParameterSpec(DIGEST_NAME);
29+
private static final OAEPParameterSpec OAEP_PARAMETER_SPEC =
30+
new OAEPParameterSpec(DIGEST_NAME, MGF_NAME, MGF_PARAMETER_SPEC, PSpecified.DEFAULT);
31+
32+
private final KeyPair _wrappingKeyPair;
33+
private final SecureRandom _secureRandom;
34+
private final DataKeyGenerator _dataKeyGenerator;
35+
36+
private RsaOaepKeyring(Builder builder) {
37+
_wrappingKeyPair = builder._wrappingKeyPair;
38+
_secureRandom = builder._secureRandom;
39+
_dataKeyGenerator = builder._dataKeyGenerator;
40+
}
41+
42+
public static Builder builder() {
43+
return new Builder();
44+
}
45+
46+
@Override
47+
public EncryptionMaterials onEncrypt(EncryptionMaterials materials) {
48+
if (materials.plaintextDataKey() == null) {
49+
SecretKey dataKey = _dataKeyGenerator.generateDataKey(materials.algorithmSuite());
50+
materials = materials.toBuilder()
51+
.plaintextDataKey(dataKey.getEncoded())
52+
.build();
53+
}
54+
55+
try {
56+
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
57+
cipher.init(Cipher.WRAP_MODE, _wrappingKeyPair.getPublic(), OAEP_PARAMETER_SPEC, _secureRandom);
58+
59+
// Create a pseudo-data key with the content encryption appended to the data key
60+
byte[] dataKey = materials.plaintextDataKey();
61+
byte[] dataCipherName = materials.algorithmSuite().cipherName().getBytes(StandardCharsets.UTF_8);
62+
byte[] pseudoDataKey = new byte[1 + dataKey.length + dataCipherName.length];
63+
64+
pseudoDataKey[0] = (byte)dataKey.length;
65+
System.arraycopy(dataKey, 0, pseudoDataKey, 1, dataKey.length);
66+
System.arraycopy(dataCipherName, 0, pseudoDataKey, 1 + dataKey.length, dataCipherName.length);
67+
68+
byte[] ciphertext = cipher.wrap(new SecretKeySpec(pseudoDataKey, materials.algorithmSuite().dataKeyAlgorithm()));
69+
70+
EncryptedDataKey encryptedDataKey = EncryptedDataKey.builder()
71+
.keyProviderId(KEY_PROVIDER_ID)
72+
.ciphertext(ciphertext)
73+
.build();
74+
75+
List<EncryptedDataKey> encryptedDataKeys = new ArrayList<>(materials.encryptedDataKeys());
76+
encryptedDataKeys.add(encryptedDataKey);
77+
78+
return materials.toBuilder()
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+
throw new S3EncryptionClientException("Decryption materials already contains a plaintext data key.");
90+
}
91+
92+
for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) {
93+
if (!encryptedDataKey.keyProviderId().equals(KEY_PROVIDER_ID)) {
94+
continue;
95+
}
96+
97+
try {
98+
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
99+
cipher.init(Cipher.UNWRAP_MODE, _wrappingKeyPair.getPrivate(), OAEP_PARAMETER_SPEC, _secureRandom);
100+
101+
String dataKeyAlgorithm = materials.algorithmSuite().dataKeyAlgorithm();
102+
Key pseudoDataKey = cipher.unwrap(encryptedDataKey.ciphertext(), dataKeyAlgorithm, Cipher.SECRET_KEY);
103+
104+
byte[] plaintext = parsePseudoDataKey(materials, pseudoDataKey.getEncoded());
105+
106+
return materials.toBuilder().plaintextDataKey(plaintext).build();
107+
} catch (Exception e) {
108+
throw new S3EncryptionClientException("Unable to " + KEY_PROVIDER_ID + " unwrap", e);
109+
}
110+
}
111+
112+
return materials;
113+
}
114+
115+
private byte[] parsePseudoDataKey(DecryptionMaterials materials, byte[] pseudoDataKey) {
116+
int dataKeyLengthBytes = pseudoDataKey[0];
117+
if (!(dataKeyLengthBytes == 16 || dataKeyLengthBytes == 24 || dataKeyLengthBytes == 32)) {
118+
throw new S3EncryptionClientException("Invalid key length (" + dataKeyLengthBytes + ") in encrypted data key");
119+
}
120+
121+
int dataCipherNameLength = pseudoDataKey.length - dataKeyLengthBytes - 1;
122+
if (dataCipherNameLength <= 0) {
123+
throw new S3EncryptionClientException("Invalid data cipher name length (" + dataCipherNameLength + ") in encrypted data key");
124+
}
125+
126+
byte[] dataKey = new byte[dataKeyLengthBytes];
127+
byte[] dataCipherName = new byte[dataCipherNameLength];
128+
System.arraycopy(pseudoDataKey, 1, dataKey, 0, dataKeyLengthBytes);
129+
System.arraycopy(pseudoDataKey, 1 + dataKeyLengthBytes, dataCipherName, 0, dataCipherNameLength);
130+
131+
byte[] expectedDataCipherName = materials.algorithmSuite().cipherName().getBytes(StandardCharsets.UTF_8);
132+
if (!Arrays.equals(expectedDataCipherName, dataCipherName)) {
133+
throw new S3EncryptionClientException("The data cipher does not match the data cipher used for encryption. The object may be altered or corrupted");
134+
}
135+
136+
return dataKey;
137+
}
138+
139+
public static class Builder {
140+
private KeyPair _wrappingKeyPair;
141+
private SecureRandom _secureRandom = new SecureRandom();
142+
private DataKeyGenerator _dataKeyGenerator = new DefaultDataKeyGenerator();
143+
144+
private Builder() {}
145+
146+
public Builder wrappingKeyPair(KeyPair wrappingKeyPair) {
147+
if (!wrappingKeyPair.getPublic().getAlgorithm().equals(KEY_ALGORITHM)) {
148+
throw new S3EncryptionClientException("Invalid algorithm '" + wrappingKeyPair.getPublic().getAlgorithm() + "', expecting " + KEY_ALGORITHM);
149+
}
150+
_wrappingKeyPair = wrappingKeyPair;
151+
return this;
152+
}
153+
154+
public Builder secureRandom(SecureRandom secureRandom) {
155+
_secureRandom = secureRandom;
156+
return this;
157+
}
158+
159+
public Builder dataKeyGenerator(DataKeyGenerator dataKeyGenerator) {
160+
_dataKeyGenerator = dataKeyGenerator;
161+
return this;
162+
}
163+
164+
public RsaOaepKeyring build() {
165+
return new RsaOaepKeyring(this);
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)