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(LoggingAuditTrail.class);
|
||||||
}
|
}
|
||||||
list.add(SecurityLicensee.class);
|
list.add(SecurityLicensee.class);
|
||||||
list.add(InternalCryptoService.class);
|
|
||||||
list.add(FileRolesStore.class);
|
list.add(FileRolesStore.class);
|
||||||
list.add(Realms.class);
|
list.add(Realms.class);
|
||||||
return list;
|
return list;
|
||||||
|
|
|
@ -106,27 +106,9 @@ public interface CryptoService {
|
||||||
*/
|
*/
|
||||||
boolean encrypted(byte[] bytes);
|
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
|
* Flag for callers to determine if values will actually be encrypted or returned plaintext
|
||||||
* @return true if values will be encrypted
|
* @return true if values will be encrypted
|
||||||
*/
|
*/
|
||||||
boolean encryptionEnabled();
|
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;
|
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.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
@ -27,7 +13,6 @@ import javax.crypto.Mac;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -39,16 +24,25 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.regex.Pattern;
|
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.Security.setting;
|
||||||
import static org.elasticsearch.xpack.security.authc.support.SecuredString.constantTimeEquals;
|
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 String KEY_ALGO = "HmacSHA512";
|
||||||
public static final int KEY_SIZE = 1024;
|
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 =
|
public static final Setting<String> ENCRYPTION_KEY_ALGO_SETTING =
|
||||||
new Setting<>(setting("encryption_key.algorithm"), DEFAULT_KEY_ALGORITH, s -> s, Property.NodeScope);
|
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 SecureRandom secureRandom = new SecureRandom();
|
||||||
private final String encryptionAlgorithm;
|
private final String encryptionAlgorithm;
|
||||||
private final String keyAlgorithm;
|
private final String keyAlgorithm;
|
||||||
private final int keyLength;
|
private final int keyLength;
|
||||||
private final int ivLength;
|
private final int ivLength;
|
||||||
|
|
||||||
private Path keyFile;
|
private final Path keyFile;
|
||||||
|
|
||||||
private SecretKey randomKey;
|
private final SecretKey randomKey;
|
||||||
private String randomKeyBase64;
|
private final String randomKeyBase64;
|
||||||
|
|
||||||
private volatile SecretKey encryptionKey;
|
private final SecretKey encryptionKey;
|
||||||
private volatile SecretKey systemKey;
|
private final SecretKey systemKey;
|
||||||
private volatile SecretKey signingKey;
|
private final SecretKey signingKey;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public InternalCryptoService(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
public InternalCryptoService(Settings settings, Environment env) throws Exception {
|
||||||
this(settings, env, watcherService, Collections.<Listener>emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
InternalCryptoService(Settings settings, Environment env, ResourceWatcherService watcherService, List<Listener> listeners) {
|
|
||||||
super(settings);
|
super(settings);
|
||||||
this.env = env;
|
|
||||||
this.watcherService = watcherService;
|
|
||||||
this.listeners = new CopyOnWriteArrayList<>(listeners);
|
|
||||||
this.encryptionAlgorithm = ENCRYPTION_ALGO_SETTING.get(settings);
|
this.encryptionAlgorithm = ENCRYPTION_ALGO_SETTING.get(settings);
|
||||||
this.keyLength = ENCRYPTION_KEY_LENGTH_SETTING.get(settings);
|
this.keyLength = ENCRYPTION_KEY_LENGTH_SETTING.get(settings);
|
||||||
this.ivLength = keyLength / 8;
|
this.ivLength = keyLength / 8;
|
||||||
this.keyAlgorithm = ENCRYPTION_KEY_ALGO_SETTING.get(settings);
|
this.keyAlgorithm = ENCRYPTION_KEY_ALGO_SETTING.get(settings);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() throws ElasticsearchException {
|
|
||||||
if (keyLength % 8 != 0) {
|
if (keyLength % 8 != 0) {
|
||||||
throw new IllegalArgumentException("invalid key length [" + keyLength + "]. value must be a multiple of 8");
|
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);
|
keyFile = resolveSystemKey(settings, env);
|
||||||
systemKey = readSystemKey(keyFile);
|
systemKey = readSystemKey(keyFile);
|
||||||
randomKey = generateSecretKey(RANDOM_KEY_SIZE);
|
randomKey = generateSecretKey(RANDOM_KEY_SIZE);
|
||||||
|
@ -144,6 +106,7 @@ public class InternalCryptoService extends AbstractLifecycleComponent implements
|
||||||
} catch (NoSuchAlgorithmException nsae) {
|
} catch (NoSuchAlgorithmException nsae) {
|
||||||
throw new ElasticsearchException("failed to start crypto service. could not load encryption key", 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() {
|
public static byte[] generateKey() {
|
||||||
|
@ -366,11 +329,6 @@ public class InternalCryptoService extends AbstractLifecycleComponent implements
|
||||||
return bytesBeginsWith(ENCRYPTED_BYTE_PREFIX, bytes);
|
return bytesBeginsWith(ENCRYPTED_BYTE_PREFIX, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void register(Listener listener) {
|
|
||||||
this.listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean encryptionEnabled() {
|
public boolean encryptionEnabled() {
|
||||||
return this.encryptionKey != null;
|
return this.encryptionKey != null;
|
||||||
|
@ -477,87 +435,6 @@ public class InternalCryptoService extends AbstractLifecycleComponent implements
|
||||||
return true;
|
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
|
* 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)
|
* Mac#getInstance and obtaining a lock (in the internals)
|
||||||
|
|
|
@ -5,28 +5,17 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.security.crypto;
|
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.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
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.notNullValue;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class InternalCryptoServiceTests extends ESTestCase {
|
public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
private ResourceWatcherService watcherService;
|
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
private Environment env;
|
private Environment env;
|
||||||
private Path keyFile;
|
private Path keyFile;
|
||||||
private ThreadPool threadPool;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
|
@ -54,30 +38,19 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
.put("path.home", createTempDir())
|
.put("path.home", createTempDir())
|
||||||
.build();
|
.build();
|
||||||
env = new Environment(settings);
|
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 {
|
public void testSigned() throws Exception {
|
||||||
// randomize whether to use a system key or not
|
// randomize whether to use a system key or not
|
||||||
Settings settings = randomBoolean() ? this.settings : Settings.EMPTY;
|
Settings settings = randomBoolean() ? this.settings : Settings.EMPTY;
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
assertThat(service.signed(signed), is(true));
|
assertThat(service.signed(signed), is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSignAndUnsign() throws Exception {
|
public void testSignAndUnsign() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
assertThat(text.equals(signed), is(false));
|
assertThat(text.equals(signed), is(false));
|
||||||
|
@ -86,8 +59,7 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSignAndUnsignNoKeyFile() throws Exception {
|
public void testSignAndUnsignNoKeyFile() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env);
|
||||||
service.start();
|
|
||||||
final String text = randomAsciiOfLength(10);
|
final String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
// we always have some sort of key to sign with
|
// we always have some sort of key to sign with
|
||||||
|
@ -97,8 +69,7 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testTamperedSignature() throws Exception {
|
public void testTamperedSignature() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
int i = signed.indexOf("$$", 2);
|
int i = signed.indexOf("$$", 2);
|
||||||
|
@ -115,8 +86,7 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testTamperedSignatureOneChar() throws Exception {
|
public void testTamperedSignatureOneChar() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
int i = signed.indexOf("$$", 2);
|
int i = signed.indexOf("$$", 2);
|
||||||
|
@ -135,8 +105,7 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testTamperedSignatureLength() throws Exception {
|
public void testTamperedSignatureLength() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
int i = signed.indexOf("$$", 2);
|
int i = signed.indexOf("$$", 2);
|
||||||
|
@ -162,10 +131,9 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEncryptionAndDecryptionChars() {
|
public void testEncryptionAndDecryptionChars() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
assertThat(service.encryptionEnabled(), is(true));
|
||||||
assertThat(service.encryptionEnabled(), is(true));
|
|
||||||
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
|
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
|
||||||
final char[] encrypted = service.encrypt(chars);
|
final char[] encrypted = service.encrypt(chars);
|
||||||
assertThat(encrypted, notNullValue());
|
assertThat(encrypted, notNullValue());
|
||||||
|
@ -175,10 +143,9 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
assertThat(Arrays.equals(chars, decrypted), is(true));
|
assertThat(Arrays.equals(chars, decrypted), is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEncryptionAndDecryptionBytes() {
|
public void testEncryptionAndDecryptionBytes() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
assertThat(service.encryptionEnabled(), is(true));
|
||||||
assertThat(service.encryptionEnabled(), is(true));
|
|
||||||
final byte[] bytes = randomByteArray();
|
final byte[] bytes = randomByteArray();
|
||||||
final byte[] encrypted = service.encrypt(bytes);
|
final byte[] encrypted = service.encrypt(bytes);
|
||||||
assertThat(encrypted, notNullValue());
|
assertThat(encrypted, notNullValue());
|
||||||
|
@ -188,10 +155,9 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
assertThat(Arrays.equals(bytes, decrypted), is(true));
|
assertThat(Arrays.equals(bytes, decrypted), is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEncryptionAndDecryptionCharsWithoutKey() {
|
public void testEncryptionAndDecryptionCharsWithoutKey() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env);
|
||||||
service.start();
|
assertThat(service.encryptionEnabled(), is(false));
|
||||||
assertThat(service.encryptionEnabled(), is(false));
|
|
||||||
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
|
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
|
||||||
final char[] encryptedChars = service.encrypt(chars);
|
final char[] encryptedChars = service.encrypt(chars);
|
||||||
final char[] decryptedChars = service.decrypt(encryptedChars);
|
final char[] decryptedChars = service.decrypt(encryptedChars);
|
||||||
|
@ -199,10 +165,9 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
assertThat(chars, equalTo(decryptedChars));
|
assertThat(chars, equalTo(decryptedChars));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEncryptionAndDecryptionBytesWithoutKey() {
|
public void testEncryptionAndDecryptionBytesWithoutKey() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env);
|
||||||
service.start();
|
assertThat(service.encryptionEnabled(), is(false));
|
||||||
assertThat(service.encryptionEnabled(), is(false));
|
|
||||||
final byte[] bytes = randomByteArray();
|
final byte[] bytes = randomByteArray();
|
||||||
final byte[] encryptedBytes = service.encrypt(bytes);
|
final byte[] encryptedBytes = service.encrypt(bytes);
|
||||||
final byte[] decryptedBytes = service.decrypt(bytes);
|
final byte[] decryptedBytes = service.decrypt(bytes);
|
||||||
|
@ -210,22 +175,19 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
assertThat(decryptedBytes, equalTo(encryptedBytes));
|
assertThat(decryptedBytes, equalTo(encryptedBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEncryptionEnabledWithKey() {
|
public void testEncryptionEnabledWithKey() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
assertThat(service.encryptionEnabled(), is(true));
|
||||||
assertThat(service.encryptionEnabled(), is(true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEncryptionEnabledWithoutKey() {
|
public void testEncryptionEnabledWithoutKey() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(Settings.EMPTY, env);
|
||||||
service.start();
|
assertThat(service.encryptionEnabled(), is(false));
|
||||||
assertThat(service.encryptionEnabled(), is(false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testChangingAByte() {
|
public void testChangingAByte() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
assertThat(service.encryptionEnabled(), is(true));
|
||||||
assertThat(service.encryptionEnabled(), is(true));
|
|
||||||
// We need at least one byte to test changing a byte, otherwise output is always the same
|
// We need at least one byte to test changing a byte, otherwise output is always the same
|
||||||
final byte[] bytes = randomByteArray(1);
|
final byte[] bytes = randomByteArray(1);
|
||||||
final byte[] encrypted = service.encrypt(bytes);
|
final byte[] encrypted = service.encrypt(bytes);
|
||||||
|
@ -243,10 +205,9 @@ public class InternalCryptoServiceTests extends ESTestCase {
|
||||||
assertThat(Arrays.equals(bytes, decrypted), is(false));
|
assertThat(Arrays.equals(bytes, decrypted), is(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEncryptedChar() {
|
public void testEncryptedChar() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
assertThat(service.encryptionEnabled(), is(true));
|
||||||
assertThat(service.encryptionEnabled(), is(true));
|
|
||||||
|
|
||||||
assertThat(service.encrypted((char[]) null), is(false));
|
assertThat(service.encrypted((char[]) null), is(false));
|
||||||
assertThat(service.encrypted(new char[0]), 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));
|
assertThat(service.encrypted(service.encrypt(randomAsciiOfLength(10).toCharArray())), is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEncryptedByte() {
|
public void testEncryptedByte() throws Exception {
|
||||||
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService);
|
InternalCryptoService service = new InternalCryptoService(settings, env);
|
||||||
service.start();
|
assertThat(service.encryptionEnabled(), is(true));
|
||||||
assertThat(service.encryptionEnabled(), is(true));
|
|
||||||
|
|
||||||
assertThat(service.encrypted((byte[]) null), is(false));
|
assertThat(service.encrypted((byte[]) null), is(false));
|
||||||
assertThat(service.encrypted(new byte[0]), 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));
|
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() {
|
public void testSigningKeyCanBeRecomputedConsistently() {
|
||||||
final SecretKey systemKey = new SecretKeySpec(InternalCryptoService.generateKey(), InternalCryptoService.KEY_ALGO);
|
final SecretKey systemKey = new SecretKeySpec(InternalCryptoService.generateKey(), InternalCryptoService.KEY_ALGO);
|
||||||
final SecretKey randomKey = InternalCryptoService.generateSecretKey(InternalCryptoService.RANDOM_KEY_SIZE);
|
final SecretKey randomKey = InternalCryptoService.generateSecretKey(InternalCryptoService.RANDOM_KEY_SIZE);
|
||||||
|
|
Loading…
Reference in New Issue