All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.devnied.emvnfccard.parser.EmvTemplate Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2019 MILLAU Julien
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.devnied.emvnfccard.parser;

import com.github.devnied.emvnfccard.enums.CommandEnum;
import com.github.devnied.emvnfccard.enums.EmvCardScheme;
import com.github.devnied.emvnfccard.exception.CommunicationException;
import com.github.devnied.emvnfccard.iso7816emv.EmvTags;
import com.github.devnied.emvnfccard.iso7816emv.ITerminal;
import com.github.devnied.emvnfccard.iso7816emv.TLV;
import com.github.devnied.emvnfccard.iso7816emv.impl.DefaultTerminalImpl;
import com.github.devnied.emvnfccard.model.Application;
import com.github.devnied.emvnfccard.model.EmvCard;
import com.github.devnied.emvnfccard.model.enums.CardStateEnum;
import com.github.devnied.emvnfccard.parser.impl.EmvParser;
import com.github.devnied.emvnfccard.parser.impl.GeldKarteParser;
import com.github.devnied.emvnfccard.parser.impl.ProviderWrapper;
import com.github.devnied.emvnfccard.utils.*;
import fr.devnied.bitlib.BytesUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Emv Template.
* Class used to detect the EMV template of the card and select the right parser * * @author MILLAU Julien * */ public class EmvTemplate { /** * Class Logger */ private static final Logger LOGGER = LoggerFactory.getLogger(EmvTemplate.class); /** * Max record for SFI */ public static final int MAX_RECORD_SFI = 16; /** * PPSE directory "2PAY.SYS.DDF01" */ private static final byte[] PPSE = "2PAY.SYS.DDF01".getBytes(); /** * PSE directory "1PAY.SYS.DDF01" */ private static final byte[] PSE = "1PAY.SYS.DDF01".getBytes(); /** * EMV Terminal */ private ITerminal terminal; /** * Provider */ private IProvider provider; /** * Parser list */ private List parsers; /** * Config */ private Config config; /** * Card data */ private EmvCard card; /** * Create builder * * @return a new instance of builder */ public static Builder Builder() { return new Builder(); } /** * Create a new Config * * @return a new instance of config */ public static Config Config() { return new Config(); } /** * Build a new Config. */ public static class Config { /** * use contact less mode */ public boolean contactLess = true; /** * Boolean to indicate if the parser need to read transaction history */ public boolean readTransactions = true; /** * Boolean used to indicate if you want to read all card aids */ public boolean readAllAids = true; /** * Boolean used to indicate if you want to extract ATS or ATR */ public boolean readAt = true; /** * Boolean used to indicate if you want to read CPLC data */ public boolean readCplc = false; /** * Boolean used to indicate to not add provided parser implementation */ public boolean removeDefaultParsers; /** * Package private. Use {@link #Builder()} to build a new one * */ Config() { } /** * Setter for the field contactLess (default true) * * @param contactLess * the contactLess to set * @return the config instance */ public Config setContactLess(final boolean contactLess) { this.contactLess = contactLess; return this; } /** * Setter for the field readTransactions (default true) * * @param readTransactions * the readTransactions to set * @return the config instance */ public Config setReadTransactions(final boolean readTransactions) { this.readTransactions = readTransactions; return this; } /** * Setter for the field readAllAids (default true) * * @param readAllAids * the readAllAids to set * @return the config instance */ public Config setReadAllAids(final boolean readAllAids) { this.readAllAids = readAllAids; return this; } /** * Setter for the field removeDefaultParsers (default false) * * @param removeDefaultParsers * the removeDefaultParsers to set * @return the config instance */ public Config setRemoveDefaultParsers(boolean removeDefaultParsers) { this.removeDefaultParsers = removeDefaultParsers; return this; } /** * Setter for the field readAt (default true) * * @param readAt * the readAt to set * @return the config instance */ public Config setReadAt(boolean readAt) { this.readAt = readAt; return this; } /** * Setter for the field readCplc (default true) * * @param readCplc * the readCplc to set * @return the config instance */ public Config setReadCplc(boolean readCplc) { this.readCplc = readCplc; return this; } } /** * Build a new {@link EmvTemplate}. *

* Calling {@link #setProvider} is required before calling {@link #build()}. * All other methods are optional. */ public static class Builder { private IProvider provider; private ITerminal terminal; private Config config; /** * Package private. Use {@link #Builder()} to build a new one * */ Builder() { } /** * Setter for the field provider * * @param provider * the provider to set * @return the config instance */ public Builder setProvider(final IProvider provider) { this.provider = provider; return this; } /** * Setter for the field terminal * * @param terminal * the terminal to set * @return the config instance */ public Builder setTerminal(final ITerminal terminal) { this.terminal = terminal; return this; } /** * Setter for the field config * * @param config * the config to set * @return the config instance */ public Builder setConfig(Config config) { this.config = config; return this; } /** Create the {@link EmvTemplate} instances. */ public EmvTemplate build() { if (provider == null) { throw new IllegalArgumentException("Provider may not be null."); } // Set default terminal implementation if (terminal == null) { terminal = new DefaultTerminalImpl(); } return new EmvTemplate(provider, terminal, config); } } /** * Call {@link EmvParser.build()} to create an new instance * * @param pProvider * provider to launch command and communicate with the card * @param pTerminal * terminal data * @param pConfig * parser configuration (Default configuration used if null) */ private EmvTemplate(final IProvider pProvider, final ITerminal pTerminal, final Config pConfig) { provider = new ProviderWrapper(pProvider); terminal = pTerminal; config = pConfig; if (config == null) { config = Config(); } parsers = new ArrayList(); if (!config.removeDefaultParsers) { addDefaultParsers(); } card = new EmvCard(); } /** * Add default parser implementation */ private void addDefaultParsers() { parsers.add(new GeldKarteParser(this)); parsers.add(new EmvParser(this)); } /** * Method used to add a list of parser to the current EMV template * * @param pParsers * parser implementation to add * @return current EmvTemplate */ public EmvTemplate addParsers(final IParser... pParsers) { if (pParsers != null) { for (IParser parser : pParsers) { parsers.add(0, parser); } } return this; } /** * Method used to read public data from EMV card * * @return data read from card or null if any provider match the card type * @throws CommunicationException communication error */ public EmvCard readEmvCard() throws CommunicationException { // Read CPLC Infos if (config.readCplc){ readCPLCInfos(); } // Update ATS or ATR if (config.readAt){ card.setAt(BytesUtils.bytesToStringNoSpace(provider.getAt())); card.setAtrDescription(config.contactLess ? AtrUtils.getDescriptionFromAts(card.getAt()) : AtrUtils.getDescription(card.getAt())); } // use PSE first if (!readWithPSE()) { // Find with AID readWithAID(); } return card; } /** * * Try to read generic infos about the SmartCard as defined in the * "GlobalPlatform Card Specification" (GPCS). * @throws CommunicationException communication error * */ protected void readCPLCInfos() throws CommunicationException { card.setCplc(CPLCUtils.parse(provider.transceive(new CommandApdu(CommandEnum.GET_DATA, 0x9F, 0x7F, null, 0).toBytes()))); } /** * Read EMV card with Payment System Environment or Proximity Payment System * Environment * * @return true is succeed false otherwise * @throws CommunicationException communication error */ protected boolean readWithPSE() throws CommunicationException { boolean ret = false; if (LOGGER.isDebugEnabled()) { LOGGER.debug("Try to read card with Payment System Environment"); } // Select the payment environment PPSE or PSE directory byte[] data = selectPaymentEnvironment(); if (ResponseUtils.isSucceed(data)) { // Parse FCI Template card.getApplications().addAll(parseFCIProprietaryTemplate(data)); Collections.sort(card.getApplications()); // For each application for (Application app : card.getApplications()) { boolean status = false; String applicationAid = BytesUtils.bytesToStringNoSpace(app.getAid()); for (IParser impl : parsers) { if (impl.getId() != null && impl.getId().matcher(applicationAid).matches()) { status = impl.parse(app); break; } } if (!ret && status) { ret = status; if (!config.readAllAids) { break; } } } if (!ret) { card.setState(CardStateEnum.LOCKED); } } else if (LOGGER.isDebugEnabled()) { LOGGER.debug((config.contactLess ? "PPSE" : "PSE") + " not found -> Use kown AID"); } return ret; } /** * Method used to parse FCI Proprietary Template * * @param pData * data to parse * @return the list of EMV application in the card * @throws CommunicationException communication error */ protected List parseFCIProprietaryTemplate(final byte[] pData) throws CommunicationException { List ret = new ArrayList(); // Get SFI byte[] data = TlvUtil.getValue(pData, EmvTags.SFI); // Check SFI if (data != null) { int sfi = BytesUtils.byteArrayToInt(data); if (LOGGER.isDebugEnabled()) { LOGGER.debug("SFI found:" + sfi); } // For each records for (int rec = 0; rec < MAX_RECORD_SFI; rec++) { data = provider.transceive(new CommandApdu(CommandEnum.READ_RECORD, rec, sfi << 3 | 4, 0).toBytes()); // Check response if (ResponseUtils.isSucceed(data)) { // Get applications Tags ret.addAll(getApplicationTemplate(data)); } else { // No more records break; } } } else { // Read Application template ret.addAll(getApplicationTemplate(pData)); if (LOGGER.isDebugEnabled()) { LOGGER.debug("(FCI) Issuer Discretionary Data is already present"); } } return ret; } /** * Method used to get the application list, if the Kernel Identifier is * defined,
* this value need to be appended to the ADF Name in the data field of
* the SELECT command. * * @param pData * FCI proprietary template data * @return the application data (Aid,extended Aid, ...) */ protected List getApplicationTemplate(final byte[] pData) { List ret = new ArrayList(); // Search Application template List listTlv = TlvUtil.getlistTLV(pData, EmvTags.APPLICATION_TEMPLATE); // For each application template for (TLV tlv : listTlv) { Application application = new Application(); // Get AID, Kernel_Identifier and application label List listTlvData = TlvUtil.getlistTLV(tlv.getValueBytes(), EmvTags.AID_CARD, EmvTags.APPLICATION_LABEL, EmvTags.APPLICATION_PRIORITY_INDICATOR); // For each data for (TLV data : listTlvData) { if (data.getTag() == EmvTags.APPLICATION_PRIORITY_INDICATOR) { application.setPriority(BytesUtils.byteArrayToInt(data.getValueBytes())); } else if (data.getTag() == EmvTags.APPLICATION_LABEL) { application.setApplicationLabel(new String(data.getValueBytes())); } else { application.setAid(data.getValueBytes()); ret.add(application); } } } return ret; } /** * Read EMV card with AID */ protected void readWithAID() throws CommunicationException { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Try to read card with AID"); } // Test each card from know EMV AID Application app = new Application(); for (EmvCardScheme type : EmvCardScheme.values()) { for (byte[] aid : type.getAidByte()) { app.setAid(aid); app.setApplicationLabel(type.getName()); String applicationAid = BytesUtils.bytesToStringNoSpace(aid); for (IParser impl : parsers) { if (impl.getId() != null && impl.getId().matcher(applicationAid).matches() && impl.parse(app)) { // Remove previously added Application template card.getApplications().clear(); // Add Application card.getApplications().add(app); return; } } } } } /** * Method used to select payment environment PSE or PPSE * * @return response byte array * @throws CommunicationException communication error */ protected byte[] selectPaymentEnvironment() throws CommunicationException { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Select " + (config.contactLess ? "PPSE" : "PSE") + " Application"); } // Select the PPSE or PSE directory return provider.transceive(new CommandApdu(CommandEnum.SELECT, config.contactLess ? PPSE : PSE, 0).toBytes()); } /** * Method used to get the field card * * @return the card */ public EmvCard getCard() { return card; } /** * Get the field provider * * @return the provider */ public IProvider getProvider() { return provider; } /** * Get the field config * * @return the config */ public Config getConfig() { return config; } /** * Get the field terminal * * @return the terminal */ public ITerminal getTerminal() { return terminal; } /** * Get the field parsers * * @return the parsers */ public List getParsers() { return Collections.unmodifiableList(parsers); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy