diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertParsingUtils.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertParsingUtils.java index b3f00a9258c14..7f380fa076ee8 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertParsingUtils.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertParsingUtils.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.core.ssl; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.Nullable; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; @@ -93,6 +95,23 @@ public static Certificate[] readCertificates(List certPaths) throws Certif return certificates.toArray(new Certificate[0]); } + public static X509Certificate readX509Certificate(Path path) throws CertificateException, IOException { + List certificates = PemUtils.readCertificates(List.of(path)); + if (certificates.size() != 1) { + throw new IllegalArgumentException("expected a single certificate in file [" + path.toAbsolutePath() + "] but found [" + + certificates.size() + "]"); + } + final Certificate cert = certificates.get(0); + if (cert instanceof X509Certificate) { + return (X509Certificate) cert; + } else { + throw new IllegalArgumentException("the certificate in " + path.toAbsolutePath() + " is not an X.509 certificate (" + + cert.getType() + + " : " + + cert.getClass() + ")"); + } + } + @SuppressWarnings("unchecked") public static X509Certificate[] readX509Certificates(List certPaths) throws CertificateException, IOException { Collection certificates = new ArrayList<>(); @@ -149,8 +168,8 @@ public static Map readKeyPairsFromKeystore(KeyStore store, Fun /** * Creates a {@link KeyStore} from a PEM encoded certificate and key file */ - public static KeyStore getKeyStoreFromPEM(Path certificatePath, Path keyPath, char[] keyPassword) - throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException { + public static KeyStore getKeyStoreFromPEM(Path certificatePath, Path keyPath, char[] keyPassword) throws IOException, + GeneralSecurityException { final PrivateKey key = PemUtils.readPrivateKey(keyPath, () -> keyPassword); final Certificate[] certificates = readCertificates(Collections.singletonList(certificatePath)); return getKeyStore(certificates, key, keyPassword); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/DerParser.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/DerParser.java deleted file mode 100644 index 4250189a5c456..0000000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/DerParser.java +++ /dev/null @@ -1,285 +0,0 @@ -/* @notice - 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.xpack.core.ssl; - - -import org.elasticsearch.common.hash.MessageDigests; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; - -/** - * 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 - */ -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() == false) { - 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 MessageDigests.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(); - } - } -} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PEMKeyConfig.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PEMKeyConfig.java index 4de1f8af01517..3850b08288dd0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PEMKeyConfig.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PEMKeyConfig.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.core.ssl; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.Nullable; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.env.Environment; @@ -22,6 +23,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.security.AccessControlException; +import java.security.GeneralSecurityException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -120,6 +122,8 @@ private static PrivateKey readPrivateKey(String keyPath, SecureString keyPasswor throw unreadableKeyConfigFile(accessException, KEY_FILE, key); } catch (AccessControlException securityException) { throw blockedKeyConfigFile(securityException, environment, KEY_FILE, key); + } catch (GeneralSecurityException e) { + throw new IllegalStateException("Error parsing Private Key from: " + keyPath, e); } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PemUtils.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PemUtils.java deleted file mode 100644 index d3129499a79aa..0000000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PemUtils.java +++ /dev/null @@ -1,603 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.core.ssl; - -import org.elasticsearch.common.hash.MessageDigests; -import org.elasticsearch.core.CharArrays; - -import java.io.BufferedReader; -import java.io.IOException; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.security.KeyException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.MessageDigest; -import java.security.PrivateKey; -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.Arrays; -import java.util.Base64; - -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.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -public 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 { - 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 IllegalStateException("Error parsing Private Key from: " + keyPath.toString() + ". File is empty"); - } - if (PKCS8_ENCRYPTED_HEADER.equals(line.trim())) { - char[] password = passwordSupplier.get(); - if (password == null) { - throw new IllegalArgumentException("cannot read encrypted key 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 IllegalStateException("Error parsing Private Key from: " + keyPath.toString() + ". File did not contain a " + - "supported key format"); - } - } catch (GeneralSecurityException e) { - throw new IllegalStateException("Error parsing Private Key from: " + keyPath.toString(), 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 KeyException("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)); - 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 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 { - String padding = "PKCS5Padding"; - SecretKey encryptionKey; - String[] valueTokens = dekHeaderValue.split(","); - if (valueTokens.length != 2) { - throw new IOException("Malformed PEM file, DEK-Info PEM header is invalid"); - } - String algorithm = valueTokens[0]; - String ivString = valueTokens[1]; - byte[] iv = hexStringToByteArray(ivString); - 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 = MessageDigests.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 is not hexadecimal"); - } - data[i / 2] = (byte) ((k << 4) + l); - } - return data; - } else { - throw new IllegalStateException("Hexadeciamal string length is odd, can't convert to 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); - DerParser.Asn1Object choice = parser.readAsn1Object(); - parser = choice.getParser(); - String namedCurve = getEcCurveNameFromOid(parser.readAsn1Object().getOid()); - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); - AlgorithmParameterSpec algorithmParameterSpec = new ECGenParameterSpec(namedCurve); - keyPairGenerator.initialize(algorithmParameterSpec); - 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"); - } - - private static String getEcCurveNameFromOid(String oidString) throws GeneralSecurityException { - switch (oidString) { - // see https://tools.ietf.org/html/rfc5480#section-2.1.1.1 - case "1.2.840.10045.3.1": - return "secp192r1"; - case "1.3.132.0.1": - return "sect163k1"; - case "1.3.132.0.15": - return "sect163r2"; - case "1.3.132.0.33": - return "secp224r1"; - case "1.3.132.0.26": - return "sect233k1"; - case "1.3.132.0.27": - return "sect233r1"; - case "1.2.840.10045.3.1.7": - return "secp256r1"; - case "1.3.132.0.16": - return "sect283k1"; - case "1.3.132.0.17": - return "sect283r1"; - case "1.3.132.0.34": - return "secp384r1"; - case "1.3.132.0.36": - return "sect409k1"; - case "1.3.132.0.37": - return "sect409r1"; - case "1.3.132.0.35": - return "secp521r1"; - case "1.3.132.0.38": - return "sect571k1"; - case "1.3.132.0.39": - return "sect571r1"; - } - throw new GeneralSecurityException("Error parsing EC named curve identifier. Named curve with OID: " + oidString + " is not " + - "supported"); - } -} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/RestrictedTrustManager.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/RestrictedTrustManager.java index 4fcc40bf61b5a..e0ca46229b054 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/RestrictedTrustManager.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/RestrictedTrustManager.java @@ -9,6 +9,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.message.ParameterizedMessage; +import org.elasticsearch.common.ssl.DerParser; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/CertParsingUtilsTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/CertParsingUtilsTests.java index 92ee16c26adbc..dbc9ed50910ab 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/CertParsingUtilsTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/CertParsingUtilsTests.java @@ -7,19 +7,20 @@ package org.elasticsearch.xpack.core.ssl; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.test.ESTestCase; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.interfaces.ECPrivateKey; -import java.util.Collections; import java.util.List; import static org.hamcrest.Matchers.equalTo; @@ -40,7 +41,7 @@ public void testReadKeysCorrectly() throws Exception { assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); - PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath + PrivateKey privateKey = org.elasticsearch.common.ssl.PemUtils.readPrivateKey(getDataPath ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), "testnode"::toCharArray); assertThat(privateKey, notNullValue()); @@ -81,14 +82,12 @@ public void testReadEllipticCurveCertificateAndKey() throws Exception { verifyPrime256v1ECKey(keyNoSpecPath); Path certPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/prime256v1-cert.pem"); - Certificate[] certs = CertParsingUtils.readCertificates(Collections.singletonList(certPath.toString()), newEnvironment()); - assertEquals(1, certs.length); - Certificate cert = certs[0]; + X509Certificate cert = CertParsingUtils.readX509Certificate(certPath); assertNotNull(cert); assertEquals("EC", cert.getPublicKey().getAlgorithm()); } - private void verifyPrime256v1ECKey(Path keyPath) throws IOException { + private void verifyPrime256v1ECKey(Path keyPath) throws IOException, GeneralSecurityException { PrivateKey privateKey = PemUtils.readPrivateKey(keyPath, () -> null); assertEquals("EC", privateKey.getAlgorithm()); assertThat(privateKey, instanceOf(ECPrivateKey.class)); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/PemUtilsTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/PemUtilsTests.java deleted file mode 100644 index a0e74f812fa71..0000000000000 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/PemUtilsTests.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.core.ssl; - -import org.elasticsearch.test.ESTestCase; - -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.AlgorithmParameters; -import java.security.Key; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.interfaces.ECPrivateKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; - -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 { - - public void testReadPKCS8RsaKey() throws Exception { - Key key = getKeyFromKeystore("RSA"); - assertThat(key, notNullValue()); - assertThat(key, instanceOf(PrivateKey.class)); - PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/rsa_key_pkcs8_plain.pem"), ""::toCharArray); - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode_with_bagattrs.pem"), ""::toCharArray); - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/dsa_key_pkcs8_plain.pem"), ""::toCharArray); - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/ec_key_pkcs8_plain.pem"), ""::toCharArray); - assertThat(privateKey, notNullValue()); - assertThat(privateKey, equalTo(key)); - } - - public void testReadEcKeyCurves() throws Exception { - String curve = randomFrom("secp256r1", "secp384r1", "secp521r1"); - PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/private_" + curve + ".pem"), ""::toCharArray); - assertThat(privateKey, instanceOf(ECPrivateKey.class)); - ECParameterSpec parameterSpec = ((ECPrivateKey) privateKey).getParams(); - ECGenParameterSpec algorithmParameterSpec = new ECGenParameterSpec(curve); - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(algorithmParameterSpec); - assertThat(parameterSpec, equalTo(algoParameters.getParameterSpec(ECParameterSpec.class))); - } - - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/key_pkcs8_encrypted.pem"), "testnode"::toCharArray); - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), "testnode"::toCharArray); - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode-aes" + bits + ".pem"), - "testnode"::toCharArray); - - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode-unprotected.pem"), - "testnode"::toCharArray); - - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/dsa_key_openssl_plain.pem"), - ""::toCharArray); - - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/dsa_key_openssl_plain_with_params.pem"), - ""::toCharArray); - - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/dsa_key_openssl_encrypted.pem"), - "testnode"::toCharArray); - - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/ec_key_openssl_plain.pem"), - ""::toCharArray); - - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/ec_key_openssl_plain_with_params.pem"), - ""::toCharArray); - - 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 - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/ec_key_openssl_encrypted.pem"), - "testnode"::toCharArray); - - assertThat(privateKey, notNullValue()); - assertThat(privateKey, equalTo(key)); - } - - public void testReadUnsupportedKey() { - IllegalStateException e = expectThrows(IllegalStateException.class, () -> PemUtils.readPrivateKey(getDataPath - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/key_unsupported.pem"), - "testnode"::toCharArray)); - assertThat(e.getMessage(), containsString("File did not contain a supported key format")); - } - - public void testReadUnsupportedPemFile() { - IllegalStateException e = expectThrows(IllegalStateException.class, () -> PemUtils.readPrivateKey(getDataPath - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), - "testnode"::toCharArray)); - assertThat(e.getMessage(), containsString("File did not contain a supported key format")); - } - - public void testReadCorruptedKey() { - IllegalStateException e = expectThrows(IllegalStateException.class, () -> PemUtils.readPrivateKey(getDataPath - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/corrupted_key_pkcs8_plain.pem"), - "testnode"::toCharArray)); - assertThat(e.getMessage(), containsString("Error parsing Private Key from")); - assertThat(e.getCause().getMessage(), containsString("Malformed PEM file, PEM footer is invalid or missing")); - } - - public void testReadEmptyFile() { - IllegalStateException e = expectThrows(IllegalStateException.class, () -> PemUtils.readPrivateKey(getDataPath - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/empty.pem"), - "testnode"::toCharArray)); - assertThat(e.getMessage(), containsString("File is empty")); - } - - private Key getKeyFromKeystore(String algo) throws Exception { - Path keystorePath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/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/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java index d5825964963b3..fc29c554daa6e 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java @@ -25,6 +25,7 @@ import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContextBuilder; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.CheckedRunnable; import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.Settings; @@ -56,6 +57,7 @@ import java.nio.file.StandardOpenOption; import java.security.AccessControlException; import java.security.AccessController; +import java.security.GeneralSecurityException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -596,8 +598,7 @@ private static MockWebServer getSslServer(Path keyStorePath, String keyStorePass return server; } - private static MockWebServer getSslServer(Path keyPath, Path certPath, String password) throws KeyStoreException, CertificateException, - NoSuchAlgorithmException, IOException, KeyManagementException, UnrecoverableKeyException { + private static MockWebServer getSslServer(Path keyPath, Path certPath, String password) throws GeneralSecurityException, IOException { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null, password.toCharArray()); keyStore.setKeyEntry("testnode_ec", PemUtils.readPrivateKey(keyPath, password::toCharArray), password.toCharArray(), diff --git a/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/idp/SamlIdentityProviderBuilderTests.java b/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/idp/SamlIdentityProviderBuilderTests.java index b56388ec5e063..6d7f51d4756e3 100644 --- a/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/idp/SamlIdentityProviderBuilderTests.java +++ b/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/idp/SamlIdentityProviderBuilderTests.java @@ -11,10 +11,10 @@ import org.elasticsearch.common.ValidationException; import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.xpack.idp.saml.sp.SamlServiceProviderResolver; import org.elasticsearch.xpack.idp.saml.sp.ServiceProviderDefaults; import org.elasticsearch.xpack.idp.saml.sp.WildcardServiceProviderResolver; diff --git a/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/sp/SamlServiceProviderDocumentTests.java b/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/sp/SamlServiceProviderDocumentTests.java index e6427489685bf..9dd34be72c2a5 100644 --- a/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/sp/SamlServiceProviderDocumentTests.java +++ b/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/sp/SamlServiceProviderDocumentTests.java @@ -24,7 +24,7 @@ import org.opensaml.security.x509.X509Credential; import java.io.IOException; -import java.security.cert.CertificateException; +import java.security.GeneralSecurityException; import java.security.cert.X509Certificate; import java.util.HashSet; import java.util.List; @@ -87,7 +87,7 @@ public void testStreamRoundTripWithAllFields() throws Exception { assertThat(assertSerializationRoundTrip(doc2), equalTo(doc1)); } - private SamlServiceProviderDocument createFullDocument() throws CertificateException, IOException { + private SamlServiceProviderDocument createFullDocument() throws GeneralSecurityException, IOException { final List credentials = readCredentials(); final List certificates = credentials.stream() .map(X509Credential::getEntityCertificate) diff --git a/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/test/IdpSamlTestCase.java b/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/test/IdpSamlTestCase.java index 157c7ce22620c..93ff42397aac6 100644 --- a/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/test/IdpSamlTestCase.java +++ b/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/test/IdpSamlTestCase.java @@ -10,10 +10,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.FileMatchers; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.xpack.idp.saml.idp.SamlIdentityProvider; import org.elasticsearch.xpack.idp.saml.sp.SamlServiceProvider; import org.elasticsearch.xpack.idp.saml.sp.SamlServiceProviderResolver; @@ -41,8 +41,8 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.security.GeneralSecurityException; import java.security.PrivateKey; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; @@ -117,7 +117,7 @@ protected static void mockRegisteredServiceProvider(SamlServiceProviderResolver }).when(resolverMock).resolve(Mockito.eq(entityId), Mockito.any(ActionListener.class)); } - protected List readCredentials() throws CertificateException, IOException { + protected List readCredentials() throws GeneralSecurityException, IOException { List list = new ArrayList<>(2); list.add(readCredentials("RSA", 1024)); list.add(readCredentials("RSA", 2048)); @@ -125,7 +125,7 @@ protected List readCredentials() throws CertificateException, IO return list; } - protected X509Credential readCredentials(String type, int size) throws CertificateException, IOException { + protected X509Credential readCredentials(String type, int size) throws GeneralSecurityException, IOException { Path certPath = getDataPath("/keypair/keypair_" + type + "_" + size + ".crt"); Path keyPath = getDataPath("/keypair/keypair_" + type + "_" + size + ".key"); assertThat(certPath, FileMatchers.isRegularFile()); diff --git a/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateGenerateTool.java b/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateGenerateTool.java index 0c25098935d63..733146136b7bd 100644 --- a/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateGenerateTool.java +++ b/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateGenerateTool.java @@ -22,6 +22,7 @@ import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.UserException; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.common.xcontent.ParseField; import org.elasticsearch.common.Strings; import org.elasticsearch.core.SuppressForbidden; @@ -36,7 +37,6 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.env.Environment; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import javax.security.auth.x500.X500Principal; import java.io.IOException; @@ -593,8 +593,7 @@ private static void printConclusion(Terminal terminal, boolean csr, Path outputF * @param prompt whether to prompt the user or not * @return the {@link PrivateKey} that was read from the file */ - private static PrivateKey readPrivateKey(String path, char[] password, Terminal terminal, boolean prompt) - throws Exception { + private static PrivateKey readPrivateKey(String path, char[] password, Terminal terminal, boolean prompt) throws Exception { AtomicReference passwordReference = new AtomicReference<>(password); try { return PemUtils.readPrivateKey(resolvePath(path), () -> { diff --git a/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateTool.java b/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateTool.java index c021a845f695c..45fae3ccf69e1 100644 --- a/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateTool.java +++ b/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateTool.java @@ -26,6 +26,7 @@ import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal.Verbosity; import org.elasticsearch.cli.UserException; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.CheckedConsumer; import org.elasticsearch.core.CheckedFunction; import org.elasticsearch.common.xcontent.ParseField; @@ -42,7 +43,6 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.env.Environment; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import javax.security.auth.x500.X500Principal; @@ -1053,8 +1053,7 @@ private static void fullyWriteFile(Path file, CheckedConsumer passwordReference = new AtomicReference<>(password); try { return PemUtils.readPrivateKey(path, () -> { diff --git a/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/HttpCertificateCommand.java b/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/HttpCertificateCommand.java index c25f4e2a77ff8..568b6c37e42d1 100644 --- a/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/HttpCertificateCommand.java +++ b/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/HttpCertificateCommand.java @@ -26,13 +26,13 @@ import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.UserException; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.PathUtils; import org.elasticsearch.common.io.Streams; import org.elasticsearch.common.network.InetAddresses; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.env.Environment; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import javax.security.auth.x500.X500Principal; import java.io.IOException; @@ -1006,7 +1006,7 @@ private PrivateKey readPrivateKey(Path path, Terminal terminal) { terminal.println(""); return terminal.readSecret("Password for " + path.getFileName() + ":"); }); - } catch (IOException e) { + } catch (IOException | GeneralSecurityException e) { throw new ElasticsearchException("Failed to read private key from " + path, e); } } diff --git a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateGenerateToolTests.java b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateGenerateToolTests.java index 34ba3f3a96327..71a990bd8807f 100644 --- a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateGenerateToolTests.java +++ b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateGenerateToolTests.java @@ -9,6 +9,7 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import org.bouncycastle.asn1.DLTaggedObject; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.internal.io.IOUtils; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; @@ -38,7 +39,6 @@ import org.elasticsearch.xpack.security.cli.CertificateGenerateTool.CertificateInformation; import org.elasticsearch.xpack.security.cli.CertificateGenerateTool.Name; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.junit.After; import org.junit.BeforeClass; @@ -317,8 +317,10 @@ public void testGeneratingSignedCertificates() throws Exception { } } - PrivateKey privateKey = PemUtils.readPrivateKey(zipRoot.resolve("ca").resolve("ca.key"), () -> keyPassword != null ? - SecuritySettingsSourceField.TEST_PASSWORD.toCharArray() : null); + PrivateKey privateKey = PemUtils.readPrivateKey( + zipRoot.resolve("ca").resolve("ca.key"), + () -> keyPassword != null ? SecuritySettingsSourceField.TEST_PASSWORD.toCharArray() : null + ); assertEquals(caInfo.privateKey, privateKey); } else { assertFalse(Files.exists(zipRoot.resolve("ca"))); diff --git a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateToolTests.java b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateToolTests.java index 3eb96f6edd3e3..2ea064d44cd12 100644 --- a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateToolTests.java +++ b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateToolTests.java @@ -29,6 +29,7 @@ import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.UserException; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.core.PathUtils; import org.elasticsearch.common.network.NetworkAddress; @@ -39,7 +40,6 @@ import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.SecuritySettingsSourceField; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.xpack.security.cli.CertificateTool.CAInfo; import org.elasticsearch.xpack.security.cli.CertificateTool.CertificateAuthorityCommand; import org.elasticsearch.xpack.security.cli.CertificateTool.CertificateCommand; diff --git a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/HttpCertificateCommandTests.java b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/HttpCertificateCommandTests.java index 99c4e350b1ee1..f55cdaccbac96 100644 --- a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/HttpCertificateCommandTests.java +++ b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/HttpCertificateCommandTests.java @@ -25,6 +25,7 @@ import org.bouncycastle.util.io.pem.PemReader; import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.common.CheckedBiFunction; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.CheckedFunction; import org.elasticsearch.core.Tuple; import org.elasticsearch.common.network.NetworkAddress; @@ -32,7 +33,6 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.xpack.security.cli.HttpCertificateCommand.FileType; import org.hamcrest.Matchers; import org.junit.Before; diff --git a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/PemToKeystore.java b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/PemToKeystore.java index f869e31233283..0399e8ab280c2 100644 --- a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/PemToKeystore.java +++ b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/PemToKeystore.java @@ -8,8 +8,8 @@ package org.elasticsearch.xpack.security.cli; import org.elasticsearch.cli.SuppressForbidden; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import java.io.IOException; import java.io.OutputStream; diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java index b484a091e9992..dc0093b98918e 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java @@ -13,12 +13,12 @@ import org.apache.http.util.EntityUtils; import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.test.SecuritySingleNodeTestCase; import org.elasticsearch.xpack.core.common.socket.SocketAccess; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.common.ssl.SslClientAuthenticationMode; import javax.net.ssl.KeyManager; diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/transport/ssl/EllipticCurveSSLTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/transport/ssl/EllipticCurveSSLTests.java index adec7af49851e..d9c3a62a16a9e 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/transport/ssl/EllipticCurveSSLTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/transport/ssl/EllipticCurveSSLTests.java @@ -8,11 +8,11 @@ import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.transport.TransportInfo; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.junit.BeforeClass; import javax.net.ssl.HandshakeCompletedEvent; diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/ssl/SSLTrustRestrictionsTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/ssl/SSLTrustRestrictionsTests.java index c7cdd81ec2cf7..ea695e0713688 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/ssl/SSLTrustRestrictionsTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/ssl/SSLTrustRestrictionsTests.java @@ -8,6 +8,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.PathUtils; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; @@ -19,7 +20,6 @@ import org.elasticsearch.transport.Transport; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.xpack.core.ssl.RestrictedTrustManager; import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLService; diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/ssl/SslClientAuthenticationTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/ssl/SslClientAuthenticationTests.java index 3295b2dc1b576..231995352ded6 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/ssl/SslClientAuthenticationTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/ssl/SslClientAuthenticationTests.java @@ -12,6 +12,7 @@ import org.apache.http.util.EntityUtils; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.jdk.JavaVersion; import org.elasticsearch.client.Request; import org.elasticsearch.client.RequestOptions; @@ -23,7 +24,6 @@ import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.common.ssl.SslClientAuthenticationMode; import javax.net.ssl.KeyManager; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlMetadataCommand.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlMetadataCommand.java index 2ae69f8e7b2f6..543ed3d4788a5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlMetadataCommand.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlMetadataCommand.java @@ -38,6 +38,7 @@ import org.elasticsearch.cli.SuppressForbidden; import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.UserException; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.CheckedFunction; import org.elasticsearch.common.Strings; import org.elasticsearch.core.PathUtils; @@ -51,7 +52,6 @@ import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.xpack.security.authc.saml.SamlSpMetadataBuilder.ContactInfo; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.core.xml.io.MarshallingException; @@ -329,8 +329,7 @@ private static char[] getChars(String password) { return password == null ? null : password.toCharArray(); } - private static PrivateKey readSigningKey(Path path, char[] password, Terminal terminal) - throws Exception { + private static PrivateKey readSigningKey(Path path, char[] password, Terminal terminal) throws Exception { AtomicReference passwordReference = new AtomicReference<>(password); try { return PemUtils.readPrivateKey(path, () -> { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlMetadataCommandTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlMetadataCommandTests.java index b5797d22d7ff8..20931c8928c00 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlMetadataCommandTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlMetadataCommandTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.cli.UserException; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.Tuple; import org.elasticsearch.common.settings.KeyStoreWrapper; import org.elasticsearch.common.settings.MockSecureSettings; @@ -18,7 +19,6 @@ import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.hamcrest.CoreMatchers; import org.junit.Before; import org.opensaml.saml.common.xml.SAMLConstants; @@ -446,7 +446,7 @@ public void testErrorSigningMetadataWithWrongPassword() throws Exception { final UserException userException = expectThrows(UserException.class, () -> command.possiblySignDescriptor(terminal, options, descriptor, env)); assertThat(userException.getMessage(), containsString("Unable to create metadata document")); - assertThat(terminal.getErrorOutput(), containsString("Error parsing Private Key from")); + assertThat(terminal.getErrorOutput(), containsString("cannot load PEM private key from [")); } public void testSigningMetadataWithPem() throws Exception { @@ -735,8 +735,7 @@ private boolean validateSignature(Signature signature) { try { Certificate[] certificates = CertParsingUtils. readCertificates(Collections.singletonList(getDataPath("saml.crt").toString()), newEnvironment()); - PrivateKey key = PemUtils.readPrivateKey(getDataPath("saml.key"), - ""::toCharArray); + PrivateKey key = PemUtils.readPrivateKey(getDataPath("saml.key"), ""::toCharArray); Credential verificationCredential = new BasicX509Credential((java.security.cert.X509Certificate) certificates[0], key); SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); profileValidator.validate(signature); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java index 8314885fd3db6..a0f3872c46b48 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java @@ -10,6 +10,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.jdk.JavaVersion; import org.elasticsearch.core.Tuple; import org.elasticsearch.common.settings.MockSecureSettings; @@ -32,7 +33,6 @@ import org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.TestsSSLService; import org.elasticsearch.xpack.security.authc.Realms; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlTestCase.java index 7fa149cc4a9fb..46ff92c71bf36 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlTestCase.java @@ -10,11 +10,11 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.core.Tuple; import org.elasticsearch.core.PathUtils; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.opensaml.saml.common.xml.SAMLConstants; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLDriverTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLDriverTests.java index 2a0c666b1425c..f4b116e36ec22 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLDriverTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLDriverTests.java @@ -6,13 +6,13 @@ */ package org.elasticsearch.xpack.security.transport.nio; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.jdk.JavaVersion; import org.elasticsearch.nio.FlushOperation; import org.elasticsearch.nio.InboundChannelBuffer; import org.elasticsearch.nio.Page; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.hamcrest.Matcher; import java.io.IOException; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/ssl/SSLErrorMessageFileTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/ssl/SSLErrorMessageFileTests.java index 0cf547ce4e2bc..691b9a7345e3e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/ssl/SSLErrorMessageFileTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/ssl/SSLErrorMessageFileTests.java @@ -9,6 +9,7 @@ import org.apache.lucene.util.Constants; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.common.ssl.SslConfigException; import org.elasticsearch.core.Nullable; import org.elasticsearch.core.PathUtils; import org.elasticsearch.common.settings.Settings; @@ -78,7 +79,7 @@ public void testMessageForMissingPemCertificate() { } public void testMessageForMissingPemKey() { - checkMissingKeyManagerResource("key", "key", withCertificate("cert1a.crt")); + checkMissingKeyManagerResource("PEM private key", "key", withCertificate("cert1a.crt")); } public void testMessageForMissingTruststore() { @@ -98,7 +99,7 @@ public void testMessageForPemCertificateWithoutReadAccess() throws Exception { } public void testMessageForPemKeyWithoutReadAccess() throws Exception { - checkUnreadableKeyManagerResource("cert1a.key", "key", "key", withCertificate("cert1a.crt")); + checkUnreadableKeyManagerResource("cert1a.key", "PEM private key", "key", withCertificate("cert1a.crt")); } public void testMessageForTruststoreWithoutReadAccess() throws Exception { @@ -118,7 +119,7 @@ public void testMessageForPemCertificateOutsideConfigDir() throws Exception { } public void testMessageForPemKeyOutsideConfigDir() throws Exception { - checkBlockedKeyManagerResource("key", "key", withCertificate("cert1a.crt")); + checkBlockedKeyManagerResource("PEM private key", "key", withCertificate("cert1a.crt")); } public void testMessageForTrustStoreOutsideConfigDir() throws Exception { @@ -235,14 +236,21 @@ private void checkMissingResource(String sslManagerType, String fileType, String final String key = prefix + "." + configKey; settings.put(key, fileName); + final String fileErrorMessage = "cannot read configured " + fileType + " [" + fileName + "] because the file does not exist"; Throwable exception = expectFailure(settings); assertThat(exception, throwableWithMessage("failed to load SSL configuration [" + prefix + "]")); assertThat(exception, instanceOf(ElasticsearchSecurityException.class)); exception = exception.getCause(); - assertThat(exception, throwableWithMessage( - "failed to initialize SSL " + sslManagerType + " - " + fileType + " file [" + fileName + "] does not exist")); - assertThat(exception, instanceOf(ElasticsearchException.class)); + // This is needed temporarily while we're converting from X-Pack SSL to libs/ssl-config + if (exception.getMessage().contains(sslManagerType)) { + String message = "failed to initialize SSL " + sslManagerType + " - " + fileType + " file [" + fileName + "] does not exist"; + assertThat(exception, throwableWithMessage(message)); + assertThat(exception, instanceOf(ElasticsearchException.class)); + } else { + assertThat(exception, throwableWithMessage(fileErrorMessage)); + assertThat(exception, instanceOf(SslConfigException.class)); + } exception = exception.getCause(); assertThat(exception, instanceOf(NoSuchFileException.class)); @@ -259,14 +267,26 @@ private void checkUnreadableResource(String sslManagerType, String fromResource, final String key = prefix + "." + configKey; settings.put(key, fileName); + final String fileErrorMessage = "not permitted to read the " + fileType + " file [" + fileName + "]"; + Throwable exception = expectFailure(settings); assertThat(exception, throwableWithMessage("failed to load SSL configuration [" + prefix + "]")); assertThat(exception, instanceOf(ElasticsearchSecurityException.class)); exception = exception.getCause(); - assertThat(exception, throwableWithMessage( - "failed to initialize SSL " + sslManagerType + " - not permitted to read " + fileType + " file [" + fileName + "]")); - assertThat(exception, instanceOf(ElasticsearchException.class)); + // This is needed temporarily while we're converting from X-Pack SSL to libs/ssl-config + if (exception.getMessage().contains(sslManagerType)) { + assertThat( + exception, + throwableWithMessage( + "failed to initialize SSL " + sslManagerType + " - not permitted to read " + fileType + " file [" + fileName + "]" + ) + ); + assertThat(exception, instanceOf(ElasticsearchException.class)); + } else { + assertThat(exception, throwableWithMessage(fileErrorMessage)); + assertThat(exception, instanceOf(SslConfigException.class)); + } exception = exception.getCause(); assertThat(exception, instanceOf(AccessDeniedException.class)); @@ -283,15 +303,25 @@ private void checkBlockedResource(String sslManagerType, String fileType, String final String key = prefix + "." + configKey; settings.put(key, fileName); + final String fileErrorMessage = "cannot read configured " + fileType + " [" + fileName + + "] because access to read the file is blocked; SSL resources should be placed in the " + + "Elasticsearch config directory"; + Throwable exception = expectFailure(settings); assertThat(exception, throwableWithMessage("failed to load SSL configuration [" + prefix + "]")); assertThat(exception, instanceOf(ElasticsearchSecurityException.class)); exception = exception.getCause(); - assertThat(exception.getMessage(), - containsString("failed to initialize SSL " + sslManagerType + " - access to read " + fileType + " file")); - assertThat(exception.getMessage(), containsString("file.error")); - assertThat(exception, instanceOf(ElasticsearchException.class)); + // This is needed temporarily while we're converting from X-Pack SSL to libs/ssl-config + if (exception.getMessage().contains(sslManagerType)) { + String message = "failed to initialize SSL " + sslManagerType + " - access to read " + fileType + " file"; + assertThat(exception.getMessage(), containsString(message)); + assertThat(exception.getMessage(), containsString("file.error")); + assertThat(exception, instanceOf(ElasticsearchException.class)); + } else { + assertThat(exception, throwableWithMessage(fileErrorMessage)); + assertThat(exception, instanceOf(SslConfigException.class)); + } exception = exception.getCause(); assertThat(exception, instanceOf(AccessControlException.class)); diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/email/EmailSslTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/email/EmailSslTests.java index fe8d7f32d44a0..dd50cdcd14e6f 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/email/EmailSslTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/email/EmailSslTests.java @@ -11,10 +11,10 @@ import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.ssl.PemUtils; import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.ssl.CertParsingUtils; -import org.elasticsearch.xpack.core.ssl.PemUtils; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.watcher.execution.WatchExecutionContext; import org.elasticsearch.xpack.core.watcher.watch.Payload;