Skip to content

Commit 69ada50

Browse files
committed
Add a key manager interface, capable of providing encryption keys.
Default implementation is unchanged.
1 parent 86b67a4 commit 69ada50

File tree

2 files changed

+158
-31
lines changed

2 files changed

+158
-31
lines changed

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/common/TransformedRecordSerializerJCE.java

Lines changed: 95 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,48 +31,43 @@
3131
import java.security.GeneralSecurityException;
3232
import java.security.Key;
3333
import java.security.SecureRandom;
34+
import java.util.Random;
3435

3536
/**
3637
* An extension of {@link TransformedRecordSerializer} to use JCE to encrypt and decrypt records.
3738
* @param <M> type of {@link Message} that underlying records will use
3839
*/
3940
@API(API.Status.UNSTABLE)
4041
public class TransformedRecordSerializerJCE<M extends Message> extends TransformedRecordSerializer<M> {
41-
42-
@Nullable
43-
protected final String cipherName;
44-
@Nullable
45-
protected final Key encryptionKey;
4642
@Nullable
47-
protected final SecureRandom secureRandom;
43+
protected final TransformedRecordSerializerKeyManager keyManager;
4844

4945
protected TransformedRecordSerializerJCE(@Nonnull RecordSerializer<M> inner,
5046
boolean compressWhenSerializing,
5147
int compressionLevel,
5248
boolean encryptWhenSerializing,
5349
double writeValidationRatio,
54-
@Nullable String cipherName,
55-
@Nullable Key encryptionKey,
56-
@Nullable SecureRandom secureRandom) {
50+
@Nullable TransformedRecordSerializerKeyManager keyManager) {
5751
super(inner, compressWhenSerializing, compressionLevel, encryptWhenSerializing, writeValidationRatio);
58-
this.cipherName = cipherName;
59-
this.encryptionKey = encryptionKey;
60-
this.secureRandom = secureRandom;
52+
this.keyManager = keyManager;
6153
}
6254

6355
@Override
6456
protected void encrypt(@Nonnull TransformedRecordSerializerState state, @Nullable StoreTimer timer) throws GeneralSecurityException {
65-
if (cipherName == null || encryptionKey == null || secureRandom == null) {
66-
throw new RecordSerializationException("attempted to encrypt without setting cipher name and key");
57+
if (keyManager == null) {
58+
throw new RecordSerializationException("attempted to encrypt without setting key manager (cipher name and key)");
6759
}
6860
long startTime = System.nanoTime();
6961

62+
int keyNumber = keyManager.getSerializationKey();
63+
state.keyNumber = keyNumber;
64+
7065
byte[] ivData = new byte[CipherPool.IV_SIZE];
71-
secureRandom.nextBytes(ivData);
66+
keyManager.getRandom(keyNumber).nextBytes(ivData);
7267
IvParameterSpec iv = new IvParameterSpec(ivData);
73-
Cipher cipher = CipherPool.borrowCipher(cipherName);
68+
Cipher cipher = CipherPool.borrowCipher(keyManager.getCipher(keyNumber));
7469
try {
75-
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, iv);
70+
cipher.init(Cipher.ENCRYPT_MODE, keyManager.getKey(keyNumber), iv);
7671

7772
byte[] plainText = state.getDataArray();
7873
byte[] cipherText = cipher.doFinal(plainText);
@@ -93,7 +88,7 @@ protected void encrypt(@Nonnull TransformedRecordSerializerState state, @Nullabl
9388

9489
@Override
9590
protected void decrypt(@Nonnull TransformedRecordSerializerState state, @Nullable StoreTimer timer) throws GeneralSecurityException {
96-
if (cipherName == null || encryptionKey == null || secureRandom == null) {
91+
if (keyManager == null) {
9792
throw new RecordSerializationException("missing encryption key or provider during decryption");
9893
}
9994
long startTime = System.nanoTime();
@@ -104,9 +99,9 @@ protected void decrypt(@Nonnull TransformedRecordSerializerState state, @Nullabl
10499

105100
byte[] cipherText = new byte[state.length - CipherPool.IV_SIZE];
106101
System.arraycopy(state.data, state.offset + CipherPool.IV_SIZE, cipherText, 0, cipherText.length);
107-
Cipher cipher = CipherPool.borrowCipher(cipherName);
102+
Cipher cipher = CipherPool.borrowCipher(keyManager.getCipher(state.keyNumber));
108103
try {
109-
cipher.init(Cipher.DECRYPT_MODE, encryptionKey, iv);
104+
cipher.init(Cipher.DECRYPT_MODE, keyManager.getKey(state.keyNumber), iv);
110105

111106
byte[] plainText = cipher.doFinal(cipherText);
112107
state.setDataArray(plainText);
@@ -155,6 +150,8 @@ public static <M extends Message> Builder<M> newBuilder(@Nonnull RecordSerialize
155150
* @param <M> type of {@link Message} that underlying records will use
156151
*/
157152
public static class Builder<M extends Message> extends TransformedRecordSerializer.Builder<M> {
153+
@Nullable
154+
protected TransformedRecordSerializerKeyManager keyManager;
158155
@Nullable
159156
protected String cipherName;
160157
@Nullable
@@ -272,6 +269,26 @@ public Builder<M> clearSecureRandom() {
272269
return this;
273270
}
274271

272+
/**
273+
* Sets the key manager used during cryptographic operations.
274+
* @param keyManager key manager to use for encrypting and decrypting
275+
* @return this <code>Builder</code>
276+
*/
277+
public Builder<M> setKeyManager(@Nonnull TransformedRecordSerializerKeyManager keyManager) {
278+
this.keyManager = keyManager;
279+
return this;
280+
}
281+
282+
/**
283+
* Clears a previously set key manager
284+
* that might have been passed to this <code>Builder</code>.
285+
* @return this <code>Builder</code>
286+
*/
287+
public Builder<M> clearKeyManager() {
288+
this.keyManager = null;
289+
return this;
290+
}
291+
275292
/**
276293
* Construct a {@link TransformedRecordSerializerJCE} from the
277294
* parameters specified by this builder. If one has enabled
@@ -282,17 +299,18 @@ public Builder<M> clearSecureRandom() {
282299
*/
283300
@Override
284301
public TransformedRecordSerializerJCE<M> build() {
285-
if (encryptWhenSerializing) {
286-
if (encryptionKey == null) {
302+
if (keyManager == null) {
303+
if (encryptionKey != null) {
304+
keyManager = new FixedZeroKeyManager(encryptionKey, cipherName, secureRandom);
305+
} else if (encryptWhenSerializing) {
287306
throw new RecordCoreArgumentException("cannot encrypt when serializing if encryption key is not set");
288307
}
289-
}
290-
if (encryptionKey != null) {
291-
if (cipherName == null) {
292-
cipherName = CipherPool.DEFAULT_CIPHER;
308+
} else {
309+
if (encryptionKey != null) {
310+
throw new RecordCoreArgumentException("cannot specify both key manager and encryption key");
293311
}
294-
if (secureRandom == null) {
295-
secureRandom = new SecureRandom();
312+
if (cipherName != null) {
313+
throw new RecordCoreArgumentException("cannot specify both key manager and cipher name");
296314
}
297315
}
298316
return new TransformedRecordSerializerJCE<>(
@@ -301,10 +319,56 @@ public TransformedRecordSerializerJCE<M> build() {
301319
compressionLevel,
302320
encryptWhenSerializing,
303321
writeValidationRatio,
304-
cipherName,
305-
encryptionKey,
306-
secureRandom
322+
keyManager
307323
);
308324
}
325+
326+
}
327+
328+
static class FixedZeroKeyManager implements TransformedRecordSerializerKeyManager {
329+
private final Key encryptionKey;
330+
private final String cipherName;
331+
private final SecureRandom secureRandom;
332+
333+
public FixedZeroKeyManager(@Nonnull Key encryptionKey, @Nullable String cipherName, @Nullable SecureRandom secureRandom) {
334+
if (cipherName == null) {
335+
cipherName = CipherPool.DEFAULT_CIPHER;
336+
}
337+
if (secureRandom == null) {
338+
secureRandom = new SecureRandom();
339+
}
340+
this.encryptionKey = encryptionKey;
341+
this.cipherName = cipherName;
342+
this.secureRandom = secureRandom;
343+
}
344+
345+
@Override
346+
public int getSerializationKey() {
347+
return 0;
348+
}
349+
350+
@Override
351+
public Key getKey(int keyNumber) {
352+
if (keyNumber != 0) {
353+
throw new RecordSerializationException("only provide key number 0");
354+
}
355+
return encryptionKey;
356+
}
357+
358+
@Override
359+
public String getCipher(int keyNumber) {
360+
if (keyNumber != 0) {
361+
throw new RecordSerializationException("only provide key number 0");
362+
}
363+
return cipherName;
364+
}
365+
366+
@Override
367+
public Random getRandom(int keyNumber) {
368+
if (keyNumber != 0) {
369+
throw new RecordSerializationException("only provide key number 0");
370+
}
371+
return secureRandom;
372+
}
309373
}
310374
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* TransformedRecordSerializerKeyManager.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2018 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.provider.common;
22+
23+
import com.apple.foundationdb.annotation.API;
24+
25+
import java.security.Key;
26+
import java.util.Random;
27+
28+
/**
29+
* An interface between {@link TransformedRecordSerializerJCE} and a source of keys with associated cipher algorithms.
30+
* Each key is identified by a unique <em>key number</em>, which is persisted in serialized records so that the key
31+
* can be recovered at deserialization time.
32+
*/
33+
@API(API.Status.EXPERIMENTAL)
34+
public interface TransformedRecordSerializerKeyManager {
35+
/**
36+
* Get the key number to be used for <em>serializing</em> a record.
37+
* Typically, this would be the <em>latest</em> key.
38+
* @return the key number to use
39+
*/
40+
int getSerializationKey();
41+
42+
/**
43+
* Get the key with the given key number.
44+
* @param keyNumber the unique key identifier
45+
* @return the cipher used with this key
46+
*/
47+
Key getKey(int keyNumber);
48+
49+
/**
50+
* Get the name of the cipher used with the given key number.
51+
* @param keyNumber the unique key identifier
52+
* @return the cipher used with this key
53+
*/
54+
String getCipher(int keyNumber);
55+
56+
/**
57+
* Get a random generator to fill IVs when encrypting.
58+
* Normally this would be a {@link java.security.SecureRandom} and would not depend on the key.
59+
*/
60+
// TODO: Perhaps it would be better to have the KM give out an IvParameterSpec or something?
61+
// Maybe wait until we have another algorithm that's different enough.
62+
Random getRandom(int keyNumber);
63+
}

0 commit comments

Comments
 (0)