KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > avalon > cornerstone > blocks > sockets > SSLFactoryBuilder


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12  * implied.
13  *
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.avalon.cornerstone.blocks.sockets;
19
20 import javax.net.ssl.KeyManagerFactory;
21 import javax.net.ssl.SSLContext;
22 import javax.net.ssl.TrustManagerFactory;
23 import java.io.File JavaDoc;
24 import java.io.FileInputStream JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.security.GeneralSecurityException JavaDoc;
28 import java.security.KeyStore JavaDoc;
29 import java.util.Arrays JavaDoc;
30 import javax.net.ssl.SSLServerSocketFactory;
31 import javax.net.ssl.SSLSocketFactory;
32 import org.apache.avalon.framework.activity.Disposable;
33 import org.apache.avalon.framework.activity.Initializable;
34 import org.apache.avalon.framework.configuration.Configurable;
35 import org.apache.avalon.framework.configuration.Configuration;
36 import org.apache.avalon.framework.configuration.ConfigurationException;
37 import org.apache.avalon.framework.context.Context;
38 import org.apache.avalon.framework.context.ContextException;
39 import org.apache.avalon.framework.context.Contextualizable;
40 import org.apache.avalon.framework.logger.AbstractLogEnabled;
41
42 /**
43  * Builds SSLContexts with desired properties. Hides all the gory
44  * details of SSLContext productions behind nice Avalon
45  * interfaces. Married to Sun JCA implementation.
46  * <p>
47  * Configuration looks like:
48  * <pre>
49  * &lt;ssl-factory&gt;
50  * &lt;keystore&gt;
51  * &lt;file&gt;conf/keystore&lt;/file&gt; &lt;!-- keystore file location --&gt;
52  * &lt;password&gt;&lt;/password&gt; &lt;!-- Key Store file password, only used to check keystore integrity --&gt;
53  * &lt;key-password&gt;&lt;/key-password&gt; &lt;!-- Only required when you need to decrypt a private key --&gt;
54  * &lt;type&gt;JKS&lt;/type&gt; &lt;!-- Key Store file format, defaults to JKS --&gt;
55  * &lt;algorithm&gt;SunX509&lt;/algorithm&gt; &lt;!-- Cryptography provider ID, defaults to SunX509 --&gt;
56  * &lt;/keystore&gt;
57  * &lt;!-- SSL protocol to use, defaults to TLS, another possible value is SSL --&gt;
58  * &lt;protocol&gt;TLS&lt;/protocol&gt;
59  * &lt;/ssl-factory&gt;
60  * </pre>
61  * </p>
62  * <p>
63  * Notes on keystore files. Absolute paths are supported. Relative
64  * paths are interpreted relative to .sar base directory. Defaults to
65  * conf/keystore. Since keystore usually contains sensitive keys it
66  * maybe beneficial to <b>not</b> include the keystores into the .sar
67  * files.
68  * </p>
69  * @author <a HREF="mailto:greg-avalon-apps at nest.cx">Greg Steuck</a>
70  */

71 public class SSLFactoryBuilder extends AbstractLogEnabled
72     implements Configurable, Contextualizable, Disposable, Initializable
73 {
74     private File JavaDoc m_baseDirectory;
75     private File JavaDoc m_keystoreFile;
76
77     private String JavaDoc m_keystorePassword;
78     private String JavaDoc m_keyPassword;
79     private String JavaDoc m_protocol;
80     private String JavaDoc m_provider;
81     private String JavaDoc m_keystoreFormat;
82
83     private SSLContext m_ctx;
84
85     static
86     {
87         // Registers Sun's providers
88
java.security.Security.addProvider( new sun.security.provider.Sun() );
89         java.security.Security.addProvider( new com.sun.net.ssl.internal.ssl.Provider() );
90     }
91
92     /**
93      * Requires a BlockContext. We'll see how we end up expressing
94      * these dependencies.
95      * @avalon.entry key="urn:avalon:home"
96      */

97     public void contextualize( final Context context ) throws ContextException
98     {
99         try
100         {
101             m_baseDirectory = (File JavaDoc) context.get( "urn:avalon:home" );
102         }
103         catch( ContextException ce )
104         {
105             m_baseDirectory = (File JavaDoc)context.get( "app.home" );
106         }
107     }
108
109     public void configure( final Configuration configuration )
110         throws ConfigurationException
111     {
112         final Configuration storeConfig = configuration.getChild( "keystore" );
113         final String JavaDoc fileName = storeConfig.getChild( "file" ).getValue( "conf/keystore" );
114         final File JavaDoc configuredFile = new File JavaDoc( fileName );
115         if( !configuredFile.isAbsolute() )
116         {
117             m_keystoreFile = new File JavaDoc( m_baseDirectory, fileName );
118         }
119         else
120         {
121             m_keystoreFile = configuredFile;
122         }
123
124         m_keystorePassword = storeConfig.getChild( "password" ).getValue( null );
125         m_keyPassword = storeConfig.getChild( "key-password" ).getValue( null );
126         // key is named incorrectly, left as is for compatibility
127
m_provider = storeConfig.getChild( "algorithm" ).getValue( "SunX509" );
128         // key is named incorrectly, left as is for compatibility
129
m_keystoreFormat = storeConfig.getChild( "type" ).getValue( "JKS" );
130         // ugly compatibility workaround follows
131
m_protocol = configuration.getChild( "protocol" ).
132             getValue( storeConfig.getChild( "protocol" ).getValue( "TLS" ) );
133     }
134
135     /**
136      * Produces a fresh ssl socket factory with configured parameters.
137      */

138     public SSLSocketFactory buildSocketFactory()
139     {
140         return m_ctx.getSocketFactory();
141     }
142
143     /**
144      * Produces a fresh ssl server socket factory with configured
145      * parameters.
146      */

147     public SSLServerSocketFactory buildServerSocketFactory()
148     {
149         return m_ctx.getServerSocketFactory();
150     }
151
152     public void initialize()
153         throws IOException JavaDoc, GeneralSecurityException JavaDoc
154     {
155         final FileInputStream JavaDoc keyStream = new FileInputStream JavaDoc( m_keystoreFile );
156         try
157         {
158             m_ctx = makeContext( keyStream, m_keystorePassword,
159                                  m_keyPassword, m_protocol,
160                                  m_provider, m_keystoreFormat );
161         }
162         finally
163         {
164             try
165             {
166                 keyStream.close();
167             }
168             catch( IOException JavaDoc e )
169             {
170                 // avoids hiding exceptions from makeContext
171
// by catching this IOException
172
getLogger().error( "Error keyStream.close failed", e );
173             }
174         }
175     }
176
177     public void dispose()
178     {
179         m_keystorePassword = null;
180         m_keyPassword = null;
181     }
182
183     /**
184      * Creates an SSL context which uses the keys and certificates
185      * provided by the given <tt>keyStream</tt>. For simplicity the
186      * same key stream (keystore) is used for both key and trust
187      * factory.
188      *
189      * @param keyStream to read the keys from
190      * @param keystorePassword password for the keystore, can be null
191      * if integrity verification is not desired
192      * @param keyPassword passphrase which unlocks the keys in the key file
193      * (should really be a char[] so that it can be cleaned after use)
194      * @param protocol the standard name of the requested protocol
195      * @param provider the standard name of the requested algorithm
196      * @param keystoreFormat the type of keystore
197      *
198      * @return context configured with these keys and certificates
199      * @throws IOException if files can't be read
200      * @throws GeneralSecurityException is something goes wrong inside
201      * cryptography framework
202      */

203     private static SSLContext makeContext( InputStream JavaDoc keyStream,
204                                            String JavaDoc keystorePassword,
205                                            String JavaDoc keyPassword,
206                                            String JavaDoc protocol,
207                                            String JavaDoc provider,
208                                            String JavaDoc keystoreFormat )
209         throws IOException JavaDoc, GeneralSecurityException JavaDoc
210     {
211         final KeyStore JavaDoc keystore = loadKeystore( keyStream,
212                                                 keystorePassword,
213                                                 keystoreFormat );
214         final KeyManagerFactory kmf = KeyManagerFactory.getInstance( provider );
215         // even though undocumented Sun's implementation doesn't allow
216
// null passphrases, but zero sized arrays are OK
217
final char[] passChars = ( keyPassword != null ) ?
218             keyPassword.toCharArray() : new char[ 0 ];
219         try
220         {
221             kmf.init( keystore, passChars );
222         }
223         finally
224         {
225             Arrays.fill( passChars, (char)0 );
226         }
227
228         final TrustManagerFactory tmf =
229             TrustManagerFactory.getInstance( provider );
230         tmf.init( keystore );
231
232         final SSLContext result = SSLContext.getInstance( protocol );
233         result.init( kmf.getKeyManagers(),
234                      tmf.getTrustManagers(),
235                      new java.security.SecureRandom JavaDoc() );
236         return result;
237     }
238
239     /**
240      * Builds a keystore loaded from the given stream. The passphrase
241      * is used to verify the keystore file integrity.
242      * @param keyStream to load from
243      * @param passphrase for the store integrity verification (or null if
244      * integrity check is not wanted)
245      * @param keystoreFormat the type of keystore
246      * @return loaded key store
247      * @throws IOException if file can not be read
248      * @throws GeneralSecurityException if key store can't be built
249      */

250     private static KeyStore JavaDoc loadKeystore( InputStream JavaDoc keyStream,
251                                           String JavaDoc passphrase,
252                                           String JavaDoc keystoreFormat )
253         throws GeneralSecurityException JavaDoc, IOException JavaDoc
254     {
255         final KeyStore JavaDoc ks = KeyStore.getInstance( keystoreFormat );
256
257         if( passphrase != null )
258         {
259             final char[] passChars = passphrase.toCharArray();
260             try
261             {
262                 ks.load( keyStream, passChars );
263             }
264             finally
265             {
266                 Arrays.fill( passChars, (char)0 );
267             }
268         }
269         else
270         {
271             ks.load( keyStream, null );
272         }
273
274         return ks;
275     }
276 }
277
Popular Tags