Keyple Card Calypso C++ Library - 3.2.2
Component of the Keyple C++ middleware
CommandSvDebitOrUndebit.cpp
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ *
3 * *
4 * See the NOTICE file(s) distributed with this work for additional *
5 * information regarding copyright ownership. *
6 * *
7 * This program and the accompanying materials are made available under the *
8 * terms of the Eclipse Public License 2.0 which is available at *
9 * http://www.eclipse.org/legal/epl-2.0 *
10 * *
11 * SPDX-License-Identifier: EPL-2.0 *
12 ******************************************************************************/
13
14#include "keyple/card/calypso/CommandSvDebitOrUndebit.hpp"
15
16#include <map>
17#include <memory>
18#include <string>
19#include <vector>
20
21#include "keyple/card/calypso/CalypsoCardAdapter.hpp"
22#include "keyple/card/calypso/CalypsoCardConstant.hpp"
23#include "keyple/card/calypso/CardAccessForbiddenException.hpp"
24#include "keyple/card/calypso/CardIllegalParameterException.hpp"
25#include "keyple/card/calypso/CardSecurityDataException.hpp"
26#include "keyple/card/calypso/CardSessionBufferOverflowException.hpp"
27#include "keyple/card/calypso/CardTerminatedException.hpp"
28#include "keyple/core/util/ApduUtil.hpp"
29#include "keyple/core/util/ByteArrayUtil.hpp"
30#include "keyple/core/util/cpp/Arrays.hpp"
31#include "keyple/core/util/cpp/System.hpp"
32#include "keyple/core/util/cpp/exception/IllegalStateException.hpp"
33#include "keypop/calypso/card/transaction/CardSignatureNotVerifiableException.hpp"
34#include "keypop/calypso/card/transaction/CryptoException.hpp"
35#include "keypop/calypso/card/transaction/CryptoIOException.hpp"
36#include "keypop/calypso/card/transaction/InvalidCardSignatureException.hpp"
37#include "keypop/calypso/crypto/symmetric/SymmetricCryptoException.hpp"
38#include "keypop/calypso/crypto/symmetric/SymmetricCryptoIOException.hpp"
39
40namespace keyple {
41namespace card {
42namespace calypso {
43
44using keyple::core::util::ApduUtil;
45using keyple::core::util::ByteArrayUtil;
46using keyple::core::util::cpp::Arrays;
47using keyple::core::util::cpp::System;
48using keyple::core::util::cpp::exception::IllegalStateException;
49using keypop::calypso::card::transaction::CardSignatureNotVerifiableException;
50using keypop::calypso::card::transaction::CryptoException;
51using keypop::calypso::card::transaction::CryptoIOException;
52using keypop::calypso::card::transaction::InvalidCardSignatureException;
53using keypop::calypso::crypto::symmetric::SymmetricCryptoException;
54using keypop::calypso::crypto::symmetric::SymmetricCryptoIOException;
55
56const std::string CommandSvDebitOrUndebit::MSG_CARD_SV_MAC_NOT_VERIFIABLE
57 = "Unable to verify the card SV MAC associated to the SV operation";
58const std::string CommandSvDebitOrUndebit::MSG_INVALID_CARD_SESSION_MAC
59 = "Invalid card session MAC";
60const int CommandSvDebitOrUndebit::SW_POSTPONED_DATA = 0x6200;
61
62const std::map<int, const std::shared_ptr<Command::StatusProperties>>
63 CommandSvDebitOrUndebit::STATUS_TABLE = [] {
64 std::map<int, const std::shared_ptr<Command::StatusProperties>> m(
65 Command::STATUS_TABLE);
66
67 m.insert(
68 {{0x6400,
69 std::make_shared<StatusProperties>(
70 "Too many modifications in session",
71 typeid(CardSessionBufferOverflowException))},
72 {0x6700,
73 std::make_shared<StatusProperties>(
74 "Lc value not supported",
75 typeid(CardIllegalParameterException))},
76 {0x6900,
77 std::make_shared<StatusProperties>(
78 "Transaction counter is 0 or SV TNum is FFFEh or FFFFh",
79 typeid(CardTerminatedException))},
80 {0x6985,
81 std::make_shared<StatusProperties>(
82 "Preconditions not satisfied",
83 typeid(CardAccessForbiddenException))},
84 {0x6988,
85 std::make_shared<StatusProperties>(
86 "Incorrect signatureHi", typeid(CardSecurityDataException))},
87 {SW_POSTPONED_DATA,
88 std::make_shared<StatusProperties>(
89 "Successful execution, response data postponed until session "
90 "closing")}});
91 return m;
92 }();
93
94CommandSvDebitOrUndebit::CommandSvDebitOrUndebit(
95 bool isDebitCommand,
96 const std::shared_ptr<DtoAdapters::TransactionContextDto>&
97 transactionContext,
98 const std::shared_ptr<DtoAdapters::CommandContextDto>& commandContext,
99 int amount,
100 const std::vector<std::uint8_t>& date,
101 const std::vector<std::uint8_t>& time,
102 bool isExtendedModeAllowed,
103 bool isSvNegativeBalanceAuthorized)
104: Command(
105 isDebitCommand ? CardCommandRef::SV_DEBIT : CardCommandRef::SV_UNDEBIT,
106 std::unique_ptr<int>(new int(computeExpectedResponseLength(
107 commandContext, isExtendedModeAllowed))),
108 transactionContext,
109 commandContext)
110,
111/* Keeps a copy of these fields until the builder is finalized */
112mAmount(amount)
113, mIsDebitCommand(isDebitCommand)
114, mIsExtendedModeAllowed(isExtendedModeAllowed)
115, mIsSvNegativeBalanceAuthorized(isSvNegativeBalanceAuthorized)
116{
117 /*
118 * Handle the dataIn size with signatureHi length according to card product
119 * type (3.2 rev have a 10-byte signature)
120 */
121 mDataIn = std::vector<std::uint8_t>(15 + (isExtendedModeAllowed ? 10 : 5));
122
123 /* dataIn[0] will be filled in at the finalization phase. */
124 const std::uint16_t amountShort = isDebitCommand
125 ? static_cast<std::uint16_t>(-amount)
126 : static_cast<std::uint16_t>(amount);
127 ByteArrayUtil::copyBytes(amountShort, mDataIn, 1, 2);
128 mDataIn[3] = date[0];
129 mDataIn[4] = date[1];
130 mDataIn[5] = time[0];
131 mDataIn[6] = time[1];
132 mDataIn[7] = transactionContext->getCard()->getSvKvc();
133
134 /*
135 * dataIn[8]..dataIn[8+7+sigLen] will be filled in at the finalization
136 * phase. Add dummy apdu request to ensure it exists when checking the
137 * session buffer usage APDU Case 3 (in session) or 4 (outside session).
138 */
139 std::unique_ptr<std::uint8_t> le(
140 computeLe(commandContext, isExtendedModeAllowed));
141
142 std::vector<std::uint8_t> apdu;
143 if (le == nullptr) {
144 apdu = ApduUtil::build(0, 0, 0, 0, mDataIn);
145
146 } else {
147 apdu = ApduUtil::build(0, 0, 0, 0, mDataIn, *le.get());
148 }
149
150 setApduRequest(std::make_shared<DtoAdapters::ApduRequestAdapter>(apdu));
151}
152
153void
154CommandSvDebitOrUndebit::finalizeRequest()
155{
156 if (mIsDebitCommand && !mIsSvNegativeBalanceAuthorized
157 && (getTransactionContext()->getCard()->getSvBalance() - mAmount) < 0) {
158 throw IllegalStateException("Negative balances are not allowed");
159 }
160
161 auto svCommandSecurityData(
162 std::make_shared<DtoAdapters::SvCommandSecurityDataApiAdapter>());
163 svCommandSecurityData->setSvGetRequest(
164 getTransactionContext()->getCard()->getSvGetHeader());
165 svCommandSecurityData->setSvGetResponse(
166 getTransactionContext()->getCard()->getSvGetData());
167 svCommandSecurityData->setSvCommandPartialRequest(
168 getSvDebitOrUndebitData());
169
170 try {
171 getTransactionContext()
172 ->getSymmetricCryptoCardTransactionManagerSpi()
173 ->computeSvCommandSecurityData(svCommandSecurityData);
174
175 } catch (const SymmetricCryptoException& e) {
176 throw CryptoException(e.what(), e);
177
178 } catch (const SymmetricCryptoIOException& e) {
179 throw CryptoIOException(e.what(), e);
180 }
181
182 finalizeCommand(svCommandSecurityData);
183 encryptRequestAndUpdateTerminalSessionMacIfNeeded();
184}
185
186bool
187CommandSvDebitOrUndebit::isCryptoServiceRequiredToFinalizeRequest() const
188{
189 return true;
190}
191
192bool
193CommandSvDebitOrUndebit::synchronizeCryptoServiceBeforeCardProcessing()
194{
195 return false;
196}
197
198void
199CommandSvDebitOrUndebit::parseResponse(
200 std::shared_ptr<ApduResponseApi> apduResponse)
201{
202 decryptResponseAndUpdateTerminalSessionMacIfNeeded(apduResponse);
203 Command::setApduResponseAndCheckStatus(apduResponse);
204
205 if (apduResponse->getDataOut().size() != 0
206 && apduResponse->getDataOut().size() != 3
207 && apduResponse->getDataOut().size() != 6) {
208 throw IllegalStateException(
209 "SV Debit/Undebit response is not the correct length. Expected: "
210 "0/3/6, Actual: "
211 + std::to_string(apduResponse->getDataOut().size()));
212 }
213
214 std::shared_ptr<CalypsoCardAdapter> calypsoCard
215 = getTransactionContext()->getCard();
216 calypsoCard->setSvOperationSignature(apduResponse->getDataOut());
217 updateCalypsoCardSvHistory(calypsoCard);
218 updateTerminalSessionIfNeeded();
219
220 if (!getCommandContext()->isSecureSessionOpen()) {
221 try {
222 if (!getTransactionContext()
223 ->getSymmetricCryptoCardTransactionManagerSpi()
224 ->isCardSvMacValid(
225 getTransactionContext()
226 ->getCard()
227 ->getSvOperationSignature())) {
228 throw InvalidCardSignatureException(
229 MSG_INVALID_CARD_SESSION_MAC);
230 }
231
232 } catch (const SymmetricCryptoIOException& e) {
233 throw CardSignatureNotVerifiableException(
234 MSG_CARD_SV_MAC_NOT_VERIFIABLE, e);
235
236 } catch (const SymmetricCryptoException& e) {
237 throw CryptoIOException(e.what(), e);
238 }
239 }
240}
241
242const std::map<int, const std::shared_ptr<Command::StatusProperties>>&
243CommandSvDebitOrUndebit::getStatusTable() const
244{
245 return STATUS_TABLE;
246}
247
248int
249CommandSvDebitOrUndebit::computeExpectedResponseLength(
250 const std::shared_ptr<DtoAdapters::CommandContextDto>& commandContext,
251 bool isExtendedModeAllowed)
252{
253 if (commandContext->isSecureSessionOpen()) {
254 return 0;
255 } else {
256 return isExtendedModeAllowed ? 6 : 3;
257 }
258}
259
260std::unique_ptr<std::uint8_t>
261CommandSvDebitOrUndebit::computeLe(
262 const std::shared_ptr<DtoAdapters::CommandContextDto>& commandContext,
263 bool isExtendedModeAllowed)
264{
265 if (commandContext->isSecureSessionOpen()) {
266 return nullptr;
267 } else {
268 return std::unique_ptr<std::uint8_t>(
269 new std::uint8_t(isExtendedModeAllowed ? 6 : 3));
270 }
271}
272
273void
274CommandSvDebitOrUndebit::finalizeCommand(
275 std::shared_ptr<DtoAdapters::SvCommandSecurityDataApiAdapter>
276 svCommandSecurityData)
277{
278 const std::uint8_t p1 = svCommandSecurityData->getTerminalChallenge()[0];
279 std::uint8_t p2 = svCommandSecurityData->getTerminalChallenge()[1];
280 mDataIn[0] = svCommandSecurityData->getTerminalChallenge()[2];
281 System::arraycopy(
282 svCommandSecurityData->getSerialNumber(), 0, mDataIn, 8, 4);
283 System::arraycopy(
284 svCommandSecurityData->getTransactionNumber(), 0, mDataIn, 12, 3);
285 System::arraycopy(
286 svCommandSecurityData->getTerminalSvMac(),
287 0,
288 mDataIn,
289 15,
290 svCommandSecurityData->getTerminalSvMac().size());
291
292 /* APDU Case 3 (in session) or 4 (outside session) */
293 auto apdu(
294 std::make_shared<DtoAdapters::ApduRequestAdapter>(ApduUtil::build(
295 getTransactionContext()->getCard()->getCardClass()
296 == CalypsoCardClass::LEGACY
297 ? CalypsoCardClass::LEGACY_STORED_VALUE.getValue()
298 : CalypsoCardClass::ISO.getValue(),
299 getCommandRef().getInstructionByte(),
300 p1,
301 p2,
302 mDataIn,
303 *computeLe(getCommandContext(), mIsExtendedModeAllowed))));
304 apdu->addSuccessfulStatusWord(SW_POSTPONED_DATA);
305 setApduRequest(apdu);
306}
307
308std::vector<std::uint8_t>
309CommandSvDebitOrUndebit::getSvDebitOrUndebitData()
310{
311 std::vector<std::uint8_t> svDebitOrUndebitData(12);
312 svDebitOrUndebitData[0] = getCommandRef().getInstructionByte();
313
314 /*
315 * svDebitOrUndebitData[1,2] / P1P2 not set because ignored.
316 * Lc is 5 bytes longer in product type 3.2.
317 */
318 svDebitOrUndebitData[3] = mIsExtendedModeAllowed ? 0x19 : 0x14;
319
320 /* Appends the fixed part of dataIn. */
321 System::arraycopy(mDataIn, 0, svDebitOrUndebitData, 4, 8);
322
323 return svDebitOrUndebitData;
324}
325
326void
327CommandSvDebitOrUndebit::updateCalypsoCardSvHistory(
328 std::shared_ptr<CalypsoCardAdapter> calypsoCard)
329{
330 const int balance = calypsoCard->getSvBalance() - mAmount;
331 calypsoCard->updateSvData(balance, calypsoCard->getSvLastTNum() + 1);
332
333 std::vector<std::uint8_t> debitLog(19);
334 System::arraycopy(getApduRequest()->getApdu(), 6, debitLog, 0, 14);
335 ByteArrayUtil::copyBytes(balance, debitLog, 14, 3);
336 ByteArrayUtil::copyBytes(calypsoCard->getSvLastTNum(), debitLog, 17, 2);
337 calypsoCard->addCyclicContent(
338 CalypsoCardConstant::SV_DEBIT_LOG_FILE_SFI, debitLog);
339}
340
341} /* namespace calypso */
342} /* namespace card */
343} /* namespace keyple */