Skip to content

Commit 7a0a4a5

Browse files
tvernumjkakavas
authored andcommitted
Improve errors when TLS files cannot be read (#44787)
This change improves the exception messages that are thrown when the system cannot read TLS resources such as keystores, truststores, certificates, keys or certificate-chains (CAs). This change specifically handles: - Files that do not exist - Files that cannot be read due to file-system permissions - Files that cannot be read due to the ES security-manager Relates: #43079
1 parent 5efc017 commit 7a0a4a5

File tree

23 files changed

+754
-54
lines changed

23 files changed

+754
-54
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertParsingUtils.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ static Path resolvePath(String path, Environment environment) {
5353
return environment.configFile().resolve(path);
5454
}
5555

56+
static List<Path> resolvePaths(List<String> certPaths, Environment environment) {
57+
return certPaths.stream().map(p -> environment.configFile().resolve(p)).collect(Collectors.toList());
58+
}
59+
5660
public static KeyStore readKeyStore(Path path, String type, char[] password)
5761
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
5862
try (InputStream in = Files.newInputStream(path)) {
@@ -67,12 +71,12 @@ public static KeyStore readKeyStore(Path path, String type, char[] password)
6771
* Reads the provided paths and parses them into {@link Certificate} objects
6872
*
6973
* @param certPaths the paths to the PEM encoded certificates
70-
* @param environment the environment to resolve files against. May be {@code null}
74+
* @param environment the environment to resolve files against. May be not be {@code null}
7175
* @return an array of {@link Certificate} objects
7276
*/
73-
public static Certificate[] readCertificates(List<String> certPaths, @Nullable Environment environment)
77+
public static Certificate[] readCertificates(List<String> certPaths, Environment environment)
7478
throws CertificateException, IOException {
75-
final List<Path> resolvedPaths = certPaths.stream().map(p -> environment.configFile().resolve(p)).collect(Collectors.toList());
79+
final List<Path> resolvedPaths = resolvePaths(certPaths, environment);
7680
return readCertificates(resolvedPaths);
7781
}
7882

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/KeyConfig.java

+29
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
*/
66
package org.elasticsearch.xpack.core.ssl;
77

8+
import org.elasticsearch.ElasticsearchException;
89
import org.elasticsearch.common.Nullable;
910
import org.elasticsearch.env.Environment;
1011
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
1112

1213
import javax.net.ssl.X509ExtendedKeyManager;
1314
import javax.net.ssl.X509ExtendedTrustManager;
1415

16+
import java.io.IOException;
17+
import java.nio.file.AccessDeniedException;
1518
import java.nio.file.Path;
19+
import java.security.AccessControlException;
1620
import java.security.PrivateKey;
1721
import java.util.Collection;
1822
import java.util.Collections;
@@ -64,6 +68,31 @@ List<PrivateKey> privateKeys(@Nullable Environment environment) {
6468

6569
abstract X509ExtendedKeyManager createKeyManager(@Nullable Environment environment);
6670

71+
/**
72+
* generate a new exception caused by a missing file, that is required for this key config
73+
*/
74+
static ElasticsearchException missingKeyConfigFile(IOException cause, String fileType, Path path) {
75+
return new ElasticsearchException(
76+
"failed to initialize SSL KeyManager - " + fileType + " file [{}] does not exist", cause, path.toAbsolutePath());
77+
}
78+
79+
/**
80+
* generate a new exception caused by an unreadable file (i.e. file-system access denied), that is required for this key config
81+
*/
82+
static ElasticsearchException unreadableKeyConfigFile(AccessDeniedException cause, String fileType, Path path) {
83+
return new ElasticsearchException(
84+
"failed to initialize SSL KeyManager - not permitted to read " + fileType + " file [{}]", cause, path.toAbsolutePath());
85+
}
86+
87+
/**
88+
* generate a new exception caused by a blocked file (i.e. security-manager access denied), that is required for this key config
89+
*/
90+
static ElasticsearchException blockedKeyConfigFile(AccessControlException cause, Environment environment, String fileType, Path path) {
91+
return new ElasticsearchException(
92+
"failed to initialize SSL KeyManager - access to read {} file [{}] is blocked;" +
93+
" SSL resources should be placed in the [{}] directory", cause, fileType, path, environment.configFile());
94+
}
95+
6796
abstract List<PrivateKey> privateKeys(@Nullable Environment environment);
6897

6998
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PEMKeyConfig.java

+31-6
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414
import javax.net.ssl.X509ExtendedKeyManager;
1515
import javax.net.ssl.X509ExtendedTrustManager;
1616

17+
import java.io.FileNotFoundException;
1718
import java.io.IOException;
1819
import java.io.UncheckedIOException;
20+
import java.nio.file.AccessDeniedException;
21+
import java.nio.file.NoSuchFileException;
1922
import java.nio.file.Path;
23+
import java.security.AccessControlException;
2024
import java.security.KeyStoreException;
2125
import java.security.NoSuchAlgorithmException;
2226
import java.security.PrivateKey;
@@ -35,6 +39,9 @@
3539
*/
3640
class PEMKeyConfig extends KeyConfig {
3741

42+
private static final String CERTIFICATE_FILE = "certificate";
43+
private static final String KEY_FILE = "key";
44+
3845
private final String keyPath;
3946
private final SecureString keyPassword;
4047
private final String certPath;
@@ -55,20 +62,29 @@ class PEMKeyConfig extends KeyConfig {
5562
@Override
5663
X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
5764
try {
58-
PrivateKey privateKey = readPrivateKey(CertParsingUtils.resolvePath(keyPath, environment), keyPassword);
65+
PrivateKey privateKey = readPrivateKey(keyPath, keyPassword, environment);
5966
if (privateKey == null) {
6067
throw new IllegalArgumentException("private key [" + keyPath + "] could not be loaded");
6168
}
6269
Certificate[] certificateChain = getCertificateChain(environment);
6370

6471
return CertParsingUtils.keyManager(certificateChain, privateKey, keyPassword.getChars());
6572
} catch (IOException | UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
66-
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
73+
throw new ElasticsearchException("failed to initialize SSL KeyManagerFactory", e);
6774
}
6875
}
6976

7077
private Certificate[] getCertificateChain(@Nullable Environment environment) throws CertificateException, IOException {
71-
return CertParsingUtils.readCertificates(Collections.singletonList(certPath), environment);
78+
final Path certificate = CertParsingUtils.resolvePath(certPath, environment);
79+
try {
80+
return CertParsingUtils.readCertificates(Collections.singletonList(certificate));
81+
} catch (FileNotFoundException | NoSuchFileException fileException) {
82+
throw missingKeyConfigFile(fileException, CERTIFICATE_FILE, certificate);
83+
} catch (AccessDeniedException accessException) {
84+
throw unreadableKeyConfigFile(accessException, CERTIFICATE_FILE, certificate);
85+
} catch (AccessControlException securityException) {
86+
throw blockedKeyConfigFile(securityException, environment, CERTIFICATE_FILE, certificate);
87+
}
7288
}
7389

7490
@Override
@@ -87,14 +103,23 @@ Collection<CertificateInfo> certificates(Environment environment) throws Certifi
87103
@Override
88104
List<PrivateKey> privateKeys(@Nullable Environment environment) {
89105
try {
90-
return Collections.singletonList(readPrivateKey(CertParsingUtils.resolvePath(keyPath, environment), keyPassword));
106+
return Collections.singletonList(readPrivateKey(keyPath, keyPassword, environment));
91107
} catch (IOException e) {
92108
throw new UncheckedIOException("failed to read key", e);
93109
}
94110
}
95111

96-
private static PrivateKey readPrivateKey(Path keyPath, SecureString keyPassword) throws IOException {
97-
return PemUtils.readPrivateKey(keyPath, keyPassword::getChars);
112+
private static PrivateKey readPrivateKey(String keyPath, SecureString keyPassword, Environment environment) throws IOException {
113+
final Path key = CertParsingUtils.resolvePath(keyPath, environment);
114+
try {
115+
return PemUtils.readPrivateKey(key, keyPassword::getChars);
116+
} catch (FileNotFoundException | NoSuchFileException fileException) {
117+
throw missingKeyConfigFile(fileException, KEY_FILE, key);
118+
} catch (AccessDeniedException accessException) {
119+
throw unreadableKeyConfigFile(accessException, KEY_FILE, key);
120+
} catch (AccessControlException securityException) {
121+
throw blockedKeyConfigFile(securityException, environment, KEY_FILE, key);
122+
}
98123
}
99124

100125
@Override

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PEMTrustConfig.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
import javax.net.ssl.X509ExtendedTrustManager;
1515

1616
import java.io.IOException;
17+
import java.nio.file.AccessDeniedException;
18+
import java.nio.file.NoSuchFileException;
1719
import java.nio.file.Path;
20+
import java.security.AccessControlException;
1821
import java.security.cert.Certificate;
1922
import java.security.cert.CertificateException;
2023
import java.security.cert.X509Certificate;
@@ -29,10 +32,13 @@
2932
*/
3033
class PEMTrustConfig extends TrustConfig {
3134

35+
private static final String CA_FILE = "certificate_authorities";
36+
3237
private final List<String> caPaths;
3338

3439
/**
3540
* Create a new trust configuration that is built from the certificate files
41+
*
3642
* @param caPaths the paths to the certificate files to trust
3743
*/
3844
PEMTrustConfig(List<String> caPaths) {
@@ -44,8 +50,17 @@ X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
4450
try {
4551
Certificate[] certificates = CertParsingUtils.readCertificates(caPaths, environment);
4652
return CertParsingUtils.trustManager(certificates);
53+
} catch (NoSuchFileException noSuchFileException) {
54+
final Path missingPath = CertParsingUtils.resolvePath(noSuchFileException.getFile(), environment);
55+
throw missingTrustConfigFile(noSuchFileException, CA_FILE, missingPath);
56+
} catch (AccessDeniedException accessDeniedException) {
57+
final Path missingPath = CertParsingUtils.resolvePath(accessDeniedException.getFile(), environment);
58+
throw unreadableTrustConfigFile(accessDeniedException, CA_FILE, missingPath);
59+
} catch (AccessControlException accessControlException) {
60+
final List<Path> paths = CertParsingUtils.resolvePaths(caPaths, environment);
61+
throw blockedTrustConfigFile(accessControlException, environment, CA_FILE, paths);
4762
} catch (Exception e) {
48-
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
63+
throw new ElasticsearchException("failed to initialize SSL TrustManager", e);
4964
}
5065
}
5166

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PemUtils.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.nio.file.Files;
1717
import java.nio.file.Path;
1818
import java.security.GeneralSecurityException;
19+
import java.security.KeyException;
1920
import java.security.KeyFactory;
2021
import java.security.KeyPairGenerator;
2122
import java.security.MessageDigest;
@@ -72,7 +73,7 @@ private PemUtils() {
7273
* @param passwordSupplier A password supplier for the potentially encrypted (password protected) key
7374
* @return a private key from the contents of the file
7475
*/
75-
public static PrivateKey readPrivateKey(Path keyPath, Supplier<char[]> passwordSupplier) {
76+
public static PrivateKey readPrivateKey(Path keyPath, Supplier<char[]> passwordSupplier) throws IOException {
7677
try (BufferedReader bReader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) {
7778
String line = bReader.readLine();
7879
while (null != line && line.startsWith(HEADER) == false){
@@ -103,7 +104,7 @@ public static PrivateKey readPrivateKey(Path keyPath, Supplier<char[]> passwordS
103104
throw new IllegalStateException("Error parsing Private Key from: " + keyPath.toString() + ". File did not contain a " +
104105
"supported key format");
105106
}
106-
} catch (IOException | GeneralSecurityException e) {
107+
} catch (GeneralSecurityException e) {
107108
throw new IllegalStateException("Error parsing Private Key from: " + keyPath.toString(), e);
108109
}
109110
}
@@ -176,7 +177,7 @@ private static PrivateKey parsePKCS8(BufferedReader bReader) throws IOException,
176177
line = bReader.readLine();
177178
}
178179
if (null == line || PKCS8_FOOTER.equals(line.trim()) == false) {
179-
throw new IOException("Malformed PEM file, PEM footer is invalid or missing");
180+
throw new KeyException("Malformed PEM file, PEM footer is invalid or missing");
180181
}
181182
byte[] keyBytes = Base64.getDecoder().decode(sb.toString());
182183
String keyAlgo = getKeyAlgorithmIdentifier(keyBytes);

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java

+20-13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.apache.logging.log4j.Logger;
1212
import org.apache.lucene.util.SetOnce;
1313
import org.elasticsearch.ElasticsearchException;
14+
import org.elasticsearch.ElasticsearchSecurityException;
1415
import org.elasticsearch.common.CheckedSupplier;
1516
import org.elasticsearch.common.Strings;
1617
import org.elasticsearch.common.settings.Settings;
@@ -416,27 +417,33 @@ Map<SSLConfiguration, SSLContextHolder> loadSSLConfigurations() {
416417
sslSettingsMap.putAll(getRealmsSSLSettings(settings));
417418
sslSettingsMap.putAll(getMonitoringExporterSettings(settings));
418419

419-
sslSettingsMap.forEach((key, sslSettings) -> {
420-
final SSLConfiguration configuration = new SSLConfiguration(sslSettings);
421-
storeSslConfiguration(key, configuration);
422-
sslContextHolders.computeIfAbsent(configuration, this::createSslContext);
423-
});
420+
sslSettingsMap.forEach((key, sslSettings) -> loadConfiguration(key, sslSettings, sslContextHolders));
424421

425422
final Settings transportSSLSettings = settings.getByPrefix(XPackSettings.TRANSPORT_SSL_PREFIX);
426-
final SSLConfiguration transportSSLConfiguration = new SSLConfiguration(transportSSLSettings);
423+
final SSLConfiguration transportSSLConfiguration =
424+
loadConfiguration(XPackSettings.TRANSPORT_SSL_PREFIX, transportSSLSettings, sslContextHolders);
427425
this.transportSSLConfiguration.set(transportSSLConfiguration);
428-
storeSslConfiguration(XPackSettings.TRANSPORT_SSL_PREFIX, transportSSLConfiguration);
429426
Map<String, Settings> profileSettings = getTransportProfileSSLSettings(settings);
430-
sslContextHolders.computeIfAbsent(transportSSLConfiguration, this::createSslContext);
431-
profileSettings.forEach((key, profileSetting) -> {
432-
final SSLConfiguration configuration = new SSLConfiguration(profileSetting);
433-
storeSslConfiguration(key, configuration);
434-
sslContextHolders.computeIfAbsent(configuration, this::createSslContext);
435-
});
427+
profileSettings.forEach((key, profileSetting) -> loadConfiguration(key, profileSetting, sslContextHolders));
436428

437429
return Collections.unmodifiableMap(sslContextHolders);
438430
}
439431

432+
private SSLConfiguration loadConfiguration(String key, Settings settings, Map<SSLConfiguration, SSLContextHolder> contextHolders) {
433+
if (key.endsWith(".")) {
434+
// Drop trailing '.' so that any exception messages are consistent
435+
key = key.substring(0, key.length() - 1);
436+
}
437+
try {
438+
final SSLConfiguration configuration = new SSLConfiguration(settings);
439+
storeSslConfiguration(key, configuration);
440+
contextHolders.computeIfAbsent(configuration, this::createSslContext);
441+
return configuration;
442+
} catch (Exception e) {
443+
throw new ElasticsearchSecurityException("failed to load SSL configuration [{}]", e, key);
444+
}
445+
}
446+
440447
private void storeSslConfiguration(String key, SSLConfiguration configuration) {
441448
if (key.endsWith(".")) {
442449
key = key.substring(0, key.length() - 1);

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/StoreKeyConfig.java

+27-10
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,18 @@
1414
import javax.net.ssl.X509ExtendedKeyManager;
1515
import javax.net.ssl.X509ExtendedTrustManager;
1616

17+
import java.io.FileNotFoundException;
1718
import java.io.IOException;
19+
import java.nio.file.AccessDeniedException;
20+
import java.nio.file.NoSuchFileException;
1821
import java.nio.file.Path;
22+
import java.security.AccessControlException;
1923
import java.security.GeneralSecurityException;
2024
import java.security.Key;
2125
import java.security.KeyStore;
2226
import java.security.KeyStoreException;
23-
import java.security.NoSuchAlgorithmException;
2427
import java.security.PrivateKey;
25-
import java.security.UnrecoverableKeyException;
2628
import java.security.cert.Certificate;
27-
import java.security.cert.CertificateException;
2829
import java.security.cert.X509Certificate;
2930
import java.util.ArrayList;
3031
import java.util.Collection;
@@ -38,6 +39,8 @@
3839
*/
3940
class StoreKeyConfig extends KeyConfig {
4041

42+
private static final String KEYSTORE_FILE = "keystore";
43+
4144
final String keyStorePath;
4245
final String keyStoreType;
4346
final SecureString keyStorePassword;
@@ -68,28 +71,42 @@ class StoreKeyConfig extends KeyConfig {
6871

6972
@Override
7073
X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
74+
Path ksPath = keyStorePath == null ? null : CertParsingUtils.resolvePath(keyStorePath, environment);
7175
try {
72-
KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
76+
KeyStore ks = getStore(ksPath, keyStoreType, keyStorePassword);
7377
checkKeyStore(ks);
7478
return CertParsingUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm);
75-
} catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
76-
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
79+
} catch (FileNotFoundException | NoSuchFileException e) {
80+
throw missingKeyConfigFile(e, KEYSTORE_FILE, ksPath);
81+
} catch (AccessDeniedException e) {
82+
throw unreadableKeyConfigFile(e, KEYSTORE_FILE, ksPath);
83+
} catch (AccessControlException e) {
84+
throw blockedKeyConfigFile(e, environment, KEYSTORE_FILE, ksPath);
85+
} catch (IOException | GeneralSecurityException e) {
86+
throw new ElasticsearchException("failed to initialize SSL KeyManager", e);
7787
}
7888
}
7989

8090
@Override
8191
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
92+
final Path ksPath = CertParsingUtils.resolvePath(keyStorePath, environment);
8293
try {
83-
KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
94+
KeyStore ks = getStore(ksPath, keyStoreType, keyStorePassword);
8495
return CertParsingUtils.trustManager(ks, trustStoreAlgorithm);
85-
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
86-
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
96+
} catch (FileNotFoundException | NoSuchFileException e) {
97+
throw missingTrustConfigFile(e, KEYSTORE_FILE, ksPath);
98+
} catch (AccessDeniedException e) {
99+
throw missingTrustConfigFile(e, KEYSTORE_FILE, ksPath);
100+
} catch (AccessControlException e) {
101+
throw blockedTrustConfigFile(e, environment, KEYSTORE_FILE, List.of(ksPath));
102+
} catch (IOException | GeneralSecurityException e) {
103+
throw new ElasticsearchException("failed to initialize SSL TrustManager", e);
87104
}
88105
}
89106

90107
@Override
91108
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
92-
final KeyStore trustStore = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
109+
final KeyStore trustStore = getStore(CertParsingUtils.resolvePath(keyStorePath, environment), keyStoreType, keyStorePassword);
93110
final List<CertificateInfo> certificates = new ArrayList<>();
94111
final Enumeration<String> aliases = trustStore.aliases();
95112
while (aliases.hasMoreElements()) {

0 commit comments

Comments
 (0)