Keyple Card Calypso C++ Library - 3.2.2
Component of the Keyple C++ middleware
SecurePkiModeTransactionManagerAdapter.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/SecurePkiModeTransactionManagerAdapter.hpp"
15
16#include <memory>
17#include <string>
18#include <utility>
19#include <vector>
20
21#include "keyple/card/calypso/CalypsoCardAdapter.hpp"
22#include "keyple/card/calypso/CalypsoCardConstant.hpp"
23#include "keyple/card/calypso/CommandChangePin.hpp"
24#include "keyple/card/calypso/CommandCloseSecureSession.hpp"
25#include "keyple/card/calypso/CommandGetDataCertificate.hpp"
26#include "keyple/card/calypso/CommandOpenSecureSession.hpp"
27#include "keyple/card/calypso/CommandVerifyPin.hpp"
28#include "keyple/core/plugin/CardIOException.hpp"
29#include "keyple/core/plugin/ReaderIOException.hpp"
30#include "keyple/core/util/HexUtil.hpp"
31#include "keyple/core/util/KeypleAssert.hpp"
32#include "keyple/core/util/cpp/Arrays.hpp"
33#include "keyple/core/util/cpp/exception/IllegalStateException.hpp"
34#include "keyple/core/util/cpp/exception/RuntimeException.hpp"
35#include "keyple/core/util/cpp/exception/UnsupportedOperationException.hpp"
36#include "keypop/calypso/card/transaction/InvalidCertificateException.hpp"
37#include "keypop/calypso/card/transaction/UnexpectedCommandStatusException.hpp"
38#include "keypop/calypso/crypto/asymmetric/AsymmetricCryptoException.hpp"
39#include "keypop/calypso/crypto/asymmetric/certificate/CertificateValidationException.hpp"
40#include "keypop/reader/CardCommunicationException.hpp"
41#include "keypop/reader/ReaderCommunicationException.hpp"
42#include "keypop/reader/selection/InvalidCardResponseException.hpp"
43
44namespace keyple {
45namespace card {
46namespace calypso {
47
48using keyple::core::plugin::CardIOException;
49using keyple::core::plugin::ReaderIOException;
50using keyple::core::util::Assert;
51using keyple::core::util::HexUtil;
52using keyple::core::util::cpp::Arrays;
53using keyple::core::util::cpp::exception::IllegalStateException;
54using keyple::core::util::cpp::exception::RuntimeException;
55using keyple::core::util::cpp::exception::UnsupportedOperationException;
56using keypop::calypso::card::transaction::InvalidCertificateException;
57using keypop::calypso::card::transaction::UnexpectedCommandStatusException;
58using keypop::calypso::crypto::asymmetric::AsymmetricCryptoException;
59using keypop::calypso::crypto::asymmetric::certificate::
60 CertificateValidationException;
61using keypop::reader::CardCommunicationException;
62using keypop::reader::ReaderCommunicationException;
63using keypop::reader::selection::InvalidCardResponseException;
64
65const std::string SecurePkiModeTransactionManagerAdapter::MSG_PIN_NOT_AVAILABLE
66 = "PIN is not available for this card";
67const std::string
68 SecurePkiModeTransactionManagerAdapter ::MSG_INVALID_CARD_CERTIFICATE
69 = "Invalid card certificate";
70const std::string
71 SecurePkiModeTransactionManagerAdapter ::MSG_INVALID_CA_CERTIFICATE
72 = "Invalid CA certificate";
73
74SecurePkiModeTransactionManagerAdapter::SecurePkiModeTransactionManagerAdapter(
75 std::shared_ptr<ProxyReaderApi> cardReader,
76 std::shared_ptr<CalypsoCardAdapter> card,
77 std::shared_ptr<AsymmetricCryptoSecuritySettingAdapter>
78 asymmetricCryptoSecuritySetting)
79: TransactionManagerAdapter<SecurePkiModeTransactionManager>(cardReader, card)
80, SecureTransactionManagerAdapter<SecurePkiModeTransactionManager>(
81 cardReader, card)
82, mAsymmetricCryptoSecuritySetting(asymmetricCryptoSecuritySetting)
83, mPayloadCapacity(card->getPayloadCapacity())
84{
85 std::shared_ptr<AsymmetricCryptoCardTransactionManagerSpi>
86 asymmetricCryptoCardTransactionManagerSpi
87 = asymmetricCryptoSecuritySetting
88 ->getCryptoCardTransactionManagerFactorySpi()
89 ->createCardTransactionManager();
90
91 mCryptoExtension
92 = std::dynamic_pointer_cast<CardTransactionCryptoExtension>(
93 asymmetricCryptoCardTransactionManagerSpi);
94
95 mTransactionContext = std::make_shared<DtoAdapters::TransactionContextDto>(
96 card, asymmetricCryptoCardTransactionManagerSpi);
97
98 // C++ secure random setup
99 std::random_device rd;
100 std::mt19937 gen(rd());
101}
102
103void
104SecurePkiModeTransactionManagerAdapter::resetCommandContext()
105{
106 mIsSecureSessionOpen = false;
107}
108
109std::shared_ptr<DtoAdapters::TransactionContextDto>
110SecurePkiModeTransactionManagerAdapter::getTransactionContext() const
111{
112 return mTransactionContext;
113}
114
115std::shared_ptr<DtoAdapters::CommandContextDto>
116SecurePkiModeTransactionManagerAdapter::getCommandContext() const
117{
118 return std::make_shared<DtoAdapters::CommandContextDto>(
119 mIsSecureSessionOpen, false);
120}
121
122int
123SecurePkiModeTransactionManagerAdapter::getPayloadCapacity() const
124{
125 return mPayloadCapacity;
126}
127
128void
129SecurePkiModeTransactionManagerAdapter::resetTransaction()
130{
131 resetCommandContext();
132
133 mIsGetDataCardCertificatePrepared = false;
134 mIsGetDataCaCertificatePrepared = false;
135
136 disablePreOpenMode();
137
138 mCommands.clear();
139
140 if (mTransactionContext->isSecureSessionOpen()) {
141 try {
142 auto cancelSecureSessionCommand
143 = std::make_shared<CommandCloseSecureSession>(
144 mTransactionContext, getCommandContext(), true);
145
146 cancelSecureSessionCommand->finalizeRequest();
147 std::vector<std::shared_ptr<Command>> commands(1);
148 commands.push_back(cancelSecureSessionCommand);
149 executeCardCommands(commands, ChannelControl::KEEP_OPEN);
150
151 } catch (const RuntimeException& e) {
152 mLogger->warn(
153 "Failed to abort secure session [reason=%]\n", e.what());
154 }
155
156 /* Finally */
157 mCard->restoreFiles();
158 mTransactionContext->setSecureSessionOpen(false);
159 }
160}
161
162void
163SecurePkiModeTransactionManagerAdapter::prepareNewSecureSessionIfNeeded(
164 const std::shared_ptr<Command>& /*command*/)
165{
166 /* NOP */
167}
168
169bool
170SecurePkiModeTransactionManagerAdapter::canConfigureReadOnOpenSecureSession()
171 const
172{
173 return mIsSecureSessionOpen && !mCommands.empty()
174 && mCommands[mCommands.size() - 1]->getCommandRef()
175 == CardCommandRef::OPEN_SECURE_SESSION
176 && !std::dynamic_pointer_cast<CommandOpenSecureSession>(
177 mCommands[mCommands.size() - 1])
178 ->isReadModeConfigured();
179}
180
181SecurePkiModeTransactionManager&
182SecurePkiModeTransactionManagerAdapter::prepareVerifyPin(
183 const std::vector<std::uint8_t>& pin)
184{
185 try {
186 Assert::getInstance().isEqual(
187 pin.size(), CalypsoCardConstant::PIN_LENGTH, "PIN length");
188 if (!mCard->isPinFeatureAvailable()) {
189 throw UnsupportedOperationException(MSG_PIN_NOT_AVAILABLE);
190 }
191
192 mCommands.push_back(
193 std::make_shared<CommandVerifyPin>(
194 mTransactionContext, getCommandContext(), pin));
195
196 } catch (...) {
197 resetTransaction();
198 throw;
199 }
200
201 return *this;
202}
203
204SecurePkiModeTransactionManager&
205SecurePkiModeTransactionManagerAdapter::prepareChangePin(
206 const std::vector<std::uint8_t>& newPin)
207{
208 try {
209 Assert::getInstance().isEqual(
210 newPin.size(), CalypsoCardConstant::PIN_LENGTH, "PIN length");
211 if (!mCard->isPinFeatureAvailable()) {
212 throw UnsupportedOperationException(MSG_PIN_NOT_AVAILABLE);
213 }
214
215 /* CL-PIN-MENCRYPT.1 */
216 mCommands.push_back(
217 std::make_shared<CommandChangePin>(
218 mTransactionContext, getCommandContext(), newPin));
219
220 } catch (...) {
221 resetTransaction();
222 throw;
223 }
224
225 return *this;
226}
227
228SecurePkiModeTransactionManager&
229SecurePkiModeTransactionManagerAdapter::prepareGetData(GetDataTag tag)
230{
231 SecureTransactionManagerAdapter<
232 SecurePkiModeTransactionManager>::prepareGetData(tag);
233
234 if (tag == GetDataTag::CARD_CERTIFICATE) {
235 mIsGetDataCardCertificatePrepared = true;
236
237 } else if (tag == GetDataTag::CA_CERTIFICATE) {
238 mIsGetDataCaCertificatePrepared = true;
239 }
240
241 return *this;
242}
243
244// SecurePkiModeTransactionManager&
245// SecurePkiModeTransactionManagerAdapter::processCommands(
246// keypop::calypso::card::transaction::ChannelControl channelControl)
247// {
248// try {
249// return processCommands(
250// keypop::reader::valueOf(static_cast<int>(channelControl)));
251//
252// } catch (const CardCommunicationException& e) {
253// throw CardIOException(e.what(), Exception(e.what()));
254//
255// } catch (const ReaderCommunicationException& e) {
256// throw ReaderIOException(e.what(), Exception(e.what()));
257//
258// } catch (const InvalidCardResponseException& e) {
259// throw UnexpectedCommandStatusException(e.what(), e);
260// }
261// }
262
263SecurePkiModeTransactionManager&
264SecurePkiModeTransactionManagerAdapter::processCommands(
265 ChannelControl channelControl)
266{
267 if (mCommands.empty()) {
268 return *this;
269 }
270
271 try {
272 /*
273 * In the case that the CA certificate is missing before the parsing of
274 * the response to the "open secure session" command, we seamlessly
275 * trigger the execution of Get Data commands to fetch it. Depending on
276 * the current status of the session, these commands might also be
277 * integrated to the session hash. We need to keep the channel open and
278 * close or keep it open as expected after the execution of the Get Data
279 * commands (role of mOriginalChannelControl).
280 */
281 mOriginalChannelControl = channelControl;
282 if (mCard->getCaCertificate().size() == 0
283 && !mIsGetDataCaCertificatePrepared) {
284 executeCardCommands(mCommands, ChannelControl::KEEP_OPEN);
285 } else {
286 executeCardCommands(mCommands, channelControl);
287 }
288
289 } catch (...) {
290 resetTransaction();
291
292 /* Finally */
293 mCommands.clear();
294
295 throw;
296 }
297
298 /* Finally */
299 mCommands.clear();
300
301 return *this;
302}
303
304void
305SecurePkiModeTransactionManagerAdapter::parseCommandResponse(
306 const std::shared_ptr<Command>& command,
307 const std::shared_ptr<ApduResponseApi>& apduResponse)
308{
309 if (command->getCommandRef() == CardCommandRef::OPEN_SECURE_SESSION) {
310 checkCardCertificateAndGetCardPublicKey();
311 }
312
313 command->parseResponse(apduResponse);
314}
315
316void
317SecurePkiModeTransactionManagerAdapter ::
318 checkCardCertificateAndGetCardPublicKey()
319{
320 /* Parse the card certificate raw data */
321 std::shared_ptr<CardCertificateSpi> cardCertificateSpi
322 = parseCardCertificate();
323
324 if (!Arrays::equals(
325 mCard->getApplicationSerialNumber(),
326 cardCertificateSpi->getCardSerialNumber())) {
327 throw InvalidCertificateException(
328 "Card serial number and certificate card serial number mismatch");
329 }
330
331 /* Try to retrieve the issuer certificate content from the store */
332 std::shared_ptr<CaCertificateContentSpi> caCertificateContentSpi
333 = mAsymmetricCryptoSecuritySetting->getCaCertificate(
334 cardCertificateSpi->getIssuerPublicKeyReference());
335
336 /*
337 * If the issuer certificate content is not already registered, then
338 * retrieve it from the card.
339 */
340 if (caCertificateContentSpi == nullptr) {
341 /*
342 * Read the CA certificate from the card using the original channel
343 * control.
344 */
345 readCaCertificate();
346
347 /* Parse the CA certificate raw data */
348 std::shared_ptr<CaCertificateSpi> caCertificateSpi
349 = parseCaCertificate();
350
351 /* Register the CA certificate into the store */
352 mAsymmetricCryptoSecuritySetting->addCaCertificate(
353 std::dynamic_pointer_cast<CaCertificate>(caCertificateSpi));
354
355 /* Retrieve the CA certificate content from the store */
356 caCertificateContentSpi
357 = mAsymmetricCryptoSecuritySetting->getCaCertificate(
358 cardCertificateSpi->getIssuerPublicKeyReference());
359
360 } else {
361 /* Force the closing of the channel if originally requested */
362 if (mOriginalChannelControl == ChannelControl::CLOSE_AFTER) {
363 executeCardCommands({}, ChannelControl::CLOSE_AFTER);
364 }
365 }
366
367 /*
368 * Check the card certificate using the issuer certificate content and
369 * extract the public key.
370 */
371 std::shared_ptr<CardPublicKeySpi> cardPublicKeySpi;
372
373 try {
374 cardPublicKeySpi = cardCertificateSpi->checkCertificateAndGetPublicKey(
375 caCertificateContentSpi);
376
377 } catch (const CertificateValidationException& e) {
378 throw InvalidCertificateException(MSG_INVALID_CARD_CERTIFICATE, e);
379
380 } catch (const AsymmetricCryptoException& e) {
381 throw CryptoException("Failed to check the card certificate", e);
382 }
383
384 /* Save the card public key into the card image */
385 mCard->setCardPublicKeySpi(cardPublicKeySpi);
386}
387
388std::shared_ptr<CardCertificateSpi>
389SecurePkiModeTransactionManagerAdapter::parseCardCertificate()
390{
391 const std::vector<std::uint8_t> cardCertificateBytes
392 = mCard->getCardCertificate();
393
394 std::shared_ptr<CardCertificateParserSpi> cardCertificateParser
395 = mAsymmetricCryptoSecuritySetting->getCardCertificateParser(
396 cardCertificateBytes[0]);
397
398 if (cardCertificateParser == nullptr) {
399 throw IllegalStateException(
400 "No certificate parser registered for type "
401 + HexUtil::toHex(cardCertificateBytes[0]));
402 }
403
404 try {
405 return cardCertificateParser->parseCertificate(cardCertificateBytes);
406
407 } catch (const CertificateValidationException& e) {
408 throw InvalidCertificateException(MSG_INVALID_CARD_CERTIFICATE, e);
409 }
410}
411
412std::shared_ptr<CaCertificateSpi>
413SecurePkiModeTransactionManagerAdapter::parseCaCertificate()
414{
415 const std::vector<std::uint8_t> caCertificateBytes
416 = mCard->getCaCertificate();
417
418 std::shared_ptr<CaCertificateParserSpi> caCertificateParser
419 = mAsymmetricCryptoSecuritySetting->getCaCertificateParser(
420 caCertificateBytes[0]);
421
422 if (caCertificateParser == nullptr) {
423 throw IllegalStateException(
424 "No certificate parser registered for type "
425 + HexUtil::toHex(caCertificateBytes[0]));
426 }
427
428 try {
429 return caCertificateParser->parseCertificate(caCertificateBytes);
430
431 } catch (const CertificateValidationException& e) {
432 throw InvalidCertificateException(MSG_INVALID_CA_CERTIFICATE, e);
433 }
434}
435
436void
437SecurePkiModeTransactionManagerAdapter::readCaCertificate()
438{
439 std::vector<std::shared_ptr<Command>> commands(2);
440
441 commands.push_back(
442 std::make_shared<CommandGetDataCertificate>(
443 mTransactionContext, getCommandContext(), false, true));
444 commands.push_back(
445 std::make_shared<CommandGetDataCertificate>(
446 mTransactionContext, getCommandContext(), false, false));
447
448 executeCardCommands(commands, mOriginalChannelControl);
449}
450
451std::shared_ptr<CardTransactionCryptoExtension>
452SecurePkiModeTransactionManagerAdapter::getCryptoExtension()
453{
454 return mCryptoExtension;
455}
456
457SecurePkiModeTransactionManager&
458SecurePkiModeTransactionManagerAdapter::prepareOpenSecureSession()
459{
460 checkNoSecureSession();
461
462 if (mCard->getCardCertificate().size() == 0
463 && !mIsGetDataCardCertificatePrepared) {
464 prepareGetData(GetDataTag::CARD_CERTIFICATE);
465 }
466
467 std::vector<std::uint8_t> terminalChallenge(8);
468 mSecureRandom->nextBytes(terminalChallenge);
469
470 mCommands.push_back(
471 std::make_shared<CommandOpenSecureSession>(
472 mTransactionContext, getCommandContext(), terminalChallenge));
473
474 mIsSecureSessionOpen = true;
475
476 return *this;
477}
478
479SecurePkiModeTransactionManager&
480SecurePkiModeTransactionManagerAdapter::prepareCloseSecureSession()
481{
482 try {
483 checkSecureSession();
484 mCommands.push_back(
485 std::make_shared<CommandCloseSecureSession>(
486 mTransactionContext, getCommandContext(), false));
487
488 } catch (const std::exception&) {
489 resetTransaction();
490
491 /* Finally */
492 resetCommandContext();
493 disablePreOpenMode();
494
495 throw;
496 }
497
498 /* Finally */
499 resetCommandContext();
500 disablePreOpenMode();
501
502 return *this;
503}
504
505} /* namespace calypso */
506} /* namespace card */
507} /* namespace keyple */