Skip to content

Commit 60d554c

Browse files
committed
Limit the scope of BouncyCastle dependency (elastic#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.
1 parent de93989 commit 60d554c

File tree

231 files changed

+5369
-1550
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

+5369
-1550
lines changed

x-pack/plugin/core/build.gradle

+1
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 <[email protected]>'
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

+1-2
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

+308
Large diffs are not rendered by default.
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)