Add hostname verification support for transport and ldaps connections

SSL and TLS do not require hostname verification, but without it they are susceptible
to man in the middle attacks. This adds support for hostname verification for
transport client connections and for ldaps connections.

Closes elastic/elasticsearch#489

Original commit: elastic/x-pack-elasticsearch@c9380f0319
This commit is contained in:
jaymode 2014-12-17 11:19:19 -05:00
parent c052a8ca95
commit cc9568d1bb
29 changed files with 736 additions and 303 deletions

View File

@ -10,7 +10,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryRealm; import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryRealm;
import org.elasticsearch.shield.authc.esusers.ESUsersRealm; import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
import org.elasticsearch.shield.authc.ldap.LdapRealm; import org.elasticsearch.shield.authc.ldap.LdapRealm;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory; import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory;
import org.elasticsearch.shield.support.AbstractShieldModule; import org.elasticsearch.shield.support.AbstractShieldModule;
/** /**
@ -27,7 +27,7 @@ public class AuthenticationModule extends AbstractShieldModule.Node {
// This socket factory needs to be configured before any LDAP connections are created. LDAP configuration // This socket factory needs to be configured before any LDAP connections are created. LDAP configuration
// for JNDI invokes a static getSocketFactory method from LdapSslSocketFactory. // for JNDI invokes a static getSocketFactory method from LdapSslSocketFactory.
requestStaticInjection(LdapSslSocketFactory.class); requestStaticInjection(AbstractLdapSslSocketFactory.class);
MapBinder<String, Realm.Factory> mapBinder = MapBinder.newMapBinder(binder(), String.class, Realm.Factory.class); MapBinder<String, Realm.Factory> mapBinder = MapBinder.newMapBinder(binder(), String.class, Realm.Factory.class);
mapBinder.addBinding(ESUsersRealm.TYPE).to(ESUsersRealm.Factory.class).asEagerSingleton(); mapBinder.addBinding(ESUsersRealm.TYPE).to(ESUsersRealm.Factory.class).asEagerSingleton();

View File

@ -12,7 +12,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldSettingsException; import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory; import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory; import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory;
import javax.naming.Context; import javax.naming.Context;
import javax.naming.NamingEnumeration; import javax.naming.NamingEnumeration;
@ -59,7 +59,7 @@ public class ActiveDirectoryConnectionFactory extends ConnectionFactory {
.put("java.naming.ldap.attributes.binary", "tokenGroups") .put("java.naming.ldap.attributes.binary", "tokenGroups")
.put(Context.REFERRAL, "follow"); .put(Context.REFERRAL, "follow");
LdapSslSocketFactory.configureJndiSSL(ldapUrls, builder); configureJndiSSL(ldapUrls, builder);
sharedLdapEnv = builder.build(); sharedLdapEnv = builder.build();
} }

View File

@ -12,7 +12,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldSettingsException; import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory; import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory; import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory;
import javax.naming.Context; import javax.naming.Context;
import javax.naming.NamingException; import javax.naming.NamingException;
@ -63,7 +63,7 @@ public class LdapConnectionFactory extends ConnectionFactory {
.put(JNDI_LDAP_CONNECT_TIMEOUT, Long.toString(settings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis())) .put(JNDI_LDAP_CONNECT_TIMEOUT, Long.toString(settings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis()))
.put(Context.REFERRAL, "follow"); .put(Context.REFERRAL, "follow");
LdapSslSocketFactory.configureJndiSSL(ldapUrls, builder); configureJndiSSL(ldapUrls, builder);
sharedLdapEnv = builder.build(); sharedLdapEnv = builder.build();
groupSearchDN = settings.get(GROUP_SEARCH_BASEDN_SETTING); groupSearchDN = settings.get(GROUP_SEARCH_BASEDN_SETTING);

View File

@ -0,0 +1,86 @@
/*
* 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.shield.authc.support.ldap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.shield.ssl.SSLService;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
/**
* Abstract class that wraps a SSLSocketFactory and uses it to create sockets for use with LDAP via JNDI
*/
public abstract class AbstractLdapSslSocketFactory extends SocketFactory {
protected static ESLogger logger = Loggers.getLogger(AbstractLdapSslSocketFactory.class);
protected static SSLService sslService;
private final SSLSocketFactory socketFactory;
/**
* This should only be invoked once to establish a static instance that will be used for each constructor.
*/
@Inject
public static void init(SSLService sslService) {
AbstractLdapSslSocketFactory.sslService = sslService;
}
public AbstractLdapSslSocketFactory(SSLSocketFactory sslSocketFactory) {
socketFactory = sslSocketFactory;
}
//The following methods are all wrappers around the instance of socketFactory
@Override
public SSLSocket createSocket() throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket();
configureSSLSocket(socket);
return socket;
}
@Override
public SSLSocket createSocket(String host, int port) throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, port);
configureSSLSocket(socket);
return socket;
}
@Override
public SSLSocket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, port, localHost, localPort);
configureSSLSocket(socket);
return socket;
}
@Override
public SSLSocket createSocket(InetAddress host, int port) throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, port);
configureSSLSocket(socket);
return socket;
}
@Override
public SSLSocket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, port, localHost, localPort);
configureSSLSocket(socket);
return socket;
}
/**
* This method allows for performing additional configuration on each socket. All 'createSocket' methods will
* call this method before returning the socket to the caller. The default implementation is a no-op
* @param sslSocket
*/
protected void configureSSLSocket(SSLSocket sslSocket) {
}
}

View File

@ -5,12 +5,22 @@
*/ */
package org.elasticsearch.shield.authc.support.ldap; package org.elasticsearch.shield.authc.support.ldap;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.shield.authc.support.SecuredString;
import java.io.Serializable;
import java.util.regex.Pattern;
import static java.util.Arrays.asList;
import static org.elasticsearch.common.base.Predicates.contains;
import static org.elasticsearch.common.collect.Iterables.all;
/** /**
* This factory holds settings needed for authenticating to LDAP and creating LdapConnections. * This factory holds settings needed for authenticating to LDAP and creating LdapConnections.
* Each created LdapConnection needs to be closed or else connections will pill up consuming resources. * Each created LdapConnection needs to be closed or else connections will pill up consuming resources.
@ -31,7 +41,11 @@ public abstract class ConnectionFactory {
public static final String TIMEOUT_TCP_CONNECTION_SETTING = "timeout.tcp_connect"; public static final String TIMEOUT_TCP_CONNECTION_SETTING = "timeout.tcp_connect";
public static final String TIMEOUT_TCP_READ_SETTING = "timeout.tcp_read"; public static final String TIMEOUT_TCP_READ_SETTING = "timeout.tcp_read";
public static final String TIMEOUT_LDAP_SETTING = "timeout.ldap_search"; public static final String TIMEOUT_LDAP_SETTING = "timeout.ldap_search";
public static final String HOSTNAME_VERIFICATION_SETTING = "hostname_verification";
public static final TimeValue TIMEOUT_DEFAULT = TimeValue.timeValueSeconds(5); public static final TimeValue TIMEOUT_DEFAULT = TimeValue.timeValueSeconds(5);
static final String JAVA_NAMING_LDAP_FACTORY_SOCKET = "java.naming.ldap.factory.socket";
private static final Pattern STARTS_WITH_LDAPS = Pattern.compile("^ldaps:.*", Pattern.CASE_INSENSITIVE);
private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", Pattern.CASE_INSENSITIVE);
protected final ESLogger logger = Loggers.getLogger(getClass()); protected final ESLogger logger = Loggers.getLogger(getClass());
private final Settings settings; private final Settings settings;
@ -49,4 +63,45 @@ public abstract class ConnectionFactory {
*/ */
public abstract AbstractLdapConnection open(String user, SecuredString password) ; public abstract AbstractLdapConnection open(String user, SecuredString password) ;
/**
* If one of the ldapUrls are SSL this will set the LdapSslSocketFactory as a socket provider on the builder
*
* @param ldapUrls array of ldap urls, either all SSL or none with SSL (no mixing)
* @param builder set of jndi properties, that will
* @throws org.elasticsearch.shield.ShieldSettingsException if URLs have mixed protocols.
*/
protected void configureJndiSSL(String[] ldapUrls, ImmutableMap.Builder<String, Serializable> builder) {
boolean secureProtocol = secureUrls(ldapUrls);
if (secureProtocol) {
if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
builder.put(JAVA_NAMING_LDAP_FACTORY_SOCKET, HostnameVerifyingLdapSslSocketFactory.class.getName());
logger.debug("using encryption for LDAP connections with hostname verification");
} else {
builder.put(JAVA_NAMING_LDAP_FACTORY_SOCKET, LdapSslSocketFactory.class.getName());
logger.debug("using encryption for LDAP connections without hostname verification");
}
} else {
logger.warn("encryption not used for LDAP connections");
}
}
/**
* @param ldapUrls URLS in the form of "ldap://..." or "ldaps://..."
* @return true if all URLS are ldaps, also true it ldapUrls is empty. False otherwise
*/
private boolean secureUrls(String[] ldapUrls) {
if (ldapUrls.length == 0) {
return true;
}
boolean allSecure = all(asList(ldapUrls), contains(STARTS_WITH_LDAPS));
boolean allClear = all(asList(ldapUrls), contains(STARTS_WITH_LDAP));
if (!allSecure && !allClear) {
//No mixing is allowed because LdapSSLSocketFactory produces only SSL sockets and not clear text sockets
throw new ShieldSettingsException("Configured ldap protocols are not all equal " +
"(ldaps://.. and ldap://..): [" + Strings.arrayToCommaDelimitedString(ldapUrls) + "]");
}
return allSecure;
}
} }

View File

@ -0,0 +1,62 @@
/*
* 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.shield.authc.support.ldap;
import javax.net.SocketFactory;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
/**
* This factory is needed for JNDI configuration for LDAP connections with hostname verification. Each SSLSocket must
* have the appropriate SSLParameters set to indicate that hostname verification is required
*/
public class HostnameVerifyingLdapSslSocketFactory extends AbstractLdapSslSocketFactory {
private static HostnameVerifyingLdapSslSocketFactory instance;
private final SSLParameters sslParameters;
public HostnameVerifyingLdapSslSocketFactory(SSLSocketFactory socketFactory) {
super(socketFactory);
sslParameters = new SSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("LDAPS");
}
/**
* This is invoked by JNDI and the returned SocketFactory must be an HostnameVerifyingLdapSslSocketFactory object
*
* @return a singleton instance of HostnameVerifyingLdapSslSocketFactory set by calling the init static method.
*/
public static synchronized SocketFactory getDefault() {
if (instance == null) {
instance = new HostnameVerifyingLdapSslSocketFactory(sslService.getSSLSocketFactory());
}
return instance;
}
/**
* This clears the static factory. There are threading issues with this. But for
* testing this is useful.
*
* WARNING: THIS METHOD SHOULD ONLY BE CALLED IN TESTS!!!!
*
* TODO: find a way to change the tests such that we can remove this method
*/
public static void clear() {
logger.error("clear should only be called by tests");
instance = null;
}
/**
* Configures the socket to require hostname verification using the LDAPS
* @param sslSocket
*/
@Override
protected void configureSSLSocket(SSLSocket sslSocket) {
sslSocket.setSSLParameters(sslParameters);
}
}

View File

@ -5,24 +5,8 @@
*/ */
package org.elasticsearch.shield.authc.support.ldap; package org.elasticsearch.shield.authc.support.ldap;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.ssl.SSLService;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import java.io.IOException; import javax.net.ssl.SSLSocketFactory;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.util.regex.Pattern;
import static java.util.Arrays.asList;
import static org.elasticsearch.common.base.Predicates.contains;
import static org.elasticsearch.common.collect.Iterables.all;
/** /**
* This factory is needed for JNDI configuration for LDAP connections. It wraps a single instance of a static * This factory is needed for JNDI configuration for LDAP connections. It wraps a single instance of a static
@ -32,37 +16,12 @@ import static org.elasticsearch.common.collect.Iterables.all;
* <p/> * <p/>
* http://docs.oracle.com/javase/tutorial/jndi/ldap/ssl.html * http://docs.oracle.com/javase/tutorial/jndi/ldap/ssl.html
*/ */
public class LdapSslSocketFactory extends SocketFactory { public class LdapSslSocketFactory extends AbstractLdapSslSocketFactory {
private static ESLogger logger = Loggers.getLogger(LdapSslSocketFactory.class);
static final String JAVA_NAMING_LDAP_FACTORY_SOCKET = "java.naming.ldap.factory.socket";
private static final Pattern STARTS_WITH_LDAPS = Pattern.compile("^ldaps:.*", Pattern.CASE_INSENSITIVE);
private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", Pattern.CASE_INSENSITIVE);
private static LdapSslSocketFactory instance; private static LdapSslSocketFactory instance;
private static SSLService sslService; public LdapSslSocketFactory(SSLSocketFactory socketFactory) {
super(socketFactory);
/**
* This should only be invoked once to establish a static instance that will be used for each constructor.
*/
@Inject
public static void init(SSLService sslService) {
LdapSslSocketFactory.sslService = sslService;
}
/**
* This clears the static factory. There are threading issues with this. But for
* testing this is useful.
*
* WARNING: THIS METHOD SHOULD ONLY BE CALLED IN TESTS!!!!
*
* TODO: find a way to change the tests such that we can remove this method
*/
public static void clear() {
logger.error("clear should only be called by tests");
instance = null;
} }
/** /**
@ -77,75 +36,16 @@ public class LdapSslSocketFactory extends SocketFactory {
return instance; return instance;
} }
final private SocketFactory socketFactory;
private LdapSslSocketFactory(SocketFactory wrappedSocketFactory) {
socketFactory = wrappedSocketFactory;
}
//The following methods are all wrappers around the instance of socketFactory
@Override
public Socket createSocket() throws IOException {
return socketFactory.createSocket();
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return socketFactory.createSocket(host, port);
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return socketFactory.createSocket(host, port, localHost, localPort);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return socketFactory.createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
return socketFactory.createSocket(host, port, localHost, localPort);
}
/** /**
* If one of the ldapUrls are SSL this will set the LdapSslSocketFactory as a socket provider on the builder * This clears the static factory. There are threading issues with this. But for
* testing this is useful.
* *
* @param ldapUrls array of ldap urls, either all SSL or none with SSL (no mixing) * WARNING: THIS METHOD SHOULD ONLY BE CALLED IN TESTS!!!!
* @param builder set of jndi properties, that will *
* @throws org.elasticsearch.shield.ShieldSettingsException if URLs have mixed protocols. * TODO: find a way to change the tests such that we can remove this method
*/ */
public static void configureJndiSSL(String[] ldapUrls, ImmutableMap.Builder<String, Serializable> builder) { public static void clear() {
boolean secureProtocol = secureUrls(ldapUrls); logger.error("clear should only be called by tests");
if (secureProtocol) { instance = null;
builder.put(JAVA_NAMING_LDAP_FACTORY_SOCKET, LdapSslSocketFactory.class.getName());
} else {
logger.warn("LdapSslSocketFactory not used for LDAP connections");
if (logger.isDebugEnabled()) {
logger.debug("LdapSslSocketFactory: secureProtocol = [{}]", secureProtocol);
}
}
}
/**
* @param ldapUrls URLS in the form of "ldap://..." or "ldaps://..."
* @return true if all URLS are ldaps, also true it ldapUrls is empty. False otherwise
*/
public static boolean secureUrls(String[] ldapUrls) {
if (ldapUrls.length == 0) {
return true;
}
boolean allSecure = all(asList(ldapUrls), contains(STARTS_WITH_LDAPS));
boolean allClear = all(asList(ldapUrls), contains(STARTS_WITH_LDAP));
if (!allSecure && !allClear) {
//No mixing is allowed because LdapSSLSocketFactory produces only SSL sockets and not clear text sockets
throw new ShieldSettingsException("Configured ldap protocols are not all equal " +
"(ldaps://.. and ldap://..): [" + Strings.arrayToCommaDelimitedString(ldapUrls) + "]");
}
return allSecure;
} }
} }

View File

@ -46,8 +46,12 @@ public class SSLService extends AbstractComponent {
} }
public SSLEngine createSSLEngine(Settings settings) { public SSLEngine createSSLEngine(Settings settings) {
return createSSLEngine(settings, null, -1);
}
public SSLEngine createSSLEngine(Settings settings, String host, int port) {
String[] ciphers = settings.getAsArray("ciphers", componentSettings.getAsArray("ciphers", DEFAULT_CIPHERS)); String[] ciphers = settings.getAsArray("ciphers", componentSettings.getAsArray("ciphers", DEFAULT_CIPHERS));
return createSSLEngine(getSslContext(settings), ciphers); return createSSLEngine(getSslContext(settings), ciphers, host, port);
} }
public SSLContext getSslContext() { public SSLContext getSslContext() {
@ -99,8 +103,8 @@ public class SSLService extends AbstractComponent {
return sslContext; return sslContext;
} }
private SSLEngine createSSLEngine(SSLContext sslContext, String[] ciphers) { private SSLEngine createSSLEngine(SSLContext sslContext, String[] ciphers, String host, int port) {
SSLEngine sslEngine = sslContext.createSSLEngine(); SSLEngine sslEngine = sslContext.createSSLEngine(host, port);
try { try {
sslEngine.setEnabledCipherSuites(ciphers); sslEngine.setEnabledCipherSuites(ciphers);
} catch (Throwable t) { } catch (Throwable t) {

View File

@ -8,10 +8,10 @@ package org.elasticsearch.shield.transport.netty;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.netty.channel.ChannelPipeline; import org.elasticsearch.common.netty.channel.*;
import org.elasticsearch.common.netty.channel.ChannelPipelineFactory;
import org.elasticsearch.common.netty.handler.ssl.SslHandler; import org.elasticsearch.common.netty.handler.ssl.SslHandler;
import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.shield.ssl.SSLService; import org.elasticsearch.shield.ssl.SSLService;
@ -20,11 +20,14 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.netty.NettyTransport; import org.elasticsearch.transport.netty.NettyTransport;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import java.net.InetSocketAddress;
/** /**
* *
*/ */
public class NettySecuredTransport extends NettyTransport { public class NettySecuredTransport extends NettyTransport {
public static final String HOSTNAME_VERIFICATION_SETTING = "shield.ssl.hostname_verification";
private final SSLService sslService; private final SSLService sslService;
private final @Nullable IPFilter authenticator; private final @Nullable IPFilter authenticator;
@ -91,13 +94,38 @@ public class NettySecuredTransport extends NettyTransport {
public ChannelPipeline getPipeline() throws Exception { public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline(); ChannelPipeline pipeline = super.getPipeline();
if (ssl) { if (ssl) {
SSLEngine clientEngine = sslService.createSSLEngine(); pipeline.addFirst("sslInitializer", new ClientSslHandlerInitializer());
clientEngine.setUseClientMode(true);
pipeline.addFirst("ssl", new SslHandler(clientEngine));
} }
pipeline.replace("dispatcher", "dispatcher", new SecuredMessageChannelHandler(nettyTransport, "default", logger)); pipeline.replace("dispatcher", "dispatcher", new SecuredMessageChannelHandler(nettyTransport, "default", logger));
return pipeline; return pipeline;
} }
/**
* Handler that waits until connect is called to create a SSLEngine with the proper parameters in order to
* perform hostname verification
*/
private class ClientSslHandlerInitializer extends SimpleChannelHandler {
@Override
public void connectRequested(ChannelHandlerContext ctx, ChannelStateEvent e) {
SSLEngine sslEngine;
if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
InetSocketAddress inetSocketAddress = (InetSocketAddress) e.getValue();
String hostname = inetSocketAddress.getHostName();
int port = inetSocketAddress.getPort();
sslEngine = sslService.createSSLEngine(ImmutableSettings.EMPTY, hostname, port);
SSLParameters parameters = new SSLParameters();
parameters.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(parameters);
} else {
sslEngine = sslService.createSSLEngine();
}
sslEngine.setUseClientMode(true);
ctx.getPipeline().replace(this, "ssl", new SslHandler(sslEngine));
ctx.sendDownstream(e);
}
}
} }
} }

View File

@ -37,12 +37,14 @@ public class SecuredMessageChannelHandler extends MessageChannelHandler {
public void channelConnected(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception { public void channelConnected(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception {
SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class); SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class);
// Get notified when SSL handshake is done. // Make sure handler is present and we are the client
if (sslHandler == null) { if (sslHandler == null || !sslHandler.getEngine().getUseClientMode()) {
return; return;
} }
final ChannelFuture handshakeFuture = sslHandler.handshake(); final ChannelFuture handshakeFuture = sslHandler.handshake();
// Get notified when SSL handshake is done.
handshakeFuture.addListener(new ChannelFutureListener() { handshakeFuture.addListener(new ChannelFutureListener() {
@Override @Override
public void operationComplete(ChannelFuture future) throws Exception { public void operationComplete(ChannelFuture future) throws Exception {

View File

@ -11,11 +11,9 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.ldap.LdapConnection; import org.elasticsearch.shield.authc.ldap.LdapConnection;
import org.elasticsearch.shield.authc.ldap.LdapConnectionFactory; import org.elasticsearch.shield.authc.ldap.LdapConnectionFactory;
import org.elasticsearch.shield.authc.ldap.LdapConnectionTests; import org.elasticsearch.shield.authc.ldap.LdapConnectionTests;
import org.elasticsearch.shield.authc.ldap.LdapException;
import org.elasticsearch.shield.authc.support.SecuredStringTests; import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapConnection; import org.elasticsearch.shield.authc.support.ldap.*;
import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapTest;
import org.elasticsearch.shield.ssl.SSLService; import org.elasticsearch.shield.ssl.SSLService;
import org.elasticsearch.test.ElasticsearchTestCase; import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.test.junit.annotations.Network; import org.elasticsearch.test.junit.annotations.Network;
@ -40,7 +38,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
@BeforeClass @BeforeClass
public static void setTrustStore() throws URISyntaxException { public static void setTrustStore() throws URISyntaxException {
File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile(); File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile();
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder() AbstractLdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore.path", filename) .put("shield.ssl.keystore.path", filename)
.put("shield.ssl.keystore.password", "changeit") .put("shield.ssl.keystore.password", "changeit")
.build())); .build()));
@ -49,12 +47,13 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
@AfterClass @AfterClass
public static void clearTrustStore() { public static void clearTrustStore() {
LdapSslSocketFactory.clear(); LdapSslSocketFactory.clear();
HostnameVerifyingLdapSslSocketFactory.clear();
} }
@Test @SuppressWarnings("unchecked") @Test @SuppressWarnings("unchecked")
public void testAdAuth() { public void testAdAuth() {
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory( ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(
buildAdSettings(AD_LDAP_URL, AD_DOMAIN)); buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false));
String userName = "ironman"; String userName = "ironman";
try (AbstractLdapConnection ldap = connectionFactory.open(userName, SecuredStringTests.build(PASSWORD))) { try (AbstractLdapConnection ldap = connectionFactory.open(userName, SecuredStringTests.build(PASSWORD))) {
@ -76,6 +75,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
public void testTcpReadTimeout() { public void testTcpReadTimeout() {
Settings settings = ImmutableSettings.builder() Settings settings = ImmutableSettings.builder()
.put(buildAdSettings(AD_LDAP_URL, AD_DOMAIN)) .put(buildAdSettings(AD_LDAP_URL, AD_DOMAIN))
.put(ConnectionFactory.HOSTNAME_VERIFICATION_SETTING, false)
.put(ConnectionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") .put(ConnectionFactory.TIMEOUT_TCP_READ_SETTING, "1ms")
.build(); .build();
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(settings); ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(settings);
@ -90,7 +90,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
@Test @Test
public void testAdAuth_avengers() { public void testAdAuth_avengers() {
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory( ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(
buildAdSettings(AD_LDAP_URL, AD_DOMAIN)); buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false));
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", }; String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", };
for(String user: users) { for(String user: users) {
@ -102,7 +102,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
@Test @SuppressWarnings("unchecked") @Test @SuppressWarnings("unchecked")
public void testAdAuth_specificUserSearch() { public void testAdAuth_specificUserSearch() {
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"); Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", false);
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(settings); ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(settings);
String userName = "hulk"; String userName = "hulk";
@ -124,7 +124,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
public void testAD_standardLdapConnection(){ public void testAD_standardLdapConnection(){
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com"; String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
Settings settings = LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, true); Settings settings = LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, true, false);
LdapConnectionFactory connectionFactory = new LdapConnectionFactory(settings); LdapConnectionFactory connectionFactory = new LdapConnectionFactory(settings);
String user = "Bruce Banner"; String user = "Bruce Banner";
@ -146,18 +146,52 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
} }
} }
@Test(expected = ActiveDirectoryException.class)
public void testAdAuthWithHostnameVerification() {
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(
buildAdSettings(AD_LDAP_URL, AD_DOMAIN));
String userName = "ironman";
try (AbstractLdapConnection ldap = connectionFactory.open(userName, SecuredStringTests.build(PASSWORD))) {
fail("Test active directory certificate does not have proper hostname/ip address for hostname verification");
}
}
@Test(expected = LdapException.class)
public void testADStandardLdapHostnameVerification(){
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
Settings settings = LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, true);
LdapConnectionFactory connectionFactory = new LdapConnectionFactory(settings);
String user = "Bruce Banner";
try (LdapConnection ldap = connectionFactory.open(user, SecuredStringTests.build(PASSWORD))) {
fail("Test active directory certificate does not have proper hostname/ip address for hostname verification");
}
}
public static Settings buildAdSettings(String ldapUrl, String adDomainName) { public static Settings buildAdSettings(String ldapUrl, String adDomainName) {
return buildAdSettings(ldapUrl, adDomainName, true);
}
public static Settings buildAdSettings(String ldapUrl, String adDomainName, boolean hostnameVerification) {
return ImmutableSettings.builder() return ImmutableSettings.builder()
.put(ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl) .put(ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl)
.put(ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName) .put(ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
.put(ActiveDirectoryConnectionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification)
.build(); .build();
} }
public static Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN) { public static Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN) {
return buildAdSettings(ldapUrl, adDomainName, userSearchDN, true);
}
public static Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN, boolean hostnameVerification) {
return ImmutableSettings.builder() return ImmutableSettings.builder()
.putArray(ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl) .putArray(ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl)
.put(ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName) .put(ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
.put(ActiveDirectoryConnectionFactory.AD_USER_SEARCH_BASEDN_SETTING, userSearchDN) .put(ActiveDirectoryConnectionFactory.AD_USER_SEARCH_BASEDN_SETTING, userSearchDN)
.put(ActiveDirectoryConnectionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification)
.build(); .build();
} }
} }

View File

@ -10,16 +10,17 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.support.SecuredStringTests; import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory; import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory;
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory;
import org.elasticsearch.shield.authc.support.ldap.HostnameVerifyingLdapSslSocketFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory; import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
import org.elasticsearch.shield.ssl.SSLService; import org.elasticsearch.shield.ssl.SSLService;
import org.elasticsearch.test.ElasticsearchTestCase; import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.test.junit.annotations.Network; import org.elasticsearch.test.junit.annotations.Network;
import org.junit.AfterClass; import org.junit.*;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
@ -32,16 +33,18 @@ public class OpenLdapTests extends ElasticsearchTestCase {
@BeforeClass @BeforeClass
public static void setTrustStore() throws URISyntaxException { public static void setTrustStore() throws URISyntaxException {
File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile(); Path keystore = Paths.get(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).toAbsolutePath();
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder() SSLService sslService = new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore.path", filename) .put("shield.ssl.keystore.path", keystore)
.put("shield.ssl.keystore.password", "changeit") .put("shield.ssl.keystore.password", "changeit")
.build())); .build());
AbstractLdapSslSocketFactory.init(sslService);
} }
@AfterClass @AfterClass
public static void clearTrustStore() { public static void clearTrustStore() {
LdapSslSocketFactory.clear(); LdapSslSocketFactory.clear();
HostnameVerifyingLdapSslSocketFactory.clear();
} }
@Test @Test
@ -51,7 +54,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
LdapConnectionFactory connectionFactory = new LdapConnectionFactory( LdapConnectionFactory connectionFactory = new LdapConnectionFactory(
LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true)); LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true, false));
String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" }; String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" };
for (String user : users) { for (String user : users) {
@ -67,6 +70,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
Settings settings = ImmutableSettings.builder() Settings settings = ImmutableSettings.builder()
.put(LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true)) .put(LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true))
.put(ConnectionFactory.HOSTNAME_VERIFICATION_SETTING, false)
.put(ConnectionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") //1 millisecond .put(ConnectionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") //1 millisecond
.build(); .build();
@ -86,16 +90,32 @@ public class OpenLdapTests extends ElasticsearchTestCase {
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
Settings settings = ImmutableSettings.builder() Settings settings = ImmutableSettings.builder()
.put(LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true)) .put(LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true))
.put(ConnectionFactory.HOSTNAME_VERIFICATION_SETTING, false)
.put(ConnectionFactory.TIMEOUT_LDAP_SETTING, "1ms") //1 millisecond .put(ConnectionFactory.TIMEOUT_LDAP_SETTING, "1ms") //1 millisecond
.build(); .build();
LdapConnectionFactory connectionFactory = new LdapConnectionFactory(settings); LdapConnectionFactory connectionFactory = new LdapConnectionFactory(settings);
try (LdapConnection ldap = connectionFactory.open("thor", SecuredStringTests.build(PASSWORD))){ try (LdapConnection ldap = connectionFactory.open("thor", SecuredStringTests.build(PASSWORD))) {
ldap.groups(); ldap.groups();
fail("The server should timeout the group request"); fail("The server should timeout the group request");
} catch (LdapException e) { } catch (LdapException e) {
assertThat(e.getCause().getMessage(), containsString("error code 32")); //openldap response for timeout assertThat(e.getCause().getMessage(), containsString("error code 32")); //openldap response for timeout
} }
} }
@Test(expected = LdapException.class)
public void testStandardLdapConnectionHostnameVerification() {
//openldap does not use cn as naming attributes by default
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
LdapConnectionFactory connectionFactory = new LdapConnectionFactory(
LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true));
String user = "blackwidow";
try (LdapConnection ldap = connectionFactory.open(user, SecuredStringTests.build(PASSWORD))) {
fail("OpenLDAP certificate does not contain the correct hostname/ip so hostname verification should fail on open");
}
}
} }

View File

@ -0,0 +1,123 @@
/*
* 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.shield.authc.support.ldap;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.ldap.LdapConnectionTests;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.ssl.SSLService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.hamcrest.Matchers;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.Serializable;
import java.net.URISyntaxException;
import static org.hamcrest.Matchers.equalTo;
public class ConnectionFactoryTests extends ElasticsearchTestCase {
@Test
public void testConfigure_1ldaps() {
String[] urls = new String[] { "ldaps://example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat((String) settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET),
equalTo(LdapSslSocketFactory.class.getName()));
}
@Test
public void testConfigure_2ldaps() {
String[] urls = new String[] { "ldaps://primary.example.com:636", "LDAPS://secondary.example.com:10636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), Matchers.<Serializable>equalTo(LdapSslSocketFactory.class.getName()));
}
@Test
public void testConfigure_2ldap() {
String[] urls = new String[] { "ldap://primary.example.com:392", "LDAP://secondary.example.com:10392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), equalTo(null));
}
@Test
public void testConfigure_1ldapsWithHostnameVerification() {
String[] urls = new String[] { "ldaps://example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.builder();
createConnectionFactoryWithHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat((String) settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET),
equalTo(HostnameVerifyingLdapSslSocketFactory.class.getName()));
}
@Test
public void testConfigure_2ldapsWithHostnameVerification() {
String[] urls = new String[] { "ldaps://primary.example.com:636", "LDAPS://secondary.example.com:10636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), Matchers.<Serializable>equalTo(HostnameVerifyingLdapSslSocketFactory.class.getName()));
}
@Test
public void testConfigure_2ldapWithHostnameVerification() {
String[] urls = new String[] { "ldap://primary.example.com:392", "LDAP://secondary.example.com:10392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), equalTo(null));
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1ldaps_1ldap() {
String[] urls = new String[] { "LDAPS://primary.example.com:636", "ldap://secondary.example.com:392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1ldap_1ldaps() {
String[] urls = new String[] { "ldap://primary.example.com:392", "ldaps://secondary.example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
}
private ConnectionFactory createConnectionFactoryWithoutHostnameVerification() {
return new ConnectionFactory(ImmutableSettings.builder().put("hostname_verification", false).build()) {
@Override
public AbstractLdapConnection open(String user, SecuredString password) {
return null;
}
};
}
private ConnectionFactory createConnectionFactoryWithHostnameVerification() {
return new ConnectionFactory(ImmutableSettings.EMPTY) {
@Override
public AbstractLdapConnection open(String user, SecuredString password) {
return null;
}
};
}
}

View File

@ -1,87 +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.shield.authc.support.ldap;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.ldap.LdapConnectionTests;
import org.elasticsearch.shield.ssl.SSLService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.hamcrest.Matchers;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.Serializable;
import java.net.URISyntaxException;
import static org.hamcrest.Matchers.equalTo;
public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
@BeforeClass
public static void setTrustStore() throws URISyntaxException {
File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile();
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore.path", filename)
.put("shield.ssl.keystore.password", "changeit")
.build()));
}
@AfterClass
public static void clearTrustStore() {
LdapSslSocketFactory.clear();
}
@Test
public void testConfigure_1ldaps() {
String[] urls = new String[] { "ldaps://example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat((String) settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET),
equalTo("org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory"));
}
@Test
public void testConfigure_2ldaps() {
String[] urls = new String[] { "ldaps://primary.example.com:636", "LDAPS://secondary.example.com:10636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), Matchers.<Serializable>equalTo("org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory"));
}
@Test
public void testConfigure_2ldap() {
String[] urls = new String[] { "ldap://primary.example.com:392", "LDAP://secondary.example.com:10392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), equalTo(null));
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1ldaps_1ldap() {
String[] urls = new String[] { "LDAPS://primary.example.com:636", "ldap://secondary.example.com:392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1ldap_1ldaps() {
String[] urls = new String[] { "ldap://primary.example.com:392", "ldaps://secondary.example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
}
}

View File

@ -46,12 +46,21 @@ public abstract class LdapTest extends ElasticsearchTestCase {
return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch ); return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch );
} }
public static Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, boolean isSubTreeSearch, boolean hostnameVerification) {
return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch, hostnameVerification );
}
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch) { public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, isSubTreeSearch, true);
}
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch, boolean hostnameVerification) {
return ImmutableSettings.builder() return ImmutableSettings.builder()
.putArray(LdapConnectionFactory.URLS_SETTING, ldapUrl) .putArray(LdapConnectionFactory.URLS_SETTING, ldapUrl)
.putArray(LdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate) .putArray(LdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate)
.put(LdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase) .put(LdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase)
.put(LdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build(); .put(LdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch)
.put(LdapConnectionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification).build();
} }
protected Settings buildNonCachingSettings() { protected Settings buildNonCachingSettings() {

View File

@ -89,6 +89,7 @@ public class ServerTransportFilterIntegrationTest extends ShieldIntegrationTest
.put(ShieldSettingsSource.getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode")) .put(ShieldSettingsSource.getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode"))
.put("node.mode", "network") .put("node.mode", "network")
.put("node.name", "my-test-node") .put("node.name", "my-test-node")
.put("network.host", "localhost")
.put("cluster.name", internalCluster().getClusterName()) .put("cluster.name", internalCluster().getClusterName())
.put("discovery.zen.ping.multicast.enabled", false) .put("discovery.zen.ping.multicast.enabled", false)
.put("discovery.zen.ping.unicast.hosts", unicastHost) .put("discovery.zen.ping.unicast.hosts", unicastHost)

View File

@ -0,0 +1,95 @@
/*
* 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.shield.transport.netty;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.NoNodeAvailableException;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
import org.elasticsearch.test.ShieldIntegrationTest;
import org.elasticsearch.transport.Transport;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
@ClusterScope(scope = Scope.SUITE)
public class SslHostnameVerificationIntegrationTests extends ShieldIntegrationTest {
static Path keystore;
@Override
protected boolean sslTransportEnabled() {
return true;
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
ImmutableSettings.Builder settingsBuilder = settingsBuilder().put(super.nodeSettings(nodeOrdinal));
try {
/*
* This keystore uses a cert without any subject alternative names and a CN of "Elasticsearch Test Node No SAN"
* that will not resolve to a DNS name and will always cause hostname verification failures
*/
keystore = Paths.get(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode-no-subjaltname.jks").toURI());
assertThat(Files.exists(keystore), is(true));
} catch (Exception e) {
throw new RuntimeException(e);
}
return settingsBuilder.put("shield.ssl.keystore.path", keystore.toAbsolutePath()) // settings for client truststore
.put("shield.ssl.keystore.password", "testnode-no-subjaltname")
.put("shield.ssl.truststore.path", keystore.toAbsolutePath()) // settings for client truststore
.put("shield.ssl.truststore.password", "testnode-no-subjaltname")
.put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, false) // disable hostname verification as this test uses non-localhost addresses
.build();
}
@Override
protected Settings transportClientSettings() {
return ImmutableSettings.builder().put(super.transportClientSettings())
.put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, false)
.put("shield.ssl.truststore.path", keystore.toAbsolutePath()) // settings for client truststore
.put("shield.ssl.truststore.password", "testnode-no-subjaltname")
.build();
}
@Test(expected = NoNodeAvailableException.class)
public void testThatHostnameMismatchDeniesTransportClientConnection() throws Exception {
Transport transport = internalCluster().getDataNodeInstance(Transport.class);
TransportAddress transportAddress = transport.boundAddress().publishAddress();
assertThat(transportAddress, instanceOf(InetSocketTransportAddress.class));
InetSocketAddress inetSocketAddress = ((InetSocketTransportAddress) transportAddress).address();
Settings settings = ImmutableSettings.builder().put(transportClientSettings())
.put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, true)
.build();
try (TransportClient client = new TransportClient(settings, false)) {
client.addTransportAddress(new InetSocketTransportAddress(inetSocketAddress.getHostName(), inetSocketAddress.getPort()));
client.admin().cluster().prepareHealth().get();
fail("Expected a NoNodeAvailableException due to hostname verification failures");
}
}
@Test
public void testTransportClientConnectionIgnoringHostnameVerification() throws Exception {
Client client = internalCluster().transportClient();
assertGreenClusterState(client);
}
}

View File

@ -21,6 +21,7 @@ import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.signature.InternalSignatureService; import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
import org.elasticsearch.test.discovery.ClusterDiscoveryConfiguration; import org.elasticsearch.test.discovery.ClusterDiscoveryConfiguration;
import java.io.File; import java.io.File;
@ -239,7 +240,8 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ
if (sslTransportEnabled) { if (sslTransportEnabled) {
builder.put("shield.ssl.keystore.path", store.getPath()) builder.put("shield.ssl.keystore.path", store.getPath())
.put("shield.ssl.keystore.password", password); .put("shield.ssl.keystore.password", password)
.put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, RandomizedTest.randomBoolean());
} }
if (sslTransportEnabled && RandomizedTest.randomBoolean()) { if (sslTransportEnabled && RandomizedTest.randomBoolean()) {

View File

@ -0,0 +1,69 @@
= Keystore Details
This document details the steps used to create the certificate and keystore files in this directory.
== Instructions on generating self-signed certificates
The certificates in this directory have been generated using the following openssl configuration and commands.
OpenSSL Configuration File
[source,shell]
-----------------------------------------------------------------------------------------------------------
[ req ]
default_bits = 2048 # Size of keys
default_keyfile = key.pem # name of generated keys
default_md = sha256 # message digest algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
0.organizationName = Organization Name (company)
organizationalUnitName = Organizational Unit Name (department, division)
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64
[ v3_req ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
#subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
IP.1 = 127.0.0.1
IP.2 = ::2
-----------------------------------------------------------------------------------------------------------
NOTE: The `alt_names` section provides the Subject Alternative Names for each certificate. This is necessary for testing
with hostname verification enabled.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl req -new -x509 -extensions v3_req -out <NAME>.cert -keyout <NAME>.pem -days 1460 -config config.cnf
-----------------------------------------------------------------------------------------------------------
When prompted the password is always set to the value of <NAME>.
Because we intend to import these certificates into a Java Keystore file, they certificate and private key must be combined
in a PKCS12 certificate.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl pkcs12 -export -name <NAME> -in <NAME>.cert -inkey <NAME>.pem -out <NAME>.p12
-----------------------------------------------------------------------------------------------------------
== Creating the Keystore
We need to create a keystore from the created PKCS12 certificate.
[source,shell]
-----------------------------------------------------------------------------------------------------------
keytool -importkeystore -destkeystore <NAME>.jks -srckeystore <NAME>.p12 -srcstoretype pkcs12 -alias <NAME>
-----------------------------------------------------------------------------------------------------------
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.

View File

@ -1,18 +1,19 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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
hTWLIhU=
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,17 +1,21 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDMzCCAhugAwIBAgIEIqIBljANBgkqhkiG9w0BAQsFADBKMQwwCgYDVQQKEwNvcmcxFjAUBgNV MIIDcTCCAlmgAwIBAgIJANA/+zUU47fOMA0GCSqGSIb3DQEBCwUAMEoxDDAKBgNV
BAsTDWVsYXN0aWNzZWFyY2gxIjAgBgNVBAMTGUVsYXN0aWNzZWFyY2ggVGVzdCBDbGllbnQwHhcN BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEiMCAGA1UEAxMZRWxhc3Rp
MTQwNzIyMDc1MzA1WhcNMTQxMDIwMDc1MzA1WjBKMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsTDWVs Y3NlYXJjaCBUZXN0IENsaWVudDAeFw0xNDEyMTYxNzAyNTRaFw0xODEyMTUxNzAy
YXN0aWNzZWFyY2gxIjAgBgNVBAMTGUVsYXN0aWNzZWFyY2ggVGVzdCBDbGllbnQwggEiMA0GCSqG NTRaMEoxDDAKBgNVBAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEiMCAG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCZTqtl7W0u/e7GAqyFhbC4DLlBByH7B0ZwPdh/ulVfIzDi A1UEAxMZRWxhc3RpY3NlYXJjaCBUZXN0IENsaWVudDCCASIwDQYJKoZIhvcNAQEB
Er7zkOGQtxkrsWHQZmfeyroLwKhfIaA69Jvd+JRFqn53zW3iziduXeMs9qnNP6Mb1ePmBmrs6icU BQADggEPADCCAQoCggEBALceMGq3C3QP6uuCE/93MlwDP3NBVwl5eNL3JvygwQoU
J1Wcxz5O+JofJ8g3+9KWPHVS4Ls1or43W14fV5LAnSt5lOhC33ayawUstls3mdb2LUvqkgZTwFxd YpoABTU51+3JWjHkWxMbinUFEQV67EMa+5Uja8m39MoHC2hlTuECufe17bAFN23I
SwRAB6o6x+5JLrA3p2sVh97C4PyC5BxfR7EWfR6/gc7BvcqP9yxfKPf4TmaufStNQ3ESoH5wzGUy SE/x8qaBPECvjR1Rf3Dt+rfJ7/n76aObIt7exsR1uxB/O2eRMNGXZSCsQrQy+n/o
i/XWUy0GYjaqk+DM1RTjhVetDr2jPl2akILqA0W3pTtjutsVxmN0W8EN9hGBlCWHyxjHAgMBAAGj T45gFDkY1CeMthsUxj4TkmeHK+wcDx00Te1mfA4J3Ef5ykIxAwDJxEBIaqGAv077
ITAfMB0GA1UdDgQWBBR15qT6iIEGeeeE7IMY9ZxGhDQHWzANBgkqhkiG9w0BAQsFAAOCAQEAdhic +02RdG1IHGEvJeTGwZ+GFem9U8zF3UVBX/jyqHY82YyBaXrwjlhDz4dmHVqTVQYC
qBw2TqnkOZOtRCxt5u4DnZE63i1W//kuFEjf0ee6/PFpPlKLROCzBCnf/ys49ixg6Q50H6U0SKwL Qn/SkrQyJknggnWCrCoGS31GfKyFbmrUWjhAtVkreRkCAwEAAaNaMFgwCQYDVR0T
bMhdyAqfZakEknR8uTpvWP3WA+BcCuUB2drlvd3DWI+FF889otOwacNOrwyHnrN7Uv18Hh9PPcyD BAIwADAdBgNVHQ4EFgQU4QdRaaIGD6uyQk/WiwEtExBI7hgwLAYDVR0RBCUwI4IJ
x3V9uZSWzIQ4hU2+Fe0HHLenxIUFh6nqfCBy+81+AKQQuN4vE7TRUq/2WIK5rmwB8pakEoyc15ks bG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAACMA0GCSqGSIb3DQEBCwUA
i/NGa1GjUvfM8G4FprINJ+Xrg6kcC7SL5pJVMCA+8c/iMPm22/XOYj7m4UL9BXKW9FFJX+3AMVZm A4IBAQAexhtJBZ2jQrJ1M7HRFCFRVP6oi/5D139/0t8qG9KL9oQXlY//gQweSPk9
I4xHtBAZU4nkbnvDrbmsQvzGAp565LBv1g== gCPywfQjBfY6Fck2iliCw6YR3K/Bax9lOX/rKJ/jWrcUUBKzP9ap8ijKa/1EJchp
VVf7M2g7V00pSR63fl+EnMzv8H/uTK4BMWiE9sl01oK/kyVDR/wd/GktnQpZYF5B
5FvuMi2ocM+hSpbejLVi4yW6/wN6rcjofon/u3ngzicwou59r99IavlW/qk5DHbI
HD2pK4tOqQX06pzdc8MHKQ16JxSt3dk/aljmvDUf93e/KNI9RSoZpydBqXlpaACi
52lEcHwQJVoe6L5VdHc6090U09K7
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,18 +1,19 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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
dF+PXJw=
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDTTCCAjWgAwIBAgIJALL7dwEsWamvMA0GCSqGSIb3DQEBCwUAME8xDDAKBgNV
BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEnMCUGA1UEAxMeRWxhc3Rp
Y3NlYXJjaCBUZXN0IE5vZGUgTm8gU0FOMB4XDTE0MTIxNjE5NTcyNloXDTE4MTIx
NTE5NTcyNlowTzEMMAoGA1UEChMDb3JnMRYwFAYDVQQLEw1lbGFzdGljc2VhcmNo
MScwJQYDVQQDEx5FbGFzdGljc2VhcmNoIFRlc3QgTm9kZSBObyBTQU4wggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkIGS7A/V6TesR34ajMyNYL3tB1OjW
Raq4KtF8FfW1H6nHGrWa/qXjZWPirczy1k2n6ZL7YOCcv/YeY8xAqC9mGQxvEuqo
EaqXq2cjRdAs/7zqzRkdPPi3Jw/p/RHrDfOAzOsMnBGc0G2Hrsj//aP44vp85pek
fM3t2kNAYZWYCzXUqWAIUoxBDK4DcQdsN8H4KTMIwQEEiRtcKnL/b8QGKsyGLfLq
36ZABHZ4kY2SmcP3bWxZtbFN4hamdwoAtYe+lS0/ee8/fOTLyZ3Ey+X6EEmGO1lk
WR4XLli15k1L2HBzWGG7zwxVEC5r2h3Sx1njYh/Jq3khIdSvDbiMmM+VAgMBAAGj
LDAqMAkGA1UdEwQCMAAwHQYDVR0OBBYEFGm8wrYF9mJweJ1vloDw19e0PUuIMA0G
CSqGSIb3DQEBCwUAA4IBAQBbEZ73weDphNIcmvN25v6NIfjBebqgm0/2grDFwmZe
Z1DibzRoVfoQ7WeUqbPS7SHUQ+KzIN1GdfHXhW9r6mmLbtzPv90Q/8zBcNv5HNZZ
YK+T2r9hoAWEY6nB1fiOJ4udkFMYfAi6LiSxave4IPWp/WIqd0IWtPtkPl+MmG41
TfRom8TnO+o+VsjgDkY5Q1JDsNQKy1BrtxzIZyz7d1zYKTQ+HXZ4yeYJoVoc3k4y
6w9eX2zAUZ6Z3d4an6CLr6Hew9Dj2VX1vqCj1a5/VvHZVyVxyh4hg8sHYm7tZOJX
wN3B5GcKwbbFjaMVBLaMlP62OdGg7tCh61evWm+l06S0
-----END CERTIFICATE-----

View File

@ -1,17 +1,21 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDLzCCAhegAwIBAgIEQv9ZtTANBgkqhkiG9w0BAQsFADBIMQwwCgYDVQQKEwNvcmcxFjAUBgNV MIIDbTCCAlWgAwIBAgIJAJ+K5mGS3n/AMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNV
BAsTDWVsYXN0aWNzZWFyY2gxIDAeBgNVBAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMB4XDTE0 BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3Rp
MDcyMjA3NTI0N1oXDTE0MTAyMDA3NTI0N1owSDEMMAoGA1UEChMDb3JnMRYwFAYDVQQLEw1lbGFz Y3NlYXJjaCBUZXN0IE5vZGUwHhcNMTQxMjE2MTcwNDQ1WhcNMTgxMjE1MTcwNDQ1
dGljc2VhcmNoMSAwHgYDVQQDExdFbGFzdGljc2VhcmNoIFRlc3QgTm9kZTCCASIwDQYJKoZIhvcN WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsTDWVsYXN0aWNzZWFyY2gxIDAeBgNV
AQEBBQADggEPADCCAQoCggEBAJJ7G1e8NzJAhx4PjZQQQimDECwe3nN8ulG23cOE5fYePKh8b0L7 BAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkqhkiG9w0BAQEFAAOC
cr5O7gMOo7XiXZnptXh8n2J0Fi+3UqnyEallxUmUewdUiwGuzeFHHBtpS2S6LjXR2J+n0Q267u7j AQ8AMIIBCgKCAQEAzhpW7iwkm+Og+HP7U00nbmh0Hy9Z2Ldp5i8tJSlSQwTxCCvO
wisnUxRv7Cwre/PP2U89b303M1mg/fqNE5Zjb1MpCywoWq+Z07xNDX9aoOWTKQbRuR2931IGZ1QL rse6jwJQN98Dk1ApaSzimZrlKOotFyPV1L3fnOzJbTp1Yq/VsYP4zJkjWtID0qUf
gX//emymIGGh7XVPoydhtEkPcGXAdFz3OO/K33PTEk24vGzr33dBm/yywa12kaC/6Q49luNmdgsG 8Rg8bLhjKAG+ZlLuai5XZqnLkdmqvQeR61VhpXWFm0Om153tWmAiHL18ywY71gXN
M5JQ8kKQe0Cv64yMC17pMI2PWwpBuInF1RCx588I3ctHCATm7AL2E/YvG9MqlvkCAwEAAaMhMB8w EnkeFo9OW4fDqkz6h7NJziYvU6URSKErZDEixk5GIPv9K9hiIfi0KQM6xaHp0d2w
HQYDVR0OBBYEFPi2gUxc88/DfM89Ku63Z/JkjeQxMA0GCSqGSIb3DQEBCwUAA4IBAQAm3XJyNIAZ VCyFVC0OUdugz6untURzJVx4U3X1bQcv/o2BoUotWh/5h8o5eeiiv2OGZ1XlO+33
jXyI/Hxv0rdZRRIoMzSjA+vyGwcrr4lBLcOxDgtHM68PPfhleHDK4JtTaGftuFqv8ylGa+zVJ+0N 1tweYI4wFjDwnAyHHRr/rk2ZIBiBYGaSzHnuhQIDAQABo1owWDAJBgNVHRMEAjAA
e7xFqvFwi0R9goU5j1GgTmt7mYPhgcgS8j9VCTxKZEsO0KmvV5CRz2jB1m4FdHbSR3KKwpgCt6kp MB0GA1UdDgQWBBTwGg2LF8+mzsvBBWxJKv6VXv3dMTAsBgNVHREEJTAjgglsb2Nh
K0HifL+pRHQa3Ts9DCBmzG4fwNGPsZvoI47T+JQ9EiUWdEW+dUvr/0T6yLTRsfc0ftZ2OTIJ97JV bGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAIwDQYJKoZIhvcNAQELBQADggEB
BgOlAa4b1b8v9933wv53fDwkhhdBvO/uFxrxeYk4szofe2l4fy5CdE7rkDk1mHIHtYWdAQhVL6cL ABP4ufLToJhcUselVxV9LPD5VGPEHGLdIFqsUEix7DMsiNpR76X6a8qNQbZpdbd6
vuDnZppB7koaDhBsssDBpRstnEHL +qPKqoaMgC7znX7qZtCqRbIXTWbudZPxFkcHdiWx3SiALMQYabeUGetClX3sCndU
SUoV8f34i8dJxfNcqhLcsh4zpgxtmwsvs5OLMTBvm0Xo2zUFUjlmrt41pBrWEuq9
nkObc/cr6Syiz3sy4pYVJO1/YwHaZgE/URqjVlari70DR3ES4YnIUnLQajKx2Q0/
gXVgzjbe68KPOUGCz6GYiWq+d4tcWdHzLv1GsaqQ1MD9P21ArfrX4DpzgPDrO6MP
9Ppq5DQGa2q4mz3kipd5RIs=
-----END CERTIFICATE----- -----END CERTIFICATE-----