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; 039import org.mortbay.jetty.Server; 040import org.mortbay.jetty.security.SslSocketConnector; 041import org.apache.commons.io.IOUtils; 042 043import dk.netarkivet.common.exceptions.IOFailure; 044import dk.netarkivet.common.utils.Settings; 045 046/** 047 * This is a registry for HTTPS remote file, meant for serving registered files to remote hosts. It will use secure 048 * communication using a shared certificate. The embedded webserver handling remote files for HTTPSRemoteFile 049 * point-to-point communication. Optimised to use direct transfer on local machine. 050 */ 051public class HTTPSRemoteFileRegistry extends HTTPRemoteFileRegistry { 052 053 // Constants defining default X509 algorithm for security framework. 054 private static final String SUN_JCEKS_KEYSTORE_TYPE = "JCEKS"; 055 private static final String SUN_X509_CERTIFICATE_ALGORITHM = "SunX509"; 056 private static final String SSL_PROTOCOL = "SSL"; 057 private static final String SHA1_PRNG_RANDOM_ALGORITHM = "SHA1PRNG"; 058 /** Protocol used in this registry. */ 059 private static final String PROTOCOL = "https"; 060 061 /** A hostname verifier accepting any host. */ 062 private static final HostnameVerifier ACCEPTING_HOSTNAME_VERIFIER = new HostnameVerifier() { 063 public boolean verify(String string, SSLSession sslSession) { 064 return true; 065 } 066 }; 067 068 /** The path to the keystore containing the certificate used for SSL in HTTPS connections. */ 069 private static final String KEYSTORE_PATH = Settings.get(HTTPSRemoteFile.HTTPSREMOTEFILE_KEYSTORE_FILE); 070 /** The keystore password. */ 071 private static final String KEYSTORE_PASSWORD = Settings.get(HTTPSRemoteFile.HTTPSREMOTEFILE_KEYSTORE_PASSWORD); 072 /** The certificate password. */ 073 private static final String KEY_PASSWORD = Settings.get(HTTPSRemoteFile.HTTPSREMOTEFILE_KEY_PASSWORD); 074 075 /** 076 * An SSL context, used for creating SSL connections only accepting this certificate. 077 */ 078 private final SSLContext sslContext; 079 080 // This all initialises the ssl context to use the key in the keystore above. 081 private HTTPSRemoteFileRegistry() { 082 FileInputStream keyStoreInputStream = null; 083 try { 084 keyStoreInputStream = new FileInputStream(KEYSTORE_PATH); 085 KeyStore store = KeyStore.getInstance(SUN_JCEKS_KEYSTORE_TYPE); 086 store.load(keyStoreInputStream, KEYSTORE_PASSWORD.toCharArray()); 087 KeyManagerFactory kmf = KeyManagerFactory.getInstance(SUN_X509_CERTIFICATE_ALGORITHM); 088 kmf.init(store, KEY_PASSWORD.toCharArray()); 089 TrustManagerFactory tmf = TrustManagerFactory.getInstance(SUN_X509_CERTIFICATE_ALGORITHM); 090 tmf.init(store); 091 sslContext = SSLContext.getInstance(SSL_PROTOCOL); 092 sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), 093 SecureRandom.getInstance(SHA1_PRNG_RANDOM_ALGORITHM)); 094 } catch (GeneralSecurityException | IOException e) { 095 throw new IOFailure("Unable to create secure environment for keystore '" + KEYSTORE_PATH + "'", e); 096 } finally { 097 IOUtils.closeQuietly(keyStoreInputStream); 098 } 099 } 100 101 /** 102 * Get the unique instance. 103 * 104 * @return The unique instance. 105 */ 106 public static synchronized HTTPRemoteFileRegistry getInstance() { 107 synchronized (HTTPRemoteFile.class) { 108 if (instance == null) { 109 instance = new HTTPSRemoteFileRegistry(); 110 } 111 return instance; 112 } 113 } 114 115 /** 116 * Get the protocol used for this registry, that is 'https'. 117 * 118 * @return "https", the protocol. 119 */ 120 @Override 121 protected String getProtocol() { 122 return PROTOCOL; 123 } 124 125 /** 126 * Start the server, including a handler that responds with registered files, removes registered files on request, 127 * and gives 404 otherwise. Connection to this web host only possible with the shared certificate. 128 */ 129 protected void startServer() { 130 server = new Server(); 131 //This sets up a secure connector 132 SslSocketConnector connector = new SslSocketConnector(); 133 connector.setKeystore(KEYSTORE_PATH); 134 connector.setPassword(KEYSTORE_PASSWORD); 135 connector.setKeyPassword(KEY_PASSWORD); 136 connector.setTruststore(KEYSTORE_PATH); 137 connector.setTrustPassword(KEYSTORE_PASSWORD); 138 connector.setNeedClientAuth(true); 139 connector.setPort(port); 140 //This initialises the server. 141 server.addConnector(connector); 142 server.addHandler(new HTTPRemoteFileRegistryHandler()); 143 try { 144 server.start(); 145 } catch (Exception e) { 146 throw new IOFailure("Cannot start HTTPSRemoteFile registry", e); 147 } 148 } 149 150 /** 151 * Open a connection to an URL in this registry. Thus opens SSL connections using the certificate above. 152 * 153 * @param url The URL to open connection to. 154 * @return an open connection to the given url 155 * @throws IOException If unable to open connection to the URL 156 * @throws IOFailure If the connection is not a secure connection 157 */ 158 @Override 159 protected URLConnection openConnection(URL url) throws IOException { 160 URLConnection connection = url.openConnection(); 161 if (!(connection instanceof HttpsURLConnection)) { 162 throw new IOFailure("Not a secure URL to remote file: " + url); 163 } 164 HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; 165 httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory()); 166 httpsConnection.setHostnameVerifier(ACCEPTING_HOSTNAME_VERIFIER); 167 return httpsConnection; 168 } 169 170}