001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.server.tools;
018
019
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.net.InetAddress;
023import java.net.Socket;
024import java.security.KeyStore;
025import java.security.KeyStoreException;
026import java.security.NoSuchAlgorithmException;
027import java.security.Provider;
028import java.util.Arrays;
029import java.util.List;
030
031import javax.net.ssl.KeyManager;
032import javax.net.ssl.KeyManagerFactory;
033import javax.net.ssl.SSLContext;
034import javax.net.ssl.SSLSocket;
035import javax.net.ssl.SSLSocketFactory;
036import javax.net.ssl.TrustManager;
037import javax.net.ssl.TrustManagerFactory;
038import javax.net.ssl.X509TrustManager;
039
040import org.opends.server.extensions.BlindTrustManagerProvider;
041import org.forgerock.i18n.slf4j.LocalizedLogger;
042import org.forgerock.opendj.ldap.SSLContextBuilder;
043import org.opends.server.util.CollectionUtils;
044import org.opends.server.util.ExpirationCheckTrustManager;
045import org.opends.server.util.SelectableCertificateKeyManager;
046
047import com.forgerock.opendj.cli.ConnectionFactoryProvider;
048
049import static org.opends.messages.ToolMessages.*;
050
051
052/**
053 * This class provides SSL connection related utility functions.
054 */
055public class SSLConnectionFactory
056{
057  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
058
059  /**
060   * List of available TLS protocols. By default, corresponds to all TLS protocols available in the JVM.
061   * The list may be overridden if <em>org.opends.ldaps.protocols</em> system property is set.
062   */
063  private static final String[] TLS_PROTOCOLS;
064
065  static
066  {
067    List<String> protocols = null;
068    try
069    {
070      protocols = ConnectionFactoryProvider.getDefaultProtocols();
071    }
072    catch (NoSuchAlgorithmException ex)
073    {
074      logger.trace("Unable to retrieve default TLS protocols of the JVM, defaulting to TLSv1", ex);
075      protocols = Arrays.asList(SSLContextBuilder.PROTOCOL_TLS1);
076    }
077    TLS_PROTOCOLS = protocols.toArray(new String[protocols.size()]);
078  }
079
080  private SSLSocketFactory sslSocketFactory;
081
082  /**
083   * Constructor for the SSL connection factory.
084   */
085  public SSLConnectionFactory()
086  {
087  }
088
089  /**
090   * Initialize the connection factory by creating the key and
091   * trust managers for the SSL connection.
092   *
093   * @param  trustAll            Indicates whether to blindly trust all
094   *                             certificates.
095   * @param  keyStorePath        The path to the key store file.
096   * @param  keyStorePassword    The PIN to use to access the key store
097   *                             contents.
098   * @param  clientAlias         The alias to use for the client certificate.
099   * @param  trustStorePath      The path to the trust store file.
100   * @param  trustStorePassword  The PIN to use to access the trust store
101   *                             contents.
102   *
103   * @throws  SSLConnectionException  If a problem occurs while initializing the
104   *                                  connection factory.
105   */
106  public void init(boolean trustAll, String keyStorePath,
107                   String keyStorePassword, String clientAlias,
108                   String trustStorePath, String trustStorePassword)
109         throws SSLConnectionException
110  {
111    try
112    {
113      SSLContext ctx = SSLContext.getInstance("TLS");
114      KeyManager[] keyManagers = null;
115      TrustManager[] trustManagers = null;
116
117      if(trustAll)
118      {
119        BlindTrustManagerProvider blindTrustProvider =
120            new BlindTrustManagerProvider();
121        trustManagers = blindTrustProvider.getTrustManagers();
122      } else if (trustStorePath == null) {
123        trustManagers = PromptTrustManager.getTrustManagers();
124      } else
125      {
126        TrustManager[] tmpTrustManagers =
127             getTrustManagers(KeyStore.getDefaultType(), null, trustStorePath,
128                              trustStorePassword);
129        trustManagers = new TrustManager[tmpTrustManagers.length];
130        for (int i=0; i < trustManagers.length; i++)
131        {
132          trustManagers[i] =
133               new ExpirationCheckTrustManager((X509TrustManager)
134                                               tmpTrustManagers[i]);
135        }
136      }
137      if(keyStorePath != null)
138      {
139        keyManagers = getKeyManagers(KeyStore.getDefaultType(), null,
140                          keyStorePath, keyStorePassword);
141
142        if (clientAlias != null)
143        {
144          keyManagers = SelectableCertificateKeyManager.wrap(keyManagers, CollectionUtils.newTreeSet(clientAlias));
145        }
146      }
147
148      ctx.init(keyManagers, trustManagers, new java.security.SecureRandom());
149      sslSocketFactory = ctx.getSocketFactory();
150    } catch(Exception e)
151    {
152      throw new SSLConnectionException(
153              ERR_TOOLS_CANNOT_CREATE_SSL_CONNECTION.get(e.getMessage()), e);
154    }
155  }
156
157  /**
158   * Create the SSL socket connection to the specified host.
159   *
160   * @param  hostName    The address of the system to which the connection
161   *                     should be established.
162   * @param  portNumber  The port number to which the connection should be
163   *                     established.
164   *
165   * @return  The SSL socket established to the specified host.
166   *
167   * @throws  SSLConnectionException  If a problem occurs while performing SSL
168   *                                  negotiation.
169   *
170   * @throws  IOException  If a problem occurs while attempting to communicate
171   *                       with the server.
172   */
173  public Socket createSocket(String hostName, int portNumber)
174      throws SSLConnectionException, IOException
175  {
176    if(sslSocketFactory == null)
177    {
178      throw new SSLConnectionException(ERR_TOOLS_SSL_CONNECTION_NOT_INITIALIZED.get());
179    }
180    return socketWithEnabledProtocols(sslSocketFactory.createSocket(hostName, portNumber));
181  }
182
183  private Socket socketWithEnabledProtocols(Socket socket)
184  {
185    SSLSocket sslSocket = (SSLSocket) socket;
186    sslSocket.setEnabledProtocols(TLS_PROTOCOLS);
187    return sslSocket;
188  }
189
190  /**
191   * Create the SSL socket connection to the specified host.
192   *
193   * @param host
194   *          The address of the system to which the connection should be
195   *          established.
196   * @param portNumber
197   *          The port number to which the connection should be established.
198   * @return The SSL socket established to the specified host.
199   * @throws SSLConnectionException
200   *           If a problem occurs while performing SSL negotiation.
201   * @throws IOException
202   *           If a problem occurs while attempting to communicate with the
203   *           server.
204   */
205  public Socket createSocket(InetAddress host, int portNumber)
206      throws SSLConnectionException, IOException
207  {
208    if (sslSocketFactory == null)
209    {
210      throw new SSLConnectionException(ERR_TOOLS_SSL_CONNECTION_NOT_INITIALIZED.get());
211    }
212    return socketWithEnabledProtocols(sslSocketFactory.createSocket(host, portNumber));
213  }
214
215  /**
216   * Create the SSL socket connection to the specified host layered over
217   * an existing socket.
218   *
219   * @param  s           The socket to use for the existing connection.
220   * @param  hostName    The address of the system to which the connection
221   *                     should be established.
222   * @param  portNumber  The port number to which the connection should be
223   *                     established.
224   * @param  autoClose   Indicates whether the underlying connection should be
225   *                     automatically closed when the SSL session is ended.
226   *
227   * @return  The SSL socket established to the specified host.
228   *
229   * @throws  SSLConnectionException  If a problem occurs while performing SSL
230   *                                  negotiation.
231   *
232   * @throws  IOException  If a problem occurs while attempting to communicate
233   *                       with the server.
234   */
235  public Socket createSocket(Socket s, String hostName, int portNumber,
236                             boolean autoClose)
237         throws SSLConnectionException, IOException
238  {
239    if(sslSocketFactory == null)
240    {
241      throw new SSLConnectionException(ERR_TOOLS_SSL_CONNECTION_NOT_INITIALIZED.get());
242    }
243    return socketWithEnabledProtocols(sslSocketFactory.createSocket(s, hostName, portNumber, autoClose));
244  }
245
246  /**
247   * Retrieves a set of <CODE>KeyManager</CODE> objects that may be used for
248   * interactions requiring access to a key manager.
249   *
250   * @param  keyStoreType  The key store type to use with the specified file.
251   * @param  provider      The provider to use when accessing the key store.
252   * @param  keyStoreFile  The path to the file containing the key store data.
253   * @param  keyStorePass  The PIN needed to access the key store contents.
254   *
255   * @return  A set of <CODE>KeyManager</CODE> objects that may be used for
256   *          interactions requiring access to a key manager.
257   *
258   * @throws  KeyStoreException  If a problem occurs while interacting with the
259   *                             key store.
260   *
261   * @throws  SSLConnectionException  If a problem occurs while trying to load
262   *                                 key store file.
263   */
264
265  private KeyManager[] getKeyManagers(String keyStoreType,
266                                      Provider provider,
267                                      String keyStoreFile,
268                                      String keyStorePass)
269          throws KeyStoreException, SSLConnectionException
270  {
271    if(keyStoreFile == null)
272    {
273      // Lookup the file name through the JDK property.
274      keyStoreFile = getKeyStore();
275    }
276
277    if(keyStorePass == null)
278    {
279      // Lookup the keystore PIN through the JDK property.
280      keyStorePass = getKeyStorePIN();
281    }
282
283    KeyStore ks = null;
284    if(provider != null)
285    {
286      ks = KeyStore.getInstance(keyStoreType, provider);
287    } else
288    {
289      ks = KeyStore.getInstance(keyStoreType);
290    }
291
292    char[] keyStorePIN = null;
293    if(keyStorePass != null)
294    {
295      keyStorePIN = keyStorePass.toCharArray();
296    }
297
298    try
299    {
300      FileInputStream inputStream = new FileInputStream(keyStoreFile);
301      ks.load(inputStream, keyStorePIN);
302      inputStream.close();
303
304    } catch(Exception e)
305    {
306      logger.traceException(e);
307
308      throw new SSLConnectionException(
309              ERR_TOOLS_CANNOT_LOAD_KEYSTORE_FILE.get(keyStoreFile), e);
310    }
311
312    try
313    {
314      String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
315      KeyManagerFactory keyManagerFactory =
316        KeyManagerFactory.getInstance(keyManagerAlgorithm);
317
318      keyManagerFactory.init(ks, keyStorePIN);
319      return keyManagerFactory.getKeyManagers();
320    } catch(Exception ke)
321    {
322      logger.traceException(ke);
323
324      throw new SSLConnectionException(
325              ERR_TOOLS_CANNOT_INIT_KEYMANAGER.get(keyStoreFile), ke);
326    }
327
328  }
329
330
331  /**
332   * Retrieves a set of <CODE>TrustManager</CODE> objects that may be used for
333   * interactions requiring access to a trust manager.
334   *
335   * @param  trustStoreType  The trust store type to use with the specified
336   *                         file.
337   * @param  provider        The provider to use when accessing the trust store.
338   * @param  trustStoreFile  The path to the file containing the trust store
339   *                         data.
340   * @param  trustStorePass  The PIN needed to access the trust store contents.
341   *
342   * @return  A set of <CODE>TrustManager</CODE> objects that may be used for
343   *          interactions requiring access to a trust manager.
344   *
345   * @throws  KeyStoreException  If a problem occurs while interacting with the
346   *                             trust store.
347   *
348   * @throws  SSLConnectionException  If a problem occurs while trying to load
349   *                                 trust store file.
350   */
351  private TrustManager[] getTrustManagers(String trustStoreType,
352                                            Provider provider,
353                                            String trustStoreFile,
354                                            String trustStorePass)
355      throws KeyStoreException, SSLConnectionException
356  {
357    if(trustStoreFile == null)
358    {
359      trustStoreFile = getTrustStore();
360      // No trust store file available.
361      if(trustStoreFile == null)
362      {
363        return null;
364      }
365    }
366
367    if(trustStorePass == null)
368    {
369      trustStorePass = getTrustStorePIN();
370    }
371
372    KeyStore trustStore = null;
373    if(provider != null)
374    {
375      trustStore = KeyStore.getInstance(trustStoreType, provider);
376    } else
377    {
378      trustStore = KeyStore.getInstance(trustStoreType);
379    }
380
381    char[] trustStorePIN = null;
382    if(trustStorePass != null)
383    {
384      trustStorePIN = trustStorePass.toCharArray();
385    }
386
387    try
388    {
389      FileInputStream inputStream = new FileInputStream(trustStoreFile);
390      trustStore.load(inputStream, trustStorePIN);
391      inputStream.close();
392    } catch(Exception e)
393    {
394      logger.traceException(e);
395
396      throw new SSLConnectionException(
397              ERR_TOOLS_CANNOT_LOAD_TRUSTSTORE_FILE.get(trustStoreFile), e);
398    }
399
400    try
401    {
402      String trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
403      TrustManagerFactory trustManagerFactory =
404        TrustManagerFactory.getInstance(trustManagerAlgorithm);
405
406      trustManagerFactory.init(trustStore);
407      return trustManagerFactory.getTrustManagers();
408    } catch(Exception ke)
409    {
410      logger.traceException(ke);
411
412      throw new SSLConnectionException(
413              ERR_TOOLS_CANNOT_INIT_TRUSTMANAGER.get(trustStoreFile), ke);
414    }
415
416  }
417
418  /**
419   * Read the KeyStore PIN from the JSSE system property.
420   *
421   * @return  The PIN that should be used to access the key store.
422   */
423
424   private String getKeyStorePIN()
425   {
426    return System.getProperty("javax.net.ssl.keyStorePassword");
427   }
428
429  /**
430   * Read the TrustStore PIN from the JSSE system property.
431   *
432   * @return  The PIN that should be used to access the trust store.
433   */
434
435   private String getTrustStorePIN()
436   {
437    return System.getProperty("javax.net.ssl.trustStorePassword");
438   }
439
440  /**
441   * Read the KeyStore from the JSSE system property.
442   *
443   * @return  The path to the key store file.
444   */
445
446   private String getKeyStore()
447   {
448    return System.getProperty("javax.net.ssl.keyStore");
449   }
450
451  /**
452   * Read the TrustStore from the JSSE system property.
453   *
454   * @return  The path to the trust store file.
455   */
456
457   private String getTrustStore()
458   {
459    return System.getProperty("javax.net.ssl.trustStore");
460   }
461
462}
463