package com.redhat.rcm.version.util.http; import static org.apache.commons.codec.binary.Base64.decodeBase64; import static org.apache.commons.io.IOUtils.closeQuietly; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.security.KeyFactory; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.redhat.rcm.version.VManException; public final class SSLUtils { private static final Logger LOGGER = LoggerFactory.getLogger( SSLUtils.class ); private static final String CLASSPATH_PREFIX = "classpath:"; private SSLUtils() { } public static void initSSLContext( final String basedir ) throws VManException { KeyManagerFactory kmf; try { kmf = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm() ); kmf.init( null, null ); } catch ( final NoSuchAlgorithmException e ) { throw new VManException( "Cannot initialize KeyManagerFactory: %s", e, e.getMessage() ); } catch ( final UnrecoverableKeyException e ) { throw new VManException( "Cannot initialize KeyManagerFactory: %s", e, e.getMessage() ); } catch ( final KeyStoreException e ) { throw new VManException( "Cannot initialize KeyManagerFactory: %s", e, e.getMessage() ); } KeyManager km = null; for ( final KeyManager keyManager : kmf.getKeyManagers() ) { if ( keyManager instanceof X509KeyManager ) { km = keyManager; } } final TrustManager tm = loadTrustManager( basedir ); SSLContext ctx; try { ctx = SSLContext.getInstance( "SSL" ); } catch ( final NoSuchAlgorithmException e ) { throw new VManException( "Failed to retrieve SSLContext: %s", e, e.getMessage() ); } try { ctx.init( new KeyManager[] { km }, new TrustManager[] { tm }, null ); } catch ( final KeyManagementException e ) { throw new VManException( "Failed to initialize SSLContext with new PEM-based TrustStore: %s", e, e.getMessage() ); } SSLContext.setDefault( ctx ); } private static TrustManager loadTrustManager( final String basedir ) throws VManException { // KeyStore ks; // try // { // ks = KeyStore.getInstance( KeyStore.getDefaultType() ); // ks.load( null ); // } // catch ( final KeyStoreException e ) // { // throw new VManException( "Failed to load trust-store KeyStore instance: %s", e, e.getMessage() ); // } // catch ( final NoSuchAlgorithmException e ) // { // throw new VManException( "Failed to load trust-store KeyStore instance: %s", e, e.getMessage() ); // } // catch ( final CertificateException e ) // { // throw new VManException( "Failed to load trust-store KeyStore instance: %s", e, e.getMessage() ); // } // catch ( final IOException e ) // { // throw new VManException( "Failed to load trust-store KeyStore instance: %s", e, e.getMessage() ); // } // // if ( basedir.startsWith( CLASSPATH_PREFIX ) ) // { // final String cpDir = basedir.substring( CLASSPATH_PREFIX.length() ); // loadFromClasspath( cpDir, ks ); // } // else // { // final File dir = new File( basedir ); // if ( dir.exists() && dir.isDirectory() ) // { // final String[] fnames = dir.list(); // if ( fnames != null ) // { // for ( final String fname : fnames ) // { // final File f = new File( dir, fname ); // loadFromFile( f.getPath(), ks ); // } // } // } // } // InputUtils.setTrustKeyStore( ks ); TrustManagerFactory dtmf; try { dtmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() ); dtmf.init( (KeyStore) null ); } catch ( final NoSuchAlgorithmException e ) { throw new VManException( "Failed to initialize default trust-store: %s", e, e.getMessage() ); } catch ( final KeyStoreException e ) { throw new VManException( "Failed to initialize default trust-store: %s", e, e.getMessage() ); } X509TrustManager dtm = null; for ( final TrustManager ctm : dtmf.getTrustManagers() ) { if ( ctm instanceof X509TrustManager ) { dtm = (X509TrustManager) ctm; break; } } // try // { // if ( ks.size() < 1 ) // { return dtm; // } // } // catch ( final KeyStoreException e ) // { // } // // TrustManagerFactory tmf; // try // { // tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() ); // tmf.init( ks ); // } // catch ( final NoSuchAlgorithmException e ) // { // throw new VManException( "Failed to initialize trust-store from .pem files: %s", e, e.getMessage() ); // } // catch ( final KeyStoreException e ) // { // throw new VManException( "Failed to initialize trust-store from .pem files: %s", e, e.getMessage() ); // } // // X509TrustManager tm = null; // for ( final TrustManager ctm : tmf.getTrustManagers() ) // { // if ( ctm instanceof X509TrustManager ) // { // tm = (X509TrustManager) ctm; // break; // } // } // // return new MultiTrustManager( tm, dtm ); } private static void loadFromClasspath( final String basepath, final KeyStore ks ) throws VManException { Enumeration<URL> resources; try { resources = Thread.currentThread() .getContextClassLoader() .getResources( basepath ); } catch ( final IOException e ) { throw new VManException( "Failed to scan classpath for certificate base path: %s. Reason: %s", e, basepath, e.getMessage() ); } final List<URL> urls = Collections.list( resources ); for ( final URL url : urls ) { if ( "jar".equals( url.getProtocol() ) ) { loadFromJar( url, basepath, ks ); } else { loadFromFile( url.getPath(), ks ); } } } private static void loadFromFile( final String path, final KeyStore ks ) throws VManException { final File f = new File( path ); if ( f.exists() && f.isFile() ) { LOGGER.info( "Loading SSL server PEM from file: " + f ); InputStream is = null; try { is = new FileInputStream( f ); readCerts( is, f.getName(), ks ); } catch ( final CertificateException e ) { throw new VManException( "Failed to read classpath certificate file: %s. Reason: %s", e, f, e.getMessage() ); } catch ( final KeyStoreException e ) { throw new VManException( "Failed to add certificate from classpath file: %s. Reason: %s", e, f, e.getMessage() ); } catch ( final NoSuchAlgorithmException e ) { throw new VManException( "Failed to read classpath certificate file: %s. Reason: %s", e, f, e.getMessage() ); } catch ( final IOException e ) { throw new VManException( "Failed to read classpath certificate file: %s. Reason: %s", e, f, e.getMessage() ); } finally { if ( is != null ) { try { is.close(); } catch ( final IOException e ) { } } } } } private static void loadFromJar( final URL url, final String basepath, final KeyStore ks ) throws VManException { String jar = url.getPath(); final int idx = jar.indexOf( "!" ); if ( idx > -1 ) { jar = jar.substring( 0, idx ); } if ( jar.startsWith( "file:" ) ) { jar = jar.substring( 5 ); } try { final JarFile jf = new JarFile( jar ); final List<JarEntry> entries = Collections.list( jf.entries() ); for ( final JarEntry entry : entries ) { final String name = entry.getName(); if ( name.startsWith( basepath ) ) { LOGGER.info( "Loading SSL server PEM from: " + name + " in jar: " + jar ); final InputStream is = jf.getInputStream( entry ); try { readCerts( is, new File( name ).getName(), ks ); } catch ( final CertificateException e ) { throw new VManException( "Failed to read certificates from classpath jar entry: %s!%s. Reason: %s", e, jar, name, e.getMessage() ); } catch ( final KeyStoreException e ) { throw new VManException( "Failed to read certificates from classpath jar entry: %s!%s. Reason: %s", e, jar, name, e.getMessage() ); } catch ( final NoSuchAlgorithmException e ) { throw new VManException( "Failed to read certificates from classpath jar entry: %s!%s. Reason: %s", e, jar, name, e.getMessage() ); } finally { if ( is != null ) { try { is.close(); } catch ( final IOException eInner ) { } } } } } } catch ( final IOException e ) { throw new VManException( "Failed to open classpath jar: %s. Reason: %s", e, jar, e.getMessage() ); } } public static void readKeyAndCert( final InputStream is, final String keyPass, final KeyStore ks ) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, InvalidKeySpecException { final CertificateFactory certFactory = CertificateFactory.getInstance( "X.509" ); final KeyFactory keyFactory = KeyFactory.getInstance( "RSA" ); final List<String> lines = readLines( is ); String currentHeader = null; final StringBuilder current = new StringBuilder(); final Map<String, String> entries = new LinkedHashMap<String, String>(); for ( final String line : lines ) { if ( line == null ) { continue; } if ( line.startsWith( "-----BEGIN" ) ) { currentHeader = line.trim(); current.setLength( 0 ); } else if ( line.startsWith( "-----END" ) ) { entries.put( currentHeader, current.toString() ); } else { current.append( line.trim() ); } } final List<Certificate> certs = new ArrayList<Certificate>(); for ( int pass = 0; pass < 2; pass++ ) { for ( final Map.Entry<String, String> entry : entries.entrySet() ) { final String header = entry.getKey(); final byte[] data = decodeBase64( entry.getValue() ); if ( pass > 0 && header.contains( "BEGIN PRIVATE KEY" ) ) { final KeySpec spec = new PKCS8EncodedKeySpec( data ); final PrivateKey key = keyFactory.generatePrivate( spec ); ks.setKeyEntry( "key", key, keyPass.toCharArray(), certs.toArray( new Certificate[] {} ) ); } else if ( pass < 1 && header.contains( "BEGIN CERTIFICATE" ) ) { final Certificate c = certFactory.generateCertificate( new ByteArrayInputStream( data ) ); ks.setCertificateEntry( "certificate", c ); certs.add( c ); } } } } public static void readCerts( final InputStream is, final String aliasPrefix, final KeyStore ks ) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException { final CertificateFactory certFactory = CertificateFactory.getInstance( "X.509" ); final List<String> lines = readLines( is ); final StringBuilder current = new StringBuilder(); final List<String> entries = new ArrayList<String>(); for ( final String line : lines ) { if ( line == null ) { continue; } if ( line.startsWith( "-----BEGIN" ) ) { current.setLength( 0 ); } else if ( line.startsWith( "-----END" ) ) { entries.add( current.toString() ); } else { current.append( line.trim() ); } } int i = 0; for ( final String entry : entries ) { final byte[] data = decodeBase64( entry ); final Certificate c = certFactory.generateCertificate( new ByteArrayInputStream( data ) ); ks.setCertificateEntry( aliasPrefix + i, c ); i++; } } private static List<String> readLines( final InputStream is ) throws IOException { final List<String> lines = new ArrayList<String>(); BufferedReader reader = null; try { reader = new BufferedReader( new InputStreamReader( is ) ); String line = null; while ( ( line = reader.readLine() ) != null ) { lines.add( line.trim() ); } } finally { closeQuietly( reader ); } return lines; } }