From e417040127372811eda23d5f63a417a96bb904a9 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Thu, 10 Jan 2019 15:48:36 +1100 Subject: [PATCH 1/8] Add SSL Configuration Library This introduces a new ssl-config library that can parse and validate SSL/TLS settings and files. It supports the standard configuration settings as used in the Elastic Stack such as "ssl.verification_mode" and "ssl.certificate_authorities" as well as all file formats used in other parts of Elasticsearch security (such as PEM, JKS, PKCS#12, PKCS#8, et al). --- libs/ssl-config/build.gradle | 45 ++ .../common/ssl/DefaultJdkTrustConfig.java | 128 ++++ .../elasticsearch/common/ssl/DerParser.java | 299 +++++++++ .../common/ssl/EmptyKeyConfig.java | 52 ++ .../common/ssl/KeyStoreUtil.java | 157 +++++ .../common/ssl/PemKeyConfig.java | 124 ++++ .../common/ssl/PemTrustConfig.java | 122 ++++ .../elasticsearch/common/ssl/PemUtils.java | 609 ++++++++++++++++++ .../ssl/SslClientAuthenticationMode.java | 97 +++ .../common/ssl/SslConfigException.java | 33 + .../common/ssl/SslConfiguration.java | 160 +++++ .../common/ssl/SslConfigurationKeys.java | 181 ++++++ .../common/ssl/SslConfigurationLoader.java | 366 +++++++++++ .../common/ssl/SslKeyConfig.java | 46 ++ .../common/ssl/SslTrustConfig.java | 46 ++ .../common/ssl/SslVerificationMode.java | 100 +++ .../common/ssl/StoreKeyConfig.java | 106 +++ .../common/ssl/StoreTrustConfig.java | 90 +++ .../common/ssl/TrustEverythingConfig.java | 92 +++ .../ssl/DefaultJdkTrustConfigTests.java | 79 +++ .../common/ssl/PemKeyConfigTests.java | 148 +++++ .../common/ssl/PemTrustConfigTests.java | 132 ++++ .../common/ssl/PemUtilsTests.java | 219 +++++++ .../ssl/SslConfigurationLoaderTests.java | 219 +++++++ .../common/ssl/SslConfigurationTests.java | 140 ++++ .../common/ssl/StoreKeyConfigTests.java | 217 +++++++ .../common/ssl/StoreTrustConfigTests.java | 169 +++++ .../src/test/resources/certs/README.txt | 69 ++ .../src/test/resources/certs/ca-all/ca.jks | Bin 0 -> 2460 bytes .../src/test/resources/certs/ca-all/ca.p12 | Bin 0 -> 2818 bytes .../src/test/resources/certs/ca1/ca.crt | 19 + .../src/test/resources/certs/ca1/ca.key | 27 + .../src/test/resources/certs/ca1/ca.p12 | Bin 0 -> 1066 bytes .../src/test/resources/certs/ca2/ca.crt | 19 + .../src/test/resources/certs/ca2/ca.key | 27 + .../src/test/resources/certs/ca2/ca.p12 | Bin 0 -> 1066 bytes .../src/test/resources/certs/ca3/ca.crt | 19 + .../src/test/resources/certs/ca3/ca.key | 27 + .../src/test/resources/certs/ca3/ca.p12 | Bin 0 -> 1066 bytes .../test/resources/certs/cert-all/certs.jks | Bin 0 -> 4268 bytes .../test/resources/certs/cert-all/certs.p12 | Bin 0 -> 4757 bytes .../src/test/resources/certs/cert1/cert1.crt | 19 + .../src/test/resources/certs/cert1/cert1.key | 27 + .../src/test/resources/certs/cert1/cert1.p12 | Bin 0 -> 2456 bytes .../src/test/resources/certs/cert2/cert2.crt | 19 + .../src/test/resources/certs/cert2/cert2.key | 30 + .../src/test/resources/certs/cert2/cert2.p12 | Bin 0 -> 2456 bytes .../resources/certs/pem-utils/README.asciidoc | 149 +++++ .../pem-utils/corrupted_key_pkcs8_plain.pem | 24 + .../pem-utils/dsa_key_openssl_encrypted.pem | 15 + .../certs/pem-utils/dsa_key_openssl_plain.pem | 12 + .../dsa_key_openssl_plain_with_params.pem | 18 + .../certs/pem-utils/dsa_key_pkcs8_plain.pem | 9 + .../pem-utils/ec_key_openssl_encrypted.pem | 7 + .../certs/pem-utils/ec_key_openssl_plain.pem | 4 + .../ec_key_openssl_plain_with_params.pem | 7 + .../certs/pem-utils/ec_key_pkcs8_plain.pem | 4 + .../test/resources/certs/pem-utils/empty.pem | 0 .../certs/pem-utils/key_pkcs8_encrypted.pem | 29 + .../certs/pem-utils/key_unsupported.pem | 7 + .../certs/pem-utils/rsa_key_pkcs8_plain.pem | 28 + .../certs/pem-utils/testnode-aes128.pem | 30 + .../certs/pem-utils/testnode-aes192.pem | 30 + .../certs/pem-utils/testnode-aes256.pem | 30 + .../certs/pem-utils/testnode-unprotected.pem | 27 + .../resources/certs/pem-utils/testnode.crt | 23 + .../resources/certs/pem-utils/testnode.jks | Bin 0 -> 9360 bytes .../resources/certs/pem-utils/testnode.pem | 30 + .../pem-utils/testnode_with_bagattrs.pem | 32 + 69 files changed, 4993 insertions(+) create mode 100644 libs/ssl-config/build.gradle create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DefaultJdkTrustConfig.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DerParser.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/EmptyKeyConfig.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslClientAuthenticationMode.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigException.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationKeys.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslKeyConfig.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslTrustConfig.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslVerificationMode.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreKeyConfig.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreTrustConfig.java create mode 100644 libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/TrustEverythingConfig.java create mode 100644 libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/DefaultJdkTrustConfigTests.java create mode 100644 libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemKeyConfigTests.java create mode 100644 libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemTrustConfigTests.java create mode 100644 libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemUtilsTests.java create mode 100644 libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationLoaderTests.java create mode 100644 libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationTests.java create mode 100644 libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java create mode 100644 libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreTrustConfigTests.java create mode 100644 libs/ssl-config/src/test/resources/certs/README.txt create mode 100644 libs/ssl-config/src/test/resources/certs/ca-all/ca.jks create mode 100644 libs/ssl-config/src/test/resources/certs/ca-all/ca.p12 create mode 100644 libs/ssl-config/src/test/resources/certs/ca1/ca.crt create mode 100644 libs/ssl-config/src/test/resources/certs/ca1/ca.key create mode 100644 libs/ssl-config/src/test/resources/certs/ca1/ca.p12 create mode 100644 libs/ssl-config/src/test/resources/certs/ca2/ca.crt create mode 100644 libs/ssl-config/src/test/resources/certs/ca2/ca.key create mode 100644 libs/ssl-config/src/test/resources/certs/ca2/ca.p12 create mode 100644 libs/ssl-config/src/test/resources/certs/ca3/ca.crt create mode 100644 libs/ssl-config/src/test/resources/certs/ca3/ca.key create mode 100644 libs/ssl-config/src/test/resources/certs/ca3/ca.p12 create mode 100644 libs/ssl-config/src/test/resources/certs/cert-all/certs.jks create mode 100644 libs/ssl-config/src/test/resources/certs/cert-all/certs.p12 create mode 100644 libs/ssl-config/src/test/resources/certs/cert1/cert1.crt create mode 100644 libs/ssl-config/src/test/resources/certs/cert1/cert1.key create mode 100644 libs/ssl-config/src/test/resources/certs/cert1/cert1.p12 create mode 100644 libs/ssl-config/src/test/resources/certs/cert2/cert2.crt create mode 100644 libs/ssl-config/src/test/resources/certs/cert2/cert2.key create mode 100644 libs/ssl-config/src/test/resources/certs/cert2/cert2.p12 create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/README.asciidoc create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/corrupted_key_pkcs8_plain.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_encrypted.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_plain.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_plain_with_params.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_pkcs8_plain.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_encrypted.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_plain.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_plain_with_params.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_pkcs8_plain.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/empty.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/key_pkcs8_encrypted.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/key_unsupported.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/rsa_key_pkcs8_plain.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes128.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes192.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes256.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/testnode-unprotected.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/testnode.crt create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/testnode.jks create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/testnode.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/testnode_with_bagattrs.pem diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle new file mode 100644 index 0000000000000..ba9c8675b478e --- /dev/null +++ b/libs/ssl-config/build.gradle @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +archivesBaseName = 'elasticsearch-ssl-config' + +dependencies { + compile "org.elasticsearch:elasticsearch-core:${version}" + + if (isEclipse == false || project.path == ":libs:dissect-tests") { + testCompile("org.elasticsearch.test:framework:${version}") { + exclude group: 'org.elasticsearch', module: 'elasticsearch-ssl-config' + } + } + + testCompile "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" + testCompile "junit:junit:${versions.junit}" + testCompile "org.hamcrest:hamcrest-all:${versions.hamcrest}" +} + +jarHell.enabled = false +forbiddenApisMain { + replaceSignatureFiles 'jdk-signatures' +} +forbiddenPatterns { + exclude '**/*.key' + exclude '**/*.pem' + exclude '**/*.p12' + exclude '**/*.jks' +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DefaultJdkTrustConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DefaultJdkTrustConfig.java new file mode 100644 index 0000000000000..5a1fbe72c3f49 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DefaultJdkTrustConfig.java @@ -0,0 +1,128 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.common.Nullable; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import java.io.IOException; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.function.BiFunction; + +/** + * This class represents a trust configuration that corresponds to the default trusted CAs of the JDK + */ +final class DefaultJdkTrustConfig implements SslTrustConfig { + + private final BiFunction systemProperties; + private final char[] trustStorePassword; + + /** + * Create a trust config that uses System properties to determine the TrustStore type, and the relevant password. + */ + DefaultJdkTrustConfig() { + this(System::getProperty); + } + + /** + * Create a trust config that uses supplied {@link BiFunction} to determine the TrustStore type, and the relevant password. + */ + DefaultJdkTrustConfig(BiFunction systemProperties) { + this(systemProperties, isPkcs11Truststore(systemProperties) ? getSystemTrustStorePassword(systemProperties) : null); + } + + /** + * @param trustStorePassword the password for the truststore. It applies only when PKCS#11 tokens are used, is null otherwise + */ + DefaultJdkTrustConfig(BiFunction systemProperties, @Nullable char[] trustStorePassword) { + this.systemProperties = systemProperties; + this.trustStorePassword = trustStorePassword; + } + + @Override + public X509ExtendedTrustManager createTrustManager() { + try { + return KeyStoreUtil.createTrustManager(getSystemTrustStore(), TrustManagerFactory.getDefaultAlgorithm()); + } catch (GeneralSecurityException e) { + throw new SslConfigException("failed to initialize a TrustManager for the system keystore", e); + } + } + + /** + * When a PKCS#11 token is used as the system default keystore/truststore, we need to pass the keystore + * password when loading, even for reading certificates only ( as opposed to i.e. JKS keystores where + * we only need to pass the password for reading Private Key entries ). + * + * @return the KeyStore used as truststore for PKCS#11 initialized with the password, null otherwise + */ + private KeyStore getSystemTrustStore() { + if (isPkcs11Truststore(systemProperties) && trustStorePassword != null) { + try { + KeyStore keyStore = KeyStore.getInstance("PKCS11"); + keyStore.load(null, trustStorePassword); + return keyStore; + } catch (GeneralSecurityException | IOException e) { + throw new SslConfigException("failed to load the system PKCS#11 truststore", e); + } + } + return null; + } + + private static boolean isPkcs11Truststore(BiFunction systemProperties) { + return systemProperties.apply("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11"); + } + + private static char[] getSystemTrustStorePassword(BiFunction systemProperties) { + return systemProperties.apply("javax.net.ssl.trustStorePassword", "").toCharArray(); + } + + @Override + public Collection getDependentFiles() { + return Collections.emptyList(); + } + + @Override + public String toString() { + return "JDK-trusted-certs"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final DefaultJdkTrustConfig that = (DefaultJdkTrustConfig) o; + return Arrays.equals(this.trustStorePassword, that.trustStorePassword); + } + + @Override + public int hashCode() { + return Arrays.hashCode(trustStorePassword); + } +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DerParser.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DerParser.java new file mode 100644 index 0000000000000..7401ee8ee58d5 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DerParser.java @@ -0,0 +1,299 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.util.Objects; + +/** + * A bare-minimum ASN.1 DER decoder, just having enough functions to + * decode PKCS#1 private keys in order to remain JCE/JVM agnostic. + *

+ * Based on https://github.com/groovenauts/jmeter_oauth_plugin/blob/master/jmeter/src/ + * main/java/org/apache/jmeter/protocol/oauth/sampler/PrivateKeyReader.java + */ +final class DerParser { + // Constructed Flag + private static final int CONSTRUCTED = 0x20; + + // Tag and data types + private static final int INTEGER = 0x02; + private static final int OCTET_STRING = 0x04; + private static final int OBJECT_OID = 0x06; + private static final int NUMERIC_STRING = 0x12; + private static final int PRINTABLE_STRING = 0x13; + private static final int VIDEOTEX_STRING = 0x15; + private static final int IA5_STRING = 0x16; + private static final int GRAPHIC_STRING = 0x19; + private static final int ISO646_STRING = 0x1A; + private static final int GENERAL_STRING = 0x1B; + + private static final int UTF8_STRING = 0x0C; + private static final int UNIVERSAL_STRING = 0x1C; + private static final int BMP_STRING = 0x1E; + + + private InputStream derInputStream; + private int maxAsnObjectLength; + + DerParser(byte[] bytes) { + this.derInputStream = new ByteArrayInputStream(bytes); + this.maxAsnObjectLength = bytes.length; + } + + Asn1Object readAsn1Object() throws IOException { + int tag = derInputStream.read(); + if (tag == -1) { + throw new IOException("Invalid DER: stream too short, missing tag"); + } + int length = getLength(); + // getLength() can return any 32 bit integer, so ensure that a corrupted encoding won't + // force us into allocating a very large array + if (length > maxAsnObjectLength) { + throw new IOException("Invalid DER: size of ASN.1 object to be parsed appears to be larger than the size of the key file " + + "itself."); + } + byte[] value = new byte[length]; + int n = derInputStream.read(value); + if (n < length) { + throw new IOException("Invalid DER: stream too short, missing value. " + + "Could only read " + n + " out of " + length + " bytes"); + } + return new Asn1Object(tag, length, value); + + } + + /** + * Decode the length of the field. Can only support length + * encoding up to 4 octets. + *

+ * In BER/DER encoding, length can be encoded in 2 forms: + *

+ *
    + *
  • Short form. One octet. Bit 8 has value "0" and bits 7-1 + * give the length. + *
  • + *
  • Long form. Two to 127 octets (only 4 is supported here). + * Bit 8 of first octet has value "1" and bits 7-1 give the + * number of additional length octets. Second and following + * octets give the length, base 256, most significant digit first. + *
  • + *
+ * + * @return The length as integer + */ + private int getLength() throws IOException { + + int i = derInputStream.read(); + if (i == -1) + throw new IOException("Invalid DER: length missing"); + + // A single byte short length + if ((i & ~0x7F) == 0) + return i; + + int num = i & 0x7F; + + // We can't handle length longer than 4 bytes + if (i >= 0xFF || num > 4) + throw new IOException("Invalid DER: length field too big (" + + i + ")"); //$NON-NLS-1$ + + byte[] bytes = new byte[num]; + int n = derInputStream.read(bytes); + if (n < num) + throw new IOException("Invalid DER: length too short"); + + return new BigInteger(1, bytes).intValue(); + } + + + /** + * An ASN.1 TLV. The object is not parsed. It can + * only handle integers. + * + * @author zhang + */ + static class Asn1Object { + + protected final int type; + protected final int length; + protected final byte[] value; + protected final int tag; + + /** + * Construct a ASN.1 TLV. The TLV could be either a + * constructed or primitive entity. + *

+ * The first byte in DER encoding is made of following fields: + *

+ *
+         * -------------------------------------------------
+         * |Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1|
+         * -------------------------------------------------
+         * |  Class    | CF  |     +      Type             |
+         * -------------------------------------------------
+         * 
+ *
    + *
  • Class: Universal, Application, Context or Private + *
  • CF: Constructed flag. If 1, the field is constructed. + *
  • Type: This is actually called tag in ASN.1. It + * indicates data type (Integer, String) or a construct + * (sequence, choice, set). + *
+ * + * @param tag Tag or Identifier + * @param length Length of the field + * @param value Encoded octet string for the field. + */ + Asn1Object(int tag, int length, byte[] value) { + this.tag = tag; + this.type = tag & 0x1F; + this.length = length; + this.value = value; + } + + public int getType() { + return type; + } + + public int getLength() { + return length; + } + + public byte[] getValue() { + return value; + } + + public boolean isConstructed() { + return (tag & DerParser.CONSTRUCTED) == DerParser.CONSTRUCTED; + } + + /** + * For constructed field, return a parser for its content. + * + * @return A parser for the construct. + */ + public DerParser getParser() throws IOException { + if (!isConstructed()) + throw new IOException("Invalid DER: can't parse primitive entity"); //$NON-NLS-1$ + + return new DerParser(value); + } + + /** + * Get the value as integer + * + * @return BigInteger + */ + public BigInteger getInteger() throws IOException { + if (type != DerParser.INTEGER) + throw new IOException("Invalid DER: object is not integer"); //$NON-NLS-1$ + + return new BigInteger(value); + } + + public String getString() throws IOException { + + String encoding; + + switch (type) { + case DerParser.OCTET_STRING: + // octet string is basically a byte array + return toHexString(value); + case DerParser.NUMERIC_STRING: + case DerParser.PRINTABLE_STRING: + case DerParser.VIDEOTEX_STRING: + case DerParser.IA5_STRING: + case DerParser.GRAPHIC_STRING: + case DerParser.ISO646_STRING: + case DerParser.GENERAL_STRING: + encoding = "ISO-8859-1"; //$NON-NLS-1$ + break; + + case DerParser.BMP_STRING: + encoding = "UTF-16BE"; //$NON-NLS-1$ + break; + + case DerParser.UTF8_STRING: + encoding = "UTF-8"; //$NON-NLS-1$ + break; + + case DerParser.UNIVERSAL_STRING: + throw new IOException("Invalid DER: can't handle UCS-4 string"); //$NON-NLS-1$ + + default: + throw new IOException("Invalid DER: object is not a string"); //$NON-NLS-1$ + } + + return new String(value, encoding); + } + + public String getOid() throws IOException { + + if (type != DerParser.OBJECT_OID) { + throw new IOException("Ivalid DER: object is not object OID"); + } + StringBuilder sb = new StringBuilder(64); + switch (value[0] / 40) { + case 0: + sb.append('0'); + break; + case 1: + sb.append('1'); + value[0] -= 40; + break; + default: + sb.append('2'); + value[0] -= 80; + break; + } + int oidPart = 0; + for (int i = 0; i < length; i++) { + oidPart = (oidPart << 7) + (value[i] & 0x7F); + if ((value[i] & 0x80) == 0) { + sb.append('.'); + sb.append(oidPart); + oidPart = 0; + } + } + + return sb.toString(); + } + } + + private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); + private static String toHexString(byte[] bytes) { + Objects.requireNonNull(bytes); + StringBuilder sb = new StringBuilder(2 * bytes.length); + + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + sb.append(HEX_DIGITS[b >> 4 & 0xf]).append(HEX_DIGITS[b & 0xf]); + } + + return sb.toString(); + } + +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/EmptyKeyConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/EmptyKeyConfig.java new file mode 100644 index 0000000000000..0844ffb7ee952 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/EmptyKeyConfig.java @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.X509ExtendedKeyManager; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; + +/** + * A {@link SslKeyConfig} that does nothing (provides a null key manager) + */ +final class EmptyKeyConfig implements SslKeyConfig { + + static final EmptyKeyConfig INSTANCE = new EmptyKeyConfig(); + + private EmptyKeyConfig() { + // Enforce a single instance + } + + @Override + public Collection getDependentFiles() { + return Collections.emptyList(); + } + + @Override + public X509ExtendedKeyManager createKeyManager() { + return null; + } + + @Override + public String toString() { + return "empty-key-config"; + } +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java new file mode 100644 index 0000000000000..6eaa32ddf5170 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java @@ -0,0 +1,157 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.common.Nullable; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509ExtendedTrustManager; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.util.Collection; +import java.util.Locale; + +/** + * A variety of utility methods for working with or constructing {@link KeyStore} instances. + */ +final class KeyStoreUtil { + + private KeyStoreUtil() { + throw new IllegalStateException("Utility class should not be instantiated"); + } + + /** + * Make a best guess about the "type" (see {@link KeyStore#getType()}) of the keystore file located at the given {@code Path}. + * This method only references the file name of the keystore, it does not look at its contents. + */ + static String inferKeyStoreType(Path path) { + String name = path == null ? "" : path.toString().toLowerCase(Locale.ROOT); + if (name.endsWith(".p12") || name.endsWith(".pfx") || name.endsWith(".pkcs12")) { + return "PKCS12"; + } else { + return "jks"; + } + } + + /** + * Read the given keystore file. + * + * @throws SslConfigException If there is a problem reading from the provided path + * @throws GeneralSecurityException If there is a problem with the keystore contents + */ + static KeyStore readKeyStore(Path path, String type, char[] password) throws GeneralSecurityException { + try { + KeyStore keyStore = KeyStore.getInstance(type); + try (InputStream in = Files.newInputStream(path)) { + keyStore.load(in, password); + } + return keyStore; + } catch (FileNotFoundException | NoSuchFileException e) { + throw new SslConfigException("cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + + "] because the file does not exist", e); + } catch (IOException e) { + throw new SslConfigException("cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + "] - " + e.getMessage(), + e); + } + } + + /** + * Construct an in-memory keystore for the provided certificates and the associated private key. + * + * @throws GeneralSecurityException If there is a problem with the provided certificates/key + */ + static KeyStore buildKeyStore(Collection certificates, PrivateKey privateKey, char[] password) + throws GeneralSecurityException { + KeyStore keyStore = buildNewKeyStore(); + keyStore.setKeyEntry("key", privateKey, password, certificates.toArray(new Certificate[0])); + return keyStore; + } + + static KeyStore buildKeyStore(Iterable certificates) throws GeneralSecurityException { + assert certificates != null : "Cannot create keystore with null certificates"; + KeyStore store = buildNewKeyStore(); + int counter = 0; + for (Certificate certificate : certificates) { + store.setCertificateEntry("cert-" + counter, certificate); + counter++; + } + return store; + } + + private static KeyStore buildNewKeyStore() throws GeneralSecurityException { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + keyStore.load(null, null); + } catch (IOException e) { + // This should never happen so callers really shouldn't be forced to deal with it themselves. + throw new SslConfigException("Unexpected error initializing a new in-memory keystore", e); + } + return keyStore; + } + + /** + * Creates a {@link X509ExtendedKeyManager} based on the key material in the provided {@link KeyStore} + */ + static X509ExtendedKeyManager createKeyManager(KeyStore keyStore, char[] password, String algorithm) throws GeneralSecurityException { + KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); + kmf.init(keyStore, password); + KeyManager[] keyManagers = kmf.getKeyManagers(); + for (KeyManager keyManager : keyManagers) { + if (keyManager instanceof X509ExtendedKeyManager) { + return (X509ExtendedKeyManager) keyManager; + } + } + throw new SslConfigException("failed to find a X509ExtendedKeyManager in the key manager factory for [" + algorithm + + "] and keystore [" + keyStore + "]"); + } + + /** + * Creates a {@link X509ExtendedTrustManager} based on the trust material in the provided {@link KeyStore} + */ + static X509ExtendedTrustManager createTrustManager(@Nullable KeyStore trustStore, String algorithm) + throws NoSuchAlgorithmException, KeyStoreException { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); + tmf.init(trustStore); + TrustManager[] trustManagers = tmf.getTrustManagers(); + for (TrustManager trustManager : trustManagers) { + if (trustManager instanceof X509ExtendedTrustManager) { + return (X509ExtendedTrustManager) trustManager; + } + } + throw new SslConfigException("failed to find a X509ExtendedTrustManager in the trust manager factory for [" + algorithm + + "] and truststore [" + trustStore + "]"); + } + + +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java new file mode 100644 index 0000000000000..5c5d0d7c77710 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java @@ -0,0 +1,124 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * A {@link SslKeyConfig} that reads from PEM formatted paths. + */ +public final class PemKeyConfig implements SslKeyConfig { + private final Path certificate; + private final Path key; + private final char[] keyPassword; + + public PemKeyConfig(Path certificate, Path key, char[] keyPassword) { + this.certificate = Objects.requireNonNull(certificate, "Certificate cannot be null"); + this.key = Objects.requireNonNull(key, "Key cannot be null"); + this.keyPassword = Objects.requireNonNull(keyPassword, "Key password cannot be null (but may be empty)"); + } + + @Override + public Collection getDependentFiles() { + return Arrays.asList(certificate, key); + } + + @Override + public X509ExtendedKeyManager createKeyManager() { + try { + PrivateKey privateKey = getPrivateKey(); + List certificates = getCertificates(); + final KeyStore keyStore = KeyStoreUtil.buildKeyStore(certificates, privateKey, keyPassword); + return KeyStoreUtil.createKeyManager(keyStore, keyPassword, KeyManagerFactory.getDefaultAlgorithm()); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new SslConfigException("failed to load a KeyManager for certificate/key pair [" + certificate + "], [" + key + "]", e); + } + } + + private PrivateKey getPrivateKey() { + try { + final PrivateKey privateKey = PemUtils.readPrivateKey(key, () -> keyPassword); + if (privateKey == null) { + throw new SslConfigException("could not load ssl private key file [" + key + "]"); + } + return privateKey; + } catch (FileNotFoundException | NoSuchFileException e) { + throw new SslConfigException("the configured ssl private key file [" + key.toAbsolutePath() + "] does not exist", e); + } catch (IOException e) { + throw new SslConfigException("the configured ssl private key file [" + key.toAbsolutePath() + "] cannot be read", e); + } catch (GeneralSecurityException e) { + throw new SslConfigException("cannot load ssl private key file [" + key.toAbsolutePath() + "]", e); + } + } + + private List getCertificates() { + try { + return PemUtils.readCertificates(Collections.singleton(certificate)); + } catch (FileNotFoundException | NoSuchFileException e) { + throw new SslConfigException("the configured ssl certificate file [" + certificate.toAbsolutePath() + "] does not exist", e); + } catch (IOException e) { + throw new SslConfigException("the configured ssl certificate file [" + certificate .toAbsolutePath()+ "] cannot be read", e); + } catch (GeneralSecurityException e) { + throw new SslConfigException("cannot load ssl certificate from [" + certificate.toAbsolutePath() + "]", e); + } + } + + @Override + public String toString() { + return "PEM-key-config{cert=" + certificate.toAbsolutePath() + " key=" + key.toAbsolutePath() + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final PemKeyConfig that = (PemKeyConfig) o; + return Objects.equals(this.certificate, that.certificate) && + Objects.equals(this.key, that.key) && + Arrays.equals(this.keyPassword, that.keyPassword); + } + + @Override + public int hashCode() { + int result = Objects.hash(certificate, key); + result = 31 * result + Arrays.hashCode(keyPassword); + return result; + } +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java new file mode 100644 index 0000000000000..a436a7969f06f --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java @@ -0,0 +1,122 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * A {@link org.elasticsearch.common.ssl.SslTrustConfig} that reads a list of PEM encoded trusted certificates (CAs) from the file + * system. + * Strictly speaking, this class does not require PEM certificates, and will load any file that can be read by + * {@link java.security.cert.CertificateFactory#generateCertificate(InputStream)}. + */ +public final class PemTrustConfig implements SslTrustConfig { + private final List certificateAuthorities; + + /** + * Construct a new trust config for the provided paths. + * The paths are stored as-is, and are not read until {@link #createTrustManager()} is called. + * This means that + *
    + *
  1. validation of the file (contents and accessibility) is deferred, and this constructor will not fail on missing + * of invalid files.
  2. + *
  3. + * if the contents of the files are modified, then subsequent calls {@link #createTrustManager()} will return a new trust + * manager that trust a different set of CAs. + *
  4. + *
+ */ + public PemTrustConfig(List certificateAuthorities) { + this.certificateAuthorities = Collections.unmodifiableList(certificateAuthorities); + } + + @Override + public Collection getDependentFiles() { + return certificateAuthorities; + } + + @Override + public X509ExtendedTrustManager createTrustManager() { + try { + final List certificates = loadCertificates(); + KeyStore store = KeyStoreUtil.buildKeyStore(certificates); + return KeyStoreUtil.createTrustManager(store, TrustManagerFactory.getDefaultAlgorithm()); + } catch (GeneralSecurityException e) { + throw new SslConfigException("cannot create trust using PEM certificates [" + caPathsAsString() + "]", e); + } + } + + private List loadCertificates() throws CertificateException { + try { + return PemUtils.readCertificates(this.certificateAuthorities); + } catch (FileNotFoundException | NoSuchFileException e) { + throw new SslConfigException("cannot configure trust using PEM certificates [" + caPathsAsString() + + "] because one or more files do not exist", e); + } catch (IOException e) { + throw new SslConfigException("cannot configure trust using PEM certificates [" + caPathsAsString() + + "] because one or more files cannot be read", e); + } + } + + @Override + public String toString() { + return "PEM-trust{" + caPathsAsString() + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final PemTrustConfig that = (PemTrustConfig) o; + return Objects.equals(this.certificateAuthorities, that.certificateAuthorities); + } + + @Override + public int hashCode() { + return Objects.hash(certificateAuthorities); + } + + private String caPathsAsString() { + return certificateAuthorities.stream() + .map(Path::toAbsolutePath) + .map(Object::toString) + .collect(Collectors.joining(",")); + } + +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java new file mode 100644 index 0000000000000..cf137312273da --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java @@ -0,0 +1,609 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.common.CharArrays; + +import javax.crypto.Cipher; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.interfaces.ECKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +final class PemUtils { + + private static final String PKCS1_HEADER = "-----BEGIN RSA PRIVATE KEY-----"; + private static final String PKCS1_FOOTER = "-----END RSA PRIVATE KEY-----"; + private static final String OPENSSL_DSA_HEADER = "-----BEGIN DSA PRIVATE KEY-----"; + private static final String OPENSSL_DSA_FOOTER = "-----END DSA PRIVATE KEY-----"; + private static final String OPENSSL_DSA_PARAMS_HEADER ="-----BEGIN DSA PARAMETERS-----"; + private static final String OPENSSL_DSA_PARAMS_FOOTER ="-----END DSA PARAMETERS-----"; + private static final String PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----"; + private static final String PKCS8_FOOTER = "-----END PRIVATE KEY-----"; + private static final String PKCS8_ENCRYPTED_HEADER = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; + private static final String PKCS8_ENCRYPTED_FOOTER = "-----END ENCRYPTED PRIVATE KEY-----"; + private static final String OPENSSL_EC_HEADER = "-----BEGIN EC PRIVATE KEY-----"; + private static final String OPENSSL_EC_FOOTER = "-----END EC PRIVATE KEY-----"; + private static final String OPENSSL_EC_PARAMS_HEADER = "-----BEGIN EC PARAMETERS-----"; + private static final String OPENSSL_EC_PARAMS_FOOTER = "-----END EC PARAMETERS-----"; + private static final String HEADER = "-----BEGIN"; + + private PemUtils() { + throw new IllegalStateException("Utility class should not be instantiated"); + } + + /** + * Creates a {@link PrivateKey} from the contents of a file. Supports PKCS#1, PKCS#8 + * encoded formats of encrypted and plaintext RSA, DSA and EC(secp256r1) keys + * + * @param keyPath the path for the key file + * @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 passwordSupplier) throws IOException, GeneralSecurityException { + try (BufferedReader bReader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) { + String line = bReader.readLine(); + while (null != line && line.startsWith(HEADER) == false) { + line = bReader.readLine(); + } + if (null == line) { + throw new SslConfigException("Error parsing Private Key [" + keyPath.toAbsolutePath() + "], file is empty"); + } + if (PKCS8_ENCRYPTED_HEADER.equals(line.trim())) { + char[] password = passwordSupplier.get(); + if (password == null) { + throw new SslConfigException("cannot read encrypted key [" + keyPath.toAbsolutePath() + "] without a password"); + } + return parsePKCS8Encrypted(bReader, password); + } else if (PKCS8_HEADER.equals(line.trim())) { + return parsePKCS8(bReader); + } else if (PKCS1_HEADER.equals(line.trim())) { + return parsePKCS1Rsa(bReader, passwordSupplier); + } else if (OPENSSL_DSA_HEADER.equals(line.trim())) { + return parseOpenSslDsa(bReader, passwordSupplier); + } else if (OPENSSL_DSA_PARAMS_HEADER.equals(line.trim())) { + return parseOpenSslDsa(removeDsaHeaders(bReader), passwordSupplier); + } else if (OPENSSL_EC_HEADER.equals(line.trim())) { + return parseOpenSslEC(bReader, passwordSupplier); + } else if (OPENSSL_EC_PARAMS_HEADER.equals(line.trim())) { + return parseOpenSslEC(removeECHeaders(bReader), passwordSupplier); + } else { + throw new SslConfigException("error parsing Private Key [" + keyPath.toAbsolutePath() + + "], file does not contain a supported key format"); + } + } catch (FileNotFoundException | NoSuchFileException e) { + throw new SslConfigException("private key file [" + keyPath.toAbsolutePath() + "] does not exist", e); + } catch (IOException | GeneralSecurityException e) { + throw new SslConfigException("private key file [" + keyPath.toAbsolutePath() + "] cannot be parsed", e); + } + } + + /** + * Removes the EC Headers that OpenSSL adds to EC private keys as the information in them + * is redundant + * + * @throws IOException if the EC Parameter footer is missing + */ + private static BufferedReader removeECHeaders(BufferedReader bReader) throws IOException { + String line = bReader.readLine(); + while (line != null) { + if (OPENSSL_EC_PARAMS_FOOTER.equals(line.trim())) { + break; + } + line = bReader.readLine(); + } + if (null == line || OPENSSL_EC_PARAMS_FOOTER.equals(line.trim()) == false) { + throw new IOException("Malformed PEM file, EC Parameters footer is missing"); + } + // Verify that the key starts with the correct header before passing it to parseOpenSslEC + if (OPENSSL_EC_HEADER.equals(bReader.readLine()) == false) { + throw new IOException("Malformed PEM file, EC Key header is missing"); + } + return bReader; + } + + /** + * Removes the DSA Params Headers that OpenSSL adds to DSA private keys as the information in them + * is redundant + * + * @throws IOException if the EC Parameter footer is missing + */ + private static BufferedReader removeDsaHeaders(BufferedReader bReader) throws IOException { + String line = bReader.readLine(); + while (line != null) { + if (OPENSSL_DSA_PARAMS_FOOTER.equals(line.trim())) { + break; + } + line = bReader.readLine(); + } + if (null == line || OPENSSL_DSA_PARAMS_FOOTER.equals(line.trim()) == false) { + throw new IOException("Malformed PEM file, DSA Parameters footer is missing"); + } + // Verify that the key starts with the correct header before passing it to parseOpenSslDsa + if (OPENSSL_DSA_HEADER.equals(bReader.readLine()) == false) { + throw new IOException("Malformed PEM file, DSA Key header is missing"); + } + return bReader; + } + + /** + * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an plaintext private key encoded in + * PKCS#8 + * + * @param bReader the {@link BufferedReader} containing the key file contents + * @return {@link PrivateKey} + * @throws IOException if the file can't be read + * @throws GeneralSecurityException if the private key can't be generated from the {@link PKCS8EncodedKeySpec} + */ + private static PrivateKey parsePKCS8(BufferedReader bReader) throws IOException, GeneralSecurityException { + StringBuilder sb = new StringBuilder(); + String line = bReader.readLine(); + while (line != null) { + if (PKCS8_FOOTER.equals(line.trim())) { + break; + } + sb.append(line.trim()); + line = bReader.readLine(); + } + if (null == line || PKCS8_FOOTER.equals(line.trim()) == false) { + throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); + } + byte[] keyBytes = Base64.getDecoder().decode(sb.toString()); + String keyAlgo = getKeyAlgorithmIdentifier(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(keyAlgo); + return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); + } + + /** + * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an EC private key encoded in + * OpenSSL traditional format. + * + * @param bReader the {@link BufferedReader} containing the key file contents + * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key + * @return {@link PrivateKey} + * @throws IOException if the file can't be read + * @throws GeneralSecurityException if the private key can't be generated from the {@link ECPrivateKeySpec} + */ + private static PrivateKey parseOpenSslEC(BufferedReader bReader, Supplier passwordSupplier) throws IOException, + GeneralSecurityException { + StringBuilder sb = new StringBuilder(); + String line = bReader.readLine(); + Map pemHeaders = new HashMap<>(); + while (line != null) { + if (OPENSSL_EC_FOOTER.equals(line.trim())) { + break; + } + // Parse PEM headers according to https://www.ietf.org/rfc/rfc1421.txt + if (line.contains(":")) { + String[] header = line.split(":"); + pemHeaders.put(header[0].trim(), header[1].trim()); + } else { + sb.append(line.trim()); + } + line = bReader.readLine(); + } + if (null == line || OPENSSL_EC_FOOTER.equals(line.trim()) == false) { + throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); + } + byte[] keyBytes = possiblyDecryptPKCS1Key(pemHeaders, sb.toString(), passwordSupplier); + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + ECPrivateKeySpec ecSpec = parseEcDer(keyBytes); + return keyFactory.generatePrivate(ecSpec); + } + + /** + * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an RSA private key encoded in + * OpenSSL traditional format. + * + * @param bReader the {@link BufferedReader} containing the key file contents + * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key + * @return {@link PrivateKey} + * @throws IOException if the file can't be read + * @throws GeneralSecurityException if the private key can't be generated from the {@link RSAPrivateCrtKeySpec} + */ + private static PrivateKey parsePKCS1Rsa(BufferedReader bReader, Supplier passwordSupplier) throws IOException, + GeneralSecurityException { + StringBuilder sb = new StringBuilder(); + String line = bReader.readLine(); + Map pemHeaders = new HashMap<>(); + + while (line != null) { + if (PKCS1_FOOTER.equals(line.trim())) { + // Unencrypted + break; + } + // Parse PEM headers according to https://www.ietf.org/rfc/rfc1421.txt + if (line.contains(":")) { + String[] header = line.split(":"); + pemHeaders.put(header[0].trim(), header[1].trim()); + } else { + sb.append(line.trim()); + } + line = bReader.readLine(); + } + if (null == line || PKCS1_FOOTER.equals(line.trim()) == false) { + throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); + } + byte[] keyBytes = possiblyDecryptPKCS1Key(pemHeaders, sb.toString(), passwordSupplier); + RSAPrivateCrtKeySpec spec = parseRsaDer(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(spec); + } + + /** + * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an DSA private key encoded in + * OpenSSL traditional format. + * + * @param bReader the {@link BufferedReader} containing the key file contents + * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key + * @return {@link PrivateKey} + * @throws IOException if the file can't be read + * @throws GeneralSecurityException if the private key can't be generated from the {@link DSAPrivateKeySpec} + */ + private static PrivateKey parseOpenSslDsa(BufferedReader bReader, Supplier passwordSupplier) throws IOException, + GeneralSecurityException { + StringBuilder sb = new StringBuilder(); + String line = bReader.readLine(); + Map pemHeaders = new HashMap<>(); + + while (line != null) { + if (OPENSSL_DSA_FOOTER.equals(line.trim())) { + // Unencrypted + break; + } + // Parse PEM headers according to https://www.ietf.org/rfc/rfc1421.txt + if (line.contains(":")) { + String[] header = line.split(":"); + pemHeaders.put(header[0].trim(), header[1].trim()); + } else { + sb.append(line.trim()); + } + line = bReader.readLine(); + } + if (null == line || OPENSSL_DSA_FOOTER.equals(line.trim()) == false) { + throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); + } + byte[] keyBytes = possiblyDecryptPKCS1Key(pemHeaders, sb.toString(), passwordSupplier); + DSAPrivateKeySpec spec = parseDsaDer(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("DSA"); + return keyFactory.generatePrivate(spec); + } + + /** + * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an encrypted private key encoded in + * PKCS#8 + * + * @param bReader the {@link BufferedReader} containing the key file contents + * @param keyPassword The password for the encrypted (password protected) key + * @return {@link PrivateKey} + * @throws IOException if the file can't be read + * @throws GeneralSecurityException if the private key can't be generated from the {@link PKCS8EncodedKeySpec} + */ + private static PrivateKey parsePKCS8Encrypted(BufferedReader bReader, char[] keyPassword) throws IOException, + GeneralSecurityException { + StringBuilder sb = new StringBuilder(); + String line = bReader.readLine(); + while (line != null) { + if (PKCS8_ENCRYPTED_FOOTER.equals(line.trim())) { + break; + } + sb.append(line.trim()); + line = bReader.readLine(); + } + if (null == line || PKCS8_ENCRYPTED_FOOTER.equals(line.trim()) == false) { + throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); + } + byte[] keyBytes = Base64.getDecoder().decode(sb.toString()); + + EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(keyBytes); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); + SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(keyPassword)); + Arrays.fill(keyPassword, '\u0000'); + Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); + cipher.init(Cipher.DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters()); + PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher); + String keyAlgo = getKeyAlgorithmIdentifier(keySpec.getEncoded()); + KeyFactory keyFactory = KeyFactory.getInstance(keyAlgo); + return keyFactory.generatePrivate(keySpec); + } + + /** + * Decrypts the password protected contents using the algorithm and IV that is specified in the PEM Headers of the file + * + * @param pemHeaders The Proc-Type and DEK-Info PEM headers that have been extracted from the key file + * @param keyContents The key as a base64 encoded String + * @param passwordSupplier A password supplier for the encrypted (password protected) key + * @return the decrypted key bytes + * @throws GeneralSecurityException if the key can't be decrypted + * @throws IOException if the PEM headers are missing or malformed + */ + private static byte[] possiblyDecryptPKCS1Key(Map pemHeaders, String keyContents, Supplier passwordSupplier) + throws GeneralSecurityException, IOException { + byte[] keyBytes = Base64.getDecoder().decode(keyContents); + String procType = pemHeaders.get("Proc-Type"); + if ("4,ENCRYPTED".equals(procType)) { + //We only handle PEM encryption + String encryptionParameters = pemHeaders.get("DEK-Info"); + if (null == encryptionParameters) { + //malformed pem + throw new IOException("Malformed PEM File, DEK-Info header is missing"); + } + char[] password = passwordSupplier.get(); + if (password == null) { + throw new IOException("cannot read encrypted key without a password"); + } + Cipher cipher = getCipherFromParameters(encryptionParameters, password); + byte[] decryptedKeyBytes = cipher.doFinal(keyBytes); + return decryptedKeyBytes; + } + return keyBytes; + } + + /** + * Creates a {@link Cipher} from the contents of the DEK-Info header of a PEM file. RFC 1421 indicates that supported algorithms are + * defined in RFC 1423. RFC 1423 only defines DES-CBS and triple DES (EDE) in CBC mode. AES in CBC mode is also widely used though ( 3 + * different variants of 128, 192, 256 bit keys ) + * + * @param dekHeaderValue The value of the the DEK-Info PEM header + * @param password The password with which the key is encrypted + * @return a cipher of the appropriate algorithm and parameters to be used for decryption + * @throws GeneralSecurityException if the algorithm is not available in the used security provider, or if the key is inappropriate + * for the cipher + * @throws IOException if the DEK-Info PEM header is invalid + */ + private static Cipher getCipherFromParameters(String dekHeaderValue, char[] password) throws + GeneralSecurityException, IOException { + final String padding = "PKCS5Padding"; + final SecretKey encryptionKey; + final String[] valueTokens = dekHeaderValue.split(","); + if (valueTokens.length != 2) { + throw new IOException("Malformed PEM file, DEK-Info PEM header is invalid"); + } + final String algorithm = valueTokens[0]; + final String ivString = valueTokens[1]; + final byte[] iv; + try { + iv = hexStringToByteArray(ivString); + } catch (IllegalArgumentException e) { + throw new IOException("Malformed PEM file, DEK-Info IV is invalid", e); + } + if ("DES-CBC".equals(algorithm)) { + byte[] key = generateOpenSslKey(password, iv, 8); + encryptionKey = new SecretKeySpec(key, "DES"); + } else if ("DES-EDE3-CBC".equals(algorithm)) { + byte[] key = generateOpenSslKey(password, iv, 24); + encryptionKey = new SecretKeySpec(key, "DESede"); + } else if ("AES-128-CBC".equals(algorithm)) { + byte[] key = generateOpenSslKey(password, iv, 16); + encryptionKey = new SecretKeySpec(key, "AES"); + } else if ("AES-192-CBC".equals(algorithm)) { + byte[] key = generateOpenSslKey(password, iv, 24); + encryptionKey = new SecretKeySpec(key, "AES"); + } else if ("AES-256-CBC".equals(algorithm)) { + byte[] key = generateOpenSslKey(password, iv, 32); + encryptionKey = new SecretKeySpec(key, "AES"); + } else { + throw new GeneralSecurityException("Private Key encrypted with unsupported algorithm [" + algorithm + "]"); + } + String transformation = encryptionKey.getAlgorithm() + "/" + "CBC" + "/" + padding; + Cipher cipher = Cipher.getInstance(transformation); + cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv)); + return cipher; + } + + /** + * Performs key stretching in the same manner that OpenSSL does. This is basically a KDF + * that uses n rounds of salted MD5 (as many times as needed to get the necessary number of key bytes) + *

+ * https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_PrivateKey_traditional.html + */ + private static byte[] generateOpenSslKey(char[] password, byte[] salt, int keyLength) { + byte[] passwordBytes = CharArrays.toUtf8Bytes(password); + MessageDigest md5 = messageDigest("md5"); + byte[] key = new byte[keyLength]; + int copied = 0; + int remaining; + while (copied < keyLength) { + remaining = keyLength - copied; + md5.update(passwordBytes, 0, passwordBytes.length); + md5.update(salt, 0, 8);// AES IV (salt) is longer but we only need 8 bytes + byte[] tempDigest = md5.digest(); + int bytesToCopy = (remaining > 16) ? 16 : remaining; // MD5 digests are 16 bytes + System.arraycopy(tempDigest, 0, key, copied, bytesToCopy); + copied += bytesToCopy; + if (remaining == 0) { + break; + } + md5.update(tempDigest, 0, 16); // use previous round digest as IV + } + Arrays.fill(passwordBytes, (byte) 0); + return key; + } + + /** + * Converts a hexadecimal string to a byte array + */ + private static byte[] hexStringToByteArray(String hexString) { + int len = hexString.length(); + if (len % 2 == 0) { + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + final int k = Character.digit(hexString.charAt(i), 16); + final int l = Character.digit(hexString.charAt(i + 1), 16); + if (k == -1 || l == -1) { + throw new IllegalStateException("String [" + hexString + "] is not hexadecimal"); + } + data[i / 2] = (byte) ((k << 4) + l); + } + return data; + } else { + throw new IllegalStateException("Hexadecimal string [" + hexString + + "] has odd length and cannot be converted to a byte array"); + } + } + + /** + * Parses a DER encoded EC key to an {@link ECPrivateKeySpec} using a minimal {@link DerParser} + * + * @param keyBytes the private key raw bytes + * @return {@link ECPrivateKeySpec} + * @throws IOException if the DER encoded key can't be parsed + */ + private static ECPrivateKeySpec parseEcDer(byte[] keyBytes) throws IOException, + GeneralSecurityException { + DerParser parser = new DerParser(keyBytes); + DerParser.Asn1Object sequence = parser.readAsn1Object(); + parser = sequence.getParser(); + parser.readAsn1Object().getInteger(); // version + String keyHex = parser.readAsn1Object().getString(); + BigInteger privateKeyInt = new BigInteger(keyHex, 16); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); + AlgorithmParameterSpec prime256v1ParamSpec = new ECGenParameterSpec("secp256r1"); + keyPairGenerator.initialize(prime256v1ParamSpec); + ECParameterSpec parameterSpec = ((ECKey) keyPairGenerator.generateKeyPair().getPrivate()).getParams(); + return new ECPrivateKeySpec(privateKeyInt, parameterSpec); + } + + /** + * Parses a DER encoded RSA key to a {@link RSAPrivateCrtKeySpec} using a minimal {@link DerParser} + * + * @param keyBytes the private key raw bytes + * @return {@link RSAPrivateCrtKeySpec} + * @throws IOException if the DER encoded key can't be parsed + */ + private static RSAPrivateCrtKeySpec parseRsaDer(byte[] keyBytes) throws IOException { + DerParser parser = new DerParser(keyBytes); + DerParser.Asn1Object sequence = parser.readAsn1Object(); + parser = sequence.getParser(); + parser.readAsn1Object().getInteger(); // (version) We don't need it but must read to get to modulus + BigInteger modulus = parser.readAsn1Object().getInteger(); + BigInteger publicExponent = parser.readAsn1Object().getInteger(); + BigInteger privateExponent = parser.readAsn1Object().getInteger(); + BigInteger prime1 = parser.readAsn1Object().getInteger(); + BigInteger prime2 = parser.readAsn1Object().getInteger(); + BigInteger exponent1 = parser.readAsn1Object().getInteger(); + BigInteger exponent2 = parser.readAsn1Object().getInteger(); + BigInteger coefficient = parser.readAsn1Object().getInteger(); + return new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, prime1, prime2, exponent1, exponent2, coefficient); + } + + /** + * Parses a DER encoded DSA key to a {@link DSAPrivateKeySpec} using a minimal {@link DerParser} + * + * @param keyBytes the private key raw bytes + * @return {@link DSAPrivateKeySpec} + * @throws IOException if the DER encoded key can't be parsed + */ + private static DSAPrivateKeySpec parseDsaDer(byte[] keyBytes) throws IOException { + DerParser parser = new DerParser(keyBytes); + DerParser.Asn1Object sequence = parser.readAsn1Object(); + parser = sequence.getParser(); + parser.readAsn1Object().getInteger(); // (version) We don't need it but must read to get to p + BigInteger p = parser.readAsn1Object().getInteger(); + BigInteger q = parser.readAsn1Object().getInteger(); + BigInteger g = parser.readAsn1Object().getInteger(); + parser.readAsn1Object().getInteger(); // we don't need x + BigInteger x = parser.readAsn1Object().getInteger(); + return new DSAPrivateKeySpec(x, p, q, g); + } + + /** + * Parses a DER encoded private key and reads its algorithm identifier Object OID. + * + * @param keyBytes the private key raw bytes + * @return A string identifier for the key algorithm (RSA, DSA, or EC) + * @throws GeneralSecurityException if the algorithm oid that is parsed from ASN.1 is unknown + * @throws IOException if the DER encoded key can't be parsed + */ + private static String getKeyAlgorithmIdentifier(byte[] keyBytes) throws IOException, GeneralSecurityException { + DerParser parser = new DerParser(keyBytes); + DerParser.Asn1Object sequence = parser.readAsn1Object(); + parser = sequence.getParser(); + parser.readAsn1Object().getInteger(); // version + DerParser.Asn1Object algSequence = parser.readAsn1Object(); + parser = algSequence.getParser(); + String oidString = parser.readAsn1Object().getOid(); + switch (oidString) { + case "1.2.840.10040.4.1": + return "DSA"; + case "1.2.840.113549.1.1.1": + return "RSA"; + case "1.2.840.10045.2.1": + return "EC"; + } + throw new GeneralSecurityException("Error parsing key algorithm identifier. Algorithm with OID [" + oidString + + "] is not żsupported"); + } + + static List readCertificates(Collection certPaths) throws CertificateException, IOException { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + List certificates = new ArrayList<>(certPaths.size()); + for (Path path : certPaths) { + try (InputStream input = Files.newInputStream(path)) { + certificates.addAll(certFactory.generateCertificates(input)); + } + } + return certificates; + } + + private static MessageDigest messageDigest(String digestAlgorithm) { + try { + return MessageDigest.getInstance(digestAlgorithm); + } catch (NoSuchAlgorithmException e) { + throw new SslConfigException("unexpected exception creating MessageDigest instance for [" + digestAlgorithm + "]", e); + } + } +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslClientAuthenticationMode.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslClientAuthenticationMode.java new file mode 100644 index 0000000000000..e46555be702c4 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslClientAuthenticationMode.java @@ -0,0 +1,97 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.common.ssl; + +import javax.net.ssl.SSLParameters; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * The client authentication mode that is used for SSL servers. + */ +public enum SslClientAuthenticationMode { + + /** + * Never request a client certificate. + */ + NONE() { + public boolean enabled() { + return false; + } + + public void configure(SSLParameters sslParameters) { + // nothing to do here + assert !sslParameters.getWantClientAuth(); + assert !sslParameters.getNeedClientAuth(); + } + }, + /** + * Request a client certificate, but do not enforce that one is provided. + */ + OPTIONAL() { + public boolean enabled() { + return true; + } + + public void configure(SSLParameters sslParameters) { + sslParameters.setWantClientAuth(true); + } + }, + /** + * Request and require a client certificate. + */ + REQUIRED() { + public boolean enabled() { + return true; + } + + public void configure(SSLParameters sslParameters) { + sslParameters.setNeedClientAuth(true); + } + }; + + /** + * @return true if client authentication is enabled + */ + public abstract boolean enabled(); + + /** + * Configure client authentication of the provided {@link SSLParameters} + */ + public abstract void configure(SSLParameters sslParameters); + + private static final Map LOOKUP = new LinkedHashMap<>(3); + static { + LOOKUP.put("none", NONE); + LOOKUP.put("optional", OPTIONAL); + LOOKUP.put("required", REQUIRED); + } + + public static SslClientAuthenticationMode parse(String value) { + final SslClientAuthenticationMode mode = LOOKUP.get(value.toLowerCase(Locale.ROOT)); + if (mode == null) { + final String allowedValues = LOOKUP.keySet().stream().collect(Collectors.joining(",")); + throw new SslConfigException("could not resolve ssl client authentication, unknown value [" + + value + "], recognised values are [" + allowedValues + "]"); + } + return mode; + } +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigException.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigException.java new file mode 100644 index 0000000000000..ae5d332e0a1e7 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigException.java @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +/** + * A base exception for problems that occur while trying to configure SSL. + */ +public class SslConfigException extends RuntimeException { + public SslConfigException(String message, Exception cause) { + super(message, cause); + } + + public SslConfigException(String message) { + super(message); + } +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java new file mode 100644 index 0000000000000..1fe6277e1bca7 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java @@ -0,0 +1,160 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509ExtendedTrustManager; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * A object encapsulating all necessary configuration for an SSL context (client or server). + * The configuration itself is immutable, but the {@link #getKeyConfig() key config} and + * {@link #getTrustConfig() trust config} may depend on reading key and certificate material + * from files (see {@link #getDependentFiles()}, and the content of those files may change. + */ +public class SslConfiguration { + + private final SslTrustConfig trustConfig; + private final SslKeyConfig keyConfig; + private final SslVerificationMode verificationMode; + private final SslClientAuthenticationMode clientAuth; + private final List ciphers; + private final List supportedProtocols; + + public SslConfiguration(SslTrustConfig trustConfig, SslKeyConfig keyConfig, SslVerificationMode verificationMode, + SslClientAuthenticationMode clientAuth, List ciphers, List supportedProtocols) { + this.trustConfig = trustConfig; + this.keyConfig = keyConfig; + this.verificationMode = verificationMode; + this.clientAuth = clientAuth; + this.ciphers = Collections.unmodifiableList(ciphers); + this.supportedProtocols = Collections.unmodifiableList(supportedProtocols); + } + + public SslTrustConfig getTrustConfig() { + return trustConfig; + } + + public SslKeyConfig getKeyConfig() { + return keyConfig; + } + + public SslVerificationMode getVerificationMode() { + return verificationMode; + } + + public SslClientAuthenticationMode getClientAuth() { + return clientAuth; + } + + public List getCipherSuites() { + return ciphers; + } + + public List getSupportedProtocols() { + return supportedProtocols; + } + + /** + * @return A collection of files that are used by this SSL configuration. If the contents of these files change, then any + * subsequent call to {@link #createSslContext()} (or similar methods) may create a context with different behaviour. + * It is recommended that these files be monitored for changes, and a new ssl-context is created whenever any of the files are modified. + */ + public Collection getDependentFiles() { + Set paths = new HashSet<>(keyConfig.getDependentFiles()); + paths.addAll(trustConfig.getDependentFiles()); + return paths; + } + + /** + * Dynamically create a new SSL context based on the current state of the configuration. + * Because the {@link #getKeyConfig() key config} and {@link #getTrustConfig() trust config} may change based on the + * contents of their referenced files (see {@link #getDependentFiles()}, consecutive calls to this method may + * return ssl-contexts with different configurations. + */ + public SSLContext createSslContext() { + try { + final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); + final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); + SSLContext sslContext = SSLContext.getInstance(contextAlgorithm()); + sslContext.init(new X509ExtendedKeyManager[] { keyManager }, new X509ExtendedTrustManager[] { trustManager }, null); + return sslContext; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new SslConfigException("cannot create ssl context", e); + } + } + + /** + * Picks the best (highest security / most recent standard) SSL/TLS algorithm that is supported by the + * {@link #getSupportedProtocols() configured protocols}. + */ + private String contextAlgorithm() { + if (supportedProtocols.isEmpty()) { + // shouldn't happen... + return "TLSv1.2"; + } + for (String tryProtocol : Arrays.asList("TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3")) { + if (supportedProtocols.contains(tryProtocol)) { + return tryProtocol; + } + } + return "SSL"; + } + + @Override + public String toString() { + return getClass().getSimpleName() + '{' + + "trustConfig=" + trustConfig + + ", keyConfig=" + keyConfig + + ", verificationMode=" + verificationMode + + ", clientAuth=" + clientAuth + + ", ciphers=" + ciphers + + ", supportedProtocols=" + supportedProtocols + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final SslConfiguration that = (SslConfiguration) o; + return Objects.equals(this.trustConfig, that.trustConfig) && + Objects.equals(this.keyConfig, that.keyConfig) && + this.verificationMode == that.verificationMode && + this.clientAuth == that.clientAuth && + Objects.equals(this.ciphers, that.ciphers) && + Objects.equals(this.supportedProtocols, that.supportedProtocols); + } + + @Override + public int hashCode() { + return Objects.hash(trustConfig, keyConfig, verificationMode, clientAuth, ciphers, supportedProtocols); + } +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationKeys.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationKeys.java new file mode 100644 index 0000000000000..6e717f1c4cd11 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationKeys.java @@ -0,0 +1,181 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.TrustManagerFactory; +import java.security.KeyStore; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Utility class for handling the standard setting keys for use in SSL configuration. + * + * @see SslConfiguration + * @see SslConfigurationLoader + */ +public class SslConfigurationKeys { + /** + * The SSL/TLS protocols (i.e. versions) that should be used + */ + public static final String PROTOCOLS = "supported_protocols"; + + /** + * The SSL/TLS cipher suites that should be used + */ + public static final String CIPHERS = "cipher_suites"; + + /** + * Whether certificate and/or hostname verification should be used + */ + public static final String VERIFICATION_MODE = "verification_mode"; + + /** + * When operating as a server, whether to request/require client certificates + */ + public static final String CLIENT_AUTH = "client_authentication"; + + // Trust + /** + * A list of paths to PEM formatted certificates that should be trusted as CAs + */ + public static final String CERTIFICATE_AUTHORITIES = "certificate_authorities"; + /** + * The path to a KeyStore file (in a format supported by this JRE) that should be used as a trust-store + */ + public static final String TRUSTSTORE_PATH = "truststore.path"; + /** + * The password for the file configured in {@link #TRUSTSTORE_PATH}, as a secure setting. + */ + public static final String TRUSTSTORE_SECURE_PASSWORD = "truststore.secure_password"; + /** + * The password for the file configured in {@link #TRUSTSTORE_PATH}, as a non-secure setting. + * The use of this setting {@link #isDeprecated(String) is deprecated}. + */ + public static final String TRUSTSTORE_LEGACY_PASSWORD = "truststore.password"; + /** + * The {@link KeyStore#getType() keystore type} for the file configured in {@link #TRUSTSTORE_PATH}. + */ + public static final String TRUSTSTORE_TYPE = "truststore.type"; + /** + * The {@link TrustManagerFactory#getAlgorithm() trust management algorithm} to use when configuring trust + * with a {@link #TRUSTSTORE_PATH truststore}. + */ + public static final String TRUSTSTORE_ALGORITHM = "truststore.algorithm"; + + // Key Management + // -- Keystore + /** + * The path to a KeyStore file (in a format supported by this JRE) that should be used for key management + */ + public static final String KEYSTORE_PATH = "keystore.path"; + /** + * The password for the file configured in {@link #KEYSTORE_PATH}, as a secure setting. + */ + public static final String KEYSTORE_SECURE_PASSWORD = "keystore.secure_password"; + /** + * The password for the file configured in {@link #KEYSTORE_PATH}, as a non-secure setting. + * The use of this setting {@link #isDeprecated(String) is deprecated}. + */ + public static final String KEYSTORE_LEGACY_PASSWORD = "keystore.password"; + /** + * The password for the key within the {@link #KEYSTORE_PATH configured keystore}, as a secure setting. + * If no key password is specified, it will default to the keystore password. + */ + public static final String KEYSTORE_SECURE_KEY_PASSWORD = "keystore.secure_key_password"; + /** + * The password for the key within the {@link #KEYSTORE_PATH configured keystore}, as a non-secure setting. + * The use of this setting {@link #isDeprecated(String) is deprecated}. + * If no key password is specified, it will default to the keystore password. + */ + public static final String KEYSTORE_LEGACY_KEY_PASSWORD = "keystore.key_password"; + /** + * The {@link KeyStore#getType() keystore type} for the file configured in {@link #KEYSTORE_PATH}. + */ + public static final String KEYSTORE_TYPE = "keystore.type"; + /** + * The {@link javax.net.ssl.KeyManagerFactory#getAlgorithm() key management algorithm} to use when + * connstructing a Key manager from a {@link #KEYSTORE_PATH keystore}. + */ + public static final String KEYSTORE_ALGORITHM = "keystore.algorithm"; + // -- PEM + /** + * The path to a PEM formatted file that contains the certificate to be used as part of key management + */ + public static final String CERTIFICATE = "certificate"; + /** + * The path to a PEM formatted file that contains the private key for the configured {@link #CERTIFICATE}. + */ + public static final String KEY = "key"; + /** + * The password to read the configured {@link #KEY}, as a secure setting. + * This (or the {@link #KEY_LEGACY_PASSPHRASE legacy fallback}) is required if the key file is encrypted. + */ + public static final String KEY_SECURE_PASSPHRASE = "secure_key_passphrase"; + /** + * The password to read the configured {@link #KEY}, as a non-secure setting. + * The use of this setting {@link #isDeprecated(String) is deprecated}. + */ + public static final String KEY_LEGACY_PASSPHRASE = "key_passphrase"; + + private static final Set DEPRECATED_KEYS = new HashSet<>( + Arrays.asList(TRUSTSTORE_LEGACY_PASSWORD, KEYSTORE_LEGACY_PASSWORD, KEYSTORE_LEGACY_KEY_PASSWORD, KEY_LEGACY_PASSPHRASE) + ); + + private SslConfigurationKeys() { + throw new IllegalStateException("Utility class should not be instantiated"); + } + + /** + * The list of keys that are used to load a non-secure, non-list setting + */ + public static List getStringKeys() { + return Arrays.asList( + VERIFICATION_MODE, CLIENT_AUTH, + TRUSTSTORE_PATH, TRUSTSTORE_LEGACY_PASSWORD, TRUSTSTORE_TYPE, TRUSTSTORE_TYPE, + KEYSTORE_PATH, KEYSTORE_LEGACY_PASSWORD, KEYSTORE_LEGACY_KEY_PASSWORD, KEYSTORE_TYPE, KEYSTORE_ALGORITHM, + CERTIFICATE, KEY, KEY_LEGACY_PASSPHRASE + ); + } + + /** + * The list of keys that are used to load a non-secure, list setting + */ + public static List getListKeys() { + return Arrays.asList(PROTOCOLS, CIPHERS, CERTIFICATE_AUTHORITIES); + } + + /** + * The list of keys that are used to load a secure setting (such as a password) that would typically be stored in the elasticsearch + * keystore. + */ + public static List getSecureStringKeys() { + return Arrays.asList(TRUSTSTORE_SECURE_PASSWORD, KEYSTORE_SECURE_PASSWORD, KEYSTORE_SECURE_KEY_PASSWORD, KEY_SECURE_PASSPHRASE); + } + + /** + * @return {@code true} if the provided key is a deprecated setting + */ + public static boolean isDeprecated(String key) { + return DEPRECATED_KEYS.contains(key); + } + +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java new file mode 100644 index 0000000000000..8af6662b43b05 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java @@ -0,0 +1,366 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.crypto.Cipher; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.elasticsearch.common.ssl.KeyStoreUtil.inferKeyStoreType; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.CERTIFICATE; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.CERTIFICATE_AUTHORITIES; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.CIPHERS; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.CLIENT_AUTH; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEY; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEYSTORE_ALGORITHM; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEYSTORE_LEGACY_KEY_PASSWORD; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEYSTORE_LEGACY_PASSWORD; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEYSTORE_PATH; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEYSTORE_SECURE_KEY_PASSWORD; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEYSTORE_SECURE_PASSWORD; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEYSTORE_TYPE; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEY_LEGACY_PASSPHRASE; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEY_SECURE_PASSPHRASE; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.PROTOCOLS; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.TRUSTSTORE_ALGORITHM; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.TRUSTSTORE_LEGACY_PASSWORD; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.TRUSTSTORE_PATH; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.TRUSTSTORE_SECURE_PASSWORD; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.TRUSTSTORE_TYPE; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.VERIFICATION_MODE; + +/** + * Loads {@link SslConfiguration} from settings. + * This class handles the logic of interpreting the various "ssl.*" configuration settings and their interactions + * (as well as being aware of dependencies and conflicts between different settings). + * The constructed {@code SslConfiguration} has sensible defaults for any settings that are not explicitly configured, + * and these defaults can be overridden through the various {@code setDefaultXyz} methods. + * It is {@code abstract} because this library has minimal dependencies, so the extraction of the setting values from + * the underlying setting source must be handled by the code that makes use of this class. + * + * @see SslConfiguration + * @see SslConfigurationKeys + */ +public abstract class SslConfigurationLoader { + + static final List DEFAULT_PROTOCOLS = Arrays.asList("TLSv1.2", "TLSv1.1", "TLSv1"); + static final List DEFAULT_CIPHERS = loadDefaultCiphers(); + private static final char[] EMPTY_PASSWORD = new char[0]; + + private final String settingPrefix; + + private SslTrustConfig defaultTrustConfig; + private SslKeyConfig defaultKeyConfig; + private SslVerificationMode defaultVerificationMode; + private SslClientAuthenticationMode defaultClientAuth; + private List defaultCiphers; + private List defaultProtocols; + + /** + * Construct a new loader with the "standard" default values. + * + * @param settingPrefix The prefix to apply to all settings that are loaded. It may be the empty string, otherwise it + * must end in a "." (period). For example, if the prefix is {@code "reindex.ssl."} then the keys that are + * passed to methods like {@link #getSettingAsString(String)} will be in the form + * {@code "reindex.ssl.verification_mode"}, and those same keys will be reported in error messages (via + * {@link SslConfigException}). + */ + public SslConfigurationLoader(String settingPrefix) { + this.settingPrefix = settingPrefix == null ? "" : settingPrefix; + if (this.settingPrefix.isEmpty() == false && this.settingPrefix.endsWith(".") == false) { + throw new IllegalArgumentException("Setting prefix [" + settingPrefix + "] must be blank or end in '.'"); + } + this.defaultTrustConfig = new DefaultJdkTrustConfig(); + this.defaultKeyConfig = EmptyKeyConfig.INSTANCE; + this.defaultVerificationMode = SslVerificationMode.FULL; + this.defaultClientAuth = SslClientAuthenticationMode.OPTIONAL; + this.defaultProtocols = DEFAULT_PROTOCOLS; + this.defaultCiphers = DEFAULT_CIPHERS; + } + + /** + * Change the default trust config. + * The initial trust config is {@link DefaultJdkTrustConfig}, which trusts the JDK's default CA certs + */ + public void setDefaultTrustConfig(SslTrustConfig defaultTrustConfig) { + this.defaultTrustConfig = defaultTrustConfig; + } + + /** + * Change the default key config. + * The initial key config is {@link EmptyKeyConfig}, which does not provide any keys + */ + public void setDefaultKeyConfig(SslKeyConfig defaultKeyConfig) { + this.defaultKeyConfig = defaultKeyConfig; + } + + /** + * Change the default verification mode. + * The initial verification mode is {@link SslVerificationMode#FULL}. + */ + public void setDefaultVerificationMode(SslVerificationMode defaultVerificationMode) { + this.defaultVerificationMode = defaultVerificationMode; + } + + /** + * Change the default client authentication mode. + * The initial client auth mode is {@link SslClientAuthenticationMode#OPTIONAL}. + */ + public void setDefaultClientAuth(SslClientAuthenticationMode defaultClientAuth) { + this.defaultClientAuth = defaultClientAuth; + } + + /** + * Change the default supported ciphers. + * The initial cipher list depends on the availability of {@link #has256BitAES() 256 bit AES}. + * + * @see #loadDefaultCiphers() + */ + public void setDefaultCiphers(List defaultCiphers) { + this.defaultCiphers = defaultCiphers; + } + + /** + * Change the default SSL/TLS protocol list. + * The initial protocol list is defined by {@link #DEFAULT_PROTOCOLS} + */ + public void setDefaultProtocols(List defaultProtocols) { + this.defaultProtocols = defaultProtocols; + } + + /** + * Clients of this class should implement this method to load a fully-qualified key from the preferred settings source. + * This method will be called for basic string settings (see {@link SslConfigurationKeys#getStringKeys()}). + *

+ * The setting should be returned as a string, and this class will convert it to the relevant type. + * + * @throws Exception If a {@link RuntimeException} is thrown, it will be rethrown unwrapped. All checked exceptions are wrapped in + * {@link SslConfigException} before being rethrown. + */ + protected abstract String getSettingAsString(String key) throws Exception; + + /** + * Clients of this class should implement this method to load a fully-qualified key from the preferred secure settings source. + * This method will be called for any setting keys that are marked as being + * {@link SslConfigurationKeys#getSecureStringKeys() secure} settings. + * + * @throws Exception If a {@link RuntimeException} is thrown, it will be rethrown unwrapped. All checked exceptions are wrapped in + * {@link SslConfigException} before being rethrown. + */ + protected abstract char[] getSecureSetting(String key) throws Exception; + + /** + * Clients of this class should implement this method to load a fully-qualified key from the preferred settings source. + * This method will be called for list settings (see {@link SslConfigurationKeys#getListKeys()}). + *

+ * The setting should be returned as a list of strings, and this class will convert the values to the relevant type. + * + * @throws Exception If a {@link RuntimeException} is thrown, it will be rethrown unwrapped. All checked exceptions are wrapped in + * {@link SslConfigException} before being rethrown. + */ + protected abstract List getSettingAsList(String key) throws Exception; + + /** + * Resolve all necessary configuration settings, and load a {@link SslConfiguration}. + * + * @param basePath The base path to use for any settings that represent file paths. Typically points to the Elasticsearch + * configuration directory. + * + * @throws SslConfigException For any problems with the configuration, or with loading the required SSL classes. + */ + public SslConfiguration load(Path basePath) { + Objects.requireNonNull(basePath, "Base Path cannot be null"); + final List protocols = resolveListSetting(PROTOCOLS, Function.identity(), defaultProtocols); + final List ciphers = resolveListSetting(CIPHERS, Function.identity(), defaultCiphers); + final SslVerificationMode verificationMode = resolveSetting(VERIFICATION_MODE, SslVerificationMode::parse, defaultVerificationMode); + final SslClientAuthenticationMode clientAuth = resolveSetting(CLIENT_AUTH, SslClientAuthenticationMode::parse, defaultClientAuth); + + final SslTrustConfig trustConfig = buildTrustConfig(basePath, verificationMode); + final SslKeyConfig keyConfig = buildKeyConfig(basePath); + + return new SslConfiguration(trustConfig, keyConfig, verificationMode, clientAuth, ciphers, protocols); + } + + private SslTrustConfig buildTrustConfig(Path basePath, SslVerificationMode verificationMode) { + final List certificateAuthorities = resolveListSetting(CERTIFICATE_AUTHORITIES, basePath::resolve, null); + final Path trustStorePath = resolveSetting(TRUSTSTORE_PATH, basePath::resolve, null); + + if (certificateAuthorities != null && trustStorePath != null) { + throw new SslConfigException("cannot specify both [" + settingPrefix + CERTIFICATE_AUTHORITIES + "] and [" + + settingPrefix + TRUSTSTORE_PATH + "]"); + } + if (verificationMode.isCertificateVerificationEnabled() == false) { + return TrustEverythingConfig.TRUST_EVERYTHING; + } + if (certificateAuthorities != null) { + return new PemTrustConfig(certificateAuthorities); + } + if (trustStorePath != null) { + final char[] password = resolvePasswordSetting(TRUSTSTORE_SECURE_PASSWORD, TRUSTSTORE_LEGACY_PASSWORD); + final String storeType = resolveSetting(TRUSTSTORE_TYPE, Function.identity(), inferKeyStoreType(trustStorePath)); + final String algorithm = resolveSetting(TRUSTSTORE_ALGORITHM, Function.identity(), TrustManagerFactory.getDefaultAlgorithm()); + return new StoreTrustConfig(trustStorePath, password, storeType, algorithm); + } + return defaultTrustConfig; + } + + private SslKeyConfig buildKeyConfig(Path basePath) { + final Path certificatePath = resolveSetting(CERTIFICATE, basePath::resolve, null); + final Path keyPath = resolveSetting(KEY, basePath::resolve, null); + final Path keyStorePath = resolveSetting(KEYSTORE_PATH, basePath::resolve, null); + + if (certificatePath != null && keyStorePath != null) { + throw new SslConfigException("cannot specify both [" + settingPrefix + CERTIFICATE + "] and [" + + settingPrefix + KEYSTORE_PATH + "]"); + } + + if (certificatePath != null || keyPath != null) { + if (keyPath == null) { + throw new SslConfigException("cannot specify [" + settingPrefix + CERTIFICATE + "] without also setting [" + + settingPrefix + KEY + "]"); + } + if (certificatePath == null) { + throw new SslConfigException("cannot specify [" + settingPrefix + KEYSTORE_PATH + "] without also setting [" + + settingPrefix + CERTIFICATE + "]"); + } + final char[] password = resolvePasswordSetting(KEY_SECURE_PASSPHRASE, KEY_LEGACY_PASSPHRASE); + return new PemKeyConfig(certificatePath, keyPath, password); + } + + if (keyStorePath != null) { + final char[] storePassword = resolvePasswordSetting(KEYSTORE_SECURE_PASSWORD, KEYSTORE_LEGACY_PASSWORD); + char[] keyPassword = resolvePasswordSetting(KEYSTORE_SECURE_KEY_PASSWORD, KEYSTORE_LEGACY_KEY_PASSWORD); + if (keyPassword.length == 0) { + keyPassword = storePassword; + } + final String storeType = resolveSetting(KEYSTORE_TYPE, Function.identity(), inferKeyStoreType(keyStorePath)); + final String algorithm = resolveSetting(KEYSTORE_ALGORITHM, Function.identity(), KeyManagerFactory.getDefaultAlgorithm()); + return new StoreKeyConfig(keyStorePath, storePassword, storeType, keyPassword, algorithm); + } + + return defaultKeyConfig; + } + + private char[] resolvePasswordSetting(String secureSettingKey, String legacySettingKey) { + final char[] securePassword = resolveSecureSetting(secureSettingKey, null); + final String legacyPassword = resolveSetting(legacySettingKey, Function.identity(), null); + if (securePassword == null) { + if (legacyPassword == null) { + return EMPTY_PASSWORD; + } else { + return legacyPassword.toCharArray(); + } + } else { + if (legacyPassword != null) { + throw new SslConfigException("cannot specify both [" + settingPrefix + secureSettingKey + "] and [" + + settingPrefix + legacySettingKey + "]"); + } else { + return securePassword; + } + } + } + + private V resolveSetting(String key, Function parser, V defaultValue) { + try { + String setting = getSettingAsString(settingPrefix + key); + if (setting == null || setting.isEmpty()) { + return defaultValue; + } + return parser.apply(setting); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new SslConfigException("cannot retrieve setting [" + settingPrefix + key + "]", e); + } + } + + private char[] resolveSecureSetting(String key, char[] defaultValue) { + try { + char[] setting = getSecureSetting(settingPrefix + key); + if (setting == null || setting.length == 0) { + return defaultValue; + } + return setting; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new SslConfigException("cannot retrieve secure setting [" + settingPrefix + key + "]", e); + } + + } + + private List resolveListSetting(String key, Function parser, List defaultValue) { + try { + final List list = getSettingAsList(settingPrefix + key); + if (list == null || list.isEmpty()) { + return defaultValue; + } + return list.stream().map(parser).collect(Collectors.toList()); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new SslConfigException("cannot retrieve setting [" + settingPrefix + key + "]", e); + } + } + + private static List loadDefaultCiphers() { + final List ciphers128 = Arrays.asList( + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA" + ); + final List ciphers256 = Arrays.asList( + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA" + ); + if (has256BitAES()) { + List ciphers = new ArrayList<>(ciphers256.size() + ciphers128.size()); + ciphers.addAll(ciphers256); + ciphers.addAll(ciphers128); + return ciphers; + } else { + return ciphers128; + } + } + + private static boolean has256BitAES() { + try { + return Cipher.getMaxAllowedKeyLength("AES") > 128; + } catch (NoSuchAlgorithmException e) { + // No AES? Things are going to be very weird, but technically that means we don't have 256 bit AES, so ... + return false; + } + } +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslKeyConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslKeyConfig.java new file mode 100644 index 0000000000000..4f5e6b8669310 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslKeyConfig.java @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.X509ExtendedKeyManager; +import java.nio.file.Path; +import java.util.Collection; + +/** + * An interface for building a key manager at runtime. + * The method for constructing the key manager is implementation dependent. + */ +public interface SslKeyConfig { + + /** + * @return A collection of files that are read by this config object. + * The {@link #createKeyManager()} method will read these files dynamically, so the behaviour of this key config may change whenever + * any of these files are modified. + */ + Collection getDependentFiles(); + + /** + * @return A new {@link X509ExtendedKeyManager}. + * @throws SslConfigException if there is a problem configuring the key manager. + */ + X509ExtendedKeyManager createKeyManager(); + +} + diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslTrustConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslTrustConfig.java new file mode 100644 index 0000000000000..2cd61218e6bb7 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslTrustConfig.java @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.X509ExtendedTrustManager; +import java.nio.file.Path; +import java.util.Collection; + +/** + * An interface for building a trust manager at runtime. + * The method for constructing the trust manager is implementation dependent. + */ +public interface SslTrustConfig { + + /** + * @return A collection of files that are read by this config object. + * The {@link #createTrustManager()} method will read these files dynamically, so the behaviour of this trust config may change if + * any of these files are modified. + */ + Collection getDependentFiles(); + + /** + * @return A new {@link X509ExtendedTrustManager}. + * @throws SslConfigException if there is a problem configuring the trust manager. + */ + X509ExtendedTrustManager createTrustManager(); + +} + diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslVerificationMode.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslVerificationMode.java new file mode 100644 index 0000000000000..8771f80173561 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslVerificationMode.java @@ -0,0 +1,100 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.common.ssl; + +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Represents the verification mode to be used for SSL connections. + */ +public enum SslVerificationMode { + /** + * Verify neither the hostname, nor the provided certificate. + */ + NONE { + @Override + public boolean isHostnameVerificationEnabled() { + return false; + } + + @Override + public boolean isCertificateVerificationEnabled() { + return false; + } + }, + /** + * Verify the provided certificate against the trust chain, but do not verify the hostname. + */ + CERTIFICATE { + @Override + public boolean isHostnameVerificationEnabled() { + return false; + } + + @Override + public boolean isCertificateVerificationEnabled() { + return true; + } + }, + /** + * Verify the provided certificate against the trust chain, and also verify that the hostname to which this client is connected + * matches one of the Subject-Alternative-Names in the certificate. + */ + FULL { + @Override + public boolean isHostnameVerificationEnabled() { + return true; + } + + @Override + public boolean isCertificateVerificationEnabled() { + return true; + } + }; + + /** + * @return true if hostname verification is enabled + */ + public abstract boolean isHostnameVerificationEnabled(); + + /** + * @return true if certificate verification is enabled + */ + public abstract boolean isCertificateVerificationEnabled(); + + private static final Map LOOKUP = new LinkedHashMap<>(3); + static { + LOOKUP.put("none", NONE); + LOOKUP.put("certificate", CERTIFICATE); + LOOKUP.put("full", FULL); + } + + public static SslVerificationMode parse(String value) { + final SslVerificationMode mode = LOOKUP.get(value.toLowerCase(Locale.ROOT)); + if (mode == null) { + final String allowedValues = LOOKUP.keySet().stream().collect(Collectors.joining(",")); + throw new SslConfigException("could not resolve ssl client verification mode, unknown value [" + + value + "], recognised values are [" + allowedValues + "]"); + } + return mode; + } +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreKeyConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreKeyConfig.java new file mode 100644 index 0000000000000..683aaaaa06cb4 --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreKeyConfig.java @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.UnrecoverableKeyException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; + +/** + * A {@link SslKeyConfig} that builds a Key Manager from a keystore file. + */ +public class StoreKeyConfig implements SslKeyConfig { + private final Path path; + private final char[] storePassword; + private final String type; + private final char[] keyPassword; + private final String algorithm; + + /** + * @param path The path to the keystore file + * @param storePassword The password for the keystore + * @param type The {@link KeyStore#getType() type} of the keystore (typically "PKCS12" or "jks"). + * See {@link KeyStoreUtil#inferKeyStoreType(Path)}. + * @param keyPassword The password for the key(s) within the keystore + * (see {@link javax.net.ssl.KeyManagerFactory#init(KeyStore, char[])}). + * @param algorithm The algorithm to use for the Key Manager (see {@link KeyManagerFactory#getAlgorithm()}). + */ + StoreKeyConfig(Path path, char[] storePassword, String type, char[] keyPassword, String algorithm) { + this.path = path; + this.storePassword = storePassword; + this.type = type; + this.keyPassword = keyPassword; + this.algorithm = algorithm; + } + + @Override + public Collection getDependentFiles() { + return Collections.singleton(path); + } + + @Override + public X509ExtendedKeyManager createKeyManager() { + try { + final KeyStore keyStore = KeyStoreUtil.readKeyStore(path, type, storePassword); + checkKeyStore(keyStore); + return KeyStoreUtil.createKeyManager(keyStore, keyPassword, algorithm); + } catch (UnrecoverableKeyException e) { + String message = "failed to load a KeyManager for keystore [" + path.toAbsolutePath() + + "], this is usually caused by an incorrect key-password"; + if (keyPassword.length == 0) { + message += " (no key-password was provided)"; + } else if (Arrays.equals(storePassword, keyPassword)) { + message += " (we tried to access the key using the same password as the keystore)"; + } + throw new SslConfigException(message, e); + } catch (GeneralSecurityException e) { + throw new SslConfigException("failed to load a KeyManager for keystore [" + path + "] of type [" + type + "]", e); + } + } + + /** + * Verifies that the keystore contains at least 1 private key entry. + */ + private void checkKeyStore(KeyStore keyStore) throws KeyStoreException { + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + if (keyStore.isKeyEntry(alias)) { + return; + } + } + final String message; + if (path != null) { + message = "the keystore [" + path + "] does not contain a private key entry"; + } else { + message = "the configured PKCS#11 token does not contain a private key entry"; + } + throw new SslConfigException(message); + } + +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreTrustConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreTrustConfig.java new file mode 100644 index 0000000000000..0dc0a3818c8ed --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreTrustConfig.java @@ -0,0 +1,90 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.X509ExtendedTrustManager; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; + +/** + * A {@link SslTrustConfig} that builds a Trust Manager from a keystore file. + */ +final class StoreTrustConfig implements SslTrustConfig { + private final Path path; + private final char[] password; + private final String type; + private final String algorithm; + + /** + * @param path The path to the keystore file + * @param password The password for the keystore + * @param type The {@link KeyStore#getType() type} of the keystore (typically "PKCS12" or "jks"). + * See {@link KeyStoreUtil#inferKeyStoreType(Path)}. + * @param algorithm The algorithm to use for the Trust Manager (see {@link javax.net.ssl.TrustManagerFactory#getAlgorithm()}). + */ + StoreTrustConfig(Path path, char[] password, String type, String algorithm) { + this.path = path; + this.type = type; + this.algorithm = algorithm; + this.password = password; + } + + @Override + public Collection getDependentFiles() { + return Collections.singleton(path); + } + + @Override + public X509ExtendedTrustManager createTrustManager() { + try { + final KeyStore store = KeyStoreUtil.readKeyStore(path, type, password); + checkTrustStore(store); + return KeyStoreUtil.createTrustManager(store, algorithm); + } catch (GeneralSecurityException e) { + throw new SslConfigException("cannot create trust manager for path=[" + (path == null ? null : path.toAbsolutePath()) + + "] type=[" + type + "] password=[" + (password.length == 0 ? "" : "") + "]", e); + } + } + + /** + * Verifies that the keystore contains at least 1 trusted certificate entry. + */ + private void checkTrustStore(KeyStore store) throws GeneralSecurityException { + Enumeration aliases = store.aliases(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + if (store.isCertificateEntry(alias)) { + return; + } + } + final String message; + if (path != null) { + message = "the truststore [" + path + "] does not contain any trusted certificate entries"; + } else { + message = "the configured PKCS#11 token does not contain any trusted certificate entries"; + } + throw new SslConfigException(message); + } + +} diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/TrustEverythingConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/TrustEverythingConfig.java new file mode 100644 index 0000000000000..f3ed83a7e7d7b --- /dev/null +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/TrustEverythingConfig.java @@ -0,0 +1,92 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedTrustManager; +import java.net.Socket; +import java.nio.file.Path; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Collections; + +/** + * A {@link SslTrustConfig} that trusts all certificates. Used when {@link SslVerificationMode#isCertificateVerificationEnabled()} is + * {@code false}. + * This class cannot be used on FIPS-140 JVM as it has its own trust manager implementation. + */ +final class TrustEverythingConfig implements SslTrustConfig { + + static final TrustEverythingConfig TRUST_EVERYTHING = new TrustEverythingConfig(); + + private TrustEverythingConfig() { + // single instances + } + + /** + * The {@link X509ExtendedTrustManager} that will trust all certificates. + * All methods are implemented as a no-op and do not throw exceptions regardless of the certificate presented. + */ + private static final X509ExtendedTrustManager TRUST_MANAGER = new X509ExtendedTrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) { + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) { + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + @Override + public Collection getDependentFiles() { + return Collections.emptyList(); + } + + @Override + public X509ExtendedTrustManager createTrustManager() { + return TRUST_MANAGER; + } + + @Override + public String toString() { + return "trust everything"; + } +} diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/DefaultJdkTrustConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/DefaultJdkTrustConfigTests.java new file mode 100644 index 0000000000000..b1aad439e47e1 --- /dev/null +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/DefaultJdkTrustConfigTests.java @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.test.ESTestCase; +import org.junit.Assert; + +import javax.net.ssl.X509ExtendedTrustManager; +import java.security.cert.X509Certificate; +import java.util.Locale; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.emptyArray; +import static org.hamcrest.Matchers.emptyIterable; +import static org.hamcrest.Matchers.not; + +public class DefaultJdkTrustConfigTests extends ESTestCase { + + private static final BiFunction EMPTY_SYSTEM_PROPERTIES = (key, defaultValue) -> defaultValue; + + public void testGetSystemTrustStoreWithNoSystemProperties() throws Exception { + final DefaultJdkTrustConfig trustConfig = new DefaultJdkTrustConfig((key, defaultValue) -> defaultValue); + assertThat(trustConfig.getDependentFiles(), emptyIterable()); + final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); + assertStandardIssuers(trustManager); + } + + public void testGetNonPKCS11TrustStoreWithPasswordSet() throws Exception { + final DefaultJdkTrustConfig trustConfig = new DefaultJdkTrustConfig(EMPTY_SYSTEM_PROPERTIES, "fakepassword".toCharArray()); + assertThat(trustConfig.getDependentFiles(), emptyIterable()); + final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); + assertStandardIssuers(trustManager); + } + + private void assertStandardIssuers(X509ExtendedTrustManager trustManager) { + assertThat(trustManager.getAcceptedIssuers(), not(emptyArray())); + // This is a sample of the CAs that we expect on every JRE. + // We can safely change this list if the JRE's issuer list changes, but we want to assert something useful. + assertHasTrustedIssuer(trustManager, "VeriSign"); + assertHasTrustedIssuer(trustManager, "GeoTrust"); + assertHasTrustedIssuer(trustManager, "DigiCert"); + assertHasTrustedIssuer(trustManager, "thawte"); + assertHasTrustedIssuer(trustManager, "COMODO"); + } + + private void assertHasTrustedIssuer(X509ExtendedTrustManager trustManager, String name) { + final String lowerName = name.toLowerCase(Locale.ROOT); + final Optional ca = Stream.of(trustManager.getAcceptedIssuers()) + .filter(cert -> cert.getSubjectDN().getName().toLowerCase(Locale.ROOT).contains(lowerName)) + .findAny(); + if (ca.isPresent() == false) { + logger.info("Failed to find issuer [{}] in trust manager, but did find ...", lowerName); + for (X509Certificate cert : trustManager.getAcceptedIssuers()) { + logger.info(" - {}", cert.getSubjectDN().getName().replaceFirst("^\\w+=([^,]+),.*", "$1")); + } + Assert.fail("Cannot find trusted issuer with name [" + name + "]."); + } + } + +} diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemKeyConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemKeyConfigTests.java new file mode 100644 index 0000000000000..8a5bb469e3c2c --- /dev/null +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemKeyConfigTests.java @@ -0,0 +1,148 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.test.ESTestCase; +import org.hamcrest.Matchers; + +import javax.net.ssl.X509ExtendedKeyManager; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.iterableWithSize; +import static org.hamcrest.Matchers.notNullValue; + +public class PemKeyConfigTests extends ESTestCase { + private static final int IP_NAME = 7; + private static final int DNS_NAME = 2; + + public void testBuildKeyConfigFromPemFilesWithoutPassword() throws Exception { + final Path cert = getDataPath("/certs/cert1/cert1.crt"); + final Path key = getDataPath("/certs/cert1/cert1.key"); + final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key)); + assertCertificateAndKey(keyConfig, "CN=cert1"); + } + + public void testBuildKeyConfigFromPemFilesWithPassword() throws Exception { + final Path cert = getDataPath("/certs/cert2/cert2.crt"); + final Path key = getDataPath("/certs/cert2/cert2.key"); + final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "c2-pass".toCharArray()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key)); + assertCertificateAndKey(keyConfig, "CN=cert2"); + } + + public void testKeyManagerFailsWithIncorrectPassword() throws Exception { + final Path cert = getDataPath("/certs/cert2/cert2.crt"); + final Path key = getDataPath("/certs/cert2/cert2.key"); + final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "wrong-password".toCharArray()); + assertPasswordIsIncorrect(keyConfig, key); + } + + public void testMissingCertificateFailsWithMeaningfulMessage() throws Exception { + final Path key = getDataPath("/certs/cert1/cert1.key"); + final Path cert = key.getParent().resolve("dne.crt"); + + final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]); + assertFileNotFound(keyConfig, "certificate", cert); + } + + public void testMissingKeyFailsWithMeaningfulMessage() throws Exception { + final Path cert = getDataPath("/certs/cert1/cert1.crt"); + final Path key = cert.getParent().resolve("dne.key"); + + final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]); + assertFileNotFound(keyConfig, "private key", key); + } + + public void testKeyConfigReloadsFileContents() throws Exception { + final Path cert1 = getDataPath("/certs/cert1/cert1.crt"); + final Path key1 = getDataPath("/certs/cert1/cert1.key"); + final Path cert2 = getDataPath("/certs/cert2/cert2.crt"); + final Path key2 = getDataPath("/certs/cert2/cert2.key"); + final Path cert = createTempFile("cert", ".crt"); + final Path key = createTempFile("cert", ".key"); + + final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]); + + Files.copy(cert1, cert, StandardCopyOption.REPLACE_EXISTING); + Files.copy(key1, key, StandardCopyOption.REPLACE_EXISTING); + assertCertificateAndKey(keyConfig, "CN=cert1"); + + Files.copy(cert2, cert, StandardCopyOption.REPLACE_EXISTING); + Files.copy(key2, key, StandardCopyOption.REPLACE_EXISTING); + assertPasswordIsIncorrect(keyConfig, key); + + Files.copy(cert1, cert, StandardCopyOption.REPLACE_EXISTING); + Files.copy(key1, key, StandardCopyOption.REPLACE_EXISTING); + assertCertificateAndKey(keyConfig, "CN=cert1"); + + Files.delete(cert); + assertFileNotFound(keyConfig, "certificate", cert); + } + + private void assertCertificateAndKey(PemKeyConfig keyConfig, String expectedDN) throws CertificateParsingException { + final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); + assertThat(keyManager, notNullValue()); + + final PrivateKey privateKey = keyManager.getPrivateKey("key"); + assertThat(privateKey, notNullValue()); + assertThat(privateKey.getAlgorithm(), is("RSA")); + + final X509Certificate[] chain = keyManager.getCertificateChain("key"); + assertThat(chain, notNullValue()); + assertThat(chain, arrayWithSize(1)); + final X509Certificate certificate = chain[0]; + assertThat(certificate.getIssuerDN().getName(), is("CN=Test CA 1")); + assertThat(certificate.getSubjectDN().getName(), is(expectedDN)); + assertThat(certificate.getSubjectAlternativeNames(), iterableWithSize(2)); + assertThat(certificate.getSubjectAlternativeNames(), containsInAnyOrder( + Arrays.asList(DNS_NAME, "localhost"), + Arrays.asList(IP_NAME, "127.0.0.1") + )); + } + + private void assertPasswordIsIncorrect(PemKeyConfig keyConfig, Path key) { + final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); + assertThat(exception.getMessage(), containsString("private key file")); + assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); + assertThat(exception.getCause(), instanceOf(GeneralSecurityException.class)); + } + + private void assertFileNotFound(PemKeyConfig keyConfig, String type, Path file) { + final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); + assertThat(exception.getMessage(), containsString(type + " file")); + assertThat(exception.getMessage(), containsString(file.toAbsolutePath().toString())); + assertThat(exception.getMessage(), containsString("does not exist")); + assertThat(exception.getCause(), instanceOf(NoSuchFileException.class)); + } +} diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemTrustConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemTrustConfigTests.java new file mode 100644 index 0000000000000..770d09589ad5e --- /dev/null +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemTrustConfigTests.java @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.test.ESTestCase; +import org.hamcrest.Matchers; + +import javax.net.ssl.X509ExtendedTrustManager; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PemTrustConfigTests extends ESTestCase { + + public void testBuildTrustConfigFromSinglePemFile() throws Exception { + final Path cert = getDataPath("/certs/ca1/ca.crt"); + final PemTrustConfig trustConfig = new PemTrustConfig(Collections.singletonList(cert)); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert)); + assertCertificateChain(trustConfig, "CN=Test CA 1"); + } + + public void testBuildTrustConfigFromMultiplePemFiles() throws Exception { + final Path cert1 = getDataPath("/certs/ca1/ca.crt"); + final Path cert2 = getDataPath("/certs/ca2/ca.crt"); + final Path cert3 = getDataPath("/certs/ca3/ca.crt"); + final PemTrustConfig trustConfig = new PemTrustConfig(Arrays.asList(cert1, cert2, cert3)); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert1, cert2, cert3)); + assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2", "CN=Test CA 3"); + } + + public void testBadFileFormatFails() throws Exception { + final Path ca = createTempFile("ca", ".crt"); + Files.write(ca, randomByteArrayOfLength(128), StandardOpenOption.APPEND); + final PemTrustConfig trustConfig = new PemTrustConfig(Collections.singletonList(ca)); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ca)); + assertInvalidFileFormat(trustConfig, ca); + } + + public void testMissingFileFailsWithMeaningfulMessage() throws Exception { + final Path cert = getDataPath("/certs/ca1/ca.crt").getParent().resolve("dne.crt"); + final PemTrustConfig trustConfig = new PemTrustConfig(Collections.singletonList(cert)); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert)); + assertFileNotFound(trustConfig, cert); + } + + public void testOneMissingFileFailsWithMeaningfulMessageEvenIfOtherFileExist() throws Exception { + final Path cert1 = getDataPath("/certs/ca1/ca.crt"); + final Path cert2 = getDataPath("/certs/ca2/ca.crt").getParent().resolve("dne.crt"); + final Path cert3 = getDataPath("/certs/ca3/ca.crt"); + final PemTrustConfig trustConfig = new PemTrustConfig(Arrays.asList(cert1, cert2, cert3)); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert1, cert2, cert3)); + assertFileNotFound(trustConfig, cert2); + } + + public void testTrustConfigReloadsFileContents() throws Exception { + final Path cert1 = getDataPath("/certs/ca1/ca.crt"); + final Path cert2 = getDataPath("/certs/ca2/ca.crt"); + final Path cert3 = getDataPath("/certs/ca3/ca.crt"); + + final Path ca1 = createTempFile("ca1", ".crt"); + final Path ca2 = createTempFile("ca2", ".crt"); + + final PemTrustConfig trustConfig = new PemTrustConfig(Arrays.asList(ca1, ca2)); + + Files.copy(cert1, ca1, StandardCopyOption.REPLACE_EXISTING); + Files.copy(cert2, ca2, StandardCopyOption.REPLACE_EXISTING); + assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2"); + + Files.copy(cert3, ca2, StandardCopyOption.REPLACE_EXISTING); + assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 3"); + + Files.delete(ca1); + assertFileNotFound(trustConfig, ca1); + + Files.write(ca1, randomByteArrayOfLength(128), StandardOpenOption.CREATE); + assertInvalidFileFormat(trustConfig, ca1); + } + + private void assertCertificateChain(PemTrustConfig trustConfig, String... caNames) { + final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); + final X509Certificate[] issuers = trustManager.getAcceptedIssuers(); + final Set issuerNames = Stream.of(issuers) + .map(X509Certificate::getSubjectDN) + .map(Principal::getName) + .collect(Collectors.toSet()); + + assertThat(issuerNames, Matchers.containsInAnyOrder(caNames)); + } + + private void assertInvalidFileFormat(PemTrustConfig trustConfig, Path file) { + final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), Matchers.containsString("cannot create trust")); + assertThat(exception.getMessage(), Matchers.containsString("PEM")); + assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); + assertThat(exception.getCause(), Matchers.instanceOf(GeneralSecurityException.class)); + } + + private void assertFileNotFound(PemTrustConfig trustConfig, Path file) { + final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), Matchers.containsString("files do not exist")); + assertThat(exception.getMessage(), Matchers.containsString("PEM")); + assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); + assertThat(exception.getCause(), Matchers.instanceOf(NoSuchFileException.class)); + } +} diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemUtilsTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemUtilsTests.java new file mode 100644 index 0000000000000..60f0cd168ce1e --- /dev/null +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemUtilsTests.java @@ -0,0 +1,219 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.test.ESTestCase; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.util.function.Supplier; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.core.StringContains.containsString; + +public class PemUtilsTests extends ESTestCase { + + private static final Supplier EMPTY_PASSWORD = () -> new char[0]; + private static final Supplier TESTNODE_PASSWORD = "testnode"::toCharArray; + + public void testReadPKCS8RsaKey() throws Exception { + Key key = getKeyFromKeystore("RSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/rsa_key_pkcs8_plain.pem"), EMPTY_PASSWORD); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadPKCS8RsaKeyWithBagAttrs() throws Exception { + Key key = getKeyFromKeystore("RSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/testnode_with_bagattrs.pem"), EMPTY_PASSWORD); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadPKCS8DsaKey() throws Exception { + Key key = getKeyFromKeystore("DSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/dsa_key_pkcs8_plain.pem"), EMPTY_PASSWORD); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadPKCS8EcKey() throws Exception { + Key key = getKeyFromKeystore("EC"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_pkcs8_plain.pem"), EMPTY_PASSWORD); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadEncryptedPKCS8Key() throws Exception { + assumeFalse("Can't run in a FIPS JVM, PBE KeySpec is not available", inFipsJvm()); + Key key = getKeyFromKeystore("RSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath + ("/certs/pem-utils/key_pkcs8_encrypted.pem"), TESTNODE_PASSWORD); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadDESEncryptedPKCS1Key() throws Exception { + Key key = getKeyFromKeystore("RSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/testnode.pem"), TESTNODE_PASSWORD); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadAESEncryptedPKCS1Key() throws Exception { + Key key = getKeyFromKeystore("RSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + String bits = randomFrom("128", "192", "256"); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/testnode-aes" + bits + ".pem"), TESTNODE_PASSWORD); + + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadPKCS1RsaKey() throws Exception { + Key key = getKeyFromKeystore("RSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/testnode-unprotected.pem"), TESTNODE_PASSWORD); + + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadOpenSslDsaKey() throws Exception { + Key key = getKeyFromKeystore("DSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/dsa_key_openssl_plain.pem"), EMPTY_PASSWORD); + + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadOpenSslDsaKeyWithParams() throws Exception { + Key key = getKeyFromKeystore("DSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/dsa_key_openssl_plain_with_params.pem"), + EMPTY_PASSWORD); + + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadEncryptedOpenSslDsaKey() throws Exception { + Key key = getKeyFromKeystore("DSA"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/dsa_key_openssl_encrypted.pem"), TESTNODE_PASSWORD); + + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadOpenSslEcKey() throws Exception { + Key key = getKeyFromKeystore("EC"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_openssl_plain.pem"), EMPTY_PASSWORD); + + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadOpenSslEcKeyWithParams() throws Exception { + Key key = getKeyFromKeystore("EC"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_openssl_plain_with_params.pem"), + EMPTY_PASSWORD); + + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadEncryptedOpenSslEcKey() throws Exception { + Key key = getKeyFromKeystore("EC"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_openssl_encrypted.pem"), TESTNODE_PASSWORD); + + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadUnsupportedKey() { + final Path path = getDataPath("/certs/pem-utils/key_unsupported.pem"); + SslConfigException e = expectThrows(SslConfigException.class, () -> PemUtils.readPrivateKey(path, TESTNODE_PASSWORD)); + assertThat(e.getMessage(), containsString("file does not contain a supported key format")); + assertThat(e.getMessage(), containsString(path.toAbsolutePath().toString())); + } + + public void testReadPemCertificateAsKey() { + final Path path = getDataPath("/certs/pem-utils/testnode.crt"); + SslConfigException e = expectThrows(SslConfigException.class, () -> PemUtils.readPrivateKey(path, TESTNODE_PASSWORD)); + assertThat(e.getMessage(), containsString("file does not contain a supported key format")); + assertThat(e.getMessage(), containsString(path.toAbsolutePath().toString())); + } + + public void testReadCorruptedKey() { + final Path path = getDataPath("/certs/pem-utils/corrupted_key_pkcs8_plain.pem"); + SslConfigException e = expectThrows(SslConfigException.class, () -> PemUtils.readPrivateKey(path, TESTNODE_PASSWORD)); + assertThat(e.getMessage(), containsString("private key")); + assertThat(e.getMessage(), containsString("cannot be parsed")); + assertThat(e.getMessage(), containsString(path.toAbsolutePath().toString())); + assertThat(e.getCause().getMessage(), containsString("PEM footer is invalid or missing")); + } + + public void testReadEmptyFile() { + final Path path = getDataPath("/certs/pem-utils/empty.pem"); + SslConfigException e = expectThrows(SslConfigException.class, () -> PemUtils.readPrivateKey(path, TESTNODE_PASSWORD)); + assertThat(e.getMessage(), containsString("file is empty")); + assertThat(e.getMessage(), containsString(path.toAbsolutePath().toString())); + } + + private Key getKeyFromKeystore(String algo) throws Exception { + Path keystorePath = getDataPath("/certs/pem-utils/testnode.jks"); + try (InputStream in = Files.newInputStream(keystorePath)) { + KeyStore keyStore = KeyStore.getInstance("jks"); + keyStore.load(in, "testnode".toCharArray()); + return keyStore.getKey("testnode_" + algo, "testnode".toCharArray()); + } + } +} diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationLoaderTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationLoaderTests.java new file mode 100644 index 0000000000000..643cc08a76b54 --- /dev/null +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationLoaderTests.java @@ -0,0 +1,219 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.common.settings.MockSecureSettings; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +public class SslConfigurationLoaderTests extends ESTestCase { + + private final Path certRoot = getDataPath("/certs/ca1/ca.crt").getParent().getParent(); + + private Settings settings; + private MockSecureSettings secureSettings = new MockSecureSettings(); + private SslConfigurationLoader loader = new SslConfigurationLoader("test.ssl.") { + @Override + protected String getSettingAsString(String key) throws Exception { + return settings.get(key); + } + + @Override + protected char[] getSecureSetting(String key) throws Exception { + final SecureString secStr = secureSettings.getString(key); + return secStr == null ? null : secStr.getChars(); + } + + @Override + protected List getSettingAsList(String key) throws Exception { + return settings.getAsList(key); + } + }; + + /** + * A test for non-trust, non-key configurations. + * These are straight forward and can all be tested together + */ + public void testBasicConfigurationOptions() { + final SslVerificationMode verificationMode = randomFrom(SslVerificationMode.values()); + final SslClientAuthenticationMode clientAuth = randomFrom(SslClientAuthenticationMode.values()); + final String[] ciphers = generateRandomStringArray(8, 12, false, false); + final String[] protocols = generateRandomStringArray(4, 5, false, false); + settings = Settings.builder() + .put("test.ssl.verification_mode", verificationMode.name().toLowerCase(Locale.ROOT)) + .put("test.ssl.client_authentication", clientAuth.name().toLowerCase(Locale.ROOT)) + .putList("test.ssl.cipher_suites", ciphers) + .putList("test.ssl.supported_protocols", protocols) + .build(); + final SslConfiguration configuration = loader.load(certRoot); + assertThat(configuration.getClientAuth(), is(clientAuth)); + assertThat(configuration.getVerificationMode(), is(verificationMode)); + assertThat(configuration.getCipherSuites(), equalTo(Arrays.asList(ciphers))); + assertThat(configuration.getSupportedProtocols(), equalTo(Arrays.asList(protocols))); + if (verificationMode == SslVerificationMode.NONE) { + final SslTrustConfig trustConfig = configuration.getTrustConfig(); + assertThat(trustConfig, instanceOf(TrustEverythingConfig.class)); + } + } + + public void testLoadTrustFromPemCAs() { + settings = Settings.builder() + .putList("test.ssl.certificate_authorities", "ca1/ca.crt", "ca2/ca.crt", "ca3/ca.crt") + .build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslTrustConfig trustConfig = configuration.getTrustConfig(); + assertThat(trustConfig, instanceOf(PemTrustConfig.class)); + assertThat(trustConfig.getDependentFiles(), + containsInAnyOrder(getDataPath("/certs/ca1/ca.crt"), getDataPath("/certs/ca2/ca.crt"), getDataPath("/certs/ca3/ca.crt"))); + assertThat(trustConfig.createTrustManager(), notNullValue()); + } + + public void testLoadTrustFromPkcs12() { + final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.p12"); + if (randomBoolean()) { + builder.put("test.ssl.truststore.password", "p12-pass"); + } else { + secureSettings.setString("test.ssl.truststore.secure_password", "p12-pass"); + } + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.truststore.type", "PKCS12"); + } + if (randomBoolean()) { + builder.put("test.ssl.truststore.algorithm", TrustManagerFactory.getDefaultAlgorithm()); + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslTrustConfig trustConfig = configuration.getTrustConfig(); + assertThat(trustConfig, instanceOf(StoreTrustConfig.class)); + assertThat(trustConfig.getDependentFiles(), containsInAnyOrder(getDataPath("/certs/ca-all/ca.p12"))); + assertThat(trustConfig.createTrustManager(), notNullValue()); + } + + public void testLoadTrustFromJKS() { + final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.jks"); + if (randomBoolean()) { + builder.put("test.ssl.truststore.password", "jks-pass"); + } else { + secureSettings.setString("test.ssl.truststore.secure_password", "jks-pass"); + } + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.truststore.type", "jks"); + } + if (randomBoolean()) { + builder.put("test.ssl.truststore.algorithm", TrustManagerFactory.getDefaultAlgorithm()); + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslTrustConfig trustConfig = configuration.getTrustConfig(); + assertThat(trustConfig, instanceOf(StoreTrustConfig.class)); + assertThat(trustConfig.getDependentFiles(), containsInAnyOrder(getDataPath("/certs/ca-all/ca.jks"))); + assertThat(trustConfig.createTrustManager(), notNullValue()); + } + + public void testLoadKeysFromPemFiles() { + final boolean usePassword = randomBoolean(); + final boolean useLegacyPassword = usePassword && randomBoolean(); + final String certName = usePassword ? "cert2" : "cert1"; + final Settings.Builder builder = Settings.builder() + .put("test.ssl.certificate", certName + "/" + certName + ".crt") + .put("test.ssl.key", certName + "/" + certName + ".key"); + if (usePassword) { + if (useLegacyPassword) { + builder.put("test.ssl.key_passphrase", "c2-pass"); + } else { + secureSettings.setString("test.ssl.secure_key_passphrase", "c2-pass"); + } + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslKeyConfig keyConfig = configuration.getKeyConfig(); + assertThat(keyConfig, instanceOf(PemKeyConfig.class)); + assertThat(keyConfig.getDependentFiles(), containsInAnyOrder( + getDataPath("/certs/" + certName + "/" + certName + ".crt"), getDataPath("/certs/" + certName + "/" + certName + ".key"))); + assertThat(keyConfig.createKeyManager(), notNullValue()); + } + + public void testLoadKeysFromPKCS12() { + final Settings.Builder builder = Settings.builder() + .put("test.ssl.keystore.path", "cert-all/certs.p12"); + if (randomBoolean()) { + builder.put("test.ssl.keystore.password", "p12-pass"); + } else { + secureSettings.setString("test.ssl.keystore.secure_password", "p12-pass"); + } + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.keystore.type", "PKCS12"); + } + if (randomBoolean()) { + builder.put("test.ssl.keystore.algorithm", KeyManagerFactory.getDefaultAlgorithm()); + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslKeyConfig keyConfig = configuration.getKeyConfig(); + assertThat(keyConfig, instanceOf(StoreKeyConfig.class)); + assertThat(keyConfig.getDependentFiles(), containsInAnyOrder(getDataPath("/certs/cert-all/certs.p12"))); + assertThat(keyConfig.createKeyManager(), notNullValue()); + } + + public void testLoadKeysFromJKS() { + final Settings.Builder builder = Settings.builder() + .put("test.ssl.keystore.path", "cert-all/certs.jks"); + if (randomBoolean()) { + builder.put("test.ssl.keystore.password", "jks-pass"); + } else { + secureSettings.setString("test.ssl.keystore.secure_password", "jks-pass"); + } + if (randomBoolean()) { + builder.put("test.ssl.keystore.key_password", "key-pass"); + } else { + secureSettings.setString("test.ssl.keystore.secure_key_password", "key-pass"); + } + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.keystore.type", "jks"); + } + if (randomBoolean()) { + builder.put("test.ssl.keystore.algorithm", KeyManagerFactory.getDefaultAlgorithm()); + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslKeyConfig keyConfig = configuration.getKeyConfig(); + assertThat(keyConfig, instanceOf(StoreKeyConfig.class)); + assertThat(keyConfig.getDependentFiles(), containsInAnyOrder(getDataPath("/certs/cert-all/certs.jks"))); + assertThat(keyConfig.createKeyManager(), notNullValue()); + } +} diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationTests.java new file mode 100644 index 0000000000000..b8986462ebe47 --- /dev/null +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationTests.java @@ -0,0 +1,140 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.EqualsHashCodeTestUtils; +import org.hamcrest.Matchers; +import org.mockito.Mockito; + +import javax.net.ssl.SSLContext; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.elasticsearch.common.ssl.SslConfigurationLoader.DEFAULT_CIPHERS; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +public class SslConfigurationTests extends ESTestCase { + + static final String[] VALID_PROTOCOLS = { "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3", "SSLv2Hello", "SSLv2" }; + + public void testBasicConstruction() { + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + Mockito.when(trustConfig.toString()).thenReturn("TEST-TRUST"); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + Mockito.when(keyConfig.toString()).thenReturn("TEST-KEY"); + final SslVerificationMode verificationMode = randomFrom(SslVerificationMode.values()); + final SslClientAuthenticationMode clientAuth = randomFrom(SslClientAuthenticationMode.values()); + final List ciphers = randomSubsetOf(randomIntBetween(1, DEFAULT_CIPHERS.size()), DEFAULT_CIPHERS); + final List protocols = randomSubsetOf(randomIntBetween(1, 4), VALID_PROTOCOLS); + final SslConfiguration configuration = + new SslConfiguration(trustConfig, keyConfig, verificationMode, clientAuth, ciphers, protocols); + + assertThat(configuration.getTrustConfig(), is(trustConfig)); + assertThat(configuration.getKeyConfig(), is(keyConfig)); + assertThat(configuration.getVerificationMode(), is(verificationMode)); + assertThat(configuration.getClientAuth(), is(clientAuth)); + assertThat(configuration.getCipherSuites(), is(ciphers)); + assertThat(configuration.getSupportedProtocols(), is(protocols)); + + assertThat(configuration.toString(), containsString("TEST-TRUST")); + assertThat(configuration.toString(), containsString("TEST-KEY")); + assertThat(configuration.toString(), containsString(verificationMode.toString())); + assertThat(configuration.toString(), containsString(clientAuth.toString())); + assertThat(configuration.toString(), containsString(randomFrom(ciphers))); + assertThat(configuration.toString(), containsString(randomFrom(protocols))); + } + + public void testEqualsAndHashCode() { + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + final SslVerificationMode verificationMode = randomFrom(SslVerificationMode.values()); + final SslClientAuthenticationMode clientAuth = randomFrom(SslClientAuthenticationMode.values()); + final List ciphers = randomSubsetOf(randomIntBetween(1, DEFAULT_CIPHERS.size() - 1), DEFAULT_CIPHERS); + final List protocols = randomSubsetOf(randomIntBetween(1, VALID_PROTOCOLS.length - 1), VALID_PROTOCOLS); + final SslConfiguration configuration = + new SslConfiguration(trustConfig, keyConfig, verificationMode, clientAuth, ciphers, protocols); + + EqualsHashCodeTestUtils.checkEqualsAndHashCode(configuration, + orig -> new SslConfiguration(orig.getTrustConfig(), orig.getKeyConfig(), orig.getVerificationMode(), orig.getClientAuth(), + orig.getCipherSuites(), orig.getSupportedProtocols()), + orig -> { + switch (randomIntBetween(1, 4)) { + case 1: + return new SslConfiguration(orig.getTrustConfig(), orig.getKeyConfig(), + randomValueOtherThan(orig.getVerificationMode(), () -> randomFrom(SslVerificationMode.values())), + orig.getClientAuth(), orig.getCipherSuites(), orig.getSupportedProtocols()); + case 2: + return new SslConfiguration(orig.getTrustConfig(), orig.getKeyConfig(), orig.getVerificationMode(), + randomValueOtherThan(orig.getClientAuth(), () -> randomFrom(SslClientAuthenticationMode.values())), + orig.getCipherSuites(), orig.getSupportedProtocols()); + case 3: + return new SslConfiguration(orig.getTrustConfig(), orig.getKeyConfig(), + orig.getVerificationMode(), orig.getClientAuth(), DEFAULT_CIPHERS, orig.getSupportedProtocols()); + case 4: + default: + return new SslConfiguration(orig.getTrustConfig(), orig.getKeyConfig(), orig.getVerificationMode(), + orig.getClientAuth(), orig.getCipherSuites(), Arrays.asList(VALID_PROTOCOLS)); + } + }); + } + + public void testDependentFiles() { + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + final SslConfiguration configuration = new SslConfiguration(trustConfig, keyConfig, + randomFrom(SslVerificationMode.values()), randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, SslConfigurationLoader.DEFAULT_PROTOCOLS); + + final Path dir = createTempDir(); + final Path file1 = dir.resolve(randomAlphaOfLength(1) + ".pem"); + final Path file2 = dir.resolve(randomAlphaOfLength(2) + ".pem"); + final Path file3 = dir.resolve(randomAlphaOfLength(3) + ".pem"); + final Path file4 = dir.resolve(randomAlphaOfLength(4) + ".pem"); + final Path file5 = dir.resolve(randomAlphaOfLength(5) + ".pem"); + + Mockito.when(trustConfig.getDependentFiles()).thenReturn(Arrays.asList(file1, file2)); + Mockito.when(keyConfig.getDependentFiles()).thenReturn(Arrays.asList(file3, file4, file5)); + assertThat(configuration.getDependentFiles(), Matchers.containsInAnyOrder(file1, file2, file3, file4, file5)); + } + + public void testBuildSslContext() { + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + final String protocol = randomFrom(SslConfigurationLoader.DEFAULT_PROTOCOLS); + final SslConfiguration configuration = new SslConfiguration(trustConfig, keyConfig, + randomFrom(SslVerificationMode.values()), randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, Collections.singletonList(protocol)); + + Mockito.when(trustConfig.createTrustManager()).thenReturn(null); + Mockito.when(keyConfig.createKeyManager()).thenReturn(null); + final SSLContext sslContext = configuration.createSslContext(); + assertThat(sslContext.getProtocol(), equalTo(protocol)); + + Mockito.verify(trustConfig).createTrustManager(); + Mockito.verify(keyConfig).createKeyManager(); + Mockito.verifyNoMoreInteractions(trustConfig, keyConfig); + } + +} diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java new file mode 100644 index 0000000000000..00e15443b6a42 --- /dev/null +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java @@ -0,0 +1,217 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.test.ESTestCase; +import org.hamcrest.Matchers; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.iterableWithSize; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +public class StoreKeyConfigTests extends ESTestCase { + + private static final int IP_NAME = 7; + private static final int DNS_NAME = 2; + + private static final char[] P12_PASS = "p12-pass".toCharArray(); + private static final char[] JKS_PASS = "jks-pass".toCharArray(); + + public void testLoadSingleKeyPKCS12() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path p12 = getDataPath("/certs/cert1/cert1.p12"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); + assertKeysLoaded(keyConfig, "cert1"); + } + + public void testLoadMultipleKeyPKCS12() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path p12 = getDataPath("/certs/cert-all/certs.p12"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); + assertKeysLoaded(keyConfig, "cert1", "cert2"); + } + + public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path jks = getDataPath("/certs/cert-all/certs.jks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, JKS_PASS, "jks", "key-pass".toCharArray(), + KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(jks)); + assertKeysLoaded(keyConfig, "cert1", "cert2"); + } + + public void testKeyManagerFailsWithIncorrectStorePassword() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path jks = getDataPath("/certs/cert-all/certs.jks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, P12_PASS, "jks", "key-pass".toCharArray(), + KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(jks)); + assertPasswordIsIncorrect(keyConfig, jks); + } + + public void testKeyManagerFailsWithIncorrectKeyPassword() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path jks = getDataPath("/certs/cert-all/certs.jks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, JKS_PASS, "jks", JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(jks)); + assertPasswordIsIncorrect(keyConfig, jks); + } + + public void testKeyManagerFailsWithMissingKeystoreFile() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path path = getDataPath("/certs/cert-all/certs.jks").getParent().resolve("dne.jks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(path, JKS_PASS, "jks", JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(path)); + assertFileNotFound(keyConfig, path); + } + + public void testMissingKeyEntriesFailsWithMeaningfulMessage() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path ks; + final char[] password; + final String type; + if (randomBoolean()) { + type = "PKCS12"; + ks = getDataPath("/certs/ca-all/ca.p12"); + password = P12_PASS; + } else { + type = "jks"; + ks = getDataPath("/certs/ca-all/ca.jks"); + password = JKS_PASS; + } + final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, type, password, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertNoPrivateKeyEntries(keyConfig, ks); + } + + public void testKeyConfigReloadsFileContents() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path cert1 = getDataPath("/certs/cert1/cert1.p12"); + final Path cert2 = getDataPath("/certs/cert2/cert2.p12"); + final Path jks = getDataPath("/certs/cert-all/certs.jks"); + + final Path p12 = createTempFile("cert", ".p12"); + + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + + Files.copy(cert1, p12, StandardCopyOption.REPLACE_EXISTING); + assertKeysLoaded(keyConfig, "cert1"); + assertKeysNotLoaded(keyConfig, "cert2"); + + Files.copy(jks, p12, StandardCopyOption.REPLACE_EXISTING); + // Because (a) cannot load a JKS as a PKCS12 & (b) the password is wrong. + assertBadKeyStore(keyConfig, p12); + + Files.copy(cert2, p12, StandardCopyOption.REPLACE_EXISTING); + assertKeysLoaded(keyConfig, "cert2"); + assertKeysNotLoaded(keyConfig, "cert1"); + + Files.delete(p12); + assertFileNotFound(keyConfig, p12); + } + + private void assertKeysLoaded(StoreKeyConfig keyConfig, String... names) throws CertificateParsingException { + final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); + assertThat(keyManager, notNullValue()); + + for (String name : names) { + final PrivateKey privateKey = keyManager.getPrivateKey(name); + assertThat(privateKey, notNullValue()); + assertThat(privateKey.getAlgorithm(), is("RSA")); + + final X509Certificate[] chain = keyManager.getCertificateChain(name); + assertThat(chain, notNullValue()); + assertThat(chain, arrayWithSize(1)); + final X509Certificate certificate = chain[0]; + assertThat(certificate.getIssuerDN().getName(), is("CN=Test CA 1")); + assertThat(certificate.getSubjectDN().getName(), is("CN=" + name)); + assertThat(certificate.getSubjectAlternativeNames(), iterableWithSize(2)); + assertThat(certificate.getSubjectAlternativeNames(), containsInAnyOrder( + Arrays.asList(DNS_NAME, "localhost"), + Arrays.asList(IP_NAME, "127.0.0.1") + )); + } + } + + private void assertKeysNotLoaded(StoreKeyConfig keyConfig, String... names) throws CertificateParsingException { + final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); + assertThat(keyManager, notNullValue()); + + for (String name : names) { + final PrivateKey privateKey = keyManager.getPrivateKey(name); + assertThat(privateKey, nullValue()); + } + } + + private void assertPasswordIsIncorrect(StoreKeyConfig keyConfig, Path key) { + final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); + assertThat(exception.getMessage(), containsString("keystore")); + assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); + if (exception.getCause() instanceof GeneralSecurityException) { + assertThat(exception.getMessage(), containsString("password")); + } else { + assertThat(exception.getCause(), instanceOf(IOException.class)); + assertThat(exception.getCause().getMessage(), containsString("password")); + } + } + + private void assertBadKeyStore(StoreKeyConfig keyConfig, Path key) { + final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); + assertThat(exception.getMessage(), containsString("keystore")); + assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); + assertThat(exception.getCause(), instanceOf(IOException.class)); + assertThat(exception.getCause().getMessage(), containsString("tampered")); + } + + private void assertFileNotFound(StoreKeyConfig keyConfig, Path file) { + final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); + assertThat(exception.getMessage(), containsString("keystore")); + assertThat(exception.getMessage(), containsString(file.toAbsolutePath().toString())); + assertThat(exception.getMessage(), containsString("does not exist")); + assertThat(exception.getCause(), instanceOf(NoSuchFileException.class)); + } + + private void assertNoPrivateKeyEntries(StoreKeyConfig keyConfig, Path file) { + final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); + assertThat(exception.getMessage(), containsString("keystore")); + assertThat(exception.getMessage(), containsString(file.toAbsolutePath().toString())); + assertThat(exception.getMessage(), containsString("does not contain a private key entry")); + } +} diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreTrustConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreTrustConfigTests.java new file mode 100644 index 0000000000000..355e5cd6c8c79 --- /dev/null +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreTrustConfigTests.java @@ -0,0 +1,169 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.test.ESTestCase; +import org.hamcrest.Matchers; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.containsString; + +public class StoreTrustConfigTests extends ESTestCase { + + private static final char[] P12_PASS = "p12-pass".toCharArray(); + private static final char[] JKS_PASS = "jks-pass".toCharArray(); + private static final String DEFAULT_ALGORITHM = TrustManagerFactory.getDefaultAlgorithm(); + + public void testBuildTrustConfigFromPKCS12() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path ks = getDataPath("/certs/ca1/ca.p12"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, "PKCS12", DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertCertificateChain(trustConfig, "CN=Test CA 1"); + } + + public void testBuildTrustConfigFromJKS() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path ks = getDataPath("/certs/ca-all/ca.jks"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, "jks", DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2", "CN=Test CA 3"); + } + + public void testBadKeyStoreFormatFails() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path ks = createTempFile("ca", ".p12"); + Files.write(ks, randomByteArrayOfLength(128), StandardOpenOption.APPEND); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], randomFrom("PKCS12", "jks"), DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertInvalidFileFormat(trustConfig, ks); + } + + public void testMissingKeyStoreFailsWithMeaningfulMessage() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path ks = getDataPath("/certs/ca-all/ca.p12").getParent().resolve("keystore.dne"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], randomFrom("PKCS12", "jks"), DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertFileNotFound(trustConfig, ks); + } + + public void testIncorrectPasswordFailsWithMeaningfulMessage() throws Exception { + final Path ks = getDataPath("/certs/ca1/ca.p12"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], "PKCS12", DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertPasswordIsIncorrect(trustConfig, ks); + } + + public void testMissingTrustEntriesFailsWithMeaningfulMessage() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path ks; + final char[] password; + final String type; + if (randomBoolean()) { + type = "PKCS12"; + ks = getDataPath("/certs/cert-all/certs.p12"); + password = P12_PASS; + } else { + type = "jks"; + ks = getDataPath("/certs/cert-all/certs.jks"); + password = JKS_PASS; + } + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, password, type, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertNoCertificateEntries(trustConfig, ks); + } + + public void testTrustConfigReloadsKeysStoreContents() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path ks1 = getDataPath("/certs/ca1/ca.p12"); + final Path ksAll = getDataPath("/certs/ca-all/ca.p12"); + + final Path ks = createTempFile("ca", "p12"); + + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, "PKCS12", DEFAULT_ALGORITHM); + + Files.copy(ks1, ks, StandardCopyOption.REPLACE_EXISTING); + assertCertificateChain(trustConfig, "CN=Test CA 1"); + + Files.delete(ks); + assertFileNotFound(trustConfig, ks); + + Files.write(ks, randomByteArrayOfLength(128), StandardOpenOption.CREATE); + assertInvalidFileFormat(trustConfig, ks); + + Files.copy(ksAll, ks, StandardCopyOption.REPLACE_EXISTING); + assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2", "CN=Test CA 3"); + } + + private void assertCertificateChain(StoreTrustConfig trustConfig, String... caNames) { + final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); + final X509Certificate[] issuers = trustManager.getAcceptedIssuers(); + final Set issuerNames = Stream.of(issuers) + .map(X509Certificate::getSubjectDN) + .map(Principal::getName) + .collect(Collectors.toSet()); + + assertThat(issuerNames, Matchers.containsInAnyOrder(caNames)); + } + + private void assertInvalidFileFormat(StoreTrustConfig trustConfig, Path file) { + final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), Matchers.containsString("cannot read")); + assertThat(exception.getMessage(), Matchers.containsString("keystore")); + assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); + assertThat(exception.getCause(), Matchers.instanceOf(IOException.class)); + } + + private void assertFileNotFound(StoreTrustConfig trustConfig, Path file) { + final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), Matchers.containsString("file does not exist")); + assertThat(exception.getMessage(), Matchers.containsString("keystore")); + assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); + assertThat(exception.getCause(), Matchers.instanceOf(NoSuchFileException.class)); + } + + private void assertPasswordIsIncorrect(StoreTrustConfig trustConfig, Path key) { + final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), containsString("keystore")); + assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); + assertThat(exception.getMessage(), containsString("password")); + } + + private void assertNoCertificateEntries(StoreTrustConfig trustConfig, Path file) { + final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), Matchers.containsString("does not contain any trusted certificate entries")); + assertThat(exception.getMessage(), Matchers.containsString("truststore")); + assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); + } + +} diff --git a/libs/ssl-config/src/test/resources/certs/README.txt b/libs/ssl-config/src/test/resources/certs/README.txt new file mode 100644 index 0000000000000..c225bbbf3400c --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/README.txt @@ -0,0 +1,69 @@ +1. Create first CA PEM ("ca1") + +elasticsearch-certutil ca --pem --out ca1.zip --days 9999 --ca-dn "CN=Test CA 1" +unzip ca1.zip +mv ca ca1 + +2. Create first CA PEM ("ca2") + +elasticsearch-certutil ca --pem --out ca2.zip --days 9999 --ca-dn "CN=Test CA 2" +unzip ca2.zip +mv ca ca2 + +3. Create first CA PEM ("ca3") + +elasticsearch-certutil ca --pem --out ca3.zip --days 9999 --ca-dn "CN=Test CA 3" +unzip ca3.zip +mv ca ca3 + +4. Create "cert1" PEM + +elasticsearch-certutil cert --pem --out cert1.zip --name cert1 --ip 127.0.0.1 --dns localhost --days 9999 --ca-key ca1/ca.key --ca-cert ca1/ca.crt +unzip cert1.zip + +5. Create "cert2" PEM (same as cert1, but with a password) + +elasticsearch-certutil cert --pem --out cert2.zip --name cert2 --ip 127.0.0.1 --dns localhost --days 9999 --ca-key ca1/ca.key --ca-cert ca1/ca.crt --pass "c2-pass" +unzip cert2.zip + +6. Convert CAs to PKCS#12 + +for n in 1 2 3 +do + keytool -importcert -file ca${n}/ca.crt -alias ca -keystore ca${n}/ca.p12 -storetype PKCS12 -storepass p12-pass -v + keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.p12 -storetype PKCS12 -storepass p12-pass -v +done + +7. Convert CAs to JKS + +for n in 1 2 3 +do + keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.jks -storetype jks -storepass jks-pass -v +done + +8. Convert Certs to PKCS#12 + +for Cert in cert1 cert2 +do + openssl pkcs12 -export -out $Cert/$Cert.p12 -inkey $Cert/$Cert.key -in $Cert/$Cert.crt -name $Cert -passout pass:p12-pass +done + +9. Import Certs into single PKCS#12 keystore + +for Cert in cert1 cert2 +do + keytool -importkeystore -noprompt \ + -srckeystore $Cert/$Cert.p12 -srcstoretype PKCS12 -srcstorepass p12-pass \ + -destkeystore cert-all/certs.p12 -deststoretype PKCS12 -deststorepass p12-pass +done + +10. Import Certs into single JKS keystore with separate key-password + +for Cert in cert1 cert2 +do + keytool -importkeystore -noprompt \ + -srckeystore $Cert/$Cert.p12 -srcstoretype PKCS12 -srcstorepass p12-pass \ + -destkeystore cert-all/certs.jks -deststoretype jks -deststorepass jks-pass + keytool -keypasswd -keystore cert-all/certs.jks -alias $Cert -keypass p12-pass -new key-pass -storepass jks-pass +done + diff --git a/libs/ssl-config/src/test/resources/certs/ca-all/ca.jks b/libs/ssl-config/src/test/resources/certs/ca-all/ca.jks new file mode 100644 index 0000000000000000000000000000000000000000..0c00ff13d906ff3cb4e584dea41e702d31eab693 GIT binary patch literal 2460 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 UbeXLd#TYiI>bWM60s{etpl{Y(Qvd(} literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/ca1/ca.crt b/libs/ssl-config/src/test/resources/certs/ca1/ca.crt new file mode 100644 index 0000000000000..08e351d886c2f --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/ca1/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUZ0xcthORO/ye5P1Ia/IarOGvwHYwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAxMJVGVzdCBDQSAxMB4XDTE5MDEwMzA3MzgyNloXDTQ2MDUy +MDA3MzgyNlowFDESMBAGA1UEAxMJVGVzdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArrEcyCaTpx0JCZdNAhb/nGROBRNPl2QdKuFM1pLRMoKl +1XAMYRy88B1jSuteP18O+pk83F6jV7byYyp8f616nTHqoFfzl4rN/iM02b63/oQd +qSSn4oPyiPfsS4I49taSJnH+8slbNg9iyiwoFnywcVaj1X9t+DAQXDFxNczpuIiq +Q8apxoORiJz4U/sC9dNOV4y8DnB0Vi6Ypb3npMvt/H3mvHC6tRuibVNSh2oBSa3o +gA1+ovxmGXavxfX2uquE+R4umgTr3HiHBviFvw2o1EPc9wHbR0iuyUtym3REIFko +VmdzIanZEtdk3HyHa7wggP9zMWfETVZuurJ64VhL0wIDAQABo1MwUTAdBgNVHQ4E +FgQUh3fjY8KpBOoVTBJ5bcenE/g9dZcwHwYDVR0jBBgwFoAUh3fjY8KpBOoVTBJ5 +bcenE/g9dZcwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAWTDz +r1a7K41KefdsjMM75z6Vxm5nXeGDItmUWaerVVUmRkeln+bbY0uReoHELuTA76uR +TMt9fOAmXpmfhbssRKv9TffOg5nb5IAvjDDRkCIXCJvBHcNLuYsTkjC7beuQEfSg +3ayoRWfaF4EliTk96pEsnGNz0szWkhx/oWvZ8pvY/HA80xxiAbIFDgh4MJhwiCeh +1PwbvUx+i6VYfM/6eIGk1WIY5wJR56Dj8afVsOED+hn2Rs5oWFXY0Eu6XNHJfAFg +RyaTrL3+X9SK08yUJwQMFnW/k4IUKWi3JyWb3PwGOqIjUXIfNH/WkwMZSErkyNln +mHWm8kQbx9OLpi5BlQ== +-----END CERTIFICATE----- diff --git a/libs/ssl-config/src/test/resources/certs/ca1/ca.key b/libs/ssl-config/src/test/resources/certs/ca1/ca.key new file mode 100644 index 0000000000000..990ad2d005d34 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/ca1/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEArrEcyCaTpx0JCZdNAhb/nGROBRNPl2QdKuFM1pLRMoKl1XAM +YRy88B1jSuteP18O+pk83F6jV7byYyp8f616nTHqoFfzl4rN/iM02b63/oQdqSSn +4oPyiPfsS4I49taSJnH+8slbNg9iyiwoFnywcVaj1X9t+DAQXDFxNczpuIiqQ8ap +xoORiJz4U/sC9dNOV4y8DnB0Vi6Ypb3npMvt/H3mvHC6tRuibVNSh2oBSa3ogA1+ +ovxmGXavxfX2uquE+R4umgTr3HiHBviFvw2o1EPc9wHbR0iuyUtym3REIFkoVmdz +IanZEtdk3HyHa7wggP9zMWfETVZuurJ64VhL0wIDAQABAoIBAG6T1fAr2xLRIkNb +7ncAL9TC+U/lJWBjEsNt0cGRNbKPWIF+Z5ehJUeoko195y6d8VFXZlrn3OVM/Kkg +36XCHfca/bV5dsvaJQJVLsMWIkmNP2kttsd/Viq1JHG3gG9e6yxCxGrSYlYZ7yKi +SM3TJ6zWduZRvz52ziRNd6fiiZ8wc/wwWMXvXoMJ39EhGdZKKTowvKU59oV4ieg8 +vEK1V2ec2D6RN6m0CHzm4erKZSIJoMJAhKV1D42+z73XhJuv/gbIlUlXrG1l4XW9 +Sd59dU9v4NGCv6BpqQqC//fVwW7KfTGwmMtL7AP5vria/dzf9EyZxPk+HLgC6bHK +Y3TU2WECgYEA9x0A/aB/4CepYAGUxIlc8r+ylhYTWmUafjvBJ8fPQs2PQi5JgE2s +HE4yRcnPIZPaDMYDUPluXoSkirX5jYqQAVeOhTut8tTwV1FMf57P470FPVMgV5X6 +axsiMBIcRvYAc1cq7n7k45Ix8YNKfp5WjG1r7XLR9Xa5Q0Bno9WxII8CgYEAtPlg +NuJnSnrka9jZQQTvzp6ULP448MWdjHmYmj93lUIC9XL3hkPqu+cZjZT5C2xf/36w +5wEAHSNVO8SUjJ1bKgjyfyvaxosonbrDv3TWl1Ib8NFmumXjMzCw6LfDmrJbZc8X +VA7VG1HClgPmojDc61s/F7unRbRUcIowP1dVOn0CgYA/u/xQbf/tSW129JFxK1iM +x4KBEUqGiwMNQc4su20qdqgXUqbkb6QPXN+8fjNtHpwjpUKftOWRfTaPDCZEKlO/ +9NwuYtkXg3JFoxNO6yAFRfA/A9yYmncO/t2PdmxSpQoytW2+O34/b6pv9wPUqnP6 +HhKzGGUsoSVhQhA5All/4wKBgA9Nv0sk3iM4PTS5g7Wx2y2Xz2P2o44IyAfnCHaS +w2QFzwY+kJv0BleZdVm5rU2//mY2qnL+bKoKIN0LBJzXeawWUZtbdAayId8kugTo +tnTZZq94pb1BfHMJvQwQ7iOYzY3Qc2KSVocW5OOWtNwmUag9cRpqrfyBAVr69JWG +pxhpAoGAUYkzE88ay83byRR+I/bVLDyI/OLs9mSfPJ1BbntDnmzv/ReUJCr9cZvo +18/iQyhICA1IO+V0JgQPRY7cz44cBTb9QiQJvtRtFyyKLBC9roYZ7Y8CfTAcX73H +7yY5UdS82r/Quu2Kp9EUbrTqmev7h8k5/kjXkvIdLv7soLMGJh8= +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/ca1/ca.p12 b/libs/ssl-config/src/test/resources/certs/ca1/ca.p12 new file mode 100644 index 0000000000000000000000000000000000000000..0fd23865b718dbe636d5858e2df48de3de29f2c3 GIT binary patch literal 1066 zcmV+_1l9X6f&?Z40Ru3C1K$P-Duzgg_YDCD0ic2d&;)`5%rJrj$S{Hf#0Ci}hDe6@ z4FLxRpn?OnFoFZI0s#Opf&;1s2`Yw2hW8Bt2LUiC1_~;MNQUt@QkvZlv?-zL zNggUd=Re|zDkEVabP2V;Msf?oB-y<7Pc8o4(pOWQkuf5{aShe6GSeDmnhlWDp%)i9c!&h|PADoo3D|nb$bXX%5aLOfl9dcVRPg@AL|JoO_`? zsF>Wvv`v!}A~}dC)8s3}9PY21*vSc8!_dGj_OV_gvz5d$5&Md+S}R}CF#bJg>YAkB z)?S0?;8Yn?7N3SsUzb2Dc#vD3><*6{D8EnvmvN;igvOk(Iso(Kp`+oklQg5*~KJ6{d1g`NOctRsv^P zlR}xx1xE4XF&-TcUJ9SzyK!ZHk-hI!Fh0iC4??b^Mw$Cb^&dwt$69iBB(nbCf^JQ{ z5VkWz=MyE8_9n{glUkbl7VEZEENUhJ2n7;W?lE*S`<#9>#0I=-%Q>ylRlI(S7ocXb7O_Yk z?k68~slnmrnK69u)Z_&TM)xe0o)9vi54QjzUKC{Tam}{N3`Xi2D$)erTvRrw-Az$a z-ExCr6RtSEx#aOM$I8hOwz3Vr(|aHyUq$|V1~c*!%(P7NZjli5 z)I%;!I9Ms$9?Z=`WY~x;9kN8$#9ub^h4nFUW_b2PQ6RrXAMp+CFZ%;mC5`=?ROpAk zD3pM@qrbiSRpTgzhF2)HlI)`cj>l4_qU_Fv#`%M4_ltg2PZNtvH+zN*I!li9H1f|* zldfBjrXQ`TAp#!tr{Uqmb(H_ON1`Yb%yIvnt$2N_V%il!&nNc)59BqrS1do>`|#nq zsUMlE=I$En*Eh>$>U?A8#8G!TGeDetUe25zz=FNe$#hpu-`DG|H{5i)PHKn4y#-^& zq=c!UdjwswgS2^G@$x^w0$M?Bc`!aOAutIB1uG5%0vZJX1QadxGm=y)=j6x{eY~3m ked4onjVlBc>;V73IoP;Q@`&Z{sh*?z8@$}-9Zc8*3>YK0_UA8AEsf13{} z8Y%i;0zW}_8xrfYu$sXepN}%OcIvsI)Vgf7Lr3Lsb$~ZgtxbpDdax<6RlTC0|hGj_IO zAWC+x3`xzH-RSb10=2b&e%7lIA2@pceQe%3!c{Yi(`MBmvd&iPxaZEy|C*;q{KRVclj*>aG$>Stbk#p-02Op)DTlQh)h?U4 z ztNa0U!Y-BK)+IUM>)m|E)6e}k0OD_09@Q&xve{38er z+p^0XgF5*S=r^c@~w>{u{tjkhW5`a$}X{; zcZZ<&P-dDrWas%FzVN|qB^f$NWEHEVTD+n8miuE}fHt0%%UV?FD_2lYQzN3f5&Y$T zDwO|J#RAY{bdiY)9!@Y&59ZP~^{acj*Iu!Y(e!G^mW?;!s~FC;Y(An>Bf~5Ik(tS2Zd3}M0_d16)%g|Tm2NsG1w26 kQK^!Ii8llkx=mLVA{>|L9`%vqgfb~E0s{etpw}7kAOHXW literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/ca3/ca.crt b/libs/ssl-config/src/test/resources/certs/ca3/ca.crt new file mode 100644 index 0000000000000..3f408375378d4 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/ca3/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCjCCAfKgAwIBAgIVAJMN07EKmD5RR6PmEZDjqSWBpRY9MA0GCSqGSIb3DQEB +CwUAMBQxEjAQBgNVBAMTCVRlc3QgQ0EgMzAeFw0xOTAxMDMwNzQwMTlaFw00NjA1 +MjAwNzQwMTlaMBQxEjAQBgNVBAMTCVRlc3QgQ0EgMzCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAJEPRYQiGOmfaHdOiCWPqzWgmHgbdw32yaQFKUOqqAQn +OhkujPg2jJdqMjp7aFrZjLjVi2WyHdAPe3ibuUHT6PyTmcgtDxe+xAUJCG6vJKpy +vrDNXVq9rotts+gEKeRbhr0lw2lppDhNNGiKkRm7R2sNRTyIW0vBNujicSWcli2H +hQDjFWornjcd8OBFLvhY3tSwic8uwuSAYgrZfWHOgBWSC1xqUFHa6561K264yeNZ +8cyb41euYAsohdQ/VPPFu3ISEYlZwTwwp/e2l8IpgKpB59Mrdc1TonpD5h3XMiW8 +iPJE0eZ9KJSv1SzEty2nno4s7LAu6cvwXQD2RP2vY4cCAwEAAaNTMFEwHQYDVR0O +BBYEFAQZfDT971m21ReFP+rO5pTFrLLIMB8GA1UdIwQYMBaAFAQZfDT971m21ReF +P+rO5pTFrLLIMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEHK +JublAXM3FprLQRU+MAIQyZp4VpSanxL/JE6UkGv3P0aQUQZt7BR+uv7xWc1Ygrk9 +gOK7pZh9vXZSUc6NFEa05O9lXUvYy9UN+iFxlWPe3niPHt8lW/6oUqMfnE2ePIG6 +Iaoaks29qwcbKYnl4ZcurdbcCfCoc7GAQYE1zKO4fJqnRogFaoPEwHJ5+5nmd4Xl +8TP/v4ISHzB/cyCIiA9O8EssRXLBxwd7zgY0kicBKgy3/Rtd/HfGLunZdBBun6Hk +T5L+SVLGLIoFFahA7NQUQPKzrx8KMgh6SiskWblc8pk4/Q1Z2q9E1L69z4SDl1oq +HDYgZBcLjn4QXONgz8Y= +-----END CERTIFICATE----- diff --git a/libs/ssl-config/src/test/resources/certs/ca3/ca.key b/libs/ssl-config/src/test/resources/certs/ca3/ca.key new file mode 100644 index 0000000000000..3626d71ba2032 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/ca3/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAkQ9FhCIY6Z9od06IJY+rNaCYeBt3DfbJpAUpQ6qoBCc6GS6M ++DaMl2oyOntoWtmMuNWLZbId0A97eJu5QdPo/JOZyC0PF77EBQkIbq8kqnK+sM1d +Wr2ui22z6AQp5FuGvSXDaWmkOE00aIqRGbtHaw1FPIhbS8E26OJxJZyWLYeFAOMV +aiueNx3w4EUu+Fje1LCJzy7C5IBiCtl9Yc6AFZILXGpQUdrrnrUrbrjJ41nxzJvj +V65gCyiF1D9U88W7chIRiVnBPDCn97aXwimAqkHn0yt1zVOiekPmHdcyJbyI8kTR +5n0olK/VLMS3LaeejizssC7py/BdAPZE/a9jhwIDAQABAoIBADld7sIIsg2Ca0/z +kMg5/x2gO2wUgIrXNHtXRzBphzTNRp662Ck5eXRQHTkfoO985bgbS5uWS1ADL3NN +MoCkC5oHzWNq3nMnkGHlZp5PSZLW+i71qJvANA0T/3gcXWzf/XNEQfmoO7fAYJ+P +XT7t35qojt8XlfNpoAuNse2L9aBfQ8knEP7Aym/G3ko9mD1dvm2YKYYkzpLKYOoE +5MpCNCAcdn/va3bqEEgzq8ogwO9gNwerLnXCbId6xpInwfoTE6I15UySmeDQg+8P +nDiVWd+/xCEjFVBNwxDG1BjG0j2AlUZoDFuBeRmn48SjgRTc02PRdGKdC0Mys+1o +ZMIEpqECgYEA39/9vfGAQpz4ssLFL+AqKKRmgJwt/ZRkHtUK5w+c3dZBi1aRhWMY +5mrQt6USSm5YovJR6bxvjlD/nVwnENprLYdlrwAIEbT9jpVFtGHZqQumrH3JmGtu +Uz3mjZuHXblgoGUfAL6X20qK891iJoQ5VUtWYrP0ndd1f8CyE7/qzWkCgYEApeAD +aCBUMFBtmmXXrrQWBTu9tTgn26jMmFk/p5NBeSKGs2GCUB1sRgPUc35NCdtIetjs +frQlsEOKTD2PAnnTKL2ZFuk/mxFwQIXJVjAnhMICm92J8UL4GjWH1EuaLRQqks57 +n52dd9mDZFFE1lAfAihq9BEdLfwgFKlgoyl8W28CgYAzmwt/tGKveEWv10vjDFZL +hhIGxXmogYNOxCc+OhAb5t63At6Kk9xSiP7RxmBf/e26qgcNzR0d/jfeCzcKIH8i +QJrE60nw4vqr2mb1/LRSzle+XUSSOPl2gMdbjyV2ClxmvMiXwFd6+kTrj/WnEUWy +Dqq8F+VkWR1BtKaX/N5gOQKBgHsBNqWFq8i0K8LeGOYFx3qUBacYAH6kmyuyq0CC +M4A3uTnWakMsvnjhKC+JDmnrwcDPkfiXcIdYXnsQ/zbvzkWc66SQzUkZ0msWiuou +BXAuSq74xu0xIziUT6h/c9JP7Q42rnf78qTImOXQWkKu4X/BJybcdg3+tG999xqn +jf9jAoGAG4hzKaKqvqKOfDGr3Eebu2/uS73xtUTxiUheqIqoTJHO4jjWOcjKI1my +91pnfC/t3fGLB2ObqDTw+H3jRf7gqIkMbUAswjpMX3GpOm2+eI5U9B8y5dV5PoUt +RV32fzc/xu9Ib2i3Q29vuHrZczb4lxpi8UVWCLcrPuvC2lM8QTY= +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/ca3/ca.p12 b/libs/ssl-config/src/test/resources/certs/ca3/ca.p12 new file mode 100644 index 0000000000000000000000000000000000000000..1331cec05d77efa8874e1488cdd01ff1d86fc940 GIT binary patch literal 1066 zcmV+_1l9X6f&?Z40Ru3C1K$P-Duzgg_YDCD0ic2d&;)`5%rJrj$S{Hf#0Ci}hDe6@ z4FLxRpn?OnFoFZI0s#Opf&;1s2`Yw2hW8Bt2LUiC1_~;MNQU0?F}#D_;xm*aa6sc0s{cUP=JC1aJD5(nerH+Wxf+5Jk;DOZ#buyM*_;P(}c9T z>PYG!7k*@G(StzDGwDGN<2a1!jLVC_p!+=s>ub9P15zr*-ZU zj`PaouF>j$iL?*Z^%g&?FL2wVG=I!hof?U*O(;IFD+}1ic+?kCmus*Fzg!4>U53*J z!E6?v0@UI48njrBewaRg(4ky3+3_9rHiVP1v}ga83x>cQ&XWX`E7qImcD;>};!Xq6LFFS$D`mRWga zE`Rs5p_4}-Tsm11roRflM%S!o2%@AO4s}h`f9!8_Q>c@YTAmr94l#X8r9Y4r zoO=vWkPF~Ax;j>W-ojK@(G8qN_JPjO3-lkGZFf!3iS~CQN!}e zUZYS+-<_F`=$)Q7)r{UnlYv@ zSogpwNka{+*?i*&aruM>$*z2Ly#KLC4!rTe#4Cp}PR)v!z2Xo}V|RMtS7EIqtX$`o zFi4`oAahbksnxL56N)+Y`1}=R7dC=Pm=CKDYhnEI%s{g_A(I&|+wFUHii7+xAqLQu zO#J0woy}intVTB)czqha`k~AO0U|XJ^?2SPk9{I)0iVzLA;J)QM!CTQNa=NzfX}1w zjLw%7FBmw>sqI7F6RebL7Eng`*BFc2B-lZpl9Sy%g^Onk)N*RNY3N~bS49^75<5)2 zC5;TUw&ac!7(!EAuq-_HbtL`KoeS4}tx<%tU)1ZBx8^cqY~*@;iUvEp=$(oGQStY@ zJIOT8+rN}HJMoaGtFI)OvCb9`pc9*Ddh2HcD4L)yXQHZLNGvPF%--ph>LcKF{)BTy zIIlR4#{?{rx0W!OQ60ali=Q-Gf=6{uEyL3}JyF2Iz}lTNs#TMkv;Z-$S_9)Ore?Hq zz;$$d(b09y3ey^%$=0SqHGlG8WiUQ4AutIB1uG5%0vZJX1QZZtwI7X?*GEMVo|vk1 kaJiE^Q7Z%#z<}{rL?7Te8a4k~6kM7xQr#1h0s{etp!Xs75&!@I literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/cert-all/certs.jks b/libs/ssl-config/src/test/resources/certs/cert-all/certs.jks new file mode 100644 index 0000000000000000000000000000000000000000..4cf839ce8d046fa5e18de403210a79844870d002 GIT binary patch literal 4268 zcmcJRWmFVgx5uaGhLM&~ItPXn5EzH<1|_6ZYDgIxB$O1CW?*S&*~tcZRSKmbLg^Ru(1MHeX{3PwxG|#-$PJ?o`>|_uCW{s zfx5s>6o}~LJruXW_#4_9fywxQ#hi!_Dv>o3=v*OJ@-1*f5`XemcP<_}TVE?m^;{E) zzEUCDD4&|YRrv&%A=HMwlZy)en*O^9M;@+NU45(Zi``3yRZ22%k1jceIIA(yw@urR zkD|@$=c?ABbv1e67f#+b_HVT07oLnf?$>0|t09)Na%7fbT&X2Z_eGt|sh-K1vs;dQ zQoMWhhL78C)HYO3Kd9R{mQ~C^ngQmN(|BBy_SfK{k`AF%cY1YR|6GQE<5)*(uL*pQ4Xagr@j>o`!;o!b+Nmw zq#TFaQ)@u)h3dvKkadL|>k7wy-+HX8O7UD5DBe4)x#sn5@+Lf4hm~#0DAEVbJB~O( zuPVs9(Z!S^)#{qO-ufyAov4@!54f1EG0&&x7v?AgE}^aJktv+_7bUk$i9Zu;Xh0H% zb01hfch95IZw8o zGTEIHHr{Z$uX-$;yfA8M!`F-fQVDj>$G|)#F&DgEacahY;&aj zL}F^XM)QT=x#`BnGV0VJN)_>n5a%J4nb)Rrx9elkQA>LI(GF#0leYJsHPFaxpsF=9 zf>FS|GWwRCVYQBWaFQ=lipTAT6lyO(bY8+rO$#wX8$l7blhGG+e^~DW(OAggNMcJC z%a*0@oOpP+Hjqx`u+ zQRj|G*-Q|m{;U}G&lB(M3jlq@b&?OTEC~W?$M!Qi?MGTgOc#S1~j^t<@XmTWt2u-%? zYwh%6yZ{4t+^=XirH9qF?}iya*KD?&GGv#t6b2K?q~x=9+vi&6)jIF5>&uuFdh<7|lf_VX^=K zm>m`cW`RY4z!)$H2m(QcTvFF$I|@cy$Ge(A*7pss6qBi76oe!KPt>3MrT_xT2mmms z7%hyN5NrYg(~%fEqWn1SDRYRy*cd3pWMN`3I80hXOj7bb1BHYXOi~>7?+`{UMhPST zd)ME#42uHZ`sdNtP7IC$QUR{#NkLISAYk~Bb?wZRN8Lp5=knWf{0{h|qZF;a4beb$ z5`x)zolZJFGs{g^zGKaY)MR&gZo#<$#-TZ$vG@>{UrglC)ML1S%+QDwF+wOvS0a`zO7us)1S^0d|P2mxFYnnTq zH!`Nfv#X8^EJ|v;n|YO63FpzzO8C^$^odiEBx-o+!u+{rp@``Fd$S4F?V^B8typ)% zmxC1q1A}xVG%X=y4iJ`iXIK}h2^|-}u%`lM3I(FR`bj*KKsSM~1kkc{_S&CsV!!s{ z<5lW84@`T1(3mJvpp|41;z#_HL?AE_0K~e&oMAVw@9icfgdPHIyCe_`BNq0zsa@A;;I}vc!5)EkI#vnmX z+;-%gl}t$pF*{bxfFq@Rs_MttlsBeAF5z^1XhEjbzGMk)*=_US3ssDfmqtvXW1Q%L zGNt!V8RQz_NGxu>ymG)S4c;(2wr%UjhPACrWUpG&5)elt9@xgF->axxrMB;g-Qaf0 zZZ{@LkPi12J(A8ZD+J}lwcWgZsOBd2Dp1a6)@%M9RksTf&7Fpb%m*o<;!7+4}hQL-_{+z;`^KEFEZPp!gt#unbEQs-XW6OUl zie=t2Z)#btUTv=Yv+DWfA<7^CZj}_#=oi2uL{)LJvyC7i=|g1CJZcpkH{j54^XZlQ z0(7Btl(qGhQleMQM7$SDuJx{`%^0L4ze-!NwVRwEGNCJ>DojnFVMt-%s^YtAHslDn zmwc%91$Jo|zY>YK-08Qmm$Fxv+Ed!8+(S*tepAVF1P8+22tncJAYN9 zzC*T}dKu0&w=90h#Q6=Sm@M3ls5sO@PX*PdDl z(g@!i5Hj9t#8WdvR%>?W>XdmEhXSGbExmYCsEAZdFkclba^0yk@?h=#nh3GX1kuuz zf*Y}gkJ*MKV@GVRUaY!=vqe#4@-a7cn`DpwyPPZ4C9S01ZTe=!nw|~4#KgYBc-d&N z3&&wfm-7Arl%w>CSAwxO4!o7RK9g;tPfX{P|FKNIt?s-X>vlyqzfA#ljf-NucD3H=pSjWsNR3*4Ai`k>Neu{MHi=W9s8 zRV%U1VjxkqHHQe(E&&5=r5(LEl61I`G*^Pokulu?Nk;|^UZ)2*M>`V`BZ zG*h}tv_Zvrn`%tx2YT%0#hiDeG(806X72x>O-kJ3O4(xdL|2Ldx-D%m+{sClG-0Lr zu`bL`A5lFz6vtK}4l=1HoW8RPT8kP*l55&lqo?p>W);+lWnZ&LcY4u1;kIx2SLcZ& z*^ygM0~6it+DuYEPJiR*8PNBg!`}oEIRl(7BgOvHE%1r7m5}9$cJ}#=VH`mQGn= zhtx=5AABf2?;KJBt=s)Ih>j}~?vbxFZVFaqpht1dFfs%M%vjm!^YJ4XFiGUa8Jnvc#1`_ZTXf)Bl- z%Zc6*sS7}}6EAR{t7UmJ;SOS;5fwH3(IWM(@XiP&i?J`@HtQyvUezh!jl1&XPQla6 za;uK(U!pW3%=1l>`IKfHAIiDbE)54!c?1jois-pnSdgRI1=ps^>NBQ z=v_x=#ZcI)#n(aZtcmJ?oIEO0Gt%?ku4(Mo4M~xLNLqZip$Eh??q1}U902H1ZeBps zV>apcUBl^)&)kov%y-3u`26BGY%ruB4HWiV(%pk=Uh964(KnEC_VP3bZAqQroRNZs z`kxh1phXsBN!XKCsKk$kFg-I-XOMd7=|jf*EwKbTZ)86!V?1q~!pfY@nLB(L&@PK%XW=TvtEz#{lT^fy2W%ZK oA{e*&IeKkn7Q)OmUv+aq;Cq<(2t5AN0di&a7x%PT&SK5K0P87#`~Uy| literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/cert-all/certs.p12 b/libs/ssl-config/src/test/resources/certs/cert-all/certs.p12 new file mode 100644 index 0000000000000000000000000000000000000000..b971a1e39c83baeea8e4fab3cf6b76804047ee48 GIT binary patch literal 4757 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 literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/cert1/cert1.crt b/libs/ssl-config/src/test/resources/certs/cert1/cert1.crt new file mode 100644 index 0000000000000..51f39295f62e3 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/cert1/cert1.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIUCYPL1cogC+8WOfSZytklHmrHQh4wDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAxMJVGVzdCBDQSAxMB4XDTE5MDEwMzA3NDA0MloXDTQ2MDUy +MDA3NDA0MlowEDEOMAwGA1UEAxMFY2VydDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCWz6ITDTlkTLueB30Jx0+7sWHdlM5ObZjWhMQ1eyJD0gYU/gkH +2C88IN/PtSv04tzFS6PA4KPDLIyaAhczPlGElSansiui//CpieCI4tt5c2BgVo3X +dJaylYoW3CRILUrlSBOMUmJCQEokverxMrz8DeppNxRfj99pQkoxUkmFMZj/C7XN +VYrTttdF1li5FUtWJxw234OUfum3PQIzz6YTmoPtLrJ2fB8I4CH8R5hwGcryhBSA +qq8pgy61aTPCgEBZ1c4Dvl65X8dG2QEVPjwMZnnbGjvlZefOgkmAWJ1VjihA3GVg +O2mx4tB4D2x5K/OAxh2foZkDVhqJfBkOblLnAgMBAAGjaTBnMB0GA1UdDgQWBBRM +RZ6Qlozj5hWTqf3+oTznFyZTsDAfBgNVHSMEGDAWgBSHd+NjwqkE6hVMEnltx6cT ++D11lzAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwCQYDVR0TBAIwADANBgkq +hkiG9w0BAQsFAAOCAQEAOTUJ64T6kO2H51j2bKIof4ij4yoDD86gLmUF7qXB2Wt4 +tMDCqs9+5VnRzSWY1652mpwPClcK/MfE26PR6DUunoES+8VSbARWh0OB6zsAAWyp +WJ4RxlfYdNpJZjpx3umLGj4yeCh0iOhfoArBUT3vaJJrea+rTro4UFE2Z29uWALr +NvjKZ0Qrn1DMP3N9b7y81dR9RMlzeqk5tlPhAqhHzQM0hDdFKA5uIFn71QQpd5SI +y8MpllWFGGq/+5m7FD0t71GQ/m5xCyfUiqQU31Nj3ThU21SPHBqZIZvQ/na/OaAf +GySn+0ZHAvyNRTL2y2Fk/YAY68kgx2E44H5YSqbFJA== +-----END CERTIFICATE----- diff --git a/libs/ssl-config/src/test/resources/certs/cert1/cert1.key b/libs/ssl-config/src/test/resources/certs/cert1/cert1.key new file mode 100644 index 0000000000000..461ae5ee31ba7 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/cert1/cert1.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAls+iEw05ZEy7ngd9CcdPu7Fh3ZTOTm2Y1oTENXsiQ9IGFP4J +B9gvPCDfz7Ur9OLcxUujwOCjwyyMmgIXMz5RhJUmp7Irov/wqYngiOLbeXNgYFaN +13SWspWKFtwkSC1K5UgTjFJiQkBKJL3q8TK8/A3qaTcUX4/faUJKMVJJhTGY/wu1 +zVWK07bXRdZYuRVLViccNt+DlH7ptz0CM8+mE5qD7S6ydnwfCOAh/EeYcBnK8oQU +gKqvKYMutWkzwoBAWdXOA75euV/HRtkBFT48DGZ52xo75WXnzoJJgFidVY4oQNxl +YDtpseLQeA9seSvzgMYdn6GZA1YaiXwZDm5S5wIDAQABAoIBABCilJEfa045/JQA +5XT3rD7a4R2s9VjHVA2NlYsEqxHqD8uu/dYEraknQyjJJjEb+Rg2MLjszoOP3W57 +fo2jeSBzx1DGIXQYYTaCQ+c1htoNtPrLcVfrv1exkQrWe5YOkO1blvRqffYq20LU +RB8Y5qmy60Fx1uh3mUAmFML9/agYVJo4yCxnNDrMg9UjF4bn/39uOf6C7mEVJRTl +7ET5wcbdl10EOWW2m60hJOQLSOY9N1eafFEO+V2Xb80PC2t3Mqt5+T7n0CKCx/p9 +4F7QAz+hsfksY3oTUUXwL0KoJTLdJrjCoG4mWJ/Re3qEKJqmMfT4XpJKrF7HfgcK +RCyH06kCgYEA/5TVQK4G1Dc4LnSCmCb+ECQvmGRBtK6Alh3Txb4IwGHDGMfC8W4O +gt03A8ZE92pjITHd1+cLykKBsmaVmEtiyD7YL5G3mumR1YdMFEBSZdxOTeD95+aL +YxTofPsDIUIPSFecRWwri7TyYcvUGyDchL0vDc6Gp95/ZFFgt26uxAsCgYEAlw7e +g2McHws9cSAfouULbKbf6jXzy21t6CeqJGID/kjdUws63prcQvmFtFHWrv3rKO09 +hgb3Kd3gUz8t9tAD3F718bSZwzLASwO5ujPHZQVRTotutgCGeKPgXqzyVWeo45ji +4DfQl53jG0aA1DzoZSA3/owcuX6CVGPLzQnhehUCgYAHe9UuuqnKhv9nJNQ6HlIs +KNMX9D+USdPMEX2E+caJ05MB47+KkD1uiYm125VjZUMX0rz7OHG472+ayLQyrGpt +EKIF6o9kwtgZV4fbw/Jltyi30RG+O5rzQMZ5+mOiEqwd4yrZQYyY36iFQpGoZbLv +VBbPoa+BtNsoFdXuKRiG9wKBgBjJhdXFc53ceE6R2N8f+onvsBp8k+6znC9WIuMp +ekJFrpur4hMZEj+jNj9qlnHMlMP4efn+NpyWHfNLEL3JUHje1Di/S+Pt9gPZLqbR +TEzVXIwo8RfIakhti6m9c150ThBazA/C2OWoMNYO8aDiBbhiWw3X6/a8PaKfZZfV +oTwpAoGASCTx55uThl9rN+XDKFXN500K4r+Q9OBOEkfDuDUERpBohUfoy8dO5eiT +mGiqx5P0hoxEC70vnw5fJz4ZpSJ7LcpCfq2TezknJP3MZKwTdBM/pODSPMU5YCW4 +T9ocEQui5PKOTLlVo1QKrG8w6f/YMfZuGa9zP/LmTLZEado9nuk= +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/cert1/cert1.p12 b/libs/ssl-config/src/test/resources/certs/cert1/cert1.p12 new file mode 100644 index 0000000000000000000000000000000000000000..2635a887bc87e4b0c7eab4a4609caf53fa701a27 GIT binary patch literal 2456 zcmY+Ec{CJ?9><3nlNpRPVyumtm|G3TI%tH*K4VSDb}fa>l-)%`b|FTVWU^$POoT!r zOV)JlGDz8%iEPDVZugw`-hJH>aWM4X4cu&CGz`SKqj5$QF}a6e zXXgY&k%8G2CVt&@Tzz-%O>VLZvUb($?Y!d`jb8hTdCRnIr7HWg&N2s+qOO|&iMxpf z@Y&thuj(%;es0L?g`2!&M!yg-vAunbD&YVrH)x9ySRGcsTMxTw&OJaRMD_frPFD}g zHmzuu)8LnHFZ^9+a4&dMq{i06ObBFfvhvOzb&R~=yzB;R>Z_Bsq%R#i?aSRqYLxk9 z9c@88l85B~q%Jb&qqrHo+cKdKE-&pS2J?wFJJkYDB_*ZKVD-O$BivvQc$79oyVwCt zJIK6Y(Sz=saU|q*JdTF@wC*9?wwg=gt^K#^mt^r9SE3clQf05i_)=JWi+F4Qjq1L! z%AoETsMjt$YW$42k*59Zq@A4NX;)L0ExV>?n%11l$Zb@f?-kMP{A`V(tqebskL$&- zgV-Zz`4h|sceO&V&kGvDb@9cC*N0qbkse8`+9(`=;k@>_A?H0OMw@pmE`Ktj&5}u2 zUDh)@W;s44B~7hX8Fl+Wih$S~!4r_J?arbsO;CP`wnU)7k$Jf@KG^1ZZUuGJNN#R? zft*0KHByP38F_~VuLmpFPS{vO30gtVB!5PSmZ(ARyS-L^IOemFnT z^OfVjV_llW!?Z5Ej}%smIH%8j>wT-~nJP+t#;4rBwLg2T-1IQg2!~;i zevIk~{i)8CZ$;nElR!6k>APvHu;Nds_4Rk?)z|ICs%O3(#r~@1GihP$#iwD8r1V^#Pw3RSSp_Mrn zAKzD1=P|rkuPL!nV)*FlDXV+(0s<_rnfxtfS)o8ak!fm=D5($rFINO66@jx$L;sKu z4A}2`z9~~Tu6DhuvaqkdbckqCFPr?(eM687-#e_OM3YVCTV@_pbS=a&XF?~qW}QTO zF60KeXQJLDHdD6+B#|-;jV{vfz!B3FeqP17He?M}KCntbHnB}>wdutf4CM^gjOAHQ?8gY#Y$CkGD;GnY%Uz$6 zPr?Qx=ra9g8P2(Cf>tghaMT(jHk27Sv~t)GmMeUjz2n=-FG8OQUecYI0Hd9orbPEj zlE$=0NP_)h0&~<^C&FPIrL!|!YW)!t62C!3a&kTPf-wMA0?}aUg_+`-ny@RuBQ)tj z=`7CEF1osM9eT8m4rmMC2Nr&`OZ(LMLhN&?w;v?l$aoc*PfIw@3mr@AQhpx2FwFs) z4dT*2sa{)Zu;@Jiwl;QseQW)Y+1jVSsKykH=f4U zGJYDRhnD?~Jwr5I%q|V;wwDzUT*d3BEssA7StqFj3vcxzHEHv`8Nre z#8u#1v$61X?33!ybP=*Zqg=CF3o-MBTJcjJfNwLM%nWNDUmSEM3*k18a z?7S~-tNKZFnY6or8%G$FAlh^&2EwS$>AX`Fa6EPvr-kUp+1%avguwM(9Z7noT6rmf z&%@m>$m>qvPSESY7TyQDP`gHJ0`9(R+N-bmL|?1B9j zC=NY-lwLQYNQkr?S|8!NT?|I`Bc3DZ>-3b>u}nhf)aW$uBB`lg&kTfG-+jy?U9j)m za{p5^gZL4K^*pGg!Y!+NY36_Oiu_W+#T4T&NE?!xeBoyEV_#Hq3+yyA05&xi*3c?^E?Aq7(XKt>+ zMn^vQasb4ocn)BJRzS<3Az%&^49G6V0RRcvcJo)$1BSSjczMLKzR^|JfdQf*&JL{~ MetwyX^uGu6FKEGTvH$=8 literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/cert2/cert2.crt b/libs/ssl-config/src/test/resources/certs/cert2/cert2.crt new file mode 100644 index 0000000000000..3b7ca01e48bae --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/cert2/cert2.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIULGiQ5jnAntO91sS7Al5aUv8/jg8wDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAxMJVGVzdCBDQSAxMB4XDTE5MDEwMzA3NDE1NVoXDTQ2MDUy +MDA3NDE1NVowEDEOMAwGA1UEAxMFY2VydDIwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDQbV6x3P9sstd5zKkjOylk+/X1j0vI6C93HwkF3d9NwhMoV1zq +aSj2SmAQCz4mIjMlAFR9mm6F+3sb8xkMFJD2Mypc5dW6TS5krhbhJdMpoVbceZtS +yuIsvQi1GT2Uwyu89doDiUNBIANqaexrK5x2S/Fy4L8dNl1x2k6PJi6zVpvbnNLV +TSbuSMp3oY23PpX/m6wzJlCYicO8ucMhPwmC0OL9WJNKny4vuEPdiV6/LwCVS4Vr +UpfNqgXLzRMJEbx7C2QEG7T6o2g2101oANBuPaDZcwIQ//EI3IkT10JcABIbwsdj +/Oqj0cf7iEW1IfJWx+kRVT8NfEA5QjL1KQ4HAgMBAAGjaTBnMB0GA1UdDgQWBBS+ +/gUxdwfGB0XcPHsbM6Qw50S2OTAfBgNVHSMEGDAWgBSHd+NjwqkE6hVMEnltx6cT ++D11lzAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwCQYDVR0TBAIwADANBgkq +hkiG9w0BAQsFAAOCAQEAiztFGa3uZVb6Fs8evN4CU+hPFYdhF57lEfT6Xa1OUD1B +5e5rDOZfVloy4gzLdtNCS9lieTgB7Yc3wDUCm0cS48JCMxykSRTI6M0Fmofsgd5d +OKR7CB+jR1Egj6nZren62XCgqjuJ+dbP4DinY6TifFzFX1vOD4RTb0mEn2WHL/JB +DnDxOETmBtKFyueprMtXkTO23dXsYXQeo2Gyih+t5ksqMnxCW2GFkkOqrOUQY8CF +6CVmmb9UCYk3f3Av9TedqJ8Cmoe+HSP0R2oxpnc7cd1v37QPxWgHETro9zS4FBrt +6KgNTP99b+aLxeeuMKJuRVR+TCZPuyYbBzUfp2ZZOg== +-----END CERTIFICATE----- diff --git a/libs/ssl-config/src/test/resources/certs/cert2/cert2.key b/libs/ssl-config/src/test/resources/certs/cert2/cert2.key new file mode 100644 index 0000000000000..1384b2daf309a --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/cert2/cert2.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,68F4EFD94D4D9BDF + +TL01d1JqaN0P1sb8284jOGpLn42cubkLY7JPj+bmpL2PEH9cT2xo7MM5cwvNSbPX +nuZ1CMFSFqAyxulh/rfZwU1BumKLiwM+ep0A/lWwKJEor+ancCBdkIHWfSg3Jc0i ++nHhL3e7+W+XRfSlafQQkhFkeOXs0hSa4WYX6tymvMAf8OLAenKB/0MOvKjd6EOu +ZIvnBxAjAaDoRr4X4izIVlNmsIbvYARW9WjjcK2FZhNn+WLLTkfwEkl0glRl440L +ET9qAjoH7j5KL5yKw5h2HhALSLaXdiAfCGvhpU4K0Afpb225mvR/uL3HLpJEGKKb +DuoK99zvjHqReq1YndIR688ioc0O4dUvujZKgn8OR2JvGJVSc+mgaIWadfs2aADz +CJIlqnSJa1EW6qm6knLJwieEBoNHbeFszrCKrYdy7uicys9PR1XsoMrOly+HhVnR +PChA3gV6AVIjyMAUkFLg4NAHjgDbb81lu8ENFmlJcjgVeXDMykrpBmhzmhSPtOEb +6bdQCKuA7zXIpJCmP66ZSuNHxikrfqLJjXW3PojLuCx5nfO0akpxjSeyLHzv/YXV +YxnpLJxMybG1KUHyDHRmDrd+UPzLnh52O/g9NoiAlluUcblH+BKer18dJdGSl95G +P+Ted08S0yP2niNz6XHv5KbztzsP73n0w82akCQB8ZAsGJ0CNw7S7N6tWam/UkMN +tvM9nFCjvwah1funu8nj9QyWrzac6ngPP0s8tcKG0ahLEJYn7Hx2Oqn7K39fbMkX +UOJVNEBQP2Anf5dJMfYPEI0xihv3ec1RxpipOm9DKQ878jxnqxgZxa+Pab9j/JxQ +j+BGaoOnYj2jHBnt4nCP75F0BEZaGxsZX2MjJySK+5jy2WW3JRC/E0qPkL6oBvT2 +3e37fep4XKSjqR9lSYjW+AlAVw2uPkwxDp5sD7XFGsH3SM1qj54NWwljpKXnOTbQ +Xws18VEiWsQ3kD7ft11w8/67Bb27TsWEJRo1vCC4KsYBCyjEOrp13aJxpSiNI24W +oSmrQTsCco1Yrxfs0noNu0FzfJHV6qmHR7ps0fj+AWJyquYbIEK1762+r2uutgSg +ZVaPWLkm9uq5K5rNXsj3w1L1CK4YcDre0yt+Kg4Pt3OQR2lF8CpABguA3gR9kO2g +9N8hAfiOq5MDliU+9r6Cr/dPkdzV0Eo971HdfaLD7S/pjSP37fcTcOpDhwNt3UcK +amhR/Zm8Ll414zc/iNiVTcu6+chzSY9Yhwfa8A/XuIfoqMrTiaBMPlxe0tNKaKVs +09d6U5JoTQJcnei/4ODkIIYOzsmMtHgKtmeg86AB8yeLNgqWCrJi+Fxnha+cJ9No ++STMQP4vS8qgRe5XkRGaAZBHyPAwxOou1Iu167LjlFFl0YSYUZTChha5XBG2Uhm8 +FeIH1Ip+83fxMoyiYROOdeMuLXtQIndA3fmjduBEO0kPtAwQ2xfH4g59XnnSL97S +9AslnPnUWzDR0zBr4GQBNAKLaMIFGzhDZEzaooYetoeDYSczil/Rf1D6wyM6Cgjq +BK7c0kNum9uXaDbYCh6spzYua0j1noqsBTm9V25178lNbkQ6yAPdeYCxRmUXHtXk +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/cert2/cert2.p12 b/libs/ssl-config/src/test/resources/certs/cert2/cert2.p12 new file mode 100644 index 0000000000000000000000000000000000000000..269e855f7709843933975c58237541d8cec052b3 GIT binary patch literal 2456 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 Kqh$NP2lQWnTyQ1; literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/README.asciidoc b/libs/ssl-config/src/test/resources/certs/pem-utils/README.asciidoc new file mode 100644 index 0000000000000..0136e967106e1 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/README.asciidoc @@ -0,0 +1,149 @@ += Keystore Details +This document details the steps used to create the certificate and keystore files in this directory. + +== Instructions on generating self-signed certificates +The certificates in this directory have been generated using the following openssl configuration and commands. + +OpenSSL Configuration File is located in this directory as `openssl_config.cnf`. + +NOTE: The `alt_names` section provides the Subject Alternative Names for each certificate. This is necessary for testing +with hostname verification enabled. + +[source,shell] +----------------------------------------------------------------------------------------------------------- +openssl req -new -x509 -extensions v3_req -out .cert -keyout .pem -days 1460 -config config.cnf +----------------------------------------------------------------------------------------------------------- + +When prompted the password is always set to the value of . + +Because we intend to import these certificates into a Java Keystore file, they certificate and private key must be combined +in a PKCS12 certificate. + +[source,shell] +----------------------------------------------------------------------------------------------------------- +openssl pkcs12 -export -name -in .cert -inkey .pem -out .p12 +----------------------------------------------------------------------------------------------------------- + +== Creating the Keystore +We need to create a keystore from the created PKCS12 certificate. + +[source,shell] +----------------------------------------------------------------------------------------------------------- +keytool -importkeystore -destkeystore .jks -srckeystore .p12 -srcstoretype pkcs12 -alias +----------------------------------------------------------------------------------------------------------- + +The keystore is now created and has the private/public key pair. You can import additional trusted certificates using +`keytool -importcert`. When doing so make sure to specify an alias so that others can recreate the keystore if necessary. + +=== Changes and additions for removing Bouncy Castle Dependency + +`testnode-unprotected.pem` is simply the decrypted `testnode.pem` +------ +openssl rsa -in testnode.pem -out testnode-unprotected.pem +------ + +`rsa_key_pkcs8_plain.pem` is the same plaintext key encoded in `PKCS#8` +------ +openssl pkcs8 -topk8 -inform PEM -outform PEM -in testnode-unprotected.pem -out rsa_key_pkcs8_plain.pem -nocrypt +------ + +`testnode-aes{128,192,256}.pem` is the testnode.pem private key, encrypted with `AES-128`, `AES-192` and `AES-256` +respectively, encoded in `PKCS#1` +[source,shell] +------ +openssl rsa -aes128 -in testnode-unprotected.pem -out testnode-aes128.pem +------ +[source,shell] +------ +openssl rsa -aes192 -in testnode-unprotected.pem -out testnode-aes192.pem +------ +[source,shell] +------ +openssl rsa -aes256 -in testnode-unprotected.pem -out testnode-aes256.pem +------ + +Adding `DSA` and `EC` Keys to the Keystore + +[source,shell] +------ +keytool -genkeypair -keyalg DSA -alias testnode_dsa -keystore testnode.jks -storepass testnode \ + -keypass testnode -validity 10000 -keysize 1024 -dname "CN=Elasticsearch Test Node" \ + -ext SAN=dns:localhost,dns:localhost.localdomain,dns:localhost4,dns:localhost4.localdomain4,dns:localhost6,dns:localhost6.localdomain6,ip:127.0.0.1,ip:0:0:0:0:0:0:0:1 +------ +[source,shell] +------ +keytool -genkeypair -keyalg EC -alias testnode_ec -keystore testnode.jks -storepass testnode \ + -keypass testnode -validity 10000 -keysize 256 -dname "CN=Elasticsearch Test Node" \ + -ext SAN=dns:localhost,dns:localhost.localdomain,dns:localhost4,dns:localhost4.localdomain4,dns:localhost6,dns:localhost6.localdomain6,ip:127.0.0.1,ip:0:0:0:0:0:0:0:1 +------ + +Exporting the `DSA` and `EC` private keys from the keystore + +[source,shell] +---- +keytool -importkeystore -srckeystore testnode.jks -destkeystore dsa.p12 -deststoretype PKCS12 \ + -srcalias testnode_dsa -deststorepass testnode -destkeypass testnode +---- +[source,shell] +---- +openssl pkcs12 -in dsa.p12 -nodes -nocerts | openssl pkcs8 -topk8 -nocrypt -outform pem \ + -out dsa_key_pkcs8_plain.pem +---- +[source,shell] +---- +keytool -importkeystore -srckeystore testnode.jks -destkeystore ec.p12 -deststoretype PKCS12 \ + -srcalias testnode_ec -deststorepass testnode -destkeypass testnode +---- +[source,shell] +---- +openssl pkcs12 -in ec.p12 -nodes -nocerts | openssl pkcs8 -topk8 -nocrypt -outform pem \ + -out ec_key_pkcs8_plain.pem +---- + + + +Create `PKCS#8` encrypted key from the encrypted `PKCS#1` encoded `testnode.pem` +[source,shell] +----- +openssl pkcs8 -topk8 -inform PEM -outform PEM -in testnode.pem -out key_pkcs8_encrypted.pem +----- +[source,shell] +----- +ssh-keygen -t ed25519 -f key_unsupported.pem +----- + + +Convert `prime256v1-key-noparam.pem` to `PKCS#8` format +----- +openssl pkcs8 -topk8 -in prime256v1-key-noparam.pem -nocrypt -out prime256v1-key-noparam-pkcs8.pem +----- + +Generate the keys and self-signed certificates in `nodes/self/` : + +------ +openssl req -newkey rsa:2048 -keyout n1.c1.key -x509 -days 3650 -subj "/CN=n1.c1" -reqexts SAN \ + -extensions SAN -config <(cat /etc/ssl/openssl.cnf \ + <(printf "[SAN]\nsubjectAltName=otherName.1:2.5.4.3;UTF8:node1.cluster1")) -out n1.c1.crt +------ + + +Create a `CA` keypair for testing +[source,shell] +----- +openssl req -newkey rsa:2048 -nodes -keyout ca.key -x509 -subj "/CN=certAuth" -days 10000 -out ca.crt +----- + +Generate Certificates signed with our CA for testing +[source,shell] +------ + openssl req -new -newkey rsa:2048 -keyout n2.c2.key -reqexts SAN -extensions SAN \ + -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=otherName.1:2.5.4.3;UTF8:node2.cluster2"))\ + -out n2.c2.csr +------ + +[source,shell] +------ +openssl x509 -req -in n2.c2.csr -extensions SAN -CA ca.crt -CAkey ca.key -CAcreateserial \ + -extfile <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=otherName.1:2.5.4.3;UTF8:node2.cluster2"))\ + -out n2.c2.crt -days 10000 +------ diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/corrupted_key_pkcs8_plain.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/corrupted_key_pkcs8_plain.pem new file mode 100644 index 0000000000000..4b2271a6f3971 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/corrupted_key_pkcs8_plain.pem @@ -0,0 +1,24 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDesZnVBuxbT4y7 +KtIuYx8MUq0sGQgVbxXSBG66sWDU9Qoo1HUyra0xXCONgRMBT9RjSIpk7OOC9g8q +ENNgFO179YdHVkrgJhW/tNBf+C0VAb+B79zu7SwtyH2nt9t378dmItL+sERkMiiG ++BS/O+cDz44hifDiS7Eqj/mJugAhLjWSUyD+UBObxXvUsxjryKeG3vX9mRCgAcqB +xH3PjI1i9DVaoobwMbwpE5eW2WXexOspuXnMmGfrrR6z/VmdHqe/C3rGdJOX+Y0c +yOR+/Vuzisn+nLeo/GJx2hIif8rKiNRyAdUXfx+4DLYJBN2NUbl9aP2LP6ZC8ubf +6qwhhB0XAgMBAAECggEBAKuzP6qSNfaJNTayY2/EmRHFRSP1ANiV17sgE8f6L3DC +pdypQtuaMSkXo4nc9SxTwqvyKFJ8m0ZENZj3dCJmwFyNCIqmLAD7HFW9MdRs40WJ +HYEv0aaeUyvRo6CHD74/r/w96XTZr0GZssmtyUFRDGNRyoJter7gIW9xprLcKHFr +YTmdaAXbOm5W/K3844EBouTYzYnZYWQjB3jT/g5dIic3AtLb5YfGlpaXXb74xTOU +BqY1uKonGiDCh0aXXRl2Ucyre6FWslNNy4cAAXm6/5GT6iMo7wDXQftvtyK2IszP +IFcOG6xcAaJjgZ5wvM3ch0qNhQi4vL7c4Bm5JS9meoECgYEA88ItaVrfm2osX/6/ +fA8wYxxYU5RQRyOgLuzBXoRkISynLJaLVj2gFOQxVQeUK++xK6R182RQatOJcWFT +WwmIL3CchCwnnXgPvMc51iFKY94DbdvrRatP8c5sSk7IQlpS3aVa7f7DCqexggr5 +3PYysuiLirL+n9I1oZiUxpsS6/cCgYEA6eCcDshQzb7UQfWy//BRMp7u6DDuq+54 +38kJIFsPX0/CGyWsiFYEac8VH7jaGof99j7Zuebeb50TX57ZCBEK2LaHe474ggkY +GGSoo3VWBn44A1P5ADaRGRwJ4/u79qAg0ldnyxFHWtW+Wbn11DoOg40rl+DOnFBJ +W+bWJn4az+ECgYEAzWduDt5lmLfiRs4LG4ZNFudWwq8y6o9ptsEIvRXArnfLM3Z0 +Waq6T4Bu1aD6Sf/EAuul/QAmB67TnbgOnqMsoBU7vuDaTQZT9JbI9Ni+r+Lwbs2n +tuCCEFgKxp8Wf1tPgriJJA3O2xauLNAE9x57YGk21Ry6FYD0coR5sdYRHscCgYEA +lGQM4Fw82K5RoqAwOK/T9RheYTha1v/x9ZtqjPr53/GNKQhYVhCtsCzSLFRvHhJX +EpyCLK/NRmgVWMBC2BloFmSJxd3K00bN4PxM+5mBQZFoHMR04qu8mH/vzpV0h2DG +Mm9+zZti+MFRi0CwNz2248T4ed8LeKaARS1LhxTQEkECgYBFsPNkfGWyP4zsgzFs diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_encrypted.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_encrypted.pem new file mode 100644 index 0000000000000..a251de23f4879 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_encrypted.pem @@ -0,0 +1,15 @@ +-----BEGIN DSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,BE9A0B63873F6B7A + +lGSpJkwN0J9p+2Wm58706EYz6mmjgz7okjMtsR87GMIiK/wVwjKmyUa73QTVVs15 +N/EOySftBk3VUSPx9G1ZMxKpp3l/hvkIcsDDfCPAZFqwdQQJ8BEeF9jDd5ZoI6Yz +Yus1+X8A1OpX1O7PCZ08e2fLeVuEWg62/JQcNukuvL7AKm+qa1sda5/ktquv2eMZ +nbTiOE3Xe+uDsgABQdy1h4EsMEaMdE6QrWdxLGWDGcdzSzfltvnhmmsK2CQsV4e1 +huQeb8ylShJuIr+mgtKgUlIlJwSd7ka8hIdmGt1LO9+NZOPUGN04daQkETtfwsmu +YIYkh66CuLbT4nZny64Spa7AeINSmf9GA72/QtRSo3M7Khlw/95Lz24iKAy7/Lbt +AKYenSQeJtlNgWzPcDIeUrIzXXmAXHN5YGMg/7X0h7EGu5BxYbLydkBRvSkV9gzU +Ms6JD5aON10DQhjIUwUcBnhSnwPPpIVa2xf9mqytkcg+zDgr57ygZ9n4D+iv4jiC +ZJuFCFrgeqHrCEKRphWRckyhPo25ix9XXv7FmUw8jxb/3uTk93CS4Wv5LK4JkK6Z +AyF99S2kDqsE1u71qHJU2w== +-----END DSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_plain.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_plain.pem new file mode 100644 index 0000000000000..a64642fc9ab0c --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_plain.pem @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR ++1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb ++DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdg +UI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlX +TAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCj +rh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQB +TDv+z0kqAoGAd0xuuUUSAXsXaQ/dp9ThBTVzdVhGk6VAcWb403uMXUyXKsnCIAST +m6bVWKjNxO1EsP3Slyd5CwbqIRUBK5NjzdQP/hHGtEIbqtYKY1VZI7T91Lk8/Dc/ +p9Vgh27bPR8Yq8wPKU3EIJzYi0Nw8AxZf10yK+5tQ6pPUa3dH6lXt5oCFF1LyfuB +qBYh7hyIsfkb+cZoQ57t +-----END DSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_plain_with_params.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_plain_with_params.pem new file mode 100644 index 0000000000000..0a2ea861b9b66 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_openssl_plain_with_params.pem @@ -0,0 +1,18 @@ +-----BEGIN DSA PARAMETERS----- +ThisisnotvalidabutwedontparseiteitherwaykFJyVA+0q1vAej5iQVmUvu1y +fuA5axTA5IT86U7acP0KV8eawbDXVhqiP0zcSeP731coxJaUHC6XB0FVMhYi4fZn +fexykg9Kxe/QBfDtcj3CEJNH/xoptJQVx3hi+0BPPK8+eUXTjwkQerGMwUD7UQak +xuUS/22GakHZV5G/kCc= +-----END DSA PARAMETERS----- +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR ++1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb ++DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdg +UI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlX +TAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCj +rh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQB +TDv+z0kqAoGAd0xuuUUSAXsXaQ/dp9ThBTVzdVhGk6VAcWb403uMXUyXKsnCIAST +m6bVWKjNxO1EsP3Slyd5CwbqIRUBK5NjzdQP/hHGtEIbqtYKY1VZI7T91Lk8/Dc/ +p9Vgh27bPR8Yq8wPKU3EIJzYi0Nw8AxZf10yK+5tQ6pPUa3dH6lXt5oCFF1LyfuB +qBYh7hyIsfkb+cZoQ57t +-----END DSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_pkcs8_plain.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_pkcs8_plain.pem new file mode 100644 index 0000000000000..fc5f17ce89897 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/dsa_key_pkcs8_plain.pem @@ -0,0 +1,9 @@ +-----BEGIN PRIVATE KEY----- +MIIBSwIBADCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdS +PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVCl +pJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith +1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7L +vKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3 +zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImo +g9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoEFgIUXUvJ+4GoFiHuHIix+Rv5xmhDnu0= +-----END PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_encrypted.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_encrypted.pem new file mode 100644 index 0000000000000..69dfde4b3c502 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_encrypted.pem @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,692E4272CB077E56A0D4772B323EFB14 + +BXvDiK0ulUFKw1fDq5TMVb9gAXCeWCGUGOg/+A65aaxd1zU+aR2dxhCGXjsiLzRn +YFSZR2J/L7YP1qvWC7f0NQ== +-----END EC PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_plain.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_plain.pem new file mode 100644 index 0000000000000..e1d0a6a8319c0 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_plain.pem @@ -0,0 +1,4 @@ +-----BEGIN EC PRIVATE KEY----- +MDECAQEEILEXCgqp9wZqKVmG6HTESPeCyx2O4TDoFqyILz7OGocEoAoGCCqGSM49 +AwEH +-----END EC PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_plain_with_params.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_plain_with_params.pem new file mode 100644 index 0000000000000..2ad57473236b3 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_openssl_plain_with_params.pem @@ -0,0 +1,7 @@ +-----BEGIN EC PARAMETERS----- +Notvalidbutnotparsed +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MDECAQEEILEXCgqp9wZqKVmG6HTESPeCyx2O4TDoFqyILz7OGocEoAoGCCqGSM49 +AwEH +-----END EC PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_pkcs8_plain.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_pkcs8_plain.pem new file mode 100644 index 0000000000000..7e6de54424702 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/ec_key_pkcs8_plain.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCxFwoKqfcGailZhuh0 +xEj3gssdjuEw6BasiC8+zhqHBA== +-----END PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/empty.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/empty.pem new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/key_pkcs8_encrypted.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/key_pkcs8_encrypted.pem new file mode 100644 index 0000000000000..28059d5a2266d --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/key_pkcs8_encrypted.pem @@ -0,0 +1,29 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE6TAbBgkqhkiG9w0BBQMwDgQI2jwlFL0XId0CAggABIIEyMujZbpG6zKb2pVu +soamTaoLcZwNofS9ncGIEH1nbI8UpPY81VeOIBm4mneDt8RU5bIOXP4IZEZY9uU+ +pugKQ3hT8vBQjJujjuctUPaFxB0kGEeITOInY2jn2BFDbUgy5Z7EVD4G2K06SDDK +oD+twbzZo9x34VizwpHHb8wE+DFyYc+sp+Re2Qk3FReKgjdJezfcRHbKrrlx2rJ+ +k/YAPmzcFYVbuUiB6HY6BGzSJO1JxT8iNJE+Hmk3ZLXG590hp0vuGSkY/ihbeix4 +1rQs7u4riqXJ+DJBmXt/wXUij0/k6s4igACNsT2MkZkGEDkzqzE+kj2VYOHSX+Wd +5W0WCfftcsIQ8eow4ACec/Ns9ionLjx1xnbTjRMkpGgbVsreupU9AQ4MhLNNgwyl +six/cxUxTvH8Modd0/4KQFkeo352A6+DKCaPZ87SoF2Rge1otcJaZVcX1gBvIztB +/xzYwyUydQEwblU0kCYWRgxlKP9jxFoke2RX1BodRfAMNDxS0XyYrA/JzB7ZRsS7 +QGYPy/PPb014U3KhpJdjwbNu2VaCVdGryYA9+BTP+Vzwcp8MZoMPnnjnBh1YyVAj +c7oyzKU5e5SVsYni1Kt/536YxQgFCAUHv/g+zQqqGEvyiMXhsCwVpoy7UcFYgmlw +40g3+ejwvlO3YA67gQQKebEv6/Laz1hVNiXT0m3okAXWxXgF/g2eBO5NTRdtaWn3 +VNH5ub+LOr6cMhk9BAtKgRG+xeh8/2SqH12UbwtlmxqnBAfHtqlE6yJ1ViMDHxF9 +101xJlEONmC3fcEAjShK6LEbFwPJns3WbGc0ds36CzXWtO29XGssr+YoiF9e3Eus +/XQjmjOJxRoWkNEYsxlJ3IRH2vUcdCoAp8IlD7JYxx8UBCSJDBo7/0QKU6INeWjo +5+aNbaLAJULSKo1LTZjjANm+G+KcSnbn5Ed8fmY+D61A5/7WMIVxq/uDLFvxCnRG +QcLbtqbPztiWwWZOuTwNTA3bfAhEG0ZzNr+0z33jW5T9ChvdutgxJMf3Khazx9cx +mWyCpJwtSv7hSbp4nCS2fmHCum2yIrOnou8TSOlQFERZ3UEZMgLpWeupH/W5C3Ad +rOspFrK6K8a/iNSs5OdYUIK2iHddTs5u7AEZ9I5MTuYnccuGuXfQTTA06TJvJTax +c2oDbXMnXs4pHLiiSRp34IHIYubdrj8X5vTODC5djl8h1167ToXo5zGdXqT1om+u +4ndNLbbI1vld5G7KAL6TlTETg+N7S8v3KYoBEWzykwgqqppWnWTqPWQxM8Iph5ly +AQlzz7feERi/h/s57RZ5ksoVAdbtk2U6wgHnLrWhKZ7+ZOAfpNAjGHwWyXTzylXo +zQ9A8Kmd0jBMsru4fsGpldf4lTsqO/abUSWrAAREGnlz/ZjEb944Yox7JUhWC15C +WxXK2rFbiF3S0HtEvU17rdn4HCsZBilnY+hTpHj1MA6O451/A3ghxGXFKz/9LUcS +YBRQJaSM3hTqC3WoTVBeVc5nCFOpu4F89JqhEgXOLKweueMbTMRSNm93tXWT13s3 +Q/o0pNJv/K6+bIQwsX/oDafMXcW7STxQJObbAleRbcn8/rGS2eEnVZ6907faUR/L +7eu9vgAa/jh9FHpZ0Q== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/key_unsupported.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/key_unsupported.pem new file mode 100644 index 0000000000000..96f95848d099f --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/key_unsupported.pem @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBqIPMG94HL7zedFzsvi45mHS8ZuyLQXqvHpHobcdNCJAAAAJimRM7VpkTO +1QAAAAtzc2gtZWQyNTUxOQAAACBqIPMG94HL7zedFzsvi45mHS8ZuyLQXqvHpHobcdNCJA +AAAEBvVc8FVPGUs3LZ1o+LnjW4uUlEnk/5LQQ9yO2eiI3SFGog8wb3gcvvN50XOy+LjmYd +Lxm7ItBeq8ekehtx00IkAAAAEWlvYW5uaXNAc2VjdXJlYm94AQIDBA== +-----END OPENSSH PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/rsa_key_pkcs8_plain.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/rsa_key_pkcs8_plain.pem new file mode 100644 index 0000000000000..dd1675957f69f --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/rsa_key_pkcs8_plain.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDesZnVBuxbT4y7 +KtIuYx8MUq0sGQgVbxXSBG66sWDU9Qoo1HUyra0xXCONgRMBT9RjSIpk7OOC9g8q +ENNgFO179YdHVkrgJhW/tNBf+C0VAb+B79zu7SwtyH2nt9t378dmItL+sERkMiiG ++BS/O+cDz44hifDiS7Eqj/mJugAhLjWSUyD+UBObxXvUsxjryKeG3vX9mRCgAcqB +xH3PjI1i9DVaoobwMbwpE5eW2WXexOspuXnMmGfrrR6z/VmdHqe/C3rGdJOX+Y0c +yOR+/Vuzisn+nLeo/GJx2hIif8rKiNRyAdUXfx+4DLYJBN2NUbl9aP2LP6ZC8ubf +6qwhhB0XAgMBAAECggEBAKuzP6qSNfaJNTayY2/EmRHFRSP1ANiV17sgE8f6L3DC +pdypQtuaMSkXo4nc9SxTwqvyKFJ8m0ZENZj3dCJmwFyNCIqmLAD7HFW9MdRs40WJ +HYEv0aaeUyvRo6CHD74/r/w96XTZr0GZssmtyUFRDGNRyoJter7gIW9xprLcKHFr +YTmdaAXbOm5W/K3844EBouTYzYnZYWQjB3jT/g5dIic3AtLb5YfGlpaXXb74xTOU +BqY1uKonGiDCh0aXXRl2Ucyre6FWslNNy4cAAXm6/5GT6iMo7wDXQftvtyK2IszP +IFcOG6xcAaJjgZ5wvM3ch0qNhQi4vL7c4Bm5JS9meoECgYEA88ItaVrfm2osX/6/ +fA8wYxxYU5RQRyOgLuzBXoRkISynLJaLVj2gFOQxVQeUK++xK6R182RQatOJcWFT +WwmIL3CchCwnnXgPvMc51iFKY94DbdvrRatP8c5sSk7IQlpS3aVa7f7DCqexggr5 +3PYysuiLirL+n9I1oZiUxpsS6/cCgYEA6eCcDshQzb7UQfWy//BRMp7u6DDuq+54 +38kJIFsPX0/CGyWsiFYEac8VH7jaGof99j7Zuebeb50TX57ZCBEK2LaHe474ggkY +GGSoo3VWBn44A1P5ADaRGRwJ4/u79qAg0ldnyxFHWtW+Wbn11DoOg40rl+DOnFBJ +W+bWJn4az+ECgYEAzWduDt5lmLfiRs4LG4ZNFudWwq8y6o9ptsEIvRXArnfLM3Z0 +Waq6T4Bu1aD6Sf/EAuul/QAmB67TnbgOnqMsoBU7vuDaTQZT9JbI9Ni+r+Lwbs2n +tuCCEFgKxp8Wf1tPgriJJA3O2xauLNAE9x57YGk21Ry6FYD0coR5sdYRHscCgYEA +lGQM4Fw82K5RoqAwOK/T9RheYTha1v/x9ZtqjPr53/GNKQhYVhCtsCzSLFRvHhJX +EpyCLK/NRmgVWMBC2BloFmSJxd3K00bN4PxM+5mBQZFoHMR04qu8mH/vzpV0h2DG +Mm9+zZti+MFRi0CwNz2248T4ed8LeKaARS1LhxTQEkECgYBFsPNkfGWyP4zsgzFs +3tMgXnIgl3Lh+vnEIzVakASf3RZrSucJhA713u5L9YB64wPdVJp4YZIoEmHebP9J +Jt1f9ghcWk6ffUVBQJPmWuRbB/BU8SI+kgtf50Jnizbfm5qoQEt2UdGUbwU3P1+t +z4SnBvIZ3b2inN+Hwdm5onOBlw== +-----END PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes128.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes128.pem new file mode 100644 index 0000000000000..b4448ec8afaf3 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes128.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,AD45A956510B909DCCACCE07DE3BA1C2 + +Vk+KErTbsSdjNO5vaCpik/OLkaOQ4Fm3rNIUrQPMEBiK/TXnHMvC/X1DZenSwA8W +yHuSpoAAg/HjQv5UskRtn6Rt74ALViM4hO6BleNxr/8lIBZAeLNjqoGwf62MyExV +rraRhXvYepiTnVSQDYuTafxdIXqzg7O5kYcR46gpphXTjMWDMLxsEiKQ1u51lPFU +SzxSMGMKiJL3PAXuWyoKgUihw6sv+mVPzq4MVcZKTrlcNRGRFQWUhVzqNd5Qdx/v +vBUFbWVcMXx4tSsx/WtIOiUwZTbmLk4dpXysb0+Tp6lb+7AQ2RR+9tkBWEdBPUx9 +qkBfFdAvfnA5vKR0SwAZU0dFaDWlQD2ktCJv4hwPN0XYMIv5WW9HoA+R88y+dhHT +sYgM3eEusQv9byC+XCzxPNg40yC8X9TG2z2deMUl6ippsrTULPx1WaoLf12x1Yl3 +vZ7MFB2hvJmWYofjTVz7Xa3FMH1dhJgBTwpUY//EgPhSaTrEMGwrXJQk40nam/LX +KjK/acvYmZHZZZJ+E0Pv481tFiiWVlXqfI9Tw1ffi4EzezhQTtzz2EBHaanHNEFa +C+7XQnxmBoNPpwOBh4Lh9oLcDN9uOGBLb+dIzn2cNQZXhBCKI8IV14YtZGZYhRHg +D+q7V6I/lEd1WNerHZRNI9o4ZBTJl+7GlJ1gveDTdcx28hCdC5oae6ZwIzSZPuDA +JPF3vr2yci7JsUpBqnaSnxpz5eKYbng3WjqweBXNgRWLhF8HT8fmWNJyvYjWpg+x +c8vh/FEM1HY3jsxE8NtIAlObJDMm/K/k8keVbbGm8c58oKdO4kdM+Z6aLe53nFo8 +ISwxsps//eak25Rx2H0bNvO4LVhqNHPXyYQ2nqtx7UpEgndrggHP7n3vcjtdE1f3 +N83gSm6SIVIeQJom16Z5Cjm4PRvJltIf2njpLTeP43eMoYNNVSCr2iZyrSNXnEes +TI47HidjCNkCp5ahPnuzzyeBCo9L9x2odTNOrga8sBii7VQBE3cGhAFkaUf0E6os +gpPqUWHkXE9Nb6H6EBR4gwbdpUqcgrm+kp6Ei5N/z7gSfV91WO45WmMLpCPmlPDQ +An+drt25y+AhaIEmoczUGAiz3jOdyd6Xqw+dVXGb9WPxXL4YnXgr4mSC2am9Vad6 +/MgIqYfqA/AOW1wY2dhoqfAGG2ITadFh82W6cqMhmeDQtDFb6/s96O4e46zev+fP +Nhro3k+JnL3InC9qAvkxEa/FpbL205X3X3FTXM6xK9ZDvq8+hbPxCjg73mXQfbbG +0/M8hE5hDgILTPiHhHFzGVNjYTAvjNnttg1n7+A52WGs+Hfwlf2x10p8Y2YwyOon +qfEMM3G1C3sDzEYmo+w0IZ+pesMWejMPOFiHYRCWVl8r5jx9lTSvbB3Xj+0Ygyo9 +15iLEGyr623I8LDBegqpNntlhX+AeHuJcthPRB6Jl2S0Q1xikD4fW1Ge29/l9Ndi +7TvZoSGh1jfA71pE1Ay2RyH5PMNj8KJvTGZPFEuIdzDUKlJkC3xUEvl6Q0prU171 +d/ka98AxLR9jUur0ARqxsckd5IXDTlZqsRs8W/gk5FP9RibiN7upiJcKgwYddiJx +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes192.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes192.pem new file mode 100644 index 0000000000000..4696e77bd3f64 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes192.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-192-CBC,FACBF3734C8DD3C53F31E294D7E8DC16 + +9g2VpXQljNeeag2/jh0b1aKE+xcbkNKfIMeUljhiOxULegO53Apn/THshhJhtgPG +VYRlmk1ImCnwbWiy4C7WVXbOh1yGbYMPLipbtjEI7dr7OPbRX+GYn2Sln6iW9K61 +A019xPz1dLJ4bciNf5gcq5Wf/Qxj8R33ZPqANIHyMeZDSdGqFu+BQyQuQtJqFLkv +nokev80VIRuxinfmV3RSdUHo3g7iXRNq10bwxV+fns5fyzm5eq4q8Ac0M2NbhWds +wVl2gft73W41nXFqgS17Xo7cuAIdE07EGXVOq7UGKwLvAkgRWhZEt0BJZKB3XQAs +GfApMSOfIfTIS0YFjmkbGMKfprc8cgqPyDafKLDAGwViTWfM5oO2duium7OjV80g +eaL6iAImxFzfg3n8hsHg31iisM5p6d9VegXlY7YacdkFR11LN47nXoFU9l9vtKPG +TSouB4/0Dw4eCxmfbmJiO4pe8jn4pk4XhMszqc0Q+fRkHXeEigQgFsI4SSkuNk7r +EPSMqPSHpB5SkLyccfvd/wSBv1DvfdMIA5+CUUj3qAT7pm6tvtj0ZnXXnUVexlfp +9+mPMrP0oJ8fSX5kQksCbw+a4C+1ffCzU4S1CUVKboopHzbU2LG80XvjPqXGj2OL +++ghD7OjcD7DqWkO81FQPadrHqWMa8gf2rHmuamZh58LIpattu99lIHVHfFJhYlg +s8EEJQRLa7V4/1Mx9uZGKNmjHNzw/QGW5VqZ3MoVTuXQ3uKmfsXdUTpGRszkJzU1 +zpIOGOMWctWcCmTXpYEhYfiNcPK/WyHntlQJpUgutX/Pho4Q9dP0U1fgsHiKTcRJ +IAg3/pdCiv48K3Wx8Ib+J09mx4wP0rYnaT6f3LSTV+O8u+D8swjngDJ9vYOnyBQt +Z5nYrCpQcvaTGhWAQdz9OqAmPwjY7aLn3hbT0Jf3aFxH3uiWJi0UE3ahLhNWiDTU +PT1VtQ1fSt/ZpJM6KduR1aBFYcEyPIE/MQq9Y2jcYKrIyc4OqkZBwVOFZtRx8cQ7 +tsy1iY3FJjKllp1VdDKRtPs1oKqyX3k446iYryjZs3cDbWV+H5MSwxh7yqw+j5qE +XfvhaImoDFAEisep+w2i7nu80D5uNhFr9bHC/MnRCVlzO1HfrNNns1Oncey1ebJL +PSmpYAiArym6m6fIM9EtTtUrkNUmU0LeqfAaDUmGgtufmmExOtH7/pEuOfbCzoO9 +ZX+TMBRMlOGg55Wc+J597AyEg9mqGKqgoPF8Si2qEElOFYVlaZ88YGPaXKLKI2DA +T7LXYlf+njThf948xsgM41JxE2VG6Ibo3ucHXFEF+QVk+Arrv8jQEGNc1n6cv4Ep +ICoWwHAWN4gvACBi6V0C8Mb5V9cRL6hkCsVZUyOGOKm580qiakxmUe+xGHuMW7Cs +208L5Lsgnn4ynRKLT0yfup73XdQzut/Bkws4ECdDSoSH45VNMR7bdjoGsWkCn5n/ +gbU8PWTPYL907KLpwRBx8fvmOgP2lLBj2gmwyJeowRlzc1MLtsUnH/7H2YSQJgbX +0ZKIRHASwjpnlL4uhp1QMn9Nj9H++MiJ59q7kUmZBJstlbyAw11HkP4cwCIccNO4 +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes256.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes256.pem new file mode 100644 index 0000000000000..64c765456a1ff --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-aes256.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,134008CB231A5AD0F27EB8F6FB18A873 + +aJRdAed/XZ+Rl6/s/TwOw8rj+sw2ficvnKjCVJj5wt0+qD2NumPpkXmK9J0+SP21 +Mzzm8H0pQRWrI78vwfFXUxUmQMAuavB9k8HuvZtj1b4GvfHrT/BBbs5wS0RPbE6N +xZuvTvr5UMYFsP85lotcooau3CLtkVXz9ucMQv9v1r2dBvq/7owzl3M+AxhS1oU2 +f8qc3Q411RhVQl29tZha9gidfzBvOO2HH8AqjHxWMHw448oo/b+fXVrpezD1LkmP +0JxP+kJDt1KCiwXj7oRAMaHbHemA2HS713TK+6HammQroF0PCB33Dasy6zaPmP5G +HiJAHvBiblc+vCT7D1lUQCmbjRmeoSESq/P3l8Jhag+wT8SSm5nGaiX7aYHqc00Q +17Gw5e8/iWOU+c3DjCH5qXZFxVrpJgSvVBrrnF3y4sQCG41QpPC7X3mWYWLHZ5vX +GxcI4f1aJ+jECDTvdpE9KL6ncZ05p3A3wr+FqrDPJTb+S1mpD0f6lRhnKILXK83N ++EbRVRTCH5QIx5ZepX28ykuZQa6vHGtnL9WXLX4ZgAIe2abMA5hNs72Hi47LUrss +lA3gMdydKA/WtoimBLqb9brEy9qFsP/2YatKnyXYkjeCgtTQ5LELWSqnFkzQ51wk +VPhT8SqXcPIe9rrNmf7xwJvcZ0IS4tEkT+TovFAs1lo86bCx7VKfWfxcWG/FvW1L +5/1tU4uhpXLOjhOvWOx56zqxt9RORMw3SEh3At4vVHqT2xQAStT1d9QU0/QiM3EE +pBf9uQjRfzlwXph6Gs8XmQYLjSwHurT8hrkoa4/czhE4v7BTst+q6fB3gtxOgV6z +GVBsRK0Lz0ldd56UvnzyChUpE8EFE/Kv6P7T8cgTPnTcGchO4hcKyC31doAFn0pU +LURMC5szvRUEHbPriz9/9qeHBLFMAmGkCfXpwjNoynsKA7/VlAd/44CP82Ljd8Fb +PdwXjz8JNAL+gg3q8Xz4S+z6ZNXVJ1U9GDxjesp7QRbhl1J/ynsGyqIADUmPKjyk +8yFihQYBiZdgiYaOBl9F2X0SINUKaANmVO7HJG+WbPs68fcObfFHRWugC7FljY+b +Az6tNhkKVerCXBEMsZ9XNY05SsyAvcKsWcJbxon9ecIeu7/N8k9eseUL0xQg1oQX +L6wjgmS2ckpPnKVFPXhujZb45PtYEA2ObGd6fPV+82cSgfFM6sPorAmmFhThBXa+ +nE8o72MPVvdUFas3Fs7YugxeFTh9jO4zp/3XA+fFfpxPQbwWjnjxS87OAB+AF6iy +Ul/jZP46kDOnyLdMLvSf5Oq8A73bdGa/09ODsoWjrXlyYmfUZPKPGQ5Hbs5cUSvs +GciJvb3o3OYfSjkn6DVF95f53TiJ9pbGY+zG85f/F3BwbqpRmNYLyxvl4ZzjLs+U +PN24gC78ROzgvHAhY0Ta6PQw8SN5FEoQGmOQT2otZc+Apu1J1Z85mpxd0dYPh29m +kWvx13gZSGxCvNttqfqcRQJTOerQ4PRIyMDJG/sou8hDU51X9USAfjs1spuE6X/a +PIGNpM2TIOaqU/IIFJrGx01vVBhYGvYF8D/q+wwwnjJGYQl7Hscc+JdFmhWE0T2R +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-unprotected.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-unprotected.pem new file mode 100644 index 0000000000000..1602461b11517 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode-unprotected.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA3rGZ1QbsW0+MuyrSLmMfDFKtLBkIFW8V0gRuurFg1PUKKNR1 +Mq2tMVwjjYETAU/UY0iKZOzjgvYPKhDTYBTte/WHR1ZK4CYVv7TQX/gtFQG/ge/c +7u0sLch9p7fbd+/HZiLS/rBEZDIohvgUvzvnA8+OIYnw4kuxKo/5iboAIS41klMg +/lATm8V71LMY68inht71/ZkQoAHKgcR9z4yNYvQ1WqKG8DG8KROXltll3sTrKbl5 +zJhn660es/1ZnR6nvwt6xnSTl/mNHMjkfv1bs4rJ/py3qPxicdoSIn/KyojUcgHV +F38fuAy2CQTdjVG5fWj9iz+mQvLm3+qsIYQdFwIDAQABAoIBAQCrsz+qkjX2iTU2 +smNvxJkRxUUj9QDYlde7IBPH+i9wwqXcqULbmjEpF6OJ3PUsU8Kr8ihSfJtGRDWY +93QiZsBcjQiKpiwA+xxVvTHUbONFiR2BL9GmnlMr0aOghw++P6/8Pel02a9BmbLJ +rclBUQxjUcqCbXq+4CFvcaay3Chxa2E5nWgF2zpuVvyt/OOBAaLk2M2J2WFkIwd4 +0/4OXSInNwLS2+WHxpaWl12++MUzlAamNbiqJxogwodGl10ZdlHMq3uhVrJTTcuH +AAF5uv+Rk+ojKO8A10H7b7citiLMzyBXDhusXAGiY4GecLzN3IdKjYUIuLy+3OAZ +uSUvZnqBAoGBAPPCLWla35tqLF/+v3wPMGMcWFOUUEcjoC7swV6EZCEspyyWi1Y9 +oBTkMVUHlCvvsSukdfNkUGrTiXFhU1sJiC9wnIQsJ514D7zHOdYhSmPeA23b60Wr +T/HObEpOyEJaUt2lWu3+wwqnsYIK+dz2MrLoi4qy/p/SNaGYlMabEuv3AoGBAOng +nA7IUM2+1EH1sv/wUTKe7ugw7qvueN/JCSBbD19PwhslrIhWBGnPFR+42hqH/fY+ +2bnm3m+dE1+e2QgRCti2h3uO+IIJGBhkqKN1VgZ+OANT+QA2kRkcCeP7u/agINJX +Z8sRR1rVvlm59dQ6DoONK5fgzpxQSVvm1iZ+Gs/hAoGBAM1nbg7eZZi34kbOCxuG +TRbnVsKvMuqPabbBCL0VwK53yzN2dFmquk+AbtWg+kn/xALrpf0AJgeu0524Dp6j +LKAVO77g2k0GU/SWyPTYvq/i8G7Np7bgghBYCsafFn9bT4K4iSQNztsWrizQBPce +e2BpNtUcuhWA9HKEebHWER7HAoGBAJRkDOBcPNiuUaKgMDiv0/UYXmE4Wtb/8fWb +aoz6+d/xjSkIWFYQrbAs0ixUbx4SVxKcgiyvzUZoFVjAQtgZaBZkicXdytNGzeD8 +TPuZgUGRaBzEdOKrvJh/786VdIdgxjJvfs2bYvjBUYtAsDc9tuPE+HnfC3imgEUt +S4cU0BJBAoGARbDzZHxlsj+M7IMxbN7TIF5yIJdy4fr5xCM1WpAEn90Wa0rnCYQO +9d7uS/WAeuMD3VSaeGGSKBJh3mz/SSbdX/YIXFpOn31FQUCT5lrkWwfwVPEiPpIL +X+dCZ4s235uaqEBLdlHRlG8FNz9frc+EpwbyGd29opzfh8HZuaJzgZc= +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.crt b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.crt new file mode 100644 index 0000000000000..08c160bcea5ff --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID0zCCArugAwIBAgIJALi5bDfjMszLMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNV +BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3Rp +Y3NlYXJjaCBUZXN0IE5vZGUwHhcNMTUwOTIzMTg1MjU3WhcNMTkwOTIyMTg1MjU3 +WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsTDWVsYXN0aWNzZWFyY2gxIDAeBgNV +BAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3rGZ1QbsW0+MuyrSLmMfDFKtLBkIFW8V0gRuurFg1PUKKNR1 +Mq2tMVwjjYETAU/UY0iKZOzjgvYPKhDTYBTte/WHR1ZK4CYVv7TQX/gtFQG/ge/c +7u0sLch9p7fbd+/HZiLS/rBEZDIohvgUvzvnA8+OIYnw4kuxKo/5iboAIS41klMg +/lATm8V71LMY68inht71/ZkQoAHKgcR9z4yNYvQ1WqKG8DG8KROXltll3sTrKbl5 +zJhn660es/1ZnR6nvwt6xnSTl/mNHMjkfv1bs4rJ/py3qPxicdoSIn/KyojUcgHV +F38fuAy2CQTdjVG5fWj9iz+mQvLm3+qsIYQdFwIDAQABo4G/MIG8MAkGA1UdEwQC +MAAwHQYDVR0OBBYEFEMMWLWQi/g83PzlHYqAVnty5L7HMIGPBgNVHREEgYcwgYSC +CWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggpsb2NhbGhvc3Q0ghds +b2NhbGhvc3Q0LmxvY2FsZG9tYWluNIIKbG9jYWxob3N0NoIXbG9jYWxob3N0Ni5s +b2NhbGRvbWFpbjaHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQEL +BQADggEBAMjGGXT8Nt1tbl2GkiKtmiuGE2Ej66YuZ37WSJViaRNDVHLlg87TCcHe +k2rdO+6sFqQbbzEfwQ05T7xGmVu7tm54HwKMRugoQ3wct0bQC5wEWYN+oMDvSyO6 +M28mZwWb4VtR2IRyWP+ve5DHwTM9mxWa6rBlGzsQqH6YkJpZojzqk/mQTug+Y8aE +mVoqRIPMHq9ob+S9qd5lp09+MtYpwPfTPx/NN+xMEooXWW/ARfpGhWPkg/FuCu4z +1tFmCqHgNcWirzMm3dQpF78muE9ng6OB2MXQwL4VgnVkxmlZNHbkR2v/t8MyZJxC +y4g6cTMM3S/UMt5/+aIB2JAuMKyuD+A= +-----END CERTIFICATE----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.jks b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.jks new file mode 100644 index 0000000000000000000000000000000000000000..ebe6146124e8fd607e46a1a3129bdf9b4de0370d GIT binary patch literal 9360 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 literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.pem new file mode 100644 index 0000000000000..5a67e1033440d --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,9D867F7E0C94D013 + +dVoVCjPeg1wgS7rVtOvGfQcrZyLkx393aWRnFq45tbjKBVuITtJ9vI7o4QXOV/15 +Gnb6WhXGIdWrzsxEAd46K6hIuNSISd4Emsx6c2Q5hTqWXXfexbOZBNfTtXtdJPnJ +1jAaikhtztLo3JSLTKNY5sNxd+XbaQyYVUWvueK6zOaIIMETvB+VPVFd9i1ROibk +Sgdtyj01KjkoalifqK/tA0CIYNKL0S6/eoK3UhAlpIprlpV+cnXa940C6bjLeJPt +PMAGGp5RrplxSgrSerw3I9DOWkHGtpqzIka3XneNUXJP8k4HUJ+aZkGH2ZILKS8d +4KMIb+KZSpHEGn+6uGccWLtZZmAjWJrDw56JbQtSHdRYLBRSOjLbTvQoPu/2Hpli +7HOxbotlvjptMunncq5aqK57SHA1dh0cwF7J3LUmGFJ67eoz+VV3b5qMn4MopSeI +mS16Ydd3nGpjSrln/elM0CQxqWfcOAXRZpDpFUQoXcBrLVzvz2DBl/0CrTRLhgzi +CO+5/IVcBWRlYpRNGgjjP7q0j6URID3jk5J06fYQXmBiwQT5j+GZqqzpMCJ9mIy2 +1O9SN1hebJnIcEU+E0njn/MGjlYdPywhaCy8pqElp6Q8TUEJpwLRFO/owCoBet/n +ZmCXUjfCGhc1pWHufFcDEQ6xMgEWWY/tdwCZeSU7EhErTjCbfupg+55A5fpDml0m +3wH4CFcuRjlqyx6Ywixm1ATeitDtJl5HQTw6b8OtEXwSgRmZ0eSqSRVk9QbVS7gu +IpQe09/Zimb5HzjZqZ3fdqHlcW4xax8hyJeyIvF5ZJ57eY8CBvu/wP2GDn26QnvF +xQqdfDbq1H4JmpwUHpbFwBoQK4Q6WFd1z4EA9bRQeo3H9PoqoOwMDjzajwLRF7b7 +q6tYH/n9PyHwdf1c4fFwgSmL1toXGfKlA9hjIaLsRSDD6srT5EdUk78bsnddwI51 +tu7C7P4JG+h1VdRNMNTlqtileWsIE7Nn2A1OkcUxZdF5mamENpDpJcHePLto6c8q +FKiwyFMsxhgsj6HK2HqO+UA4sX5Ni4oHwiPmb//EZLn045M5i1AN26KosJmb8++D +sgR5reWRy+UqJCTYblVg+7Dx++ggUnfxVyQEsWmw5r5f4KU5wXBkvoVMGtPNa9DE +n/uLtObD1qkNL38pRsr2OGRchYCgEoKGqEISBP4knfGXLOlWiW/246j9QzI97r1u +tvy7fKg28G7AUz9l6bpewsPHefBUeRQeieP9eJINaEpxkF/w2RpKDLpQjWxwDDOM +s+D0mrBMJve17AmJ8rMw6dIQPZYNZ88/jz1uQuUwQ2YlbmtZbCG81k9YMFGEU9XS +cyhJxj8hvYnt2PR5Z9/cJPyWOs0m/ufOeeQQ8SnU/lzmrQnpzUd2Z6p5i/B7LdRP +n1kX+l1qynuPnjvBz4nJQE0p6nzW8RyCDSniC9mtYtZmhgC8icqxgbvS7uEOBIYJ +NbK+0bEETTO34iY/JVTIqLOw3iQZYMeUpxpj6Phgx/oooxMTquMecPKNgeVtaBst +qjTNPX0ti1/HYpZqzYi8SV8YjHSJWCVMsZjKPr3W/HIcCKqYoIfgzi83Ha2KMQx6 +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode_with_bagattrs.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode_with_bagattrs.pem new file mode 100644 index 0000000000000..ce8299cd070fc --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode_with_bagattrs.pem @@ -0,0 +1,32 @@ +Bag Attributes + friendlyName: testnode_rsa + localKeyID: 54 69 6D 65 20 31 35 32 35 33 33 36 38 32 39 33 39 37 +Key Attributes: +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDesZnVBuxbT4y7 +KtIuYx8MUq0sGQgVbxXSBG66sWDU9Qoo1HUyra0xXCONgRMBT9RjSIpk7OOC9g8q +ENNgFO179YdHVkrgJhW/tNBf+C0VAb+B79zu7SwtyH2nt9t378dmItL+sERkMiiG ++BS/O+cDz44hifDiS7Eqj/mJugAhLjWSUyD+UBObxXvUsxjryKeG3vX9mRCgAcqB +xH3PjI1i9DVaoobwMbwpE5eW2WXexOspuXnMmGfrrR6z/VmdHqe/C3rGdJOX+Y0c +yOR+/Vuzisn+nLeo/GJx2hIif8rKiNRyAdUXfx+4DLYJBN2NUbl9aP2LP6ZC8ubf +6qwhhB0XAgMBAAECggEBAKuzP6qSNfaJNTayY2/EmRHFRSP1ANiV17sgE8f6L3DC +pdypQtuaMSkXo4nc9SxTwqvyKFJ8m0ZENZj3dCJmwFyNCIqmLAD7HFW9MdRs40WJ +HYEv0aaeUyvRo6CHD74/r/w96XTZr0GZssmtyUFRDGNRyoJter7gIW9xprLcKHFr +YTmdaAXbOm5W/K3844EBouTYzYnZYWQjB3jT/g5dIic3AtLb5YfGlpaXXb74xTOU +BqY1uKonGiDCh0aXXRl2Ucyre6FWslNNy4cAAXm6/5GT6iMo7wDXQftvtyK2IszP +IFcOG6xcAaJjgZ5wvM3ch0qNhQi4vL7c4Bm5JS9meoECgYEA88ItaVrfm2osX/6/ +fA8wYxxYU5RQRyOgLuzBXoRkISynLJaLVj2gFOQxVQeUK++xK6R182RQatOJcWFT +WwmIL3CchCwnnXgPvMc51iFKY94DbdvrRatP8c5sSk7IQlpS3aVa7f7DCqexggr5 +3PYysuiLirL+n9I1oZiUxpsS6/cCgYEA6eCcDshQzb7UQfWy//BRMp7u6DDuq+54 +38kJIFsPX0/CGyWsiFYEac8VH7jaGof99j7Zuebeb50TX57ZCBEK2LaHe474ggkY +GGSoo3VWBn44A1P5ADaRGRwJ4/u79qAg0ldnyxFHWtW+Wbn11DoOg40rl+DOnFBJ +W+bWJn4az+ECgYEAzWduDt5lmLfiRs4LG4ZNFudWwq8y6o9ptsEIvRXArnfLM3Z0 +Waq6T4Bu1aD6Sf/EAuul/QAmB67TnbgOnqMsoBU7vuDaTQZT9JbI9Ni+r+Lwbs2n +tuCCEFgKxp8Wf1tPgriJJA3O2xauLNAE9x57YGk21Ry6FYD0coR5sdYRHscCgYEA +lGQM4Fw82K5RoqAwOK/T9RheYTha1v/x9ZtqjPr53/GNKQhYVhCtsCzSLFRvHhJX +EpyCLK/NRmgVWMBC2BloFmSJxd3K00bN4PxM+5mBQZFoHMR04qu8mH/vzpV0h2DG +Mm9+zZti+MFRi0CwNz2248T4ed8LeKaARS1LhxTQEkECgYBFsPNkfGWyP4zsgzFs +3tMgXnIgl3Lh+vnEIzVakASf3RZrSucJhA713u5L9YB64wPdVJp4YZIoEmHebP9J +Jt1f9ghcWk6ffUVBQJPmWuRbB/BU8SI+kgtf50Jnizbfm5qoQEt2UdGUbwU3P1+t +z4SnBvIZ3b2inN+Hwdm5onOBlw== +-----END PRIVATE KEY----- From ca341f6e6f16c220e98a62602d36b9da4b224aa7 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Thu, 10 Jan 2019 16:15:59 +1100 Subject: [PATCH 2/8] Fix copyright on DerParser --- .../elasticsearch/common/ssl/DerParser.java | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DerParser.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DerParser.java index 7401ee8ee58d5..da650369d508c 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DerParser.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/DerParser.java @@ -1,20 +1,18 @@ /* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + Copyright (c) 1998-2010 AOL Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ package org.elasticsearch.common.ssl; From 23610173acdd4f4ded0edd395520d18bb0c415d5 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Thu, 10 Jan 2019 18:12:47 +1100 Subject: [PATCH 3/8] Remove irrelevant assert from test We can't control the message, and it varies by JRE release so there's no good reason to assert on it. --- .../java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java index 00e15443b6a42..8764fe8fa411e 100644 --- a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java @@ -197,7 +197,6 @@ private void assertBadKeyStore(StoreKeyConfig keyConfig, Path key) { assertThat(exception.getMessage(), containsString("keystore")); assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); assertThat(exception.getCause(), instanceOf(IOException.class)); - assertThat(exception.getCause().getMessage(), containsString("tampered")); } private void assertFileNotFound(StoreKeyConfig keyConfig, Path file) { From 82d0b84ee319021f77b24a533db8f7a3f3a8c633 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Fri, 11 Jan 2019 15:20:06 +1100 Subject: [PATCH 4/8] Address feedback --- libs/ssl-config/build.gradle | 3 +- .../common/ssl/KeyStoreUtil.java | 24 ++++++++------ .../common/ssl/PemKeyConfig.java | 8 ++--- .../common/ssl/PemTrustConfig.java | 2 +- .../ssl/SslClientAuthenticationMode.java | 14 +++++--- .../common/ssl/SslConfiguration.java | 32 +++++++++++-------- .../common/ssl/SslConfigurationLoader.java | 13 +++++--- .../common/ssl/SslVerificationMode.java | 14 +++++--- .../common/ssl/StoreKeyConfigTests.java | 3 +- .../common/ssl/StoreTrustConfigTests.java | 4 +-- .../src/test/resources/certs/README.txt | 26 +++++++++------ 11 files changed, 84 insertions(+), 59 deletions(-) diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle index ba9c8675b478e..d1f13b36bd729 100644 --- a/libs/ssl-config/build.gradle +++ b/libs/ssl-config/build.gradle @@ -22,7 +22,7 @@ archivesBaseName = 'elasticsearch-ssl-config' dependencies { compile "org.elasticsearch:elasticsearch-core:${version}" - if (isEclipse == false || project.path == ":libs:dissect-tests") { + if (isEclipse == false || project.path == ":libs:ssl-config-tests") { testCompile("org.elasticsearch.test:framework:${version}") { exclude group: 'org.elasticsearch', module: 'elasticsearch-ssl-config' } @@ -33,7 +33,6 @@ dependencies { testCompile "org.hamcrest:hamcrest-all:${versions.hamcrest}" } -jarHell.enabled = false forbiddenApisMain { replaceSignatureFiles 'jdk-signatures' } diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java index 6eaa32ddf5170..0a2526c7f7cfa 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java @@ -27,11 +27,9 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyStore; @@ -71,15 +69,16 @@ static String inferKeyStoreType(Path path) { * @throws GeneralSecurityException If there is a problem with the keystore contents */ static KeyStore readKeyStore(Path path, String type, char[] password) throws GeneralSecurityException { + if (Files.notExists(path)) { + throw new SslConfigException("cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + + "] because the file does not exist"); + } try { KeyStore keyStore = KeyStore.getInstance(type); try (InputStream in = Files.newInputStream(path)) { keyStore.load(in, password); } return keyStore; - } catch (FileNotFoundException | NoSuchFileException e) { - throw new SslConfigException("cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() - + "] because the file does not exist", e); } catch (IOException e) { throw new SslConfigException("cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + "] - " + e.getMessage(), e); @@ -87,18 +86,25 @@ static KeyStore readKeyStore(Path path, String type, char[] password) throws Gen } /** - * Construct an in-memory keystore for the provided certificates and the associated private key. + * Construct an in-memory keystore with a single key entry. + * @param certificateChain A certificate chain (ordered from subject to issuer) + * @param privateKey The private key that corresponds to the subject certificate (index 0 of {@code certificateChain}) + * @param password The password for the private key * * @throws GeneralSecurityException If there is a problem with the provided certificates/key */ - static KeyStore buildKeyStore(Collection certificates, PrivateKey privateKey, char[] password) + static KeyStore buildKeyStore(Collection certificateChain, PrivateKey privateKey, char[] password) throws GeneralSecurityException { KeyStore keyStore = buildNewKeyStore(); - keyStore.setKeyEntry("key", privateKey, password, certificates.toArray(new Certificate[0])); + keyStore.setKeyEntry("key", privateKey, password, certificateChain.toArray(new Certificate[0])); return keyStore; } - static KeyStore buildKeyStore(Iterable certificates) throws GeneralSecurityException { + /** + * Construct an in-memory keystore with multiple trusted cert entries. + * @param certificates The root certificates to trust + */ + static KeyStore buildTrustStore(Iterable certificates) throws GeneralSecurityException { assert certificates != null : "Cannot create keystore with null certificates"; KeyStore store = buildNewKeyStore(); int counter = 0; diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java index 5c5d0d7c77710..dd091e0a22218 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java @@ -56,14 +56,12 @@ public Collection getDependentFiles() { @Override public X509ExtendedKeyManager createKeyManager() { + PrivateKey privateKey = getPrivateKey(); + List certificates = getCertificates(); try { - PrivateKey privateKey = getPrivateKey(); - List certificates = getCertificates(); final KeyStore keyStore = KeyStoreUtil.buildKeyStore(certificates, privateKey, keyPassword); return KeyStoreUtil.createKeyManager(keyStore, keyPassword, KeyManagerFactory.getDefaultAlgorithm()); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { + } catch (GeneralSecurityException e) { throw new SslConfigException("failed to load a KeyManager for certificate/key pair [" + certificate + "], [" + key + "]", e); } } diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java index a436a7969f06f..f3cf8cd8bd7aa 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java @@ -71,7 +71,7 @@ public Collection getDependentFiles() { public X509ExtendedTrustManager createTrustManager() { try { final List certificates = loadCertificates(); - KeyStore store = KeyStoreUtil.buildKeyStore(certificates); + KeyStore store = KeyStoreUtil.buildTrustStore(certificates); return KeyStoreUtil.createTrustManager(store, TrustManagerFactory.getDefaultAlgorithm()); } catch (GeneralSecurityException e) { throw new SslConfigException("cannot create trust using PEM certificates [" + caPathsAsString() + "]", e); diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslClientAuthenticationMode.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslClientAuthenticationMode.java index e46555be702c4..8a972b6c78826 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslClientAuthenticationMode.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslClientAuthenticationMode.java @@ -19,6 +19,7 @@ package org.elasticsearch.common.ssl; import javax.net.ssl.SSLParameters; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; @@ -78,11 +79,14 @@ public void configure(SSLParameters sslParameters) { */ public abstract void configure(SSLParameters sslParameters); - private static final Map LOOKUP = new LinkedHashMap<>(3); - static { - LOOKUP.put("none", NONE); - LOOKUP.put("optional", OPTIONAL); - LOOKUP.put("required", REQUIRED); + private static final Map LOOKUP = Collections.unmodifiableMap(buildLookup()); + + static Map buildLookup() { + final Map map = new LinkedHashMap<>(3); + map.put("none", NONE); + map.put("optional", OPTIONAL); + map.put("required", REQUIRED); + return map; } public static SslClientAuthenticationMode parse(String value) { diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java index 1fe6277e1bca7..146ba916b6b07 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java @@ -23,6 +23,7 @@ import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; import java.nio.file.Path; +import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -48,10 +49,16 @@ public class SslConfiguration { public SslConfiguration(SslTrustConfig trustConfig, SslKeyConfig keyConfig, SslVerificationMode verificationMode, SslClientAuthenticationMode clientAuth, List ciphers, List supportedProtocols) { - this.trustConfig = trustConfig; - this.keyConfig = keyConfig; - this.verificationMode = verificationMode; - this.clientAuth = clientAuth; + if (ciphers == null || ciphers.isEmpty()) { + throw new SslConfigException("cannot configure SSL/TLS without any supported cipher suites"); + } + if (supportedProtocols == null || supportedProtocols.isEmpty()) { + throw new SslConfigException("cannot configure SSL/TLS without any supported protocols"); + } + this.trustConfig = Objects.requireNonNull(trustConfig, "trust config cannot be null"); + this.keyConfig = Objects.requireNonNull(keyConfig, "key config cannot be null"); + this.verificationMode = Objects.requireNonNull(verificationMode, "verification mode cannot be null"); + this.clientAuth = Objects.requireNonNull(clientAuth, "client authentication cannot be null"); this.ciphers = Collections.unmodifiableList(ciphers); this.supportedProtocols = Collections.unmodifiableList(supportedProtocols); } @@ -98,27 +105,24 @@ public Collection getDependentFiles() { * return ssl-contexts with different configurations. */ public SSLContext createSslContext() { + final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); + final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); try { - final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); - final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); - SSLContext sslContext = SSLContext.getInstance(contextAlgorithm()); + SSLContext sslContext = SSLContext.getInstance(contextProtocol()); sslContext.init(new X509ExtendedKeyManager[] { keyManager }, new X509ExtendedTrustManager[] { trustManager }, null); return sslContext; - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { + } catch (GeneralSecurityException e) { throw new SslConfigException("cannot create ssl context", e); } } /** - * Picks the best (highest security / most recent standard) SSL/TLS algorithm that is supported by the + * Picks the best (highest security / most recent standard) SSL/TLS protocol (/version) that is supported by the * {@link #getSupportedProtocols() configured protocols}. */ - private String contextAlgorithm() { + private String contextProtocol() { if (supportedProtocols.isEmpty()) { - // shouldn't happen... - return "TLSv1.2"; + throw new SslConfigException("no SSL/TLS protocols have been configured"); } for (String tryProtocol : Arrays.asList("TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3")) { if (supportedProtocols.contains(tryProtocol)) { diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java index 8af6662b43b05..186d20b1ea858 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java @@ -160,7 +160,7 @@ public void setDefaultProtocols(List defaultProtocols) { * The setting should be returned as a string, and this class will convert it to the relevant type. * * @throws Exception If a {@link RuntimeException} is thrown, it will be rethrown unwrapped. All checked exceptions are wrapped in - * {@link SslConfigException} before being rethrown. + * {@link SslConfigException} before being rethrown. */ protected abstract String getSettingAsString(String key) throws Exception; @@ -170,7 +170,7 @@ public void setDefaultProtocols(List defaultProtocols) { * {@link SslConfigurationKeys#getSecureStringKeys() secure} settings. * * @throws Exception If a {@link RuntimeException} is thrown, it will be rethrown unwrapped. All checked exceptions are wrapped in - * {@link SslConfigException} before being rethrown. + * {@link SslConfigException} before being rethrown. */ protected abstract char[] getSecureSetting(String key) throws Exception; @@ -181,7 +181,7 @@ public void setDefaultProtocols(List defaultProtocols) { * The setting should be returned as a list of strings, and this class will convert the values to the relevant type. * * @throws Exception If a {@link RuntimeException} is thrown, it will be rethrown unwrapped. All checked exceptions are wrapped in - * {@link SslConfigException} before being rethrown. + * {@link SslConfigException} before being rethrown. */ protected abstract List getSettingAsList(String key) throws Exception; @@ -190,7 +190,6 @@ public void setDefaultProtocols(List defaultProtocols) { * * @param basePath The base path to use for any settings that represent file paths. Typically points to the Elasticsearch * configuration directory. - * * @throws SslConfigException For any problems with the configuration, or with loading the required SSL classes. */ public SslConfiguration load(Path basePath) { @@ -203,6 +202,12 @@ public SslConfiguration load(Path basePath) { final SslTrustConfig trustConfig = buildTrustConfig(basePath, verificationMode); final SslKeyConfig keyConfig = buildKeyConfig(basePath); + if (protocols == null || protocols.isEmpty()) { + throw new SslConfigException("no protocols configured in [" + settingPrefix + PROTOCOLS + "]"); + } + if (ciphers == null || ciphers.isEmpty()) { + throw new SslConfigException("no cipher suites configured in [" + settingPrefix + CIPHERS + "]"); + } return new SslConfiguration(trustConfig, keyConfig, verificationMode, clientAuth, ciphers, protocols); } diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslVerificationMode.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslVerificationMode.java index 8771f80173561..eee6e9cb2d6a1 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslVerificationMode.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslVerificationMode.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.common.ssl; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; @@ -81,11 +82,14 @@ public boolean isCertificateVerificationEnabled() { */ public abstract boolean isCertificateVerificationEnabled(); - private static final Map LOOKUP = new LinkedHashMap<>(3); - static { - LOOKUP.put("none", NONE); - LOOKUP.put("certificate", CERTIFICATE); - LOOKUP.put("full", FULL); + private static final Map LOOKUP = Collections.unmodifiableMap(buildLookup()); + + private static Map buildLookup() { + Map map = new LinkedHashMap<>(3); + map.put("none", NONE); + map.put("certificate", CERTIFICATE); + map.put("full", FULL); + return map; } public static SslVerificationMode parse(String value) { diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java index 8764fe8fa411e..f5fcc16c6a023 100644 --- a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java @@ -26,7 +26,6 @@ import javax.net.ssl.X509ExtendedKeyManager; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.security.GeneralSecurityException; @@ -204,7 +203,7 @@ private void assertFileNotFound(StoreKeyConfig keyConfig, Path file) { assertThat(exception.getMessage(), containsString("keystore")); assertThat(exception.getMessage(), containsString(file.toAbsolutePath().toString())); assertThat(exception.getMessage(), containsString("does not exist")); - assertThat(exception.getCause(), instanceOf(NoSuchFileException.class)); + assertThat(exception.getCause(), nullValue()); } private void assertNoPrivateKeyEntries(StoreKeyConfig keyConfig, Path file) { diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreTrustConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreTrustConfigTests.java index 355e5cd6c8c79..207bf9179415f 100644 --- a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreTrustConfigTests.java +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreTrustConfigTests.java @@ -26,7 +26,6 @@ import javax.net.ssl.X509ExtendedTrustManager; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; @@ -37,6 +36,7 @@ import java.util.stream.Stream; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.nullValue; public class StoreTrustConfigTests extends ESTestCase { @@ -149,7 +149,7 @@ private void assertFileNotFound(StoreTrustConfig trustConfig, Path file) { assertThat(exception.getMessage(), Matchers.containsString("file does not exist")); assertThat(exception.getMessage(), Matchers.containsString("keystore")); assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); - assertThat(exception.getCause(), Matchers.instanceOf(NoSuchFileException.class)); + assertThat(exception.getCause(), nullValue()); } private void assertPasswordIsIncorrect(StoreTrustConfig trustConfig, Path key) { diff --git a/libs/ssl-config/src/test/resources/certs/README.txt b/libs/ssl-config/src/test/resources/certs/README.txt index c225bbbf3400c..a04a31011b4dd 100644 --- a/libs/ssl-config/src/test/resources/certs/README.txt +++ b/libs/ssl-config/src/test/resources/certs/README.txt @@ -1,32 +1,38 @@ -1. Create first CA PEM ("ca1") +#!/bin/bash +# +# This is README describes how the certificates in this directory were created. +# This file can also be executed as a script +# + +# 1. Create first CA PEM ("ca1") elasticsearch-certutil ca --pem --out ca1.zip --days 9999 --ca-dn "CN=Test CA 1" unzip ca1.zip mv ca ca1 -2. Create first CA PEM ("ca2") +# 2. Create first CA PEM ("ca2") elasticsearch-certutil ca --pem --out ca2.zip --days 9999 --ca-dn "CN=Test CA 2" unzip ca2.zip mv ca ca2 -3. Create first CA PEM ("ca3") +# 3. Create first CA PEM ("ca3") elasticsearch-certutil ca --pem --out ca3.zip --days 9999 --ca-dn "CN=Test CA 3" unzip ca3.zip mv ca ca3 -4. Create "cert1" PEM +# 4. Create "cert1" PEM elasticsearch-certutil cert --pem --out cert1.zip --name cert1 --ip 127.0.0.1 --dns localhost --days 9999 --ca-key ca1/ca.key --ca-cert ca1/ca.crt unzip cert1.zip -5. Create "cert2" PEM (same as cert1, but with a password) +# 5. Create "cert2" PEM (same as cert1, but with a password) elasticsearch-certutil cert --pem --out cert2.zip --name cert2 --ip 127.0.0.1 --dns localhost --days 9999 --ca-key ca1/ca.key --ca-cert ca1/ca.crt --pass "c2-pass" unzip cert2.zip -6. Convert CAs to PKCS#12 +# 6. Convert CAs to PKCS#12 for n in 1 2 3 do @@ -34,21 +40,21 @@ do keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.p12 -storetype PKCS12 -storepass p12-pass -v done -7. Convert CAs to JKS +# 7. Convert CAs to JKS for n in 1 2 3 do keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.jks -storetype jks -storepass jks-pass -v done -8. Convert Certs to PKCS#12 +# 8. Convert Certs to PKCS#12 for Cert in cert1 cert2 do openssl pkcs12 -export -out $Cert/$Cert.p12 -inkey $Cert/$Cert.key -in $Cert/$Cert.crt -name $Cert -passout pass:p12-pass done -9. Import Certs into single PKCS#12 keystore +# 9. Import Certs into single PKCS#12 keystore for Cert in cert1 cert2 do @@ -57,7 +63,7 @@ do -destkeystore cert-all/certs.p12 -deststoretype PKCS12 -deststorepass p12-pass done -10. Import Certs into single JKS keystore with separate key-password +# 10. Import Certs into single JKS keystore with separate key-password for Cert in cert1 cert2 do From cde210fa0857a33bd27bc1103d996ed72a798517 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Fri, 11 Jan 2019 16:33:39 +1100 Subject: [PATCH 5/8] Fix tests to run on BC-FIPS JVM --- .../common/ssl/PemTrustConfig.java | 4 +++- .../elasticsearch/common/ssl/PemUtils.java | 6 +++++- .../common/ssl/PemTrustConfigTests.java | 20 ++++++++++++++++++- .../ssl/SslConfigurationLoaderTests.java | 1 + 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java index f3cf8cd8bd7aa..fea5de0f31613 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java @@ -80,7 +80,9 @@ public X509ExtendedTrustManager createTrustManager() { private List loadCertificates() throws CertificateException { try { - return PemUtils.readCertificates(this.certificateAuthorities); + final List certificates = PemUtils.readCertificates(this.certificateAuthorities); + System.err.printf("Loaded certificates: %s from %s\n", certificates, this.certificateAuthorities); + return certificates; } catch (FileNotFoundException | NoSuchFileException e) { throw new SslConfigException("cannot configure trust using PEM certificates [" + caPathsAsString() + "] because one or more files do not exist", e); diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java index cf137312273da..aca7ba56b2ae9 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java @@ -593,7 +593,11 @@ static List readCertificates(Collection certPaths) throws Cer List certificates = new ArrayList<>(certPaths.size()); for (Path path : certPaths) { try (InputStream input = Files.newInputStream(path)) { - certificates.addAll(certFactory.generateCertificates(input)); + final Collection parsed = certFactory.generateCertificates(input); + if (parsed.isEmpty()) { + throw new SslConfigException("failed to parse any certificates from [" + path.toAbsolutePath() + "]"); + } + certificates.addAll(parsed); } } return certificates; diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemTrustConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemTrustConfigTests.java index 770d09589ad5e..3d78976e1e83b 100644 --- a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemTrustConfigTests.java +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemTrustConfigTests.java @@ -63,6 +63,13 @@ public void testBadFileFormatFails() throws Exception { assertInvalidFileFormat(trustConfig, ca); } + public void testEmptyFileFails() throws Exception { + final Path ca = createTempFile("ca", ".crt"); + final PemTrustConfig trustConfig = new PemTrustConfig(Collections.singletonList(ca)); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ca)); + assertEmptyFile(trustConfig, ca); + } + public void testMissingFileFailsWithMeaningfulMessage() throws Exception { final Path cert = getDataPath("/certs/ca1/ca.crt").getParent().resolve("dne.crt"); final PemTrustConfig trustConfig = new PemTrustConfig(Collections.singletonList(cert)); @@ -114,11 +121,22 @@ private void assertCertificateChain(PemTrustConfig trustConfig, String... caName assertThat(issuerNames, Matchers.containsInAnyOrder(caNames)); } + private void assertEmptyFile(PemTrustConfig trustConfig, Path file) { + final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); + assertThat(exception.getMessage(), Matchers.containsString("failed to parse any certificates")); + } + private void assertInvalidFileFormat(PemTrustConfig trustConfig, Path file) { + if (inFipsJvm()) { + // When running on BC-FIPS, an invalid file format behaves like an empty file + assertEmptyFile(trustConfig, file); + return; + } final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); assertThat(exception.getMessage(), Matchers.containsString("cannot create trust")); assertThat(exception.getMessage(), Matchers.containsString("PEM")); - assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); assertThat(exception.getCause(), Matchers.instanceOf(GeneralSecurityException.class)); } diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationLoaderTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationLoaderTests.java index 643cc08a76b54..20a161b78fd5f 100644 --- a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationLoaderTests.java +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationLoaderTests.java @@ -190,6 +190,7 @@ public void testLoadKeysFromPKCS12() { } public void testLoadKeysFromJKS() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder() .put("test.ssl.keystore.path", "cert-all/certs.jks"); if (randomBoolean()) { From 32ba20efcc3a6b967cfc315f596d9b8890212ee1 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Fri, 11 Jan 2019 16:56:34 +1100 Subject: [PATCH 6/8] Remove debugging output --- .../java/org/elasticsearch/common/ssl/PemTrustConfig.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java index fea5de0f31613..f3cf8cd8bd7aa 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java @@ -80,9 +80,7 @@ public X509ExtendedTrustManager createTrustManager() { private List loadCertificates() throws CertificateException { try { - final List certificates = PemUtils.readCertificates(this.certificateAuthorities); - System.err.printf("Loaded certificates: %s from %s\n", certificates, this.certificateAuthorities); - return certificates; + return PemUtils.readCertificates(this.certificateAuthorities); } catch (FileNotFoundException | NoSuchFileException e) { throw new SslConfigException("cannot configure trust using PEM certificates [" + caPathsAsString() + "] because one or more files do not exist", e); From a5d0144fdbf9cfa567915106ea63d849f398075f Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Tue, 15 Jan 2019 17:03:02 +1100 Subject: [PATCH 7/8] Address feedback on Gradle setup for ssl-config --- settings.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/settings.gradle b/settings.gradle index 43313f7236cb7..4430f457cdb7e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -95,6 +95,7 @@ if (isEclipse) { projects << 'libs:x-content-tests' projects << 'libs:secure-sm-tests' projects << 'libs:grok-tests' + projects << 'libs:ssl-config' } include projects.toArray(new String[0]) @@ -130,6 +131,10 @@ if (isEclipse) { project(":libs:grok").buildFileName = 'eclipse-build.gradle' project(":libs:grok-tests").projectDir = new File(rootProject.projectDir, 'libs/grok/src/test') project(":libs:grok-tests").buildFileName = 'eclipse-build.gradle' + project(":libs:ssl-config").projectDir = new File(rootProject.projectDir, 'libs/ssl-config/src/main') + project(":libs:ssl-config").buildFileName = 'eclipse-build.gradle' + project(":libs:ssl-config-tests").projectDir = new File(rootProject.projectDir, 'libs/ssl-config/src/test') + project(":libs:ssl-config-tests").buildFileName = 'eclipse-build.gradle' } // look for extra plugins for elasticsearch @@ -141,3 +146,4 @@ if (extraProjects.exists()) { } project(":libs:cli").name = 'elasticsearch-cli' +project(":libs:ssl-config").name = 'elasticsearch-ssl-config' From a052d3552535727f36a1df6d769b423e09c77802 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Wed, 16 Jan 2019 13:41:29 +1100 Subject: [PATCH 8/8] Remove archivesBaseName from build.gradle --- libs/ssl-config/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle index d1f13b36bd729..8d5b1d18b8c04 100644 --- a/libs/ssl-config/build.gradle +++ b/libs/ssl-config/build.gradle @@ -17,8 +17,6 @@ * under the License. */ -archivesBaseName = 'elasticsearch-ssl-config' - dependencies { compile "org.elasticsearch:elasticsearch-core:${version}"