/****************************************************************************
* Copyright (C) 2012 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.ifd.scio;
import iso.std.iso_iec._24727.tech.schema.AltVUMessagesType;
import iso.std.iso_iec._24727.tech.schema.DisplayCapabilityType;
import iso.std.iso_iec._24727.tech.schema.GetIFDCapabilities;
import iso.std.iso_iec._24727.tech.schema.GetIFDCapabilitiesResponse;
import iso.std.iso_iec._24727.tech.schema.IFDCapabilitiesType;
import iso.std.iso_iec._24727.tech.schema.InputUnitType;
import iso.std.iso_iec._24727.tech.schema.KeyPadCapabilityType;
import iso.std.iso_iec._24727.tech.schema.OutputInfoType;
import iso.std.iso_iec._24727.tech.schema.PasswordAttributesType;
import iso.std.iso_iec._24727.tech.schema.PinInputType;
import iso.std.iso_iec._24727.tech.schema.Transmit;
import iso.std.iso_iec._24727.tech.schema.TransmitResponse;
import iso.std.iso_iec._24727.tech.schema.VerifyUser;
import iso.std.iso_iec._24727.tech.schema.VerifyUserResponse;
import java.math.BigInteger;
import java.util.List;
import oasis.names.tc.dss._1_0.core.schema.Result;
import org.openecard.common.ECardConstants;
import org.openecard.common.I18n;
import org.openecard.common.WSHelper;
import org.openecard.common.apdu.common.CardCommandStatus;
import org.openecard.common.util.PINUtils;
import org.openecard.common.util.UtilException;
import org.openecard.gui.ResultStatus;
import org.openecard.gui.UserConsent;
import org.openecard.gui.UserConsentNavigator;
import org.openecard.gui.definition.PasswordField;
import org.openecard.gui.definition.Step;
import org.openecard.gui.definition.Text;
import org.openecard.gui.definition.UserConsentDescription;
import org.openecard.gui.executor.ExecutionEngine;
import org.openecard.gui.executor.StepAction;
import org.openecard.ifd.scio.wrapper.SCTerminal;
import org.openecard.ifd.scio.wrapper.SCWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Tobias Wich <tobias.wich@ecsec.de>
*/
class AbstractTerminal {
private static final Logger _logger = LoggerFactory.getLogger(AbstractTerminal.class);
private final I18n lang = I18n.getTranslation("pace");
private final IFD ifd;
private final SCWrapper scwrapper;
private final UserConsent gui;
private final byte[] ctxHandle;
private final BigInteger displayIdx;
private IFDCapabilitiesType capabilities;
private Boolean canBeep = null;
private Boolean canBlink = null;
private Boolean canDisplay = null;
private Boolean canEnter = null;
private BigInteger keyIdx = null;
public AbstractTerminal(IFD ifd, SCWrapper scwrapper, UserConsent gui, byte[] ctxHandle, BigInteger displayIdx) {
this.ifd = ifd;
this.scwrapper = scwrapper;
this.gui = gui;
this.ctxHandle = ctxHandle;
this.displayIdx = displayIdx;
}
public void output(String ifdName, OutputInfoType outInfo) throws IFDException {
getCapabilities(ifdName);
// extract values from outInfo for convenience
BigInteger didx = outInfo.getDisplayIndex();
if (didx == null) {
didx = BigInteger.valueOf(0);
}
String msg = outInfo.getMessage();
BigInteger timeout = outInfo.getTimeout();
Boolean acoustic = outInfo.isAcousticalSignal();
if (acoustic == null) {
acoustic = Boolean.FALSE;
}
Boolean optic = outInfo.isOpticalSignal();
if (optic == null) {
optic = Boolean.FALSE;
}
if (acoustic.booleanValue()) {
if (canBeep() || isVirtual()) {
beep();
} else {
IFDException ex = new IFDException("No device to output a beep available.");
_logger.warn(ex.getMessage(), ex);
throw ex;
}
}
if (optic.booleanValue()) {
if (canBlink() || isVirtual()) {
blink();
} else {
IFDException ex = new IFDException("No device to output a blink available.");
_logger.warn(ex.getMessage(), ex);
throw ex;
}
}
if (msg != null) {
if (canDisplay() || isVirtual()) {
display(msg, timeout);
} else {
IFDException ex = new IFDException("No device to output a message available.");
_logger.warn(ex.getMessage(), ex);
throw ex;
}
}
}
public VerifyUserResponse verifyUser(VerifyUser verify) throws IFDException {
byte[] handle = verify.getSlotHandle();
// get capabilities
final SCTerminal term = scwrapper.getTerminal(handle);
getCapabilities(term.getName());
// check if is possible to perform PinCompare protocol
List<String> protoList = this.capabilities.getSlotCapability().get(0).getProtocol();
if (! protoList.contains(ECardConstants.Protocol.PIN_COMPARE)) {
throw new IFDException("PinCompare protocol is not supported by this IFD.");
}
// get values from requested command
InputUnitType inputUnit = verify.getInputUnit();
AltVUMessagesType allMsgs = getMessagesOrDefaults(verify.getAltVUMessages());
BigInteger firstTimeout = verify.getTimeoutUntilFirstKey();
firstTimeout = (firstTimeout == null) ? BigInteger.valueOf(60000) : firstTimeout;
BigInteger otherTimeout = verify.getTimeoutAfterFirstKey();
otherTimeout = (otherTimeout == null) ? BigInteger.valueOf(15000) : otherTimeout;
final byte[] template = verify.getTemplate();
// check which type of authentication to perform
if (inputUnit.getBiometricInput() != null) {
// TODO: implement
String msg = "Biometric authentication not supported by IFD.";
IFDException ex = new IFDException(ECardConstants.Minor.IFD.IO.UNKNOWN_INPUT_UNIT, msg);
_logger.warn(ex.getMessage(), ex);
throw ex;
} else if (inputUnit.getPinInput() != null) {
final PinInputType pinInput = inputUnit.getPinInput();
// we have a sophisticated card reader
if (canNativePinVerify(handle)) {
// create custom pinAction to submit pin to terminal
NativePinStepAction pinAction = new NativePinStepAction("enter-pin", pinInput, term, template);
// display message instructing user what to do
UserConsentDescription uc = pinUserConsent("step_pin_userconsent", pinAction);
UserConsentNavigator ucr = gui.obtainNavigator(uc);
ExecutionEngine exec = new ExecutionEngine(ucr);
// run gui
ResultStatus status = exec.process();
if (status == ResultStatus.CANCEL) {
String msg = "PIN entry cancelled by user.";
IFDException ex = new IFDException(ECardConstants.Minor.IFD.CANCELLATION_BY_USER, msg);
_logger.warn(ex.getMessage(), ex);
throw ex;
} else if (pinAction.exception != null) {
_logger.warn(pinAction.exception.getMessage(), pinAction.exception);
throw pinAction.exception;
}
// input by user
byte[] verifyResponse = pinAction.response;
// evaluate result
Result result = checkNativePinVerify(verifyResponse);
VerifyUserResponse response = WSHelper.makeResponse(VerifyUserResponse.class, result);
response.setResponse(verifyResponse);
return response;
} else if (isVirtual()) { // software method
// get pin, encode and send
int minLength = pinInput.getPasswordAttributes().getMinLength().intValue();
int maxLength = pinInput.getPasswordAttributes().getMaxLength().intValue();
UserConsentDescription uc = pinUserConsent("step_pin_userconsent", minLength, maxLength);
UserConsentNavigator ucr = gui.obtainNavigator(uc);
ExecutionEngine exec = new ExecutionEngine(ucr);
ResultStatus status = exec.process();
if (status == ResultStatus.CANCEL) {
String msg = "PIN entry cancelled by user.";
IFDException ex = new IFDException(ECardConstants.Minor.IFD.CANCELLATION_BY_USER, msg);
_logger.warn(ex.getMessage(), ex);
throw ex;
}
String rawPIN = getPinFromUserConsent(exec);
PasswordAttributesType attributes = pinInput.getPasswordAttributes();
Transmit verifyTransmit;
try {
verifyTransmit = PINUtils.buildVerifyTransmit(rawPIN, attributes, template, handle);
} catch (UtilException e) {
IFDException ex = new IFDException(e);
throw ex;
}
// send to reader
TransmitResponse transResp = ifd.transmit(verifyTransmit);
// produce messages
if (transResp.getResult().getResultMajor().equals(ECardConstants.Major.ERROR)) {
if (transResp.getOutputAPDU().isEmpty()) {
IFDException ex = new IFDException(transResp.getResult());
_logger.warn(ex.getMessage(), ex);
throw ex;
} else {
VerifyUserResponse response = WSHelper.makeResponse(VerifyUserResponse.class, transResp.getResult());
response.setResponse(transResp.getOutputAPDU().get(0));
return response;
}
} else {
VerifyUserResponse response = WSHelper.makeResponse(VerifyUserResponse.class, transResp.getResult());
response.setResponse(transResp.getOutputAPDU().get(0));
return response;
}
} else {
IFDException ex = new IFDException("No input unit available to perform PinCompare protocol.");
_logger.warn(ex.getMessage(), ex);
throw ex;
}
} else {
IFDException ex = new IFDException(ECardConstants.Minor.IFD.IO.UNKNOWN_INPUT_UNIT, "Unsupported authentication input method requested.");
_logger.warn(ex.getMessage(), ex);
throw ex;
}
}
private static AltVUMessagesType getMessagesOrDefaults(AltVUMessagesType messages) {
AltVUMessagesType allMsgs = new AltVUMessagesType();
if (messages == null || messages.getAuthenticationRequestMessage() == null) {
allMsgs.setAuthenticationRequestMessage("Enter secret:");
} else {
allMsgs.setAuthenticationRequestMessage(messages.getAuthenticationRequestMessage());
}
if (messages == null || messages.getSuccessMessage() == null) {
allMsgs.setSuccessMessage("Secret entered successfully.");
} else {
allMsgs.setSuccessMessage(messages.getSuccessMessage());
}
if (messages == null || messages.getAuthenticationFailedMessage() == null) {
allMsgs.setAuthenticationFailedMessage("Secret not entered successfully.");
} else {
allMsgs.setAuthenticationFailedMessage(messages.getAuthenticationFailedMessage());
}
if (messages == null || messages.getRequestConfirmationMessage() == null) {
allMsgs.setRequestConfirmationMessage("Enter secret again:");
} else {
allMsgs.setRequestConfirmationMessage(messages.getRequestConfirmationMessage());
}
if (messages == null || messages.getCancelMessage() == null) {
allMsgs.setCancelMessage("Canceled secret input.");
} else {
allMsgs.setCancelMessage(messages.getCancelMessage());
}
return allMsgs;
}
private void beep() {
if (canBeep()) {
// TODO: implement
}
}
private void blink() {
if (canBlink()) {
// TODO: implement
}
}
private void display(String msg, BigInteger timeout) {
if (canDisplay()) {
// TODO: implement
}
}
private boolean canBeep() {
if (canBeep == null) {
canBeep = capabilities.isAcousticSignalUnit();
}
return canBeep.booleanValue();
}
private boolean canBlink() {
if (canBlink == null) {
canBlink = capabilities.isOpticalSignalUnit();
}
return canBlink.booleanValue();
}
private boolean canDisplay() {
if (canDisplay == null) {
canDisplay = Boolean.FALSE;
if (displayIdx == null && ! capabilities.getDisplayCapability().isEmpty()) {
canDisplay = Boolean.TRUE;
} else {
for (DisplayCapabilityType disp : capabilities.getDisplayCapability()) {
if (disp.getIndex().equals(displayIdx)) {
canDisplay = Boolean.TRUE;
break;
}
}
}
}
return canDisplay.booleanValue();
}
private DisplayCapabilityType getDisplayCapabilities() {
if (canDisplay) {
if (displayIdx == null) {
DisplayCapabilityType disp = capabilities.getDisplayCapability().get(0);
return disp;
} else {
for (DisplayCapabilityType disp : capabilities.getDisplayCapability()) {
if (disp.getIndex().equals(displayIdx)) {
return disp;
}
}
return null;
}
} else {
return null;
}
}
private boolean canEnter() {
if (canEnter == null) {
canEnter = Boolean.FALSE;
if (keyIdx == null && ! capabilities.getKeyPadCapability().isEmpty()) {
canEnter = Boolean.TRUE;
} else {
for (KeyPadCapabilityType key : capabilities.getKeyPadCapability()) {
if (key.getIndex().equals(keyIdx)) {
canEnter = Boolean.TRUE;
break;
}
}
}
}
return canEnter.booleanValue();
}
private KeyPadCapabilityType getKeypadCapabilities() {
if (canEnter) {
if (keyIdx == null) {
KeyPadCapabilityType key = capabilities.getKeyPadCapability().get(0);
return key;
} else {
for (KeyPadCapabilityType key : capabilities.getKeyPadCapability()) {
if (key.getIndex().equals(keyIdx)) {
return key;
}
}
return null;
}
} else {
return null;
}
}
private boolean isVirtual() {
return gui != null;
}
private boolean canNativePinVerify(byte[] slotHandle) {
try {
SCTerminal term = this.scwrapper.getTerminal(slotHandle);
return term.supportsPinCompare();
} catch (IFDException ex) {
return false;
}
}
private static Result checkNativePinVerify(byte[] response) {
byte sw1 = response[0];
byte sw2 = response[1];
if (sw1 == (byte)0x64) {
if (sw2 == (byte)0x00) {
return WSHelper.makeResultError(ECardConstants.Minor.IFD.TIMEOUT_ERROR, "Verify operation timed out.");
} else if (sw2 == (byte)0x01) {
return WSHelper.makeResultError(ECardConstants.Minor.IFD.CANCELLATION_BY_USER, "Verify operation was cancelled with the cancel button.");
} else if (sw2 == (byte)0x02) {
return WSHelper.makeResultUnknownError("Modify PIN operation failed because two PINs were different.");
} else if (sw2 == (byte)0x03) {
return WSHelper.makeResultUnknownError("PIN has wrong length.");
}
} else if (sw1 == (byte)0x6b) {
if (sw2 == (byte)0x80) {
return WSHelper.makeResultUnknownError("Invalid parameter passed to verify command.");
}
} else if (sw1 == (byte)0x90) {
if (sw2 == (byte)0x00) {
return WSHelper.makeResultOK();
}
}
return WSHelper.makeResultUnknownError(CardCommandStatus.getMessage(response));
}
private void getCapabilities(String ifdName) throws IFDException {
GetIFDCapabilities capabilitiesReq = new GetIFDCapabilities();
capabilitiesReq.setContextHandle(ctxHandle);
capabilitiesReq.setIFDName(ifdName);
GetIFDCapabilitiesResponse cap = ifd.getIFDCapabilities(capabilitiesReq);
Result r = cap.getResult();
if (r.getResultMajor().equals(ECardConstants.Major.ERROR)) {
IFDException ex = new IFDException(r);
_logger.warn(ex.getMessage(), ex);
throw ex;
}
this.capabilities = cap.getIFDCapabilities();
}
private UserConsentDescription pinUserConsent(String title, int minLength, int maxLength) {
UserConsentDescription uc = new UserConsentDescription(lang.translationForKey(title));
// create step
Step s = new Step("enter-pin", lang.translationForKey("step_pace_title", "PIN"));
uc.getSteps().add(s);
// add text instructing user
PasswordField i1 = new PasswordField("pin");
s.getInputInfoUnits().add(i1);
i1.setDescription("PIN:");
i1.setMinLength(minLength);
i1.setMaxLength(maxLength);
return uc;
}
private UserConsentDescription pinUserConsent(String title, StepAction action) {
UserConsentDescription uc = new UserConsentDescription(lang.translationForKey(title));
// create step
Step s = new Step("enter-pin", lang.translationForKey("step_pace_title", "PIN"));
s.setAction(action);
uc.getSteps().add(s);
s.setInstantReturn(true);
// add text instructing user
Text i1 = new Text();
s.getInputInfoUnits().add(i1);
i1.setText(lang.translationForKey("step_pace_native_description", "PIN"));
return uc;
}
private static String getPinFromUserConsent(ExecutionEngine response) {
PasswordField p = (PasswordField) response.getResults().get("enter-pin").getResult("pin");
return p.getValue();
}
}