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:
Ryan Ernst 2016-07-09 15:25:08 -07:00
parent 58058792a7
commit 4d72a29b63
4 changed files with 58 additions and 454 deletions

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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);