Skip to content

Commit 57e33e0

Browse files
committed
Use AES key per stream
Switched to use AES key for each stream which brings key auto-rotation Signed-off-by: Andrey Pleskach <[email protected]>
1 parent 9825df9 commit 57e33e0

File tree

6 files changed

+60
-95
lines changed

6 files changed

+60
-95
lines changed

src/main/java/org/opensearch/repository/encrypted/EncryptedRepository.java

Lines changed: 3 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,10 @@
55

66
package org.opensearch.repository.encrypted;
77

8-
import org.apache.logging.log4j.LogManager;
9-
import org.apache.logging.log4j.Logger;
108
import org.opensearch.cluster.metadata.RepositoryMetadata;
119
import org.opensearch.cluster.service.ClusterService;
12-
import org.opensearch.common.blobstore.BlobContainer;
1310
import org.opensearch.common.blobstore.BlobPath;
1411
import org.opensearch.common.blobstore.BlobStore;
15-
import org.opensearch.core.common.bytes.BytesArray;
1612
import org.opensearch.common.cache.Cache;
1713
import org.opensearch.common.cache.CacheBuilder;
1814
import org.opensearch.common.settings.Setting;
@@ -21,27 +17,19 @@
2117
import org.opensearch.core.common.unit.ByteSizeValue;
2218
import org.opensearch.core.xcontent.NamedXContentRegistry;
2319
import org.opensearch.indices.recovery.RecoverySettings;
24-
import org.opensearch.repositories.RepositoryException;
2520
import org.opensearch.repositories.RepositoryStats;
2621
import org.opensearch.repositories.blobstore.BlobStoreRepository;
2722
import org.opensearch.repository.encrypted.security.CryptoIO;
2823
import org.opensearch.repository.encrypted.security.EncryptionData;
29-
import org.opensearch.repository.encrypted.security.EncryptionDataGenerator;
3024
import org.opensearch.repository.encrypted.security.EncryptionDataSerializer;
3125

32-
import java.io.IOException;
33-
import java.io.InputStream;
3426
import java.security.Provider;
3527
import java.util.Locale;
3628

3729
public class EncryptedRepository extends BlobStoreRepository {
3830

39-
private static final Logger LOGGER = LogManager.getLogger(EncryptedRepository.class);
40-
4131
public static final String REPOSITORY_TYPE = "encrypted";
4232

43-
public static final String METADATA_FILE_NAME = ".repository_metadata";
44-
4533
public static final Setting<String> CLIENT_SETTING = Setting.simpleString("client", "default");
4634

4735
public static final Setting<Boolean> COMPRESS_SETTING = Setting.boolSetting("compress", true);
@@ -58,8 +46,6 @@ public class EncryptedRepository extends BlobStoreRepository {
5846

5947
private final Cache<String, EncryptionData> encryptionDataCache;
6048

61-
private final EncryptionDataGenerator encryptionDataGenerator;
62-
6349
private final Provider securityProvider;
6450

6551
public EncryptedRepository(final RepositoryMetadata metadata,
@@ -82,7 +68,6 @@ public EncryptedRepository(final RepositoryMetadata metadata,
8268
this.blobStorageRepositoryType = blobStorageRepositoryType;
8369
this.blobStorageRepository = blobStorageRepository;
8470
this.encryptionDataCache = encryptionDataCache;
85-
this.encryptionDataGenerator = new EncryptionDataGenerator(securityProvider);
8671
this.securityProvider = securityProvider;
8772
}
8873

@@ -124,38 +109,13 @@ protected void doClose() {
124109
@Override
125110
protected BlobStore createBlobStore() throws Exception {
126111
return new EncryptedBlobStore(blobStorageRepository.blobStore(),
127-
new CryptoIO(encryptionDataCache.computeIfAbsent(settingsKey(metadata.settings()),
128-
this::createOrRestoreEncryptionData), securityProvider));
112+
new CryptoIO(new EncryptionDataSerializer(
113+
encryptedRepositorySettings.rsaKeyPair(settingsKey(metadata.settings())), securityProvider),
114+
securityProvider));
129115
}
130116

131117
private String settingsKey(final Settings settings) {
132118
return String.format(Locale.getDefault(), "%s-%s", blobStorageRepositoryType, CLIENT_SETTING.get(settings));
133119
}
134120

135-
private EncryptionData createOrRestoreEncryptionData(final String clientName) throws IOException {
136-
final BlobStore blobStore = blobStorageRepository.blobStore();
137-
final BlobContainer blobContainer = blobStore.blobContainer(basePath());
138-
final EncryptionData encryptionData;
139-
final EncryptionDataSerializer encryptionDataSerializer = new EncryptionDataSerializer(
140-
encryptedRepositorySettings.rsaKeyPair(clientName), securityProvider);
141-
if (blobContainer.blobExists(METADATA_FILE_NAME)) {
142-
LOGGER.info("Restore encryption data");
143-
try (InputStream in = blobContainer.readBlob(METADATA_FILE_NAME)) {
144-
encryptionData = encryptionDataSerializer.deserialize(in.readAllBytes());
145-
}
146-
} else {
147-
LOGGER.info("Create encryption data");
148-
if (isReadOnly()) {
149-
throw new RepositoryException(REPOSITORY_TYPE,
150-
"Couldn't create encryption data. The repository " + metadata.name() + " is in readonly mode");
151-
}
152-
encryptionData = encryptionDataGenerator.generate();
153-
final byte[] bytes = encryptionDataSerializer.serialize(encryptionData);
154-
try (InputStream in = new BytesArray(bytes).streamInput()) {
155-
blobContainer.writeBlobAtomic(METADATA_FILE_NAME, in, bytes.length, true);
156-
}
157-
}
158-
return encryptionData;
159-
}
160-
161121
}

src/main/java/org/opensearch/repository/encrypted/security/CryptoIO.java

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@
99

1010
import javax.crypto.Cipher;
1111
import javax.crypto.CipherInputStream;
12-
import javax.crypto.SecretKey;
1312
import javax.crypto.spec.GCMParameterSpec;
1413
import java.io.BufferedInputStream;
1514
import java.io.ByteArrayInputStream;
1615
import java.io.IOException;
1716
import java.io.InputStream;
1817
import java.io.SequenceInputStream;
1918
import java.security.Provider;
20-
import java.security.SecureRandom;
2119

2220
public class CryptoIO implements Encryptor, Decryptor {
2321

@@ -27,50 +25,47 @@ public class CryptoIO implements Encryptor, Decryptor {
2725

2826
public static final int GCM_ENCRYPTED_BLOCK_LENGTH = 128;
2927

30-
public static final int GCM_IV_LENGTH = 12;
31-
3228
public static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding";
3329

34-
private final SecretKey secretKey;
35-
36-
private final byte[] aad;
30+
private final Provider securityProvider;
3731

38-
private final SecureRandom secureRandom;
32+
private final EncryptionDataGenerator encryptionDataGenerator;
3933

40-
private final Provider securityProvider;
34+
private final EncryptionDataSerializer encryptionDataSerializer;
4135

42-
public CryptoIO(final EncryptionData encryptionData, final Provider securityProvider) {
43-
this.secretKey = encryptionData.encryptionKey();
44-
this.aad = encryptionData.aad();
45-
this.secureRandom = new SecureRandom();
36+
public CryptoIO(final EncryptionDataSerializer encryptionDataSerializer, final Provider securityProvider) {
37+
this.encryptionDataSerializer = encryptionDataSerializer;
38+
this.encryptionDataGenerator = new EncryptionDataGenerator(securityProvider);
4639
this.securityProvider = securityProvider;
4740
}
4841

4942
public InputStream encrypt(final InputStream in) throws IOException {
5043
return Permissions.doPrivileged(() -> {
51-
final byte[] iv = new byte[GCM_IV_LENGTH];
52-
secureRandom.nextBytes(iv);
53-
final Cipher cipher = createEncryptingCipher(secretKey,
54-
new GCMParameterSpec(GCM_ENCRYPTED_BLOCK_LENGTH, iv), CIPHER_TRANSFORMATION, securityProvider);
55-
cipher.updateAAD(aad);
56-
return new BufferedInputStream(
57-
new SequenceInputStream(new ByteArrayInputStream(iv), new CipherInputStream(in, cipher)),
58-
BUFFER_SIZE);
44+
final EncryptionData encryptionData = encryptionDataGenerator.generate();
45+
final Cipher cipher = createEncryptingCipher(encryptionData.encryptionKey(),
46+
new GCMParameterSpec(GCM_ENCRYPTED_BLOCK_LENGTH, encryptionData.iv()), CIPHER_TRANSFORMATION,
47+
securityProvider);
48+
cipher.updateAAD(encryptionData.aad());
49+
return new BufferedInputStream(new SequenceInputStream(
50+
new ByteArrayInputStream(encryptionDataSerializer.serialize(encryptionData)),
51+
new CipherInputStream(in, cipher)), BUFFER_SIZE);
5952
});
6053
}
6154

6255
public InputStream decrypt(final InputStream in) throws IOException {
6356
return Permissions.doPrivileged(() -> {
64-
final Cipher cipher = createDecryptingCipher(secretKey,
65-
new GCMParameterSpec(GCM_ENCRYPTED_BLOCK_LENGTH, in.readNBytes(GCM_IV_LENGTH)),
66-
CIPHER_TRANSFORMATION, securityProvider);
67-
cipher.updateAAD(aad);
57+
final EncryptionData encryptionData = encryptionDataSerializer
58+
.deserialize(in.readNBytes(EncryptionDataSerializer.ENC_DATA_SIZE));
59+
final Cipher cipher = createDecryptingCipher(encryptionData.encryptionKey(),
60+
new GCMParameterSpec(GCM_ENCRYPTED_BLOCK_LENGTH, encryptionData.iv()), CIPHER_TRANSFORMATION,
61+
securityProvider);
62+
cipher.updateAAD(encryptionData.aad());
6863
return new BufferedInputStream(new CipherInputStream(in, cipher), BUFFER_SIZE);
6964
});
7065
}
7166

7267
public long encryptedStreamSize(final long originSize) {
73-
return originSize + GCM_TAG_LENGTH + GCM_IV_LENGTH;
68+
return originSize + GCM_TAG_LENGTH + EncryptionDataSerializer.ENC_DATA_SIZE;
7469
}
7570

7671
}

src/main/java/org/opensearch/repository/encrypted/security/EncryptionData.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ public final class EncryptionData {
1515

1616
private final byte[] aad;
1717

18-
public EncryptionData(final SecretKey encryptionKey, final byte[] aad) {
18+
private final byte[] iv;
19+
20+
public EncryptionData(final SecretKey encryptionKey, final byte[] aad, final byte[] iv) {
1921
this.encryptionKey = encryptionKey;
2022
this.aad = aad;
23+
this.iv = iv;
2124
}
2225

2326
public SecretKey encryptionKey() {
@@ -28,20 +31,26 @@ public byte[] aad() {
2831
return aad;
2932
}
3033

34+
public byte[] iv() {
35+
return iv;
36+
}
37+
3138
@Override
3239
public boolean equals(Object o) {
3340
if (this == o)
3441
return true;
3542
if (o == null || getClass() != o.getClass())
3643
return false;
37-
final EncryptionData that = (EncryptionData) o;
38-
return Objects.equals(encryptionKey, that.encryptionKey) && Arrays.equals(aad, that.aad);
44+
EncryptionData that = (EncryptionData) o;
45+
return Objects.equals(encryptionKey, that.encryptionKey) && Arrays.equals(aad, that.aad)
46+
&& Arrays.equals(iv, that.iv);
3947
}
4048

4149
@Override
4250
public int hashCode() {
4351
int result = Objects.hash(encryptionKey);
4452
result = 31 * result + Arrays.hashCode(aad);
53+
result = 31 * result + Arrays.hashCode(iv);
4554
return result;
4655
}
4756
}

src/main/java/org/opensearch/repository/encrypted/security/EncryptionDataGenerator.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ public final class EncryptionDataGenerator {
1919

2020
private static final int AAD_SIZE = 32;
2121

22+
public static final int GCM_IV_LENGTH = 12;
23+
2224
private final KeyGenerator aesKeyGenerator;
2325

24-
private final SecureRandom random;
26+
private final SecureRandom random = new SecureRandom();
2527

2628
public EncryptionDataGenerator(final Provider securityProvider) {
27-
this.random = new SecureRandom();
2829
try {
2930
this.aesKeyGenerator = Permissions.doPrivileged(() -> {
3031
try {
@@ -43,7 +44,9 @@ public EncryptionDataGenerator(final Provider securityProvider) {
4344
public EncryptionData generate() {
4445
final byte[] aad = new byte[AAD_SIZE];
4546
random.nextBytes(aad);
46-
return new EncryptionData(aesKeyGenerator.generateKey(), aad);
47+
final byte[] iv = new byte[GCM_IV_LENGTH];
48+
random.nextBytes(iv);
49+
return new EncryptionData(aesKeyGenerator.generateKey(), aad, iv);
4750
}
4851

4952
}

src/main/java/org/opensearch/repository/encrypted/security/EncryptionDataSerializer.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ public class EncryptionDataSerializer implements Encryptor, Decryptor {
3838

3939
public static final int ENCRYPTED_AAD_SIZE = 256;
4040

41-
public static final int ENC_DATA_SIZE = ENCRYPTED_KEY_SIZE + ENCRYPTED_AAD_SIZE + SIGNATURE_SIZE + Integer.BYTES;
41+
public static final int ENC_DATA_SIZE = EncryptionDataGenerator.GCM_IV_LENGTH + ENCRYPTED_KEY_SIZE
42+
+ ENCRYPTED_AAD_SIZE + SIGNATURE_SIZE + Integer.BYTES;
4243

4344
private final Provider securityProvider;
4445

@@ -55,11 +56,13 @@ public byte[] serialize(final EncryptionData encryptionData) throws IOException
5556
}
5657
final byte[] key = encryptionData.encryptionKey().getEncoded();
5758
final byte[] aad = encryptionData.aad();
58-
final byte[] signature = sign(ByteBuffer.allocate(key.length + aad.length).put(key).put(aad).array());
59+
final byte[] iv = encryptionData.iv();
60+
final byte[] signature = sign(
61+
ByteBuffer.allocate(key.length + aad.length + iv.length).put(key).put(aad).put(iv).array());
5962
final byte[] encryptedKey = encrypt(key, "Couldn't encrypt " + KEY_ALGORITHM + " key");
6063
final byte[] encryptedAad = encrypt(aad, "Couldn't encrypt AAD");
61-
return ByteBuffer.allocate(ENC_DATA_SIZE).put(encryptedKey).put(encryptedAad).put(signature).putInt(VERSION)
62-
.array();
64+
return ByteBuffer.allocate(ENC_DATA_SIZE).put(iv).put(encryptedKey).put(encryptedAad).put(signature)
65+
.putInt(VERSION).array();
6366
});
6467
}
6568

@@ -69,15 +72,17 @@ public EncryptionData deserialize(final byte[] metadata) throws IOException {
6972
final byte[] encryptedKey = new byte[256];
7073
final byte[] encryptedAad = new byte[256];
7174
final byte[] signature = new byte[256];
75+
final byte[] iv = new byte[EncryptionDataGenerator.GCM_IV_LENGTH];
76+
buffer.get(iv);
7277
buffer.get(encryptedKey);
7378
buffer.get(encryptedAad);
7479
buffer.get(signature);
7580
buffer.getInt(); // skip version
7681
final byte[] decryptedKey = decrypt(encryptedKey, "Couldn't decrypt " + KEY_ALGORITHM + " key");
7782
final byte[] decryptedAdd = decrypt(encryptedAad, "Couldn't decrypt AAD");
78-
verifySignature(signature, ByteBuffer.allocate(decryptedKey.length + decryptedAdd.length).put(decryptedKey)
79-
.put(decryptedAdd).array());
80-
return new EncryptionData(new SecretKeySpec(decryptedKey, KEY_ALGORITHM), decryptedAdd);
83+
verifySignature(signature, ByteBuffer.allocate(decryptedKey.length + decryptedAdd.length + iv.length)
84+
.put(decryptedKey).put(decryptedAdd).put(iv).array());
85+
return new EncryptionData(new SecretKeySpec(decryptedKey, KEY_ALGORITHM), decryptedAdd, iv);
8186
});
8287
}
8388

src/test/java/org/opensearch/repository/encrypted/security/CryptoIOTests.java

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,22 @@
66
package org.opensearch.repository.encrypted.security;
77

88
import org.bouncycastle.jce.provider.BouncyCastleProvider;
9-
import org.junit.BeforeClass;
10-
import org.opensearch.test.OpenSearchTestCase;
9+
import org.opensearch.repository.encrypted.RsaKeyAwareTest;
1110

1211
import java.io.ByteArrayInputStream;
1312
import java.io.IOException;
1413
import java.io.InputStream;
1514
import java.security.Provider;
16-
import java.security.Security;
1715

18-
public class CryptoIOTests extends OpenSearchTestCase {
16+
public class CryptoIOTests extends RsaKeyAwareTest {
1917

2018
private static final int MAX_BYES_SIZE = 18_192;
2119

2220
private final Provider securityProvider = new BouncyCastleProvider();
2321

24-
private final EncryptionData encData = new EncryptionDataGenerator(securityProvider).generate();
25-
26-
@BeforeClass
27-
static void setupProvider() {
28-
Security.addProvider(new BouncyCastleProvider());
29-
}
30-
3122
public void testEncryptAndDecrypt() throws IOException {
32-
final CryptoIO cryptoIo = new CryptoIO(encData, securityProvider);
23+
final CryptoIO cryptoIo = new CryptoIO(new EncryptionDataSerializer(rsaKeyPair, securityProvider),
24+
securityProvider);
3325
final byte[] sequence = randomByteArrayOfLength(randomInt(MAX_BYES_SIZE));
3426

3527
try (InputStream encIn = cryptoIo.encrypt(new ByteArrayInputStream(sequence))) {
@@ -42,7 +34,8 @@ public void testEncryptAndDecrypt() throws IOException {
4234
}
4335

4436
public void testEncryptedStreamSize() throws IOException {
45-
final CryptoIO cryptoIo = new CryptoIO(encData, securityProvider);
37+
final CryptoIO cryptoIo = new CryptoIO(new EncryptionDataSerializer(rsaKeyPair, securityProvider),
38+
securityProvider);
4639
final byte[] sequence = randomByteArrayOfLength(randomInt(MAX_BYES_SIZE));
4740

4841
try (InputStream encIn = cryptoIo.encrypt(new ByteArrayInputStream(sequence))) {

0 commit comments

Comments
 (0)