mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-03-24 17:09:48 +00:00
Limit the scope of BouncyCastle dependency (#30358)
Limits the scope of the runtime dependency on BouncyCastle so that it can be eventually removed. * Splits functionality related to reading and generating certificates and keys in two utility classes so that reading certificates and keys doesn't require BouncyCastle. * Implements a class for parsing PEM Encoded key material (which also adds support for reading PKCS8 encoded encrypted private keys). * Removes BouncyCastle dependency for all of our test suites(except for the tests that explicitly test certificate generation) by using pre-generated keys/certificates/keystores.
This commit is contained in:
parent
6ce86a8d7f
commit
a8faf9768a
@ -88,6 +88,7 @@ compileTestJava.options.compilerArgs << "-Xlint:-deprecation,-rawtypes,-serial,-
|
||||
licenseHeaders {
|
||||
approvedLicenses << 'BCrypt (BSD-like)'
|
||||
additionalLicense 'BCRYP', 'BCrypt (BSD-like)', 'Copyright (c) 2006 Damien Miller <djm@mindrot.org>'
|
||||
excludes << 'org/elasticsearch/xpack/core/ssl/DerParser.java'
|
||||
}
|
||||
|
||||
// make LicenseSigner available for testing signed licenses
|
||||
|
@ -7,7 +7,6 @@ package org.elasticsearch.xpack.core;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.lucene.util.SetOnce;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.elasticsearch.SpecialPermission;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
@ -125,7 +124,7 @@ public class XPackPlugin extends XPackClientPlugin implements ScriptPlugin, Exte
|
||||
|
||||
public XPackPlugin(
|
||||
final Settings settings,
|
||||
final Path configPath) throws IOException, DestroyFailedException, OperatorCreationException, GeneralSecurityException {
|
||||
final Path configPath) {
|
||||
super(settings);
|
||||
this.settings = settings;
|
||||
this.transportClientMode = transportClientMode(settings);
|
||||
|
@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.DERTaggedObject;
|
||||
import org.bouncycastle.asn1.DERUTF8String;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.asn1.x509.Time;
|
||||
import org.bouncycastle.cert.CertIOException;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.network.InetAddressHelper;
|
||||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* Utility methods that deal with {@link Certificate}, {@link KeyStore}, {@link X509ExtendedTrustManager}, {@link X509ExtendedKeyManager}
|
||||
* and other certificate related objects.
|
||||
*/
|
||||
public class CertGenUtils {
|
||||
|
||||
private static final String CN_OID = "2.5.4.3";
|
||||
|
||||
private static final int SERIAL_BIT_LENGTH = 20 * 8;
|
||||
private static final BouncyCastleProvider BC_PROV = new BouncyCastleProvider();
|
||||
|
||||
private CertGenUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a CA certificate
|
||||
*/
|
||||
public static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days)
|
||||
throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
|
||||
return generateSignedCertificate(x500Principal, null, keyPair, null, null, true, days, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signed certificate using the provided CA private key and
|
||||
* information from the CA certificate
|
||||
*
|
||||
* @param principal the principal of the certificate; commonly referred to as the
|
||||
* distinguished name (DN)
|
||||
* @param subjectAltNames the subject alternative names that should be added to the
|
||||
* certificate as an X509v3 extension. May be {@code null}
|
||||
* @param keyPair the key pair that will be associated with the certificate
|
||||
* @param caCert the CA certificate. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param days no of days certificate will be valid from now
|
||||
* @return a signed {@link X509Certificate}
|
||||
*/
|
||||
public static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
|
||||
X509Certificate caCert, PrivateKey caPrivKey, int days)
|
||||
throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
|
||||
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signed certificate using the provided CA private key and
|
||||
* information from the CA certificate
|
||||
*
|
||||
* @param principal the principal of the certificate; commonly referred to as the
|
||||
* distinguished name (DN)
|
||||
* @param subjectAltNames the subject alternative names that should be added to the
|
||||
* certificate as an X509v3 extension. May be {@code null}
|
||||
* @param keyPair the key pair that will be associated with the certificate
|
||||
* @param caCert the CA certificate. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param days no of days certificate will be valid from now
|
||||
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
|
||||
* empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)}
|
||||
* @return a signed {@link X509Certificate}
|
||||
*/
|
||||
public static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
|
||||
X509Certificate caCert, PrivateKey caPrivKey,
|
||||
int days, String signatureAlgorithm)
|
||||
throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
|
||||
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, signatureAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signed certificate
|
||||
*
|
||||
* @param principal the principal of the certificate; commonly referred to as the
|
||||
* distinguished name (DN)
|
||||
* @param subjectAltNames the subject alternative names that should be added to the
|
||||
* certificate as an X509v3 extension. May be {@code null}
|
||||
* @param keyPair the key pair that will be associated with the certificate
|
||||
* @param caCert the CA certificate. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param isCa whether or not the generated certificate is a CA
|
||||
* @param days no of days certificate will be valid from now
|
||||
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
|
||||
* empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)}
|
||||
* @return a signed {@link X509Certificate}
|
||||
*/
|
||||
private static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
|
||||
X509Certificate caCert, PrivateKey caPrivKey, boolean isCa,
|
||||
int days, String signatureAlgorithm)
|
||||
throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
|
||||
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
|
||||
final DateTime notBefore = new DateTime(DateTimeZone.UTC);
|
||||
if (days < 1) {
|
||||
throw new IllegalArgumentException("the certificate must be valid for at least one day");
|
||||
}
|
||||
final DateTime notAfter = notBefore.plusDays(days);
|
||||
final BigInteger serial = CertGenUtils.getSerial();
|
||||
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
|
||||
|
||||
X500Name subject = X500Name.getInstance(principal.getEncoded());
|
||||
final X500Name issuer;
|
||||
final AuthorityKeyIdentifier authorityKeyIdentifier;
|
||||
if (caCert != null) {
|
||||
if (caCert.getBasicConstraints() < 0) {
|
||||
throw new IllegalArgumentException("ca certificate is not a CA!");
|
||||
}
|
||||
issuer = X500Name.getInstance(caCert.getIssuerX500Principal().getEncoded());
|
||||
authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey());
|
||||
} else {
|
||||
issuer = subject;
|
||||
authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(keyPair.getPublic());
|
||||
}
|
||||
|
||||
JcaX509v3CertificateBuilder builder =
|
||||
new JcaX509v3CertificateBuilder(issuer, serial,
|
||||
new Time(notBefore.toDate(), Locale.ROOT), new Time(notAfter.toDate(), Locale.ROOT), subject, keyPair.getPublic());
|
||||
|
||||
builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(keyPair.getPublic()));
|
||||
builder.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier);
|
||||
if (subjectAltNames != null) {
|
||||
builder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
|
||||
}
|
||||
builder.addExtension(Extension.basicConstraints, isCa, new BasicConstraints(isCa));
|
||||
|
||||
PrivateKey signingKey = caPrivKey != null ? caPrivKey : keyPair.getPrivate();
|
||||
ContentSigner signer = new JcaContentSignerBuilder(
|
||||
(Strings.isNullOrEmpty(signatureAlgorithm)) ? getDefaultSignatureAlgorithm(signingKey) : signatureAlgorithm)
|
||||
.setProvider(CertGenUtils.BC_PROV).build(signingKey);
|
||||
X509CertificateHolder certificateHolder = builder.build(signer);
|
||||
return new JcaX509CertificateConverter().getCertificate(certificateHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the private key algorithm {@link PrivateKey#getAlgorithm()}
|
||||
* determines default signing algorithm used by CertGenUtils
|
||||
*
|
||||
* @param key {@link PrivateKey}
|
||||
* @return algorithm
|
||||
*/
|
||||
private static String getDefaultSignatureAlgorithm(PrivateKey key) {
|
||||
String signatureAlgorithm = null;
|
||||
switch (key.getAlgorithm()) {
|
||||
case "RSA":
|
||||
signatureAlgorithm = "SHA256withRSA";
|
||||
break;
|
||||
case "DSA":
|
||||
signatureAlgorithm = "SHA256withDSA";
|
||||
break;
|
||||
case "EC":
|
||||
signatureAlgorithm = "SHA256withECDSA";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported algorithm : " + key.getAlgorithm()
|
||||
+ " for signature, allowed values for private key algorithm are [RSA, DSA, EC]");
|
||||
}
|
||||
return signatureAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a certificate signing request
|
||||
*
|
||||
* @param keyPair the key pair that will be associated by the certificate generated from the certificate signing request
|
||||
* @param principal the principal of the certificate; commonly referred to as the distinguished name (DN)
|
||||
* @param sanList the subject alternative names that should be added to the certificate as an X509v3 extension. May be
|
||||
* {@code null}
|
||||
* @return a certificate signing request
|
||||
*/
|
||||
static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList)
|
||||
throws IOException, OperatorCreationException {
|
||||
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
|
||||
Objects.requireNonNull(keyPair.getPublic(), "Public-Key must not be null");
|
||||
Objects.requireNonNull(principal, "Principal must not be null");
|
||||
JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(principal, keyPair.getPublic());
|
||||
if (sanList != null) {
|
||||
ExtensionsGenerator extGen = new ExtensionsGenerator();
|
||||
extGen.addExtension(Extension.subjectAlternativeName, false, sanList);
|
||||
builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate());
|
||||
}
|
||||
|
||||
return builder.build(new JcaContentSignerBuilder("SHA256withRSA").setProvider(CertGenUtils.BC_PROV).build(keyPair.getPrivate()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a random serial for a certificate that is generated from a {@link SecureRandom}
|
||||
*/
|
||||
public static BigInteger getSerial() {
|
||||
SecureRandom random = new SecureRandom();
|
||||
BigInteger serial = new BigInteger(SERIAL_BIT_LENGTH, random);
|
||||
assert serial.compareTo(BigInteger.valueOf(0L)) >= 0;
|
||||
return serial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a RSA key pair with the provided key size (in bits)
|
||||
*/
|
||||
public static KeyPair generateKeyPair(int keysize) throws NoSuchAlgorithmException {
|
||||
// generate a private key
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(keysize);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@link InetAddress} objects into a {@link GeneralNames} object that is used to represent subject alternative names.
|
||||
*/
|
||||
public static GeneralNames getSubjectAlternativeNames(boolean resolveName, Set<InetAddress> addresses) throws SocketException {
|
||||
Set<GeneralName> generalNameList = new HashSet<>();
|
||||
for (InetAddress address : addresses) {
|
||||
if (address.isAnyLocalAddress()) {
|
||||
// it is a wildcard address
|
||||
for (InetAddress inetAddress : InetAddressHelper.getAllAddresses()) {
|
||||
addSubjectAlternativeNames(resolveName, inetAddress, generalNameList);
|
||||
}
|
||||
} else {
|
||||
addSubjectAlternativeNames(resolveName, address, generalNameList);
|
||||
}
|
||||
}
|
||||
return new GeneralNames(generalNameList.toArray(new GeneralName[generalNameList.size()]));
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "need to use getHostName to resolve DNS name and getHostAddress to ensure we resolved the name")
|
||||
private static void addSubjectAlternativeNames(boolean resolveName, InetAddress inetAddress, Set<GeneralName> list) {
|
||||
String hostaddress = inetAddress.getHostAddress();
|
||||
String ip = NetworkAddress.format(inetAddress);
|
||||
list.add(new GeneralName(GeneralName.iPAddress, ip));
|
||||
if (resolveName && (inetAddress.isLinkLocalAddress() == false)) {
|
||||
String possibleHostName = inetAddress.getHostName();
|
||||
if (possibleHostName.equals(hostaddress) == false) {
|
||||
list.add(new GeneralName(GeneralName.dNSName, possibleHostName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an X.509 {@link GeneralName} for use as a <em>Common Name</em> in the certificate's <em>Subject Alternative Names</em>
|
||||
* extension. A <em>common name</em> is a name with a tag of {@link GeneralName#otherName OTHER}, with an object-id that references
|
||||
* the {@link #CN_OID cn} attribute, an explicit tag of '0', and a DER encoded UTF8 string for the name.
|
||||
* This usage of using the {@code cn} OID as a <em>Subject Alternative Name</em> is <strong>non-standard</strong> and will not be
|
||||
* recognised by other X.509/TLS implementations.
|
||||
*/
|
||||
public static GeneralName createCommonName(String cn) {
|
||||
final ASN1Encodable[] sequence = {new ASN1ObjectIdentifier(CN_OID), new DERTaggedObject(true, 0, new DERUTF8String(cn))};
|
||||
return new GeneralName(GeneralName.otherName, new DERSequence(sequence));
|
||||
}
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
||||
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.IOException;
|
||||
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.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings.getKeyStoreType;
|
||||
|
||||
public class CertParsingUtils {
|
||||
|
||||
private CertParsingUtils() {
|
||||
throw new IllegalStateException("Utility class should not be instantiated");
|
||||
}
|
||||
/**
|
||||
* Resolves a path with or without an {@link Environment} as we may be running in a transport client where we do not have access to
|
||||
* the environment
|
||||
*/
|
||||
@SuppressForbidden(reason = "we don't have the environment to resolve files from when running in a transport client")
|
||||
static Path resolvePath(String path, @Nullable Environment environment) {
|
||||
if (environment != null) {
|
||||
return environment.configFile().resolve(path);
|
||||
}
|
||||
return PathUtils.get(path).normalize();
|
||||
}
|
||||
|
||||
static KeyStore readKeyStore(Path path, String type, char[] password)
|
||||
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
|
||||
try (InputStream in = Files.newInputStream(path)) {
|
||||
KeyStore store = KeyStore.getInstance(type);
|
||||
assert password != null;
|
||||
store.load(in, password);
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the provided paths and parses them into {@link Certificate} objects
|
||||
*
|
||||
* @param certPaths the paths to the PEM encoded certificates
|
||||
* @param environment the environment to resolve files against. May be {@code null}
|
||||
* @return an array of {@link Certificate} objects
|
||||
*/
|
||||
public static Certificate[] readCertificates(List<String> certPaths, @Nullable Environment environment)
|
||||
throws CertificateException, IOException {
|
||||
final List<Path> resolvedPaths = certPaths.stream().map(p -> resolvePath(p, environment)).collect(Collectors.toList());
|
||||
return readCertificates(resolvedPaths);
|
||||
}
|
||||
|
||||
public static Certificate[] readCertificates(List<Path> certPaths) throws CertificateException, IOException {
|
||||
Collection<Certificate> certificates = new ArrayList<>();
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
for (Path path : certPaths) {
|
||||
try (InputStream input = Files.newInputStream(path)) {
|
||||
certificates.addAll((Collection<Certificate>) certFactory.generateCertificates(input));
|
||||
}
|
||||
}
|
||||
return certificates.toArray(new Certificate[0]);
|
||||
}
|
||||
|
||||
public static X509Certificate[] readX509Certificates(List<Path> certPaths) throws CertificateException, IOException {
|
||||
Collection<X509Certificate> certificates = new ArrayList<>();
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
for (Path path : certPaths) {
|
||||
try (InputStream input = Files.newInputStream(path)) {
|
||||
certificates.addAll((Collection<X509Certificate>) certFactory.generateCertificates(input));
|
||||
}
|
||||
}
|
||||
return certificates.toArray(new X509Certificate[0]);
|
||||
}
|
||||
|
||||
static List<Certificate> readCertificates(InputStream input) throws CertificateException, IOException {
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
Collection<Certificate> certificates = (Collection<Certificate>) certFactory.generateCertificates(input);
|
||||
return new ArrayList<>(certificates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all certificate-key pairs from a PKCS#12 container.
|
||||
*
|
||||
* @param path The path to the PKCS#12 container file.
|
||||
* @param password The password for the container file
|
||||
* @param keyPassword A supplier for the password for each key. The key alias is supplied as an argument to the function, and it should
|
||||
* return the password for that key. If it returns {@code null}, then the key-pair for that alias is not read.
|
||||
*/
|
||||
public static Map<Certificate, Key> readPkcs12KeyPairs(Path path, char[] password, Function<String, char[]> keyPassword)
|
||||
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
|
||||
final KeyStore store = readKeyStore(path, "PKCS12", password);
|
||||
final Enumeration<String> enumeration = store.aliases();
|
||||
final Map<Certificate, Key> map = new HashMap<>(store.size());
|
||||
while (enumeration.hasMoreElements()) {
|
||||
final String alias = enumeration.nextElement();
|
||||
if (store.isKeyEntry(alias)) {
|
||||
final char[] pass = keyPassword.apply(alias);
|
||||
map.put(store.getCertificate(alias), store.getKey(alias, pass));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link KeyStore} from a PEM encoded certificate and key file
|
||||
*/
|
||||
static KeyStore getKeyStoreFromPEM(Path certificatePath, Path keyPath, char[] keyPassword)
|
||||
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
|
||||
final PrivateKey key = PemUtils.readPrivateKey(keyPath, () -> keyPassword);
|
||||
final Certificate[] certificates = readCertificates(Collections.singletonList(certificatePath));
|
||||
return getKeyStore(certificates, key, keyPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link X509ExtendedKeyManager} that is built from the provided private key and certificate chain
|
||||
*/
|
||||
public static X509ExtendedKeyManager keyManager(Certificate[] certificateChain, PrivateKey privateKey, char[] password)
|
||||
throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException {
|
||||
KeyStore keyStore = getKeyStore(certificateChain, privateKey, password);
|
||||
return keyManager(keyStore, password, KeyManagerFactory.getDefaultAlgorithm());
|
||||
}
|
||||
|
||||
private static KeyStore getKeyStore(Certificate[] certificateChain, PrivateKey privateKey, char[] password)
|
||||
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(null, null);
|
||||
// password must be non-null for keystore...
|
||||
keyStore.setKeyEntry("key", privateKey, password, certificateChain);
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link X509ExtendedKeyManager} that is built from the provided keystore
|
||||
*/
|
||||
static X509ExtendedKeyManager keyManager(KeyStore keyStore, char[] password, String algorithm)
|
||||
throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException {
|
||||
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 IllegalStateException("failed to find a X509ExtendedKeyManager");
|
||||
}
|
||||
|
||||
public static X509ExtendedKeyManager getKeyManager(X509KeyPairSettings keyPair, Settings settings,
|
||||
@Nullable String trustStoreAlgorithm, Environment environment) {
|
||||
if (trustStoreAlgorithm == null) {
|
||||
trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
}
|
||||
final KeyConfig keyConfig = createKeyConfig(keyPair, settings, trustStoreAlgorithm);
|
||||
if (keyConfig == null) {
|
||||
return null;
|
||||
} else {
|
||||
return keyConfig.createKeyManager(environment);
|
||||
}
|
||||
}
|
||||
|
||||
static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings, String trustStoreAlgorithm) {
|
||||
String keyPath = keyPair.keyPath.get(settings).orElse(null);
|
||||
String keyStorePath = keyPair.keystorePath.get(settings).orElse(null);
|
||||
|
||||
if (keyPath != null && keyStorePath != null) {
|
||||
throw new IllegalArgumentException("you cannot specify a keystore and key file");
|
||||
}
|
||||
|
||||
if (keyPath != null) {
|
||||
SecureString keyPassword = keyPair.keyPassword.get(settings);
|
||||
String certPath = keyPair.certificatePath.get(settings).orElse(null);
|
||||
if (certPath == null) {
|
||||
throw new IllegalArgumentException("you must specify the certificates [" + keyPair.certificatePath.getKey()
|
||||
+ "] to use with the key [" + keyPair.keyPath.getKey() + "]");
|
||||
}
|
||||
return new PEMKeyConfig(keyPath, keyPassword, certPath);
|
||||
}
|
||||
|
||||
if (keyStorePath != null) {
|
||||
SecureString keyStorePassword = keyPair.keystorePassword.get(settings);
|
||||
String keyStoreAlgorithm = keyPair.keystoreAlgorithm.get(settings);
|
||||
String keyStoreType = getKeyStoreType(keyPair.keystoreType, settings, keyStorePath);
|
||||
SecureString keyStoreKeyPassword = keyPair.keystoreKeyPassword.get(settings);
|
||||
if (keyStoreKeyPassword.length() == 0) {
|
||||
keyStoreKeyPassword = keyStorePassword;
|
||||
}
|
||||
return new StoreKeyConfig(keyStorePath, keyStoreType, keyStorePassword, keyStoreKeyPassword, keyStoreAlgorithm,
|
||||
trustStoreAlgorithm);
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link X509ExtendedTrustManager} based on the provided certificates
|
||||
*
|
||||
* @param certificates the certificates to trust
|
||||
* @return a trust manager that trusts the provided certificates
|
||||
*/
|
||||
public static X509ExtendedTrustManager trustManager(Certificate[] certificates)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
|
||||
KeyStore store = trustStore(certificates);
|
||||
return trustManager(store, TrustManagerFactory.getDefaultAlgorithm());
|
||||
}
|
||||
|
||||
static KeyStore trustStore(Certificate[] certificates)
|
||||
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
|
||||
assert certificates != null : "Cannot create trust store with null certificates";
|
||||
KeyStore store = KeyStore.getInstance("jks");
|
||||
store.load(null, null);
|
||||
int counter = 0;
|
||||
for (Certificate certificate : certificates) {
|
||||
store.setCertificateEntry("cert" + counter, certificate);
|
||||
counter++;
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the truststore and creates a {@link X509ExtendedTrustManager}
|
||||
*
|
||||
* @param trustStorePath the path to the truststore
|
||||
* @param trustStorePassword the password to the truststore
|
||||
* @param trustStoreAlgorithm the algorithm to use for the truststore
|
||||
* @param env the environment to use for file resolution. May be {@code null}
|
||||
* @return a trust manager with the trust material from the store
|
||||
*/
|
||||
public static X509ExtendedTrustManager trustManager(String trustStorePath, String trustStoreType, char[] trustStorePassword,
|
||||
String trustStoreAlgorithm, @Nullable Environment env)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
|
||||
KeyStore trustStore = readKeyStore(resolvePath(trustStorePath, env), trustStoreType, trustStorePassword);
|
||||
return trustManager(trustStore, trustStoreAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link X509ExtendedTrustManager} based on the trust material in the provided {@link KeyStore}
|
||||
*/
|
||||
static X509ExtendedTrustManager trustManager(KeyStore keyStore, String algorithm)
|
||||
throws NoSuchAlgorithmException, KeyStoreException {
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
|
||||
tmf.init(keyStore);
|
||||
TrustManager[] trustManagers = tmf.getTrustManagers();
|
||||
for (TrustManager trustManager : trustManagers) {
|
||||
if (trustManager instanceof X509ExtendedTrustManager) {
|
||||
return (X509ExtendedTrustManager) trustManager;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("failed to find a X509ExtendedTrustManager");
|
||||
}
|
||||
}
|
@ -1,667 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.DERTaggedObject;
|
||||
import org.bouncycastle.asn1.DERUTF8String;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.asn1.x509.Time;
|
||||
import org.bouncycastle.cert.CertIOException;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.X509TrustedCertificateBlock;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.network.InetAddressHelper;
|
||||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
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 javax.security.auth.x500.X500Principal;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings.getKeyStoreType;
|
||||
|
||||
/**
|
||||
* Utility methods that deal with {@link Certificate}, {@link KeyStore}, {@link X509ExtendedTrustManager}, {@link X509ExtendedKeyManager}
|
||||
* and other certificate related objects.
|
||||
*/
|
||||
public class CertUtils {
|
||||
|
||||
static final String CN_OID = "2.5.4.3";
|
||||
|
||||
private static final int SERIAL_BIT_LENGTH = 20 * 8;
|
||||
static final BouncyCastleProvider BC_PROV = new BouncyCastleProvider();
|
||||
|
||||
private CertUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a path with or without an {@link Environment} as we may be running in a transport client where we do not have access to
|
||||
* the environment
|
||||
*/
|
||||
@SuppressForbidden(reason = "we don't have the environment to resolve files from when running in a transport client")
|
||||
static Path resolvePath(String path, @Nullable Environment environment) {
|
||||
if (environment != null) {
|
||||
return environment.configFile().resolve(path);
|
||||
}
|
||||
return PathUtils.get(path).normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link KeyStore} from a PEM encoded certificate and key file
|
||||
*/
|
||||
static KeyStore getKeyStoreFromPEM(Path certificatePath, Path keyPath, char[] keyPassword)
|
||||
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
|
||||
final PrivateKey key;
|
||||
try (Reader reader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) {
|
||||
key = CertUtils.readPrivateKey(reader, () -> keyPassword);
|
||||
}
|
||||
final Certificate[] certificates = readCertificates(Collections.singletonList(certificatePath));
|
||||
return getKeyStore(certificates, key, keyPassword);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a {@link X509ExtendedKeyManager} that is built from the provided private key and certificate chain
|
||||
*/
|
||||
public static X509ExtendedKeyManager keyManager(Certificate[] certificateChain, PrivateKey privateKey, char[] password)
|
||||
throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException {
|
||||
KeyStore keyStore = getKeyStore(certificateChain, privateKey, password);
|
||||
return keyManager(keyStore, password, KeyManagerFactory.getDefaultAlgorithm());
|
||||
}
|
||||
|
||||
private static KeyStore getKeyStore(Certificate[] certificateChain, PrivateKey privateKey, char[] password)
|
||||
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(null, null);
|
||||
// password must be non-null for keystore...
|
||||
keyStore.setKeyEntry("key", privateKey, password, certificateChain);
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link X509ExtendedKeyManager} that is built from the provided keystore
|
||||
*/
|
||||
static X509ExtendedKeyManager keyManager(KeyStore keyStore, char[] password, String algorithm)
|
||||
throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException {
|
||||
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 IllegalStateException("failed to find a X509ExtendedKeyManager");
|
||||
}
|
||||
|
||||
public static X509ExtendedKeyManager getKeyManager(X509KeyPairSettings keyPair, Settings settings,
|
||||
@Nullable String trustStoreAlgorithm, Environment environment) {
|
||||
if (trustStoreAlgorithm == null) {
|
||||
trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
}
|
||||
final KeyConfig keyConfig = createKeyConfig(keyPair, settings, trustStoreAlgorithm);
|
||||
if (keyConfig == null) {
|
||||
return null;
|
||||
} else {
|
||||
return keyConfig.createKeyManager(environment);
|
||||
}
|
||||
}
|
||||
|
||||
static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings, String trustStoreAlgorithm) {
|
||||
String keyPath = keyPair.keyPath.get(settings).orElse(null);
|
||||
String keyStorePath = keyPair.keystorePath.get(settings).orElse(null);
|
||||
|
||||
if (keyPath != null && keyStorePath != null) {
|
||||
throw new IllegalArgumentException("you cannot specify a keystore and key file");
|
||||
}
|
||||
|
||||
if (keyPath != null) {
|
||||
SecureString keyPassword = keyPair.keyPassword.get(settings);
|
||||
String certPath = keyPair.certificatePath.get(settings).orElse(null);
|
||||
if (certPath == null) {
|
||||
throw new IllegalArgumentException("you must specify the certificates [" + keyPair.certificatePath.getKey()
|
||||
+ "] to use with the key [" + keyPair.keyPath.getKey() + "]");
|
||||
}
|
||||
return new PEMKeyConfig(keyPath, keyPassword, certPath);
|
||||
}
|
||||
|
||||
if (keyStorePath != null) {
|
||||
SecureString keyStorePassword = keyPair.keystorePassword.get(settings);
|
||||
String keyStoreAlgorithm = keyPair.keystoreAlgorithm.get(settings);
|
||||
String keyStoreType = getKeyStoreType(keyPair.keystoreType, settings, keyStorePath);
|
||||
SecureString keyStoreKeyPassword = keyPair.keystoreKeyPassword.get(settings);
|
||||
if (keyStoreKeyPassword.length() == 0) {
|
||||
keyStoreKeyPassword = keyStorePassword;
|
||||
}
|
||||
return new StoreKeyConfig(keyStorePath, keyStoreType, keyStorePassword, keyStoreKeyPassword, keyStoreAlgorithm,
|
||||
trustStoreAlgorithm);
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link X509ExtendedTrustManager} based on the provided certificates
|
||||
*
|
||||
* @param certificates the certificates to trust
|
||||
* @return a trust manager that trusts the provided certificates
|
||||
*/
|
||||
public static X509ExtendedTrustManager trustManager(Certificate[] certificates)
|
||||
throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException {
|
||||
KeyStore store = trustStore(certificates);
|
||||
return trustManager(store, TrustManagerFactory.getDefaultAlgorithm());
|
||||
}
|
||||
|
||||
static KeyStore trustStore(Certificate[] certificates)
|
||||
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
|
||||
assert certificates != null : "Cannot create trust store with null certificates";
|
||||
KeyStore store = KeyStore.getInstance("jks");
|
||||
store.load(null, null);
|
||||
int counter = 0;
|
||||
for (Certificate certificate : certificates) {
|
||||
store.setCertificateEntry("cert" + counter, certificate);
|
||||
counter++;
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the truststore and creates a {@link X509ExtendedTrustManager}
|
||||
*
|
||||
* @param trustStorePath the path to the truststore
|
||||
* @param trustStorePassword the password to the truststore
|
||||
* @param trustStoreAlgorithm the algorithm to use for the truststore
|
||||
* @param env the environment to use for file resolution. May be {@code null}
|
||||
* @return a trust manager with the trust material from the store
|
||||
*/
|
||||
public static X509ExtendedTrustManager trustManager(String trustStorePath, String trustStoreType, char[] trustStorePassword,
|
||||
String trustStoreAlgorithm, @Nullable Environment env)
|
||||
throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException {
|
||||
KeyStore trustStore = readKeyStore(resolvePath(trustStorePath, env), trustStoreType, trustStorePassword);
|
||||
return trustManager(trustStore, trustStoreAlgorithm);
|
||||
}
|
||||
|
||||
static KeyStore readKeyStore(Path path, String type, char[] password)
|
||||
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
|
||||
try (InputStream in = Files.newInputStream(path)) {
|
||||
KeyStore store = KeyStore.getInstance(type);
|
||||
assert password != null;
|
||||
store.load(in, password);
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link X509ExtendedTrustManager} based on the trust material in the provided {@link KeyStore}
|
||||
*/
|
||||
static X509ExtendedTrustManager trustManager(KeyStore keyStore, String algorithm)
|
||||
throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException {
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
|
||||
tmf.init(keyStore);
|
||||
TrustManager[] trustManagers = tmf.getTrustManagers();
|
||||
for (TrustManager trustManager : trustManagers) {
|
||||
if (trustManager instanceof X509ExtendedTrustManager) {
|
||||
return (X509ExtendedTrustManager) trustManager;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("failed to find a X509ExtendedTrustManager");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the provided paths and parses them into {@link Certificate} objects
|
||||
*
|
||||
* @param certPaths the paths to the PEM encoded certificates
|
||||
* @param environment the environment to resolve files against. May be {@code null}
|
||||
* @return an array of {@link Certificate} objects
|
||||
*/
|
||||
public static Certificate[] readCertificates(List<String> certPaths, @Nullable Environment environment)
|
||||
throws CertificateException, IOException {
|
||||
final List<Path> resolvedPaths = certPaths.stream().map(p -> resolvePath(p, environment)).collect(Collectors.toList());
|
||||
return readCertificates(resolvedPaths);
|
||||
}
|
||||
|
||||
public static Certificate[] readCertificates(List<Path> certPaths) throws CertificateException, IOException {
|
||||
List<Certificate> certificates = new ArrayList<>(certPaths.size());
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
for (Path path : certPaths) {
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
readCertificates(reader, certificates, certFactory);
|
||||
}
|
||||
}
|
||||
return certificates.toArray(new Certificate[certificates.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificates from the provided reader
|
||||
*/
|
||||
static void readCertificates(Reader reader, List<Certificate> certificates, CertificateFactory certFactory)
|
||||
throws IOException, CertificateException {
|
||||
try (PEMParser pemParser = new PEMParser(reader)) {
|
||||
|
||||
Object parsed = pemParser.readObject();
|
||||
if (parsed == null) {
|
||||
throw new IllegalArgumentException("could not parse pem certificate");
|
||||
}
|
||||
|
||||
while (parsed != null) {
|
||||
X509CertificateHolder holder;
|
||||
if (parsed instanceof X509CertificateHolder) {
|
||||
holder = (X509CertificateHolder) parsed;
|
||||
} else if (parsed instanceof X509TrustedCertificateBlock) {
|
||||
X509TrustedCertificateBlock certificateBlock = (X509TrustedCertificateBlock) parsed;
|
||||
holder = certificateBlock.getCertificateHolder();
|
||||
} else {
|
||||
String msg = "parsed an unsupported object [" + parsed.getClass().getSimpleName() + "]";
|
||||
if (parsed instanceof PEMEncryptedKeyPair || parsed instanceof PEMKeyPair || parsed instanceof PrivateKeyInfo) {
|
||||
msg = msg + ". Encountered a PEM Key while expecting a PEM certificate.";
|
||||
}
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
certificates.add(certFactory.generateCertificate(new ByteArrayInputStream(holder.getEncoded())));
|
||||
parsed = pemParser.readObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the private key from the reader and optionally uses the password supplier to retrieve a password if the key is encrypted
|
||||
*/
|
||||
public static PrivateKey readPrivateKey(Reader reader, Supplier<char[]> passwordSupplier) throws IOException {
|
||||
try (PEMParser parser = new PEMParser(reader)) {
|
||||
PrivateKeyInfo privateKeyInfo = innerReadPrivateKey(parser, passwordSupplier);
|
||||
if (parser.readObject() != null) {
|
||||
throw new IllegalStateException("key file contained more that one entry");
|
||||
}
|
||||
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
|
||||
converter.setProvider(BC_PROV);
|
||||
return converter.getPrivateKey(privateKeyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static PrivateKeyInfo innerReadPrivateKey(PEMParser parser, Supplier<char[]> passwordSupplier) throws IOException {
|
||||
final Object parsed = parser.readObject();
|
||||
if (parsed == null) {
|
||||
throw new IllegalStateException("key file did not contain a supported key");
|
||||
}
|
||||
|
||||
PrivateKeyInfo privateKeyInfo;
|
||||
if (parsed instanceof PEMEncryptedKeyPair) {
|
||||
char[] keyPassword = passwordSupplier.get();
|
||||
if (keyPassword == null) {
|
||||
throw new IllegalArgumentException("cannot read encrypted key without a password");
|
||||
}
|
||||
// we have an encrypted key pair so we need to decrypt it
|
||||
PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) parsed;
|
||||
privateKeyInfo = encryptedKeyPair
|
||||
.decryptKeyPair(new JcePEMDecryptorProviderBuilder().setProvider(BC_PROV).build(keyPassword))
|
||||
.getPrivateKeyInfo();
|
||||
} else if (parsed instanceof PEMKeyPair) {
|
||||
privateKeyInfo = ((PEMKeyPair) parsed).getPrivateKeyInfo();
|
||||
} else if (parsed instanceof PrivateKeyInfo) {
|
||||
privateKeyInfo = (PrivateKeyInfo) parsed;
|
||||
} else if (parsed instanceof ASN1ObjectIdentifier) {
|
||||
// skip this object and recurse into this method again to read the next object
|
||||
return innerReadPrivateKey(parser, passwordSupplier);
|
||||
} else {
|
||||
String msg = "parsed an unsupported object [" + parsed.getClass().getSimpleName() + "]";
|
||||
if (parsed instanceof X509CertificateHolder || parsed instanceof X509TrustedCertificateBlock) {
|
||||
msg = msg + ". Encountered a PEM Certificate while expecting a PEM Key.";
|
||||
}
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
|
||||
return privateKeyInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all certificate-key pairs from a PKCS#12 container.
|
||||
*
|
||||
* @param path The path to the PKCS#12 container file.
|
||||
* @param password The password for the container file
|
||||
* @param keyPassword A supplier for the password for each key. The key alias is supplied as an argument to the function, and it should
|
||||
* return the password for that key. If it returns {@code null}, then the key-pair for that alias is not read.
|
||||
*/
|
||||
public static Map<Certificate, Key> readPkcs12KeyPairs(Path path, char[] password, Function<String, char[]> keyPassword, Environment
|
||||
env)
|
||||
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
|
||||
final KeyStore store = readKeyStore(path, "PKCS12", password);
|
||||
final Enumeration<String> enumeration = store.aliases();
|
||||
final Map<Certificate, Key> map = new HashMap<>(store.size());
|
||||
while (enumeration.hasMoreElements()) {
|
||||
final String alias = enumeration.nextElement();
|
||||
if (store.isKeyEntry(alias)) {
|
||||
final char[] pass = keyPassword.apply(alias);
|
||||
map.put(store.getCertificate(alias), store.getKey(alias, pass));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a CA certificate
|
||||
*/
|
||||
public static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days)
|
||||
throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
|
||||
return generateSignedCertificate(x500Principal, null, keyPair, null, null, true, days, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signed certificate using the provided CA private key and
|
||||
* information from the CA certificate
|
||||
*
|
||||
* @param principal
|
||||
* the principal of the certificate; commonly referred to as the
|
||||
* distinguished name (DN)
|
||||
* @param subjectAltNames
|
||||
* the subject alternative names that should be added to the
|
||||
* certificate as an X509v3 extension. May be {@code null}
|
||||
* @param keyPair
|
||||
* the key pair that will be associated with the certificate
|
||||
* @param caCert
|
||||
* the CA certificate. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param caPrivKey
|
||||
* the CA private key. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param days
|
||||
* no of days certificate will be valid from now
|
||||
* @return a signed {@link X509Certificate}
|
||||
*/
|
||||
public static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
|
||||
X509Certificate caCert, PrivateKey caPrivKey, int days)
|
||||
throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
|
||||
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signed certificate using the provided CA private key and
|
||||
* information from the CA certificate
|
||||
*
|
||||
* @param principal
|
||||
* the principal of the certificate; commonly referred to as the
|
||||
* distinguished name (DN)
|
||||
* @param subjectAltNames
|
||||
* the subject alternative names that should be added to the
|
||||
* certificate as an X509v3 extension. May be {@code null}
|
||||
* @param keyPair
|
||||
* the key pair that will be associated with the certificate
|
||||
* @param caCert
|
||||
* the CA certificate. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param caPrivKey
|
||||
* the CA private key. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param days
|
||||
* no of days certificate will be valid from now
|
||||
* @param signatureAlgorithm
|
||||
* algorithm used for signing certificate. If {@code null} or
|
||||
* empty, then use default algorithm {@link CertUtils#getDefaultSignatureAlgorithm(PrivateKey)}
|
||||
* @return a signed {@link X509Certificate}
|
||||
*/
|
||||
public static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
|
||||
X509Certificate caCert, PrivateKey caPrivKey, int days, String signatureAlgorithm)
|
||||
throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
|
||||
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, signatureAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signed certificate
|
||||
*
|
||||
* @param principal
|
||||
* the principal of the certificate; commonly referred to as the
|
||||
* distinguished name (DN)
|
||||
* @param subjectAltNames
|
||||
* the subject alternative names that should be added to the
|
||||
* certificate as an X509v3 extension. May be {@code null}
|
||||
* @param keyPair
|
||||
* the key pair that will be associated with the certificate
|
||||
* @param caCert
|
||||
* the CA certificate. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param caPrivKey
|
||||
* the CA private key. If {@code null}, this results in a self signed
|
||||
* certificate
|
||||
* @param isCa
|
||||
* whether or not the generated certificate is a CA
|
||||
* @param days
|
||||
* no of days certificate will be valid from now
|
||||
* @param signatureAlgorithm
|
||||
* algorithm used for signing certificate. If {@code null} or
|
||||
* empty, then use default algorithm {@link CertUtils#getDefaultSignatureAlgorithm(PrivateKey)}
|
||||
* @return a signed {@link X509Certificate}
|
||||
*/
|
||||
private static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
|
||||
X509Certificate caCert, PrivateKey caPrivKey, boolean isCa, int days, String signatureAlgorithm)
|
||||
throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
|
||||
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
|
||||
final DateTime notBefore = new DateTime(DateTimeZone.UTC);
|
||||
if (days < 1) {
|
||||
throw new IllegalArgumentException("the certificate must be valid for at least one day");
|
||||
}
|
||||
final DateTime notAfter = notBefore.plusDays(days);
|
||||
final BigInteger serial = CertUtils.getSerial();
|
||||
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
|
||||
|
||||
X500Name subject = X500Name.getInstance(principal.getEncoded());
|
||||
final X500Name issuer;
|
||||
final AuthorityKeyIdentifier authorityKeyIdentifier;
|
||||
if (caCert != null) {
|
||||
if (caCert.getBasicConstraints() < 0) {
|
||||
throw new IllegalArgumentException("ca certificate is not a CA!");
|
||||
}
|
||||
issuer = X500Name.getInstance(caCert.getIssuerX500Principal().getEncoded());
|
||||
authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey());
|
||||
} else {
|
||||
issuer = subject;
|
||||
authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(keyPair.getPublic());
|
||||
}
|
||||
|
||||
JcaX509v3CertificateBuilder builder =
|
||||
new JcaX509v3CertificateBuilder(issuer, serial,
|
||||
new Time(notBefore.toDate(), Locale.ROOT), new Time(notAfter.toDate(), Locale.ROOT), subject, keyPair.getPublic());
|
||||
|
||||
builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(keyPair.getPublic()));
|
||||
builder.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier);
|
||||
if (subjectAltNames != null) {
|
||||
builder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
|
||||
}
|
||||
builder.addExtension(Extension.basicConstraints, isCa, new BasicConstraints(isCa));
|
||||
|
||||
PrivateKey signingKey = caPrivKey != null ? caPrivKey : keyPair.getPrivate();
|
||||
ContentSigner signer = new JcaContentSignerBuilder(
|
||||
(Strings.isNullOrEmpty(signatureAlgorithm)) ? getDefaultSignatureAlgorithm(signingKey) : signatureAlgorithm)
|
||||
.setProvider(CertUtils.BC_PROV).build(signingKey);
|
||||
X509CertificateHolder certificateHolder = builder.build(signer);
|
||||
return new JcaX509CertificateConverter().getCertificate(certificateHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the private key algorithm {@link PrivateKey#getAlgorithm()}
|
||||
* determines default signing algorithm used by CertUtils
|
||||
*
|
||||
* @param key
|
||||
* {@link PrivateKey}
|
||||
* @return algorithm
|
||||
*/
|
||||
private static String getDefaultSignatureAlgorithm(PrivateKey key) {
|
||||
String signatureAlgorithm = null;
|
||||
switch (key.getAlgorithm()) {
|
||||
case "RSA":
|
||||
signatureAlgorithm = "SHA256withRSA";
|
||||
break;
|
||||
case "DSA":
|
||||
signatureAlgorithm = "SHA256withDSA";
|
||||
break;
|
||||
case "EC":
|
||||
signatureAlgorithm = "SHA256withECDSA";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported algorithm : " + key.getAlgorithm()
|
||||
+ " for signature, allowed values for private key algorithm are [RSA, DSA, EC]");
|
||||
}
|
||||
return signatureAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a certificate signing request
|
||||
*
|
||||
* @param keyPair the key pair that will be associated by the certificate generated from the certificate signing request
|
||||
* @param principal the principal of the certificate; commonly referred to as the distinguished name (DN)
|
||||
* @param sanList the subject alternative names that should be added to the certificate as an X509v3 extension. May be
|
||||
* {@code null}
|
||||
* @return a certificate signing request
|
||||
*/
|
||||
static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList)
|
||||
throws IOException, OperatorCreationException {
|
||||
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
|
||||
Objects.requireNonNull(keyPair.getPublic(), "Public-Key must not be null");
|
||||
Objects.requireNonNull(principal, "Principal must not be null");
|
||||
JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(principal, keyPair.getPublic());
|
||||
if (sanList != null) {
|
||||
ExtensionsGenerator extGen = new ExtensionsGenerator();
|
||||
extGen.addExtension(Extension.subjectAlternativeName, false, sanList);
|
||||
builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate());
|
||||
}
|
||||
|
||||
return builder.build(new JcaContentSignerBuilder("SHA256withRSA").setProvider(CertUtils.BC_PROV).build(keyPair.getPrivate()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a random serial for a certificate that is generated from a {@link SecureRandom}
|
||||
*/
|
||||
public static BigInteger getSerial() {
|
||||
SecureRandom random = new SecureRandom();
|
||||
BigInteger serial = new BigInteger(SERIAL_BIT_LENGTH, random);
|
||||
assert serial.compareTo(BigInteger.valueOf(0L)) >= 0;
|
||||
return serial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a RSA key pair with the provided key size (in bits)
|
||||
*/
|
||||
public static KeyPair generateKeyPair(int keysize) throws NoSuchAlgorithmException {
|
||||
// generate a private key
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(keysize);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@link InetAddress} objects into a {@link GeneralNames} object that is used to represent subject alternative names.
|
||||
*/
|
||||
public static GeneralNames getSubjectAlternativeNames(boolean resolveName, Set<InetAddress> addresses) throws SocketException {
|
||||
Set<GeneralName> generalNameList = new HashSet<>();
|
||||
for (InetAddress address : addresses) {
|
||||
if (address.isAnyLocalAddress()) {
|
||||
// it is a wildcard address
|
||||
for (InetAddress inetAddress : InetAddressHelper.getAllAddresses()) {
|
||||
addSubjectAlternativeNames(resolveName, inetAddress, generalNameList);
|
||||
}
|
||||
} else {
|
||||
addSubjectAlternativeNames(resolveName, address, generalNameList);
|
||||
}
|
||||
}
|
||||
return new GeneralNames(generalNameList.toArray(new GeneralName[generalNameList.size()]));
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "need to use getHostName to resolve DNS name and getHostAddress to ensure we resolved the name")
|
||||
private static void addSubjectAlternativeNames(boolean resolveName, InetAddress inetAddress, Set<GeneralName> list) {
|
||||
String hostaddress = inetAddress.getHostAddress();
|
||||
String ip = NetworkAddress.format(inetAddress);
|
||||
list.add(new GeneralName(GeneralName.iPAddress, ip));
|
||||
if (resolveName && (inetAddress.isLinkLocalAddress() == false)) {
|
||||
String possibleHostName = inetAddress.getHostName();
|
||||
if (possibleHostName.equals(hostaddress) == false) {
|
||||
list.add(new GeneralName(GeneralName.dNSName, possibleHostName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an X.509 {@link GeneralName} for use as a <em>Common Name</em> in the certificate's <em>Subject Alternative Names</em>
|
||||
* extension. A <em>common name</em> is a name with a tag of {@link GeneralName#otherName OTHER}, with an object-id that references
|
||||
* the {@link #CN_OID cn} attribute, an explicit tag of '0', and a DER encoded UTF8 string for the name.
|
||||
* This usage of using the {@code cn} OID as a <em>Subject Alternative Name</em> is <strong>non-standard</strong> and will not be
|
||||
* recognised by other X.509/TLS implementations.
|
||||
*/
|
||||
public static GeneralName createCommonName(String cn) {
|
||||
final ASN1Encodable[] sequence = { new ASN1ObjectIdentifier(CN_OID), new DERTaggedObject(true, 0, new DERUTF8String(cn)) };
|
||||
return new GeneralName(GeneralName.otherName, new DERSequence(sequence));
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import joptsimple.OptionSpec;
|
||||
import org.bouncycastle.asn1.DERIA5String;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMEncryptor;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
|
||||
@ -82,6 +83,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
|
||||
private static final Pattern ALLOWED_FILENAME_CHAR_PATTERN =
|
||||
Pattern.compile("[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1," + MAX_FILENAME_LENGTH + "}");
|
||||
private static final int DEFAULT_KEY_SIZE = 2048;
|
||||
private static final BouncyCastleProvider BC_PROV = new BouncyCastleProvider();
|
||||
|
||||
/**
|
||||
* Wraps the certgen object parser.
|
||||
@ -316,10 +318,10 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
|
||||
static void generateAndWriteCsrs(Path outputFile, Collection<CertificateInformation> certInfo, int keysize) throws Exception {
|
||||
fullyWriteFile(outputFile, (outputStream, pemWriter) -> {
|
||||
for (CertificateInformation certificateInformation : certInfo) {
|
||||
KeyPair keyPair = CertUtils.generateKeyPair(keysize);
|
||||
KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
|
||||
GeneralNames sanList = getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames,
|
||||
certificateInformation.commonNames);
|
||||
PKCS10CertificationRequest csr = CertUtils.generateCSR(keyPair, certificateInformation.name.x500Principal, sanList);
|
||||
PKCS10CertificationRequest csr = CertGenUtils.generateCSR(keyPair, certificateInformation.name.x500Principal, sanList);
|
||||
|
||||
final String dirName = certificateInformation.name.filename + "/";
|
||||
ZipEntry zipEntry = new ZipEntry(dirName);
|
||||
@ -361,7 +363,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
|
||||
if (caCertPath != null) {
|
||||
assert caKeyPath != null;
|
||||
final String resolvedCaCertPath = resolvePath(caCertPath).toAbsolutePath().toString();
|
||||
Certificate[] certificates = CertUtils.readCertificates(Collections.singletonList(resolvedCaCertPath), env);
|
||||
Certificate[] certificates = CertParsingUtils.readCertificates(Collections.singletonList(resolvedCaCertPath), env);
|
||||
if (certificates.length != 1) {
|
||||
throw new IllegalArgumentException("expected a single certificate in file [" + caCertPath + "] but found [" +
|
||||
certificates.length + "]");
|
||||
@ -373,8 +375,8 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
|
||||
|
||||
// generate the CA keys and cert
|
||||
X500Principal x500Principal = new X500Principal(dn);
|
||||
KeyPair keyPair = CertUtils.generateKeyPair(keysize);
|
||||
Certificate caCert = CertUtils.generateCACertificate(x500Principal, keyPair, days);
|
||||
KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
|
||||
Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, days);
|
||||
final char[] password;
|
||||
if (prompt) {
|
||||
password = terminal.readSecret("Enter password for CA private key: ");
|
||||
@ -399,8 +401,8 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
|
||||
writeCAInfoIfGenerated(outputStream, pemWriter, caInfo);
|
||||
|
||||
for (CertificateInformation certificateInformation : certificateInformations) {
|
||||
KeyPair keyPair = CertUtils.generateKeyPair(keysize);
|
||||
Certificate certificate = CertUtils.generateSignedCertificate(certificateInformation.name.x500Principal,
|
||||
KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
|
||||
Certificate certificate = CertGenUtils.generateSignedCertificate(certificateInformation.name.x500Principal,
|
||||
getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames,
|
||||
certificateInformation.commonNames),
|
||||
keyPair, caInfo.caCert, caInfo.privateKey, days);
|
||||
@ -483,7 +485,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
|
||||
outputStream.putNextEntry(new ZipEntry(caDirName + "ca.key"));
|
||||
if (info.password != null && info.password.length > 0) {
|
||||
try {
|
||||
PEMEncryptor encryptor = new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build(info.password);
|
||||
PEMEncryptor encryptor = new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(BC_PROV).build(info.password);
|
||||
pemWriter.writeObject(info.privateKey, encryptor);
|
||||
} finally {
|
||||
// we can safely nuke the password chars now
|
||||
@ -584,8 +586,8 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
|
||||
private static PrivateKey readPrivateKey(String path, char[] password, Terminal terminal, boolean prompt)
|
||||
throws Exception {
|
||||
AtomicReference<char[]> passwordReference = new AtomicReference<>(password);
|
||||
try (Reader reader = Files.newBufferedReader(resolvePath(path), StandardCharsets.UTF_8)) {
|
||||
return CertUtils.readPrivateKey(reader, () -> {
|
||||
try {
|
||||
return PemUtils.readPrivateKey(resolvePath(path), () -> {
|
||||
if (password != null || prompt == false) {
|
||||
return password;
|
||||
}
|
||||
@ -611,7 +613,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
|
||||
}
|
||||
|
||||
for (String cn : commonNames) {
|
||||
generalNameList.add(CertUtils.createCommonName(cn));
|
||||
generalNameList.add(CertGenUtils.createCommonName(cn));
|
||||
}
|
||||
|
||||
if (generalNameList.isEmpty()) {
|
||||
|
@ -12,6 +12,7 @@ import joptsimple.OptionSpecBuilder;
|
||||
import org.bouncycastle.asn1.DERIA5String;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMEncryptor;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
|
||||
@ -87,6 +88,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
|
||||
private static final String DEFAULT_CERT_ZIP = "certificate-bundle.zip";
|
||||
private static final String DEFAULT_CA_ZIP = "elastic-stack-ca.zip";
|
||||
private static final String DEFAULT_CA_P12 = "elastic-stack-ca.p12";
|
||||
private static final BouncyCastleProvider BC_PROV = new BouncyCastleProvider();
|
||||
|
||||
static final String DEFAULT_CERT_NAME = "instance";
|
||||
|
||||
@ -334,7 +336,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
|
||||
char[] passwordOption = getChars(caPasswordSpec.value(options));
|
||||
|
||||
Map<Certificate, Key> keys = withPassword("CA (" + path + ")", passwordOption,
|
||||
terminal, password -> CertUtils.readPkcs12KeyPairs(path, password, a -> password, env));
|
||||
terminal, password -> CertParsingUtils.readPkcs12KeyPairs(path, password, a -> password));
|
||||
|
||||
if (keys.size() != 1) {
|
||||
throw new IllegalArgumentException("expected a single key in file [" + path.toAbsolutePath() + "] but found [" +
|
||||
@ -353,7 +355,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
|
||||
String password = caPasswordSpec.value(options);
|
||||
|
||||
final String resolvedCaCertPath = cert.toAbsolutePath().toString();
|
||||
Certificate[] certificates = CertUtils.readCertificates(Collections.singletonList(resolvedCaCertPath), env);
|
||||
Certificate[] certificates = CertParsingUtils.readCertificates(Collections.singletonList(resolvedCaCertPath), env);
|
||||
if (certificates.length != 1) {
|
||||
throw new IllegalArgumentException("expected a single certificate in file [" + resolvedCaCertPath + "] but found [" +
|
||||
certificates.length + "]");
|
||||
@ -369,8 +371,8 @@ public class CertificateTool extends LoggingAwareMultiCommand {
|
||||
dn = AUTO_GEN_CA_DN;
|
||||
}
|
||||
X500Principal x500Principal = new X500Principal(dn);
|
||||
KeyPair keyPair = CertUtils.generateKeyPair(getKeySize(options));
|
||||
X509Certificate caCert = CertUtils.generateCACertificate(x500Principal, keyPair, getDays(options));
|
||||
KeyPair keyPair = CertGenUtils.generateKeyPair(getKeySize(options));
|
||||
X509Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, getDays(options));
|
||||
|
||||
if (options.hasArgument(caPasswordSpec)) {
|
||||
char[] password = getChars(caPasswordSpec.value(options));
|
||||
@ -612,10 +614,10 @@ public class CertificateTool extends LoggingAwareMultiCommand {
|
||||
void generateAndWriteCsrs(Path output, int keySize, Collection<CertificateInformation> certInfo) throws Exception {
|
||||
fullyWriteZipFile(output, (outputStream, pemWriter) -> {
|
||||
for (CertificateInformation certificateInformation : certInfo) {
|
||||
KeyPair keyPair = CertUtils.generateKeyPair(keySize);
|
||||
KeyPair keyPair = CertGenUtils.generateKeyPair(keySize);
|
||||
GeneralNames sanList = getSubjectAlternativeNamesValue(certificateInformation.ipAddresses,
|
||||
certificateInformation.dnsNames, certificateInformation.commonNames);
|
||||
PKCS10CertificationRequest csr = CertUtils.generateCSR(keyPair, certificateInformation.name.x500Principal, sanList);
|
||||
PKCS10CertificationRequest csr = CertGenUtils.generateCSR(keyPair, certificateInformation.name.x500Principal, sanList);
|
||||
|
||||
final String dirName = certificateInformation.name.filename + "/";
|
||||
ZipEntry zipEntry = new ZipEntry(dirName);
|
||||
@ -819,8 +821,8 @@ public class CertificateTool extends LoggingAwareMultiCommand {
|
||||
|
||||
private CertificateAndKey generateCertificateAndKey(CertificateInformation certificateInformation, CAInfo caInfo,
|
||||
int keySize, int days) throws Exception {
|
||||
KeyPair keyPair = CertUtils.generateKeyPair(keySize);
|
||||
Certificate certificate = CertUtils.generateSignedCertificate(certificateInformation.name.x500Principal,
|
||||
KeyPair keyPair = CertGenUtils.generateKeyPair(keySize);
|
||||
Certificate certificate = CertGenUtils.generateSignedCertificate(certificateInformation.name.x500Principal,
|
||||
getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames,
|
||||
certificateInformation.commonNames),
|
||||
keyPair, caInfo.certAndKey.cert, caInfo.certAndKey.key, days);
|
||||
@ -916,7 +918,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
|
||||
}
|
||||
|
||||
private static PEMEncryptor getEncrypter(char[] password) {
|
||||
return new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build(password);
|
||||
return new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(BC_PROV).build(password);
|
||||
}
|
||||
|
||||
private static <T, E extends Exception> T withPassword(String description, char[] password, Terminal terminal,
|
||||
@ -1015,8 +1017,8 @@ public class CertificateTool extends LoggingAwareMultiCommand {
|
||||
private static PrivateKey readPrivateKey(Path path, char[] password, Terminal terminal)
|
||||
throws Exception {
|
||||
AtomicReference<char[]> passwordReference = new AtomicReference<>(password);
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
return CertUtils.readPrivateKey(reader, () -> {
|
||||
try {
|
||||
return PemUtils.readPrivateKey(path, () -> {
|
||||
if (password != null) {
|
||||
return password;
|
||||
}
|
||||
@ -1042,7 +1044,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
|
||||
}
|
||||
|
||||
for (String cn : commonNames) {
|
||||
generalNameList.add(CertUtils.createCommonName(cn));
|
||||
generalNameList.add(CertGenUtils.createCommonName(cn));
|
||||
}
|
||||
|
||||
if (generalNameList.isEmpty()) {
|
||||
|
@ -34,7 +34,7 @@ class DefaultJDKTrustConfig extends TrustConfig {
|
||||
@Override
|
||||
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
|
||||
try {
|
||||
return CertUtils.trustManager(null, TrustManagerFactory.getDefaultAlgorithm());
|
||||
return CertParsingUtils.trustManager(null, TrustManagerFactory.getDefaultAlgorithm());
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||
}
|
||||
|
@ -0,0 +1,285 @@
|
||||
/*
|
||||
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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* <p/>In BER/DER encoding, length can be encoded in 2 forms,
|
||||
* <ul>
|
||||
* <li>Short form. One octet. Bit 8 has value "0" and bits 7-1
|
||||
* give the length.
|
||||
* </li>
|
||||
* <li>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.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @return The length as integer
|
||||
* @throws IOException
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
* <p/>The first byte in DER encoding is made of following fields,
|
||||
* <pre>
|
||||
* -------------------------------------------------
|
||||
* |Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1|
|
||||
* -------------------------------------------------
|
||||
* | Class | CF | + Type |
|
||||
* -------------------------------------------------
|
||||
* </pre>
|
||||
* <ul>
|
||||
* <li>Class: Universal, Application, Context or Private
|
||||
* <li>CF: Constructed flag. If 1, the field is constructed.
|
||||
* <li>Type: This is actually called tag in ASN.1. It
|
||||
* indicates data type (Integer, String) or a construct
|
||||
* (sequence, choice, set).
|
||||
* </ul>
|
||||
*
|
||||
* @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.
|
||||
* @throws IOException
|
||||
*/
|
||||
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
|
||||
* @throws IOException
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -58,20 +58,20 @@ class PEMKeyConfig extends KeyConfig {
|
||||
@Override
|
||||
X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
|
||||
try {
|
||||
PrivateKey privateKey = readPrivateKey(CertUtils.resolvePath(keyPath, environment), keyPassword);
|
||||
PrivateKey privateKey = readPrivateKey(CertParsingUtils.resolvePath(keyPath, environment), keyPassword);
|
||||
if (privateKey == null) {
|
||||
throw new IllegalArgumentException("private key [" + keyPath + "] could not be loaded");
|
||||
}
|
||||
Certificate[] certificateChain = getCertificateChain(environment);
|
||||
|
||||
return CertUtils.keyManager(certificateChain, privateKey, keyPassword.getChars());
|
||||
return CertParsingUtils.keyManager(certificateChain, privateKey, keyPassword.getChars());
|
||||
} catch (IOException | UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
|
||||
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Certificate[] getCertificateChain(@Nullable Environment environment) throws CertificateException, IOException {
|
||||
return CertUtils.readCertificates(Collections.singletonList(certPath), environment);
|
||||
return CertParsingUtils.readCertificates(Collections.singletonList(certPath), environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -90,23 +90,21 @@ class PEMKeyConfig extends KeyConfig {
|
||||
@Override
|
||||
List<PrivateKey> privateKeys(@Nullable Environment environment) {
|
||||
try {
|
||||
return Collections.singletonList(readPrivateKey(CertUtils.resolvePath(keyPath, environment), keyPassword));
|
||||
return Collections.singletonList(readPrivateKey(CertParsingUtils.resolvePath(keyPath, environment), keyPassword));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("failed to read key", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static PrivateKey readPrivateKey(Path keyPath, SecureString keyPassword) throws IOException {
|
||||
try (Reader reader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) {
|
||||
return CertUtils.readPrivateKey(reader, keyPassword::getChars);
|
||||
}
|
||||
return PemUtils.readPrivateKey(keyPath, keyPassword::getChars);
|
||||
}
|
||||
|
||||
@Override
|
||||
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
|
||||
try {
|
||||
Certificate[] certificates = getCertificateChain(environment);
|
||||
return CertUtils.trustManager(certificates);
|
||||
return CertParsingUtils.trustManager(certificates);
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||
}
|
||||
@ -115,8 +113,8 @@ class PEMKeyConfig extends KeyConfig {
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
List<Path> paths = new ArrayList<>(2);
|
||||
paths.add(CertUtils.resolvePath(keyPath, environment));
|
||||
paths.add(CertUtils.resolvePath(certPath, environment));
|
||||
paths.add(CertParsingUtils.resolvePath(keyPath, environment));
|
||||
paths.add(CertParsingUtils.resolvePath(certPath, environment));
|
||||
return paths;
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,8 @@ class PEMTrustConfig extends TrustConfig {
|
||||
@Override
|
||||
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
|
||||
try {
|
||||
Certificate[] certificates = CertUtils.readCertificates(caPaths, environment);
|
||||
return CertUtils.trustManager(certificates);
|
||||
Certificate[] certificates = CertParsingUtils.readCertificates(caPaths, environment);
|
||||
return CertParsingUtils.trustManager(certificates);
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||
}
|
||||
@ -53,7 +53,7 @@ class PEMTrustConfig extends TrustConfig {
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws CertificateException, IOException {
|
||||
final List<CertificateInfo> info = new ArrayList<>(caPaths.size());
|
||||
for (String path : caPaths) {
|
||||
Certificate[] chain = CertUtils.readCertificates(Collections.singletonList(path), environment);
|
||||
Certificate[] chain = CertParsingUtils.readCertificates(Collections.singletonList(path), environment);
|
||||
for (final Certificate cert : chain) {
|
||||
if (cert instanceof X509Certificate) {
|
||||
info.add(new CertificateInfo(path, "PEM", null, false, (X509Certificate) cert));
|
||||
@ -67,7 +67,7 @@ class PEMTrustConfig extends TrustConfig {
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
List<Path> paths = new ArrayList<>(caPaths.size());
|
||||
for (String path : caPaths) {
|
||||
paths.add(CertUtils.resolvePath(path, environment));
|
||||
paths.add(CertParsingUtils.resolvePath(path, environment));
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
@ -0,0 +1,559 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.elasticsearch.common.hash.MessageDigests;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.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.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 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<char[]> passwordSupplier) {
|
||||
try (BufferedReader bReader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) {
|
||||
String 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 (IOException | 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
|
||||
*
|
||||
* @param bReader
|
||||
* @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
|
||||
*
|
||||
* @param bReader
|
||||
* @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<char[]> passwordSupplier) throws IOException,
|
||||
GeneralSecurityException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = bReader.readLine();
|
||||
Map<String, String> 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<char[]> passwordSupplier) throws IOException,
|
||||
GeneralSecurityException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = bReader.readLine();
|
||||
Map<String, String> 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<char[]> passwordSupplier) throws IOException,
|
||||
GeneralSecurityException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = bReader.readLine();
|
||||
Map<String, String> 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<String, String> pemHeaders, String keyContents, Supplier<char[]> 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 {
|
||||
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)
|
||||
* <p>
|
||||
* 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);
|
||||
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");
|
||||
}
|
||||
}
|
@ -85,7 +85,7 @@ public final class RestrictedTrustConfig extends TrustConfig {
|
||||
}
|
||||
|
||||
private Path resolveGroupConfigPath(@Nullable Environment environment) {
|
||||
return CertUtils.resolvePath(groupConfigPath, environment);
|
||||
return CertParsingUtils.resolvePath(groupConfigPath, environment);
|
||||
}
|
||||
|
||||
private CertificateTrustRestrictions readTrustGroup(Path path) throws IOException {
|
||||
|
@ -7,18 +7,13 @@ package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.ASN1String;
|
||||
import org.bouncycastle.asn1.ASN1TaggedObject;
|
||||
import org.bouncycastle.asn1.DERTaggedObject;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
@ -41,10 +36,11 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public final class RestrictedTrustManager extends X509ExtendedTrustManager {
|
||||
|
||||
private static final String CN_OID = "2.5.4.3";
|
||||
private static final int SAN_CODE_OTHERNAME = 0;
|
||||
private final Logger logger;
|
||||
private final X509ExtendedTrustManager delegate;
|
||||
private final CertificateTrustRestrictions trustRestrictions;
|
||||
private final int SAN_CODE_OTHERNAME = 0;
|
||||
|
||||
public RestrictedTrustManager(Settings settings, X509ExtendedTrustManager delegate, CertificateTrustRestrictions restrictions) {
|
||||
this.logger = Loggers.getLogger(getClass(), settings);
|
||||
@ -127,47 +123,54 @@ public final class RestrictedTrustManager extends X509ExtendedTrustManager {
|
||||
return getSubjectAlternativeNames(certificate).stream()
|
||||
.filter(pair -> ((Integer) pair.get(0)).intValue() == SAN_CODE_OTHERNAME)
|
||||
.map(pair -> pair.get(1))
|
||||
.map(value -> {
|
||||
ASN1Sequence seq = ASN1Sequence.getInstance(value);
|
||||
if (seq.size() != 2) {
|
||||
String message = "Incorrect sequence length for 'other name' [" + seq + "]";
|
||||
assert false : message;
|
||||
logger.warn(message);
|
||||
return null;
|
||||
}
|
||||
final String id = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0)).getId();
|
||||
if (CertUtils.CN_OID.equals(id)) {
|
||||
ASN1TaggedObject tagged = DERTaggedObject.getInstance(seq.getObjectAt(1));
|
||||
// The JRE's handling of OtherNames is buggy.
|
||||
// The internal sun classes go to a lot of trouble to parse the GeneralNames into real object
|
||||
// And then java.security.cert.X509Certificate just turns them back into bytes
|
||||
// But in doing so, it ends up wrapping the "other name" bytes with a second tag
|
||||
// Specifically: sun.security.x509.OtherName(DerValue) never decodes the tagged "nameValue"
|
||||
// But: sun.security.x509.OtherName.encode() wraps the nameValue in a DER Tag.
|
||||
// So, there's a good chance that our tagged nameValue contains... a tagged name value.
|
||||
if (tagged.getObject() instanceof ASN1TaggedObject) {
|
||||
tagged = (ASN1TaggedObject) tagged.getObject();
|
||||
}
|
||||
final ASN1Primitive nameValue = tagged.getObject();
|
||||
if (nameValue instanceof ASN1String) {
|
||||
final String cn = ((ASN1String) nameValue).getString();
|
||||
logger.trace("Read cn [{}] from ASN1Sequence [{}]", cn, seq);
|
||||
return cn;
|
||||
} else {
|
||||
logger.warn("Certificate [{}] has 'otherName' [{}] with unsupported name-value type [{}]",
|
||||
certificate.getSubjectDN(), seq, nameValue.getClass().getSimpleName());
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Certificate [{}] has 'otherName' [{}] with unsupported object-id [{}]",
|
||||
certificate.getSubjectDN(), seq, id);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.map(value -> decodeDerValue((byte[]) value, certificate))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the otherName CN from the certificate
|
||||
*
|
||||
* @param value The DER Encoded Subject Alternative Name
|
||||
* @param certificate
|
||||
* @return the CN or null if it could not be parsed
|
||||
*/
|
||||
private String decodeDerValue(byte[] value, X509Certificate certificate) {
|
||||
try {
|
||||
DerParser parser = new DerParser(value);
|
||||
DerParser.Asn1Object seq = parser.readAsn1Object();
|
||||
parser = seq.getParser();
|
||||
String id = parser.readAsn1Object().getOid();
|
||||
if (CN_OID.equals(id)) {
|
||||
// Get the DER object with explicit 0 tag
|
||||
DerParser.Asn1Object cnObject = parser.readAsn1Object();
|
||||
parser = cnObject.getParser();
|
||||
// The JRE's handling of OtherNames is buggy.
|
||||
// The internal sun classes go to a lot of trouble to parse the GeneralNames into real object
|
||||
// And then java.security.cert.X509Certificate just turns them back into bytes
|
||||
// But in doing so, it ends up wrapping the "other name" bytes with a second tag
|
||||
// Specifically: sun.security.x509.OtherName(DerValue) never decodes the tagged "nameValue"
|
||||
// But: sun.security.x509.OtherName.encode() wraps the nameValue in a DER Tag.
|
||||
// So, there's a good chance that our tagged nameValue contains... a tagged name value.
|
||||
DerParser.Asn1Object innerObject = parser.readAsn1Object();
|
||||
if (innerObject.isConstructed()) {
|
||||
innerObject = innerObject.getParser().readAsn1Object();
|
||||
}
|
||||
logger.trace("Read innermost ASN.1 Object with type code [{}]", innerObject.getType());
|
||||
String cn = innerObject.getString();
|
||||
logger.trace("Read cn [{}] from ASN1Sequence [{}]", cn, seq);
|
||||
return cn;
|
||||
} else {
|
||||
logger.debug("Certificate [{}] has 'otherName' [{}] with unsupported object-id [{}]",
|
||||
certificate.getSubjectDN(), seq, id);
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to read 'otherName' from certificate [{}]",
|
||||
certificate.getSubjectDN());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<List<?>> getSubjectAlternativeNames(X509Certificate certificate) throws CertificateParsingException {
|
||||
final Collection<List<?>> sans = certificate.getSubjectAlternativeNames();
|
||||
|
@ -184,7 +184,7 @@ public final class SSLConfiguration {
|
||||
|
||||
private static KeyConfig createKeyConfig(Settings settings, SSLConfiguration global) {
|
||||
final String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings);
|
||||
final KeyConfig config = CertUtils.createKeyConfig(SETTINGS_PARSER.x509KeyPair, settings, trustStoreAlgorithm);
|
||||
final KeyConfig config = CertParsingUtils.createKeyConfig(SETTINGS_PARSER.x509KeyPair, settings, trustStoreAlgorithm);
|
||||
if (config != null) {
|
||||
return config;
|
||||
}
|
||||
|
@ -589,9 +589,9 @@ public class SSLService extends AbstractComponent {
|
||||
private void reloadSslContext() {
|
||||
try {
|
||||
X509ExtendedKeyManager loadedKeyManager = Optional.ofNullable(keyConfig.createKeyManager(env)).
|
||||
orElse(getEmptyKeyManager());
|
||||
orElse(getEmptyKeyManager());
|
||||
X509ExtendedTrustManager loadedTrustManager = Optional.ofNullable(trustConfig.createTrustManager(env)).
|
||||
orElse(getEmptyTrustManager());
|
||||
orElse(getEmptyTrustManager());
|
||||
SSLContext loadedSslContext = SSLContext.getInstance(sslContextAlgorithm(sslConfiguration.supportedProtocols()));
|
||||
loadedSslContext.init(new X509ExtendedKeyManager[]{loadedKeyManager},
|
||||
new X509ExtendedTrustManager[]{loadedTrustManager}, null);
|
||||
@ -601,6 +601,7 @@ public class SSLService extends AbstractComponent {
|
||||
throw new ElasticsearchException("failed to initialize the SSLContext", e);
|
||||
}
|
||||
}
|
||||
|
||||
X509ExtendedKeyManager getEmptyKeyManager() throws GeneralSecurityException, IOException {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null, null);
|
||||
|
@ -73,7 +73,7 @@ class StoreKeyConfig extends KeyConfig {
|
||||
try {
|
||||
KeyStore ks = getKeyStore(environment);
|
||||
checkKeyStore(ks);
|
||||
return CertUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm);
|
||||
return CertParsingUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm);
|
||||
} catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
|
||||
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
|
||||
}
|
||||
@ -82,7 +82,7 @@ class StoreKeyConfig extends KeyConfig {
|
||||
@Override
|
||||
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
|
||||
try {
|
||||
return CertUtils.trustManager(keyStorePath, keyStoreType, keyStorePassword.getChars(), trustStoreAlgorithm, environment);
|
||||
return CertParsingUtils.trustManager(keyStorePath, keyStoreType, keyStorePassword.getChars(), trustStoreAlgorithm, environment);
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||
}
|
||||
@ -90,8 +90,8 @@ class StoreKeyConfig extends KeyConfig {
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
final Path path = CertUtils.resolvePath(keyStorePath, environment);
|
||||
final KeyStore trustStore = CertUtils.readKeyStore(path, keyStoreType, keyStorePassword.getChars());
|
||||
final Path path = CertParsingUtils.resolvePath(keyStorePath, environment);
|
||||
final KeyStore trustStore = CertParsingUtils.readKeyStore(path, keyStoreType, keyStorePassword.getChars());
|
||||
final List<CertificateInfo> certificates = new ArrayList<>();
|
||||
final Enumeration<String> aliases = trustStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
@ -112,7 +112,7 @@ class StoreKeyConfig extends KeyConfig {
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
return Collections.singletonList(CertUtils.resolvePath(keyStorePath, environment));
|
||||
return Collections.singletonList(CertParsingUtils.resolvePath(keyStorePath, environment));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -137,7 +137,7 @@ class StoreKeyConfig extends KeyConfig {
|
||||
|
||||
private KeyStore getKeyStore(@Nullable Environment environment)
|
||||
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
|
||||
try (InputStream in = Files.newInputStream(CertUtils.resolvePath(keyStorePath, environment))) {
|
||||
try (InputStream in = Files.newInputStream(CertParsingUtils.resolvePath(keyStorePath, environment))) {
|
||||
KeyStore ks = KeyStore.getInstance(keyStoreType);
|
||||
ks.load(in, keyStorePassword.getChars());
|
||||
return ks;
|
||||
|
@ -55,7 +55,8 @@ class StoreTrustConfig extends TrustConfig {
|
||||
@Override
|
||||
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
|
||||
try {
|
||||
return CertUtils.trustManager(trustStorePath, trustStoreType, trustStorePassword.getChars(), trustStoreAlgorithm, environment);
|
||||
return CertParsingUtils.trustManager(trustStorePath, trustStoreType, trustStorePassword.getChars(),
|
||||
trustStoreAlgorithm, environment);
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||
}
|
||||
@ -63,8 +64,8 @@ class StoreTrustConfig extends TrustConfig {
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
final Path path = CertUtils.resolvePath(trustStorePath, environment);
|
||||
final KeyStore trustStore = CertUtils.readKeyStore(path, trustStoreType, trustStorePassword.getChars());
|
||||
final Path path = CertParsingUtils.resolvePath(trustStorePath, environment);
|
||||
final KeyStore trustStore = CertParsingUtils.readKeyStore(path, trustStoreType, trustStorePassword.getChars());
|
||||
final List<CertificateInfo> certificates = new ArrayList<>();
|
||||
final Enumeration<String> aliases = trustStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
@ -83,7 +84,7 @@ class StoreTrustConfig extends TrustConfig {
|
||||
if (trustStorePath == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.singletonList(CertUtils.resolvePath(trustStorePath, environment));
|
||||
return Collections.singletonList(CertParsingUtils.resolvePath(trustStorePath, environment));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,7 +79,7 @@ abstract class TrustConfig {
|
||||
}
|
||||
|
||||
try {
|
||||
return CertUtils.trustManager(trustConfigs.stream()
|
||||
return CertParsingUtils.trustManager(trustConfigs.stream()
|
||||
.flatMap((tc) -> Arrays.stream(tc.createTrustManager(environment).getAcceptedIssuers()))
|
||||
.collect(Collectors.toList())
|
||||
.toArray(new X509Certificate[0]));
|
||||
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.network.InetAddresses;
|
||||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for cert utils
|
||||
*/
|
||||
public class CertGenUtilsTests extends ESTestCase {
|
||||
|
||||
public void testSerialNotRepeated() {
|
||||
int iterations = scaledRandomIntBetween(10, 100);
|
||||
List<BigInteger> list = new ArrayList<>(iterations);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
BigInteger serial = CertGenUtils.getSerial();
|
||||
assertThat(list.contains(serial), is(false));
|
||||
list.add(serial);
|
||||
}
|
||||
}
|
||||
|
||||
public void testGenerateKeyPair() throws Exception {
|
||||
KeyPair keyPair = CertGenUtils.generateKeyPair(randomFrom(1024, 2048));
|
||||
assertThat(keyPair.getPrivate().getAlgorithm(), is("RSA"));
|
||||
assertThat(keyPair.getPublic().getAlgorithm(), is("RSA"));
|
||||
}
|
||||
|
||||
public void testSubjectAlternativeNames() throws Exception {
|
||||
final boolean resolveName = randomBoolean();
|
||||
InetAddress address = InetAddresses.forString("127.0.0.1");
|
||||
|
||||
GeneralNames generalNames = CertGenUtils.getSubjectAlternativeNames(resolveName, Collections.singleton(address));
|
||||
assertThat(generalNames, notNullValue());
|
||||
GeneralName[] generalNameArray = generalNames.getNames();
|
||||
assertThat(generalNameArray, notNullValue());
|
||||
|
||||
logger.info("resolve name [{}], address [{}], subject alt names [{}]", resolveName, NetworkAddress.format(address), generalNames);
|
||||
if (resolveName && isResolvable(address)) {
|
||||
assertThat(generalNameArray.length, is(2));
|
||||
int firstType = generalNameArray[0].getTagNo();
|
||||
if (firstType == GeneralName.iPAddress) {
|
||||
assertThat(generalNameArray[1].getTagNo(), is(GeneralName.dNSName));
|
||||
} else if (firstType == GeneralName.dNSName) {
|
||||
assertThat(generalNameArray[1].getTagNo(), is(GeneralName.iPAddress));
|
||||
} else {
|
||||
fail("unknown tag value: " + firstType);
|
||||
}
|
||||
} else {
|
||||
assertThat(generalNameArray.length, is(1));
|
||||
assertThat(generalNameArray[0].getTagNo(), is(GeneralName.iPAddress));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "need to use getHostName to resolve DNS name and getHostAddress to ensure we resolved the name")
|
||||
private boolean isResolvable(InetAddress inetAddress) {
|
||||
String hostname = inetAddress.getHostName();
|
||||
return hostname.equals(inetAddress.getHostAddress()) == false;
|
||||
}
|
||||
|
||||
public void testIsAnyLocalAddress() throws Exception {
|
||||
InetAddress address = mock(InetAddress.class);
|
||||
when(address.isAnyLocalAddress()).thenReturn(true);
|
||||
|
||||
GeneralNames generalNames = CertGenUtils.getSubjectAlternativeNames(randomBoolean(), Collections.singleton(address));
|
||||
assertThat(generalNames, notNullValue());
|
||||
GeneralName[] generalNameArray = generalNames.getNames();
|
||||
assertThat(generalNameArray, notNullValue());
|
||||
|
||||
verify(address).isAnyLocalAddress();
|
||||
verifyNoMoreInteractions(address);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
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.instanceOf;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class CertParsingUtilsTests extends ESTestCase {
|
||||
public void testReadKeysCorrectly() throws Exception {
|
||||
// read in keystore version
|
||||
Path keystorePath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks");
|
||||
Key key;
|
||||
try (InputStream in = Files.newInputStream(keystorePath)) {
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(in, "testnode".toCharArray());
|
||||
key = keyStore.getKey("testnode_RSA", "testnode".toCharArray());
|
||||
}
|
||||
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 testReadCertsCorrectly() throws Exception {
|
||||
// read in keystore version
|
||||
Path keystorePath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks");
|
||||
Certificate certificate;
|
||||
try (InputStream in = Files.newInputStream(keystorePath)) {
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(in, "testnode".toCharArray());
|
||||
certificate = keyStore.getCertificate("testnode_rsa");
|
||||
}
|
||||
assertThat(certificate, notNullValue());
|
||||
assertThat(certificate, instanceOf(X509Certificate.class));
|
||||
|
||||
Certificate pemCert;
|
||||
try (InputStream input =
|
||||
Files.newInputStream(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"))) {
|
||||
List<Certificate> certificateList = CertParsingUtils.readCertificates(input);
|
||||
assertThat(certificateList.size(), is(1));
|
||||
pemCert = certificateList.get(0);
|
||||
}
|
||||
assertThat(pemCert, notNullValue());
|
||||
assertThat(pemCert, equalTo(certificate));
|
||||
}
|
||||
|
||||
public void testReadEllipticCurveCertificateAndKey() throws Exception {
|
||||
Path keyPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/prime256v1-key.pem");
|
||||
verifyPrime256v1ECKey(keyPath);
|
||||
|
||||
Path keyPkcs8Path = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/prime256v1-key-noparam-pkcs8.pem");
|
||||
verifyPrime256v1ECKey(keyPkcs8Path);
|
||||
|
||||
Path keyNoSpecPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/prime256v1-key-noparam.pem");
|
||||
verifyPrime256v1ECKey(keyNoSpecPath);
|
||||
|
||||
Path certPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/prime256v1-cert.pem");
|
||||
Certificate[] certs = CertParsingUtils.readCertificates(Collections.singletonList(certPath.toString()), null);
|
||||
assertEquals(1, certs.length);
|
||||
Certificate cert = certs[0];
|
||||
assertNotNull(cert);
|
||||
assertEquals("EC", cert.getPublicKey().getAlgorithm());
|
||||
}
|
||||
|
||||
private void verifyPrime256v1ECKey(Path keyPath) {
|
||||
PrivateKey privateKey = PemUtils.readPrivateKey(keyPath, () -> null);
|
||||
assertEquals("EC", privateKey.getAlgorithm());
|
||||
assertThat(privateKey, instanceOf(ECPrivateKey.class));
|
||||
}
|
||||
}
|
@ -1,187 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.network.InetAddresses;
|
||||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for cert utils
|
||||
*/
|
||||
public class CertUtilsTests extends ESTestCase {
|
||||
|
||||
public void testSerialNotRepeated() {
|
||||
int iterations = scaledRandomIntBetween(10, 100);
|
||||
List<BigInteger> list = new ArrayList<>(iterations);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
BigInteger serial = CertUtils.getSerial();
|
||||
assertThat(list.contains(serial), is(false));
|
||||
list.add(serial);
|
||||
}
|
||||
}
|
||||
|
||||
public void testGenerateKeyPair() throws Exception {
|
||||
KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048));
|
||||
assertThat(keyPair.getPrivate().getAlgorithm(), is("RSA"));
|
||||
assertThat(keyPair.getPublic().getAlgorithm(), is("RSA"));
|
||||
}
|
||||
|
||||
public void testReadKeysCorrectly() throws Exception {
|
||||
// read in keystore version
|
||||
Path keystorePath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks");
|
||||
Key key;
|
||||
try (InputStream in = Files.newInputStream(keystorePath)) {
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(in, "testnode".toCharArray());
|
||||
key = keyStore.getKey("testnode", "testnode".toCharArray());
|
||||
}
|
||||
assertThat(key, notNullValue());
|
||||
assertThat(key, instanceOf(PrivateKey.class));
|
||||
|
||||
PrivateKey privateKey;
|
||||
try (Reader reader =
|
||||
Files.newBufferedReader(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"),
|
||||
StandardCharsets.UTF_8)) {
|
||||
privateKey = CertUtils.readPrivateKey(reader, "testnode"::toCharArray);
|
||||
}
|
||||
assertThat(privateKey, notNullValue());
|
||||
assertThat(privateKey, equalTo(key));
|
||||
}
|
||||
|
||||
public void testReadCertsCorrectly() throws Exception {
|
||||
// read in keystore version
|
||||
Path keystorePath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks");
|
||||
Certificate certificate;
|
||||
try (InputStream in = Files.newInputStream(keystorePath)) {
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(in, "testnode".toCharArray());
|
||||
certificate = keyStore.getCertificate("testnode");
|
||||
}
|
||||
assertThat(certificate, notNullValue());
|
||||
assertThat(certificate, instanceOf(X509Certificate.class));
|
||||
|
||||
Certificate pemCert;
|
||||
try (Reader reader =
|
||||
Files.newBufferedReader(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"),
|
||||
StandardCharsets.UTF_8)) {
|
||||
List<Certificate> certificateList = new ArrayList<>(1);
|
||||
CertUtils.readCertificates(reader, certificateList, CertificateFactory.getInstance("X.509"));
|
||||
assertThat(certificateList.size(), is(1));
|
||||
pemCert = certificateList.get(0);
|
||||
}
|
||||
assertThat(pemCert, notNullValue());
|
||||
assertThat(pemCert, equalTo(certificate));
|
||||
}
|
||||
|
||||
public void testSubjectAlternativeNames() throws Exception {
|
||||
final boolean resolveName = randomBoolean();
|
||||
InetAddress address = InetAddresses.forString("127.0.0.1");
|
||||
|
||||
GeneralNames generalNames = CertUtils.getSubjectAlternativeNames(resolveName, Collections.singleton(address));
|
||||
assertThat(generalNames, notNullValue());
|
||||
GeneralName[] generalNameArray = generalNames.getNames();
|
||||
assertThat(generalNameArray, notNullValue());
|
||||
|
||||
logger.info("resolve name [{}], address [{}], subject alt names [{}]", resolveName, NetworkAddress.format(address), generalNames);
|
||||
if (resolveName && isResolvable(address)) {
|
||||
assertThat(generalNameArray.length, is(2));
|
||||
int firstType = generalNameArray[0].getTagNo();
|
||||
if (firstType == GeneralName.iPAddress) {
|
||||
assertThat(generalNameArray[1].getTagNo(), is(GeneralName.dNSName));
|
||||
} else if (firstType == GeneralName.dNSName) {
|
||||
assertThat(generalNameArray[1].getTagNo(), is(GeneralName.iPAddress));
|
||||
} else {
|
||||
fail("unknown tag value: " + firstType);
|
||||
}
|
||||
} else {
|
||||
assertThat(generalNameArray.length, is(1));
|
||||
assertThat(generalNameArray[0].getTagNo(), is(GeneralName.iPAddress));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "need to use getHostName to resolve DNS name and getHostAddress to ensure we resolved the name")
|
||||
private boolean isResolvable(InetAddress inetAddress) {
|
||||
String hostname = inetAddress.getHostName();
|
||||
return hostname.equals(inetAddress.getHostAddress()) == false;
|
||||
}
|
||||
|
||||
public void testIsAnyLocalAddress() throws Exception {
|
||||
InetAddress address = mock(InetAddress.class);
|
||||
when(address.isAnyLocalAddress()).thenReturn(true);
|
||||
|
||||
GeneralNames generalNames = CertUtils.getSubjectAlternativeNames(randomBoolean(), Collections.singleton(address));
|
||||
assertThat(generalNames, notNullValue());
|
||||
GeneralName[] generalNameArray = generalNames.getNames();
|
||||
assertThat(generalNameArray, notNullValue());
|
||||
|
||||
verify(address).isAnyLocalAddress();
|
||||
verifyNoMoreInteractions(address);
|
||||
}
|
||||
|
||||
public void testReadEllipticCurveCertificateAndKey() throws Exception {
|
||||
Path keyPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/prime256v1-key.pem");
|
||||
try (Reader reader = Files.newBufferedReader(keyPath)) {
|
||||
verifyPrime256v1ECKey(reader);
|
||||
}
|
||||
|
||||
Path keyNoSpecPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/prime256v1-key-noparam.pem");
|
||||
try (Reader reader = Files.newBufferedReader(keyNoSpecPath)) {
|
||||
verifyPrime256v1ECKey(reader);
|
||||
}
|
||||
|
||||
Path certPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/prime256v1-cert.pem");
|
||||
Certificate[] certs = CertUtils.readCertificates(Collections.singletonList(certPath.toString()), null);
|
||||
assertEquals(1, certs.length);
|
||||
Certificate cert = certs[0];
|
||||
assertNotNull(cert);
|
||||
assertEquals("EC", cert.getPublicKey().getAlgorithm());
|
||||
}
|
||||
|
||||
private void verifyPrime256v1ECKey(Reader reader) throws Exception {
|
||||
PrivateKey privateKey = CertUtils.readPrivateKey(reader, () -> null);
|
||||
assertNotNull(privateKey);
|
||||
assertEquals("ECDSA", privateKey.getAlgorithm());
|
||||
assertThat(privateKey, instanceOf(ECPrivateKey.class));
|
||||
ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey;
|
||||
assertThat(ecPrivateKey.getParams(), instanceOf(ECNamedCurveSpec.class));
|
||||
ECNamedCurveSpec namedCurveSpec = (ECNamedCurveSpec) ecPrivateKey.getParams();
|
||||
assertEquals("prime256v1", namedCurveSpec.getName());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
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.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
|
||||
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 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 testReadEncryptedPKCS8Key() 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/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());
|
||||
}
|
||||
}
|
||||
}
|
@ -5,21 +5,21 @@
|
||||
*/
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.operator.OperatorException;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
@ -33,44 +33,59 @@ import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.xpack.core.ssl.CertUtils.generateSignedCertificate;
|
||||
|
||||
public class RestrictedTrustManagerTests extends ESTestCase {
|
||||
|
||||
/**
|
||||
* Use a small keysize for performance, since the keys are only used in this test, but a large enough keysize
|
||||
* to get past the SSL algorithm checker
|
||||
*/
|
||||
private static final int KEYSIZE = 1024;
|
||||
|
||||
private X509ExtendedTrustManager baseTrustManager;
|
||||
private Map<String, X509Certificate[]> certificates;
|
||||
private int numberOfClusters;
|
||||
private int numberOfNodes;
|
||||
|
||||
@Before
|
||||
public void generateCertificates() throws GeneralSecurityException, IOException, OperatorException {
|
||||
KeyPair caPair = CertUtils.generateKeyPair(KEYSIZE);
|
||||
X500Principal ca = new X500Principal("cn=CertAuth");
|
||||
X509Certificate caCert = CertUtils.generateCACertificate(ca, caPair, 30);
|
||||
baseTrustManager = CertUtils.trustManager(new Certificate[] { caCert });
|
||||
public void readCertificates() throws GeneralSecurityException, IOException {
|
||||
|
||||
Certificate[] caCert
|
||||
= CertParsingUtils.readCertificates(Collections.singletonList(getDataPath
|
||||
("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/nodes/ca.crt")));
|
||||
baseTrustManager = CertParsingUtils.trustManager(caCert);
|
||||
certificates = new HashMap<>();
|
||||
Files.walkFileTree(getDataPath
|
||||
("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/nodes/self-signed"), new SimpleFileVisitor<Path>() {
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
try {
|
||||
String fileName = file.getFileName().toString();
|
||||
if (fileName.endsWith(".crt")) {
|
||||
certificates.put(fileName.replace(".crt", "/self"), CertParsingUtils
|
||||
.readX509Certificates(Collections.singletonList(file)));
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
} catch (CertificateException e) {
|
||||
throw new IOException("Failed to read X.509 Certificate from: " + file.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Files.walkFileTree(getDataPath
|
||||
("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/nodes/ca-signed"), new SimpleFileVisitor<Path>() {
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
try {
|
||||
String fileName = file.getFileName().toString();
|
||||
if (fileName.endsWith(".crt")) {
|
||||
certificates.put(fileName.replace(".crt", "/ca"), CertParsingUtils
|
||||
.readX509Certificates(Collections.singletonList(file)));
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
} catch (CertificateException e) {
|
||||
throw new IOException("Failed to read X.509 Certificate from: " + file.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
numberOfClusters = scaledRandomIntBetween(2, 8);
|
||||
numberOfNodes = scaledRandomIntBetween(2, 8);
|
||||
for (int cluster = 1; cluster <= numberOfClusters; cluster++) {
|
||||
for (int node = 1; node <= numberOfNodes; node++) {
|
||||
KeyPair nodePair = CertUtils.generateKeyPair(KEYSIZE);
|
||||
final String cn = "n" + node + ".c" + cluster;
|
||||
final X500Principal principal = new X500Principal("cn=" + cn);
|
||||
final String san = "node" + node + ".cluster" + cluster + ".elasticsearch";
|
||||
final GeneralNames altNames = new GeneralNames(CertUtils.createCommonName(san));
|
||||
final X509Certificate signed = generateSignedCertificate(principal, altNames, nodePair, caCert, caPair.getPrivate(), 30);
|
||||
final X509Certificate self = generateSignedCertificate(principal, altNames, nodePair, null, null, 30);
|
||||
certificates.put(cn + "/ca", new X509Certificate[] { signed });
|
||||
certificates.put(cn + "/self", new X509Certificate[] { self });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testTrustsExplicitCertificateName() throws Exception {
|
||||
|
@ -9,8 +9,6 @@ import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
|
||||
import org.elasticsearch.common.CheckedRunnable;
|
||||
import org.elasticsearch.common.settings.MockSecureSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
@ -27,14 +25,10 @@ import org.junit.Before;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@ -42,16 +36,13 @@ import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.security.AccessController;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -87,14 +78,16 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
public void testReloadingKeyStore() throws Exception {
|
||||
final Path tempDir = createTempDir();
|
||||
final Path keystorePath = tempDir.resolve("testnode.jks");
|
||||
final Path updatedKeystorePath = tempDir.resolve("testnode_updated.jks");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), keystorePath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode_updated.jks"), updatedKeystorePath);
|
||||
MockSecureSettings secureSettings = new MockSecureSettings();
|
||||
secureSettings.setString("xpack.ssl.keystore.secure_password", "testnode");
|
||||
final Settings settings = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.put("xpack.ssl.keystore.path", keystorePath)
|
||||
.setSecureSettings(secureSettings)
|
||||
.build();
|
||||
.put("path.home", createTempDir())
|
||||
.put("xpack.ssl.keystore.path", keystorePath)
|
||||
.setSecureSettings(secureSettings)
|
||||
.build();
|
||||
final Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
||||
//Load HTTPClient only once. Client uses the same store as a truststore
|
||||
try (CloseableHttpClient client = getSSLClient(keystorePath, "testnode")) {
|
||||
@ -110,22 +103,12 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
|
||||
final Runnable modifier = () -> {
|
||||
try {
|
||||
// modify the keystore that the KeyManager uses
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(null, null);
|
||||
final KeyPair keyPair = CertUtils.generateKeyPair(512);
|
||||
X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair,
|
||||
null, null, 365);
|
||||
keyStore.setKeyEntry("key", keyPair.getPrivate(), "testnode".toCharArray(), new X509Certificate[]{cert});
|
||||
Path updated = tempDir.resolve("updated.jks");
|
||||
try (OutputStream out = Files.newOutputStream(updated)) {
|
||||
keyStore.store(out, "testnode".toCharArray());
|
||||
}
|
||||
atomicMoveIfPossible(updated, keystorePath);
|
||||
atomicMoveIfPossible(updatedKeystorePath, keystorePath);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("modification failed", e);
|
||||
}
|
||||
};
|
||||
|
||||
// The new server certificate is not in the client's truststore so SSLHandshake should fail
|
||||
final Consumer<SSLContext> keyMaterialPostChecks = (updatedContext) -> {
|
||||
try (MockWebServer server = new MockWebServer(updatedContext, true)) {
|
||||
@ -133,7 +116,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
server.start();
|
||||
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
|
||||
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
|
||||
assertThat(sslException.getCause().getMessage(), containsString("PKIX path building failed"));
|
||||
assertThat(sslException.getCause().getMessage(), containsString("PKIX path validation failed"));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Exception starting or connecting to the mock server", e);
|
||||
}
|
||||
@ -141,18 +124,21 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
validateSSLConfigurationIsReloaded(settings, env, keyMaterialPreChecks, modifier, keyMaterialPostChecks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the reloading of SSLContext when a PEM key and certificate are used.
|
||||
*/
|
||||
public void testPEMKeyCertConfigReloading() throws Exception {
|
||||
final Path tempDir = createTempDir();
|
||||
final Path keyPath = tempDir.resolve("testnode.pem");
|
||||
final Path certPath = tempDir.resolve("testnode.crt");
|
||||
public void testPEMKeyConfigReloading() throws Exception {
|
||||
Path tempDir = createTempDir();
|
||||
Path keyPath = tempDir.resolve("testnode.pem");
|
||||
Path updatedKeyPath = tempDir.resolve("testnode_updated.pem");
|
||||
Path certPath = tempDir.resolve("testnode.crt");
|
||||
Path updatedCertPath = tempDir.resolve("testnode_updated.crt");
|
||||
final Path clientTruststorePath = tempDir.resolve("testnode.jks");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), clientTruststorePath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), keyPath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode_updated.pem"), updatedKeyPath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode_updated.crt"), updatedCertPath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), certPath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), clientTruststorePath);
|
||||
MockSecureSettings secureSettings = new MockSecureSettings();
|
||||
secureSettings.setString("xpack.ssl.secure_key_passphrase", "testnode");
|
||||
final Settings settings = Settings.builder()
|
||||
@ -176,27 +162,13 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
};
|
||||
final Runnable modifier = () -> {
|
||||
try {
|
||||
final KeyPair keyPair = CertUtils.generateKeyPair(512);
|
||||
X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair,
|
||||
null, null, 365);
|
||||
Path updatedKeyPath = tempDir.resolve("updated.pem");
|
||||
Path updatedCertPath = tempDir.resolve("updated.crt");
|
||||
try (OutputStream os = Files.newOutputStream(updatedKeyPath);
|
||||
OutputStreamWriter osWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8);
|
||||
JcaPEMWriter writer = new JcaPEMWriter(osWriter)) {
|
||||
writer.writeObject(keyPair,
|
||||
new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build("testnode".toCharArray()));
|
||||
}
|
||||
try (BufferedWriter out = Files.newBufferedWriter(updatedCertPath);
|
||||
JcaPEMWriter pemWriter = new JcaPEMWriter(out)) {
|
||||
pemWriter.writeObject(cert);
|
||||
}
|
||||
atomicMoveIfPossible(updatedKeyPath, keyPath);
|
||||
atomicMoveIfPossible(updatedCertPath, certPath);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("failed to modify file", e);
|
||||
}
|
||||
};
|
||||
|
||||
// The new server certificate is not in the client's truststore so SSLHandshake should fail
|
||||
final Consumer<SSLContext> keyMaterialPostChecks = (updatedContext) -> {
|
||||
try (MockWebServer server = new MockWebServer(updatedContext, false)) {
|
||||
@ -204,12 +176,11 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
server.start();
|
||||
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
|
||||
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
|
||||
assertThat(sslException.getCause().getMessage(), containsString("PKIX path building failed"));
|
||||
assertThat(sslException.getCause().getMessage(), containsString("PKIX path validation failed"));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Exception starting or connecting to the mock server", e);
|
||||
}
|
||||
};
|
||||
|
||||
validateSSLConfigurationIsReloaded(settings, env, keyMaterialPreChecks, modifier, keyMaterialPostChecks);
|
||||
}
|
||||
}
|
||||
@ -221,7 +192,9 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
public void testReloadingTrustStore() throws Exception {
|
||||
Path tempDir = createTempDir();
|
||||
Path trustStorePath = tempDir.resolve("testnode.jks");
|
||||
Path updatedTruststorePath = tempDir.resolve("testnode_updated.jks");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), trustStorePath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode_updated.jks"), updatedTruststorePath);
|
||||
MockSecureSettings secureSettings = new MockSecureSettings();
|
||||
secureSettings.setString("xpack.ssl.truststore.secure_password", "testnode");
|
||||
Settings settings = Settings.builder()
|
||||
@ -231,9 +204,9 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
.build();
|
||||
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
||||
// Create the MockWebServer once for both pre and post checks
|
||||
try(MockWebServer server = getSslServer(trustStorePath, "testnode")){
|
||||
try (MockWebServer server = getSslServer(trustStorePath, "testnode")) {
|
||||
final Consumer<SSLContext> trustMaterialPreChecks = (context) -> {
|
||||
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()){
|
||||
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()) {
|
||||
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error connecting to the mock server", e);
|
||||
@ -242,17 +215,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
|
||||
final Runnable modifier = () -> {
|
||||
try {
|
||||
Path updatedTrustStore = tempDir.resolve("updated.jks");
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(null, null);
|
||||
final KeyPair keyPair = CertUtils.generateKeyPair(512);
|
||||
X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair,
|
||||
null, null, 365);
|
||||
keyStore.setKeyEntry("newKey", keyPair.getPrivate(), "testnode".toCharArray(), new Certificate[]{cert});
|
||||
try (OutputStream out = Files.newOutputStream(updatedTrustStore)) {
|
||||
keyStore.store(out, "testnode".toCharArray());
|
||||
}
|
||||
atomicMoveIfPossible(updatedTrustStore, trustStorePath);
|
||||
atomicMoveIfPossible(updatedTruststorePath, trustStorePath);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("failed to modify file", e);
|
||||
}
|
||||
@ -260,7 +223,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
|
||||
// Client's truststore doesn't contain the server's certificate anymore so SSLHandshake should fail
|
||||
final Consumer<SSLContext> trustMaterialPostChecks = (updatedContext) -> {
|
||||
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(updatedContext).build()){
|
||||
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(updatedContext).build()) {
|
||||
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
|
||||
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
|
||||
assertThat(sslException.getCause().getMessage(), containsString("PKIX path building failed"));
|
||||
@ -268,11 +231,9 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
throw new RuntimeException("Error closing CloseableHttpClient", e);
|
||||
}
|
||||
};
|
||||
|
||||
validateSSLConfigurationIsReloaded(settings, env, trustMaterialPreChecks, modifier, trustMaterialPostChecks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the reloading of SSLContext whose trust config is backed by PEM certificate files.
|
||||
*/
|
||||
@ -281,16 +242,18 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
Path clientCertPath = tempDir.resolve("testnode.crt");
|
||||
Path keyStorePath = tempDir.resolve("testnode.jks");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), keyStorePath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), clientCertPath);
|
||||
//Our keystore contains two Certificates it can present. One build from the RSA keypair and one build from the EC keypair. EC is
|
||||
// used since it keyManager presents the first one in alias alphabetical order (and testnode_ec comes before testnode_rsa)
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode_ec.crt"), clientCertPath);
|
||||
Settings settings = Settings.builder()
|
||||
.putList("xpack.ssl.certificate_authorities", clientCertPath.toString())
|
||||
.putList("xpack.ssl.certificate_authorities", clientCertPath.toString())
|
||||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
||||
// Create the MockWebServer once for both pre and post checks
|
||||
try(MockWebServer server = getSslServer(keyStorePath, "testnode")){
|
||||
try (MockWebServer server = getSslServer(keyStorePath, "testnode")) {
|
||||
final Consumer<SSLContext> trustMaterialPreChecks = (context) -> {
|
||||
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()){
|
||||
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()) {
|
||||
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Exception connecting to the mock server", e);
|
||||
@ -299,22 +262,18 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
|
||||
final Runnable modifier = () -> {
|
||||
try {
|
||||
final KeyPair keyPair = CertUtils.generateKeyPair(512);
|
||||
X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair,
|
||||
null, null, 365);
|
||||
Path updatedCertPath = tempDir.resolve("updated.crt");
|
||||
try (BufferedWriter out = Files.newBufferedWriter(updatedCertPath);
|
||||
JcaPEMWriter pemWriter = new JcaPEMWriter(out)) {
|
||||
pemWriter.writeObject(cert);
|
||||
}
|
||||
atomicMoveIfPossible(updatedCertPath, clientCertPath);
|
||||
Path updatedCert = tempDir.resolve("updated.crt");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode_updated.crt"),
|
||||
updatedCert, StandardCopyOption.REPLACE_EXISTING);
|
||||
atomicMoveIfPossible(updatedCert, clientCertPath);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("failed to modify file", e);
|
||||
}
|
||||
};
|
||||
|
||||
// Client doesn't trust the Server certificate anymore so SSLHandshake should fail
|
||||
final Consumer<SSLContext> trustMaterialPostChecks = (updatedContext) -> {
|
||||
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(updatedContext).build()){
|
||||
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(updatedContext).build()) {
|
||||
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
|
||||
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
|
||||
assertThat(sslException.getCause().getMessage(), containsString("PKIX path building failed"));
|
||||
@ -322,7 +281,6 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
throw new RuntimeException("Error closing CloseableHttpClient", e);
|
||||
}
|
||||
};
|
||||
|
||||
validateSSLConfigurationIsReloaded(settings, env, trustMaterialPreChecks, modifier, trustMaterialPostChecks);
|
||||
}
|
||||
}
|
||||
@ -447,9 +405,9 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
||||
Settings settings = Settings.builder()
|
||||
.putList("xpack.ssl.certificate_authorities", clientCertPath.toString())
|
||||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
.putList("xpack.ssl.certificate_authorities", clientCertPath.toString())
|
||||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
||||
final SSLService sslService = new SSLService(settings, env);
|
||||
final SSLConfiguration config = sslService.sslConfiguration(Settings.EMPTY);
|
||||
@ -471,13 +429,13 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
|
||||
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||
assertThat(sslService.sslContextHolder(config).sslContext(), sameInstance(context));
|
||||
}
|
||||
|
||||
}
|
||||
private void validateSSLConfigurationIsReloaded(Settings settings, Environment env,
|
||||
Consumer<SSLContext> preChecks,
|
||||
Runnable modificationFunction,
|
||||
Consumer<SSLContext> postChecks)
|
||||
throws Exception {
|
||||
throws Exception {
|
||||
|
||||
final CountDownLatch reloadLatch = new CountDownLatch(1);
|
||||
final SSLService sslService = new SSLService(settings, env);
|
||||
@ -512,7 +470,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
private static MockWebServer getSslServer(Path keyStorePath, String keyStorePass) throws KeyStoreException, CertificateException,
|
||||
NoSuchAlgorithmException, IOException, KeyManagementException, UnrecoverableKeyException {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
try(InputStream is = Files.newInputStream(keyStorePath)) {
|
||||
try (InputStream is = Files.newInputStream(keyStorePath)) {
|
||||
keyStore.load(is, keyStorePass.toCharArray());
|
||||
}
|
||||
final SSLContext sslContext = new SSLContextBuilder().loadKeyMaterial(keyStore, keyStorePass.toCharArray())
|
||||
@ -527,7 +485,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
NoSuchAlgorithmException,
|
||||
KeyManagementException, IOException, CertificateException {
|
||||
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
try(InputStream is = Files.newInputStream(trustStorePath)) {
|
||||
try (InputStream is = Files.newInputStream(trustStorePath)) {
|
||||
trustStore.load(is, trustStorePass.toCharArray());
|
||||
}
|
||||
final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(trustStore, null).build();
|
||||
@ -544,5 +502,4 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
throw (Exception) e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -478,7 +478,7 @@ public class SSLServiceTests extends ESTestCase {
|
||||
|
||||
final SSLService sslService = new SSLService(settings, env);
|
||||
final List<CertificateInfo> certificates = new ArrayList<>(sslService.getLoadedCertificates());
|
||||
assertThat(certificates, iterableWithSize(8));
|
||||
assertThat(certificates, iterableWithSize(10));
|
||||
Collections.sort(certificates,
|
||||
Comparator.comparing((CertificateInfo c) -> c.alias() == null ? "" : c.alias()).thenComparing(CertificateInfo::path));
|
||||
|
||||
@ -528,24 +528,6 @@ public class SSLServiceTests extends ESTestCase {
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:55Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(false));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
assertThat(cert.format(), equalTo("jks"));
|
||||
assertThat(cert.serialNumber(), equalTo("b8b96c37e332cccb"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=Elasticsearch Test Node, OU=elasticsearch, O=org"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:57Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(true));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode"));
|
||||
assertThat(cert.path(), equalTo(p12Path.toString()));
|
||||
assertThat(cert.format(), equalTo("PKCS12"));
|
||||
assertThat(cert.serialNumber(), equalTo("b8b96c37e332cccb"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=Elasticsearch Test Node, OU=elasticsearch, O=org"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:57Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(true));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode-client-profile"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
@ -555,6 +537,42 @@ public class SSLServiceTests extends ESTestCase {
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:56Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(false));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode_dsa"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
assertThat(cert.format(), equalTo("jks"));
|
||||
assertThat(cert.serialNumber(), equalTo("223c736a"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=Elasticsearch Test Node"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2045-10-02T09:43:18.000Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(true));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode_ec"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
assertThat(cert.format(), equalTo("jks"));
|
||||
assertThat(cert.serialNumber(), equalTo("7268203b"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=Elasticsearch Test Node"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2045-10-02T09:36:10.000Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(true));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode_rsa"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
assertThat(cert.format(), equalTo("jks"));
|
||||
assertThat(cert.serialNumber(), equalTo("b8b96c37e332cccb"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=Elasticsearch Test Node, OU=elasticsearch, O=org"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:57.000Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(true));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode_rsa"));
|
||||
assertThat(cert.path(), equalTo(p12Path.toString()));
|
||||
assertThat(cert.format(), equalTo("PKCS12"));
|
||||
assertThat(cert.serialNumber(), equalTo("b8b96c37e332cccb"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=Elasticsearch Test Node, OU=elasticsearch, O=org"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:57Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(true));
|
||||
|
||||
assertFalse(iterator.hasNext());
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ public class StoreKeyConfigTests extends ESTestCase {
|
||||
final StoreKeyConfig keyConfig = new StoreKeyConfig(path, type, keyStorePassword, keyStorePassword,
|
||||
KeyManagerFactory.getDefaultAlgorithm(), TrustManagerFactory.getDefaultAlgorithm());
|
||||
final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(TestEnvironment.newEnvironment(settings));
|
||||
final PrivateKey key = keyManager.getPrivateKey("testnode");
|
||||
final PrivateKey key = keyManager.getPrivateKey("testnode_rsa");
|
||||
assertThat(key, notNullValue());
|
||||
assertThat(key.getAlgorithm(), equalTo("RSA"));
|
||||
assertThat(key.getFormat(), equalTo("PKCS#8"));
|
||||
|
@ -5,26 +5,17 @@
|
||||
*/
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
/**
|
||||
* Extending SSLService to make helper methods public to access in tests
|
||||
*/
|
||||
public class TestsSSLService extends SSLService {
|
||||
|
||||
public TestsSSLService(Settings settings, Environment environment) throws CertificateException, UnrecoverableKeyException,
|
||||
NoSuchAlgorithmException, IOException, DestroyFailedException, KeyStoreException, OperatorCreationException {
|
||||
public TestsSSLService(Settings settings, Environment environment) {
|
||||
super(settings, environment);
|
||||
}
|
||||
|
||||
|
@ -5,28 +5,23 @@
|
||||
*/
|
||||
package org.elasticsearch.xpack.core.ssl.cert;
|
||||
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.core.ssl.CertUtils;
|
||||
import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class CertificateInfoTests extends ESTestCase {
|
||||
|
||||
public void testSerialization() throws Exception {
|
||||
final X500Principal principal = new X500Principal("CN=foo");
|
||||
final X509Certificate certificate = CertUtils.generateSignedCertificate(principal, new GeneralNames(new GeneralName[0]),
|
||||
getKeyPair(), null, null, 90);
|
||||
final X509Certificate certificate = CertParsingUtils.
|
||||
readX509Certificates(Collections.singletonList(getDataPath
|
||||
("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt")))[0];
|
||||
final CertificateInfo cert1 = new CertificateInfo("/path/to/cert.jks", "jks", "key", true, certificate);
|
||||
final CertificateInfo cert2 = serializeAndDeserialize(cert1);
|
||||
final CertificateInfo cert3 = serializeAndDeserialize(cert2);
|
||||
@ -40,11 +35,4 @@ public class CertificateInfoTests extends ESTestCase {
|
||||
cert1.writeTo(output);
|
||||
return new CertificateInfo(output.bytes().streamInput());
|
||||
}
|
||||
|
||||
private KeyPair getKeyPair() throws NoSuchAlgorithmException {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(2048);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
}
|
@ -34,3 +34,116 @@ keytool -importkeystore -destkeystore <NAME>.jks -srckeystore <NAME>.p12 -srcsto
|
||||
|
||||
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
|
||||
------
|
||||
|
@ -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
|
@ -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-----
|
@ -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-----
|
@ -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-----
|
@ -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-----
|
@ -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-----
|
@ -0,0 +1,4 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MDECAQEEILEXCgqp9wZqKVmG6HTESPeCyx2O4TDoFqyILz7OGocEoAoGCCqGSM49
|
||||
AwEH
|
||||
-----END EC PRIVATE KEY-----
|
@ -0,0 +1,7 @@
|
||||
-----BEGIN EC PARAMETERS-----
|
||||
Notvalidbutnotparsed
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MDECAQEEILEXCgqp9wZqKVmG6HTESPeCyx2O4TDoFqyILz7OGocEoAoGCCqGSM49
|
||||
AwEH
|
||||
-----END EC PRIVATE KEY-----
|
@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHQCAQEEIGKZz2dsNCzJQElLteZe62XZzAyG+p3Dng/Fo9IPKThmoAcGBSuBBAAK
|
||||
oUQDQgAEJQHRtUfNOMA81RefXOaFJLTxbtjPU0rOi45IHY1ug3rzflxwz7NtqHH3
|
||||
aP02sbsAmpHFiL9db7XmNDK2+58vWA==
|
||||
-----END EC PRIVATE KEY-----
|
@ -0,0 +1,4 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCxFwoKqfcGailZhuh0
|
||||
xEj3gssdjuEw6BasiC8+zhqHBA==
|
||||
-----END PRIVATE KEY-----
|
@ -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-----
|
@ -0,0 +1,7 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBqIPMG94HL7zedFzsvi45mHS8ZuyLQXqvHpHobcdNCJAAAAJimRM7VpkTO
|
||||
1QAAAAtzc2gtZWQyNTUxOQAAACBqIPMG94HL7zedFzsvi45mHS8ZuyLQXqvHpHobcdNCJA
|
||||
AAAEBvVc8FVPGUs3LZ1o+LnjW4uUlEnk/5LQQ9yO2eiI3SFGog8wb3gcvvN50XOy+LjmYd
|
||||
Lxm7ItBeq8ekehtx00IkAAAAEWlvYW5uaXNAc2VjdXJlYm94AQIDBA==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user