Security: Load system key at startup only
This change removes the polling xpack did for changes to the system key. closes elastic/elasticsearch#2768 Original commit: elastic/x-pack-elasticsearch@fe009071a8
This commit is contained in:
parent
58058792a7
commit
4d72a29b63
|
@ -175,7 +175,6 @@ public class Security implements ActionPlugin {
|
|||
list.add(LoggingAuditTrail.class);
|
||||
}
|
||||
list.add(SecurityLicensee.class);
|
||||
list.add(InternalCryptoService.class);
|
||||
list.add(FileRolesStore.class);
|
||||
list.add(Realms.class);
|
||||
return list;
|
||||
|
|
|
@ -106,27 +106,9 @@ public interface CryptoService {
|
|||
*/
|
||||
boolean encrypted(byte[] bytes);
|
||||
|
||||
/**
|
||||
* Registers a listener to be notified of key changes
|
||||
* @param listener the listener to be notified
|
||||
*/
|
||||
void register(Listener listener);
|
||||
|
||||
/**
|
||||
* Flag for callers to determine if values will actually be encrypted or returned plaintext
|
||||
* @return true if values will be encrypted
|
||||
*/
|
||||
boolean encryptionEnabled();
|
||||
|
||||
interface Listener {
|
||||
/**
|
||||
* This method will be called immediately after a new system key and encryption key are loaded by the
|
||||
* service. This provides the old keys back to the clients so that they may perform decryption and re-encryption
|
||||
* of data after a key has been changed
|
||||
*
|
||||
* @param oldSystemKey the pre-existing system key
|
||||
* @param oldEncryptionKey the pre-existing encryption key
|
||||
*/
|
||||
void onKeyChange(SecretKey oldSystemKey, SecretKey oldEncryptionKey);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,20 +5,6 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.crypto;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Setting.Property;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.watcher.FileChangesListener;
|
||||
import org.elasticsearch.watcher.FileWatcher;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.xpack.security.authc.support.CharArrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
|
@ -27,7 +13,6 @@ import javax.crypto.Mac;
|
|||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -39,16 +24,25 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Setting.Property;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.xpack.security.authc.support.CharArrays;
|
||||
|
||||
import static org.elasticsearch.xpack.security.Security.setting;
|
||||
import static org.elasticsearch.xpack.security.authc.support.SecuredString.constantTimeEquals;
|
||||
|
||||
public class InternalCryptoService extends AbstractLifecycleComponent implements CryptoService {
|
||||
public class InternalCryptoService extends AbstractComponent implements CryptoService {
|
||||
|
||||
public static final String KEY_ALGO = "HmacSHA512";
|
||||
public static final int KEY_SIZE = 1024;
|
||||
|
@ -73,65 +67,33 @@ public class InternalCryptoService extends AbstractLifecycleComponent implements
|
|||
public static final Setting<String> ENCRYPTION_KEY_ALGO_SETTING =
|
||||
new Setting<>(setting("encryption_key.algorithm"), DEFAULT_KEY_ALGORITH, s -> s, Property.NodeScope);
|
||||
|
||||
private final Environment env;
|
||||
private final ResourceWatcherService watcherService;
|
||||
private final List<Listener> listeners;
|
||||
private final SecureRandom secureRandom = new SecureRandom();
|
||||
private final String encryptionAlgorithm;
|
||||
private final String keyAlgorithm;
|
||||
private final int keyLength;
|
||||
private final int ivLength;
|
||||
|
||||
private Path keyFile;
|
||||
private final Path keyFile;
|
||||
|
||||
private SecretKey randomKey;
|
||||
private String randomKeyBase64;
|
||||
private final SecretKey randomKey;
|
||||
private final String randomKeyBase64;
|
||||
|
||||
private volatile SecretKey encryptionKey;
|
||||
private volatile SecretKey systemKey;
|
||||
private volatile SecretKey signingKey;
|
||||
private final SecretKey encryptionKey;
|
||||
private final SecretKey systemKey;
|
||||
private final SecretKey signingKey;
|
||||
|
||||
@Inject
|
||||
public InternalCryptoService(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
||||
this(settings, env, watcherService, Collections.<Listener>emptyList());
|
||||
}
|
||||
|
||||
InternalCryptoService(Settings settings, Environment env, ResourceWatcherService watcherService, List<Listener> listeners) {
|
||||
public InternalCryptoService(Settings settings, Environment env) throws Exception {
|
||||
super(settings);
|
||||
this.env = env;
|
||||
this.watcherService = watcherService;
|
||||
this.listeners = new CopyOnWriteArrayList<>(listeners);
|
||||
this.encryptionAlgorithm = ENCRYPTION_ALGO_SETTING.get(settings);
|
||||
this.keyLength = ENCRYPTION_KEY_LENGTH_SETTING.get(settings);
|
||||
this.ivLength = keyLength / 8;
|
||||
this.keyAlgorithm = ENCRYPTION_KEY_ALGO_SETTING.get(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws ElasticsearchException {
|
||||
if (keyLength % 8 != 0) {
|
||||
throw new IllegalArgumentException("invalid key length [" + keyLength + "]. value must be a multiple of 8");
|
||||
}
|
||||
|
||||
loadKeys();
|
||||
FileWatcher watcher = new FileWatcher(keyFile.getParent());
|
||||
watcher.addListener(new FileListener(listeners));
|
||||
try {
|
||||
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("failed to start watching system key file [" + keyFile.toAbsolutePath() + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() throws ElasticsearchException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() throws ElasticsearchException {
|
||||
}
|
||||
|
||||
private void loadKeys() {
|
||||
keyFile = resolveSystemKey(settings, env);
|
||||
systemKey = readSystemKey(keyFile);
|
||||
randomKey = generateSecretKey(RANDOM_KEY_SIZE);
|
||||
|
@ -144,6 +106,7 @@ public class InternalCryptoService extends AbstractLifecycleComponent implements
|
|||
} catch (NoSuchAlgorithmException nsae) {
|
||||
throw new ElasticsearchException("failed to start crypto service. could not load encryption key", nsae);
|
||||
}
|
||||
logger.info("system key [{}] has been loaded", keyFile.toAbsolutePath());
|
||||
}
|
||||
|
||||
public static byte[] generateKey() {
|
||||
|
@ -366,11 +329,6 @@ public class InternalCryptoService extends AbstractLifecycleComponent implements
|
|||
return bytesBeginsWith(ENCRYPTED_BYTE_PREFIX, bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(Listener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean encryptionEnabled() {
|
||||
return this.encryptionKey != null;
|
||||
|
@ -477,87 +435,6 @@ public class InternalCryptoService extends AbstractLifecycleComponent implements
|
|||
return true;
|
||||
}
|
||||
|
||||
private class FileListener extends FileChangesListener {
|
||||
|
||||
private final List<Listener> listeners;
|
||||
|
||||
private FileListener(List<Listener> listeners) {
|
||||
this.listeners = listeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileCreated(Path file) {
|
||||
if (file.equals(keyFile)) {
|
||||
final SecretKey oldSystemKey = systemKey;
|
||||
final SecretKey oldEncryptionKey = encryptionKey;
|
||||
|
||||
systemKey = readSystemKey(file);
|
||||
signingKey = createSigningKey(systemKey, randomKey);
|
||||
try {
|
||||
encryptionKey = encryptionKey(signingKey, keyLength, keyAlgorithm);
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
logger.error("could not load encryption key", nsae);
|
||||
encryptionKey = null;
|
||||
}
|
||||
logger.info("system key [{}] has been loaded", file.toAbsolutePath());
|
||||
callListeners(oldSystemKey, oldEncryptionKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileDeleted(Path file) {
|
||||
if (file.equals(keyFile)) {
|
||||
final SecretKey oldSystemKey = systemKey;
|
||||
final SecretKey oldEncryptionKey = encryptionKey;
|
||||
logger.error("system key file was removed! as long as the system key file is missing, elasticsearch " +
|
||||
"won't function as expected for some requests (e.g. scroll/scan)");
|
||||
systemKey = null;
|
||||
encryptionKey = null;
|
||||
signingKey = createSigningKey(systemKey, randomKey);
|
||||
|
||||
callListeners(oldSystemKey, oldEncryptionKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileChanged(Path file) {
|
||||
if (file.equals(keyFile)) {
|
||||
final SecretKey oldSystemKey = systemKey;
|
||||
final SecretKey oldEncryptionKey = encryptionKey;
|
||||
|
||||
logger.warn("system key file changed!");
|
||||
SecretKey systemKey = readSystemKey(file);
|
||||
signingKey = createSigningKey(systemKey, randomKey);
|
||||
try {
|
||||
encryptionKey = encryptionKey(signingKey, keyLength, keyAlgorithm);
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
logger.error("could not load encryption key", nsae);
|
||||
encryptionKey = null;
|
||||
}
|
||||
|
||||
callListeners(oldSystemKey, oldEncryptionKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void callListeners(SecretKey oldSystemKey, SecretKey oldEncryptionKey) {
|
||||
RuntimeException ex = null;
|
||||
for (Listener listener : listeners) {
|
||||
try {
|
||||
listener.onKeyChange(oldSystemKey, oldEncryptionKey);
|
||||
} catch (Exception e) {
|
||||
if (ex == null) ex = new RuntimeException("exception calling key change listeners");
|
||||
ex.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
|
||||
// all listeners were notified now rethrow
|
||||
if (ex != null) {
|
||||
logger.error("called all key change listeners but one or more exceptions was thrown", ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider class for the HmacSHA1 {@link Mac} that provides an optimization by using a thread local instead of calling
|
||||
* Mac#getInstance and obtaining a lock (in the internals)
|
||||
|
|
|
@ -5,28 +5,17 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.crypto;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.io.Streams;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.TestThreadPool;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Before;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
@ -34,15 +23,10 @@ import static org.hamcrest.Matchers.not;
|
|||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class InternalCryptoServiceTests extends ESTestCase {
|
||||
private ResourceWatcherService watcherService;
|
||||
private Settings settings;
|
||||
private Environment env;
|
||||
private Path keyFile;
|
||||
private ThreadPool threadPool;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
|
@ -54,30 +38,19 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
env = new Environment(settings);
|
||||
threadPool = new TestThreadPool("test");
|
||||
watcherService = new ResourceWatcherService(settings, threadPool);
|
||||
watcherService.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdown() throws InterruptedException {
|
||||
watcherService.stop();
|
||||
terminate(threadPool);
|
||||
}
|
||||
|
||||
public void testSigned() throws Exception {
|
||||
// randomize whether to use a system key or not
|
||||
Settings settings = randomBoolean() ? this.settings : Settings.EMPTY;
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
String text = randomAsciiOfLength(10);
|
||||
String signed = service.sign(text);
|
||||
assertThat(service.signed(signed), is(true));
|
||||
}
|
||||
|
||||
public void testSignAndUnsign() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
String text = randomAsciiOfLength(10);
|
||||
String signed = service.sign(text);
|
||||
assertThat(text.equals(signed), is(false));
|
||||
|
@ -86,8 +59,7 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testSignAndUnsignNoKeyFile() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env, watcherService);
|
||||
service.start();
|
||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env);
|
||||
final String text = randomAsciiOfLength(10);
|
||||
String signed = service.sign(text);
|
||||
// we always have some sort of key to sign with
|
||||
|
@ -97,8 +69,7 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testTamperedSignature() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
String text = randomAsciiOfLength(10);
|
||||
String signed = service.sign(text);
|
||||
int i = signed.indexOf("$$", 2);
|
||||
|
@ -115,8 +86,7 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testTamperedSignatureOneChar() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
String text = randomAsciiOfLength(10);
|
||||
String signed = service.sign(text);
|
||||
int i = signed.indexOf("$$", 2);
|
||||
|
@ -135,8 +105,7 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testTamperedSignatureLength() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
String text = randomAsciiOfLength(10);
|
||||
String signed = service.sign(text);
|
||||
int i = signed.indexOf("$$", 2);
|
||||
|
@ -162,9 +131,8 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testEncryptionAndDecryptionChars() {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
public void testEncryptionAndDecryptionChars() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
assertThat(service.encryptionEnabled(), is(true));
|
||||
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
|
||||
final char[] encrypted = service.encrypt(chars);
|
||||
|
@ -175,9 +143,8 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
assertThat(Arrays.equals(chars, decrypted), is(true));
|
||||
}
|
||||
|
||||
public void testEncryptionAndDecryptionBytes() {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
public void testEncryptionAndDecryptionBytes() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
assertThat(service.encryptionEnabled(), is(true));
|
||||
final byte[] bytes = randomByteArray();
|
||||
final byte[] encrypted = service.encrypt(bytes);
|
||||
|
@ -188,9 +155,8 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
assertThat(Arrays.equals(bytes, decrypted), is(true));
|
||||
}
|
||||
|
||||
public void testEncryptionAndDecryptionCharsWithoutKey() {
|
||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env, watcherService);
|
||||
service.start();
|
||||
public void testEncryptionAndDecryptionCharsWithoutKey() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env);
|
||||
assertThat(service.encryptionEnabled(), is(false));
|
||||
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
|
||||
final char[] encryptedChars = service.encrypt(chars);
|
||||
|
@ -199,9 +165,8 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
assertThat(chars, equalTo(decryptedChars));
|
||||
}
|
||||
|
||||
public void testEncryptionAndDecryptionBytesWithoutKey() {
|
||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env, watcherService);
|
||||
service.start();
|
||||
public void testEncryptionAndDecryptionBytesWithoutKey() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env);
|
||||
assertThat(service.encryptionEnabled(), is(false));
|
||||
final byte[] bytes = randomByteArray();
|
||||
final byte[] encryptedBytes = service.encrypt(bytes);
|
||||
|
@ -210,21 +175,18 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
assertThat(decryptedBytes, equalTo(encryptedBytes));
|
||||
}
|
||||
|
||||
public void testEncryptionEnabledWithKey() {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
public void testEncryptionEnabledWithKey() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
assertThat(service.encryptionEnabled(), is(true));
|
||||
}
|
||||
|
||||
public void testEncryptionEnabledWithoutKey() {
|
||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env, watcherService);
|
||||
service.start();
|
||||
public void testEncryptionEnabledWithoutKey() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env);
|
||||
assertThat(service.encryptionEnabled(), is(false));
|
||||
}
|
||||
|
||||
public void testChangingAByte() {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
public void testChangingAByte() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
assertThat(service.encryptionEnabled(), is(true));
|
||||
// We need at least one byte to test changing a byte, otherwise output is always the same
|
||||
final byte[] bytes = randomByteArray(1);
|
||||
|
@ -243,9 +205,8 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
assertThat(Arrays.equals(bytes, decrypted), is(false));
|
||||
}
|
||||
|
||||
public void testEncryptedChar() {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
public void testEncryptedChar() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
assertThat(service.encryptionEnabled(), is(true));
|
||||
|
||||
assertThat(service.encrypted((char[]) null), is(false));
|
||||
|
@ -256,9 +217,8 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
assertThat(service.encrypted(service.encrypt(randomAsciiOfLength(10).toCharArray())), is(true));
|
||||
}
|
||||
|
||||
public void testEncryptedByte() {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
public void testEncryptedByte() throws Exception {
|
||||
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||
assertThat(service.encryptionEnabled(), is(true));
|
||||
|
||||
assertThat(service.encrypted((byte[]) null), is(false));
|
||||
|
@ -269,220 +229,6 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
|||
assertThat(service.encrypted(service.encrypt(randomAsciiOfLength(10).getBytes(StandardCharsets.UTF_8))), is(true));
|
||||
}
|
||||
|
||||
public void testReloadKey() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final CryptoService.Listener listener = new CryptoService.Listener() {
|
||||
@Override
|
||||
public void onKeyChange(SecretKey oldSystemKey, SecretKey oldEncryptionKey) {
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
|
||||
// randomize how we set the listener
|
||||
InternalCryptoService service;
|
||||
if (randomBoolean()) {
|
||||
service = new InternalCryptoService(settings, env, watcherService, Collections.singletonList(listener));
|
||||
service.start();
|
||||
} else {
|
||||
service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
service.register(listener);
|
||||
}
|
||||
|
||||
String text = randomAsciiOfLength(10);
|
||||
String signed = service.sign(text);
|
||||
char[] textChars = text.toCharArray();
|
||||
char[] encrypted = service.encrypt(textChars);
|
||||
|
||||
// we need to sleep to ensure the timestamp of the file will definitely change
|
||||
// and so the resource watcher will pick up the change.
|
||||
Thread.sleep(1000L);
|
||||
|
||||
try (OutputStream os = Files.newOutputStream(keyFile)) {
|
||||
Streams.copy(InternalCryptoService.generateKey(), os);
|
||||
}
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for test to complete. Expected callback is not called");
|
||||
}
|
||||
String signed2 = service.sign(text);
|
||||
assertThat(signed.equals(signed2), is(false));
|
||||
|
||||
char[] encrypted2 = service.encrypt(textChars);
|
||||
|
||||
char[] decrypted = service.decrypt(encrypted);
|
||||
char[] decrypted2 = service.decrypt(encrypted2);
|
||||
assertThat(Arrays.equals(textChars, decrypted), is(false));
|
||||
assertThat(Arrays.equals(textChars, decrypted2), is(true));
|
||||
}
|
||||
|
||||
public void testReencryptValuesOnKeyChange() throws Exception {
|
||||
final InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
assertThat(service.encryptionEnabled(), is(true));
|
||||
final char[] text = randomAsciiOfLength(10).toCharArray();
|
||||
final char[] encrypted = service.encrypt(text);
|
||||
assertThat(text, not(equalTo(encrypted)));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
service.register(new CryptoService.Listener() {
|
||||
@Override
|
||||
public void onKeyChange(SecretKey oldSystemKey, SecretKey oldEncryptionKey) {
|
||||
final char[] plainText = service.decrypt(encrypted, oldEncryptionKey);
|
||||
assertThat(plainText, equalTo(text));
|
||||
final char[] newEncrypted = service.encrypt(plainText);
|
||||
assertThat(newEncrypted, not(equalTo(encrypted)));
|
||||
assertThat(newEncrypted, not(equalTo(plainText)));
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
// we need to sleep to ensure the timestamp of the file will definitely change
|
||||
// and so the resource watcher will pick up the change.
|
||||
Thread.sleep(1000);
|
||||
|
||||
Files.write(keyFile, InternalCryptoService.generateKey());
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for test to complete. Expected callback is not called or finished running");
|
||||
}
|
||||
}
|
||||
|
||||
public void testResignValuesOnKeyChange() throws Exception {
|
||||
final InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
final String text = randomAsciiOfLength(10);
|
||||
final String signed = service.sign(text);
|
||||
assertThat(text, not(equalTo(signed)));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
service.register(new CryptoService.Listener() {
|
||||
@Override
|
||||
public void onKeyChange(SecretKey oldSystemKey, SecretKey oldEncryptionKey) {
|
||||
try {
|
||||
assertThat(oldSystemKey, notNullValue());
|
||||
final String unsigned = service.unsignAndVerify(signed, oldSystemKey);
|
||||
assertThat(unsigned, equalTo(text));
|
||||
final String newSigned = service.sign(unsigned);
|
||||
assertThat(newSigned, not(equalTo(signed)));
|
||||
assertThat(newSigned, not(equalTo(text)));
|
||||
latch.countDown();
|
||||
} catch (IOException e) {
|
||||
logger.error("caught exception in key change listener", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// we need to sleep to ensure the timestamp of the file will definitely change
|
||||
// and so the resource watcher will pick up the change.
|
||||
Thread.sleep(1000);
|
||||
|
||||
Files.write(keyFile, InternalCryptoService.generateKey());
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for test to complete. Expected callback is not called or finished running");
|
||||
}
|
||||
}
|
||||
|
||||
public void testReencryptValuesOnKeyDeleted() throws Exception {
|
||||
final InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
assertThat(service.encryptionEnabled(), is(true));
|
||||
final char[] text = randomAsciiOfLength(10).toCharArray();
|
||||
final char[] encrypted = service.encrypt(text);
|
||||
assertThat(text, not(equalTo(encrypted)));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
service.register(new CryptoService.Listener() {
|
||||
@Override
|
||||
public void onKeyChange(SecretKey oldSystemKey, SecretKey oldEncryptionKey) {
|
||||
final char[] plainText = service.decrypt(encrypted, oldEncryptionKey);
|
||||
assertThat(plainText, equalTo(text));
|
||||
final char[] newEncrypted = service.encrypt(plainText);
|
||||
assertThat(newEncrypted, not(equalTo(encrypted)));
|
||||
assertThat(newEncrypted, equalTo(plainText));
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
// we need to sleep to ensure the timestamp of the file will definitely change
|
||||
// and so the resource watcher will pick up the change.
|
||||
Thread.sleep(1000);
|
||||
|
||||
Files.delete(keyFile);
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for test to complete. Expected callback is not called or finished running");
|
||||
}
|
||||
}
|
||||
|
||||
public void testAllListenersCalledWhenExceptionThrown() throws Exception {
|
||||
final InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
assertThat(service.encryptionEnabled(), is(true));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(3);
|
||||
service.register(new CryptoService.Listener() {
|
||||
@Override
|
||||
public void onKeyChange(SecretKey oldSystemKey, SecretKey oldEncryptionKey) {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
service.register(new CryptoService.Listener() {
|
||||
@Override
|
||||
public void onKeyChange(SecretKey oldSystemKey, SecretKey oldEncryptionKey) {
|
||||
latch.countDown();
|
||||
throw new RuntimeException("misbehaving listener");
|
||||
}
|
||||
});
|
||||
service.register(new CryptoService.Listener() {
|
||||
@Override
|
||||
public void onKeyChange(SecretKey oldSystemKey, SecretKey oldEncryptionKey) {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
// we need to sleep to ensure the timestamp of the file will definitely change
|
||||
// and so the resource watcher will pick up the change.
|
||||
Thread.sleep(1000);
|
||||
|
||||
Files.write(keyFile, InternalCryptoService.generateKey());
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for test to complete. Expected callback is not called or finished running");
|
||||
}
|
||||
}
|
||||
|
||||
public void testSigningOnKeyDeleted() throws Exception {
|
||||
final InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
||||
service.start();
|
||||
final String text = randomAsciiOfLength(10);
|
||||
final String signed = service.sign(text);
|
||||
assertThat(text, not(equalTo(signed)));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
service.register(new CryptoService.Listener() {
|
||||
@Override
|
||||
public void onKeyChange(SecretKey oldSystemKey, SecretKey oldEncryptionKey) {
|
||||
final String plainText = service.unsignAndVerify(signed, oldSystemKey);
|
||||
assertThat(plainText, equalTo(text));
|
||||
try {
|
||||
final String newSigned = service.sign(plainText);
|
||||
assertThat(newSigned, not(equalTo(signed)));
|
||||
assertThat(newSigned, not(equalTo(plainText)));
|
||||
assertThat(service.unsignAndVerify(newSigned), equalTo(plainText));
|
||||
latch.countDown();
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("unexpected exception while signing", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// we need to sleep to ensure the timestamp of the file will definitely change
|
||||
// and so the resource watcher will pick up the change.
|
||||
Thread.sleep(1000);
|
||||
|
||||
Files.delete(keyFile);
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for test to complete. Expected callback is not called or finished running");
|
||||
}
|
||||
}
|
||||
|
||||
public void testSigningKeyCanBeRecomputedConsistently() {
|
||||
final SecretKey systemKey = new SecretKeySpec(InternalCryptoService.generateKey(), InternalCryptoService.KEY_ALGO);
|
||||
final SecretKey randomKey = InternalCryptoService.generateSecretKey(InternalCryptoService.RANDOM_KEY_SIZE);
|
||||
|
|
Loading…
Reference in New Issue