Add CryptoService to support signing and encryption operations

This changes the SignatureService into a CryptoService that now supports encryption
operations in addition to the signing operations. The encryption leverages the existing
system key and uses it with AES.

Closes elastic/elasticsearch#805

Original commit: elastic/x-pack-elasticsearch@a792ed4a54
This commit is contained in:
jaymode 2015-04-15 11:44:03 -04:00
parent 39f587a497
commit bff95d9ca1
21 changed files with 619 additions and 347 deletions

View File

@ -14,7 +14,7 @@ import org.elasticsearch.shield.authc.AuthenticationModule;
import org.elasticsearch.shield.authz.AuthorizationModule;
import org.elasticsearch.shield.license.LicenseModule;
import org.elasticsearch.shield.rest.ShieldRestModule;
import org.elasticsearch.shield.signature.SignatureModule;
import org.elasticsearch.shield.crypto.CryptoModule;
import org.elasticsearch.shield.ssl.SSLModule;
import org.elasticsearch.shield.support.AbstractShieldModule;
import org.elasticsearch.shield.transport.ShieldTransportModule;
@ -42,13 +42,13 @@ public class ShieldModule extends AbstractShieldModule.Spawn {
return ImmutableList.<Module>of(
new LicenseModule(settings),
new CryptoModule(settings),
new AuthenticationModule(settings),
new AuthorizationModule(settings),
new AuditTrailModule(settings),
new ShieldRestModule(settings),
new ShieldActionModule(settings),
new ShieldTransportModule(settings),
new SignatureModule(settings),
new SSLModule(settings));
}

View File

@ -20,7 +20,7 @@ import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.authz.store.FileRolesStore;
import org.elasticsearch.shield.license.LicenseService;
import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.crypto.InternalCryptoService;
import org.elasticsearch.shield.transport.filter.IPFilter;
import java.io.File;
@ -67,7 +67,7 @@ public class ShieldPlugin extends AbstractPlugin {
@Override
public Collection<Class<? extends LifecycleComponent>> services() {
return enabled && !clientMode ?
ImmutableList.<Class<? extends LifecycleComponent>>of(LicenseService.class, FileRolesStore.class, Realms.class, InternalSignatureService.class, IPFilter.class) :
ImmutableList.<Class<? extends LifecycleComponent>>of(LicenseService.class, InternalCryptoService.class, FileRolesStore.class, Realms.class, IPFilter.class) :
ImmutableList.<Class<? extends LifecycleComponent>>of();
}

View File

@ -26,8 +26,8 @@ import org.elasticsearch.shield.authz.AuthorizationService;
import org.elasticsearch.shield.authz.Privilege;
import org.elasticsearch.shield.license.LicenseEventsNotifier;
import org.elasticsearch.shield.license.LicenseService;
import org.elasticsearch.shield.signature.SignatureException;
import org.elasticsearch.shield.signature.SignatureService;
import org.elasticsearch.shield.crypto.SignatureException;
import org.elasticsearch.shield.crypto.CryptoService;
import java.util.ArrayList;
import java.util.List;
@ -41,19 +41,19 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
private final AuthenticationService authcService;
private final AuthorizationService authzService;
private final SignatureService signatureService;
private final CryptoService cryptoService;
private final AuditTrail auditTrail;
private final ShieldActionMapper actionMapper;
private volatile boolean licenseEnabled = true;
@Inject
public ShieldActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, SignatureService signatureService,
public ShieldActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, CryptoService cryptoService,
AuditTrail auditTrail, LicenseEventsNotifier licenseEventsNotifier, ShieldActionMapper actionMapper) {
super(settings);
this.authcService = authcService;
this.authzService = authzService;
this.signatureService = signatureService;
this.cryptoService = cryptoService;
this.auditTrail = auditTrail;
this.actionMapper = actionMapper;
licenseEventsNotifier.register(new LicenseEventsNotifier.Listener() {
@ -122,7 +122,7 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
if (request instanceof SearchScrollRequest) {
SearchScrollRequest scrollRequest = (SearchScrollRequest) request;
String scrollId = scrollRequest.scrollId();
scrollRequest.scrollId(signatureService.unsignAndVerify(scrollId));
scrollRequest.scrollId(cryptoService.unsignAndVerify(scrollId));
return request;
}
@ -133,7 +133,7 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
List<String> signedIds = clearScrollRequest.scrollIds();
List<String> unsignedIds = new ArrayList<>(signedIds.size());
for (String signedId : signedIds) {
unsignedIds.add(signatureService.unsignAndVerify(signedId));
unsignedIds.add(cryptoService.unsignAndVerify(signedId));
}
clearScrollRequest.scrollIds(unsignedIds);
}
@ -153,8 +153,8 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
if (response instanceof SearchResponse) {
SearchResponse searchResponse = (SearchResponse) response;
String scrollId = searchResponse.getScrollId();
if (scrollId != null && !signatureService.signed(scrollId)) {
searchResponse.scrollId(signatureService.sign(scrollId));
if (scrollId != null && !cryptoService.signed(scrollId)) {
searchResponse.scrollId(cryptoService.sign(scrollId));
}
return response;
}

View File

@ -17,7 +17,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.shield.signature.SignatureService;
import org.elasticsearch.shield.crypto.CryptoService;
import org.elasticsearch.transport.TransportMessage;
import java.io.IOException;
@ -36,18 +36,18 @@ public class InternalAuthenticationService extends AbstractComponent implements
private final Realms realms;
private final AuditTrail auditTrail;
private final SignatureService signatureService;
private final CryptoService cryptoService;
private final boolean signUserHeader;
@Nullable
private final User anonymouseUser;
@Inject
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, SignatureService signatureService) {
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, CryptoService cryptoService) {
super(settings);
this.realms = realms;
this.auditTrail = auditTrail;
this.signatureService = signatureService;
this.cryptoService = cryptoService;
this.signUserHeader = componentSettings.getAsBoolean("sign_user_header", true);
anonymouseUser = resolveAnonymouseUser(componentSettings);
}
@ -84,13 +84,13 @@ public class InternalAuthenticationService extends AbstractComponent implements
String header = (String) message.getHeader(USER_KEY);
if (header != null) {
if (signUserHeader) {
header = signatureService.unsignAndVerify(header);
header = cryptoService.unsignAndVerify(header);
}
user = decodeUser(header);
}
if (user == null) {
user = authenticateWithRealms(action, message, fallbackUser);
header = signUserHeader ? signatureService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
header = signUserHeader ? cryptoService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
message.putHeader(USER_KEY, header);
}
message.putInContext(USER_KEY, user);
@ -104,13 +104,13 @@ public class InternalAuthenticationService extends AbstractComponent implements
}
User userFromContext = message.getFromContext(USER_KEY);
if (userFromContext != null) {
String userHeader = signUserHeader ? signatureService.sign(encodeUser(userFromContext, logger)) : encodeUser(userFromContext, logger);
String userHeader = signUserHeader ? cryptoService.sign(encodeUser(userFromContext, logger)) : encodeUser(userFromContext, logger);
message.putHeader(USER_KEY, userHeader);
return;
}
message.putInContext(USER_KEY, user);
String userHeader = signUserHeader ? signatureService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
String userHeader = signUserHeader ? cryptoService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
message.putHeader(USER_KEY, userHeader);
}

View File

@ -16,7 +16,7 @@ import java.util.Arrays;
*/
public class CharArrays {
static char[] utf8BytesToChars(byte[] utf8Bytes) {
public static char[] utf8BytesToChars(byte[] utf8Bytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(utf8Bytes);
CharBuffer charBuffer = Charsets.UTF_8.decode(byteBuffer);
char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());

View File

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.signature;
package org.elasticsearch.shield.crypto;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.support.AbstractShieldModule;
@ -11,15 +11,15 @@ import org.elasticsearch.shield.support.AbstractShieldModule;
/**
*
*/
public class SignatureModule extends AbstractShieldModule.Node {
public class CryptoModule extends AbstractShieldModule.Node {
public SignatureModule(Settings settings) {
public CryptoModule(Settings settings) {
super(settings);
}
@Override
protected void configureNode() {
bind(InternalSignatureService.class).asEagerSingleton();
bind(SignatureService.class).to(InternalSignatureService.class).asEagerSingleton();
bind(InternalCryptoService.class).asEagerSingleton();
bind(CryptoService.class).to(InternalCryptoService.class).asEagerSingleton();
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.crypto;
/**
* Service that provides cryptographic methods based on a shared system key
*/
public interface CryptoService {
/**
* Signs the given text and returns the signed text (original text + signature)
*/
String sign(String text);
/**
* Unsigns the given signed text, verifies the original text with the attached signature and if valid returns
* the unsigned (original) text. If signature verification fails a {@link SignatureException} is thrown.
*/
String unsignAndVerify(String text);
/**
* Checks whether the given text is signed.
*/
boolean signed(String text);
/**
* Encrypts the provided char array and returns the encrypted values in a Base64 encoded char array
* @param chars the characters to encrypt
* @return Base64 character array representing the encrypted data
* @throws UnsupportedOperationException if the system key is not present
*/
char[] encrypt(char[] chars);
/**
* Encrypts the provided byte array and returns the encrypted value
* @param bytes the data to encrypt
* @return encrypted data
* @throws UnsupportedOperationException if the system key is not present
*/
byte[] encrypt(byte[] bytes);
/**
* Decrypts the provided char array and returns the plain-text chars
* @param chars the Base64 encoded data to decrypt
* @return plaintext chars
* @throws UnsupportedOperationException if the system key is not present
*/
char[] decrypt(char[] chars);
/**
* Decrypts the provided byte array and returns the unencrypted bytes
* @param bytes the bytes to decrypt
* @return plaintext bytes
* @throws UnsupportedOperationException if the system key is not present
*/
byte[] decrypt(byte[] bytes);
}

View File

@ -0,0 +1,365 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.crypto;
import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.ShieldException;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.support.CharArrays;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.regex.Pattern;
import static org.elasticsearch.shield.authc.support.SecuredString.constantTimeEquals;
/**
*
*/
public class InternalCryptoService extends AbstractLifecycleComponent<InternalCryptoService> implements CryptoService {
public static final String FILE_SETTING = "shield.system_key.file";
public static final String KEY_ALGO = "HmacSHA512";
public static final int KEY_SIZE = 1024;
static final String FILE_NAME = "system_key";
static final String HMAC_ALGO = "HmacSHA1";
static final String DEFAULT_ENCRYPTION_ALGORITHM = "AES/CTR/NoPadding";
static final String DEFAULT_KEY_ALGORITH = "AES";
static final int DEFAULT_KEY_LENGTH = 128;
private static final Pattern SIG_PATTERN = Pattern.compile("^\\$\\$[0-9]+\\$\\$.+");
private final Environment env;
private final ResourceWatcherService watcherService;
private final Listener listener;
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 volatile SecretKey encryptionKey;
private volatile SecretKey systemKey;
@Inject
public InternalCryptoService(Settings settings, Environment env, ResourceWatcherService watcherService) {
this(settings, env, watcherService, Listener.NOOP);
}
InternalCryptoService(Settings settings, Environment env, ResourceWatcherService watcherService, Listener listener) {
super(settings);
this.env = env;
this.watcherService = watcherService;
this.listener = listener;
this.encryptionAlgorithm = settings.get("shield.encryption.algorithm", DEFAULT_ENCRYPTION_ALGORITHM);
this.keyLength = settings.getAsInt("shield.encryption_key.length", DEFAULT_KEY_LENGTH);
if (keyLength % 8 != 0) {
throw new ShieldSettingsException("invalid key length [" + keyLength + "]. value must be a multiple of 8");
}
this.ivLength = keyLength / 8;
this.keyAlgorithm = settings.get("shield.encryption_key.algorithm", DEFAULT_KEY_ALGORITH);
}
@Override
protected void doStart() throws ElasticsearchException {
keyFile = resolveSystemKey(settings, env);
systemKey = readSystemKey(keyFile);
encryptionKey = encryptionKey(systemKey, keyLength, keyAlgorithm);
FileWatcher watcher = new FileWatcher(keyFile.getParent().toFile());
watcher.addListener(new FileListener(listener));
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
}
@Override
protected void doStop() throws ElasticsearchException {}
@Override
protected void doClose() throws ElasticsearchException {}
public static byte[] generateKey() throws Exception {
KeyGenerator generator = KeyGenerator.getInstance(KEY_ALGO);
generator.init(KEY_SIZE);
return generator.generateKey().getEncoded();
}
public static Path resolveSystemKey(Settings settings, Environment env) {
String location = settings.get(FILE_SETTING);
if (location == null) {
return ShieldPlugin.resolveConfigFile(env, FILE_NAME);
}
return Paths.get(location);
}
static SecretKey readSystemKey(Path file) {
if (!Files.exists(file)) {
return null;
}
try {
byte[] bytes = Files.readAllBytes(file);
return new SecretKeySpec(bytes, KEY_ALGO);
} catch (IOException e) {
throw new ShieldException("could not read secret key", e);
}
}
@Override
public String sign(String text) {
SecretKey key = this.systemKey;
if (key == null) {
return text;
}
String sigStr = signInternal(text);
return "$$" + sigStr.length() + "$$" + sigStr + text;
}
@Override
public String unsignAndVerify(String signedText) {
SecretKey key = this.systemKey;
if (key == null) {
return signedText;
}
if (!signedText.startsWith("$$") || signedText.length() < 2) {
throw new SignatureException("tampered signed text");
}
String text;
String receivedSignature;
try {
// $$34$$sigtext
int i = signedText.indexOf("$$", 2);
int length = Integer.parseInt(signedText.substring(2, i));
receivedSignature = signedText.substring(i + 2, i + 2 + length);
text = signedText.substring(i + 2 + length);
} catch (Throwable t) {
logger.error("error occurred while parsing signed text", t);
throw new SignatureException("tampered signed text");
}
try {
String sig = signInternal(text);
if (constantTimeEquals(sig, receivedSignature)) {
return text;
}
} catch (Throwable t) {
logger.error("error occurred while verifying signed text", t);
throw new SignatureException("error while verifying the signed text");
}
throw new SignatureException("tampered signed text");
}
@Override
public boolean signed(String text) {
return SIG_PATTERN.matcher(text).matches();
}
@Override
public char[] encrypt(char[] chars) {
SecretKey key = this.encryptionKey;
if (key == null) {
throw new UnsupportedOperationException("encryption cannot be performed without a system key. please run bin/shield/syskeygen on one node and copy\n"
+ "the file [" + ShieldPlugin.resolveConfigFile(env, FILE_NAME) + "] to all nodes and the key will be loaded automatically.");
}
byte[] charBytes = CharArrays.toUtf8Bytes(chars);
return Base64.encodeBase64String(encryptInternal(charBytes, key)).toCharArray();
}
@Override
public byte[] encrypt(byte[] bytes) {
SecretKey key = this.encryptionKey;
if (key == null) {
throw new UnsupportedOperationException("encryption cannot be performed without a system key. please run bin/shield/syskeygen on one node and copy\n"
+ "the file [" + ShieldPlugin.resolveConfigFile(env, FILE_NAME) + "] to all nodes and the key will be loaded automatically.");
}
return encryptInternal(bytes, key);
}
@Override
public char[] decrypt(char[] chars) {
SecretKey key = this.encryptionKey;
if (key == null) {
throw new UnsupportedOperationException("decryption cannot be performed without a system key. please run bin/shield/syskeygen on one node and copy\n"
+ "the file [" + ShieldPlugin.resolveConfigFile(env, FILE_NAME) + "] to all nodes and the key will be loaded automatically.");
}
byte[] bytes = Base64.decodeBase64(new String(chars));
byte[] decrypted = decryptInternal(bytes, key);
return CharArrays.utf8BytesToChars(decrypted);
}
@Override
public byte[] decrypt(byte[] bytes) {
SecretKey key = this.encryptionKey;
if (key == null) {
throw new UnsupportedOperationException("decryption cannot be performed without a system key. please run bin/shield/syskeygen on one node and copy\n"
+ "the file [" + ShieldPlugin.resolveConfigFile(env, FILE_NAME) + "] to all nodes and the key will be loaded automatically.");
}
return decryptInternal(bytes, key);
}
private byte[] encryptInternal(byte[] bytes, SecretKey key) {
byte[] iv = new byte[ivLength];
secureRandom.nextBytes(iv);
Cipher cipher = cipher(Cipher.ENCRYPT_MODE, encryptionAlgorithm, key, iv);
try {
byte[] encrypted = cipher.doFinal(bytes);
byte[] output = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, output, 0, iv.length);
System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
return output;
} catch (BadPaddingException |IllegalBlockSizeException e) {
throw new ShieldException("error encrypting data", e);
}
}
private byte[] decryptInternal(byte[] bytes, SecretKey key) {
if (bytes.length < ivLength) {
logger.error("received data for decryption with size [{}] that is less than IV length [{}]", bytes.length, ivLength);
throw new ShieldException("invalid data to decrypt");
}
byte[] iv = new byte[ivLength];
System.arraycopy(bytes, 0, iv, 0, ivLength);
byte[] data = new byte[bytes.length - ivLength];
System.arraycopy(bytes, ivLength, data, 0, bytes.length - ivLength);
Cipher cipher = cipher(Cipher.DECRYPT_MODE, encryptionAlgorithm, key, iv);
try {
return cipher.doFinal(data);
} catch (BadPaddingException|IllegalBlockSizeException e) {
throw new ShieldException("error decrypting data", e);
}
}
static Mac createMac(SecretKey key) {
try {
Mac mac = Mac.getInstance(HMAC_ALGO);
mac.init(key);
return mac;
} catch (Exception e) {
throw new ElasticsearchException("could not initialize mac", e);
}
}
private String signInternal(String text) {
Mac mac = createMac(systemKey);
byte[] sig = mac.doFinal(text.getBytes(Charsets.UTF_8));
return Base64.encodeBase64URLSafeString(sig);
}
static Cipher cipher(int mode, String encryptionAlgorithm, SecretKey key, byte[] initializationVector) {
try {
Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
cipher.init(mode, key, new IvParameterSpec(initializationVector));
return cipher;
} catch (Exception e) {
throw new ShieldException("error creating cipher", e);
}
}
static SecretKey encryptionKey(SecretKey systemKey, int keyLength, String algorithm) {
if (systemKey == null) {
return null;
}
try {
byte[] bytes = systemKey.getEncoded();
if ((bytes.length * 8) < keyLength) {
throw new ShieldException("at least " + keyLength +" bits should be provided as key data");
}
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] digest = messageDigest.digest(bytes);
assert digest.length == (256 / 8);
if ((digest.length * 8) < keyLength) {
throw new ShieldException("requested key length is too large");
}
byte[] truncatedDigest = Arrays.copyOfRange(digest, 0, (keyLength / 8));
return new SecretKeySpec(truncatedDigest, algorithm);
} catch (NoSuchAlgorithmException e) {
throw new ShieldException("error getting encryption key", e);
}
}
private class FileListener extends FileChangesListener {
private final Listener listener;
private FileListener(Listener listener) {
this.listener = listener;
}
@Override
public void onFileCreated(File file) {
if (file.equals(keyFile.toFile())) {
systemKey = readSystemKey(file.toPath());
encryptionKey = encryptionKey(systemKey, keyLength, keyAlgorithm);
logger.info("system key [{}] has been loaded", file.getAbsolutePath());
listener.onKeyRefresh();
}
}
@Override
public void onFileDeleted(File file) {
if (file.equals(keyFile.toFile())) {
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) and won't be able to decrypt\n" +
"previously encrypted values without the original key");
systemKey = null;
encryptionKey = null;
}
}
@Override
public void onFileChanged(File file) {
if (file.equals(keyFile.toFile())) {
logger.warn("system key file changed! previously encrypted values cannot be successfully decrypted with a different key");
systemKey = readSystemKey(file.toPath());
encryptionKey = encryptionKey(systemKey, keyLength, keyAlgorithm);
listener.onKeyRefresh();
}
}
}
static interface Listener {
final Listener NOOP = new Listener() {
@Override
public void onKeyRefresh() {
}
};
void onKeyRefresh();
}
}

View File

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.signature;
package org.elasticsearch.shield.crypto;
import org.elasticsearch.shield.authz.AuthorizationException;

View File

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.signature.tool;
package org.elasticsearch.shield.crypto.tool;
import org.elasticsearch.common.cli.CheckFileCommand;
import org.elasticsearch.common.cli.CliTool;
@ -13,7 +13,7 @@ import org.elasticsearch.common.cli.commons.CommandLine;
import org.elasticsearch.common.collect.Sets;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.crypto.InternalCryptoService;
import java.io.IOException;
import java.nio.file.Files;
@ -78,7 +78,7 @@ public class SystemKeyTool extends CliTool {
protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) {
Path path = this.path;
if (path == null) {
path = InternalSignatureService.resolveFile(settings, env);
path = InternalCryptoService.resolveSystemKey(settings, env);
}
return new Path[] { path };
}
@ -88,10 +88,10 @@ public class SystemKeyTool extends CliTool {
Path path = this.path;
try {
if (path == null) {
path = InternalSignatureService.resolveFile(settings, env);
path = InternalCryptoService.resolveSystemKey(settings, env);
}
terminal.println(Terminal.Verbosity.VERBOSE, "generating...");
byte[] key = InternalSignatureService.generateKey();
byte[] key = InternalCryptoService.generateKey();
terminal.println("Storing generated key in [%s]...", path.toAbsolutePath());
Files.write(path, key, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
} catch (IOException ioe) {

View File

@ -1,220 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.signature;
import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.ShieldException;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Pattern;
import static org.elasticsearch.shield.authc.support.SecuredString.constantTimeEquals;
/**
*
*/
public class InternalSignatureService extends AbstractLifecycleComponent<InternalSignatureService> implements SignatureService {
public static final String FILE_SETTING = "shield.system_key.file";
public static final String KEY_ALGO = "HmacSHA512";
public static final int KEY_SIZE = 1024;
static final String FILE_NAME = "system_key";
static final String HMAC_ALGO = "HmacSHA1";
private static final Pattern SIG_PATTERN = Pattern.compile("^\\$\\$[0-9]+\\$\\$.+");
private final Environment env;
private final ResourceWatcherService watcherService;
private final Listener listener;
private Path keyFile;
private volatile SecretKey key;
@Inject
public InternalSignatureService(Settings settings, Environment env, ResourceWatcherService watcherService) {
this(settings, env, watcherService, Listener.NOOP);
}
InternalSignatureService(Settings settings, Environment env, ResourceWatcherService watcherService, Listener listener) {
super(settings);
this.env = env;
this.watcherService = watcherService;
this.listener = listener;
}
public static byte[] generateKey() throws Exception {
KeyGenerator generator = KeyGenerator.getInstance(KEY_ALGO);
generator.init(KEY_SIZE);
return generator.generateKey().getEncoded();
}
public static Path resolveFile(Settings settings, Environment env) {
String location = settings.get(FILE_SETTING);
if (location == null) {
return ShieldPlugin.resolveConfigFile(env, FILE_NAME);
}
return Paths.get(location);
}
static SecretKey readKey(Path file) {
if (!Files.exists(file)) {
return null;
}
try {
byte[] bytes = Files.readAllBytes(file);
return new SecretKeySpec(bytes, KEY_ALGO);
} catch (IOException e) {
throw new ShieldException("could not read secret key", e);
}
}
@Override
public String sign(String text) {
SecretKey key = this.key;
if (key == null) {
return text;
}
String sigStr = signInternal(text);
return "$$" + sigStr.length() + "$$" + sigStr + text;
}
@Override
public String unsignAndVerify(String signedText) {
SecretKey key = this.key;
if (key == null) {
return signedText;
}
if (!signedText.startsWith("$$") || signedText.length() < 2) {
throw new SignatureException("tampered signed text");
}
String text;
String receivedSignature;
try {
// $$34$$sigtext
int i = signedText.indexOf("$$", 2);
int length = Integer.parseInt(signedText.substring(2, i));
receivedSignature = signedText.substring(i + 2, i + 2 + length);
text = signedText.substring(i + 2 + length);
} catch (Throwable t) {
logger.error("error occurred while parsing signed text", t);
throw new SignatureException("tampered signed text");
}
try {
String sig = signInternal(text);
if (constantTimeEquals(sig, receivedSignature)) {
return text;
}
} catch (Throwable t) {
logger.error("error occurred while verifying signed text", t);
throw new SignatureException("error while verifying the signed text");
}
throw new SignatureException("tampered signed text");
}
@Override
public boolean signed(String text) {
return SIG_PATTERN.matcher(text).matches();
}
static Mac createMac(SecretKey key) {
try {
Mac mac = Mac.getInstance(HMAC_ALGO);
mac.init(key);
return mac;
} catch (Exception e) {
throw new ElasticsearchException("could not initialize mac", e);
}
}
private String signInternal(String text) {
Mac mac = createMac(key);
byte[] sig = mac.doFinal(text.getBytes(Charsets.UTF_8));
return Base64.encodeBase64URLSafeString(sig);
}
@Override
protected void doStart() throws ElasticsearchException {
keyFile = resolveFile(settings, env);
key = readKey(keyFile);
FileWatcher watcher = new FileWatcher(keyFile.getParent().toFile());
watcher.addListener(new FileListener(listener));
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
}
@Override
protected void doStop() throws ElasticsearchException {}
@Override
protected void doClose() throws ElasticsearchException {}
private class FileListener extends FileChangesListener {
private final Listener listener;
private FileListener(Listener listener) {
this.listener = listener;
}
@Override
public void onFileCreated(File file) {
if (file.equals(keyFile.toFile())) {
key = readKey(file.toPath());
listener.onKeyRefresh();
}
}
@Override
public void onFileDeleted(File file) {
if (file.equals(keyFile.toFile())) {
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)");
key = null;
}
}
@Override
public void onFileChanged(File file) {
if (file.equals(keyFile.toFile())) {
key = readKey(file.toPath());
listener.onKeyRefresh();
}
}
}
static interface Listener {
final Listener NOOP = new Listener() {
@Override
public void onKeyRefresh() {
}
};
void onKeyRefresh();
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.signature;
/**
*
*/
public interface SignatureService {
/**
* Signs the given text and returns the signed text (original text + signature)
*/
String sign(String text);
/**
* Unsigns the given signed text, verifies the original text with the attached signature and if valid returns
* the unsigned (original) text. If signature verification fails a {@link SignatureException} is thrown.
*/
String unsignAndVerify(String text);
/**
* Checks whether the given text is signed.
*/
boolean signed(String text);
}

View File

@ -10,10 +10,9 @@ import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.shield.authz.AuthorizationException;
import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.signature.SignatureService;
import org.elasticsearch.shield.crypto.InternalCryptoService;
import org.elasticsearch.shield.crypto.CryptoService;
import org.elasticsearch.test.ShieldIntegrationTest;
import org.junit.Before;
import org.junit.Test;
import java.util.Locale;
@ -104,8 +103,8 @@ public class ScrollIdSigningTests extends ShieldIntegrationTest {
}
private void assertSigned(String scrollId) {
SignatureService signatureService = internalCluster().getDataNodeInstance(InternalSignatureService.class);
CryptoService cryptoService = internalCluster().getDataNodeInstance(InternalCryptoService.class);
String message = String.format(Locale.ROOT, "Expected scrollId [%s] to be signed, but was not", scrollId);
assertThat(message, signatureService.signed(scrollId), is(true));
assertThat(message, cryptoService.signed(scrollId), is(true));
}
}

View File

@ -16,8 +16,8 @@ import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.authz.AuthorizationException;
import org.elasticsearch.shield.authz.AuthorizationService;
import org.elasticsearch.shield.license.LicenseEventsNotifier;
import org.elasticsearch.shield.signature.SignatureException;
import org.elasticsearch.shield.signature.SignatureService;
import org.elasticsearch.shield.crypto.SignatureException;
import org.elasticsearch.shield.crypto.CryptoService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Before;
import org.junit.Test;
@ -34,7 +34,7 @@ public class ShieldActionFilterTests extends ElasticsearchTestCase {
private AuthenticationService authcService;
private AuthorizationService authzService;
private SignatureService signatureService;
private CryptoService cryptoService;
private AuditTrail auditTrail;
private LicenseEventsNotifier licenseEventsNotifier;
private ShieldActionFilter filter;
@ -43,10 +43,10 @@ public class ShieldActionFilterTests extends ElasticsearchTestCase {
public void init() throws Exception {
authcService = mock(AuthenticationService.class);
authzService = mock(AuthorizationService.class);
signatureService = mock(SignatureService.class);
cryptoService = mock(CryptoService.class);
auditTrail = mock(AuditTrail.class);
licenseEventsNotifier = new MockLicenseEventsNotifier();
filter = new ShieldActionFilter(ImmutableSettings.EMPTY, authcService, authzService, signatureService, auditTrail, licenseEventsNotifier, new ShieldActionMapper());
filter = new ShieldActionFilter(ImmutableSettings.EMPTY, authcService, authzService, cryptoService, auditTrail, licenseEventsNotifier, new ShieldActionMapper());
}
@Test
@ -83,8 +83,8 @@ public class ShieldActionFilterTests extends ElasticsearchTestCase {
ActionFilterChain chain = mock(ActionFilterChain.class);
User user = mock(User.class);
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
when(signatureService.signed("signed_scroll_id")).thenReturn(true);
when(signatureService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id");
when(cryptoService.signed("signed_scroll_id")).thenReturn(true);
when(cryptoService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id");
filter.apply("_action", request, listener, chain);
assertThat(request.scrollId(), equalTo("scroll_id"));
verify(authzService).authorize(user, "_action", request);
@ -99,8 +99,8 @@ public class ShieldActionFilterTests extends ElasticsearchTestCase {
SignatureException sigException = new SignatureException("bad bad boy");
User user = mock(User.class);
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
when(signatureService.signed("scroll_id")).thenReturn(true);
doThrow(sigException).when(signatureService).unsignAndVerify("scroll_id");
when(cryptoService.signed("scroll_id")).thenReturn(true);
doThrow(sigException).when(cryptoService).unsignAndVerify("scroll_id");
filter.apply("_action", request, listener, chain);
verify(listener).onFailure(isA(AuthorizationException.class));
verify(auditTrail).tamperedRequest(user, "_action", request);

View File

@ -18,7 +18,7 @@ import org.elasticsearch.shield.User;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.signature.SignatureService;
import org.elasticsearch.shield.crypto.CryptoService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.TransportMessage;
import org.junit.Before;
@ -49,7 +49,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
Realm secondRealm;
AuditTrail auditTrail;
AuthenticationToken token;
SignatureService signatureService;
CryptoService cryptoService;
@Before
public void init() throws Exception {
@ -67,10 +67,10 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
}
};
realms.start();
signatureService = mock(SignatureService.class);
cryptoService = mock(CryptoService.class);
auditTrail = mock(AuditTrail.class);
service = new InternalAuthenticationService(ImmutableSettings.EMPTY, realms, auditTrail, signatureService);
service = new InternalAuthenticationService(ImmutableSettings.EMPTY, realms, auditTrail, cryptoService);
}
@Test @SuppressWarnings("unchecked")
@ -116,7 +116,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
service = spy(service);
doReturn(token).when(service).token("_action", message);
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
User result = service.authenticate("_action", message, null);
assertThat(result, notNullValue());
@ -137,7 +137,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
service = spy(service);
doReturn(token).when(service).token("_action", message);
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
User result = service.authenticate("_action", message, null);
assertThat(result, notNullValue());
@ -159,7 +159,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
verifyZeroInteractions(auditTrail);
verifyZeroInteractions(firstRealm);
verifyZeroInteractions(secondRealm);
verifyZeroInteractions(signatureService);
verifyZeroInteractions(cryptoService);
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), notNullValue());
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), is((Object) user));
}
@ -211,7 +211,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
when(firstRealm.token(message)).thenReturn(token);
when(firstRealm.supports(token)).thenReturn(true);
when(firstRealm.authenticate(token)).thenReturn(user);
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
service = spy(service);
doReturn(token).when(service).token("_action", message);
User result = service.authenticate("_action", message, null);
@ -253,7 +253,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
when(firstRealm.token(message)).thenReturn(null);
when(secondRealm.token(message)).thenReturn(null);
User.Simple user1 = new User.Simple("username", "r1", "r2");
when(signatureService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
User user2 = service.authenticate("_action", message, user1);
assertThat(user1, sameInstance(user2));
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
@ -266,7 +266,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
when(firstRealm.token(message)).thenReturn(token);
when(firstRealm.supports(token)).thenReturn(true);
when(firstRealm.authenticate(token)).thenReturn(user1);
when(signatureService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
User user2 = service.authenticate("_action", message, null);
assertThat(user1, sameInstance(user2));
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
@ -279,7 +279,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
when(firstRealm.token(message)).thenReturn(token);
when(firstRealm.supports(token)).thenReturn(true);
when(firstRealm.authenticate(token)).thenReturn(user1);
when(signatureService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
User user2 = service.authenticate("_action", message, User.SYSTEM);
assertThat(user1, sameInstance(user2));
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
@ -303,7 +303,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
when(firstRealm.token(message)).thenReturn(token);
when(firstRealm.supports(token)).thenReturn(true);
when(firstRealm.authenticate(token)).thenReturn(user1);
when(signatureService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
User user2 = service.authenticate("_action", message, User.SYSTEM);
assertThat(user1, sameInstance(user2));
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
@ -321,7 +321,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
// checking authentication from the user header
message1.putHeader(InternalAuthenticationService.USER_KEY, message.getHeader(InternalAuthenticationService.USER_KEY));
when(signatureService.unsignAndVerify("_signed_user")).thenReturn(InternalAuthenticationService.encodeUser(user1, null));
when(cryptoService.unsignAndVerify("_signed_user")).thenReturn(InternalAuthenticationService.encodeUser(user1, null));
BytesStreamOutput output = new BytesStreamOutput();
message1.writeTo(output);
BytesStreamInput input = new BytesStreamInput(output.bytes());
@ -335,7 +335,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
@Test
public void testAutheticate_Transport_ContextAndHeader_NoSigning() throws Exception {
Settings settings = ImmutableSettings.builder().put("shield.authc.sign_user_header", false).build();
service = new InternalAuthenticationService(settings, realms, auditTrail, signatureService);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService);
User user1 = new User.Simple("username", "r1", "r2");
when(firstRealm.supports(token)).thenReturn(true);
@ -367,7 +367,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
assertThat(user, equalTo(user1));
verifyZeroInteractions(firstRealm);
verifyZeroInteractions(signatureService);
verifyZeroInteractions(cryptoService);
}
@Test
@ -375,7 +375,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
User user = new User.Simple("username", "r1", "r2");
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), nullValue());
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
service.attachUserHeaderIfMissing(message, user);
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
@ -384,7 +384,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
message = new InternalMessage();
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), nullValue());
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
service.attachUserHeaderIfMissing(message, user);
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
@ -437,7 +437,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
if (username != InternalAuthenticationService.ANONYMOUS_USERNAME) {
builder.put("shield.authc.anonymous.username", username);
}
service = new InternalAuthenticationService(builder.build(), realms, auditTrail, signatureService);
service = new InternalAuthenticationService(builder.build(), realms, auditTrail, cryptoService);
RestRequest request = new FakeRestRequest();
@ -454,7 +454,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
Settings settings = ImmutableSettings.builder()
.putArray("shield.authc.anonymous.roles", "r1", "r2", "r3")
.build();
service = new InternalAuthenticationService(settings, realms, auditTrail, signatureService);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService);
InternalMessage message = new InternalMessage();
@ -469,7 +469,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
Settings settings = ImmutableSettings.builder()
.putArray("shield.authc.anonymous.roles", "r1", "r2", "r3")
.build();
service = new InternalAuthenticationService(settings, realms, auditTrail, signatureService);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService);
InternalMessage message = new InternalMessage();

View File

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.signature;
package org.elasticsearch.shield.crypto;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.settings.ImmutableSettings;
@ -17,17 +17,17 @@ import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.notNullValue;
/**
*
*/
public class InternalSignatureServiceTests extends ElasticsearchTestCase {
public class InternalCryptoServiceTests extends ElasticsearchTestCase {
private ResourceWatcherService watcherService;
private Settings settings;
@ -38,7 +38,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
@Before
public void init() throws Exception {
keyFile = new File(newTempDir(), "system_key");
Streams.copy(InternalSignatureService.generateKey(), keyFile);
Streams.copy(InternalCryptoService.generateKey(), keyFile);
settings = ImmutableSettings.builder()
.put("shield.system_key.file", keyFile.getAbsolutePath())
.put("watcher.interval.high", "2s")
@ -51,12 +51,13 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
@After
public void shutdown() throws InterruptedException {
watcherService.stop();
terminate(threadPool);
}
@Test
public void testSigned() throws Exception {
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
assertThat(service.signed(signed), is(true));
@ -64,7 +65,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
@Test
public void testSignAndUnsign() throws Exception {
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
assertThat(text.equals(signed), is(false));
@ -74,7 +75,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
@Test
public void testSignAndUnsign_NoKeyFile() throws Exception {
InternalSignatureService service = new InternalSignatureService(ImmutableSettings.EMPTY, env, watcherService).start();
InternalCryptoService service = new InternalCryptoService(ImmutableSettings.EMPTY, env, watcherService).start();
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
assertThat(text, equalTo(signed));
@ -84,7 +85,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
@Test
public void testTamperedSignature() throws Exception {
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
int i = signed.indexOf("$$", 2);
@ -102,7 +103,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
@Test
public void testTamperedSignatureOneChar() throws Exception {
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
int i = signed.indexOf("$$", 2);
@ -122,7 +123,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
@Test
public void testTamperedSignatureLength() throws Exception {
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
int i = signed.indexOf("$$", 2);
@ -148,10 +149,88 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
}
}
@Test
public void testEncryptionAndDecryptionChars() {
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
final char[] encrypted = service.encrypt(chars);
assertThat(encrypted, notNullValue());
assertThat(Arrays.equals(encrypted, chars), is(false));
final char[] decrypted = service.decrypt(encrypted);
assertThat(Arrays.equals(chars, decrypted), is(true));
}
@Test
public void testEncryptionAndDecryptionBytes() {
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
final byte[] bytes = randomByteArray();
final byte[] encrypted = service.encrypt(bytes);
assertThat(encrypted, notNullValue());
assertThat(Arrays.equals(encrypted, bytes), is(false));
final byte[] decrypted = service.decrypt(encrypted);
assertThat(Arrays.equals(bytes, decrypted), is(true));
}
@Test
public void testEncryptionAndDecryptionCharsWithoutKey() {
InternalCryptoService service = new InternalCryptoService(ImmutableSettings.EMPTY, env, watcherService).start();
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
try {
service.encrypt(chars);
fail("exception should have been thrown");
} catch (Exception e) {
assertThat(e, instanceOf(UnsupportedOperationException.class));
assertThat(e.getMessage(), containsString("system_key"));
}
try {
service.decrypt(chars);
} catch (Exception e) {
assertThat(e, instanceOf(UnsupportedOperationException.class));
assertThat(e.getMessage(), containsString("system_key"));
}
}
@Test
public void testEncryptionAndDecryptionBytesWithoutKey() {
InternalCryptoService service = new InternalCryptoService(ImmutableSettings.EMPTY, env, watcherService).start();
final byte[] bytes = randomByteArray();
try {
service.encrypt(bytes);
fail("exception should have been thrown");
} catch (Exception e) {
assertThat(e, instanceOf(UnsupportedOperationException.class));
assertThat(e.getMessage(), containsString("system_key"));
}
try {
service.decrypt(bytes);
} catch (Exception e) {
assertThat(e, instanceOf(UnsupportedOperationException.class));
assertThat(e.getMessage(), containsString("system_key"));
}
}
@Test
public void testChangingAByte() {
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
final byte[] bytes = randomByteArray();
final byte[] encrypted = service.encrypt(bytes);
assertThat(encrypted, notNullValue());
assertThat(Arrays.equals(encrypted, bytes), is(false));
int tamperedIndex = randomIntBetween(0, encrypted.length - 1);
encrypted[tamperedIndex] = randomByte();
final byte[] decrypted = service.decrypt(encrypted);
assertThat(Arrays.equals(bytes, decrypted), is(false));
}
@Test
public void testReloadKey() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService, new InternalSignatureService.Listener() {
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService, new InternalCryptoService.Listener() {
@Override
public void onKeyRefresh() {
latch.countDown();
@ -160,16 +239,34 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
char[] textChars = text.toCharArray();
char[] encrypted = service.encrypt(textChars);
// we need to sleep so to ensure the timestamp of the file will definitely change
// and so the resource watcher will pick up the change.
sleep(1000);
Streams.copy(InternalSignatureService.generateKey(), keyFile);
Streams.copy(InternalCryptoService.generateKey(), keyFile);
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));
}
private static byte[] randomByteArray() {
int count = randomIntBetween(0, 1000);
byte[] bytes = new byte[count];
for (int i = 0; i < count; i++) {
bytes[i] = randomByte();
}
return bytes;
}
}

View File

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.signature.tool;
package org.elasticsearch.shield.crypto.tool;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolTestCase;
@ -13,7 +13,7 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.crypto.InternalCryptoService;
import org.junit.Before;
import org.junit.Test;
@ -24,7 +24,7 @@ import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
import static org.elasticsearch.shield.signature.tool.SystemKeyTool.Generate;
import static org.elasticsearch.shield.crypto.tool.SystemKeyTool.Generate;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -67,7 +67,7 @@ public class SystemKeyToolTests extends CliToolTestCase {
CliTool.ExitStatus status = generate.execute(ImmutableSettings.EMPTY, env);
assertThat(status, is(CliTool.ExitStatus.OK));
byte[] bytes = Streams.copyToByteArray(path.toFile());
assertThat(bytes.length, is(InternalSignatureService.KEY_SIZE / 8));
assertThat(bytes.length, is(InternalCryptoService.KEY_SIZE / 8));
}
@Test
@ -80,7 +80,7 @@ public class SystemKeyToolTests extends CliToolTestCase {
CliTool.ExitStatus status = generate.execute(settings, env);
assertThat(status, is(CliTool.ExitStatus.OK));
byte[] bytes = Streams.copyToByteArray(path.toFile());
assertThat(bytes.length, is(InternalSignatureService.KEY_SIZE / 8));
assertThat(bytes.length, is(InternalCryptoService.KEY_SIZE / 8));
}
@Test
@ -94,7 +94,7 @@ public class SystemKeyToolTests extends CliToolTestCase {
CliTool.ExitStatus status = generate.execute(ImmutableSettings.EMPTY, env);
assertThat(status, is(CliTool.ExitStatus.OK));
byte[] bytes = Streams.copyToByteArray(path.toFile());
assertThat(bytes.length, is(InternalSignatureService.KEY_SIZE / 8));
assertThat(bytes.length, is(InternalCryptoService.KEY_SIZE / 8));
}
@Test

View File

@ -15,7 +15,7 @@ import org.elasticsearch.discovery.MasterNotDiscoveredException;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.internal.InternalNode;
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.crypto.InternalCryptoService;
import org.elasticsearch.test.ShieldIntegrationTest;
import org.elasticsearch.test.ShieldSettingsSource;
import org.elasticsearch.transport.Transport;
@ -78,7 +78,7 @@ public class ServerTransportFilterIntegrationTests extends ShieldIntegrationTest
@Test
public void testThatConnectionToServerTypeConnectionWorks() {
Settings dataNodeSettings = internalCluster().getDataNodeInstance(Settings.class);
String systemKeyFile = dataNodeSettings.get(InternalSignatureService.FILE_SETTING);
String systemKeyFile = dataNodeSettings.get(InternalCryptoService.FILE_SETTING);
Transport transport = internalCluster().getDataNodeInstance(Transport.class);
TransportAddress transportAddress = transport.boundAddress().publishAddress();
@ -98,7 +98,7 @@ public class ServerTransportFilterIntegrationTests extends ShieldIntegrationTest
.put("shield.transport.ssl", sslTransportEnabled())
.put("shield.audit.enabled", false)
.put(InternalNode.HTTP_ENABLED, false)
.put(InternalSignatureService.FILE_SETTING, systemKeyFile)
.put(InternalCryptoService.FILE_SETTING, systemKeyFile)
.build();
try (Node node = nodeBuilder().client(true).settings(nodeSettings).node()) {
assertGreenClusterState(node.client());
@ -108,7 +108,7 @@ public class ServerTransportFilterIntegrationTests extends ShieldIntegrationTest
@Test
public void testThatConnectionToClientTypeConnectionIsRejected() {
Settings dataNodeSettings = internalCluster().getDataNodeInstance(Settings.class);
String systemKeyFile = dataNodeSettings.get(InternalSignatureService.FILE_SETTING);
String systemKeyFile = dataNodeSettings.get(InternalCryptoService.FILE_SETTING);
File folder = createFolder(globalTempDir(), getClass().getSimpleName() + "-" + randomAsciiOfLength(10));
@ -129,7 +129,7 @@ public class ServerTransportFilterIntegrationTests extends ShieldIntegrationTest
.put("shield.transport.ssl", sslTransportEnabled())
.put("shield.audit.enabled", false)
.put(InternalNode.HTTP_ENABLED, false)
.put(InternalSignatureService.FILE_SETTING, systemKeyFile)
.put(InternalCryptoService.FILE_SETTING, systemKeyFile)
.put("discovery.initial_state_timeout", "2s")
.build();
try (Node node = nodeBuilder().client(true).settings(nodeSettings).build()) {

View File

@ -16,7 +16,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.crypto.InternalCryptoService;
import org.elasticsearch.shield.transport.netty.ShieldNettyHttpServerTransport;
import org.elasticsearch.test.InternalTestCluster;
import org.elasticsearch.test.ShieldIntegrationTest;
@ -57,7 +57,7 @@ public class TribeTests extends ShieldIntegrationTest {
final boolean sslTransportEnabled = globalClusterSettings.getAsBoolean("shield.transport.ssl", null);
//we need to make sure that all clusters and the tribe node use the same system key, we just point to the same file on all clusters
byte[] systemKey = Files.readAllBytes(Paths.get(globalClusterSettings.get(InternalSignatureService.FILE_SETTING)));
byte[] systemKey = Files.readAllBytes(Paths.get(globalClusterSettings.get(InternalCryptoService.FILE_SETTING)));
//we run this part in @Before instead of beforeClass because we need to have the current cluster already assigned to global
//so that we can retrieve its settings and apply some of them the the second cluster (and tribe node too)
@ -124,7 +124,7 @@ public class TribeTests extends ShieldIntegrationTest {
return true;
}
//forward the system key to the tribe clients, same file will be used
if (settingKey.equals(InternalSignatureService.FILE_SETTING)) {
if (settingKey.equals(InternalCryptoService.FILE_SETTING)) {
return true;
}
//forward ssl settings to the tribe clients, same certificates will be used

View File

@ -15,7 +15,7 @@ import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.crypto.InternalCryptoService;
import org.elasticsearch.shield.test.ShieldTestUtils;
import org.elasticsearch.shield.transport.netty.ShieldNettyHttpServerTransport;
import org.elasticsearch.shield.transport.netty.ShieldNettyTransport;
@ -111,7 +111,7 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ
ImmutableSettings.Builder builder = ImmutableSettings.builder().put(super.node(nodeOrdinal))
.put("plugin.types", ShieldPlugin.class.getName() + "," + licensePluginClass().getName())
.put("shield.audit.enabled", randomBoolean())
.put(InternalSignatureService.FILE_SETTING, writeFile(folder, "system_key", systemKey))
.put(InternalCryptoService.FILE_SETTING, writeFile(folder, "system_key", systemKey))
.put("shield.authc.realms.esusers.type", ESUsersRealm.TYPE)
.put("shield.authc.realms.esusers.order", 0)
.put("shield.authc.realms.esusers.files.users", writeFile(folder, "users", configUsers()))
@ -180,7 +180,7 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ
private static byte[] generateKey() {
try {
return InternalSignatureService.generateKey();
return InternalCryptoService.generateKey();
} catch (Exception e) {
throw new ElasticsearchException("exception while generating the system key", e);
}