View Javadoc

1   /* 
2    * Copyright (c) 2007, Fraunhofer-Gesellschaft
3    * All rights reserved.
4    * 
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions are
7    * met:
8    * 
9    * (1) Redistributions of source code must retain the above copyright
10   *     notice, this list of conditions and the disclaimer at the end.
11   *     Redistributions in binary form must reproduce the above copyright
12   *     notice, this list of conditions and the following disclaimer in
13   *     the documentation and/or other materials provided with the
14   *     distribution.
15   * 
16   * (2) Neither the name of Fraunhofer nor the names of its
17   *     contributors may be used to endorse or promote products derived
18   *     from this software without specific prior written permission.
19   * 
20   * DISCLAIMER
21   * 
22   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33   *  
34   */
35  package org.ogf.graap.wsag.api.security;
36  
37  import java.io.IOException;
38  import java.io.InputStream;
39  import java.security.KeyStore;
40  import java.security.KeyStoreException;
41  import java.security.NoSuchAlgorithmException;
42  import java.security.PrivateKey;
43  import java.security.cert.Certificate;
44  import java.security.cert.CertificateException;
45  import java.security.cert.X509Certificate;
46  import java.text.MessageFormat;
47  import java.util.Map;
48  
49  import javax.security.auth.Subject;
50  import javax.security.auth.callback.Callback;
51  import javax.security.auth.callback.CallbackHandler;
52  import javax.security.auth.callback.UnsupportedCallbackException;
53  import javax.security.auth.login.LoginException;
54  import javax.security.auth.spi.LoginModule;
55  import javax.security.auth.x500.X500Principal;
56  import javax.security.auth.x500.X500PrivateCredential;
57  
58  import org.ogf.graap.wsag.api.configuration.WSAG4JConfiguration;
59  
60  /**
61   * KeystoreLoginModule
62   * 
63   * @author Oliver Waeldrich
64   * 
65   */
66  public class KeystoreLoginModule
67      implements LoginModule
68  {
69      private Subject klmSubject;
70  
71      private CallbackHandler cbHandler;
72  
73      // private Map sharedState;
74  
75      @SuppressWarnings( "rawtypes" )
76      private Map klmOptions;
77  
78      // variables related to access the keystore
79      private KeyStore keystore;
80  
81      private String keystoreType;
82  
83      private String keystoreFile;
84  
85      private String keystorePassword;
86  
87      private String alias;
88  
89      private String privateKeyPassword;
90  
91      private String truststoreType;
92  
93      private String truststoreFile;
94  
95      private String truststorePassword;
96  
97      // variables related to user authentication
98      private X500Principal userPrincipal;
99  
100     // variables related to login module steering
101     private boolean login = false;
102 
103     private boolean commit = false;
104 
105     /**
106      * {@inheritDoc}
107      * 
108      * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
109      *      javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
110      */
111     @SuppressWarnings( "rawtypes" )
112     public void initialize( Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options )
113     {
114         this.klmSubject = subject;
115         this.cbHandler = callbackHandler;
116         // this.sharedState = sharedState;
117         this.klmOptions = options;
118 
119         initializeOptions();
120     }
121 
122     private void initializeOptions()
123     {
124 
125         keystoreFile = (String) klmOptions.get( "keyStoreURL" );
126         keystoreType = (String) klmOptions.get( "keyStoreType" );
127         alias = (String) klmOptions.get( "keyStoreAlias" );
128 
129         truststoreFile = (String) klmOptions.get( "trustStoreURL" );
130         truststoreType = (String) klmOptions.get( "trustStoreType" );
131 
132         //
133         // processing of the options
134         //
135         // keystoreFile = resolveKeystoreURL(keystoreFile);
136         // truststoreFile = resolveKeystoreURL(truststoreFile);
137 
138         keystoreType = ( keystoreType == null ) ? "JKS" : keystoreType;
139         truststoreType = ( truststoreType == null ) ? "JKS" : truststoreType;
140     }
141 
142     /**
143      * {@inheritDoc}
144      * 
145      * @see javax.security.auth.spi.LoginModule#login()
146      */
147     public boolean login() throws LoginException
148     {
149         KeystoreCallback ksCallback = new KeystoreCallback();
150         Callback[] callbacks = new Callback[] { ksCallback };
151 
152         // handle login callbacks
153         try
154         {
155             cbHandler.handle( callbacks );
156         }
157         catch ( IOException e )
158         {
159             String message = "IO error during login";
160             LoginException le = new LoginException( message );
161             le.initCause( e );
162             throw le;
163         }
164         catch ( UnsupportedCallbackException e )
165         {
166             String message = "Invalid callback handler. Callback not supported.";
167             LoginException le = new LoginException( message );
168             le.initCause( e );
169             throw le;
170         }
171 
172         keystorePassword = ksCallback.getKeystorePassword();
173 
174         truststorePassword = ksCallback.getTruststorePassword();
175 
176         privateKeyPassword = ksCallback.getPrivateKeyPassword();
177 
178         //
179         // if an empty alias is supplied, we set the alias to null
180         // this is for treating PKCS12 files, where certificates
181         // do not have a alias
182         //
183         // if ("".equals(alias)) alias = null;
184 
185         if ( ( keystoreFile == null ) || ( keystorePassword == null ) || ( privateKeyPassword == null ) )
186         {
187 
188             String message =
189                 "Missing required parameter. "
190                     + "The KeystoreLoginModule requires the following parameters: "
191                     + "[keystoreFilename, keystorePassword, alias, privateKeyPassword]";
192 
193             throw new LoginException( message );
194         }
195 
196         loadKeyStore();
197 
198         login = true;
199 
200         return true;
201     }
202 
203     /**
204      * {@inheritDoc}
205      * 
206      * @see javax.security.auth.spi.LoginModule#commit()
207      */
208     public boolean commit() throws LoginException
209     {
210         if ( !login )
211         {
212             return false;
213         }
214 
215         PrivateKey userKey;
216         X500PrivateCredential userCredential;
217         X509Certificate[] userCertificateChain;
218 
219         try
220         {
221             userCertificateChain = getCertificates( alias );
222             userKey = (PrivateKey) keystore.getKey( alias, privateKeyPassword.toCharArray() );
223         }
224         catch ( KeyStoreException e )
225         {
226             // thrown by keystoreManager.getCertificateByAlias(defaultAlias)[0]
227             String message = "Could not get default certificate from KeyStoreManager";
228             LoginException le = new LoginException( message );
229             le.initCause( e );
230             throw le;
231         }
232         catch ( Exception e )
233         {
234             // thrown by keystoreManager.getKeyEntry(defaultAlias)
235             String message = "Could not get private key from KeyStoreManager";
236             LoginException le = new LoginException( message );
237             le.initCause( e );
238             throw le;
239         }
240 
241         if ( userCertificateChain == null )
242         {
243             Object[] filler = new Object[] { alias };
244             String message = MessageFormat.format( "No certificates found for user {0}", filler );
245             throw new LoginException( message );
246         }
247 
248         userCredential = new X500PrivateCredential( userCertificateChain[0], userKey );
249 
250         userPrincipal =
251             new X500Principal( userCredential.getCertificate().getSubjectX500Principal().getName() );
252 
253         klmSubject.getPrivateCredentials().add( userCredential );
254         klmSubject.getPrivateCredentials().add( keystore );
255         klmSubject.getPrincipals().add( userPrincipal );
256 
257         commit = true;
258 
259         return true;
260     }
261 
262     /**
263      * {@inheritDoc}
264      * 
265      * @see javax.security.auth.spi.LoginModule#abort()
266      */
267     public boolean abort() throws LoginException
268     {
269         if ( !login )
270         {
271             return false;
272         }
273         if ( ( login ) && ( !commit ) )
274         {
275             // login succeeded, but overall authentication failed
276             login = false;
277 
278             klmSubject.getPrincipals().remove( userPrincipal );
279             // klmSubject.getPrivateCredentials().remove( keystore );
280 
281             userPrincipal = null;
282 
283             keystore = null;
284             keystoreFile = null;
285             keystorePassword = null;
286             keystoreType = null;
287 
288             alias = null;
289             privateKeyPassword = null;
290         }
291         else
292         {
293             // overall authentication succeeded and commit succeeded,
294             // but someone else's commit failed
295             logout();
296         }
297 
298         return true;
299     }
300 
301     /**
302      * {@inheritDoc}
303      * 
304      * @see javax.security.auth.spi.LoginModule#logout()
305      */
306     public boolean logout() throws LoginException
307     {
308         klmSubject.getPrincipals().remove( userPrincipal );
309         klmSubject.getPrivateCredentials().remove( keystore );
310 
311         login = false;
312         commit = false;
313 
314         userPrincipal = null;
315 
316         keystore = null;
317         keystoreFile = null;
318         keystorePassword = null;
319         keystoreType = null;
320 
321         alias = null;
322         privateKeyPassword = null;
323 
324         return true;
325     }
326 
327     private synchronized KeyStore getKeystore() throws LoginException
328     {
329         if ( keystore == null )
330         {
331             loadKeyStore();
332         }
333         return keystore;
334     }
335 
336     private void loadKeyStore() throws LoginException
337     {
338         try
339         {
340             String actualKSType = ( keystoreType == null ) ? KeyStore.getDefaultType() : keystoreType;
341 
342             keystore = KeyStore.getInstance( actualKSType );
343 
344             if ( keystoreFile == null )
345             {
346                 throw new IOException( "No keystore specified by user." );
347             }
348 
349             InputStream ksInput = WSAG4JConfiguration.findResource( keystoreFile );
350             keystore.load( ksInput, keystorePassword.toCharArray() );
351 
352         }
353         catch ( KeyStoreException e )
354         {
355             throw new LoginException( e.getMessage() );
356         }
357         catch ( IOException e )
358         {
359             throw new LoginException( e.getMessage() );
360         }
361         catch ( CertificateException e )
362         {
363             throw new LoginException( e.getMessage() );
364         }
365         catch ( NoSuchAlgorithmException e )
366         {
367             throw new LoginException( e.getMessage() );
368         }
369 
370     }
371 
372     /**
373      * Gets the list of certificates for a given alias.
374      * <p/>
375      * 
376      * @param ksAlias
377      *            Lookup certificate chain for this alias
378      * 
379      * @return Array of X509 certificates for this alias name, or null if this alias does not exist in the
380      *         keystore
381      * 
382      * @throws KeyStoreException
383      *             error accessing the keystore
384      * 
385      * @throws LoginException
386      *             error logging into the keystore
387      */
388     private X509Certificate[] getCertificates( String ksAlias ) throws KeyStoreException, LoginException
389     {
390         Certificate[] certs = null;
391         Certificate cert = null;
392 
393         KeyStore store = getKeystore();
394 
395         if ( store != null )
396         {
397             // There's a chance that there can only be a set of trust stores
398             certs = store.getCertificateChain( ksAlias );
399             if ( certs == null || certs.length == 0 )
400             {
401                 // no cert chain, so lets check if getCertificate gives us a
402                 // result.
403                 cert = store.getCertificate( ksAlias );
404             }
405         }
406 
407         if ( cert != null )
408         {
409             certs = new Certificate[] { cert };
410         }
411         else if ( certs == null )
412         {
413             // At this pont we don't have certs or a cert
414             return null;
415         }
416 
417         X509Certificate[] x509certs = new X509Certificate[certs.length];
418         for ( int i = 0; i < certs.length; i++ )
419         {
420             x509certs[i] = (X509Certificate) certs[i];
421         }
422         return x509certs;
423     }
424 
425 }