/*
* Copyright (c) 2007, Fraunhofer-Gesellschaft
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* (1) Redistributions of source code must retain the above copyright
* notice, this list of conditions and the disclaimer at the end.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* (2) Neither the name of Fraunhofer nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* DISCLAIMER
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package org.ogf.graap.wsag.api.security;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;
import org.ogf.graap.wsag.api.configuration.WSAG4JConfiguration;
/**
* KeystoreLoginModule
*
* @author Oliver Waeldrich
*
*/
public class KeystoreLoginModule
implements LoginModule
{
private Subject klmSubject;
private CallbackHandler cbHandler;
// private Map sharedState;
@SuppressWarnings( "rawtypes" )
private Map klmOptions;
// variables related to access the keystore
private KeyStore keystore;
private String keystoreType;
private String keystoreFile;
private String keystorePassword;
private String alias;
private String privateKeyPassword;
private String truststoreType;
private String truststoreFile;
private String truststorePassword;
// variables related to user authentication
private X500Principal userPrincipal;
// variables related to login module steering
private boolean login = false;
private boolean commit = false;
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
* javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
*/
@SuppressWarnings( "rawtypes" )
public void initialize( Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options )
{
this.klmSubject = subject;
this.cbHandler = callbackHandler;
// this.sharedState = sharedState;
this.klmOptions = options;
initializeOptions();
}
private void initializeOptions()
{
keystoreFile = (String) klmOptions.get( "keyStoreURL" );
keystoreType = (String) klmOptions.get( "keyStoreType" );
alias = (String) klmOptions.get( "keyStoreAlias" );
truststoreFile = (String) klmOptions.get( "trustStoreURL" );
truststoreType = (String) klmOptions.get( "trustStoreType" );
//
// processing of the options
//
// keystoreFile = resolveKeystoreURL(keystoreFile);
// truststoreFile = resolveKeystoreURL(truststoreFile);
keystoreType = ( keystoreType == null ) ? "JKS" : keystoreType;
truststoreType = ( truststoreType == null ) ? "JKS" : truststoreType;
}
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#login()
*/
public boolean login() throws LoginException
{
KeystoreCallback ksCallback = new KeystoreCallback();
Callback[] callbacks = new Callback[] { ksCallback };
// handle login callbacks
try
{
cbHandler.handle( callbacks );
}
catch ( IOException e )
{
String message = "IO error during login";
LoginException le = new LoginException( message );
le.initCause( e );
throw le;
}
catch ( UnsupportedCallbackException e )
{
String message = "Invalid callback handler. Callback not supported.";
LoginException le = new LoginException( message );
le.initCause( e );
throw le;
}
keystorePassword = ksCallback.getKeystorePassword();
truststorePassword = ksCallback.getTruststorePassword();
privateKeyPassword = ksCallback.getPrivateKeyPassword();
//
// if an empty alias is supplied, we set the alias to null
// this is for treating PKCS12 files, where certificates
// do not have a alias
//
// if ("".equals(alias)) alias = null;
if ( ( keystoreFile == null ) || ( keystorePassword == null ) || ( privateKeyPassword == null ) )
{
String message =
"Missing required parameter. "
+ "The KeystoreLoginModule requires the following parameters: "
+ "[keystoreFilename, keystorePassword, alias, privateKeyPassword]";
throw new LoginException( message );
}
loadKeyStore();
login = true;
return true;
}
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#commit()
*/
public boolean commit() throws LoginException
{
if ( !login )
{
return false;
}
PrivateKey userKey;
X500PrivateCredential userCredential;
X509Certificate[] userCertificateChain;
try
{
userCertificateChain = getCertificates( alias );
userKey = (PrivateKey) keystore.getKey( alias, privateKeyPassword.toCharArray() );
}
catch ( KeyStoreException e )
{
// thrown by keystoreManager.getCertificateByAlias(defaultAlias)[0]
String message = "Could not get default certificate from KeyStoreManager";
LoginException le = new LoginException( message );
le.initCause( e );
throw le;
}
catch ( Exception e )
{
// thrown by keystoreManager.getKeyEntry(defaultAlias)
String message = "Could not get private key from KeyStoreManager";
LoginException le = new LoginException( message );
le.initCause( e );
throw le;
}
if ( userCertificateChain == null )
{
Object[] filler = new Object[] { alias };
String message = MessageFormat.format( "No certificates found for user {0}", filler );
throw new LoginException( message );
}
userCredential = new X500PrivateCredential( userCertificateChain[0], userKey );
userPrincipal =
new X500Principal( userCredential.getCertificate().getSubjectX500Principal().getName() );
klmSubject.getPrivateCredentials().add( userCredential );
klmSubject.getPrivateCredentials().add( keystore );
klmSubject.getPrincipals().add( userPrincipal );
commit = true;
return true;
}
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#abort()
*/
public boolean abort() throws LoginException
{
if ( !login )
{
return false;
}
if ( ( login ) && ( !commit ) )
{
// login succeeded, but overall authentication failed
login = false;
klmSubject.getPrincipals().remove( userPrincipal );
// klmSubject.getPrivateCredentials().remove( keystore );
userPrincipal = null;
keystore = null;
keystoreFile = null;
keystorePassword = null;
keystoreType = null;
alias = null;
privateKeyPassword = null;
}
else
{
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#logout()
*/
public boolean logout() throws LoginException
{
klmSubject.getPrincipals().remove( userPrincipal );
klmSubject.getPrivateCredentials().remove( keystore );
login = false;
commit = false;
userPrincipal = null;
keystore = null;
keystoreFile = null;
keystorePassword = null;
keystoreType = null;
alias = null;
privateKeyPassword = null;
return true;
}
private synchronized KeyStore getKeystore() throws LoginException
{
if ( keystore == null )
{
loadKeyStore();
}
return keystore;
}
private void loadKeyStore() throws LoginException
{
try
{
String actualKSType = ( keystoreType == null ) ? KeyStore.getDefaultType() : keystoreType;
keystore = KeyStore.getInstance( actualKSType );
if ( keystoreFile == null )
{
throw new IOException( "No keystore specified by user." );
}
InputStream ksInput = WSAG4JConfiguration.findResource( keystoreFile );
keystore.load( ksInput, keystorePassword.toCharArray() );
}
catch ( KeyStoreException e )
{
throw new LoginException( e.getMessage() );
}
catch ( IOException e )
{
throw new LoginException( e.getMessage() );
}
catch ( CertificateException e )
{
throw new LoginException( e.getMessage() );
}
catch ( NoSuchAlgorithmException e )
{
throw new LoginException( e.getMessage() );
}
}
/**
* Gets the list of certificates for a given alias.
* <p/>
*
* @param ksAlias
* Lookup certificate chain for this alias
*
* @return Array of X509 certificates for this alias name, or null if this alias does not exist in the
* keystore
*
* @throws KeyStoreException
* error accessing the keystore
*
* @throws LoginException
* error logging into the keystore
*/
private X509Certificate[] getCertificates( String ksAlias ) throws KeyStoreException, LoginException
{
Certificate[] certs = null;
Certificate cert = null;
KeyStore store = getKeystore();
if ( store != null )
{
// There's a chance that there can only be a set of trust stores
certs = store.getCertificateChain( ksAlias );
if ( certs == null || certs.length == 0 )
{
// no cert chain, so lets check if getCertificate gives us a
// result.
cert = store.getCertificate( ksAlias );
}
}
if ( cert != null )
{
certs = new Certificate[] { cert };
}
else if ( certs == null )
{
// At this pont we don't have certs or a cert
return null;
}
X509Certificate[] x509certs = new X509Certificate[certs.length];
for ( int i = 0; i < certs.length; i++ )
{
x509certs[i] = (X509Certificate) certs[i];
}
return x509certs;
}
}