/****************************************************************************
* Copyright (C) 2012-2015 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.scio;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.NoSuchAlgorithmException;
import javax.smartcardio.CardTerminals;
import javax.smartcardio.TerminalFactory;
import org.openecard.common.ifd.scio.SCIOTerminals;
import org.openecard.common.util.LinuxLibraryFinder;
import org.openecard.scio.osx.SunOSXPCSC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Proxy and abstracted Factory for SCIO PC/SC driver.
*
* @author Tobias Wich
* @author Benedikt Biallowons
*/
public class PCSCFactory implements org.openecard.common.ifd.scio.TerminalFactory {
private static final Logger logger = LoggerFactory.getLogger(PCSCFactory.class);
private static final String ALGORITHM = "PC/SC";
private final String osName;
private final String osVersion;
private TerminalFactory terminalFactory;
/**
* Default constructor with fixes for the faulty SmartcardIO library.
*
* @throws FileNotFoundException if pcsclite for Linux can't be found.
* @throws NoSuchAlgorithmException if no PC/SC provider can be found.
*/
public PCSCFactory() throws FileNotFoundException, NoSuchAlgorithmException {
osName = System.getProperty("os.name");
osVersion = System.getProperty("os.version");
if (osName.startsWith("Linux")) {
File libFile = LinuxLibraryFinder.getLibraryPath("pcsclite", "1");
System.setProperty("sun.security.smartcardio.library", libFile.getAbsolutePath());
}
loadPCSC();
}
@Override
public String getType() {
return terminalFactory.getType();
}
@Override
public SCIOTerminals terminals() {
return new PCSCTerminals(this);
}
TerminalFactory getRawFactory() {
return terminalFactory;
}
private static Integer versionCompare(String str1, String str2) {
// code taken from http://stackoverflow.com/a/6702029
String[] vals1 = str1.split("\\.");
String[] vals2 = str2.split("\\.");
int i = 0;
while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) {
i++;
}
if (i < vals1.length && i < vals2.length) {
return Integer.signum(Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i])));
}
return Integer.signum(vals1.length - vals2.length);
}
final void loadPCSC() throws NoSuchAlgorithmException {
if (osName.contains("OS X") && versionCompare(osVersion, "10.10") < 0) {
// see https://developer.apple.com/library/mac/technotes/tn2002/tn2110.html#FINDINGMAC
terminalFactory = TerminalFactory.getInstance(ALGORITHM, null, new SunOSXPCSC());
} else {
terminalFactory = TerminalFactory.getInstance(ALGORITHM, null);
}
}
void reloadPCSC() {
try {
// code taken from http://stackoverflow.com/questions/16921785/
Class pcscterminal = Class.forName("sun.security.smartcardio.PCSCTerminals");
Field contextId = pcscterminal.getDeclaredField("contextId");
contextId.setAccessible(true);
if (contextId.getLong(pcscterminal) != 0L) {
// First get a new context value
Class pcsc = Class.forName("sun.security.smartcardio.PCSC");
Method SCardEstablishContext = pcsc.getDeclaredMethod("SCardEstablishContext", Integer.TYPE);
SCardEstablishContext.setAccessible(true);
Field SCARD_SCOPE_USER = pcsc.getDeclaredField("SCARD_SCOPE_USER");
SCARD_SCOPE_USER.setAccessible(true);
long newId = ((Long) SCardEstablishContext.invoke(pcsc, SCARD_SCOPE_USER.getInt(pcsc)));
contextId.setLong(pcscterminal, newId);
// Then clear the terminals in cache
loadPCSC();
CardTerminals terminals = terminalFactory.terminals();
Field fieldTerminals = pcscterminal.getDeclaredField("terminals");
fieldTerminals.setAccessible(true);
Class classMap = Class.forName("java.util.Map");
Method clearMap = classMap.getDeclaredMethod("clear");
clearMap.invoke(fieldTerminals.get(terminals));
}
} catch (NoSuchAlgorithmException ex) {
// if it worked once it will work again
String msg = "PCSC changed it's algorithm. There is something really wrong.";
logger.error(msg, ex);
throw new RuntimeException("PCSC changed it's algorithm. There is something really wrong.");
} catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | NoSuchFieldException
| NoSuchMethodException | SecurityException ex) {
logger.error("Failed to perform reflection magic to reload TerminalFactory.", ex);
}
}
}