From 4d72a29b639b524f2946c65ead94a8099af7eb27 Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Sat, 9 Jul 2016 15:25:08 -0700 Subject: [PATCH] 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@fe009071a8ceccd891105cb35be25420fbad0923 --- .../xpack/security/Security.java | 1 - .../xpack/security/crypto/CryptoService.java | 18 - .../crypto/InternalCryptoService.java | 163 ++------- .../crypto/InternalCryptoServiceTests.java | 330 ++---------------- 4 files changed, 58 insertions(+), 454 deletions(-) diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 16d613c9ad7..d9c7a821fb0 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -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; diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/crypto/CryptoService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/crypto/CryptoService.java index cbc4796eb9d..746edd21760 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/crypto/CryptoService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/crypto/CryptoService.java @@ -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); - } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/crypto/InternalCryptoService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/crypto/InternalCryptoService.java index 08155f3b34e..a081fbb76f2 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/crypto/InternalCryptoService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/crypto/InternalCryptoService.java @@ -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 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 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.emptyList()); - } - - InternalCryptoService(Settings settings, Environment env, ResourceWatcherService watcherService, List 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 listeners; - - private FileListener(List 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) diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/crypto/InternalCryptoServiceTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/crypto/InternalCryptoServiceTests.java index 8101e202a35..eb0f6196c25 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/crypto/InternalCryptoServiceTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/crypto/InternalCryptoServiceTests.java @@ -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,10 +131,9 @@ public class InternalCryptoServiceTests extends ESTestCase { } } - public void testEncryptionAndDecryptionChars() { - InternalCryptoService service = new InternalCryptoService(settings, env, watcherService); - service.start(); - assertThat(service.encryptionEnabled(), is(true)); + 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); assertThat(encrypted, notNullValue()); @@ -175,10 +143,9 @@ public class InternalCryptoServiceTests extends ESTestCase { assertThat(Arrays.equals(chars, decrypted), is(true)); } - public void testEncryptionAndDecryptionBytes() { - InternalCryptoService service = new InternalCryptoService(settings, env, watcherService); - service.start(); - assertThat(service.encryptionEnabled(), is(true)); + 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); assertThat(encrypted, notNullValue()); @@ -188,10 +155,9 @@ 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(); - assertThat(service.encryptionEnabled(), is(false)); + 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); final char[] decryptedChars = service.decrypt(encryptedChars); @@ -199,10 +165,9 @@ public class InternalCryptoServiceTests extends ESTestCase { assertThat(chars, equalTo(decryptedChars)); } - public void testEncryptionAndDecryptionBytesWithoutKey() { - InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env, watcherService); - service.start(); - assertThat(service.encryptionEnabled(), is(false)); + 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); final byte[] decryptedBytes = service.decrypt(bytes); @@ -210,22 +175,19 @@ public class InternalCryptoServiceTests extends ESTestCase { assertThat(decryptedBytes, equalTo(encryptedBytes)); } - public void testEncryptionEnabledWithKey() { - InternalCryptoService service = new InternalCryptoService(settings, env, watcherService); - service.start(); - assertThat(service.encryptionEnabled(), is(true)); + 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(); - assertThat(service.encryptionEnabled(), is(false)); + 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(); - assertThat(service.encryptionEnabled(), is(true)); + 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); final byte[] encrypted = service.encrypt(bytes); @@ -243,10 +205,9 @@ public class InternalCryptoServiceTests extends ESTestCase { assertThat(Arrays.equals(bytes, decrypted), is(false)); } - public void testEncryptedChar() { - InternalCryptoService service = new InternalCryptoService(settings, env, watcherService); - service.start(); - assertThat(service.encryptionEnabled(), is(true)); + public void testEncryptedChar() throws Exception { + InternalCryptoService service = new InternalCryptoService(settings, env); + assertThat(service.encryptionEnabled(), is(true)); assertThat(service.encrypted((char[]) null), is(false)); assertThat(service.encrypted(new char[0]), is(false)); @@ -256,10 +217,9 @@ 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(); - assertThat(service.encryptionEnabled(), is(true)); + public void testEncryptedByte() throws Exception { + InternalCryptoService service = new InternalCryptoService(settings, env); + assertThat(service.encryptionEnabled(), is(true)); assertThat(service.encrypted((byte[]) null), is(false)); assertThat(service.encrypted(new byte[0]), 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);