/*
* The MIT License
*
* Copyright 2014, 2015, 2016 Rui Martinho (rmartinho@gmail.com), António Braz (antoniocbraz@gmail.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.poreid.cc;
import org.poreid.pcscforjava.Card;
import org.poreid.pcscforjava.CardChannel;
import org.poreid.pcscforjava.CardException;
import org.poreid.pcscforjava.CommandAPDU;
import static org.poreid.pcscforjava.PCSCDefines.SCARD_RESET_CARD;
import org.poreid.pcscforjava.ResponseAPDU;
import org.poreid.Pin;
import org.poreid.TerminalFeatures;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.poreid.POReIDSmartCard;
import org.poreid.SmartCardFile;
import org.poreid.SmartCardFileCache;
import org.poreid.CertificateChainNotFound;
import org.poreid.CertificateNotFound;
import org.poreid.KeepAlive;
import org.poreid.POReIDException;
import org.poreid.PinStatus;
import org.poreid.SecurityStatusNotSatisfiedException;
import org.poreid.common.Util;
import org.poreid.dialogs.dialog.DialogController;
import org.poreid.dialogs.pindialogs.blockedpin.BlockedPinDialogController;
import org.poreid.dialogs.pindialogs.PinBlockedException;
import org.poreid.dialogs.pindialogs.PinEntryCancelledException;
import org.poreid.dialogs.pindialogs.PinTimeoutException;
import org.poreid.dialogs.pindialogs.modifypin.ModifyPinDialogController;
import org.poreid.dialogs.pindialogs.usepinpad.PinOperation;
import org.poreid.dialogs.pindialogs.verifypin.VerifyPinDialogController;
import org.poreid.dialogs.pindialogs.usepinpad.UsePinPadDialogController;
import org.poreid.dialogs.pindialogs.wrongpin.WrongPinDialogController;
/**
*
* @author POReID
*/
public abstract class POReIDCard implements POReIDSmartCard {
private final String escudoPortugues = "/org/poreid/images/escudo.png"; // icone especifico para o cc
private final String poreidKeystore = "/org/poreid/cc/keystores/poreid.cc.ks";
private final CardSpecificReferences csr;
private final Card card;
private final CardChannel channel;
private final String aid;
private String cardPan = null;
private CertificateFactory certificateFactory = null;
private final int BLOCK_SIZE_READ = 0x100;
private final int BLOCK_SIZE_WRITE = 0xF8;
protected final int NO_FCI = -1;
private SmartCardFileCache fileCache;
private final TerminalFeatures terminalFeatures;
private final Locale locale;
private final Files files;
private final ResourceBundle bundle;
private boolean otpPinChanging;
private boolean locked;
protected POReIDCard(CardSpecificReferences csr) {
this.csr = csr;
this.card = csr.getCard();
this.aid = csr.getAID();
this.locale = csr.getLocale();
this.files = new Files(csr);
this.channel = this.card.getBasicChannel();
this.terminalFeatures = TerminalFeatures.getInstance(card, csr.getCardReaderName());
this.bundle = CCConfig.getBundle(POReIDCard.class.getSimpleName(),locale);
}
protected abstract int selectFile(String fileId) throws POReIDException;
protected abstract byte[] getModifyPinAPDU(Pin pin);
protected abstract byte[] getNFillModifyPinAPDU(Pin pin, byte[][] pins);
protected abstract boolean verifyToModify();
private byte[] readBinary(int offset, int size) throws IOException, SecurityStatusNotSatisfiedException, POReIDException {
try {
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] chunk;
size = (size == NO_FCI || size > BLOCK_SIZE_READ ? BLOCK_SIZE_READ : size);
do {
CommandAPDU readBinaryApdu = new CommandAPDU(0x00, 0xB0, offset >> 8, offset & 0xFF, size);
ResponseAPDU responseApdu = channel.transmit(readBinaryApdu, true, true);
int sw = responseApdu.getSW();
if (0x6B00 == sw) {
break;
}
if (0x6982 == sw) {
throw new SecurityStatusNotSatisfiedException("Necessário fornecer pin para utilizar recurso.");
}
if (0x9000 != sw) {
throw new IOException("Código de estado não esperado: " + Integer.toHexString(responseApdu.getSW()));
}
chunk = responseApdu.getData();
data.write(chunk);
offset += chunk.length;
} while (BLOCK_SIZE_READ == chunk.length);
return data.toByteArray();
} catch (CardException ex) {
throw new POReIDException(ex);
}
}
@Override
public final boolean verifyPin(Pin pin, byte[] pinCode) throws PinTimeoutException, PinEntryCancelledException, PinBlockedException, POReIDException {
if (!CCConfig.isExternalPinCachePermitted() && !otpPinChanging){
pinCode = null;
}
return internalVerifyPin(pin, pinCode);
}
private boolean internalVerifyPin(Pin pin, byte[] pinCode) throws PinTimeoutException, PinEntryCancelledException, PinBlockedException, POReIDException {
ResponseAPDU responseApdu;
boolean pinOk = false;
int pinTriesLeft=-1;
checkPinTries(pin, pinTriesLeft);
do {
responseApdu = resolveReaderPinpadSupportVerifyPin(pin, pinCode);
switch (responseApdu.getSW()) {
case 0x9000:
pinOk = true;
break;
case 0x6400:
throw new PinTimeoutException(pin.getLabel()+ " não foi inserido dentro do tempo regular definido pelo leitor");
case 0x6401:
throw new PinEntryCancelledException("Introdução do " + pin.getLabel() + " cancelada pelo utilizador.");
case 0x6983: // Referenced PIN not successfully verified AND no subsequent tries are allowed (remaining tries counter reached 0)
case 0x6984: // Referenced PIN remaining tries counter or usage counter reached 0
case 0x6402:
BlockedPinDialogController.getInstance(pin.getLabel(), locale).displayBlockedPinDialog(csr.getStartTime());
throw new PinBlockedException("O " + pin.getLabel() + "está bloqueado.");
case 0x6403:
DialogController.getInstance(bundle.getString("pin.issue.title"), MessageFormat.format(bundle.getString("pin.issue.message"), pin.getLabel()), locale, true).displayDialog(csr.getStartTime());
throw new POReIDException("O leitor de cartões indica que o " + pin.getLabel() + "introduzido não tem o tamanho esperado.");
default:
if ((responseApdu.getSW() & (int) 0xfffffff0) == 0x63C0) {
pinTriesLeft = responseApdu.getSW2() & 0x0f;
} else {
throw new POReIDException("Código de estado não esperado: " + Integer.toHexString(responseApdu.getSW()));
}
if (WrongPinDialogController.getInstance(pin.getLabel(), pinTriesLeft, locale).displayWrongPinDialog(csr.getStartTime())) {
throw new PinEntryCancelledException("Introdução do " + pin.getLabel() + " cancelada pelo utilizador.");
}
}
} while (true != pinOk && null == pinCode);
return pinOk;
}
private void goToPinKeyPath(Pin pin) throws POReIDException {
try {
if (null != pin.getKeyPath()) {
if (channel.transmit(new CommandAPDU(Util.hexToBytes(pin.getKeyPath())),true,true).getSW() != 0x9000) {
throw new POReIDException("Erro "+pin.getLabel()+" Key Path " + pin.getKeyPath());
}
}
} catch (CardException ex) {
throw new POReIDException("Erro "+pin.getLabel()+" Key Path " + pin.getKeyPath(), ex);
}
}
private void checkPinTries(Pin pin, int pinTriesLeft) throws POReIDException, PinBlockedException {
PinStatus pinStatus;
if (-1 == pinTriesLeft) {
pinStatus = getPinStatus(pin);
if (pinStatus.isPinStatusAvailable()){
pinTriesLeft = pinStatus.availableTries();
}
}
if (0 == pinTriesLeft) {
BlockedPinDialogController.getInstance(pin.getLabel(), locale).displayBlockedPinDialog(csr.getStartTime());
throw new PinBlockedException("O " + pin.getLabel() + " encontra-se bloqueado");
}
}
private ResponseAPDU resolveReaderPinpadSupportVerifyPin(Pin pin, byte[] pinCode) throws PinEntryCancelledException, PinTimeoutException, POReIDException {
ResponseAPDU responseApdu;
if (null != pinCode && terminalFeatures.canBypassPinpad()) {
responseApdu = verifyPinWithoutPinPad(pin, pinCode);
} else {
if (terminalFeatures.isVerifyPinThroughPinpadAvailable()) {
if (terminalFeatures.isVerifyPinThroughPinpadSupported(this.getClass().getName())) {
responseApdu = verifyPinWithPinPad(pin);
} else {
if (terminalFeatures.canBypassPinpad()) {
responseApdu = verifyPinWithoutPinPad(pin, null);
} else {
DialogController.getInstance(bundle.getString("incompatible.title"), MessageFormat.format(bundle.getString("incompatible.message.verify.error"), pin.getLabel()), locale, true).displayDialog(csr.getStartTime());
throw new POReIDException("O leitor de cartões não permite a realização da operação de verificação do " + pin.getLabel());
}
}
} else {
responseApdu = verifyPinWithoutPinPad(pin, null);
}
}
return responseApdu;
}
private ResponseAPDU verifyPinWithPinPad(Pin pin) throws POReIDException {
UsePinPadDialogController dialogCtl = UsePinPadDialogController.getInstance(PinOperation.VERIFICACAO, pin, locale);
dialogCtl.displayVerifyPinPinPadDialog();
try {
return new ResponseAPDU(terminalFeatures.transmitVerifyPinDirect(CCConfig.TIMEOUT, (byte) pin.getMinLength(), (byte) pin.getMaxLength(), getVerifyPinAPDU(pin)));
} finally {
dialogCtl.disposeVerifyPinPinPadDialog();
}
}
private ResponseAPDU verifyPinWithoutPinPad(Pin pin, byte[] pinCode) throws PinEntryCancelledException, PinTimeoutException, POReIDException {
byte[] pcode = new byte[8];
byte[] internal = null;
ResponseAPDU responseApdu;
ScheduledExecutorService scheduledExecutorService;
try {
Arrays.fill(pcode, pin.getPadChar());
if (null == pinCode || 0 == pinCode.length) {
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(new KeepAlive(card), 0, 500, TimeUnit.MILLISECONDS);
if (terminalFeatures.isVerifyPinThroughPinpadAvailable() && terminalFeatures.canBypassPinpad()) {
DialogController.getInstance(bundle.getString("incompatible.title"), MessageFormat.format(bundle.getString("incompatible.message.verify.ok"), pin.getLabel()), locale, false).displayDialog(csr.getStartTime());
}
try {
internal = VerifyPinDialogController.getInstance(CCConfig.TIMEOUT, pin, locale).askForPin();
System.arraycopy(internal, 0, pcode, 0, internal.length);
} finally {
scheduledExecutorService.shutdown();
try {
scheduledExecutorService.awaitTermination(250, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignore) {
}
}
} else {
System.arraycopy(pinCode, 0, pcode, 0, pinCode.length);
}
responseApdu = channel.transmit(new CommandAPDU(0x00, 0x20, 0x00, (byte) pin.getReference(), pcode), true, true);
} catch (CardException | IllegalStateException ex) {
throw new POReIDException(ex);
} finally {
if (null != internal) {
Arrays.fill(internal, pin.getPadChar());
}
Arrays.fill(pcode, pin.getPadChar());
}
return responseApdu;
}
public final CardSpecificReferences getCardSpecificReferences(){
return csr;
}
protected final void writeFile(SmartCardFile file, byte[] data) throws POReIDException, PinEntryCancelledException, PinBlockedException, PinTimeoutException {
boolean writeComplete = false;
try {
loadData();
selectFile(file.getFileId());
do {
try {
updateBinary(0, data);
writeComplete = true;
} catch (SecurityStatusNotSatisfiedException ex) {
if (null != file.getPin()) {
verifyPin(file.getPin(), null);
selectFile(file.getFileId());
} else {
throw new POReIDException("Erro não esperado", ex);
}
}
} while (!writeComplete);
} catch (CardException | IllegalStateException ex) {
throw new POReIDException(ex);
}
}
private void updateBinary(int offset_, byte[] data) throws CardException, SecurityStatusNotSatisfiedException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int offset = offset_;
int writeSize;
int totalSize = data.length;
byte b[] = new byte[240];
do {
Arrays.fill(b, (byte)0);
writeSize = (BLOCK_SIZE_WRITE > totalSize ? totalSize : BLOCK_SIZE_WRITE) & 0xFF;
baos.write(data, offset, writeSize);
CommandAPDU updateBinaryApdu = new CommandAPDU(0x00, 0xD6, offset >> 8, offset & 0xFF, baos.toByteArray());
baos.reset();
Util.prettyPrintBytesToHex(updateBinaryApdu.getBytes());
ResponseAPDU responseApdu = channel.transmit(updateBinaryApdu, true, true);
int sw = responseApdu.getSW();
if (0x6982 == sw) {
throw new SecurityStatusNotSatisfiedException("Necessário fornecer pin para utilizar recurso.");
}
if (0x9000 != sw) {
throw new CardException("Código de estado não esperado: " + Integer.toHexString(responseApdu.getSW()));
}
offset += writeSize;
totalSize -= writeSize;
} while (BLOCK_SIZE_WRITE == writeSize);
}
final Files getFileDescription(){
return files;
}
protected final byte[] readFile(SmartCardFile file) throws PinEntryCancelledException, PinBlockedException, POReIDException, PinTimeoutException {
String idFileid;
byte[] contents = null;
int lenght_sel;
int offset, lenght;
boolean readComplete = false;
loadData();
idFileid = this.fileCache.getSCFileCacheFileName(file);
try {
if (file.isCacheable() && this.fileCache.isCached(idFileid)) {
if (file.isUpdateable()) {
selectFile(file.getFileId());
try {
contents = this.fileCache.readNCheckCacheFile(file, readBinary(file.getDiffOffset(), file.getDiffLenght()));
} catch (SecurityStatusNotSatisfiedException ignore) {
}
} else {
contents = this.fileCache.readCachedFile(idFileid);
}
if (null != contents) {
return contents;
}
}
lenght_sel = selectFile(file.getFileId());
if (-1 != file.getOffset()) {
if (-1 != file.getLenght()) {
lenght = file.getLenght();
} else {
lenght = (lenght_sel != NO_FCI ? lenght_sel - file.getOffset() : lenght_sel);
}
offset = file.getOffset();
} else {
offset = 0;
lenght = lenght_sel;
}
do {
try {
contents = readBinary(offset, lenght);
readComplete = true;
} catch (SecurityStatusNotSatisfiedException ex) {
if (null != file.getPin()) {
verifyPin(file.getPin(), null);
selectFile(file.getFileId());
} else {
throw new POReIDException(ex);
}
}
} while (!readComplete);
if (file.isCacheable()) {
this.fileCache.writeCacheFile(idFileid, contents);
}
} catch (IOException ex) {
throw new POReIDException(ex.getMessage(), ex);
}
return contents;
}
private void selectAID(String aid) throws POReIDException{
try {
if (channel.transmit(new CommandAPDU(Util.hexToBytes(aid)), true, true).getSW() != 0x9000) {
throw new POReIDException("AID " + aid + " não foi selecionado");
}
} catch (CardException ex) {
throw new POReIDException("AID " + aid + " não foi selecionado", ex);
}
}
private void loadData() throws POReIDException {
try {
beginExclusive();
if (null == this.cardPan) {
selectAID(this.aid);
this.cardPan = Util.extractFromASN1(readBinary(0, selectFile(files.EF_5032.getFileId())), 7, 8);
selectFile(files.SOD.getFileId());
this.fileCache = new SmartCardFileCache(this.cardPan, csr.getCachePreference(), readBinary(files.SOD.getDiffOffset(), files.SOD.getDiffLenght()));
}
} catch (IOException | CardException ex) {
throw new POReIDException(ex.getMessage(), ex);
} catch (SecurityStatusNotSatisfiedException ignore) {
}
}
@Override
public final X509Certificate getCertificate(SmartCardFile file) throws CertificateNotFound {
try {
if (null == this.certificateFactory){
this.certificateFactory = CertificateFactory.getInstance("X.509");
}
return (X509Certificate) this.certificateFactory.generateCertificate(new ByteArrayInputStream(readFile(file)));
} catch (PinTimeoutException | CertificateException | PinEntryCancelledException | PinBlockedException | POReIDException ex){
throw new CertificateNotFound("Certificado não encontrado",ex);
}
}
@Override
public final X509Certificate getAuthenticationCertificate() throws CertificateNotFound {
return this.getCertificate(files.AuthenticationCertificate);
}
@Override
public final X509Certificate getQualifiedSignatureCertificate() throws CertificateNotFound {
return this.getCertificate(files.QualifiedSignatureCertificate);
}
@Override
public final List<X509Certificate> getQualifiedSignatureCertificateChain() throws CertificateChainNotFound{
try {
List<X509Certificate> l;
KeyStore ks = KeyStore.getInstance("JKS");
try (InputStream input = POReIDCard.class.getResourceAsStream(poreidKeystore)){
ks.load(input, null);
}
l = (List<X509Certificate>) Util.getCertificateChain(getCertificate(files.QualifiedSignatureSubCACertificate), ks);
l.add(0,getQualifiedSignatureCertificate());
return l;
} catch (CertificateNotFound | KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException ex) {
throw new CertificateChainNotFound("Não foi possivel obter cadeia de certificados",ex);
}
}
@Override
public final List<X509Certificate> getAuthenticationCertificateChain() throws CertificateChainNotFound {
try {
List<X509Certificate> l;
KeyStore ks = KeyStore.getInstance("JKS");
try (InputStream input = POReIDCard.class.getResourceAsStream(poreidKeystore)){
ks.load(input, null);
}
l = (List<X509Certificate>) Util.getCertificateChain(getCertificate(files.AuthenticationSubCACertificate), ks);
l.add(0,getAuthenticationCertificate());
return l;
} catch (CertificateNotFound | KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException ex) {
throw new CertificateChainNotFound("Não foi possivel obter cadeia de certificados",ex);
}
}
@Override
public final List<X509Certificate> getCertificateChain(SmartCardFile file) throws CertificateChainNotFound {
throw new RuntimeException("Não implementado");
}
@Override
public final byte[] getIcon(){
try (InputStream input = POReIDCard.class.getResourceAsStream(escudoPortugues)) {
return Util.toByteArray(input);
} catch (IOException ex) {
return new byte[0];
}
}
@Override
public PinStatus getPinStatus(Pin pin) throws POReIDException{
PinStatus pinStatus;
int triesLeft;
try {
goToPinKeyPath(pin);
ResponseAPDU responseApdu = channel.transmit(new CommandAPDU(0x00, 0x20, 0x00, pin.getReference()),true, true);
switch (responseApdu.getSW()) {
case 0x6404: // Firewalled pinpad
case 0x6982: // Security condition not satisfied
case 0x6985: // Conditions of use not satisfied
case 0x6a81: // Function not supported
case 0x6d00: // Instruction code not supported or invalid
pinStatus = new PinStatus(false);
break;
case 0x9000:
pinStatus = new PinStatus(false);
break;
default:
if ((responseApdu.getSW() & (int) 0xfffffff0) == 0x63C0) {
triesLeft = responseApdu.getSW2() & 0x0f;
pinStatus = new PinStatus(triesLeft);
} else {
pinStatus = new PinStatus(false); // não é necessário bloquear a execução só porque surgiu um valor não esperado
}
}
} catch (CardException ex) {
throw new POReIDException(ex);
}
return pinStatus;
}
@Override
public void ModifyPin(Pin pin) throws PinBlockedException, PinEntryCancelledException, POReIDException {
ResponseAPDU responseApdu;
checkPinTries(pin, -1);
try {
responseApdu = resolveReaderPinpadSupportModifyPin(pin);
if (0x9000 != responseApdu.getSW()) {
throw new POReIDException("Não foi possível modificar o" + pin.getLabel() + ". Código de estado: " + Integer.toHexString(responseApdu.getSW()));
}
} catch (CardException ex) {
throw new POReIDException("Não foi possivel modificar o "+pin.getLabel(), ex);
} catch (PinTimeoutException ignore) { }
}
private ResponseAPDU resolveReaderPinpadSupportModifyPin(Pin pin) throws PinBlockedException, PinEntryCancelledException, PinTimeoutException, CardException, POReIDException {
ResponseAPDU responseApdu;
if (!getCardSpecificReferences().isEMVCAPPin(pin)) {
if (terminalFeatures.isModifyPinThroughPinpadAvailable()) {
if (terminalFeatures.isModifyPinThroughPinpadSupported(this.getClass().getName())) {
responseApdu = modifyPinWithPinPad(pin);
} else {
if (terminalFeatures.canBypassPinpad()) { //TODO: verificar se um keep alive aqui ajuda no caso de windows 8
DialogController.getInstance(bundle.getString("incompatible.title"), MessageFormat.format(bundle.getString("incompatible.message.modify.ok"), pin.getLabel()), locale, false).displayDialog();
responseApdu = modifyPinWithoutPinPad(pin);
} else {
DialogController.getInstance(bundle.getString("incompatible.title"), MessageFormat.format(bundle.getString("incompatible.message.modify.error"), pin.getLabel()), locale, true).displayDialog();
throw new POReIDException("O leitor de cartões não permite a realização da operação de modificação do " + pin.getLabel());
}
}
} else {
responseApdu = modifyPinWithoutPinPad(pin);
}
} else {
if (terminalFeatures.canBypassPinpad()) {
DialogController.getInstance(bundle.getString("emvpin.dialog.title"), MessageFormat.format(bundle.getString("emvpin.dialog.message.ok"), pin.getLabel()), locale, false).displayDialog();
responseApdu = modifyPinWithoutPinPad(pin);
} else {
DialogController.getInstance(bundle.getString("emvpin.dialog.title"), MessageFormat.format(bundle.getString("emvpin.dialog.message.error"), pin.getLabel()), locale, true).displayDialog();
throw new POReIDException("O leitor de cartões não permite a realização da operação de modificação do " + pin.getLabel());
}
}
return responseApdu;
}
private ResponseAPDU modifyPinWithPinPad(Pin pin) throws POReIDException, PinTimeoutException, PinEntryCancelledException, PinBlockedException {
ResponseAPDU responseApdu;
UsePinPadDialogController modifyDialogCtl = null;
byte[] verifyApdu = null;
boolean pinOk = false;
int pinTriesLeft;
try {
do {
modifyDialogCtl = UsePinPadDialogController.getInstance(PinOperation.MODIFICACAO, pin, locale);
modifyDialogCtl.displayVerifyPinPinPadDialog();
if (verifyToModify()) {
verifyApdu = getVerifyPinAPDU(pin);
}
responseApdu = new ResponseAPDU(terminalFeatures.transmitModifyPinDirect(CCConfig.TIMEOUT, (byte) pin.getMinLength(), (byte) pin.getMaxLength(), verifyApdu, getModifyPinAPDU(pin)));
switch (responseApdu.getSW()) {
case 0x9000:
pinOk = true;
break;
case 0x6401:
modifyDialogCtl.disposeVerifyPinPinPadDialog();
throw new PinEntryCancelledException("Introdução do " + pin.getLabel() + " cancelada pelo utilizador.");
case 0x6983:
case 0x6402:
case 0x6984:
modifyDialogCtl.disposeVerifyPinPinPadDialog();
BlockedPinDialogController.getInstance(pin.getLabel(), locale).displayBlockedPinDialog(csr.getStartTime());
throw new PinBlockedException("O " + pin.getLabel() + "está bloqueado.");
default:
modifyDialogCtl.disposeVerifyPinPinPadDialog();
if ((responseApdu.getSW() & (int) 0xfffffff0) == 0x63C0) {
pinTriesLeft = responseApdu.getSW2() & 0x0f;
} else {
throw new POReIDException("Código de estado não esperado: " + Integer.toHexString(responseApdu.getSW()));
}
if (WrongPinDialogController.getInstance(pin.getLabel(), pinTriesLeft, locale).displayWrongPinDialog(csr.getStartTime())) {
throw new PinEntryCancelledException("Introdução do " + pin.getLabel() + " cancelada pelo utilizador.");
}
}
} while (true != pinOk);
} finally {
if (null != modifyDialogCtl){
modifyDialogCtl.disposeVerifyPinPinPadDialog();
}
}
return responseApdu;
}
private ResponseAPDU modifyPinWithoutPinPad(Pin pin) throws PinBlockedException, PinEntryCancelledException, PinTimeoutException, CardException, POReIDException {
CommandAPDU cApdu;
ResponseAPDU response;
boolean pinOk;
ByteBuffer pins[];
OTP otp=null;
do {
pins = ModifyPinDialogController.getInstance(pin.getLabel(), pin.getMinLength(), pin.getMaxLength(), locale).modifyPin();
pinOk = internalVerifyPin(pin, pins[0].array());
} while (true != pinOk);
if (getCardSpecificReferences().isEMVCAPPin(pin)) {
try {
otpPinChanging = true;
otp = new OTP(this, pin, pins);
otp.doOTPPinModify();
} finally {
otpPinChanging = false;
}
}
cApdu = new CommandAPDU(getNFillModifyPinAPDU(pin, new byte[][]{pins[0].array(), pins[1].array()}));
Arrays.fill(pins[0].array(), pin.getPadChar());
Arrays.fill(pins[1].array(), pin.getPadChar());
response = channel.transmit(cApdu, true, true);
if (null != otp && 0x9000 == response.getSW()) {
otp.finish();
}
return response;
}
protected final boolean isOTPPinChanging(){
return otpPinChanging;
}
/*private boolean isOSWindows8Plus() {
String osName = System.getProperty("os.name");
return (osName.contains("Windows 8") || osName.contains("Windows 10"));
}*/
private byte[] getVerifyPinAPDU(Pin pin) {
byte pad = pin.getPadChar();
return new byte[]{0x00, 0x20, 0x00, (byte) pin.getReference(), 0x08, pad, pad, pad, pad, pad, pad, pad, pad};
}
protected void beginExclusive() throws CardException{
if (!locked){
locked = true;
card.beginExclusive();
}
}
protected boolean endExclusive() throws CardException{
boolean unlock = true;
if (locked){
locked = false;
card.endExclusive();
} else {
unlock = false;
}
return unlock;
}
@Override
public boolean isPOReIDSmartcardPresent() throws POReIDException{
try {
return csr.getTerminal().isCardPresent();
} catch (CardException ex) {
throw new POReIDException("Ocorreu um erro durante a verificação do leitor no cartão", ex);
}
}
@Override
public void close() throws POReIDException{
try {
endExclusive();
this.card.disconnect(SCARD_RESET_CARD);
} catch (CardException ex) {
throw new POReIDException("Ocorreu um erro durante a terminação da ligação com cartão", ex);
}
}
}