001/* 002 * #%L 003 * Netarchivesuite - common 004 * %% 005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023package dk.netarkivet.common.distribute; 024 025import java.io.FileInputStream; 026import java.io.IOException; 027import java.net.URL; 028import java.net.URLConnection; 029import java.security.GeneralSecurityException; 030import java.security.KeyStore; 031import java.security.SecureRandom; 032 033import javax.net.ssl.HostnameVerifier; 034import javax.net.ssl.HttpsURLConnection; 035import javax.net.ssl.KeyManagerFactory; 036import javax.net.ssl.SSLContext; 037import javax.net.ssl.SSLSession; 038import javax.net.ssl.TrustManagerFactory; 039 040import org.apache.commons.io.IOUtils; 041import org.eclipse.jetty.server.HttpConfiguration; 042import org.eclipse.jetty.server.HttpConnectionFactory; 043import org.eclipse.jetty.server.SecureRequestCustomizer; 044import org.eclipse.jetty.server.Server; 045import org.eclipse.jetty.server.ServerConnector; 046import org.eclipse.jetty.server.SslConnectionFactory; 047import org.eclipse.jetty.util.ssl.SslContextFactory; 048 049import dk.netarkivet.common.exceptions.IOFailure; 050import dk.netarkivet.common.utils.Settings; 051 052/** 053 * This is a registry for HTTPS remote file, meant for serving registered files to remote hosts. It will use secure 054 * communication using a shared certificate. The embedded webserver handling remote files for HTTPSRemoteFile 055 * point-to-point communication. Optimised to use direct transfer on local machine. 056 */ 057public class HTTPSRemoteFileRegistry extends HTTPRemoteFileRegistry { 058 059 // Constants defining default X509 algorithm for security framework. 060 private static final String SUN_JCEKS_KEYSTORE_TYPE = "JCEKS"; 061 private static final String SUN_X509_CERTIFICATE_ALGORITHM = "SunX509"; 062 private static final String SSL_PROTOCOL = "SSL"; 063 private static final String SHA1_PRNG_RANDOM_ALGORITHM = "SHA1PRNG"; 064 /** Protocol used in this registry. */ 065 private static final String PROTOCOL = "https"; 066 067 /** A hostname verifier accepting any host. */ 068 private static final HostnameVerifier ACCEPTING_HOSTNAME_VERIFIER = new HostnameVerifier() { 069 public boolean verify(String string, SSLSession sslSession) { 070 return true; 071 } 072 }; 073 074 /** The path to the keystore containing the certificate used for SSL in HTTPS connections. */ 075 private static final String KEYSTORE_PATH = Settings.get(HTTPSRemoteFile.HTTPSREMOTEFILE_KEYSTORE_FILE); 076 /** The keystore password. */ 077 private static final String KEYSTORE_PASSWORD = Settings.get(HTTPSRemoteFile.HTTPSREMOTEFILE_KEYSTORE_PASSWORD); 078 /** The certificate password. */ 079 private static final String KEY_PASSWORD = Settings.get(HTTPSRemoteFile.HTTPSREMOTEFILE_KEY_PASSWORD); 080 081 /** 082 * An SSL context, used for creating SSL connections only accepting this certificate. 083 */ 084 private final SSLContext sslContext; 085 086 // FIXME I think this is what they call a constructor...?! 087 // This all initialises the ssl context to use the key in the keystore above. 088 private HTTPSRemoteFileRegistry() { 089 FileInputStream keyStoreInputStream = null; 090 try { 091 keyStoreInputStream = new FileInputStream(KEYSTORE_PATH); 092 KeyStore store = KeyStore.getInstance(SUN_JCEKS_KEYSTORE_TYPE); 093 store.load(keyStoreInputStream, KEYSTORE_PASSWORD.toCharArray()); 094 KeyManagerFactory kmf = KeyManagerFactory.getInstance(SUN_X509_CERTIFICATE_ALGORITHM); 095 kmf.init(store, KEY_PASSWORD.toCharArray()); 096 TrustManagerFactory tmf = TrustManagerFactory.getInstance(SUN_X509_CERTIFICATE_ALGORITHM); 097 tmf.init(store); 098 sslContext = SSLContext.getInstance(SSL_PROTOCOL); 099 sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), 100 SecureRandom.getInstance(SHA1_PRNG_RANDOM_ALGORITHM)); 101 } catch (GeneralSecurityException | IOException e) { 102 throw new IOFailure("Unable to create secure environment for keystore '" + KEYSTORE_PATH + "'", e); 103 } finally { 104 IOUtils.closeQuietly(keyStoreInputStream); 105 } 106 } 107 108 /** 109 * Get the unique instance. 110 * 111 * @return The unique instance. 112 */ 113 public static synchronized HTTPRemoteFileRegistry getInstance() { 114 synchronized (HTTPRemoteFile.class) { 115 if (instance == null) { 116 instance = new HTTPSRemoteFileRegistry(); 117 } 118 return instance; 119 } 120 } 121 122 /** 123 * Get the protocol used for this registry, that is 'https'. 124 * 125 * @return "https", the protocol. 126 */ 127 @Override 128 protected String getProtocol() { 129 return PROTOCOL; 130 } 131 132 /** 133 * Start the server, including a handler that responds with registered files, removes registered files on request, 134 * and gives 404 otherwise. Connection to this web host only possible with the shared certificate. 135 */ 136 @Override 137 protected void startServer() { 138 server = new Server(); 139 140 SslContextFactory sslContextFactory = new SslContextFactory(); 141 sslContextFactory.setKeyStorePath(KEYSTORE_PATH); 142 sslContextFactory.setKeyStorePassword(KEYSTORE_PASSWORD); 143 sslContextFactory.setKeyManagerPassword(KEY_PASSWORD); 144 sslContextFactory.setTrustStorePath(KEYSTORE_PATH); 145 sslContextFactory.setTrustStorePassword(KEYSTORE_PASSWORD); 146 sslContextFactory.setNeedClientAuth(true); 147 148 HttpConfiguration http_config = new HttpConfiguration(); 149 http_config.setSecureScheme("https"); 150 http_config.setSecurePort(port); 151 152 HttpConfiguration https_config = new HttpConfiguration(http_config); 153 https_config.addCustomizer(new SecureRequestCustomizer()); 154 155 ServerConnector sslConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, 156 "http/1.1"), new HttpConnectionFactory(https_config)); 157 sslConnector.setPort(port); 158 159 server.addConnector(sslConnector); 160 server.setHandler(new HTTPRemoteFileRegistryHandler()); 161 try { 162 server.start(); 163 } catch (Exception e) { 164 throw new IOFailure("Cannot start HTTPSRemoteFile registry", e); 165 } 166 } 167 168 /** 169 * Open a connection to an URL in this registry. Thus opens SSL connections using the certificate above. 170 * 171 * @param url The URL to open connection to. 172 * @return an open connection to the given url 173 * @throws IOException If unable to open connection to the URL 174 * @throws IOFailure If the connection is not a secure connection 175 */ 176 @Override 177 protected URLConnection openConnection(URL url) throws IOException { 178 URLConnection connection = url.openConnection(); 179 if (!(connection instanceof HttpsURLConnection)) { 180 throw new IOFailure("Not a secure URL to remote file: " + url); 181 } 182 HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; 183 httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory()); 184 httpsConnection.setHostnameVerifier(ACCEPTING_HOSTNAME_VERIFIER); 185 return httpsConnection; 186 } 187 188}