/****************************************************************************
* Copyright (C) 2012 HS Coburg.
* 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.plugins.abc4trustplugin.gui;
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.InputAPDUInfoType;
import iso.std.iso_iec._24727.tech.schema.ConnectionHandleType;
import iso.std.iso_iec._24727.tech.schema.ControlIFD;
import iso.std.iso_iec._24727.tech.schema.DIDAuthenticationDataType;
import iso.std.iso_iec._24727.tech.schema.EstablishChannel;
import iso.std.iso_iec._24727.tech.schema.EstablishChannelResponse;
import iso.std.iso_iec._24727.tech.schema.PasswordAttributesType;
import iso.std.iso_iec._24727.tech.schema.PasswordTypeType;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import org.openecard.common.ECardConstants;
import org.openecard.common.I18n;
import org.openecard.common.WSHelper;
import org.openecard.common.WSHelper.WSException;
import org.openecard.common.anytype.AuthDataMap;
import org.openecard.common.anytype.AuthDataResponse;
import org.openecard.common.apdu.ResetRetryCounter;
import org.openecard.common.apdu.exception.APDUException;
import org.openecard.common.interfaces.Dispatcher;
import org.openecard.common.interfaces.DispatcherException;
import org.openecard.common.util.ByteUtils;
import org.openecard.common.util.StringUtils;
import org.openecard.gui.StepResult;
import org.openecard.gui.definition.PasswordField;
import org.openecard.gui.definition.Step;
import org.openecard.gui.executor.ExecutionResults;
import org.openecard.gui.executor.StepAction;
import org.openecard.gui.executor.StepActionResult;
import org.openecard.gui.executor.StepActionResultStatus;
import org.openecard.ifd.scio.IFDException;
import org.openecard.ifd.scio.reader.PCSCFeatures;
import org.openecard.ifd.scio.reader.PCSCPinModify;
import org.openecard.plugins.abc4trustplugin.RecognizedState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static iso.std.iso_iec._24727.tech.schema.PasswordTypeType.ASCII_NUMERIC;
/**
* StepAction for performing PACE with the PIN and modify it.
* <br/> This StepAction tries to perform PACE with the PIN as often as possible in dependence of the retry counter.
* <br/> If PACE was executed successful the PIN is modified.
* <br/> If the retry counter reaches 1 the CANEntryStep will be shown.
*
* @author Dirk Petrautzki <petrautzki@hs-coburg.de>
*/
public class PINStepAction extends StepAction {
// translation and logger
private static final Logger logger = LoggerFactory.getLogger(PINStepAction.class);
private final I18n lang = I18n.getTranslation("pinplugin");
// translation constants
private static final String PINSTEP_TITLE = "action.changepin.userconsent.pinstep.title";
private static final String CANSTEP_TITLE = "action.changepin.userconsent.canstep.title";
private static final String ISO_8859_1 = "ISO-8859-1";
private static final String PIN_ID_PIN = "3";
private final boolean capturePin;
private final ConnectionHandleType conHandle;
private final Dispatcher dispatcher;
private int retryCounter;
private String oldPIN;
private byte[] newPIN;
private byte[] newPINRepeat;
/**
* Create a new instance of PINStepAction.
*
* @param capturePin True if the PIN has to be captured by software else false
* @param conHandle The unique ConnectionHandle for the card connection
* @param step the step this action belongs to
* @param dispatcher The Dispatcher to use
* @param retryCounter RetryCounter of the PIN
*/
public PINStepAction(boolean capturePin, ConnectionHandleType conHandle, Dispatcher dispatcher, Step step, int retryCounter) {
super(step);
this.capturePin = capturePin;
this.conHandle = conHandle;
this.dispatcher = dispatcher;
this.retryCounter = retryCounter;
}
@Override
public StepActionResult perform(Map<String, ExecutionResults> oldResults, StepResult result) {
if (result.isBack())
return new StepActionResult(StepActionResultStatus.BACK);
ExecutionResults executionResults = oldResults.get(getStepID());
verifyUserInput(executionResults);
return new StepActionResult(StepActionResultStatus.NEXT);
}
/**
* Create the step that asks the user to insert the CAN.
*
* @return Step for CAN entry
*/
private Step createCANReplacementStep() {
String title = lang.translationForKey(CANSTEP_TITLE);
RecognizedState state = RecognizedState.PIN_suspended;
CANEntryStep canStep = new CANEntryStep("can-entry", title , capturePin, state, false, false);
StepAction pinAction = new CANStepAction(capturePin, conHandle, dispatcher, canStep, state);
canStep.setAction(pinAction);
return canStep;
}
/**
* Send a ModifyPIN-PCSC-Command to the Terminal.
*
* @throws IFDException If building the Command fails.
* @throws InvocationTargetException If the ControlIFD command fails.
* @throws DispatcherException If an error in the dispatcher occurs.
*/
private void sendModifyPIN() throws IFDException, InvocationTargetException, DispatcherException {
PasswordAttributesType pwdAttr = create(true, ASCII_NUMERIC, 6, 6, 6);
pwdAttr.setPadChar(new byte[] { (byte) 0x3F });
PCSCPinModify ctrlStruct = new PCSCPinModify(pwdAttr, StringUtils.toByteArray("002C0203"));
byte[] structData = ctrlStruct.toBytes();
ControlIFD controlIFD = new ControlIFD();
controlIFD.setCommand(ByteUtils.concatenate((byte) PCSCFeatures.MODIFY_PIN_DIRECT, structData));
controlIFD.setContextHandle(conHandle.getContextHandle());
controlIFD.setIFDName(conHandle.getIFDName());
dispatcher.deliver(controlIFD);
}
/**
* Send a ResetRetryCounter-APDU.
*
* @throws APDUException if the RRC-APDU could not be sent successfully
*/
private void sendResetRetryCounter() throws APDUException {
ResetRetryCounter apdu = new ResetRetryCounter(newPIN, (byte) 0x03);
apdu.transmit(dispatcher, conHandle.getSlotHandle());
}
private static PasswordAttributesType create(boolean needsPadding, PasswordTypeType pwdType, int minLen,
int storedLen, int maxLen) {
PasswordAttributesType r = new PasswordAttributesType();
r.setMinLength(BigInteger.valueOf(minLen));
r.setStoredLength(BigInteger.valueOf(storedLen));
r.setPwdType(pwdType);
if (needsPadding) {
r.getPwdFlags().add("needs-padding");
}
r.setMaxLength(BigInteger.valueOf(maxLen));
return r;
}
/**
* Verify the input of the user (e.g. no empty mandatory fields, pin length, allowed charset).
*
* @param executionResults The results containing the OutputInfoUnits of interest.
* @return True if the input of the user could be verified, else false.
*/
private boolean verifyUserInput(ExecutionResults executionResults) {
// TODO: check pin length and possibly allowed charset with CardInfo file
PasswordField fieldOldPIN = (PasswordField) executionResults.getResult(ChangePINStep.OLD_PIN_FIELD);
PasswordField fieldNewPIN = (PasswordField) executionResults.getResult(ChangePINStep.NEW_PIN_FIELD);
/* "0024000110" << old_pin_hex.str() << "0000" << new_pin_hex.str() << "0000"; */
byte[] apdu_1 = {(byte) 0x00, (byte) 0x24, (byte) 0x00, (byte) 0x01, (byte) 0x10};
byte[] apdu_2 = {(byte) 0x00, (byte) 0x00};
byte[] apduUpdateAdmin = new byte[apdu_1.length + 6*2 + apdu_2.length*2];
byte[] new_pin_hex_byte = null;
byte[] old_pin_hex_byte = null;
try {
String new_pin_hex = String.format("%x", new BigInteger(1, fieldNewPIN.getValue().getBytes("UTF-8")));
int len = new_pin_hex.length();
new_pin_hex_byte = new byte[len / 2];
for (int i = 0; i < len; i += 2)
new_pin_hex_byte[i / 2] = (byte) ((Character.digit(new_pin_hex.charAt(i), 16) << 4)
+ Character.digit(new_pin_hex.charAt(i+1), 16));
String old_pin_hex = String.format("%x", new BigInteger(1, fieldOldPIN.getValue().getBytes("UTF-8")));
len = old_pin_hex.length();
old_pin_hex_byte = new byte[len / 2];
for (int i = 0; i < len; i += 2)
old_pin_hex_byte[i / 2] = (byte) ((Character.digit(old_pin_hex.charAt(i), 16) << 4)
+ Character.digit(old_pin_hex.charAt(i+1), 16));
} catch(java.io.UnsupportedEncodingException e) {
logger.error("Transformation from string to byte array (old/new PIN) failed.", e);
return false;
}
System.arraycopy(apdu_1, 0, apduUpdateAdmin, 0, apdu_1.length);
System.arraycopy(old_pin_hex_byte, 0, apduUpdateAdmin, apdu_1.length, old_pin_hex_byte.length);
System.arraycopy(apdu_2, 0, apduUpdateAdmin, apdu_1.length + old_pin_hex_byte.length, apdu_2.length);
System.arraycopy(new_pin_hex_byte, 0, apduUpdateAdmin, apdu_1.length + old_pin_hex_byte.length + apdu_2.length, new_pin_hex_byte.length);
System.arraycopy(apdu_2, 0, apduUpdateAdmin, apdu_1.length + old_pin_hex_byte.length + apdu_2.length + new_pin_hex_byte.length, apdu_2.length);
try {
Transmit tr = new Transmit();
tr.setSlotHandle(conHandle.getSlotHandle());
InputAPDUInfoType inputAPDU = new InputAPDUInfoType();
inputAPDU.setInputAPDU(apduUpdateAdmin);
tr.getInputAPDUInfo().add(inputAPDU);
TransmitResponse response = (TransmitResponse) dispatcher.deliver(tr);
byte[] responseAPDU = response.getOutputAPDU().get(0);
} catch (DispatcherException e) {
logger.error("Transmission of the get credentials APDU failed", e);
return false;
} catch (java.lang.reflect.InvocationTargetException e) {
logger.error("Transmission of the get credentials APDU failed", e);
return false;
}
return true;
}
/**
* Create the step that asks the user to insert the old and new pins.
*
* @return Step for PIN entry
*/
private Step createPINReplacementStep(boolean enteredWrong, boolean verifyFailed) {
String title = lang.translationForKey(PINSTEP_TITLE);
Step changePINStep = new ChangePINStep("pin-entry", title, capturePin, retryCounter, enteredWrong, verifyFailed);
StepAction pinAction = new PINStepAction(capturePin, conHandle, dispatcher, changePINStep, retryCounter);
changePINStep.setAction(pinAction);
return changePINStep;
}
}