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

org.eclipse.keyple.card.calypso.Command Maven / Gradle / Ivy

There is a newer version: 3.1.5
Show newest version
/* **************************************************************************************
 * Copyright (c) 2018 Calypso Networks Association https://calypsonet.org/
 *
 * See the NOTICE file(s) distributed with this work for additional information
 * regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the terms of the
 * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 ************************************************************************************** */
package org.eclipse.keyple.card.calypso;

import static org.eclipse.keyple.card.calypso.CalypsoCardConstant.SW_FILE_NOT_FOUND;
import static org.eclipse.keyple.card.calypso.CalypsoCardConstant.SW_RECORD_NOT_FOUND;
import static org.eclipse.keyple.card.calypso.DtoAdapters.*;

import java.util.HashMap;
import java.util.Map;
import org.eclipse.keypop.calypso.card.transaction.CryptoException;
import org.eclipse.keypop.calypso.card.transaction.CryptoIOException;
import org.eclipse.keypop.calypso.crypto.symmetric.SymmetricCryptoException;
import org.eclipse.keypop.calypso.crypto.symmetric.SymmetricCryptoIOException;
import org.eclipse.keypop.card.ApduResponseApi;

/**
 * Superclass for all card commands.
 *
 * 

It provides the generic getters to retrieve: * *

    *
  • the card command reference, *
  • the name of the command, *
  • the built {@link org.eclipse.keypop.card.spi.ApduRequestSpi}, *
  • the parsed {@link org.eclipse.keypop.card.ApduResponseApi}. *
* * @since 2.0.1 */ abstract class Command { static final byte[] APDU_RESPONSE_9000 = new byte[] {(byte) 0x90, 0x00}; /** * This Map stores expected status that could be by default initialized with sw1=90 and sw2=00 * (Success) * * @since 2.0.1 */ static final Map STATUS_TABLE; static { HashMap m = new HashMap(); m.put(0x9000, new StatusProperties("Success")); STATUS_TABLE = m; } private final CardCommandRef commandRef; private final CommandContextDto commandContext; private TransactionContextDto transactionContext; private int le; private transient String name; // NOSONAR private ApduRequestAdapter apduRequest; private ApduResponseApi apduResponse; private transient boolean isCryptoServiceSynchronized; // NOSONAR /** * Constructor dedicated for the building of referenced Calypso commands * * @param commandRef A command reference from the Calypso command table. * @param le The value of the LE field. * @param transactionContext The global transaction context common to all commands. * @param commandContext The local command context specific to each command * @since 2.0.1 */ Command( CardCommandRef commandRef, int le, TransactionContextDto transactionContext, CommandContextDto commandContext) { this.commandRef = commandRef; this.name = commandRef.getName(); this.le = le; this.transactionContext = transactionContext; this.commandContext = commandContext; } /** * Appends a string to the current name. * *

The sub name completes the name of the current command. This method must therefore only be * invoked conditionally (log level >= debug). * * @param subName The string to append. * @throws NullPointerException If the request is not set. * @since 2.0.1 */ final void addSubName(String subName) { this.name = this.name + " - " + subName; this.apduRequest.setInfo(this.name); } /** * Returns the current command identification * * @return A not null reference. * @since 2.0.1 */ final CardCommandRef getCommandRef() { return commandRef; } /** * Gets the name of this APDU command. * * @return A not empty string. * @since 2.0.1 */ final String getName() { return this.name; } /** * Sets the command {@link ApduRequestAdapter}. * * @param apduRequest The APDU request. * @since 2.0.1 */ final void setApduRequest(ApduRequestAdapter apduRequest) { this.apduRequest = apduRequest; this.apduRequest.setInfo(this.name); } /** * Sets the command {@link ApduRequestAdapter} in "best effort" mode. * * @param apduRequest The APDU request. * @since 3.0.0 */ final void setApduRequestInBestEffortMode(ApduRequestAdapter apduRequest) { setApduRequest(apduRequest); if (commandContext.isSecureSessionOpen()) { apduRequest .addSuccessfulStatusWord(SW_FILE_NOT_FOUND) .addSuccessfulStatusWord(SW_RECORD_NOT_FOUND); } } /** * Gets the {@link ApduRequestAdapter}. * * @return Null if the request is not set. * @since 2.0.1 */ final ApduRequestAdapter getApduRequest() { return apduRequest; } /** * Gets {@link ApduResponseApi} * * @return Null if the response is not set. * @since 2.0.1 */ final ApduResponseApi getApduResponse() { return apduResponse; } /** * Returns the transaction context. * * @return Null if not defined (selection process) or for legacy use (deprecated methods). * @since 2.3.2 */ final TransactionContextDto getTransactionContext() { return transactionContext; } /** * Returns the command context. * * @return Null if not defined (selection process) or for legacy use (deprecated methods). * @since 2.3.2 */ final CommandContextDto getCommandContext() { return commandContext; } /** * @param le The value of the LE field. * @since 2.3.2 */ final void setLe(int le) { this.le = le; } /** * Returns the value of the LE. * * @return 0 if LE is not set. * @since 2.3.2 */ final int getLe() { return le; } /** * Notifies that the crypto service has been synchronized. * * @since 2.3.2 */ final void confirmCryptoServiceSuccessfullySynchronized() { isCryptoServiceSynchronized = true; } /** * @return "true" if the post-processing is already done. * @since 2.3.2 */ final boolean isCryptoServiceSynchronized() { return isCryptoServiceSynchronized; } /** * Finalize the construction of the APDU request if needed. * * @since 2.3.2 */ abstract void finalizeRequest(); /** * @return "true" if the crypto service is required to finalize the construction of the request. * @since 2.3.2 */ abstract boolean isCryptoServiceRequiredToFinalizeRequest(); /** * Attempts to synchronize the crypto service before executing the finalized command on the card * and returns "true" in any of the following cases: * *

    *
  • the crypto service is not involved in the process *
  • the crypto service has been correctly synchronized *
  • the crypto service has already been synchronized *
* * @return "false" if the crypto service could not be synchronized before transmitting the * commands to the card. * @since 2.3.2 */ abstract boolean synchronizeCryptoServiceBeforeCardProcessing(); /** * Parses the APDU response, updates the card image and synchronize the crypto service if it is * involved in the process. * * @param apduResponse The APDU response. * @throws CardCommandException if status is not successful or if the length of the response is * not equal to the LE field in the request. * @since 2.3.2 */ abstract void parseResponse(ApduResponseApi apduResponse) throws CardCommandException; /** * Sets the Calypso card and invoke the {@link #setApduResponseAndCheckStatus(ApduResponseApi)} * method. * * @since 2.2.3 */ final void parseResponseForSelection(ApduResponseApi apduResponse, CalypsoCardAdapter calypsoCard) throws CardCommandException { transactionContext.setCard(calypsoCard); parseResponse(apduResponse); } /** * Updates the terminal session MAC using the parsed APDU response if the encryption is not * active. If encryption is enabled, then the session MAC has already been updated during * decryption. * * @since 2.3.2 */ final void updateTerminalSessionMacIfNeeded() { updateTerminalSessionMacIfNeeded(apduResponse.getApdu()); } /** * Updates the terminal session MAC using the provided APDU response. * * @param apduResponse The APDU response to use. * @since 2.3.2 */ final void updateTerminalSessionMacIfNeeded(byte[] apduResponse) { if (isCryptoServiceSynchronized) { return; } if (commandContext.isSecureSessionOpen()) { try { transactionContext .getSymmetricCryptoCardTransactionManagerSpi() .updateTerminalSessionMac(apduRequest.getApdu()); transactionContext .getSymmetricCryptoCardTransactionManagerSpi() .updateTerminalSessionMac(apduResponse); } catch (SymmetricCryptoException e) { throw new CryptoException(e.getMessage(), e); } catch (SymmetricCryptoIOException e) { throw new CryptoIOException(e.getMessage(), e); } } isCryptoServiceSynchronized = true; } /** * Encrypts the APDU request using the crypto service and updates the terminal session MAC if the * encryption is active. * * @since 2.3.2 */ final void encryptRequestAndUpdateTerminalSessionMacIfNeeded() { if (commandContext.isEncryptionActive()) { try { apduRequest.setApdu( transactionContext .getSymmetricCryptoCardTransactionManagerSpi() .updateTerminalSessionMac(apduRequest.getApdu())); } catch (SymmetricCryptoException e) { throw new CryptoException(e.getMessage(), e); } catch (SymmetricCryptoIOException e) { throw new CryptoIOException(e.getMessage(), e); } } } /** * Decrypts the provided APDU response using the crypto service and updates the terminal session * MAC if the encryption is active. * * @param apduResponse The APDU response to update. * @since 2.3.2 */ final void decryptResponseAndUpdateTerminalSessionMacIfNeeded(ApduResponseApi apduResponse) { if (commandContext.isEncryptionActive()) { try { byte[] decryptedApdu = transactionContext .getSymmetricCryptoCardTransactionManagerSpi() .updateTerminalSessionMac(apduResponse.getApdu()); System.arraycopy(decryptedApdu, 0, apduResponse.getApdu(), 0, decryptedApdu.length); } catch (SymmetricCryptoException e) { throw new CryptoException(e.getMessage(), e); } catch (SymmetricCryptoIOException e) { throw new CryptoIOException(e.getMessage(), e); } isCryptoServiceSynchronized = true; } } /** * Parses the response and checks the status word. * * @param apduResponse The APDU response. * @throws CardCommandException If status is not successful or if the length of the response is * not equal to the LE field in the request. * @since 2.0.1 */ final void setApduResponseAndCheckStatus(ApduResponseApi apduResponse) throws CardCommandException { this.apduResponse = apduResponse; checkStatus(); } /** * Parses the response and checks the status word in "best effort" mode. * *

Do not throw exception for "file not found" and "record not found" errors outside a secure * session. * * @param apduResponse The APDU response. * @return "false" in case of "best effort" mode and a "file not found" or a "record not found" * error occurs. In this case, the process must be stopped. * @throws CardCommandException If status is not successful and a secure session is open or the SW * is different of 6A82h and 6A83h, or if the length of the response is not equal to the LE * field in the request. * @since 2.3.2 */ final boolean setApduResponseAndCheckStatusInBestEffortMode(ApduResponseApi apduResponse) throws CardCommandException { this.apduResponse = apduResponse; try { checkStatus(); } catch (CardDataAccessException e) { if (commandContext.isSecureSessionOpen() || (apduResponse.getStatusWord() != SW_FILE_NOT_FOUND && apduResponse.getStatusWord() != SW_RECORD_NOT_FOUND)) { throw e; } return false; } return true; } /** * Returns the internal status table * * @return A not null reference * @since 2.0.1 */ Map getStatusTable() { return STATUS_TABLE; } /** * @return The properties of the result. * @throws NullPointerException If the response is not set. */ private StatusProperties getStatusWordProperties() { return getStatusTable().get(apduResponse.getStatusWord()); } /** * This method check the status word and if the length of the response is equal to the LE field in * the request.
* If status word is not referenced, then status is considered unsuccessful. * * @throws CardCommandException if status is not successful or if the length of the response is * not equal to the LE field in the request. */ private void checkStatus() throws CardCommandException { StatusProperties props = getStatusWordProperties(); if (props != null && props.isSuccessful()) { // SW is successful, then check the response length (CL-CSS-RESPLE.1) if (le != 0 && apduResponse.getDataOut().length != le) { throw new CardUnexpectedResponseLengthException( String.format( "Incorrect APDU response length (expected: %d, actual: %d)", le, apduResponse.getDataOut().length), commandRef); } // SW and response length are correct. return; } // status word is not referenced, or not successful. // exception class Class exceptionClass = props != null ? props.getExceptionClass() : null; // message String message = props != null ? props.getInformation() : "Unknown status"; // Throw the exception throw buildCommandException(exceptionClass, message); } /** * Builds a specific APDU command exception. * * @param exceptionClass the exception class. * @param message The message. * @return A not null reference. * @since 2.0.1 */ private CardCommandException buildCommandException( Class exceptionClass, String message) { CardCommandException e; if (exceptionClass == CardAccessForbiddenException.class) { e = new CardAccessForbiddenException(message, commandRef); } else if (exceptionClass == CardDataAccessException.class) { e = new CardDataAccessException(message, commandRef); } else if (exceptionClass == CardDataOutOfBoundsException.class) { e = new CardDataOutOfBoundsException(message, commandRef); } else if (exceptionClass == CardIllegalArgumentException.class) { e = new CardIllegalArgumentException(message, commandRef); } else if (exceptionClass == CardIllegalParameterException.class) { e = new CardIllegalParameterException(message, commandRef); } else if (exceptionClass == CardPinException.class) { e = new CardPinException(message, commandRef); } else if (exceptionClass == CardSecurityContextException.class) { e = new CardSecurityContextException(message, commandRef); } else if (exceptionClass == CardSecurityDataException.class) { e = new CardSecurityDataException(message, commandRef); } else if (exceptionClass == CardSessionBufferOverflowException.class) { e = new CardSessionBufferOverflowException(message, commandRef); } else if (exceptionClass == CardTerminatedException.class) { e = new CardTerminatedException(message, commandRef); } else { e = new CardUnknownStatusException(message, commandRef); } return e; } /** * This internal class provides status word properties * * @since 2.0.1 */ static class StatusProperties { private final String information; private final boolean successful; private final Class exceptionClass; /** * Creates a successful status. * * @param information the status information. * @since 2.0.1 */ StatusProperties(String information) { this.information = information; this.successful = true; this.exceptionClass = null; } /** * Creates an error status.
* If {@code exceptionClass} is null, then a successful status is created. * * @param information the status information. * @param exceptionClass the associated exception class. * @since 2.0.1 */ StatusProperties(String information, Class exceptionClass) { this.information = information; this.successful = exceptionClass == null; this.exceptionClass = exceptionClass; } /** * Gets information * * @return A nullable reference * @since 2.0.1 */ String getInformation() { return information; } /** * Gets successful indicator * * @return The successful indicator * @since 2.0.1 */ boolean isSuccessful() { return successful; } /** * Gets Exception Class * * @return A nullable reference * @since 2.0.1 */ Class getExceptionClass() { return exceptionClass; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy