org.eclipse.keyple.card.calypso.CommandCloseSecureSession Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of keyple-card-calypso-java-lib Show documentation
Show all versions of keyple-card-calypso-java-lib Show documentation
Keyple add-on to manage Calypso cards
/* **************************************************************************************
* 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.DtoAdapters.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.keyple.core.util.ApduUtil;
import org.eclipse.keypop.calypso.card.transaction.CardSignatureNotVerifiableException;
import org.eclipse.keypop.calypso.card.transaction.CryptoException;
import org.eclipse.keypop.calypso.card.transaction.CryptoIOException;
import org.eclipse.keypop.calypso.card.transaction.InvalidCardSignatureException;
import org.eclipse.keypop.calypso.crypto.asymmetric.AsymmetricCryptoException;
import org.eclipse.keypop.calypso.crypto.symmetric.SymmetricCryptoException;
import org.eclipse.keypop.calypso.crypto.symmetric.SymmetricCryptoIOException;
import org.eclipse.keypop.card.ApduResponseApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Builds the Close Secure Session APDU command.
*
* @since 2.0.1
*/
final class CommandCloseSecureSession extends Command {
private static final Logger logger = LoggerFactory.getLogger(CommandCloseSecureSession.class);
private static final String MSG_CARD_SESSION_MAC_NOT_VERIFIABLE =
"Unable to verify the card session MAC associated to the successfully closed secure session";
private static final String MSG_CARD_SV_MAC_NOT_VERIFIABLE =
"Unable to verify the card SV MAC associated to the SV operation";
private static final String MSG_INVALID_CARD_SESSION_MAC = "Invalid card session MAC";
private static final String MSG_INVALID_CARD_SESSION_SIGNATURE = "Invalid card session signature";
private static final CardCommandRef commandRef = CardCommandRef.CLOSE_SECURE_SESSION;
private static final Map STATUS_TABLE;
static {
Map m = new HashMap<>(Command.STATUS_TABLE);
m.put(
0x6700,
new StatusProperties(
"Lc signatureLo not supported (e.g. Lc=4 with a Revision 3.2 mode for Open Secure Session)",
CardIllegalParameterException.class));
m.put(
0x6B00,
new StatusProperties(
"P1 or P2 signatureLo not supported", CardIllegalParameterException.class));
m.put(
0x6985, new StatusProperties("No session was opened", CardAccessForbiddenException.class));
m.put(0x6988, new StatusProperties("incorrect signatureLo", CardSecurityDataException.class));
STATUS_TABLE = m;
}
private final boolean isAutoRatificationAsked;
private final boolean isAbortSecureSession;
private final int svPostponedDataIndex;
/** The postponed data. */
private final List postponedData = new ArrayList<>(0);
/**
* Constructor.
*
* @param transactionContext The global transaction context common to all commands.
* @param commandContext The local command context specific to each command.
* @param isAutoRatificationAsked "true" if the auto ratification is asked.
* @param svPostponedDataIndex The index of the SV postponed data or -1 if there is no SV
* postponed data.
* @since 2.3.2
*/
CommandCloseSecureSession(
TransactionContextDto transactionContext,
CommandContextDto commandContext,
boolean isAutoRatificationAsked,
int svPostponedDataIndex) {
super(commandRef, 0, transactionContext, commandContext);
this.isAutoRatificationAsked = isAutoRatificationAsked;
this.isAbortSecureSession = false;
this.svPostponedDataIndex = svPostponedDataIndex;
}
/**
* Instantiates a new command based on the product type of the card to generate either an "Abort
* Secure Session" command or a "Close Secure Session" in PKI mode.
*
* @param transactionContext The global transaction context common to all commands.
* @param context The command context.
* @param isAbort true for creating an abort session command.
* @since 2.3.2
*/
CommandCloseSecureSession(
TransactionContextDto transactionContext, CommandContextDto context, boolean isAbort) {
super(commandRef, 0, transactionContext, context);
this.isAutoRatificationAsked = true;
this.svPostponedDataIndex = -1;
if (transactionContext.isPkiMode()) {
// this a close in PKI mode.
// in this case, set the APDU earlier since there is no call to finalizeRequest
setApduRequest(
new ApduRequestAdapter(
ApduUtil.build(
getTransactionContext().getCard().getCardClass().getValue(),
commandRef.getInstructionByte(),
(byte) 0x00,
(byte) 0x00,
null,
isAbort ? (byte) 0 : (byte) 0x40)));
this.isAbortSecureSession = isAbort;
} else {
// this is a non PKI session abort
this.isAbortSecureSession = true;
}
}
/**
* {@inheritDoc}
*
* @since 2.3.2
*/
@Override
void finalizeRequest() {
if (isAbortSecureSession) {
// Abort secure session
// CL-CSS-ABORTCMD.1
setApduRequest(
new ApduRequestAdapter(
ApduUtil.build(
getTransactionContext().getCard().getCardClass().getValue(),
commandRef.getInstructionByte(),
(byte) 0x00,
(byte) 0x00,
null,
(byte) 0)));
} else {
// Close secure session
byte[] terminalSessionMac;
try {
terminalSessionMac =
getTransactionContext()
.getSymmetricCryptoCardTransactionManagerSpi()
.finalizeTerminalSessionMac();
} catch (SymmetricCryptoException e) {
throw new CryptoException(e.getMessage(), e);
} catch (SymmetricCryptoIOException e) {
throw new CryptoIOException(e.getMessage(), e);
}
setApduRequest(
new ApduRequestAdapter(
ApduUtil.build(
getTransactionContext().getCard().getCardClass().getValue(),
commandRef.getInstructionByte(),
isAutoRatificationAsked ? (byte) 0x80 : (byte) 0x00,
(byte) 0x00,
terminalSessionMac,
(byte) 0)));
}
}
/**
* {@inheritDoc}
*
* @since 2.3.2
*/
@Override
boolean isCryptoServiceRequiredToFinalizeRequest() {
return !isAbortSecureSession;
}
/**
* {@inheritDoc}
*
* @since 2.3.2
*/
@Override
boolean synchronizeCryptoServiceBeforeCardProcessing() {
return isAbortSecureSession;
}
/**
* {@inheritDoc}
*
* @since 2.3.2
*/
@Override
void parseResponse(ApduResponseApi apduResponse) throws CardCommandException {
if (isAbortSecureSession) {
processAbort(apduResponse);
return;
}
super.setApduResponseAndCheckStatus(apduResponse);
getTransactionContext().setSecureSessionOpen(false);
byte[] responseData = getApduResponse().getDataOut();
if (getTransactionContext().isPkiMode()) {
parseResponseInAsymmetricMode(responseData);
} else {
parseResponseInSymmetricMode(responseData);
}
}
/**
* Aborts the secure session.
*
* @param apduResponse The response from the APDU command.
*/
private void processAbort(ApduResponseApi apduResponse) {
getTransactionContext().setSecureSessionOpen(false);
try {
super.setApduResponseAndCheckStatus(apduResponse);
logger.info("Secure session successfully aborted");
getTransactionContext().getCard().restoreFiles();
} catch (CardCommandException e) {
logger.warn("Failed to abort secure session: {}", e.getMessage());
}
}
/**
* Parses the response in symmetric crypto mode to verify the card MAC.
*
* @param responseData The byte array containing the response data.
*/
private void parseResponseInSymmetricMode(byte[] responseData) {
// Retrieve the postponed data
int cardSessionMacLength = getTransactionContext().getCard().isExtendedModeSupported() ? 8 : 4;
int i = 0;
while (i < responseData.length - cardSessionMacLength) {
byte[] data = Arrays.copyOfRange(responseData, i + 1, i + responseData[i]);
postponedData.add(data);
i += responseData[i];
}
// Check the card session MAC (CL-CSS-MACVERIF.1)
byte[] cardSessionMac = Arrays.copyOfRange(responseData, i, responseData.length);
try {
if (!getTransactionContext()
.getSymmetricCryptoCardTransactionManagerSpi()
.isCardSessionMacValid(cardSessionMac)) {
throw new InvalidCardSignatureException(MSG_INVALID_CARD_SESSION_MAC);
}
} catch (SymmetricCryptoIOException e) {
throw new CardSignatureNotVerifiableException(MSG_CARD_SESSION_MAC_NOT_VERIFIABLE, e);
} catch (SymmetricCryptoException e) {
throw new CryptoException(e.getMessage(), e);
}
if (svPostponedDataIndex != -1) {
// CL-SV-POSTPON.1
try {
if (!getTransactionContext()
.getSymmetricCryptoCardTransactionManagerSpi()
.isCardSvMacValid(postponedData.get(svPostponedDataIndex))) {
throw new InvalidCardSignatureException(MSG_INVALID_CARD_SESSION_MAC);
}
} catch (SymmetricCryptoIOException e) {
throw new CardSignatureNotVerifiableException(MSG_CARD_SV_MAC_NOT_VERIFIABLE, e);
} catch (SymmetricCryptoException e) {
throw new CryptoException(e.getMessage(), e);
}
}
}
/**
* Parses the response in PKI mode to verify the card signature.
*
* @param responseData The byte array containing the response data.
*/
private void parseResponseInAsymmetricMode(byte[] responseData) {
try {
if (!getTransactionContext()
.getAsymmetricCryptoCardTransactionManagerSpi()
.isCardPkiSessionValid(responseData)) {
throw new InvalidCardSignatureException(MSG_INVALID_CARD_SESSION_SIGNATURE);
}
} catch (AsymmetricCryptoException e) {
throw new CryptoException(e.getMessage(), e);
}
}
/**
* {@inheritDoc}
*
* @since 2.0.1
*/
@Override
Map getStatusTable() {
return STATUS_TABLE;
}
}