Skip to content

Commit 8b6e89d

Browse files
authored
Limit the scope of BouncyCastle dependency (#30959)
* Limit the scope of BouncyCastle dependency (#30358) Limits the scope of the runtime dependency on BouncyCastle so that it can be eventually removed. * Splits functionality related to reading and generating certificates and keys in two utility classes so that reading certificates and keys doesn't require BouncyCastle. * Implements a class for parsing PEM Encoded key material (which also adds support for reading PKCS8 encoded encrypted private keys). * Removes BouncyCastle dependency for all of our test suites(except for the tests that explicitly test certificate generation) by using pre-generated keys/certificates/keystores. * Ensure intended key is selected in SamlAuthenticatorTests (#30993) Uses a specific keypair for tests that require a purposefully wrong keypair instead of selecting one randomly from the same pull from which the correct one is selected. Entropy is low because of the small space and the same key can be randomly selected as both the correct one and the wrong one, causing the tests to fail. The purposefully wrong key is also used in testSigningKeyIsReloadedForEachRequest and needs to be cleaned up afterwards so the rest of the tests don't use that for signing.
1 parent dd9c837 commit 8b6e89d

File tree

231 files changed

+5372
-1551
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

231 files changed

+5372
-1551
lines changed

x-pack/plugin/core/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ compileTestJava.options.compilerArgs << "-Xlint:-deprecation,-rawtypes,-serial,-
8888
licenseHeaders {
8989
approvedLicenses << 'BCrypt (BSD-like)'
9090
additionalLicense 'BCRYP', 'BCrypt (BSD-like)', 'Copyright (c) 2006 Damien Miller <djm@mindrot.org>'
91+
excludes << 'org/elasticsearch/xpack/core/ssl/DerParser.java'
9192
}
9293

9394
// make LicenseSigner available for testing signed licenses

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import org.apache.logging.log4j.Logger;
99
import org.apache.lucene.util.SetOnce;
10-
import org.bouncycastle.operator.OperatorCreationException;
1110
import org.elasticsearch.SpecialPermission;
1211
import org.elasticsearch.Version;
1312
import org.elasticsearch.action.ActionRequest;
@@ -125,7 +124,7 @@ public Void run() {
125124

126125
public XPackPlugin(
127126
final Settings settings,
128-
final Path configPath) throws IOException, DestroyFailedException, OperatorCreationException, GeneralSecurityException {
127+
final Path configPath) {
129128
super(settings);
130129
this.settings = settings;
131130
this.transportClientMode = transportClientMode(settings);

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

Lines changed: 308 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.core.ssl;
8+
9+
import org.elasticsearch.common.Nullable;
10+
import org.elasticsearch.common.SuppressForbidden;
11+
import org.elasticsearch.common.io.PathUtils;
12+
import org.elasticsearch.common.settings.SecureString;
13+
import org.elasticsearch.common.settings.Settings;
14+
import org.elasticsearch.env.Environment;
15+
16+
import javax.net.ssl.KeyManager;
17+
import javax.net.ssl.KeyManagerFactory;
18+
import javax.net.ssl.TrustManager;
19+
import javax.net.ssl.TrustManagerFactory;
20+
import javax.net.ssl.X509ExtendedKeyManager;
21+
import javax.net.ssl.X509ExtendedTrustManager;
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.security.Key;
28+
import java.security.KeyStore;
29+
import java.security.KeyStoreException;
30+
import java.security.NoSuchAlgorithmException;
31+
import java.security.PrivateKey;
32+
import java.security.UnrecoverableKeyException;
33+
import java.security.cert.Certificate;
34+
import java.security.cert.CertificateException;
35+
import java.security.cert.CertificateFactory;
36+
import java.security.cert.X509Certificate;
37+
import java.util.ArrayList;
38+
import java.util.Collection;
39+
import java.util.Collections;
40+
import java.util.Enumeration;
41+
import java.util.HashMap;
42+
import java.util.List;
43+
import java.util.Map;
44+
import java.util.function.Function;
45+
import java.util.stream.Collectors;
46+
47+
import static org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings.getKeyStoreType;
48+
49+
public class CertParsingUtils {
50+
51+
private CertParsingUtils() {
52+
throw new IllegalStateException("Utility class should not be instantiated");
53+
}
54+
/**
55+
* Resolves a path with or without an {@link Environment} as we may be running in a transport client where we do not have access to
56+
* the environment
57+
*/
58+
@SuppressForbidden(reason = "we don't have the environment to resolve files from when running in a transport client")
59+
static Path resolvePath(String path, @Nullable Environment environment) {
60+
if (environment != null) {
61+
return environment.configFile().resolve(path);
62+
}
63+
return PathUtils.get(path).normalize();
64+
}
65+
66+
static KeyStore readKeyStore(Path path, String type, char[] password)
67+
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
68+
try (InputStream in = Files.newInputStream(path)) {
69+
KeyStore store = KeyStore.getInstance(type);
70+
assert password != null;
71+
store.load(in, password);
72+
return store;
73+
}
74+
}
75+
76+
/**
77+
* Reads the provided paths and parses them into {@link Certificate} objects
78+
*
79+
* @param certPaths the paths to the PEM encoded certificates
80+
* @param environment the environment to resolve files against. May be {@code null}
81+
* @return an array of {@link Certificate} objects
82+
*/
83+
public static Certificate[] readCertificates(List<String> certPaths, @Nullable Environment environment)
84+
throws CertificateException, IOException {
85+
final List<Path> resolvedPaths = certPaths.stream().map(p -> resolvePath(p, environment)).collect(Collectors.toList());
86+
return readCertificates(resolvedPaths);
87+
}
88+
89+
public static Certificate[] readCertificates(List<Path> certPaths) throws CertificateException, IOException {
90+
Collection<Certificate> certificates = new ArrayList<>();
91+
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
92+
for (Path path : certPaths) {
93+
try (InputStream input = Files.newInputStream(path)) {
94+
certificates.addAll((Collection<Certificate>) certFactory.generateCertificates(input));
95+
}
96+
}
97+
return certificates.toArray(new Certificate[0]);
98+
}
99+
100+
public static X509Certificate[] readX509Certificates(List<Path> certPaths) throws CertificateException, IOException {
101+
Collection<X509Certificate> certificates = new ArrayList<>();
102+
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
103+
for (Path path : certPaths) {
104+
try (InputStream input = Files.newInputStream(path)) {
105+
certificates.addAll((Collection<X509Certificate>) certFactory.generateCertificates(input));
106+
}
107+
}
108+
return certificates.toArray(new X509Certificate[0]);
109+
}
110+
111+
static List<Certificate> readCertificates(InputStream input) throws CertificateException, IOException {
112+
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
113+
Collection<Certificate> certificates = (Collection<Certificate>) certFactory.generateCertificates(input);
114+
return new ArrayList<>(certificates);
115+
}
116+
117+
/**
118+
* Read all certificate-key pairs from a PKCS#12 container.
119+
*
120+
* @param path The path to the PKCS#12 container file.
121+
* @param password The password for the container file
122+
* @param keyPassword A supplier for the password for each key. The key alias is supplied as an argument to the function, and it should
123+
* return the password for that key. If it returns {@code null}, then the key-pair for that alias is not read.
124+
*/
125+
public static Map<Certificate, Key> readPkcs12KeyPairs(Path path, char[] password, Function<String, char[]> keyPassword)
126+
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
127+
final KeyStore store = readKeyStore(path, "PKCS12", password);
128+
final Enumeration<String> enumeration = store.aliases();
129+
final Map<Certificate, Key> map = new HashMap<>(store.size());
130+
while (enumeration.hasMoreElements()) {
131+
final String alias = enumeration.nextElement();
132+
if (store.isKeyEntry(alias)) {
133+
final char[] pass = keyPassword.apply(alias);
134+
map.put(store.getCertificate(alias), store.getKey(alias, pass));
135+
}
136+
}
137+
return map;
138+
}
139+
140+
/**
141+
* Creates a {@link KeyStore} from a PEM encoded certificate and key file
142+
*/
143+
static KeyStore getKeyStoreFromPEM(Path certificatePath, Path keyPath, char[] keyPassword)
144+
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
145+
final PrivateKey key = PemUtils.readPrivateKey(keyPath, () -> keyPassword);
146+
final Certificate[] certificates = readCertificates(Collections.singletonList(certificatePath));
147+
return getKeyStore(certificates, key, keyPassword);
148+
}
149+
150+
/**
151+
* Returns a {@link X509ExtendedKeyManager} that is built from the provided private key and certificate chain
152+
*/
153+
public static X509ExtendedKeyManager keyManager(Certificate[] certificateChain, PrivateKey privateKey, char[] password)
154+
throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException {
155+
KeyStore keyStore = getKeyStore(certificateChain, privateKey, password);
156+
return keyManager(keyStore, password, KeyManagerFactory.getDefaultAlgorithm());
157+
}
158+
159+
private static KeyStore getKeyStore(Certificate[] certificateChain, PrivateKey privateKey, char[] password)
160+
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
161+
KeyStore keyStore = KeyStore.getInstance("jks");
162+
keyStore.load(null, null);
163+
// password must be non-null for keystore...
164+
keyStore.setKeyEntry("key", privateKey, password, certificateChain);
165+
return keyStore;
166+
}
167+
168+
/**
169+
* Returns a {@link X509ExtendedKeyManager} that is built from the provided keystore
170+
*/
171+
static X509ExtendedKeyManager keyManager(KeyStore keyStore, char[] password, String algorithm)
172+
throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException {
173+
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
174+
kmf.init(keyStore, password);
175+
KeyManager[] keyManagers = kmf.getKeyManagers();
176+
for (KeyManager keyManager : keyManagers) {
177+
if (keyManager instanceof X509ExtendedKeyManager) {
178+
return (X509ExtendedKeyManager) keyManager;
179+
}
180+
}
181+
throw new IllegalStateException("failed to find a X509ExtendedKeyManager");
182+
}
183+
184+
public static X509ExtendedKeyManager getKeyManager(X509KeyPairSettings keyPair, Settings settings,
185+
@Nullable String trustStoreAlgorithm, Environment environment) {
186+
if (trustStoreAlgorithm == null) {
187+
trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
188+
}
189+
final KeyConfig keyConfig = createKeyConfig(keyPair, settings, trustStoreAlgorithm);
190+
if (keyConfig == null) {
191+
return null;
192+
} else {
193+
return keyConfig.createKeyManager(environment);
194+
}
195+
}
196+
197+
static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings, String trustStoreAlgorithm) {
198+
String keyPath = keyPair.keyPath.get(settings).orElse(null);
199+
String keyStorePath = keyPair.keystorePath.get(settings).orElse(null);
200+
201+
if (keyPath != null && keyStorePath != null) {
202+
throw new IllegalArgumentException("you cannot specify a keystore and key file");
203+
}
204+
205+
if (keyPath != null) {
206+
SecureString keyPassword = keyPair.keyPassword.get(settings);
207+
String certPath = keyPair.certificatePath.get(settings).orElse(null);
208+
if (certPath == null) {
209+
throw new IllegalArgumentException("you must specify the certificates [" + keyPair.certificatePath.getKey()
210+
+ "] to use with the key [" + keyPair.keyPath.getKey() + "]");
211+
}
212+
return new PEMKeyConfig(keyPath, keyPassword, certPath);
213+
}
214+
215+
if (keyStorePath != null) {
216+
SecureString keyStorePassword = keyPair.keystorePassword.get(settings);
217+
String keyStoreAlgorithm = keyPair.keystoreAlgorithm.get(settings);
218+
String keyStoreType = getKeyStoreType(keyPair.keystoreType, settings, keyStorePath);
219+
SecureString keyStoreKeyPassword = keyPair.keystoreKeyPassword.get(settings);
220+
if (keyStoreKeyPassword.length() == 0) {
221+
keyStoreKeyPassword = keyStorePassword;
222+
}
223+
return new StoreKeyConfig(keyStorePath, keyStoreType, keyStorePassword, keyStoreKeyPassword, keyStoreAlgorithm,
224+
trustStoreAlgorithm);
225+
}
226+
return null;
227+
228+
}
229+
230+
/**
231+
* Creates a {@link X509ExtendedTrustManager} based on the provided certificates
232+
*
233+
* @param certificates the certificates to trust
234+
* @return a trust manager that trusts the provided certificates
235+
*/
236+
public static X509ExtendedTrustManager trustManager(Certificate[] certificates)
237+
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
238+
KeyStore store = trustStore(certificates);
239+
return trustManager(store, TrustManagerFactory.getDefaultAlgorithm());
240+
}
241+
242+
static KeyStore trustStore(Certificate[] certificates)
243+
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
244+
assert certificates != null : "Cannot create trust store with null certificates";
245+
KeyStore store = KeyStore.getInstance("jks");
246+
store.load(null, null);
247+
int counter = 0;
248+
for (Certificate certificate : certificates) {
249+
store.setCertificateEntry("cert" + counter, certificate);
250+
counter++;
251+
}
252+
return store;
253+
}
254+
255+
/**
256+
* Loads the truststore and creates a {@link X509ExtendedTrustManager}
257+
*
258+
* @param trustStorePath the path to the truststore
259+
* @param trustStorePassword the password to the truststore
260+
* @param trustStoreAlgorithm the algorithm to use for the truststore
261+
* @param env the environment to use for file resolution. May be {@code null}
262+
* @return a trust manager with the trust material from the store
263+
*/
264+
public static X509ExtendedTrustManager trustManager(String trustStorePath, String trustStoreType, char[] trustStorePassword,
265+
String trustStoreAlgorithm, @Nullable Environment env)
266+
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
267+
KeyStore trustStore = readKeyStore(resolvePath(trustStorePath, env), trustStoreType, trustStorePassword);
268+
return trustManager(trustStore, trustStoreAlgorithm);
269+
}
270+
271+
/**
272+
* Creates a {@link X509ExtendedTrustManager} based on the trust material in the provided {@link KeyStore}
273+
*/
274+
static X509ExtendedTrustManager trustManager(KeyStore keyStore, String algorithm)
275+
throws NoSuchAlgorithmException, KeyStoreException {
276+
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
277+
tmf.init(keyStore);
278+
TrustManager[] trustManagers = tmf.getTrustManagers();
279+
for (TrustManager trustManager : trustManagers) {
280+
if (trustManager instanceof X509ExtendedTrustManager) {
281+
return (X509ExtendedTrustManager) trustManager;
282+
}
283+
}
284+
throw new IllegalStateException("failed to find a X509ExtendedTrustManager");
285+
}
286+
}

0 commit comments

Comments
 (0)