View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.api.security.tls;
8   
9   import org.mule.api.lifecycle.CreateException;
10  import org.mule.api.security.TlsDirectKeyStore;
11  import org.mule.api.security.TlsDirectTrustStore;
12  import org.mule.api.security.TlsIndirectKeyStore;
13  import org.mule.api.security.TlsProtocolHandler;
14  import org.mule.api.security.provider.AutoDiscoverySecurityProviderFactory;
15  import org.mule.api.security.provider.SecurityProviderFactory;
16  import org.mule.api.security.provider.SecurityProviderInfo;
17  import org.mule.config.i18n.CoreMessages;
18  import org.mule.util.FileUtils;
19  import org.mule.util.IOUtils;
20  import org.mule.util.StringUtils;
21  
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.security.GeneralSecurityException;
26  import java.security.KeyManagementException;
27  import java.security.KeyStore;
28  import java.security.KeyStoreException;
29  import java.security.NoSuchAlgorithmException;
30  import java.security.Provider;
31  import java.security.Security;
32  import java.util.Enumeration;
33  
34  import javax.net.ssl.KeyManager;
35  import javax.net.ssl.KeyManagerFactory;
36  import javax.net.ssl.SSLContext;
37  import javax.net.ssl.SSLServerSocketFactory;
38  import javax.net.ssl.SSLSocketFactory;
39  import javax.net.ssl.TrustManager;
40  import javax.net.ssl.TrustManagerFactory;
41  
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  
45  /**
46   * Support for configuring TLS/SSL connections.
47   * <p/>
48   * <h2>Introduction</h2>
49   * <p/>
50   * This class was introduced to centralise the work of TLS/SSL configuration.  It is intended
51   * to be backwards compatible with earlier code (as much as possible) and so is perhaps more
52   * complex than would be necessary if starting from zero - the main source of confusion is the
53   * distinction between direct and indirect creation of sockets and stores.
54   * <p/>
55   * <h2>Configuration</h2>
56   * <p/>
57   * The documentation in this class is intended more for programmers than end uses.  If you are
58   * configuring a connector the interfaces {@link org.mule.api.security.TlsIndirectTrustStore},
59   * {@link TlsDirectTrustStore},
60   * {@link TlsDirectKeyStore} and {@link TlsIndirectKeyStore} should provide guidance to individual
61   * properties.  In addition you should check the documentation for the specific protocol / connector
62   * used and may also need to read the discussion on direct and indirect socket and store creation
63   * below (or, more simply, just use whichever key store interface your connector implements!).
64   * <p/>
65   * <h2>Programming</h2>
66   * <p/>
67   * This class is intended to be used as a delegate as we typically want to add security to an
68   * already existing connector (so we inherit from that connector, implement the appropriate
69   * interfaces from {@link org.mule.api.security.TlsIndirectTrustStore}, {@link TlsDirectTrustStore},
70   * {@link TlsDirectKeyStore} and {@link TlsIndirectKeyStore}, and then forward calls to the
71   * interfaces to an instance of this class).
72   * <p/>
73   * <p>For setting System properties (and reading them) use {@link TlsPropertiesMapper}.  This
74   * can take a "namespace" which can then be used by {@link TlsPropertiesSocketFactory} to
75   * construct an appropriate socket factory.  This approach (storing to properties and then
76   * retrieving that information later in a socket factory) lets us pass TLS/SSL configuration
77   * into libraries that are configured by specifying on the socket factory class.</p>
78   * <p/>
79   * <h2>Direct and Indirect Socket and Store Creation</h2>
80   * <p/>
81   * For the SSL transport, which historically defined parameters for many different secure
82   * transports, the configuration interfaces worked as follows:
83   * <p/>
84   * <dl>
85   * <dt>{@link TlsDirectTrustStore}</dt><dd>Used to generate trust store directly and indirectly
86   * for all TLS/SSL conections via System properties</dd>
87   * <dt>{@link TlsDirectKeyStore}</dt><dd>Used to generate key store directly</dd>
88   * <dt>{@link TlsIndirectKeyStore}</dt><dd>Used to generate key store indirectly for all
89   * TLS/SSL conections via System properties</dd>
90   * </dl>
91   * <p/>
92   * Historically, many other transports relied on the indirect configurations defined above.
93   * So they implemented {@link org.mule.api.security.TlsIndirectTrustStore}
94   * (a superclass of {@link TlsDirectTrustStore})
95   * and relied on {@link TlsIndirectKeyStore} from the SSL configuration.  For continuity these
96   * interfaces continue to be used, even though
97   * the configurations are now typically (see individual connector/protocol documentation) specific
98   * to a protocol or connector.  <em>Note - these interfaces are new, but the original code had
99   * those methods, used as described.  The new interfaces only make things explicit.</em>
100  * <p/>
101  * <p><em>Note for programmers</em> One way to understand the above is to see that many
102  * protocols are handled by libraries that are configured by providing either properties or
103  * a socket factory.  In both cases (the latter via {@link TlsPropertiesSocketFactory}) we
104  * continue to use properties and the "indirect" interface.  Note also that the mapping
105  * in {@link TlsPropertiesMapper} correctly handles the asymmetry, so an initial call to
106  * {@link TlsConfiguration} uses the keystore defined via {@link TlsDirectKeyStore}, but
107  * when a {@link TlsConfiguration} is retrieved from System proerties using
108  * {@link TlsPropertiesMapper#readFromProperties(TlsConfiguration,java.util.Properties)}
109  * the "indirect" properties are supplied as "direct" values, meaning that the "indirect"
110  * socket factory can be retrieved from {@link #getKeyManagerFactory()}.  It just works.</p>
111  */
112 public final class TlsConfiguration
113         implements TlsDirectTrustStore, TlsDirectKeyStore, TlsIndirectKeyStore, TlsProtocolHandler
114 {
115 
116     public static final String DEFAULT_KEYSTORE = ".keystore";
117     public static final String DEFAULT_KEYSTORE_TYPE = KeyStore.getDefaultType();
118     public static final String JSSE_NAMESPACE = "javax.net";
119 
120     private Log logger = LogFactory.getLog(getClass());
121 
122     private SecurityProviderFactory spFactory = new AutoDiscoverySecurityProviderFactory();
123     private SecurityProviderInfo spInfo = spFactory.getSecurityProviderInfo();
124     private Provider provider = spFactory.getProvider();
125     private String sslType = spInfo.getDefaultSslType();
126 
127     // global
128     private String protocolHandler = spInfo.getProtocolHandler();
129 
130     // this is the key store that is generated in-memory and available to connectors explicitly.
131     // it is local to the socket.
132     private String keyStoreName = DEFAULT_KEYSTORE; // was default in https but not ssl
133     private String keyAlias = null;
134     private String keyPassword = null;
135     private String keyStorePassword = null;
136     private String keystoreType = DEFAULT_KEYSTORE_TYPE;
137     private String keyManagerAlgorithm = spInfo.getKeyManagerAlgorithm();
138     private KeyManagerFactory keyManagerFactory = null;
139 
140     // this is the key store defined in system properties that is used implicitly.
141     // note that some transports use different namespaces within system properties,
142     // so this is typically global across a particular transport.
143     // it is also used as the trust store defined in system properties if no other trust
144     // store is given and explicitTrustStoreOnly is false
145     private String clientKeyStoreName = null;
146     private String clientKeyStorePassword = null;
147     private String clientKeyStoreType = DEFAULT_KEYSTORE_TYPE;
148 
149     // this is the trust store used to construct sockets both explicitly
150     // and globally (if not set, see client key above) via the jvm defaults.
151     private String trustStoreName = null;
152     private String trustStorePassword = null;
153     private String trustStoreType = DEFAULT_KEYSTORE_TYPE;
154     private String trustManagerAlgorithm = spInfo.getKeyManagerAlgorithm();
155     private TrustManagerFactory trustManagerFactory = null;
156     private boolean explicitTrustStoreOnly = false;
157     private boolean requireClientAuthentication = false;
158 
159     /**
160      * Support for TLS connections with a given initial value for the key store
161      *
162      * @param keyStore initial value for the key store
163      */
164     public TlsConfiguration(String keyStore)
165     {
166         this.keyStoreName = keyStore;
167     }
168 
169     // note - in what follows i'm using "raw" variables rather than accessors because
170     // i think the names are clearer.  the API names for the accessors are historical
171     // and not a close fit to actual use (imho).
172 
173     /**
174      * @param anon      If the connection is anonymous then we don't care about client keys
175      * @param namespace Namespace to use for global properties (for JSSE use JSSE_NAMESPACE)
176      * @throws CreateException ON initialisation problems
177      */
178     public void initialise(boolean anon, String namespace) throws CreateException
179     {
180         if (logger.isDebugEnabled())
181         {
182             logger.debug("initialising: anon " + anon);
183         }
184         validate(anon);
185 
186         Security.addProvider(provider);
187         System.setProperty("java.protocol.handler.pkgs", protocolHandler);
188 
189         if (!anon)
190         {
191             initKeyManagerFactory();
192         }
193         initTrustManagerFactory();
194 
195         if (null != namespace)
196         {
197             new TlsPropertiesMapper(namespace).writeToProperties(System.getProperties(), this);
198         }
199     }
200 
201     private void validate(boolean anon) throws CreateException
202     {
203         assertNotNull(getProvider(), "The security provider cannot be null");
204         if (!anon)
205         {
206             assertNotNull(getKeyStore(), "The KeyStore location cannot be null");
207             assertNotNull(getKeyPassword(), "The Key password cannot be null");
208             assertNotNull(getKeyStorePassword(), "The KeyStore password cannot be null");
209             assertNotNull(getKeyManagerAlgorithm(), "The Key Manager Algorithm cannot be null");
210         }
211     }
212 
213     private void initKeyManagerFactory() throws CreateException
214     {
215         if (logger.isDebugEnabled())
216         {
217             logger.debug("initialising key manager factory from keystore data");
218         }
219 
220         KeyStore tempKeyStore;
221         try
222         {
223             tempKeyStore = loadKeyStore();
224             checkKeyStoreContainsAlias(tempKeyStore);
225         }
226         catch (Exception e)
227         {
228             throw new CreateException(
229                     CoreMessages.failedToLoad("KeyStore: " + keyStoreName), e, this);
230         }
231 
232         try
233         {
234             keyManagerFactory = KeyManagerFactory.getInstance(getKeyManagerAlgorithm());
235             keyManagerFactory.init(tempKeyStore, keyPassword.toCharArray());
236         }
237         catch (Exception e)
238         {
239             throw new CreateException(CoreMessages.failedToLoad("Key Manager"), e, this);
240         }
241     }
242 
243     protected  KeyStore loadKeyStore() throws GeneralSecurityException, IOException
244     {
245         KeyStore tempKeyStore = KeyStore.getInstance(keystoreType);
246 
247         InputStream is = IOUtils.getResourceAsStream(keyStoreName, getClass());
248         if (null == is)
249         {
250             throw new FileNotFoundException(
251                 CoreMessages.cannotLoadFromClasspath("Keystore: " + keyStoreName).getMessage());
252         }
253 
254         tempKeyStore.load(is, keyStorePassword.toCharArray());
255         return tempKeyStore;
256     }
257 
258     protected void checkKeyStoreContainsAlias(KeyStore keyStore) throws KeyStoreException
259     {
260         if (StringUtils.isNotBlank(keyAlias))
261         {
262             boolean keyAliasFound = false;
263 
264             Enumeration<String> aliases = keyStore.aliases();
265             while (aliases.hasMoreElements())
266             {
267                 String alias = aliases.nextElement();
268 
269                 if (alias.equals(keyAlias))
270                 {
271                     // if alias is found all is valid but continue processing to strip out all
272                     // other (unwanted) keys
273                     keyAliasFound = true;
274                 }
275                 else
276                 {
277                     // if the current alias is not the one we are looking for, remove
278                     // it from the keystore
279                     keyStore.deleteEntry(alias);
280                 }
281             }
282 
283             // if the alias was not found, throw an exception
284             if (!keyAliasFound)
285             {
286                 throw new IllegalStateException("Key with alias \"" + keyAlias + "\" was not found");
287             }
288         }
289     }
290 
291     private void initTrustManagerFactory() throws CreateException
292     {
293         if (null != trustStoreName)
294         {
295             trustStorePassword = null == trustStorePassword ? "" : trustStorePassword;
296 
297             KeyStore trustStore;
298             try
299             {
300                 trustStore = KeyStore.getInstance(trustStoreType);
301                 InputStream is = IOUtils.getResourceAsStream(trustStoreName, getClass());
302                 if (null == is)
303                 {
304                     throw new FileNotFoundException(
305                             "Failed to load truststore from classpath or local file: " + trustStoreName);
306                 }
307                 trustStore.load(is, trustStorePassword.toCharArray());
308             }
309             catch (Exception e)
310             {
311                 throw new CreateException(
312                         CoreMessages.failedToLoad("TrustStore: " + trustStoreName), e, this);
313             }
314 
315             try
316             {
317                 trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm);
318                 trustManagerFactory.init(trustStore);
319             }
320             catch (Exception e)
321             {
322                 throw new CreateException(
323                         CoreMessages.failedToLoad("Trust Manager (" + trustManagerAlgorithm + ")"), e, this);
324             }
325         }
326     }
327 
328 
329     private static void assertNotNull(Object value, String message)
330     {
331         if (null == value)
332         {
333             throw new IllegalArgumentException(message);
334         }
335     }
336 
337     private static String defaultForNull(String value, String deflt)
338     {
339         if (null == value)
340         {
341             return deflt;
342         }
343         else
344         {
345             return value;
346         }
347     }
348 
349     public SSLSocketFactory getSocketFactory() throws NoSuchAlgorithmException, KeyManagementException
350     {
351         return getSslContext().getSocketFactory();
352     }
353 
354     public SSLServerSocketFactory getServerSocketFactory()
355             throws NoSuchAlgorithmException, KeyManagementException
356     {
357         return getSslContext().getServerSocketFactory();
358     }
359 
360     public SSLContext getSslContext() throws NoSuchAlgorithmException, KeyManagementException
361     {
362         KeyManager[] keyManagers =
363                 null == getKeyManagerFactory() ? null : getKeyManagerFactory().getKeyManagers();
364         TrustManager[] trustManagers =
365                 null == getTrustManagerFactory() ? null : getTrustManagerFactory().getTrustManagers();
366 
367         SSLContext context = SSLContext.getInstance(getSslType());
368         // TODO - nice to have a configurable random number source set here
369         context.init(keyManagers, trustManagers, null);
370         return context;
371     }
372 
373     public String getSslType()
374     {
375         return sslType;
376     }
377 
378     public void setSslType(String sslType)
379     {
380         this.sslType = sslType;
381     }
382 
383     public Provider getProvider()
384     {
385         return provider;
386     }
387 
388     public void setProvider(Provider provider)
389     {
390         this.provider = provider;
391     }
392 
393     public String getProtocolHandler()
394     {
395         return protocolHandler;
396     }
397 
398     public void setProtocolHandler(String protocolHandler)
399     {
400         this.protocolHandler = protocolHandler;
401     }
402 
403     public SecurityProviderFactory getSecurityProviderFactory()
404     {
405         return spFactory;
406     }
407 
408     public void setSecurityProviderFactory(SecurityProviderFactory spFactory)
409     {
410         this.spFactory = spFactory;
411     }
412 
413     // access to the explicit key store variables
414 
415     public String getKeyStore()
416     {
417         return keyStoreName;
418     }
419 
420     public void setKeyStore(String name) throws IOException
421     {
422         keyStoreName = name;
423         if (null != keyStoreName)
424         {
425             keyStoreName = FileUtils.getResourcePath(keyStoreName, getClass());
426             if (logger.isDebugEnabled())
427             {
428                 logger.debug("Normalised keyStore path to: " + keyStoreName);
429             }
430         }
431     }
432 
433     public String getKeyPassword()
434     {
435         return keyPassword;
436     }
437 
438     public void setKeyPassword(String keyPassword)
439     {
440         this.keyPassword = keyPassword;
441     }
442 
443     public String getKeyStorePassword()
444     {
445         return keyStorePassword;
446     }
447 
448     public void setKeyStorePassword(String storePassword)
449     {
450         this.keyStorePassword = storePassword;
451     }
452 
453     public String getKeyStoreType()
454     {
455         return keystoreType;
456     }
457 
458     public void setKeyStoreType(String keystoreType)
459     {
460         this.keystoreType = keystoreType;
461     }
462 
463     public String getKeyManagerAlgorithm()
464     {
465         return keyManagerAlgorithm;
466     }
467 
468     public void setKeyManagerAlgorithm(String keyManagerAlgorithm)
469     {
470         this.keyManagerAlgorithm = keyManagerAlgorithm;
471     }
472 
473     public KeyManagerFactory getKeyManagerFactory()
474     {
475         return keyManagerFactory;
476     }
477 
478     // access to the implicit key store variables
479 
480     public String getClientKeyStore()
481     {
482         return clientKeyStoreName;
483     }
484 
485     public void setClientKeyStore(String name) throws IOException
486     {
487         clientKeyStoreName = name;
488         if (null != clientKeyStoreName)
489         {
490             clientKeyStoreName = FileUtils.getResourcePath(clientKeyStoreName, getClass());
491             if (logger.isDebugEnabled())
492             {
493                 logger.debug("Normalised clientKeyStore path to: " + clientKeyStoreName);
494             }
495         }
496     }
497 
498     public String getClientKeyStorePassword()
499     {
500         return clientKeyStorePassword;
501     }
502 
503     public void setClientKeyStorePassword(String clientKeyStorePassword)
504     {
505         this.clientKeyStorePassword = clientKeyStorePassword;
506     }
507 
508     public void setClientKeyStoreType(String clientKeyStoreType)
509     {
510         this.clientKeyStoreType = clientKeyStoreType;
511     }
512 
513     public String getClientKeyStoreType()
514     {
515         return clientKeyStoreType;
516     }
517 
518     // access to trust store variables
519 
520     public String getTrustStore()
521     {
522         return trustStoreName;
523     }
524 
525     public void setTrustStore(String name) throws IOException
526     {
527         trustStoreName = name;
528         if (null != trustStoreName)
529         {
530             trustStoreName = FileUtils.getResourcePath(trustStoreName, getClass());
531             if (logger.isDebugEnabled())
532             {
533                 logger.debug("Normalised trustStore path to: " + trustStoreName);
534             }
535         }
536     }
537 
538     public String getTrustStorePassword()
539     {
540         return trustStorePassword;
541     }
542 
543     public void setTrustStorePassword(String trustStorePassword)
544     {
545         this.trustStorePassword = trustStorePassword;
546     }
547 
548     public String getTrustStoreType()
549     {
550         return trustStoreType;
551     }
552 
553     public void setTrustStoreType(String trustStoreType)
554     {
555         this.trustStoreType = trustStoreType;
556     }
557 
558     public String getTrustManagerAlgorithm()
559     {
560         return trustManagerAlgorithm;
561     }
562 
563     public void setTrustManagerAlgorithm(String trustManagerAlgorithm)
564     {
565         this.trustManagerAlgorithm = defaultForNull(trustManagerAlgorithm, spInfo.getKeyManagerAlgorithm());
566     }
567 
568     public TrustManagerFactory getTrustManagerFactory()
569     {
570         return trustManagerFactory;
571     }
572 
573     public void setTrustManagerFactory(TrustManagerFactory trustManagerFactory)
574     {
575         this.trustManagerFactory = trustManagerFactory;
576     }
577 
578     public boolean isExplicitTrustStoreOnly()
579     {
580         return explicitTrustStoreOnly;
581     }
582 
583     public void setExplicitTrustStoreOnly(boolean explicitTrustStoreOnly)
584     {
585         this.explicitTrustStoreOnly = explicitTrustStoreOnly;
586     }
587 
588     public boolean isRequireClientAuthentication()
589     {
590         return requireClientAuthentication;
591     }
592 
593     public void setRequireClientAuthentication(boolean requireClientAuthentication)
594     {
595         this.requireClientAuthentication = requireClientAuthentication;
596     }
597 
598     public String getKeyAlias()
599     {
600         return keyAlias;
601     }
602 
603     public void setKeyAlias(String keyAlias)
604     {
605         this.keyAlias = keyAlias;
606     }
607 
608 }
609 
610