001/* 002Copyright 2015 Hendrik Saly 003 004Licensed under the Apache License, Version 2.0 (the "License"); 005you may not use this file except in compliance with the License. 006You may obtain a copy of the License at 007 008 http://www.apache.org/licenses/LICENSE-2.0 009 010Unless required by applicable law or agreed to in writing, software 011distributed under the License is distributed on an "AS IS" BASIS, 012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013See the License for the specific language governing permissions and 014limitations under the License. 015 */ 016 017package de.saly.es.example.tssl.netty; 018 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.FileNotFoundException; 022import java.io.IOException; 023import java.net.InetSocketAddress; 024import java.security.KeyManagementException; 025import java.security.KeyStore; 026import java.security.KeyStoreException; 027import java.security.NoSuchAlgorithmException; 028import java.security.UnrecoverableKeyException; 029import java.security.cert.CertificateException; 030import java.util.Map; 031 032import javax.net.ssl.KeyManagerFactory; 033import javax.net.ssl.SSLContext; 034import javax.net.ssl.SSLEngine; 035import javax.net.ssl.SSLParameters; 036import javax.net.ssl.TrustManagerFactory; 037 038import org.elasticsearch.Version; 039import org.elasticsearch.common.inject.Inject; 040import org.elasticsearch.common.lang3.StringUtils; 041import org.elasticsearch.common.netty.channel.ChannelHandlerContext; 042import org.elasticsearch.common.netty.channel.ChannelPipeline; 043import org.elasticsearch.common.netty.channel.ChannelPipelineFactory; 044import org.elasticsearch.common.netty.channel.ChannelStateEvent; 045import org.elasticsearch.common.netty.channel.SimpleChannelHandler; 046import org.elasticsearch.common.netty.handler.ssl.SslHandler; 047import org.elasticsearch.common.network.NetworkService; 048import org.elasticsearch.common.settings.Settings; 049import org.elasticsearch.common.util.BigArrays; 050import org.elasticsearch.common.util.concurrent.ConcurrentCollections; 051import org.elasticsearch.threadpool.ThreadPool; 052import org.elasticsearch.transport.netty.NettyTransport; 053 054import de.saly.es.example.tssl.util.ConfigConstants; 055import de.saly.es.example.tssl.util.SecurityUtil; 056 057public class SSLNettyTransport extends SecureNettyTransport { 058 059 private static final int SECURITY_SSL_TRANSPORT_NODE_SESSION_CACHE_SIZE_DEFAULT = 1000; //number of sessions 060 private static final int SECURITY_SSL_TRANSPORT_NODE_SESSION_TIMEOUT_DEFAULT = 24 * 60 * 60; //24h in seconds 061 062 private final Map<String, SSLContext> contextCache = ConcurrentCollections.newConcurrentMap(); 063 064 @Inject 065 public SSLNettyTransport(final Settings settings, final ThreadPool threadPool, final NetworkService networkService, 066 final BigArrays bigArrays, final Version version) { 067 super(settings, threadPool, networkService, bigArrays, version); 068 } 069 070 @Override 071 public ChannelPipelineFactory configureClientChannelPipelineFactory() { 072 logger.info("Node client configured for SSL"); 073 return new SSLClientChannelPipelineFactory(this, this.settings); 074 } 075 076 @Override 077 public ChannelPipelineFactory configureServerChannelPipelineFactory(final String name, final Settings settings) { 078 logger.info("Node server configured for SSL"); 079 return new SSLServerChannelPipelineFactory(this, name, settings, this.settings); 080 } 081 082 protected class SSLServerChannelPipelineFactory extends SecureServerChannelPipelineFactory { 083 084 private final String keystoreType; 085 private final String keystoreFilePath; 086 private final String keystorePassword; 087 private final boolean needClientAuth; 088 089 private final String truststoreType; 090 private final String truststoreFilePath; 091 private final String truststorePassword; 092 093 private final int sslSessionCacheSize; 094 private final int sslSessionTimeout; 095 096 public SSLServerChannelPipelineFactory(final NettyTransport nettyTransport, final String name, final Settings sslsettings, 097 final Settings essettings) { 098 super(nettyTransport, name, sslsettings); 099 100 keystoreType = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_KEYSTORE_TYPE, 101 System.getProperty("javax.net.ssl.keyStoreType", "JKS")); 102 keystoreFilePath = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_KEYSTORE_FILEPATH, 103 System.getProperty("javax.net.ssl.keyStore", null)); 104 keystorePassword = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_KEYSTORE_PASSWORD, 105 System.getProperty("javax.net.ssl.keyStorePassword", "changeit")); 106 107 truststoreType = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_TYPE, 108 System.getProperty("javax.net.ssl.trustStoreType", "JKS")); 109 truststoreFilePath = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_FILEPATH, 110 System.getProperty("javax.net.ssl.trustStore", null)); 111 truststorePassword = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_PASSWORD, 112 System.getProperty("javax.net.ssl.trustStorePassword", "changeit")); 113 114 sslSessionCacheSize = essettings.getAsInt(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_TYPE, 115 SECURITY_SSL_TRANSPORT_NODE_SESSION_CACHE_SIZE_DEFAULT); 116 sslSessionTimeout = essettings.getAsInt(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_TYPE, 117 SECURITY_SSL_TRANSPORT_NODE_SESSION_TIMEOUT_DEFAULT); 118 119 needClientAuth = essettings.getAsBoolean(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_NEED_CLIENTAUTH, true); 120 121 } 122 123 @Override 124 public ChannelPipeline getPipeline() throws Exception { 125 final ChannelPipeline pipeline = super.getPipeline(); 126 127 //Setup serverside SSL 128 final SSLContext serverContext = SSLNettyTransport.this.createSSLContext(keystoreType, keystoreFilePath, keystorePassword, 129 truststoreType, truststoreFilePath, truststorePassword, sslSessionCacheSize, sslSessionTimeout); 130 131 final SSLEngine engine = serverContext.createSSLEngine(); 132 final SSLParameters sslParams = new SSLParameters(); 133 sslParams.setCipherSuites(SecurityUtil.ENABLED_SSL_CIPHERS); 134 sslParams.setProtocols(SecurityUtil.ENABLED_SSL_PROTOCOLS); 135 sslParams.setNeedClientAuth(needClientAuth); 136 engine.setSSLParameters(sslParams); 137 engine.setUseClientMode(false); 138 139 final SslHandler sslHandler = new SslHandler(engine); 140 sslHandler.setEnableRenegotiation(true); 141 pipeline.addFirst("ssl_server", sslHandler); 142 143 return pipeline; 144 } 145 146 } 147 148 protected static class ClientSslHandler extends SimpleChannelHandler { 149 private final SSLContext serverContext; 150 private final boolean hostnameVerificationEnabled; 151 private final boolean hostnameVerificationResovleHostName; 152 153 private ClientSslHandler(final SSLContext serverContext, final boolean hostnameVerificationEnabled, 154 final boolean hostnameVerificationResovleHostName) { 155 this.hostnameVerificationEnabled = hostnameVerificationEnabled; 156 this.hostnameVerificationResovleHostName = hostnameVerificationResovleHostName; 157 this.serverContext = serverContext; 158 } 159 160 @Override 161 public void connectRequested(final ChannelHandlerContext ctx, final ChannelStateEvent event) { 162 163 //Setup clientside SSL 164 165 SSLEngine engine = null; 166 final SSLParameters sslParams = new SSLParameters(); 167 sslParams.setCipherSuites(SecurityUtil.ENABLED_SSL_CIPHERS); 168 sslParams.setProtocols(SecurityUtil.ENABLED_SSL_PROTOCOLS); 169 170 if (hostnameVerificationEnabled) { 171 final InetSocketAddress inetSocketAddress = (InetSocketAddress) event.getValue(); 172 173 String hostname = null; 174 if (hostnameVerificationResovleHostName) { 175 hostname = inetSocketAddress.getHostName(); 176 } else { 177 hostname = inetSocketAddress.getHostString(); 178 } 179 180 engine = serverContext.createSSLEngine(hostname, inetSocketAddress.getPort()); 181 sslParams.setEndpointIdentificationAlgorithm("HTTPS"); 182 } else { 183 engine = serverContext.createSSLEngine(); 184 } 185 186 engine.setSSLParameters(sslParams); 187 engine.setUseClientMode(true); 188 189 final SslHandler sslHandler = new SslHandler(engine); 190 sslHandler.setEnableRenegotiation(true); 191 ctx.getPipeline().replace(this, "ssl_client", sslHandler); 192 193 ctx.sendDownstream(event); 194 } 195 } 196 197 protected class SSLClientChannelPipelineFactory extends SecureClientChannelPipelineFactory { 198 199 private final String keystoreType; 200 private final String keystoreFilePath; 201 private final String keystorePassword; 202 private final boolean hostnameVerificationEnabled; 203 private final boolean hostnameVerificationResovleHostName; 204 private final String truststoreType; 205 private final String truststoreFilePath; 206 private final String truststorePassword; 207 private final int sslSessionCacheSize; 208 private final int sslSessionTimeout; 209 210 public SSLClientChannelPipelineFactory(final NettyTransport nettyTransport, final Settings essettings) { 211 super(nettyTransport); 212 213 keystoreType = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_KEYSTORE_TYPE, 214 System.getProperty("javax.net.ssl.keyStoreType", "JKS")); 215 keystoreFilePath = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_KEYSTORE_FILEPATH, 216 System.getProperty("javax.net.ssl.keyStore", null)); 217 keystorePassword = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_KEYSTORE_PASSWORD, 218 System.getProperty("javax.net.ssl.keyStorePassword", "changeit")); 219 220 truststoreType = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_TYPE, 221 System.getProperty("javax.net.ssl.trustStoreType", "JKS")); 222 truststoreFilePath = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_FILEPATH, 223 System.getProperty("javax.net.ssl.trustStore", null)); 224 truststorePassword = essettings.get(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_PASSWORD, 225 System.getProperty("javax.net.ssl.trustStorePassword", "changeit")); 226 227 sslSessionCacheSize = essettings.getAsInt(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_TYPE, 228 SECURITY_SSL_TRANSPORT_NODE_SESSION_CACHE_SIZE_DEFAULT); 229 sslSessionTimeout = essettings.getAsInt(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_TYPE, 230 SECURITY_SSL_TRANSPORT_NODE_SESSION_TIMEOUT_DEFAULT); 231 232 hostnameVerificationEnabled = essettings.getAsBoolean( 233 ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_ENCFORCE_HOSTNAME_VERIFICATION, true); 234 hostnameVerificationResovleHostName = essettings.getAsBoolean( 235 ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_ENCFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, true); 236 } 237 238 @Override 239 public ChannelPipeline getPipeline() throws Exception { 240 final ChannelPipeline pipeline = super.getPipeline(); 241 242 final SSLContext clientContext = SSLNettyTransport.this.createSSLContext(keystoreType, keystoreFilePath, keystorePassword, 243 truststoreType, truststoreFilePath, truststorePassword, sslSessionCacheSize, sslSessionTimeout); 244 245 pipeline.addFirst("client_ssl_handler", new ClientSslHandler(clientContext, hostnameVerificationEnabled, 246 hostnameVerificationResovleHostName)); 247 248 return pipeline; 249 } 250 251 } 252 253 private SSLContext createSSLContext(final String keystoreType, final String keystoreFilePath, final String keystorePassword, 254 final String truststoreType, final String truststoreFilePath, final String truststorePassword, final int sslSessionCacheSize, 255 final int sslSessionTimeout) throws KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException, 256 FileNotFoundException, IOException, UnrecoverableKeyException { 257 258 if (StringUtils.isBlank(keystoreFilePath) || StringUtils.isBlank(truststoreFilePath)) { 259 logger.error(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_KEYSTORE_FILEPATH + " and " 260 + ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_FILEPATH + " must be set if transport ssl is reqested."); 261 throw new IOException(ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_KEYSTORE_FILEPATH + " and " 262 + ConfigConstants.SECURITY_SSL_TRANSPORT_NODE_TRUSTSTORE_FILEPATH + " must be set if transport ssl is reqested."); 263 } 264 265 final String contextKey = keystoreFilePath + truststoreFilePath; 266 267 if (contextCache.containsKey(contextKey)) { 268 return contextCache.get(contextKey); 269 } 270 271 //## Keystore ## 272 final KeyStore ks = KeyStore.getInstance(keystoreType); 273 ks.load(new FileInputStream(new File(keystoreFilePath)), keystorePassword.toCharArray()); 274 final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 275 kmf.init(ks, keystorePassword.toCharArray()); 276 277 //## Truststore ## 278 final KeyStore ts = KeyStore.getInstance(truststoreType); 279 ts.load(new FileInputStream(new File(truststoreFilePath)), truststorePassword.toCharArray()); 280 final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 281 tmf.init(ts); 282 283 //## SSLContext ## 284 final SSLContext sslContext = SSLContext.getInstance("TLS"); 285 sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); 286 287 sslContext.getServerSessionContext().setSessionCacheSize(sslSessionCacheSize); 288 sslContext.getServerSessionContext().setSessionTimeout(sslSessionTimeout); 289 290 contextCache.put(contextKey, sslContext); 291 return sslContext; 292 } 293}