From e7d06843f9cce7ea3f3e22606cf1dc196658f801 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Thu, 26 Nov 2020 15:57:33 +0200 Subject: [PATCH] Gracefully handle exceptions from Security Providers (#65464) In certain situations, such as when configured in FIPS 140 mode, the Java security provider in use might throw a subclass of java.lang.Error. We currently do not catch these and as a result the JVM exits, shutting down elasticsearch. This commit attempts to address this by catching subclasses of Error that might be thrown for instance when a PBKDF2 implementation is used from a Security Provider in FIPS 140 mode, with the password input being less than 14 bytes (112 bits). - In our PBKDF2 family of hashers, we catch the Error and throw an ElasticsearchException while creating or verifying the hash. We throw on verification instead of simply returning false on purpose so that the message bubbles up and the cause becomes obvious (otherwise it would be indistinguishable from a wrong password). - In KeyStoreWrapper, we catch the Error in order to wrap and re-throw a GeneralSecurityException with a helpful message. This can happen when using any of the keystore CLI commands, when the node starts or when we attempt to reload secure settings. - In the `elasticsearch-users` tool, we catch the ElasticsearchException that the Hasher class re-throws and throw an appropriate UserException. Tests are missing because it's not trivial to set CI in fips approved mode right now, and thus any tests would need to be muted. There is a parallel effort in #64024 to enable that and tests will be added in a followup. --- .../common/settings/BaseKeyStoreCommand.java | 3 ++- .../org/elasticsearch/cli/KeyStoreAwareCommand.java | 4 ++-- .../common/settings/KeyStoreWrapper.java | 9 ++++++++- .../xpack/core/security/authc/support/Hasher.java | 12 ++++++++++-- .../xpack/security/authc/file/tool/UsersTool.java | 4 ++++ 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/distribution/tools/keystore-cli/src/main/java/org/elasticsearch/common/settings/BaseKeyStoreCommand.java b/distribution/tools/keystore-cli/src/main/java/org/elasticsearch/common/settings/BaseKeyStoreCommand.java index 0ba7c30c1c50..c481295399cb 100644 --- a/distribution/tools/keystore-cli/src/main/java/org/elasticsearch/common/settings/BaseKeyStoreCommand.java +++ b/distribution/tools/keystore-cli/src/main/java/org/elasticsearch/common/settings/BaseKeyStoreCommand.java @@ -28,6 +28,7 @@ import org.elasticsearch.cli.UserException; import org.elasticsearch.env.Environment; import java.nio.file.Path; +import java.security.GeneralSecurityException; public abstract class BaseKeyStoreCommand extends KeyStoreAwareCommand { @@ -68,7 +69,7 @@ public abstract class BaseKeyStoreCommand extends KeyStoreAwareCommand { keyStore.decrypt(keyStorePassword.getChars()); } executeCommand(terminal, options, env); - } catch (SecurityException e) { + } catch (SecurityException | GeneralSecurityException e) { throw new UserException(ExitCodes.DATA_ERROR, e.getMessage()); } finally { if (keyStorePassword != null) { diff --git a/server/src/main/java/org/elasticsearch/cli/KeyStoreAwareCommand.java b/server/src/main/java/org/elasticsearch/cli/KeyStoreAwareCommand.java index 201d45f54b0e..ab6043026db5 100644 --- a/server/src/main/java/org/elasticsearch/cli/KeyStoreAwareCommand.java +++ b/server/src/main/java/org/elasticsearch/cli/KeyStoreAwareCommand.java @@ -70,11 +70,11 @@ public abstract class KeyStoreAwareCommand extends EnvironmentAwareCommand { * Decrypt the {@code keyStore}, prompting the user to enter the password in the {@link Terminal} if it is password protected */ protected static void decryptKeyStore(KeyStoreWrapper keyStore, Terminal terminal) - throws UserException, GeneralSecurityException, IOException { + throws UserException, IOException { try (SecureString keystorePassword = keyStore.hasPassword() ? readPassword(terminal, false) : new SecureString(new char[0])) { keyStore.decrypt(keystorePassword.getChars()); - } catch (SecurityException e) { + } catch (SecurityException | GeneralSecurityException e) { throw new UserException(ExitCodes.DATA_ERROR, e.getMessage()); } } diff --git a/server/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java b/server/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java index d3080df034ca..d75456d4d9ba 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java +++ b/server/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java @@ -310,7 +310,14 @@ public class KeyStoreWrapper implements SecureSettings { private Cipher createCipher(int opmode, char[] password, byte[] salt, byte[] iv) throws GeneralSecurityException { PBEKeySpec keySpec = new PBEKeySpec(password, salt, KDF_ITERS, CIPHER_KEY_BITS); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KDF_ALGO); - SecretKey secretKey = keyFactory.generateSecret(keySpec); + SecretKey secretKey; + try { + secretKey = keyFactory.generateSecret(keySpec); + } catch (Error e) { + // Security Providers might throw a subclass of Error in FIPS 140 mode, if some prerequisite like + // salt, iv, or password length is not met. We catch this because we don't want the JVM to exit. + throw new GeneralSecurityException("Error generating an encryption key from the provided password", e); + } SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), CIPHER_ALGO); GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BITS, iv); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java index 2d9b3e7c3e9e..2739e94b04dd 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java @@ -622,7 +622,11 @@ public enum Hasher { result.put(Base64.getEncoder().encodeToString(secretKeyFactory.generateSecret(keySpec).getEncoded())); return result.array(); } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { - throw new ElasticsearchException("Can't use PBKDF2 for password hashing", e); + throw new ElasticsearchException("Error using PBKDF2 for password hashing", e); + } catch (Error e) { + // Security Providers might throw a subclass of Error in FIPS 140 mode, if some prerequisite like + // salt, iv, or password length is not met. We catch this because we don't want the JVM to exit. + throw new ElasticsearchException("Error using PBKDF2 implementation from the selected Security Provider", e); } } @@ -648,7 +652,11 @@ public enum Hasher { final boolean result = CharArrays.constantTimeEquals(computedPwdHash, hashChars); return result; } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { - throw new ElasticsearchException("Can't use PBKDF2 for password hashing", e); + throw new ElasticsearchException("Error using PBKDF2 for password hashing", e); + } catch (Error e) { + // Security Providers might throw a subclass of Error in FIPS 140 mode, if some prerequisite like + // salt, iv, or password length is not met. We catch this because we don't want the JVM to exit. + throw new ElasticsearchException("Error using PBKDF2 implementation from the selected Security Provider", e); } finally { if (null != hashChars) { Arrays.fill(hashChars, '\u0000'); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java index df6a86fe126b..eaa4864a81b3 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.authc.file.tool; import joptsimple.OptionSet; import joptsimple.OptionSpec; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.cli.EnvironmentAwareCommand; import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.LoggingAwareMultiCommand; @@ -446,7 +447,10 @@ public class UsersTool extends LoggingAwareMultiCommand { final char[] passwordHash; try (SecureString password = parsePassword(terminal, cliPasswordValue)) { passwordHash = hasher.hash(password); + } catch (ElasticsearchException e) { + throw new UserException(ExitCodes.DATA_ERROR, "Error storing the password for the new user", e); } + return passwordHash; }