/*************************************************************************
* Copyright 2009-2015 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.auth.euare.identity.ws;
import static java.lang.System.getProperty;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import com.eucalyptus.auth.euare.common.identity.Identity;
import com.eucalyptus.auth.euare.identity.region.RegionConfigurationManager;
import com.eucalyptus.auth.euare.identity.region.RegionConfigurations;
import com.eucalyptus.component.ComponentIds;
import com.eucalyptus.component.annotation.ComponentPart;
import com.eucalyptus.crypto.Crypto;
import com.eucalyptus.crypto.util.DelegatingX509ExtendedTrustManager;
import com.eucalyptus.crypto.util.SslUtils;
import com.eucalyptus.crypto.util.X509ExtendedTrustManagerSupport;
import com.eucalyptus.http.MappingHttpRequest;
import com.eucalyptus.util.CollectionUtils;
import com.eucalyptus.util.IO;
import com.eucalyptus.util.Pair;
import com.eucalyptus.ws.Handlers;
import com.eucalyptus.ws.IoHandlers;
import com.eucalyptus.ws.StackConfiguration;
import com.eucalyptus.ws.client.MonitoredSocketChannelInitializer;
import com.eucalyptus.ws.handlers.IoMessageWrapperHandler;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.socket.SocketChannel;
/**
*
*/
@ComponentPart( Identity.class )
public class IdentityClientChannelInitializer extends MonitoredSocketChannelInitializer {
private static final String PROTOCOL = "TLS";
private static final String PROP_SSL_TRUSTSTORE_PASSWORD = "com.eucalyptus.auth.euare.identity.regionSslTrustStorePassword";
private static final String PROP_SSL_TRUSTSTORE_TYPE = "com.eucalyptus.auth.euare.identity.regionSslTrustStoreType";
private static final String PROP_SSL_TRUSTSTORE_PATH = "com.eucalyptus.auth.euare.identity.regionSslTrustStorePath";
private static final String DEFAULT_TRUSTSTORE_PASSWORD = "changeit";
private static final String DEFAULT_TRUSTSTORE_PATH = "lib/security/cacerts";
private static final AtomicReference<Pair<Long,KeyStore>> sslTrustStore = new AtomicReference<>( );
private static final Supplier<Boolean> useDefaultCAs = new Supplier<Boolean>() {
@Override
public Boolean get() {
return RegionConfigurations.isUseDefaultCAs();
}
};
private static final Supplier<SSLContext> contextSupplier = Suppliers.memoizeWithExpiration( new Supplier<SSLContext>( ){
@Override
public SSLContext get() {
final String trustStorePath = getProperty( PROP_SSL_TRUSTSTORE_PATH, DEFAULT_TRUSTSTORE_PATH );
try {
final List<TrustManager> trustManagers = Lists.newArrayList();
trustManagers.add( regionTrustManager );
final File trustStore = new File( System.getProperty( "java.home" ), trustStorePath );
if ( trustStore.isFile( ) ) {
trustManagers.add( buildDelegatingTrustManager(
trustManagersForStore( getTrustStore( trustStore ) ),
useDefaultCAs ) );
}
final SSLContext clientContext = SSLContext.getInstance( PROTOCOL );
clientContext.init(
null,
new TrustManager[]{ buildDelegatingTrustManager( trustManagers, Suppliers.ofInstance( true ) ) },
Crypto.getSecureRandomSupplier( ).get( )
);
return clientContext;
} catch ( Exception e ) {
throw new Error( "Failed to initialize the client-side SSLContext", e );
}
}
}, 15, TimeUnit.MINUTES );
private static final X509TrustManager regionTrustManager = new RegionTrustManager( );
private static TrustManager buildDelegatingTrustManager(
final List<TrustManager> trustManagers,
final Supplier<Boolean> enable
) {
return new DelegatingX509ExtendedTrustManager(
Iterables.transform(
Iterables.filter(
trustManagers,
Predicates.instanceOf( javax.net.ssl.X509ExtendedTrustManager.class ) ),
CollectionUtils.cast( javax.net.ssl.X509ExtendedTrustManager.class ) ),
enable
);
}
@Override
protected void initChannel( final SocketChannel socketChannel ) throws Exception {
super.initChannel( socketChannel );
final ChannelPipeline pipeline = socketChannel.pipeline( );
pipeline.addLast( "decoder", IoHandlers.httpResponseDecoder( ) );
pipeline.addLast( "aggregator", IoHandlers.newHttpChunkAggregator() );
pipeline.addLast( "encoder", IoHandlers.httpRequestEncoder() );
pipeline.addLast( "wrapper", IoHandlers.ioMessageWrappingHandler( ) );
pipeline.addLast( "serializer", IoHandlers.soapMarshalling() );
pipeline.addLast( "wssec", IoHandlers.internalWsSecHandler() );
pipeline.addLast( "addressing", IoHandlers.addressingHandler() );
pipeline.addLast( "soap", IoHandlers.soapHandler() );
pipeline.addLast( "binding", IoHandlers.bindingHandler( "www_eucalyptus_com_ns_identity_2016_10_01" ) );
pipeline.addLast( "ssl-detection-handler", new IoHandlers.ClientSslHandler( "ssl-handler" ) {
@Override
protected SSLEngine createSSLEngine( final String peerHost, final int peerPort ) {
return getSSLEngine( peerHost, peerPort );
}
} );
pipeline.addLast( "remote", new RemotePathHandler( ) );
}
public static final class RemotePathHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write( final io.netty.channel.ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise ) throws Exception {
if ( msg instanceof MappingHttpRequest ) {
final MappingHttpRequest httpMessage = (MappingHttpRequest) msg;
httpMessage.setServicePath( ComponentIds.lookup( Identity.class ).getServicePath() );
String uri = URI.create( httpMessage.getUri( ) ).resolve( httpMessage.getServicePath( ) ).toString( );
if ( RegionConfigurations.isUseSsl() ) {
uri = uri.replace( "http://", "https://" );
} else {
uri = uri.replace( "https://", "http://" );
}
httpMessage.setUri( uri );
}
super.write( ctx, msg, promise );
}
}
public static SSLContext getSSLContext( ) {
return contextSupplier.get( );
}
public static SSLEngine getSSLEngine( final String peerHost, final int peerPort ) {
try {
final SSLParameters sslParams = new SSLParameters( );
if ( RegionConfigurations.isVerifyHostnames( ) ) {
sslParams.setEndpointIdentificationAlgorithm( "HTTPS" );
}
final SSLContext clientContext = getSSLContext( );
final SSLEngine engine = clientContext.createSSLEngine( peerHost, peerPort );
engine.setSSLParameters( sslParams );
engine.setUseClientMode( true );
engine.setEnabledProtocols( SslUtils.getEnabledProtocols(
RegionConfigurations.getSslProtocols( ),
engine.getSupportedProtocols( ) ) );
engine.setEnabledCipherSuites( SslUtils.getEnabledCipherSuites(
RegionConfigurations.getSslCiphers( ),
engine.getSupportedCipherSuites() ) );
return engine;
} catch ( Exception e ) {
throw new Error( "Failed to initialize the client-side SSLContext", e );
}
}
public static final class RegionTrustManager extends X509ExtendedTrustManagerSupport {
private final RegionConfigurationManager regionConfigurationManager = new RegionConfigurationManager( );
@Override
public void checkServerTrusted( final X509Certificate[] x509Certificates, final String s, final SSLEngine sslEngine ) throws CertificateException {
final X509Certificate serverCertificate = x509Certificates[0];
final String hostname = sslEngine.getHandshakeSession( ).getPeerHost( );
final SSLParameters sslParameters = sslEngine.getSSLParameters( );
if ( sslParameters != null && "HTTPS".equals( sslParameters.getEndpointIdentificationAlgorithm( ) ) ) try {
new BrowserCompatHostnameVerifier( ).verify( hostname, serverCertificate );
} catch ( SSLException e ) {
throw new CertificateException( "Server cert not valid for host" );
}
if ( !regionConfigurationManager.isRegionSSLCertificate( hostname, serverCertificate ) ) {
throw new CertificateException( "Server cert not trusted" );
}
}
}
private static KeyStore getTrustStore( final File trustStore ) throws GeneralSecurityException, IOException {
final Pair<Long,KeyStore> currentTrustStore = sslTrustStore.get( );
InputStream trustStoreIn = null;
if ( currentTrustStore != null && currentTrustStore.getLeft( ) == trustStore.lastModified( ) ) {
return currentTrustStore.getRight( );
} else try {
final String trustStoreType = getProperty( PROP_SSL_TRUSTSTORE_TYPE, KeyStore.getDefaultType( ) );
final String trustStorePassword = getProperty( PROP_SSL_TRUSTSTORE_PASSWORD, DEFAULT_TRUSTSTORE_PASSWORD );
final KeyStore userTrustStore = KeyStore.getInstance( trustStoreType );
userTrustStore.load( trustStoreIn = new FileInputStream( trustStore ), trustStorePassword.toCharArray() );
sslTrustStore.set( Pair.pair( trustStore.lastModified( ), userTrustStore ) );
return userTrustStore;
} finally {
IO.close( trustStoreIn );
}
}
private static List<TrustManager> trustManagersForStore( final KeyStore trustStore ) throws GeneralSecurityException {
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( "PKIX", "SunJSSE" );
trustManagerFactory.init( trustStore );
return Arrays.asList( trustManagerFactory.getTrustManagers() );
}
}