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.security.core.keystore;
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  import java.util.Properties;
49  
50  import javax.security.auth.Subject;
51  import javax.security.auth.callback.Callback;
52  import javax.security.auth.callback.CallbackHandler;
53  import javax.security.auth.callback.UnsupportedCallbackException;
54  import javax.security.auth.login.LoginException;
55  import javax.security.auth.spi.LoginModule;
56  import javax.security.auth.x500.X500Principal;
57  import javax.security.auth.x500.X500PrivateCredential;
58  
59  import org.apache.log4j.Logger;
60  import org.apache.ws.security.WSSecurityException;
61  import org.apache.ws.security.components.crypto.Crypto;
62  import org.apache.ws.security.components.crypto.CryptoBase;
63  import org.ogf.graap.wsag.api.configuration.WSAG4JConfiguration;
64  import org.ogf.graap.wsag.security.core.SecurityConstants;
65  import org.ogf.graap.wsag.security.core.server.Merlin;
66  
67  /**
68   * KeystoreLoginModule
69   * 
70   * @author Oliver Waeldrich
71   * 
72   */
73  public class KeystoreLoginModule implements LoginModule
74  {
75  
76      private static final Logger LOG = Logger.getLogger( KeystoreLoginModule.class );
77  
78      private Subject klmSubject;
79  
80      private CallbackHandler cbHandler;
81  
82      // private Map sharedState;
83  
84      @SuppressWarnings( "rawtypes" )
85      private Map klmOptions;
86  
87      // variables related to access the keystore
88      private KeyStore keystore;
89  
90      private String keystoreType;
91  
92      private String keystoreFile;
93  
94      private String keystorePassword;
95  
96      private String alias;
97  
98      private String privateKeyPassword;
99  
100     private String truststoreType;
101 
102     private String truststoreFile;
103 
104     private String truststorePassword;
105 
106     // variables related to user authentication
107     private Crypto userCrypto;
108 
109     private X500Principal userPrincipal;
110 
111     // variables related to login module steering
112     private boolean login = false;
113 
114     private boolean commit = false;
115 
116     /**
117      * {@inheritDoc}
118      * 
119      * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
120      *      javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
121      */
122     @SuppressWarnings( "rawtypes" )
123     public void initialize( Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options )
124     {
125         this.klmSubject = subject;
126         this.cbHandler = callbackHandler;
127         // this.sharedState = sharedState;
128         this.klmOptions = options;
129 
130         initializeOptions();
131     }
132 
133     private void initializeOptions()
134     {
135 
136         keystoreFile = (String) klmOptions.get( "keyStoreURL" );
137         keystoreType = (String) klmOptions.get( "keyStoreType" );
138         alias = (String) klmOptions.get( "keyStoreAlias" );
139 
140         truststoreFile = (String) klmOptions.get( "trustStoreURL" );
141         truststoreType = (String) klmOptions.get( "trustStoreType" );
142 
143         //
144         // processing of the options
145         //
146         // keystoreFile = resolveKeystoreURL(keystoreFile);
147         // truststoreFile = resolveKeystoreURL(truststoreFile);
148 
149         keystoreType = ( keystoreType == null ) ? "JKS" : keystoreType;
150         truststoreType = ( truststoreType == null ) ? "JKS" : truststoreType;
151     }
152 
153     /**
154      * {@inheritDoc}
155      * 
156      * @see javax.security.auth.spi.LoginModule#login()
157      */
158     public boolean login() throws LoginException
159     {
160         KeystoreCallback ksCallback = new KeystoreCallback();
161         Callback[] callbacks = new Callback[] { ksCallback };
162 
163         // handle login callbacks
164         try
165         {
166             cbHandler.handle( callbacks );
167         }
168         catch ( IOException e )
169         {
170             String message = "IO error during login";
171             LoginException le = new LoginException( message );
172             le.initCause( e );
173             throw le;
174         }
175         catch ( UnsupportedCallbackException e )
176         {
177             String message = "Invalid callback handler. Callback not supported.";
178             LoginException le = new LoginException( message );
179             le.initCause( e );
180             throw le;
181         }
182 
183         keystorePassword = ksCallback.getKeystorePassword();
184 
185         truststorePassword = ksCallback.getTruststorePassword();
186 
187         privateKeyPassword = ksCallback.getPrivateKeyPassword();
188 
189         //
190         // if an empty alias is supplied, we set the alias to null
191         // this is for treating PKCS12 files, where certificates
192         // do not have a alias
193         //
194         // if ("".equals(alias)) alias = null;
195 
196         if ( ( keystoreFile == null ) || ( keystorePassword == null ) || ( privateKeyPassword == null ) )
197         {
198 
199             String message =
200                 "Missing required parameter. "
201                     + "The KeystoreLoginModule requires the following parameters: "
202                     + "[keystoreFilename, keystorePassword, alias, privateKeyPassword]";
203 
204             throw new LoginException( message );
205         }
206 
207         loadKeyStore();
208 
209         login = true;
210 
211         return true;
212     }
213 
214     /**
215      * {@inheritDoc}
216      * 
217      * @see javax.security.auth.spi.LoginModule#commit()
218      */
219     public boolean commit() throws LoginException
220     {
221         if ( !login )
222         {
223             return false;
224         }
225 
226         PrivateKey userKey;
227         X500PrivateCredential userCredential;
228         X509Certificate[] userCertificateChain;
229 
230         try
231         {
232             userCertificateChain = getCertificates( alias );
233             userKey = (PrivateKey) keystore.getKey( alias, privateKeyPassword.toCharArray() );
234         }
235         catch ( KeyStoreException e )
236         {
237             // thrown by keystoreManager.getCertificateByAlias(defaultAlias)[0]
238             String message = "Could not get default certificate from KeyStoreManager";
239             LoginException le = new LoginException( message );
240             le.initCause( e );
241             throw le;
242         }
243         catch ( Exception e )
244         {
245             // thrown by keystoreManager.getKeyEntry(defaultAlias)
246             String message = "Could not get private key from KeyStoreManager";
247             LoginException le = new LoginException( message );
248             le.initCause( e );
249             throw le;
250         }
251 
252         if ( userCertificateChain == null )
253         {
254             Object[] filler = new Object[] { alias };
255             String message = MessageFormat.format( "No certificates found for user {0}", filler );
256             throw new LoginException( message );
257         }
258 
259         userCredential = new X500PrivateCredential( userCertificateChain[0], userKey );
260 
261         userCrypto = loadUserCrypto( userCredential, userCertificateChain, keystore );
262         userPrincipal =
263             new X500Principal( userCredential.getCertificate().getSubjectX500Principal().getName() );
264 
265         klmSubject.getPrivateCredentials().add( userCrypto );
266         klmSubject.getPrivateCredentials().add( userCredential );
267         klmSubject.getPrincipals().add( userPrincipal );
268 
269         commit = true;
270 
271         return true;
272     }
273 
274     /**
275      * {@inheritDoc}
276      * 
277      * @see javax.security.auth.spi.LoginModule#abort()
278      */
279     public boolean abort() throws LoginException
280     {
281         if ( !login )
282         {
283             return false;
284         }
285         if ( ( login ) && ( !commit ) )
286         {
287             // login succeeded, but overall authentication failed
288             login = false;
289 
290             klmSubject.getPrincipals().remove( userPrincipal );
291             klmSubject.getPublicCredentials().remove( userCrypto );
292 
293             userCrypto = null;
294             userPrincipal = null;
295 
296             keystore = null;
297             keystoreFile = null;
298             keystorePassword = null;
299             keystoreType = null;
300 
301             alias = null;
302             privateKeyPassword = null;
303         }
304         else
305         {
306             // overall authentication succeeded and commit succeeded,
307             // but someone else's commit failed
308             logout();
309         }
310 
311         return true;
312     }
313 
314     /**
315      * {@inheritDoc}
316      * 
317      * @see javax.security.auth.spi.LoginModule#logout()
318      */
319     public boolean logout() throws LoginException
320     {
321         klmSubject.getPrincipals().remove( userCrypto );
322         klmSubject.getPrincipals().remove( userPrincipal );
323 
324         login = false;
325         commit = false;
326 
327         userCrypto = null;
328         userPrincipal = null;
329 
330         keystore = null;
331         keystoreFile = null;
332         keystorePassword = null;
333         keystoreType = null;
334 
335         alias = null;
336         privateKeyPassword = null;
337 
338         return true;
339     }
340 
341     private synchronized KeyStore getKeystore() throws LoginException
342     {
343         if ( keystore == null )
344         {
345             loadKeyStore();
346         }
347         return keystore;
348     }
349 
350     private void loadKeyStore() throws LoginException
351     {
352         try
353         {
354             String actualKSType = ( keystoreType == null ) ? KeyStore.getDefaultType() : keystoreType;
355 
356             keystore = KeyStore.getInstance( actualKSType );
357 
358             if ( keystoreFile == null )
359             {
360                 throw new IOException( "No keystore specified by user." );
361             }
362 
363             InputStream ksInput = WSAG4JConfiguration.findResource( keystoreFile );
364             keystore.load( ksInput, keystorePassword.toCharArray() );
365 
366         }
367         catch ( KeyStoreException e )
368         {
369             throw new LoginException( e.getMessage() );
370         }
371         catch ( IOException e )
372         {
373             throw new LoginException( e.getMessage() );
374         }
375         catch ( CertificateException e )
376         {
377             throw new LoginException( e.getMessage() );
378         }
379         catch ( NoSuchAlgorithmException e )
380         {
381             throw new LoginException( e.getMessage() );
382         }
383 
384     }
385 
386     /**
387      * Gets the list of certificates for a given alias.
388      * <p/>
389      * 
390      * @param ksAlias
391      *            Lookup certificate chain for this alias
392      * 
393      * @return Array of X509 certificates for this alias name, or null if this alias does not exist in the
394      *         keystore
395      * 
396      * @throws KeyStoreException
397      *             error accessing the keystore
398      * 
399      * @throws LoginException
400      *             error logging into the keystore
401      */
402     private X509Certificate[] getCertificates( String ksAlias ) throws KeyStoreException, LoginException
403     {
404         Certificate[] certs = null;
405         Certificate cert = null;
406 
407         KeyStore store = getKeystore();
408 
409         if ( store != null )
410         {
411             // There's a chance that there can only be a set of trust stores
412             certs = store.getCertificateChain( ksAlias );
413             if ( certs == null || certs.length == 0 )
414             {
415                 // no cert chain, so lets check if getCertificate gives us a
416                 // result.
417                 cert = store.getCertificate( ksAlias );
418             }
419         }
420 
421         if ( cert != null )
422         {
423             certs = new Certificate[] { cert };
424         }
425         else if ( certs == null )
426         {
427             // At this pont we don't have certs or a cert
428             return null;
429         }
430 
431         X509Certificate[] x509certs = new X509Certificate[certs.length];
432         for ( int i = 0; i < certs.length; i++ )
433         {
434             x509certs[i] = (X509Certificate) certs[i];
435         }
436         return x509certs;
437     }
438 
439     private Crypto loadUserCrypto( final X500PrivateCredential privCredential,
440                                    final X509Certificate[] pubCredential, final KeyStore store )
441     {
442 
443         //
444         // create the Merlin properties
445         //
446         Properties properties = new Properties();
447         properties.setProperty( SecurityConstants.PROP_CRYPTO_PROVIDER, Merlin.class.getName() );
448 
449         properties.setProperty( SecurityConstants.PROP_KEYSTORE_TYPE, keystoreType );
450         properties.setProperty( SecurityConstants.PROP_KEYSTORE_PASS, keystorePassword );
451         properties.setProperty( SecurityConstants.PROP_KEYSTORE_ALIAS, alias );
452         properties.setProperty( SecurityConstants.PROP_KEYSTORE_ALIAS_PASS, privateKeyPassword );
453         properties.setProperty( SecurityConstants.PROP_KEYSTORE_FILE, keystoreFile );
454 
455         if ( truststoreFile != null )
456         {
457             properties.setProperty( SecurityConstants.PROP_TRUSTSTORE_FILE, truststoreFile );
458         }
459         if ( truststorePassword != null )
460         {
461             properties.setProperty( SecurityConstants.PROP_TRUSTSTORE_PASS, truststorePassword );
462         }
463         if ( truststoreType != null )
464         {
465             properties.setProperty( SecurityConstants.PROP_TRUSTSTORE_TYPE, truststoreType );
466         }
467 
468         try
469         {
470             return new Merlin( properties );
471         }
472         catch ( Exception e )
473         {
474             Object[] filler = new Object[] { e.getMessage() };
475             String message = MessageFormat.format( "Could not load user crypto. Reason: {0}", filler );
476             LOG.error( message );
477             LOG.error( "Try fallback... Does eventually not work for PKCS12 keystore." );
478         }
479 
480         //
481         // Fallback in case Merlin was not loaded
482         //
483         CryptoBase crypto = new CryptoBase()
484         {
485 
486             protected String getCryptoProvider()
487             {
488                 return null;
489             }
490 
491             public String getDefaultX509Alias()
492             {
493                 /*
494                  * The user name for signing a SOAP message is configured statically in the AXIS2
495                  * configuration of the Rampart module. By default we set it to
496                  * SecurityConstants.DEFAULT_ALIAS.
497                  */
498                 return SecurityConstants.DEFAULT_ALIAS;
499             }
500 
501             /**
502              * {@inheritDoc}
503              */
504             public PrivateKey getPrivateKey( String ksAlias, String password ) throws Exception
505             {
506                 /*
507                  * We return the private credential for requests with the SecurityConstants.DEFAULT_ALIAS,
508                  * otherwise we delegate the request to the keystore object.
509                  */
510                 if ( SecurityConstants.DEFAULT_ALIAS.equals( ksAlias ) )
511                 {
512                     return privCredential.getPrivateKey();
513                 }
514                 return (PrivateKey) keystore.getKey( ksAlias, password.toCharArray() );
515             }
516 
517             /**
518              * {@inheritDoc}
519              */
520             public X509Certificate[] getCertificates( String ksAlias ) throws WSSecurityException
521             {
522                 /*
523                  * We return the public credential for requests with the SecurityConstants.DEFAULT_ALIAS,
524                  * otherwise we delegate the request to the keystore object.
525                  */
526                 if ( SecurityConstants.DEFAULT_ALIAS.equals( ksAlias ) )
527                 {
528                     if ( pubCredential == null )
529                     {
530                         LOG.warn( "No certificate chain not provided in the login context." );
531                         return new X509Certificate[] { privCredential.getCertificate() };
532                     }
533 
534                     return pubCredential;
535                 }
536                 else
537                 {
538                     return super.getCertificates( ksAlias );
539                 }
540             }
541 
542         };
543 
544         crypto.setKeyStore( store );
545 
546         return crypto;
547     }
548 
549 }