Skip to content

Improve errors when TLS files cannot be read #44787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ static Path resolvePath(String path, Environment environment) {
return environment.configFile().resolve(path);
}

static List<Path> resolvePaths(List<String> certPaths, Environment environment) {
return certPaths.stream().map(p -> environment.configFile().resolve(p)).collect(Collectors.toList());
}

public static KeyStore readKeyStore(Path path, String type, char[] password)
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
try (InputStream in = Files.newInputStream(path)) {
Expand All @@ -67,12 +71,12 @@ public static KeyStore readKeyStore(Path path, String type, char[] password)
* Reads the provided paths and parses them into {@link Certificate} objects
*
* @param certPaths the paths to the PEM encoded certificates
* @param environment the environment to resolve files against. May be {@code null}
* @param environment the environment to resolve files against. May be not be {@code null}
* @return an array of {@link Certificate} objects
*/
public static Certificate[] readCertificates(List<String> certPaths, @Nullable Environment environment)
public static Certificate[] readCertificates(List<String> certPaths, Environment environment)
throws CertificateException, IOException {
final List<Path> resolvedPaths = certPaths.stream().map(p -> environment.configFile().resolve(p)).collect(Collectors.toList());
final List<Path> resolvedPaths = resolvePaths(certPaths, environment);
return readCertificates(resolvedPaths);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
*/
package org.elasticsearch.xpack.core.ssl;

import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;

import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;

import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.PrivateKey;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -64,6 +68,31 @@ List<PrivateKey> privateKeys(@Nullable Environment environment) {

abstract X509ExtendedKeyManager createKeyManager(@Nullable Environment environment);

/**
* generate a new exception caused by a missing file, that is required for this key config
*/
static ElasticsearchException missingKeyConfigFile(IOException cause, String fileType, Path path) {
return new ElasticsearchException(
"failed to initialize SSL KeyManager - " + fileType + " file [{}] does not exist", cause, path.toAbsolutePath());
}

/**
* generate a new exception caused by an unreadable file (i.e. file-system access denied), that is required for this key config
*/
static ElasticsearchException unreadableKeyConfigFile(AccessDeniedException cause, String fileType, Path path) {
return new ElasticsearchException(
"failed to initialize SSL KeyManager - not permitted to read " + fileType + " file [{}]", cause, path.toAbsolutePath());
}

/**
* generate a new exception caused by a blocked file (i.e. security-manager access denied), that is required for this key config
*/
static ElasticsearchException blockedKeyConfigFile(AccessControlException cause, Environment environment, String fileType, Path path) {
return new ElasticsearchException(
"failed to initialize SSL KeyManager - access to read {} file [{}] is blocked;" +
" SSL resources should be placed in the [{}] directory", cause, fileType, path, environment.configFile());
}

abstract List<PrivateKey> privateKeys(@Nullable Environment environment);

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
Expand All @@ -35,6 +39,9 @@
*/
class PEMKeyConfig extends KeyConfig {

private static final String CERTIFICATE_FILE = "certificate";
private static final String KEY_FILE = "key";

private final String keyPath;
private final SecureString keyPassword;
private final String certPath;
Expand All @@ -55,20 +62,29 @@ class PEMKeyConfig extends KeyConfig {
@Override
X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
try {
PrivateKey privateKey = readPrivateKey(CertParsingUtils.resolvePath(keyPath, environment), keyPassword);
PrivateKey privateKey = readPrivateKey(keyPath, keyPassword, environment);
if (privateKey == null) {
throw new IllegalArgumentException("private key [" + keyPath + "] could not be loaded");
}
Certificate[] certificateChain = getCertificateChain(environment);

return CertParsingUtils.keyManager(certificateChain, privateKey, keyPassword.getChars());
} catch (IOException | UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
throw new ElasticsearchException("failed to initialize SSL KeyManagerFactory", e);
}
}

private Certificate[] getCertificateChain(@Nullable Environment environment) throws CertificateException, IOException {
return CertParsingUtils.readCertificates(Collections.singletonList(certPath), environment);
final Path certificate = CertParsingUtils.resolvePath(certPath, environment);
try {
return CertParsingUtils.readCertificates(Collections.singletonList(certificate));
} catch (FileNotFoundException | NoSuchFileException fileException) {
throw missingKeyConfigFile(fileException, CERTIFICATE_FILE, certificate);
} catch (AccessDeniedException accessException) {
throw unreadableKeyConfigFile(accessException, CERTIFICATE_FILE, certificate);
} catch (AccessControlException securityException) {
throw blockedKeyConfigFile(securityException, environment, CERTIFICATE_FILE, certificate);
}
}

@Override
Expand All @@ -87,14 +103,23 @@ Collection<CertificateInfo> certificates(Environment environment) throws Certifi
@Override
List<PrivateKey> privateKeys(@Nullable Environment environment) {
try {
return Collections.singletonList(readPrivateKey(CertParsingUtils.resolvePath(keyPath, environment), keyPassword));
return Collections.singletonList(readPrivateKey(keyPath, keyPassword, environment));
} catch (IOException e) {
throw new UncheckedIOException("failed to read key", e);
}
}

private static PrivateKey readPrivateKey(Path keyPath, SecureString keyPassword) throws IOException {
return PemUtils.readPrivateKey(keyPath, keyPassword::getChars);
private static PrivateKey readPrivateKey(String keyPath, SecureString keyPassword, Environment environment) throws IOException {
final Path key = CertParsingUtils.resolvePath(keyPath, environment);
try {
return PemUtils.readPrivateKey(key, keyPassword::getChars);
} catch (FileNotFoundException | NoSuchFileException fileException) {
throw missingKeyConfigFile(fileException, KEY_FILE, key);
} catch (AccessDeniedException accessException) {
throw unreadableKeyConfigFile(accessException, KEY_FILE, key);
} catch (AccessControlException securityException) {
throw blockedKeyConfigFile(securityException, environment, KEY_FILE, key);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
import javax.net.ssl.X509ExtendedTrustManager;

import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
Expand All @@ -29,10 +32,13 @@
*/
class PEMTrustConfig extends TrustConfig {

private static final String CA_FILE = "certificate_authorities";

private final List<String> caPaths;

/**
* Create a new trust configuration that is built from the certificate files
*
* @param caPaths the paths to the certificate files to trust
*/
PEMTrustConfig(List<String> caPaths) {
Expand All @@ -44,8 +50,17 @@ X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
try {
Certificate[] certificates = CertParsingUtils.readCertificates(caPaths, environment);
return CertParsingUtils.trustManager(certificates);
} catch (NoSuchFileException noSuchFileException) {
final Path missingPath = CertParsingUtils.resolvePath(noSuchFileException.getFile(), environment);
throw missingTrustConfigFile(noSuchFileException, CA_FILE, missingPath);
} catch (AccessDeniedException accessDeniedException) {
final Path missingPath = CertParsingUtils.resolvePath(accessDeniedException.getFile(), environment);
throw unreadableTrustConfigFile(accessDeniedException, CA_FILE, missingPath);
} catch (AccessControlException accessControlException) {
final List<Path> paths = CertParsingUtils.resolvePaths(caPaths, environment);
throw blockedTrustConfigFile(accessControlException, environment, CA_FILE, paths);
} catch (Exception e) {
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
throw new ElasticsearchException("failed to initialize SSL TrustManager", e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyException;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
Expand Down Expand Up @@ -72,7 +73,7 @@ private PemUtils() {
* @param passwordSupplier A password supplier for the potentially encrypted (password protected) key
* @return a private key from the contents of the file
*/
public static PrivateKey readPrivateKey(Path keyPath, Supplier<char[]> passwordSupplier) {
public static PrivateKey readPrivateKey(Path keyPath, Supplier<char[]> passwordSupplier) throws IOException {
try (BufferedReader bReader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) {
String line = bReader.readLine();
while (null != line && line.startsWith(HEADER) == false){
Expand Down Expand Up @@ -103,7 +104,7 @@ public static PrivateKey readPrivateKey(Path keyPath, Supplier<char[]> passwordS
throw new IllegalStateException("Error parsing Private Key from: " + keyPath.toString() + ". File did not contain a " +
"supported key format");
}
} catch (IOException | GeneralSecurityException e) {
} catch (GeneralSecurityException e) {
throw new IllegalStateException("Error parsing Private Key from: " + keyPath.toString(), e);
}
}
Expand Down Expand Up @@ -176,7 +177,7 @@ private static PrivateKey parsePKCS8(BufferedReader bReader) throws IOException,
line = bReader.readLine();
}
if (null == line || PKCS8_FOOTER.equals(line.trim()) == false) {
throw new IOException("Malformed PEM file, PEM footer is invalid or missing");
throw new KeyException("Malformed PEM file, PEM footer is invalid or missing");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was needed because readPrivateKey no longer catches & wraps IOException (which, in turn, was needed in order to have specific handling of NoSuchFileException)

}
byte[] keyBytes = Base64.getDecoder().decode(sb.toString());
String keyAlgo = getKeyAlgorithmIdentifier(keyBytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.CheckedSupplier;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
Expand Down Expand Up @@ -416,27 +417,33 @@ Map<SSLConfiguration, SSLContextHolder> loadSSLConfigurations() {
sslSettingsMap.putAll(getRealmsSSLSettings(settings));
sslSettingsMap.putAll(getMonitoringExporterSettings(settings));

sslSettingsMap.forEach((key, sslSettings) -> {
final SSLConfiguration configuration = new SSLConfiguration(sslSettings);
storeSslConfiguration(key, configuration);
sslContextHolders.computeIfAbsent(configuration, this::createSslContext);
});
sslSettingsMap.forEach((key, sslSettings) -> loadConfiguration(key, sslSettings, sslContextHolders));

final Settings transportSSLSettings = settings.getByPrefix(XPackSettings.TRANSPORT_SSL_PREFIX);
final SSLConfiguration transportSSLConfiguration = new SSLConfiguration(transportSSLSettings);
final SSLConfiguration transportSSLConfiguration =
loadConfiguration(XPackSettings.TRANSPORT_SSL_PREFIX, transportSSLSettings, sslContextHolders);
this.transportSSLConfiguration.set(transportSSLConfiguration);
storeSslConfiguration(XPackSettings.TRANSPORT_SSL_PREFIX, transportSSLConfiguration);
Map<String, Settings> profileSettings = getTransportProfileSSLSettings(settings);
sslContextHolders.computeIfAbsent(transportSSLConfiguration, this::createSslContext);
profileSettings.forEach((key, profileSetting) -> {
final SSLConfiguration configuration = new SSLConfiguration(profileSetting);
storeSslConfiguration(key, configuration);
sslContextHolders.computeIfAbsent(configuration, this::createSslContext);
});
profileSettings.forEach((key, profileSetting) -> loadConfiguration(key, profileSetting, sslContextHolders));

return Collections.unmodifiableMap(sslContextHolders);
}

private SSLConfiguration loadConfiguration(String key, Settings settings, Map<SSLConfiguration, SSLContextHolder> contextHolders) {
if (key.endsWith(".")) {
// Drop trailing '.' so that any exception messages are consistent
key = key.substring(0, key.length() - 1);
}
try {
final SSLConfiguration configuration = new SSLConfiguration(settings);
storeSslConfiguration(key, configuration);
contextHolders.computeIfAbsent(configuration, this::createSslContext);
return configuration;
} catch (Exception e) {
throw new ElasticsearchSecurityException("failed to load SSL configuration [{}]", e, key);
}
}

private void storeSslConfiguration(String key, SSLConfiguration configuration) {
if (key.endsWith(".")) {
key = key.substring(0, key.length() - 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -38,6 +39,8 @@
*/
class StoreKeyConfig extends KeyConfig {

private static final String KEYSTORE_FILE = "keystore";

final String keyStorePath;
final String keyStoreType;
final SecureString keyStorePassword;
Expand Down Expand Up @@ -68,28 +71,42 @@ class StoreKeyConfig extends KeyConfig {

@Override
X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
Path ksPath = keyStorePath == null ? null : CertParsingUtils.resolvePath(keyStorePath, environment);
try {
KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
KeyStore ks = getStore(ksPath, keyStoreType, keyStorePassword);
checkKeyStore(ks);
return CertParsingUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm);
} catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
} catch (FileNotFoundException | NoSuchFileException e) {
throw missingKeyConfigFile(e, KEYSTORE_FILE, ksPath);
} catch (AccessDeniedException e) {
throw unreadableKeyConfigFile(e, KEYSTORE_FILE, ksPath);
} catch (AccessControlException e) {
throw blockedKeyConfigFile(e, environment, KEYSTORE_FILE, ksPath);
} catch (IOException | GeneralSecurityException e) {
throw new ElasticsearchException("failed to initialize SSL KeyManager", e);
}
}

@Override
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
final Path ksPath = CertParsingUtils.resolvePath(keyStorePath, environment);
try {
KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
KeyStore ks = getStore(ksPath, keyStoreType, keyStorePassword);
return CertParsingUtils.trustManager(ks, trustStoreAlgorithm);
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
} catch (FileNotFoundException | NoSuchFileException e) {
throw missingTrustConfigFile(e, KEYSTORE_FILE, ksPath);
} catch (AccessDeniedException e) {
throw missingTrustConfigFile(e, KEYSTORE_FILE, ksPath);
} catch (AccessControlException e) {
throw blockedTrustConfigFile(e, environment, KEYSTORE_FILE, List.of(ksPath));
} catch (IOException | GeneralSecurityException e) {
throw new ElasticsearchException("failed to initialize SSL TrustManager", e);
}
}

@Override
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
final KeyStore trustStore = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
final KeyStore trustStore = getStore(CertParsingUtils.resolvePath(keyStorePath, environment), keyStoreType, keyStorePassword);
final List<CertificateInfo> certificates = new ArrayList<>();
final Enumeration<String> aliases = trustStore.aliases();
while (aliases.hasMoreElements()) {
Expand Down
Loading