Skip to content

Commit 16d19c6

Browse files
authored
Thales card support (#1332)
IB-8171 Signed-off-by: Raul Metsma <[email protected]>
1 parent 7dd314c commit 16d19c6

File tree

14 files changed

+278
-79
lines changed

14 files changed

+278
-79
lines changed

client/Diagnostics.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ void Diagnostics::generalInfo(QTextStream &s)
119119

120120
reader.beginTransaction();
121121
constexpr auto APDU = &QByteArray::fromHex;
122-
auto printAID = [&](const QString &label, const QByteArray &apdu)
122+
auto printAID = [&](const QLatin1String &label, const QByteArray &apdu)
123123
{
124124
QPCSCReader::Result r = reader.transfer(apdu);
125125
s << label << ": " << Qt::hex << r.SW;
@@ -129,9 +129,11 @@ void Diagnostics::generalInfo(QTextStream &s)
129129
s << "<br />";
130130
return r;
131131
};
132-
if(printAID(QStringLiteral("AID_IDEMIA"), APDU("00A4040C 10 A000000077010800070000FE00000100")) &&
133-
reader.transfer(APDU("00A4010C 02 5000")) &&
134-
reader.transfer(APDU("00A4020C 02 5006")))
132+
if(printAID(QLatin1String("AID_IDEMIA"), APDU("00A4040C 10 A000000077010800070000FE00000100")) &&
133+
reader.transfer(APDU("00A4090C 04 5000 5006")))
134+
s << "ID - " << reader.transfer(APDU("00B00000 00")).data << "<br />";
135+
if(printAID(QLatin1String("AID_THALES"), APDU("00A4040C 0C A000000063504B43532D3135")) &&
136+
reader.transfer(APDU("00A4080C 04 DFDD 5006")))
135137
s << "ID - " << reader.transfer(APDU("00B00000 00")).data << "<br />";
136138
reader.endTransaction();
137139
}

client/Diagnostics_win.cpp

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
#include <qt_windows.h>
3333

34+
#include <span>
35+
3436
using namespace Qt::StringLiterals;
3537

3638
static QString getUserRights()
@@ -52,24 +54,19 @@ static QString getUserRights()
5254
}
5355

5456
QByteArray tokenGroupsBuffer(dwLength, 0);
55-
PTOKEN_GROUPS pGroup = PTOKEN_GROUPS(tokenGroupsBuffer.data());
57+
auto *pGroup = PTOKEN_GROUPS(tokenGroupsBuffer.data());
5658
if ( !GetTokenInformation( hToken, TokenGroups, pGroup, dwLength, &dwLength ) )
5759
return Diagnostics::tr("Unknown - error %1").arg(GetLastError());
5860

5961
QString rights = Diagnostics::tr( "User" );
6062
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
61-
PSID AdministratorsGroup {};
62-
if( AllocateAndInitializeSid( &NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
63+
if(PSID AdministratorsGroup {};
64+
AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
6365
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup ) )
6466
{
65-
for ( DWORD dwIndex = 0; dwIndex < pGroup->GroupCount; dwIndex++ )
66-
{
67-
if ( EqualSid( AdministratorsGroup, pGroup->Groups[dwIndex].Sid ) )
68-
{
69-
rights = Diagnostics::tr( "Administrator" );
70-
break;
71-
}
72-
}
67+
if(auto list = std::span<SID_AND_ATTRIBUTES>(pGroup->Groups, pGroup->GroupCount);
68+
std::any_of(list.begin(), list.end(), [&](const auto &group) { return EqualSid(AdministratorsGroup, group.Sid); }))
69+
rights = Diagnostics::tr("Administrator");
7370
FreeSid(AdministratorsGroup);
7471
}
7572

@@ -88,8 +85,8 @@ QStringList Diagnostics::packages(const QStringList &names, bool withName)
8885
}();
8986
for(QSettings::Format format: formats)
9087
{
91-
QSettings s(u"%1\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"_s.arg(group), format);
92-
for(const QString &key: s.childGroups())
88+
QSettings s("%1\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"_L1.arg(group), format);
89+
for(const auto groups = s.childGroups(); const QString &key: groups)
9390
{
9491
s.beginGroup(key);
9592
QString name = s.value("/DisplayName"_L1).toString();
@@ -149,7 +146,7 @@ void Diagnostics::run()
149146
SetDllDirectory(LPCWSTR(QCoreApplication::applicationDirPath().utf16()));
150147
static const QStringList dlls{
151148
"digidocpp", "qdigidoc4.exe", "EsteidShellExtension", "id-updater.exe", "web-eid.exe",
152-
"EstIDMinidriver", "EstIDMinidriver64", "EestiMinidriver", "EestiMinidriver64",
149+
"EstIDMinidriver", "EstIDMinidriver64", "EestiMinidriver", "EestiMinidriver64", "estgsv4md", "estgsv4md64",
153150
"zlib1", "libxml2", "libxmlsec1", "libxmlsec1-openssl",
154151
"msvcp140", "msvcp140_1", "msvcp140_2", "vcruntime140", "vcruntime140_1"};
155152
for(const QString &lib: dlls)

client/MainWindow.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,7 @@ void MainWindow::updateSelectorData(TokenData data)
10151015
ui->noCardInfo->setVisible(ui->cardInfo->token().isNull());
10161016
ui->selector->setHidden(list.isEmpty());
10171017
ui->selector->setChecked(false);
1018-
ui->cardInfo->setHidden(ui->noCardInfo->isVisible());
1018+
ui->cardInfo->setVisible(ui->noCardInfo->isHidden());
10191019
ui->cardInfo->setCursor(ui->selector->isVisible() ? Qt::PointingHandCursor : Qt::ArrowCursor);
10201020
ui->cardInfo->update(data, list.size() > 1);
10211021
if (!QPCSC::instance().serviceRunning())

client/QPCSC.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ static QStringList stateToString(DWORD state)
5353
}
5454

5555
template<auto Func, typename... Args>
56-
static auto SCCall(const char *file, int line, const char *function, Args... args)
56+
static auto SCCall(const char *file, int line, const char *function, Args&& ...args)
5757
{
58-
auto err = Func(args...);
58+
auto err = Func(std::forward<Args>(args)...);
5959
if(SCard().isDebugEnabled())
6060
QMessageLogger(file, line, function, SCard().categoryName()).debug()
6161
<< function << Qt::hex << (unsigned long)err;

client/QSmartCard.cpp

Lines changed: 155 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ QString QSmartCardData::card() const { return d->card; }
5151
bool QSmartCardData::isNull() const
5252
{ return d->data.isEmpty() && d->authCert.isNull() && d->signCert.isNull(); }
5353
bool QSmartCardData::isPinpad() const { return d->pinpad; }
54+
bool QSmartCardData::isPUKReplacable() const { return d->pukReplacable; }
5455
bool QSmartCardData::isValid() const
5556
{ return d->data.value(Expiry).toDateTime() >= QDateTime::currentDateTime(); }
5657

@@ -103,23 +104,17 @@ QPCSCReader::Result Card::transfer(QPCSCReader *reader, bool verify, const QByte
103104
}
104105

105106

106-
QByteArrayView Card::parseFCI(const QByteArray &data, quint8 expectedTag)
107+
QByteArrayView Card::parseFCI(QByteArrayView data, quint8 expectedTag)
107108
{
108-
for(auto i = data.constBegin(); i != data.constEnd(); ++i)
109+
for(auto i = data.begin(); i != data.end();)
109110
{
110-
quint8 tag(*i), size(*++i);
111+
quint8 tag(*i++), size(*i++);
111112
if(tag == expectedTag)
112-
return QByteArrayView(i + 1, size);
113-
switch(tag)
114-
{
115-
case 0x6F:
116-
case 0x62:
117-
case 0x64:
118-
case 0xA1: continue;
119-
default: i += size; break;
120-
}
113+
return {i, size};
114+
if((tag & 0x20) == 0)
115+
i += size;
121116
}
122-
return QByteArrayView();
117+
return {};
123118
}
124119

125120

@@ -291,6 +286,144 @@ bool IDEMIACard::updateCounters(QPCSCReader *reader, QSmartCardDataPrivate *d) c
291286

292287

293288

289+
const QByteArray THALESCard::AID = APDU("00A4040C 0C A000000063504B43532D3135");
290+
291+
QPCSCReader::Result THALESCard::change(QPCSCReader *reader, QSmartCardData::PinType type, const QString &pin_, const QString &newpin_) const
292+
{
293+
QByteArray cmd = CHANGE;
294+
QByteArray newpin = pinTemplate(newpin_);
295+
QByteArray pin = pinTemplate(pin_);
296+
cmd[3] = char(0x80 | type);
297+
cmd[4] = char(pin.size() + newpin.size());
298+
return transfer(reader, false, cmd + pin + newpin, type, quint8(pin.size()), true);
299+
}
300+
301+
bool THALESCard::isSupported(const QByteArray &atr)
302+
{
303+
return atr == "3BFF9600008031FE438031B85365494464B085051012233F1D";
304+
}
305+
306+
bool THALESCard::loadPerso(QPCSCReader *reader, QSmartCardDataPrivate *d) const
307+
{
308+
d->pukReplacable = false;
309+
if(d->data.isEmpty() && reader->transfer(APDU("00A4080C 02 DFDD")))
310+
{
311+
QByteArray cmd = APDU("00A4020C 02 5001");
312+
for(char data = 1; data <= 8; ++data)
313+
{
314+
cmd[6] = data;
315+
if(!reader->transfer(cmd))
316+
return false;
317+
QPCSCReader::Result result = reader->transfer(READBINARY);
318+
if(!result)
319+
return false;
320+
QString record = QString::fromUtf8(result.data.trimmed());
321+
if(record == QChar(0))
322+
record.clear();
323+
switch(data)
324+
{
325+
case QSmartCardData::SurName:
326+
case QSmartCardData::FirstName:
327+
case QSmartCardData::Citizen:
328+
case QSmartCardData::Id:
329+
case QSmartCardData::DocumentId:
330+
d->data[QSmartCardData::PersonalDataType(data)] = record;
331+
break;
332+
case QSmartCardData::BirthDate:
333+
if(!record.isEmpty())
334+
d->data[QSmartCardData::BirthDate] = QDate::fromString(record.left(10), QStringLiteral("dd MM yyyy"));
335+
break;
336+
case QSmartCardData::Expiry:
337+
d->data[QSmartCardData::Expiry] = QDateTime::fromString(record, QStringLiteral("dd MM yyyy")).addDays(1).addSecs(-1);
338+
break;
339+
default: break;
340+
}
341+
}
342+
}
343+
344+
bool readFailed = false;
345+
auto readCert = [&](const QByteArray &path) {
346+
QPCSCReader::Result data = reader->transfer(path);
347+
if(!data)
348+
{
349+
readFailed = true;
350+
return QSslCertificate();
351+
}
352+
auto sizeTag = parseFCI(data.data, 0x81);
353+
if(sizeTag.isEmpty())
354+
return QSslCertificate();
355+
QByteArray cert;
356+
QByteArray cmd = READBINARY;
357+
for(int size = quint8(sizeTag[0]) << 8 | quint8(sizeTag[1]); cert.size() < size;)
358+
{
359+
cmd[2] = char(cert.size() >> 8);
360+
cmd[3] = char(cert.size());
361+
data = reader->transfer(cmd);
362+
if(!data)
363+
{
364+
readFailed = true;
365+
return QSslCertificate();
366+
}
367+
cert += data.data;
368+
}
369+
return QSslCertificate(cert, QSsl::Der);
370+
};
371+
if(d->authCert.isNull())
372+
d->authCert = readCert(APDU("00A40804 04 ADF1 3411 00"));
373+
if(d->signCert.isNull())
374+
d->signCert = readCert(APDU("00A40804 04 ADF2 3421 00"));
375+
376+
if(readFailed)
377+
return false;
378+
return updateCounters(reader, d);
379+
}
380+
381+
QByteArray THALESCard::pinTemplate(const QString &pin)
382+
{
383+
QByteArray result = pin.toUtf8();
384+
result += QByteArray(12 - result.size(), char(0x00));
385+
return result;
386+
}
387+
388+
QPCSCReader::Result THALESCard::replace(QPCSCReader *reader, QSmartCardData::PinType type, const QString &puk_, const QString &pin_) const
389+
{
390+
QByteArray puk = pinTemplate(puk_);
391+
QByteArray pin = pinTemplate(pin_);
392+
QByteArray cmd = REPLACE;
393+
cmd[3] = char(0x80 | type);
394+
cmd[4] = char(puk.size() + pin.size());
395+
return transfer(reader, false, cmd + puk + pin, type, quint8(puk.size()), true);
396+
}
397+
398+
QByteArray THALESCard::sign(QPCSCReader *reader, const QByteArray &dgst) const
399+
{
400+
if(!reader->transfer(APDU("002241B6 09 800154840101")))
401+
return {};
402+
403+
QByteArray send {0x90, char(dgst.size())};
404+
send.append(dgst);
405+
send.insert(0, char(send.size()));
406+
send.insert(0, APDU("002A90A0"));
407+
408+
if(!reader->transfer(send))
409+
return {};
410+
411+
return reader->transfer(APDU("002A9E9A 00")).data;
412+
}
413+
414+
bool THALESCard::updateCounters(QPCSCReader *reader, QSmartCardDataPrivate *d) const
415+
{
416+
if(auto data = reader->transfer(APDU("00CB00FF 05 A003830181 00")))
417+
d->retry[QSmartCardData::Pin1Type] = quint8(data.data[14]);
418+
if(auto data = reader->transfer(APDU("00CB00FF 05 A003830183 00")))
419+
d->retry[QSmartCardData::PukType] = quint8(data.data[14]);
420+
if(auto data = reader->transfer(APDU("00CB00FF 05 A003830182 00")))
421+
d->retry[QSmartCardData::Pin2Type] = quint8(data.data[14]);
422+
return true;
423+
}
424+
425+
426+
294427
QSharedPointer<QPCSCReader> QSmartCard::Private::connect(const QString &reader)
295428
{
296429
qCDebug(CLog) << "Connecting to reader" << reader;
@@ -315,7 +448,8 @@ QSmartCard::ErrorType QSmartCard::Private::handlePinResult(QPCSCReader *reader,
315448
case 0x6401: return QSmartCard::CancelError; // Cancel (OK, SCM)
316449
case 0x6402: return QSmartCard::DifferentError;
317450
case 0x6403: return QSmartCard::LenghtError;
318-
case 0x6983: return QSmartCard::BlockedError;
451+
case 0x6983:
452+
case 0x6984: return QSmartCard::BlockedError;
319453
case 0x6985:
320454
case 0x6A80: return QSmartCard::OldNewPinSameError;
321455
default: return QSmartCard::UnknownError;
@@ -450,7 +584,12 @@ void QSmartCard::reloadCard(const TokenData &token, bool reloadCounters)
450584
if(!selectedReader->connect() || !selectedReader->beginTransaction())
451585
return;
452586

453-
if(!IDEMIACard::isSupported(selectedReader->atr())) {
587+
std::unique_ptr<Card> card;
588+
if(IDEMIACard::isSupported(selectedReader->atr()))
589+
card = std::make_unique<IDEMIACard>();
590+
else if(THALESCard::isSupported(selectedReader->atr()))
591+
card = std::make_unique<THALESCard>();
592+
else {
454593
qDebug() << "Unsupported card";
455594
return;
456595
}
@@ -460,7 +599,7 @@ void QSmartCard::reloadCard(const TokenData &token, bool reloadCounters)
460599
t = d->t.d;
461600
t->reader = selectedReader->name();
462601
t->pinpad = selectedReader->isPinPad();
463-
d->card = std::make_unique<IDEMIACard>();
602+
d->card = std::move(card);
464603
if(d->card->loadPerso(selectedReader.data(), t))
465604
{
466605
d->t.d = std::move(t);

client/QSmartCard.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class QSmartCardData
6262

6363
bool isNull() const;
6464
bool isPinpad() const;
65+
bool isPUKReplacable() const;
6566
bool isValid() const;
6667

6768
QVariant data( PersonalDataType type ) const;

client/QSmartCard_p.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Card
4141
QSmartCardData::PinType type, quint8 newPINOffset, bool requestCurrentPIN);
4242
virtual bool updateCounters(QPCSCReader *reader, QSmartCardDataPrivate *d) const = 0;
4343

44-
static QByteArrayView parseFCI(const QByteArray &data, quint8 expectedTag);
44+
static QByteArrayView parseFCI(QByteArrayView data, quint8 expectedTag);
4545

4646
static const QByteArray CHANGE;
4747
static const QByteArray READBINARY;
@@ -64,6 +64,21 @@ class IDEMIACard: public Card
6464
static const QByteArray AID, AID_OT, AID_QSCD, ATR_COSMO8, ATR_COSMOX;
6565
};
6666

67+
class THALESCard: public Card
68+
{
69+
public:
70+
QPCSCReader::Result change(QPCSCReader *reader, QSmartCardData::PinType type, const QString &pin, const QString &newpin) const final;
71+
bool loadPerso(QPCSCReader *reader, QSmartCardDataPrivate *d) const final;
72+
QPCSCReader::Result replace(QPCSCReader *reader, QSmartCardData::PinType type, const QString &puk, const QString &pin) const final;
73+
QByteArray sign(QPCSCReader *reader, const QByteArray &dgst) const final;
74+
bool updateCounters(QPCSCReader *reader, QSmartCardDataPrivate *d) const final;
75+
76+
static bool isSupported(const QByteArray &atr);
77+
static QByteArray pinTemplate(const QString &pin);
78+
79+
static const QByteArray AID;
80+
};
81+
6782
class QSmartCard::Private
6883
{
6984
public:
@@ -84,4 +99,5 @@ class QSmartCardDataPrivate: public QSharedData
8499
SslCertificate authCert, signCert;
85100
QHash<QSmartCardData::PinType,quint8> retry;
86101
bool pinpad = false;
102+
bool pukReplacable = true;
87103
};

0 commit comments

Comments
 (0)