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.*;
32  import javax.security.auth.callback.Callback;
33  import javax.security.auth.callback.CallbackHandler;
34  import javax.security.auth.callback.PasswordCallback;
35  import javax.security.auth.callback.UnsupportedCallbackException;
36  import java.io.File;
37  import java.io.IOException;
38  import java.net.InetAddress;
39  import java.net.MalformedURLException;
40  import java.net.Socket;
41  import java.net.URL;
42  import java.security.*;
43  import java.security.cert.CertificateException;
44  import java.util.ArrayList;
45  
46  /**
47   * Manual Socket Factory.
48   * Let user choose to accept or reject certificate
49   */
50  public class DavGatewaySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
51      /**
52       * Register custom Socket Factory to let user accept or reject certificate
53       */
54      public static void register() {
55          String urlString = Settings.getProperty("davmail.url");
56          try {
57              URL url = new URL(urlString);
58              String protocol = url.getProtocol();
59              if ("https".equals(protocol)) {
60                  int port = url.getPort();
61                  if (port < 0) {
62                      port = HttpsURL.DEFAULT_PORT;
63                  }
64                  Protocol.registerProtocol(url.getProtocol(),
65                          new Protocol(protocol, (ProtocolSocketFactory) new DavGatewaySSLProtocolSocketFactory(), port));
66              }
67          } catch (MalformedURLException e) {
68              DavGatewayTray.error(new BundleMessage("LOG_INVALID_URL", urlString));
69          }
70      }
71  
72      private KeyStore.ProtectionParameter getProtectionParameter(String password) {
73          if (password != null && password.length() > 0) {
74              // password provided: create a PasswordProtection
75              return new KeyStore.PasswordProtection(password.toCharArray());
76          } else {
77              // request password at runtime through a callback
78              return new KeyStore.CallbackHandlerProtection(new CallbackHandler() {
79                  public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
80                      if (callbacks.length > 0 && callbacks[0] instanceof PasswordCallback) {
81                          PasswordPromptDialog passwordPromptDialog = new PasswordPromptDialog(((PasswordCallback) callbacks[0]).getPrompt());
82                          ((PasswordCallback) callbacks[0]).setPassword(passwordPromptDialog.getPassword());
83                      }
84                  }
85              });
86          }
87      }
88  
89      private SSLContext sslcontext;
90  
91      private SSLContext createSSLContext() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyManagementException, KeyStoreException {
92          // PKCS11 client certificate settings
93          String pkcs11Library = Settings.getProperty("davmail.ssl.pkcs11Library");
94  
95          String clientKeystoreType = Settings.getProperty("davmail.ssl.clientKeystoreType");
96          // set default keystore type
97          if (clientKeystoreType == null || clientKeystoreType.length() == 0) {
98              clientKeystoreType = "PKCS11";
99          }
100 
101         if (pkcs11Library != null && pkcs11Library.length() > 0 && "PKCS11".equals(clientKeystoreType)) {
102             StringBuilder pkcs11Buffer = new StringBuilder();
103             pkcs11Buffer.append("name=DavMail\n");
104             pkcs11Buffer.append("library=").append(pkcs11Library).append('\n');
105             String pkcs11Config = Settings.getProperty("davmail.ssl.pkcs11Config");
106             if (pkcs11Config != null && pkcs11Config.length() > 0) {
107                 pkcs11Buffer.append(pkcs11Config).append('\n');
108             }
109             SunPKCS11ProviderHandler.registerProvider(pkcs11Buffer.toString());
110         }
111         String algorithm = KeyManagerFactory.getDefaultAlgorithm();
112         if ("SunX509".equals(algorithm)) {
113             algorithm = "NewSunX509";
114         } else if ("IbmX509".equals(algorithm)) {
115             algorithm = "NewIbmX509";
116         }
117         KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
118 
119         ArrayList<KeyStore.Builder> keyStoreBuilders = new ArrayList<KeyStore.Builder>();
120         // PKCS11 (smartcard) keystore with password callback
121         KeyStore.Builder scBuilder = KeyStore.Builder.newInstance("PKCS11", null, getProtectionParameter(null));
122         keyStoreBuilders.add(scBuilder);
123 
124         String clientKeystoreFile = Settings.getProperty("davmail.ssl.clientKeystoreFile");
125         String clientKeystorePass = Settings.getProperty("davmail.ssl.clientKeystorePass");
126         if (clientKeystoreFile != null && clientKeystoreFile.length() > 0
127                 && ("PKCS12".equals(clientKeystoreType) || "JKS".equals(clientKeystoreType))) {
128             // PKCS12 file based keystore
129             KeyStore.Builder fsBuilder = KeyStore.Builder.newInstance(clientKeystoreType, null,
130                     new File(clientKeystoreFile), getProtectionParameter(clientKeystorePass));
131             keyStoreBuilders.add(fsBuilder);
132         }
133         // Enable native Windows SmartCard access through MSCAPI (no PKCS11 config required)
134         if ("MSCAPI".equals(clientKeystoreType)) {
135             try {
136                 Provider provider = (Provider) Class.forName("sun.security.mscapi.SunMSCAPI").newInstance();
137                 KeyStore keyStore = KeyStore.getInstance("Windows-MY", provider);
138                 keyStore.load(null, null);
139                 keyStoreBuilders.add(KeyStore.Builder.newInstance(keyStore, new KeyStore.PasswordProtection(null)));
140             } catch (Exception e) {
141                 // ignore
142             }
143         }
144 
145         ManagerFactoryParameters keyStoreBuilderParameters = new KeyStoreBuilderParameters(keyStoreBuilders);
146         keyManagerFactory.init(keyStoreBuilderParameters);
147 
148         // Get a list of key managers
149         KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
150 
151         // Walk through the key managers and replace all X509 Key Managers with
152         // a specialized wrapped DavMail X509 Key Manager
153         for (int i = 0; i < keyManagers.length; i++) {
154             KeyManager keyManager = keyManagers[i];
155             if (keyManager instanceof X509KeyManager) {
156                 keyManagers[i] = new DavMailX509KeyManager((X509KeyManager) keyManager);
157             }
158         }
159 
160         SSLContext context = SSLContext.getInstance("TLS");
161         context.init(keyManagers, new TrustManager[]{new DavGatewayX509TrustManager()}, null);
162         return context;
163     }
164 
165     private SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, InvalidAlgorithmParameterException {
166         if (this.sslcontext == null) {
167             this.sslcontext = createSSLContext();
168         }
169         return this.sslcontext;
170     }
171 
172     public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
173         try {
174             return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
175         } catch (NoSuchAlgorithmException e) {
176             throw new IOException(e + " " + e.getMessage());
177         } catch (KeyManagementException e) {
178             throw new IOException(e + " " + e.getMessage());
179         } catch (KeyStoreException e) {
180             throw new IOException(e + " " + e.getMessage());
181         } catch (InvalidAlgorithmParameterException e) {
182             throw new IOException(e + " " + e.getMessage());
183         }
184     }
185 
186     public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort, HttpConnectionParams params) throws IOException {
187         try {
188             return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
189         } catch (NoSuchAlgorithmException e) {
190             throw new IOException(e + " " + e.getMessage());
191         } catch (KeyManagementException e) {
192             throw new IOException(e + " " + e.getMessage());
193         } catch (KeyStoreException e) {
194             throw new IOException(e + " " + e.getMessage());
195         } catch (InvalidAlgorithmParameterException e) {
196             throw new IOException(e + " " + e.getMessage());
197         }
198     }
199 
200     public Socket createSocket(String host, int port) throws IOException {
201         try {
202             return getSSLContext().getSocketFactory().createSocket(host, port);
203         } catch (NoSuchAlgorithmException e) {
204             throw new IOException(e + " " + e.getMessage());
205         } catch (KeyManagementException e) {
206             throw new IOException(e + " " + e.getMessage());
207         } catch (KeyStoreException e) {
208             throw new IOException(e + " " + e.getMessage());
209         } catch (InvalidAlgorithmParameterException e) {
210             throw new IOException(e + " " + e.getMessage());
211         }
212     }
213 
214     public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
215         try {
216             return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
217         } catch (NoSuchAlgorithmException e) {
218             throw new IOException(e + " " + e.getMessage());
219         } catch (KeyManagementException e) {
220             throw new IOException(e + " " + e.getMessage());
221         } catch (KeyStoreException e) {
222             throw new IOException(e + " " + e.getMessage());
223         } catch (InvalidAlgorithmParameterException e) {
224             throw new IOException(e + " " + e.getMessage());
225         }
226     }
227 
228     /**
229      * All instances of SSLProtocolSocketFactory are the same.
230      */
231     @Override
232     public boolean equals(Object obj) {
233         return ((obj != null) && obj.getClass().equals(this.getClass()));
234     }
235 
236     /**
237      * All instances of SSLProtocolSocketFactory have the same hash code.
238      */
239     @Override
240     public int hashCode() {
241         return this.getClass().hashCode();
242     }
243 }