View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2009  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  package davmail.http;
20  
21  import davmail.BundleMessage;
22  import davmail.Settings;
23  import davmail.ui.PasswordPromptDialog;
24  import davmail.ui.tray.DavGatewayTray;
25  import org.apache.commons.httpclient.HttpsURL;
26  import org.apache.commons.httpclient.params.HttpConnectionParams;
27  import org.apache.commons.httpclient.protocol.Protocol;
28  import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
29  import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
30  
31  import javax.net.ssl.KeyManager;
32  import javax.net.ssl.KeyManagerFactory;
33  import javax.net.ssl.KeyStoreBuilderParameters;
34  import javax.net.ssl.ManagerFactoryParameters;
35  import javax.net.ssl.SSLContext;
36  import javax.net.ssl.TrustManager;
37  import javax.net.ssl.X509KeyManager;
38  import javax.security.auth.callback.PasswordCallback;
39  import java.awt.*;
40  import java.io.BufferedReader;
41  import java.io.File;
42  import java.io.IOException;
43  import java.io.InputStreamReader;
44  import java.net.InetAddress;
45  import java.net.MalformedURLException;
46  import java.net.Socket;
47  import java.net.URL;
48  import java.security.InvalidAlgorithmParameterException;
49  import java.security.KeyManagementException;
50  import java.security.KeyStore;
51  import java.security.KeyStoreException;
52  import java.security.NoSuchAlgorithmException;
53  import java.security.Provider;
54  import java.util.ArrayList;
55  
56  /**
57   * Manual Socket Factory.
58   * Let user choose to accept or reject certificate
59   * Used by commons httpclient 3
60   */
61  public class DavGatewaySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
62      /**
63       * Register custom Socket Factory to let user accept or reject certificate
64       */
65      public static void register() {
66          String urlString = Settings.getProperty("davmail.url");
67          try {
68              URL url = new URL(urlString);
69              String protocol = url.getProtocol();
70              if ("https".equals(protocol)) {
71                  int port = url.getPort();
72                  if (port < 0) {
73                      port = HttpsURL.DEFAULT_PORT;
74                  }
75                  Protocol.registerProtocol(url.getProtocol(),
76                          new Protocol(protocol, (ProtocolSocketFactory) new DavGatewaySSLProtocolSocketFactory(), port));
77              }
78          } catch (MalformedURLException e) {
79              DavGatewayTray.error(new BundleMessage("LOG_INVALID_URL", urlString));
80          }
81      }
82  
83      private KeyStore.ProtectionParameter getProtectionParameter(String password) {
84          if (password != null && password.length() > 0) {
85              // password provided: create a PasswordProtection
86              return new KeyStore.PasswordProtection(password.toCharArray());
87          } else {
88              // request password at runtime through a callback
89              return new KeyStore.CallbackHandlerProtection(callbacks -> {
90                  if (callbacks.length > 0 && callbacks[0] instanceof PasswordCallback) {
91                      if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
92                          // headless or server mode
93                          System.out.print(((PasswordCallback) callbacks[0]).getPrompt() + ": ");
94                          String password1 = new BufferedReader(new InputStreamReader(System.in)).readLine();
95                          ((PasswordCallback) callbacks[0]).setPassword(password1.toCharArray());
96                      } else {
97                          PasswordPromptDialog passwordPromptDialog = new PasswordPromptDialog(((PasswordCallback) callbacks[0]).getPrompt());
98                          ((PasswordCallback) callbacks[0]).setPassword(passwordPromptDialog.getPassword());
99                      }
100                 }
101             });
102         }
103     }
104 
105     private SSLContext sslcontext;
106 
107     public String[] getDefaultCipherSuites() {
108         try {
109             return getSSLContext().getSocketFactory().getDefaultCipherSuites();
110         } catch (Exception e) {
111             // ignore
112         }
113         return new String[]{};
114     }
115 
116     public String[] getSupportedCipherSuites() {
117         try {
118             return getSSLContext().getSocketFactory().getSupportedCipherSuites();
119         } catch (Exception e) {
120             // ignore
121         }
122         return new String[]{};
123     }
124 
125     private SSLContext createSSLContext() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyManagementException, KeyStoreException {
126         // PKCS11 client certificate settings
127         String pkcs11Library = Settings.getProperty("davmail.ssl.pkcs11Library");
128 
129         String clientKeystoreType = Settings.getProperty("davmail.ssl.clientKeystoreType");
130         // set default keystore type
131         if (clientKeystoreType == null || clientKeystoreType.length() == 0) {
132             clientKeystoreType = "PKCS11";
133         }
134 
135         if (pkcs11Library != null && pkcs11Library.length() > 0 && "PKCS11".equals(clientKeystoreType)) {
136             StringBuilder pkcs11Buffer = new StringBuilder();
137             pkcs11Buffer.append("name=DavMail\n");
138             pkcs11Buffer.append("library=").append(pkcs11Library).append('\n');
139             String pkcs11Config = Settings.getProperty("davmail.ssl.pkcs11Config");
140             if (pkcs11Config != null && pkcs11Config.length() > 0) {
141                 pkcs11Buffer.append(pkcs11Config).append('\n');
142             }
143             SunPKCS11ProviderHandler.registerProvider(pkcs11Buffer.toString());
144         }
145         String algorithm = KeyManagerFactory.getDefaultAlgorithm();
146         if ("SunX509".equals(algorithm)) {
147             algorithm = "NewSunX509";
148         } else if ("IbmX509".equals(algorithm)) {
149             algorithm = "NewIbmX509";
150         }
151         KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
152 
153         ArrayList<KeyStore.Builder> keyStoreBuilders = new ArrayList<>();
154         // PKCS11 (smartcard) keystore with password callback
155         KeyStore.Builder scBuilder = KeyStore.Builder.newInstance("PKCS11", null, getProtectionParameter(null));
156         keyStoreBuilders.add(scBuilder);
157 
158         String clientKeystoreFile = Settings.getProperty("davmail.ssl.clientKeystoreFile");
159         String clientKeystorePass = Settings.getProperty("davmail.ssl.clientKeystorePass");
160         if (clientKeystoreFile != null && clientKeystoreFile.length() > 0
161                 && ("PKCS12".equals(clientKeystoreType) || "JKS".equals(clientKeystoreType))) {
162             // PKCS12 file based keystore
163             KeyStore.Builder fsBuilder = KeyStore.Builder.newInstance(clientKeystoreType, null,
164                     new File(clientKeystoreFile), getProtectionParameter(clientKeystorePass));
165             keyStoreBuilders.add(fsBuilder);
166         }
167         // Enable native Windows SmartCard access through MSCAPI (no PKCS11 config required)
168         if ("MSCAPI".equals(clientKeystoreType)) {
169             try {
170                 Provider provider = (Provider) Class.forName("sun.security.mscapi.SunMSCAPI").newInstance();
171                 KeyStore keyStore = KeyStore.getInstance("Windows-MY", provider);
172                 keyStore.load(null, null);
173                 keyStoreBuilders.add(KeyStore.Builder.newInstance(keyStore, new KeyStore.PasswordProtection(null)));
174             } catch (Exception e) {
175                 // ignore
176             }
177         }
178 
179         ManagerFactoryParameters keyStoreBuilderParameters = new KeyStoreBuilderParameters(keyStoreBuilders);
180         keyManagerFactory.init(keyStoreBuilderParameters);
181 
182         // Get a list of key managers
183         KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
184 
185         // Walk through the key managers and replace all X509 Key Managers with
186         // a specialized wrapped DavMail X509 Key Manager
187         for (int i = 0; i < keyManagers.length; i++) {
188             KeyManager keyManager = keyManagers[i];
189             if (keyManager instanceof X509KeyManager) {
190                 keyManagers[i] = new DavMailX509KeyManager((X509KeyManager) keyManager);
191             }
192         }
193 
194         SSLContext context = SSLContext.getInstance("TLS");
195         context.init(keyManagers, new TrustManager[]{new DavGatewayX509TrustManager()}, null);
196         return context;
197     }
198 
199     private SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, InvalidAlgorithmParameterException {
200         if (this.sslcontext == null) {
201             this.sslcontext = createSSLContext();
202         }
203         return this.sslcontext;
204     }
205 
206     public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
207         try {
208             return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
209         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
210             throw new IOException(e + " " + e.getMessage());
211         }
212     }
213 
214     public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort, HttpConnectionParams params) throws IOException {
215         try {
216             return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
217         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
218             throw new IOException(e + " " + e.getMessage());
219         }
220     }
221 
222     public Socket createSocket(String host, int port) throws IOException {
223         try {
224             return getSSLContext().getSocketFactory().createSocket(host, port);
225         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
226             throw new IOException(e + " " + e.getMessage());
227         }
228     }
229 
230     public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
231         try {
232             return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
233         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
234             throw new IOException(e + " " + e.getMessage());
235         }
236     }
237 
238     public Socket createSocket(InetAddress host, int port) throws IOException {
239         try {
240             return getSSLContext().getSocketFactory().createSocket(host, port);
241         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
242             throw new IOException(e + " " + e.getMessage());
243         }
244     }
245 
246     public Socket createSocket(InetAddress host, int port, InetAddress clientHost, int clientPort) throws IOException {
247         try {
248             return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
249         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
250             throw new IOException(e + " " + e.getMessage());
251         }
252     }
253 
254     /**
255      * All instances of SSLProtocolSocketFactory are the same.
256      */
257     @Override
258     public boolean equals(Object obj) {
259         return ((obj != null) && obj.getClass().equals(this.getClass()));
260     }
261 
262     /**
263      * All instances of SSLProtocolSocketFactory have the same hash code.
264      */
265     @Override
266     public int hashCode() {
267         return this.getClass().hashCode();
268     }
269 }