Revision control

Copy as Markdown

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qpcsccard_p.h"
#include "ndef/qnfctagtype4ndeffsm_p.h"
#include "qapduutils_p.h"
#include <QtCore/QLoggingCategory>
#include <QtCore/QTimer>
#if defined(Q_OS_DARWIN)
# define SCARD_ATTR_MAXINPUT 0x0007A007
#elif !defined(Q_OS_WIN)
# include <reader.h>
#endif
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_NFC_PCSC)
/*
Windows SCardBeginTransaction() documentation says the following:
If a transaction is held on the card for more than five seconds with no
operations happening on that card, then the card is reset. Calling any
of the Smart Card and Reader Access Functions or Direct Card Access
Functions on the card that is transacted results in the timer being
reset to continue allowing the transaction to be used.
We use a timer to ensure that transactions do not timeout unexpectedly.
*/
static constexpr int KeepAliveIntervalMs = 2500;
/*
Start a temporary transaction if a persistent transaction was not already
started due to call to onSendCommandRequest().
If a temporary transaction was initiated, it is ended when this object is
destroyed.
*/
QPcscCard::Transaction::Transaction(QPcscCard *card) : m_card(card)
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
if (!m_card->isValid() || m_card->m_inAutoTransaction)
return;
auto ret = SCardBeginTransaction(m_card->m_handle);
if (ret != SCARD_S_SUCCESS) {
qCWarning(QT_NFC_PCSC) << "SCardBeginTransaction failed:" << QPcsc::errorMessage(ret);
m_card->invalidate();
return;
}
m_initiated = true;
}
QPcscCard::Transaction::~Transaction()
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
if (!m_initiated || !m_card->isValid())
return;
auto ret = SCardEndTransaction(m_card->m_handle, SCARD_LEAVE_CARD);
if (ret != SCARD_S_SUCCESS) {
qCWarning(QT_NFC_PCSC) << "SCardEndTransaction failed:" << QPcsc::errorMessage(ret);
m_card->invalidate();
}
}
QPcscCard::QPcscCard(SCARDHANDLE handle, DWORD protocol, QObject *parent)
: QObject(parent), m_handle(handle)
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
m_keepAliveTimer = new QTimer(this);
m_keepAliveTimer->setInterval(KeepAliveIntervalMs);
connect(m_keepAliveTimer, &QTimer::timeout, this, &QPcscCard::onKeepAliveTimeout);
m_ioPci.dwProtocol = protocol;
m_ioPci.cbPciLength = sizeof(m_ioPci);
// Assume that everything is NFC Tag Type 4 for now
m_tagDetectionFsm = std::make_unique<QNfcTagType4NdefFsm>();
performNdefDetection();
}
QPcscCard::~QPcscCard()
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
invalidate();
}
void QPcscCard::performNdefDetection()
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
if (!m_isValid)
return;
Transaction transaction(this);
auto action = m_tagDetectionFsm->detectNdefSupport();
while (action == QNdefAccessFsm::SendCommand) {
auto command = m_tagDetectionFsm->getCommand(action);
if (action == QNdefAccessFsm::ProvideResponse) {
auto result = sendCommand(command, NoAutoTransaction);
action = m_tagDetectionFsm->provideResponse(result.response);
}
}
qCDebug(QT_NFC_PCSC) << "NDEF detection result" << action;
m_supportsNdef = action == QNdefAccessFsm::Done;
qCDebug(QT_NFC_PCSC) << "NDEF supported:" << m_supportsNdef;
}
/*
Release the resource associated with the card and notify the NFC target
about disconnection. The card is deleted when automatic deletion is enabled.
*/
void QPcscCard::invalidate()
{
if (!m_isValid)
return;
SCardDisconnect(m_handle, m_inAutoTransaction ? SCARD_RESET_CARD : SCARD_LEAVE_CARD);
m_isValid = false;
m_inAutoTransaction = false;
Q_EMIT disconnected();
Q_EMIT invalidated();
if (m_autodelete)
deleteLater();
}
/*
Send the given command to the card.
Start an automatic transaction if autoTransaction is StartAutoTransaction
and it is not already started. This automatic transaction lasts until the
invalidate() or onDisconnectRequest() is called.
The automatic transaction is used to ensure that other applications do not
interfere with commands sent by the user application.
If autoTransaction is NoAutoTransaction, then the calling code should ensure
that either the command is atomic or that a temporary transaction is started
using Transaction object.
*/
QPcsc::RawCommandResult QPcscCard::sendCommand(const QByteArray &command,
QPcscCard::AutoTransaction autoTransaction)
{
if (!m_isValid)
return {};
if (!m_inAutoTransaction && autoTransaction == StartAutoTransaction) {
qCDebug(QT_NFC_PCSC) << "Starting transaction";
// FIXME: Is there a timeout on this?
auto ret = SCardBeginTransaction(m_handle);
if (ret != SCARD_S_SUCCESS) {
qCWarning(QT_NFC_PCSC) << "SCardBeginTransaction failed:" << QPcsc::errorMessage(ret);
invalidate();
return {};
}
m_inAutoTransaction = true;
m_keepAliveTimer->start();
}
QPcsc::RawCommandResult result;
result.response.resize(0xFFFF + 2);
DWORD recvLength = result.response.size();
qCDebug(QT_NFC_PCSC) << "TX:" << command.toHex(':');
result.ret = SCardTransmit(m_handle, &m_ioPci, reinterpret_cast<LPCBYTE>(command.constData()),
command.size(), nullptr,
reinterpret_cast<LPBYTE>(result.response.data()), &recvLength);
if (result.ret != SCARD_S_SUCCESS) {
qCWarning(QT_NFC_PCSC) << "SCardTransmit failed:" << QPcsc::errorMessage(result.ret);
result.response.clear();
invalidate();
} else {
result.response.resize(recvLength);
qCDebug(QT_NFC_PCSC) << "RX:" << result.response.toHex(':');
}
return result;
}
void QPcscCard::onKeepAliveTimeout()
{
if (!m_isValid || !m_inAutoTransaction) {
m_keepAliveTimer->stop();
return;
}
checkCardPresent();
}
QByteArray QPcscCard::readUid()
{
QByteArray command = QCommandApdu::build(0xFF, QCommandApdu::GetData, 0x00, 0x00, {}, 256);
// Atomic command, no need for transaction.
QResponseApdu res(sendCommand(command, NoAutoTransaction).response);
if (!res.isOk())
return {};
return res.data();
}
void QPcscCard::onReadNdefMessagesRequest(const QNearFieldTarget::RequestId &request)
{
if (!m_isValid) {
Q_EMIT requestCompleted(request, QNearFieldTarget::ConnectionError, {});
return;
}
if (!m_supportsNdef) {
Q_EMIT requestCompleted(request, QNearFieldTarget::UnsupportedError, {});
return;
}
Transaction transaction(this);
auto nextState = m_tagDetectionFsm->readMessages();
while (true) {
if (nextState == QNdefAccessFsm::SendCommand) {
auto command = m_tagDetectionFsm->getCommand(nextState);
if (nextState == QNdefAccessFsm::ProvideResponse) {
auto result = sendCommand(command, NoAutoTransaction);
nextState = m_tagDetectionFsm->provideResponse(result.response);
}
} else if (nextState == QNdefAccessFsm::GetMessage) {
auto message = m_tagDetectionFsm->getMessage(nextState);
Q_EMIT ndefMessageRead(message);
} else {
break;
}
}
qCDebug(QT_NFC_PCSC) << "Final state:" << nextState;
auto errorCode = (nextState == QNdefAccessFsm::Done) ? QNearFieldTarget::NoError
: QNearFieldTarget::NdefReadError;
Q_EMIT requestCompleted(request, errorCode, {});
}
/*
Ends the persistent transaction and resets the card if sendCommand() was
used by user.
Resetting the card ensures that the current state of the card does not get
shared with other processes.
*/
void QPcscCard::onDisconnectRequest()
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
if (!m_isValid)
return;
LONG ret;
if (m_inAutoTransaction) {
// NOTE: PCSCLite does not automatically release transaction in
ret = SCardEndTransaction(m_handle, SCARD_RESET_CARD);
if (ret != SCARD_S_SUCCESS) {
qCWarning(QT_NFC_PCSC) << "SCardEndTransaction failed:" << QPcsc::errorMessage(ret);
invalidate();
return;
}
m_inAutoTransaction = false;
}
DWORD activeProtocol;
ret = SCardReconnect(m_handle, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
SCARD_LEAVE_CARD, &activeProtocol);
if (ret != SCARD_S_SUCCESS) {
qCWarning(QT_NFC_PCSC) << "SCardReconnect failed:" << QPcsc::errorMessage(ret);
invalidate();
return;
}
Q_EMIT disconnected();
}
void QPcscCard::onTargetDestroyed()
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
invalidate();
}
void QPcscCard::onSendCommandRequest(const QNearFieldTarget::RequestId &request,
const QByteArray &command)
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
if (!m_isValid) {
Q_EMIT requestCompleted(request, QNearFieldTarget::ConnectionError, {});
return;
}
auto result = sendCommand(command, StartAutoTransaction);
if (result.isOk())
Q_EMIT requestCompleted(request, QNearFieldTarget::NoError, result.response);
else
Q_EMIT requestCompleted(request, QNearFieldTarget::CommandError, {});
}
void QPcscCard::onWriteNdefMessagesRequest(const QNearFieldTarget::RequestId &request,
const QList<QNdefMessage> &messages)
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
if (!m_isValid) {
Q_EMIT requestCompleted(request, QNearFieldTarget::ConnectionError, {});
return;
}
if (!m_supportsNdef) {
Q_EMIT requestCompleted(request, QNearFieldTarget::UnsupportedError, {});
return;
}
Transaction transaction(this);
auto nextState = m_tagDetectionFsm->writeMessages(messages);
while (nextState == QNdefAccessFsm::SendCommand) {
auto command = m_tagDetectionFsm->getCommand(nextState);
if (nextState == QNdefAccessFsm::ProvideResponse) {
auto result = sendCommand(command, NoAutoTransaction);
nextState = m_tagDetectionFsm->provideResponse(result.response);
}
}
auto errorCode = (nextState == QNdefAccessFsm::Done) ? QNearFieldTarget::NoError
: QNearFieldTarget::NdefWriteError;
Q_EMIT requestCompleted(request, errorCode, {});
}
/*
Enable automatic card deletion when the connection is closed by the user
or the card otherwise becomes unavailable.
The automatic deletion is prevented initially so that
QNearFieldManagerPrivate can safely establish connections between this
object and a QNearFieldTargetPrivate proxy.
*/
void QPcscCard::enableAutodelete()
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
m_autodelete = true;
if (!m_isValid)
deleteLater();
}
/*
Check if the card is still present in the reader.
Invalidates the object if card is not present.
*/
bool QPcscCard::checkCardPresent()
{
qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
if (!m_isValid)
return false;
DWORD state;
auto ret = SCardStatus(m_handle, nullptr, nullptr, &state, nullptr, nullptr, nullptr);
if (ret != SCARD_S_SUCCESS) {
qCWarning(QT_NFC_PCSC) << "SCardStatus failed:" << QPcsc::errorMessage(ret);
invalidate();
return false;
}
qCDebug(QT_NFC_PCSC) << "State:" << Qt::hex << state;
return (state & SCARD_PRESENT) != 0;
}
int QPcscCard::readMaxInputLength()
{
if (!m_isValid)
return 0;
// Maximum standard APDU length
static constexpr int DefaultMaxInputLength = 261;
uint32_t maxInput;
DWORD attrSize = sizeof(maxInput);
auto ret = SCardGetAttrib(m_handle, SCARD_ATTR_MAXINPUT, reinterpret_cast<LPBYTE>(&maxInput),
&attrSize);
if (ret != SCARD_S_SUCCESS) {
qCDebug(QT_NFC_PCSC) << "SCardGetAttrib failed:" << QPcsc::errorMessage(ret);
return DefaultMaxInputLength;
}
if (attrSize != sizeof(maxInput)) {
qCWarning(QT_NFC_PCSC) << "Unexpected attribute size for SCARD_ATTR_MAXINPUT:" << attrSize;
return DefaultMaxInputLength;
}
return static_cast<int>(maxInput);
}
QT_END_NAMESPACE