/***********************************************************
* $Id: PKCS11SessionStore.java 42 2007-01-27 20:45:28Z wolfgang.glas $
*
* PKCS11 provider of the OpenSC project http://www.opensc-project.org
*
* Copyright (C) 2002-2006 ev-i Informationstechnologie GmbH
*
* Created: Jan 25, 2007
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
***********************************************************/
package org.opensc.pkcs11;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStore.CallbackHandlerProtection;
import java.security.KeyStore.LoadStoreParameter;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.ProtectionParameter;
import java.util.List;
import javax.security.auth.DestroyFailedException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.opensc.pkcs11.wrap.PKCS11Exception;
import org.opensc.pkcs11.wrap.PKCS11Session;
import org.opensc.pkcs11.wrap.PKCS11Slot;
import de.kp.logging.Log;
import de.kp.logging.LogFactory;
/**
* This class is used to establish a session on the token, wherever
* a SPI implementation needs to do so. It additionally implements
* the {@link LoadStoreParameter} interface in order to allow sharing
* of a session among various Spi instances.
*
* This class has been adapted to be used with android.
*
* @author wglas
* @author Stefan Krusche (krusche@dr-kruscheundpartner.de)
*
*/
public class PKCS11SessionStore implements LoadStoreParameter
{
private static Log log = LogFactory.getLog(PKCS11SessionStore.class);
PKCS11Slot slot;
PKCS11Session session;
PKCS11EventCallback cb;
CallbackHandler eventHandler;
ProtectionParameter protectionParameter;
/**
* Contruct a PKCS11SessionStore instance, which may be opened afterwards.
*/
public PKCS11SessionStore()
{
this.slot = null;
this.session = null;
this.protectionParameter = null;
this.cb = null;
}
private void changeEvent(int ev) throws IOException
{
this.cb.setEvent(ev);
if (this.eventHandler==null) return;
try
{
this.eventHandler.handle(new Callback[]{this.cb});
} catch (UnsupportedCallbackException e)
{
log.warn("PKCSEventCallback not supported by CallbackHandler ["+this.eventHandler.getClass()+"]",e);
}
}
private void eventFailed(Exception e) throws IOException
{
int fe;
switch (this.cb.getEvent())
{
default:
fe = PKCS11EventCallback.INITIALIZATION_FAILED;
break;
case PKCS11EventCallback.WAITING_FOR_CARD:
fe = PKCS11EventCallback.CARD_WAIT_FAILED;
break;
case PKCS11EventCallback.WAITING_FOR_SW_PIN:
case PKCS11EventCallback.WAITING_FOR_SW_SO_PIN:
// an IOException during software PIN entry is interpreted as an abort of
// the PIN entry process. This is done so, because there is no standard exception
// for such a situation defined.
if ((e instanceof IOException) &&
!(e instanceof PKCS11Exception))
{
fe = (this.cb.getEvent() == PKCS11EventCallback.WAITING_FOR_SW_SO_PIN) ?
PKCS11EventCallback.SO_PIN_ENTRY_ABORTED :
PKCS11EventCallback.PIN_ENTRY_ABORTED;
break;
}
// A PKCS11Exception with CKR_FUNCTION_CANCELED od CKR_CANCEL
// is interpreted as a PIN entry abort by the user.
if (e instanceof PKCS11Exception)
{
PKCS11Exception p11e = (PKCS11Exception)e;
if (p11e.getErrorCode() == PKCS11Exception.CKR_FUNCTION_CANCELED ||
p11e.getErrorCode() == PKCS11Exception.CKR_CANCEL)
{
fe = (this.cb.getEvent() == PKCS11EventCallback.WAITING_FOR_SW_SO_PIN) ?
PKCS11EventCallback.SO_PIN_ENTRY_ABORTED :
PKCS11EventCallback.PIN_ENTRY_ABORTED;
break;
}
}
fe = (this.cb.getEvent() == PKCS11EventCallback.WAITING_FOR_SW_SO_PIN) ?
PKCS11EventCallback.SO_PIN_ENTRY_FAILED :
PKCS11EventCallback.PIN_ENTRY_FAILED;
break;
case PKCS11EventCallback.HW_AUTHENTICATION_IN_PROGRESS:
case PKCS11EventCallback.SO_HW_AUTHENTICATION_IN_PROGRESS:
// A PKCS11Exception with CKR_FUNCTION_CANCELED od CKR_CANCEL
// is interpreted as an authentication abort by the user.
if (e instanceof PKCS11Exception)
{
PKCS11Exception p11e = (PKCS11Exception)e;
if (p11e.getErrorCode() == PKCS11Exception.CKR_FUNCTION_CANCELED ||
p11e.getErrorCode() == PKCS11Exception.CKR_CANCEL)
{
fe = (this.cb.getEvent() == PKCS11EventCallback.SO_HW_AUTHENTICATION_IN_PROGRESS) ?
PKCS11EventCallback.SO_AUHENTICATION_ABORTED :
PKCS11EventCallback.AUHENTICATION_ABORTED;
break;
}
}
fe = (this.cb.getEvent() == PKCS11EventCallback.SO_HW_AUTHENTICATION_IN_PROGRESS) ?
PKCS11EventCallback.SO_AUHENTICATION_FAILED :
PKCS11EventCallback.AUHENTICATION_FAILED;
break;
case PKCS11EventCallback.PIN_AUTHENTICATION_IN_PROGRESS:
fe = PKCS11EventCallback.AUHENTICATION_FAILED;
break;
case PKCS11EventCallback.SO_PIN_AUTHENTICATION_IN_PROGRESS:
fe = PKCS11EventCallback.SO_AUHENTICATION_FAILED;
break;
}
changeEvent(fe);
close();
}
/**
* Open a session by using the supplied LoadStoreParameter settings.
* You should preferrably use the implementation {@link PKCS11LoadStoreParameter}
* for opening the session. PKCS11LoadStoreParameter provides you with a full
* set of PKCS#11 related properties.
*
* @param provider The PKCS#1 provider on which to open a session.
* @param param The LoadStoreParamater set used to open a session.
*
* @see KeyStore#store(LoadStoreParameter)
*/
public void open(PKCS11Provider provider, LoadStoreParameter param) throws IOException
{
if (this.slot != null) close();
this.cb = new PKCS11EventCallback(PKCS11EventCallback.NO_EVENT);
this.eventHandler = null;
if (param instanceof PKCS11LoadStoreParameter)
this.eventHandler = ((PKCS11LoadStoreParameter)param).getEventHandler();
try
{
PKCS11LoadStoreParameter p11_param = null;
if (param instanceof PKCS11LoadStoreParameter)
p11_param = (PKCS11LoadStoreParameter) param;
// get the new slot.
PKCS11Slot s = null;
// OK, the user knows, which slot is desired.
if (p11_param != null && p11_param.getSlotId() != null)
{
s = new PKCS11Slot(provider, p11_param.getSlotId());
// is there a token ?
// no token, but user wants to wait.
if (!s.isTokenPresent() && p11_param.isWaitForSlot())
{
s.destroy();
changeEvent(PKCS11EventCallback.WAITING_FOR_CARD);
// OK, someone might argue, that we could intrduce a loop
// here in order to wait for the right token.
// For the moment, I prefer to throw an exception, if the
// user
// inserts a token into the wrong slot.
s = PKCS11Slot.waitForSlot(provider);
if (s.getId() != p11_param.getSlotId().longValue())
{
s.destroy();
throw new PKCS11Exception(
"A token has been inserted in slot number "
+ s.getId()
+ " instead of slot number "
+ p11_param.getSlotId());
}
}
}
// The user does not know, which slot is desired, so go and find
// one.
else
{
List<PKCS11Slot> slots = PKCS11Slot
.enumerateSlots(provider);
for (PKCS11Slot checkSlot : slots)
{
if (s == null && checkSlot.isTokenPresent())
s = checkSlot;
else
checkSlot.destroy();
}
// not a single token found and user wants to wait.
if (s == null && p11_param != null && p11_param.isWaitForSlot())
{
changeEvent(PKCS11EventCallback.WAITING_FOR_CARD);
s = PKCS11Slot.waitForSlot(provider);
}
}
// So, did we finally find a slot ?
if (s == null)
{
throw new PKCS11Exception(
"Could not find a valid slot with a present token.");
} else if (!s.isTokenPresent())
{
long slotId = s.getId();
s.destroy();
throw new PKCS11Exception(
"No token is present in the given slot number "
+ slotId);
}
this.slot = s;
int open_mode = PKCS11Session.OPEN_MODE_READ_ONLY;
if (p11_param != null && p11_param.isWriteEnabled())
open_mode = PKCS11Session.OPEN_MODE_READ_WRITE;
// open the session.
this.session = PKCS11Session.open(this.slot,open_mode);
if (p11_param != null)
{
this.authenticateSO(p11_param.getSOProtectionParameter());
}
this.authenticate(param.getProtectionParameter());
} catch (IOException e)
{
eventFailed(e);
throw e;
} catch (DestroyFailedException e)
{
eventFailed(e);
throw new PKCS11Exception("destroy exception caught: ",e);
}
}
/**
* Closes the session of a successful call to {@link #open(LoadStoreParameter)}
* and log any error, that might occur.
*/
public void close()
{
if (this.slot != null)
{
try
{
this.slot.destroy();
} catch (DestroyFailedException e)
{
log.warn("Cannot destroy slot:",e);
}
}
this.slot = null;
this.session = null;
this.protectionParameter = null;
this.cb = null;
this.eventHandler = null;
}
/**
* This method allows you to authenticate you against the token, if the initial call to
* {@link #open(LoadStoreParameter)} did not contain a
* ProtectionParameter. This may be use in order to search for a certificate on a token
* without entering a PIN.
*
* @param param The protection parameters used to do normal (user) authentication.
*
* @see PKCS11LoadStoreParameter#getProtectionParameter()
*/
public void authenticate(ProtectionParameter param) throws IOException
{
this.protectionParameter = param;
try
{
if (this.protectionParameter instanceof PasswordProtection)
{
changeEvent(PKCS11EventCallback.PIN_AUTHENTICATION_IN_PROGRESS);
PasswordProtection pp =
(PasswordProtection)this.protectionParameter;
this.session.loginUser(pp.getPassword());
changeEvent(PKCS11EventCallback.AUHENTICATION_SUCEEDED);
}
else if (this.protectionParameter instanceof CallbackHandlerProtection)
{
CallbackHandlerProtection cbhp =
(CallbackHandlerProtection)this.protectionParameter;
char [] pin = null;
// do authenticate with the protected auth method of the token,
// if this is possible, otherwise use the callback to authenticate.
if (this.slot.hasTokenProtectedAuthPath())
{
changeEvent(PKCS11EventCallback.HW_AUTHENTICATION_IN_PROGRESS);
}
else
{
changeEvent(PKCS11EventCallback.WAITING_FOR_SW_PIN);
CallbackHandler cbh = cbhp.getCallbackHandler();
PasswordCallback pcb = new PasswordCallback("Please enter the user pin:",false);
cbh.handle(new Callback[] { pcb });
pin = pcb.getPassword();
changeEvent(PKCS11EventCallback.PIN_AUTHENTICATION_IN_PROGRESS);
}
this.session.loginUser(pin);
changeEvent(PKCS11EventCallback.AUHENTICATION_SUCEEDED);
}
}
catch (UnsupportedCallbackException e)
{
throw new PKCS11Exception("PasswordCallback is not supported",e);
}
}
/**
* This method allows you to authenticate you against the token as the security officer
* for read/write access to the token, if the initial call to
* {@link #open(LoadStoreParameter)} did not contain a SO
* ProtectionParameter.
*
* @param param The protection parameters used to do SO (security officer) authentication.
*
* @see PKCS11LoadStoreParameter#getSOProtectionParameter()
*/
public void authenticateSO(ProtectionParameter param) throws IOException
{
try
{
if (param instanceof PasswordProtection)
{
PasswordProtection pp=(PasswordProtection)param;
changeEvent(PKCS11EventCallback.SO_PIN_AUTHENTICATION_IN_PROGRESS);
this.session.loginSO(pp.getPassword());
changeEvent(PKCS11EventCallback.SO_AUHENTICATION_SUCEEDED);
}
else if (param instanceof CallbackHandlerProtection)
{
CallbackHandlerProtection cbhp=(CallbackHandlerProtection)param;
char [] pin = null;
// do authenticate with the protected auth method of the token,
// if this is possible, otherwise use the callback to authenticate.
if (this.slot.hasTokenProtectedAuthPath())
{
changeEvent(PKCS11EventCallback.SO_HW_AUTHENTICATION_IN_PROGRESS);
}
else
{
changeEvent(PKCS11EventCallback.WAITING_FOR_SW_SO_PIN);
CallbackHandler cbh = cbhp.getCallbackHandler();
PasswordCallback pcb = new PasswordCallback("Please enter the SO pin:",false);
cbh.handle(new Callback[] { pcb });
pin = pcb.getPassword();
changeEvent(PKCS11EventCallback.SO_PIN_AUTHENTICATION_IN_PROGRESS);
}
this.session.loginSO(pin);
changeEvent(PKCS11EventCallback.SO_AUHENTICATION_SUCEEDED);
}
}
catch(UnsupportedCallbackException e)
{
throw new PKCS11Exception("PasswordCallback is not supported",e);
}
}
/**
* @return The session opened by a successful call to
* {@link #open(LoadStoreParameter)}
*/
public PKCS11Session getSession()
{
return this.session;
}
/**
* @return The slot for which a successful call to
* {@link #open(LoadStoreParameter)} opened a session.
*/
public PKCS11Slot getSlot()
{
return this.slot;
}
/* (non-Javadoc)
* @see java.security.KeyStore$LoadStoreParameter#getProtectionParameter()
*/
public ProtectionParameter getProtectionParameter()
{
return this.getProtectionParameter();
}
}