Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved diagnostics for TLS trust failures #48911

Merged
merged 7 commits into from
Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.common.ssl;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509ExtendedTrustManager;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.elasticsearch.common.ssl.SslDiagnostics.getTrustDiagnosticFailure;

public final class DiagnosticTrustManager extends X509ExtendedTrustManager {


/**
* This interface exists because the ssl-config library does not depend on log4j, however the whole purpose of this class is to log
* diagnostic messages, so it must be provided with a function by which it can do that.
*/
@FunctionalInterface
public interface DiagnosticLogger {
void warning(String message, GeneralSecurityException cause);
}


private final X509ExtendedTrustManager delegate;
private final Supplier<String> contextName;
private final DiagnosticLogger logger;
private final Map<String, List<X509Certificate>> issuers;

/**
* @param contextName The descriptive name of the context that this trust manager is operating in (e.g "xpack.security.http.ssl")
* @param logger For uses that depend on log4j, it is recommended that this parameter be equivalent to
* {@code LogManager.getLogger(DiagnosticTrustManager.class)::warn}
*/
public DiagnosticTrustManager(X509ExtendedTrustManager delegate, Supplier<String> contextName, DiagnosticLogger logger) {
this.delegate = delegate;
this.contextName = contextName;
this.logger = logger;
this.issuers = Stream.of(delegate.getAcceptedIssuers())
.collect(Collectors.toMap(cert -> cert.getSubjectX500Principal().getName(), List::of,
(List<X509Certificate> a, List<X509Certificate> b) -> {
final ArrayList<X509Certificate> list = new ArrayList<>(a.size() + b.size());
list.addAll(a);
list.addAll(b);
return list;
}));
}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
try {
delegate.checkClientTrusted(chain, authType, socket);
} catch (CertificateException e) {
diagnose(e, chain, SslDiagnostics.PeerType.CLIENT, session(socket));
throw e;
}
}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
try {
delegate.checkServerTrusted(chain, authType, socket);
} catch (CertificateException e) {
diagnose(e, chain, SslDiagnostics.PeerType.SERVER, session(socket));
throw e;
}
}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
try {
delegate.checkClientTrusted(chain, authType, engine);
} catch (CertificateException e) {
diagnose(e, chain, SslDiagnostics.PeerType.CLIENT, session(engine));
throw e;
}
}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
try {
delegate.checkServerTrusted(chain, authType, engine);
} catch (CertificateException e) {
diagnose(e, chain, SslDiagnostics.PeerType.SERVER, session(engine));
throw e;
}
}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
delegate.checkClientTrusted(chain, authType);
} catch (CertificateException e) {
diagnose(e, chain, SslDiagnostics.PeerType.CLIENT, null);
throw e;
}
}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
delegate.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
diagnose(e, chain, SslDiagnostics.PeerType.SERVER, null);
throw e;
}
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return delegate.getAcceptedIssuers();
}

private void diagnose(CertificateException cause, X509Certificate[] chain, SslDiagnostics.PeerType peerType, SSLSession session) {
final String diagnostic = getTrustDiagnosticFailure(chain, peerType, session, this.contextName.get(), this.issuers);
logger.warning(diagnostic, cause);
}

private SSLSession session(Socket socket) {
if (socket instanceof SSLSocket) {
final SSLSocket ssl = (SSLSocket) socket;
final SSLSession handshakeSession = ssl.getHandshakeSession();
if (handshakeSession == null) {
return ssl.getSession();
} else {
return handshakeSession;
}
} else {
return null;
}
}

private SSLSession session(SSLEngine engine) {
return engine.getHandshakeSession();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
Expand Down Expand Up @@ -452,7 +451,7 @@ private static Cipher getCipherFromParameters(String dekHeaderValue, char[] pass
*/
private static byte[] generateOpenSslKey(char[] password, byte[] salt, int keyLength) {
byte[] passwordBytes = CharArrays.toUtf8Bytes(password);
MessageDigest md5 = messageDigest("md5");
MessageDigest md5 = SslUtil.messageDigest("md5");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly unrelated but how do we plan to handle the divergence between this and x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PemUtils.java ? I remember having this question in another PR but can't remember if I raised it and if we discussed it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. I think my next round of SSL work will be to make x-pack depend on ssl-config for more things.

byte[] key = new byte[keyLength];
int copied = 0;
int remaining;
Expand Down Expand Up @@ -603,11 +602,4 @@ static List<Certificate> readCertificates(Collection<Path> certPaths) throws Cer
return certificates;
}

private static MessageDigest messageDigest(String digestAlgorithm) {
try {
return MessageDigest.getInstance(digestAlgorithm);
} catch (NoSuchAlgorithmException e) {
throw new SslConfigException("unexpected exception creating MessageDigest instance for [" + digestAlgorithm + "]", e);
}
}
}
Loading