View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2010  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  
20  package davmail.http;
21  
22  import davmail.Settings;
23  import davmail.ui.PasswordPromptDialog;
24  import org.apache.log4j.Logger;
25  
26  import javax.net.ssl.KeyManager;
27  import javax.net.ssl.KeyManagerFactory;
28  import javax.net.ssl.KeyStoreBuilderParameters;
29  import javax.net.ssl.ManagerFactoryParameters;
30  import javax.net.ssl.SSLContext;
31  import javax.net.ssl.SSLSocketFactory;
32  import javax.net.ssl.TrustManager;
33  import javax.net.ssl.X509KeyManager;
34  import javax.security.auth.callback.PasswordCallback;
35  import java.awt.*;
36  import java.io.BufferedReader;
37  import java.io.File;
38  import java.io.IOException;
39  import java.io.InputStreamReader;
40  import java.net.InetAddress;
41  import java.net.Socket;
42  import java.security.InvalidAlgorithmParameterException;
43  import java.security.KeyManagementException;
44  import java.security.KeyStore;
45  import java.security.KeyStoreException;
46  import java.security.NoSuchAlgorithmException;
47  import java.util.ArrayList;
48  
49  /**
50   * SSLSocketFactory implementation.
51   * Wrapper for DavGatewaySSLProtocolSocketFactory used by HttpClient 4
52   */
53  public class DavGatewaySSLSocketFactory extends SSLSocketFactory {
54      static final Logger LOGGER = Logger.getLogger(DavGatewaySSLSocketFactory.class);
55  
56      private SSLContext sslcontext;
57  
58      private SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, InvalidAlgorithmParameterException {
59          if (this.sslcontext == null) {
60              this.sslcontext = createSSLContext();
61          }
62          return this.sslcontext;
63      }
64  
65      private SSLContext createSSLContext() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyManagementException, KeyStoreException {
66          // PKCS11 client certificate settings
67          String pkcs11Library = Settings.getProperty("davmail.ssl.pkcs11Library");
68  
69          String clientKeystoreType = Settings.getProperty("davmail.ssl.clientKeystoreType");
70          // set default keystore type
71          if (clientKeystoreType == null || clientKeystoreType.isEmpty()) {
72              clientKeystoreType = "PKCS11";
73          }
74  
75          if (pkcs11Library != null && !pkcs11Library.isEmpty() && "PKCS11".equals(clientKeystoreType)) {
76              StringBuilder pkcs11Buffer = new StringBuilder();
77              pkcs11Buffer.append("name=DavMail\n");
78              pkcs11Buffer.append("library=").append(pkcs11Library).append('\n');
79              String pkcs11Config = Settings.getProperty("davmail.ssl.pkcs11Config");
80              if (pkcs11Config != null && !pkcs11Config.isEmpty()) {
81                  pkcs11Buffer.append(pkcs11Config).append('\n');
82              }
83              SunPKCS11ProviderHandler.registerProvider(pkcs11Buffer.toString());
84          }
85          String algorithm = KeyManagerFactory.getDefaultAlgorithm();
86          if ("SunX509".equals(algorithm)) {
87              algorithm = "NewSunX509";
88          } else if ("IbmX509".equals(algorithm)) {
89              algorithm = "NewIbmX509";
90          }
91          KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
92  
93          ArrayList<KeyStore.Builder> keyStoreBuilders = new ArrayList<>();
94          // PKCS11 (smartcard) keystore with password callback
95          KeyStore.Builder scBuilder = KeyStore.Builder.newInstance("PKCS11", null, getProtectionParameter(null));
96          keyStoreBuilders.add(scBuilder);
97  
98          String clientKeystoreFile = Settings.getProperty("davmail.ssl.clientKeystoreFile");
99          String clientKeystorePass = Settings.getProperty("davmail.ssl.clientKeystorePass");
100         if (clientKeystoreFile != null && !clientKeystoreFile.isEmpty()
101                 && ("PKCS12".equals(clientKeystoreType) || "JKS".equals(clientKeystoreType))) {
102             // PKCS12 file based keystore
103             KeyStore.Builder fsBuilder = KeyStore.Builder.newInstance(clientKeystoreType, null,
104                     new File(clientKeystoreFile), getProtectionParameter(clientKeystorePass));
105             keyStoreBuilders.add(fsBuilder);
106         }
107         // Enable native Windows SmartCard access through MSCAPI (no PKCS11 config required)
108         if ("MSCAPI".equals(clientKeystoreType)) {
109             try {
110                 //Provider provider = (Provider) Class.forName("sun.security.mscapi.SunMSCAPI").getDeclaredConstructor().newInstance();
111                 //KeyStore keyStore = KeyStore.getInstance("Windows-MY", provider);
112                 KeyStore keyStore = KeyStore.getInstance("Windows-MY");
113                 keyStore.load(null, null);
114                 keyStoreBuilders.add(KeyStore.Builder.newInstance(keyStore, new KeyStore.PasswordProtection(null)));
115             } catch (Exception e) {
116                 // ignore
117             }
118         }
119 
120         ManagerFactoryParameters keyStoreBuilderParameters = new KeyStoreBuilderParameters(keyStoreBuilders);
121         keyManagerFactory.init(keyStoreBuilderParameters);
122 
123         // Get a list of key managers
124         KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
125 
126         // Walk through the key managers and replace all X509 Key Managers with
127         // a specialized wrapped DavMail X509 Key Manager
128         for (int i = 0; i < keyManagers.length; i++) {
129             KeyManager keyManager = keyManagers[i];
130             if (keyManager instanceof X509KeyManager) {
131                 keyManagers[i] = new DavMailX509KeyManager((X509KeyManager) keyManager);
132             }
133         }
134 
135         SSLContext context = SSLContext.getInstance("TLS");
136         context.init(keyManagers, new TrustManager[]{new DavGatewayX509TrustManager()}, null);
137         return context;
138     }
139 
140     private KeyStore.ProtectionParameter getProtectionParameter(String password) {
141         if (password != null && !password.isEmpty()) {
142             // password provided: create a PasswordProtection
143             return new KeyStore.PasswordProtection(password.toCharArray());
144         } else {
145             // request password at runtime through a callback
146             return new KeyStore.CallbackHandlerProtection(callbacks -> {
147                 if (callbacks.length > 0 && callbacks[0] instanceof PasswordCallback) {
148                     if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
149                         // headless or server mode
150                         System.out.print(((PasswordCallback) callbacks[0]).getPrompt() + ": ");
151                         String password1 = new BufferedReader(new InputStreamReader(System.in)).readLine();
152                         ((PasswordCallback) callbacks[0]).setPassword(password1.toCharArray());
153                     } else {
154                         PasswordPromptDialog passwordPromptDialog = new PasswordPromptDialog(((PasswordCallback) callbacks[0]).getPrompt());
155                         ((PasswordCallback) callbacks[0]).setPassword(passwordPromptDialog.getPassword());
156                     }
157                 }
158             });
159         }
160     }
161 
162     @Override
163     public String[] getDefaultCipherSuites() {
164         try {
165             return getSSLContext().getSocketFactory().getDefaultCipherSuites();
166         } catch (Exception e) {
167             // ignore
168         }
169         return new String[]{};
170     }
171 
172     @Override
173     public String[] getSupportedCipherSuites() {
174         try {
175             return getSSLContext().getSocketFactory().getSupportedCipherSuites();
176         } catch (Exception e) {
177             // ignore
178         }
179         return new String[]{};
180     }
181 
182     @Override
183     public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
184         LOGGER.debug("createSocket " + host + " " + port);
185         try {
186             return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
187         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
188             throw new IOException(e + " " + e.getMessage());
189         }
190     }
191 
192     @Override
193     public Socket createSocket(String host, int port) throws IOException {
194         LOGGER.debug("createSocket " + host + " " + port);
195         try {
196             return getSSLContext().getSocketFactory().createSocket(host, port);
197         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
198             throw new IOException(e + " " + e.getMessage());
199         }
200     }
201 
202     @Override
203     public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
204         LOGGER.debug("createSocket " + host + " " + port + " " + clientHost + " " + clientPort);
205         try {
206             return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
207         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
208             throw new IOException(e + " " + e.getMessage());
209         }
210     }
211 
212     @Override
213     public Socket createSocket(InetAddress host, int port) throws IOException {
214         LOGGER.debug("createSocket " + host + " " + port);
215         try {
216             return getSSLContext().getSocketFactory().createSocket(host, port);
217         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
218             throw new IOException(e + " " + e.getMessage());
219         }
220     }
221 
222     @Override
223     public Socket createSocket(InetAddress host, int port, InetAddress clientHost, int clientPort) throws IOException {
224         LOGGER.debug("createSocket " + host + " " + port + " " + clientHost + " " + clientPort);
225         try {
226             return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
227         } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | InvalidAlgorithmParameterException e) {
228             throw new IOException(e + " " + e.getMessage());
229         }
230     }
231 }