Skip to content

Commit f462116

Browse files
committed
Thales card support
IB-8171 Signed-off-by: Raul Metsma <raul@metsma.ee>
1 parent 2eff025 commit f462116

File tree

10 files changed

+192
-19
lines changed

10 files changed

+192
-19
lines changed

client/Diagnostics.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ void Diagnostics::generalInfo(QTextStream &s)
121121
constexpr auto APDU = &QByteArray::fromHex;
122122
auto printAID = [&](const QString &label, const QByteArray &apdu)
123123
{
124-
constexpr auto APDU = &QByteArray::fromHex;
125124
QPCSCReader::Result r = reader.transfer(apdu);
126125
s << label << ": " << Qt::hex << r.SW;
127126
if (r.SW == 0x9000) s << " (OK)";

client/QSmartCard.cpp

Lines changed: 147 additions & 3 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

@@ -285,6 +286,143 @@ bool IDEMIACard::updateCounters(QPCSCReader *reader, QSmartCardDataPrivate *d) c
285286

286287

287288

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+
QHash<quint8,QByteArray> fci = parseFCI(data.data);
353+
354+
QByteArray cert;
355+
QByteArray cmd = READBINARY;
356+
for(int size = quint8(fci[0x81][0]) << 8 | quint8(fci[0x81][1]); cert.size() < size;)
357+
{
358+
cmd[2] = char(cert.size() >> 8);
359+
cmd[3] = char(cert.size());
360+
data = reader->transfer(cmd);
361+
if(!data)
362+
{
363+
readFailed = true;
364+
return QSslCertificate();
365+
}
366+
cert += data.data;
367+
}
368+
return QSslCertificate(cert, QSsl::Der);
369+
};
370+
if(d->authCert.isNull())
371+
d->authCert = readCert(APDU("00A40804 04 ADF1 3411 00"));
372+
if(d->signCert.isNull())
373+
d->signCert = readCert(APDU("00A40804 04 ADF2 3421 00"));
374+
375+
if(readFailed)
376+
return false;
377+
return updateCounters(reader, d);
378+
}
379+
380+
QByteArray THALESCard::pinTemplate(const QString &pin)
381+
{
382+
QByteArray result = pin.toUtf8();
383+
result += QByteArray(12 - result.size(), char(0x00));
384+
return result;
385+
}
386+
387+
QPCSCReader::Result THALESCard::replace(QPCSCReader *reader, QSmartCardData::PinType type, const QString &puk_, const QString &pin_) const
388+
{
389+
QByteArray puk = pinTemplate(puk_);
390+
QByteArray pin = pinTemplate(pin_);
391+
QByteArray cmd = REPLACE;
392+
cmd[3] = char(0x80 | type);
393+
cmd[4] = char(puk.size() + pin.size());
394+
return transfer(reader, false, cmd + puk + pin, type, quint8(puk.size()), true);
395+
}
396+
397+
QByteArray THALESCard::sign(QPCSCReader *reader, const QByteArray &dgst) const
398+
{
399+
if(!reader->transfer(APDU("002241B6 09 800154840101")))
400+
return {};
401+
402+
QByteArray send {0x90, char(dgst.size())};
403+
send.append(dgst);
404+
send.insert(0, char(send.size()));
405+
send.insert(0, APDU("002A90A0"));
406+
407+
if(!reader->transfer(send))
408+
return {};
409+
410+
return reader->transfer(APDU("002A9E9A 00")).data;
411+
}
412+
413+
bool THALESCard::updateCounters(QPCSCReader *reader, QSmartCardDataPrivate *d) const
414+
{
415+
if(auto data = reader->transfer(APDU("00CB00FF 05 A003830181 00")))
416+
d->retry[QSmartCardData::Pin1Type] = quint8(data.data[14]);
417+
if(auto data = reader->transfer(APDU("00CB00FF 05 A003830183 00")))
418+
d->retry[QSmartCardData::PukType] = quint8(data.data[14]);
419+
if(auto data = reader->transfer(APDU("00CB00FF 05 A003830182 00")))
420+
d->retry[QSmartCardData::Pin2Type] = quint8(data.data[14]);
421+
return true;
422+
}
423+
424+
425+
288426
QSharedPointer<QPCSCReader> QSmartCard::Private::connect(const QString &reader)
289427
{
290428
qCDebug(CLog) << "Connecting to reader" << reader;
@@ -309,7 +447,8 @@ QSmartCard::ErrorType QSmartCard::Private::handlePinResult(QPCSCReader *reader,
309447
case 0x6401: return QSmartCard::CancelError; // Cancel (OK, SCM)
310448
case 0x6402: return QSmartCard::DifferentError;
311449
case 0x6403: return QSmartCard::LenghtError;
312-
case 0x6983: return QSmartCard::BlockedError;
450+
case 0x6983:
451+
case 0x6984: return QSmartCard::BlockedError;
313452
case 0x6985:
314453
case 0x6A80: return QSmartCard::OldNewPinSameError;
315454
default: return QSmartCard::UnknownError;
@@ -444,7 +583,12 @@ void QSmartCard::reloadCard(const TokenData &token, bool reloadCounters)
444583
if(!selectedReader->connect() || !selectedReader->beginTransaction())
445584
return;
446585

447-
if(!IDEMIACard::isSupported(selectedReader->atr())) {
586+
std::unique_ptr<Card> card;
587+
if(IDEMIACard::isSupported(selectedReader->atr()))
588+
card = std::make_unique<IDEMIACard>();
589+
else if(THALESCard::isSupported(selectedReader->atr()))
590+
card = std::make_unique<THALESCard>();
591+
else {
448592
qDebug() << "Unsupported card";
449593
return;
450594
}
@@ -454,7 +598,7 @@ void QSmartCard::reloadCard(const TokenData &token, bool reloadCounters)
454598
t = d->t.d;
455599
t->reader = selectedReader->name();
456600
t->pinpad = selectedReader->isPinPad();
457-
d->card = std::make_unique<IDEMIACard>();
601+
d->card = std::move(card);
458602
if(d->card->loadPerso(selectedReader.data(), t))
459603
{
460604
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ class IDEMIACard: public Card
6464
static const QByteArray AID, AID_OT, AID_QSCD;
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
};

client/SslCertificate.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,10 @@ QString SslCertificate::toString( const QString &format ) const
262262

263263
SslCertificate::CertType SslCertificate::type() const
264264
{
265-
for(const QString &p: policies())
265+
// https://www.id.ee/wp-content/uploads/2022/02/cp_esteid_01.10.2018_version1.0.pdf
266+
for(QString p: policies())
266267
{
268+
p.remove(QLatin1String("2.999.")); // test certificates
267269
if(p.startsWith(QLatin1String("1.3.6.1.4.1.10015.1.1")) ||
268270
p.startsWith(QLatin1String("1.3.6.1.4.1.10015.3.1")) ||
269271
p.startsWith(QLatin1String("1.3.6.1.4.1.10015.1.2")) ||
@@ -283,12 +285,15 @@ SslCertificate::CertType SslCertificate::type() const
283285
p.startsWith(QLatin1String("1.3.6.1.4.1.51361.1.2.3")))
284286
return DigiIDType;
285287
if(p.startsWith(QLatin1String("1.3.6.1.4.1.51361.1.1.4")) ||
286-
p.startsWith(QLatin1String("1.3.6.1.4.1.51361.1.2.4")))
288+
p.startsWith(QLatin1String("1.3.6.1.4.1.51361.1.2.4")) ||
289+
p.startsWith(QLatin1String("1.3.6.1.4.1.51361.2.1.6")))
287290
return EResidentType;
288291
if(p.startsWith(QLatin1String("1.3.6.1.4.1.51361.1.1")) ||
289-
p.startsWith(QLatin1String("1.3.6.1.4.1.51455.1.1")) ||
290292
p.startsWith(QLatin1String("1.3.6.1.4.1.51361.1.2")) ||
291-
p.startsWith(QLatin1String("1.3.6.1.4.1.51455.1.2")))
293+
p.startsWith(QLatin1String("1.3.6.1.4.1.51361.2.1")) ||
294+
p.startsWith(QLatin1String("1.3.6.1.4.1.51455.1.1")) ||
295+
p.startsWith(QLatin1String("1.3.6.1.4.1.51455.1.2")) ||
296+
p.startsWith(QLatin1String("1.3.6.1.4.1.51455.2.1")))
292297
return EstEidType;
293298
}
294299

client/translations/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,6 +2960,10 @@ Additional licenses and components</translation>
29602960
<source>Certificate status check failed. Please check your internet connection.</source>
29612961
<translation>Certificate status check failed. Please check your internet connection.</translation>
29622962
</message>
2963+
<message>
2964+
<source>The PUK-code cannot be changed on the ID-card in the reader</source>
2965+
<translation>The PUK-code cannot be changed on the ID-card in the reader</translation>
2966+
</message>
29632967
</context>
29642968
<context>
29652969
<name>WarningDialog</name>

client/translations/et.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,6 +2960,10 @@ Täiendavad litsentsid ja komponendid</translation>
29602960
<source>Certificate status check failed. Please check your internet connection.</source>
29612961
<translation>Sertifikaadi staatuse kontrollimine ebaõnnestus. Palun kontrolli internetiühendust.</translation>
29622962
</message>
2963+
<message>
2964+
<source>The PUK-code cannot be changed on the ID-card in the reader</source>
2965+
<translation>Lugejas oleval ID-kaardil ei saa PUK-koodi muuta</translation>
2966+
</message>
29632967
</context>
29642968
<context>
29652969
<name>WarningDialog</name>

client/translations/ru.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2961,6 +2961,10 @@ Additional licenses and components</source>
29612961
<source>Change PUK</source>
29622962
<translation>Изменить PUK</translation>
29632963
</message>
2964+
<message>
2965+
<source>The PUK-code cannot be changed on the ID-card in the reader</source>
2966+
<translation>Невозможно изменить PUK-код ID-карты, находящейся в считывателе</translation>
2967+
</message>
29642968
</context>
29652969
<context>
29662970
<name>WarningDialog</name>

client/widgets/VerifyCert.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ VerifyCert::VerifyCert(QWidget *parent)
7070
ui->details->setFont( regular12 );
7171
ui->checkCert->setFont(regular12);
7272
ui->changePIN->setFont( Styles::font( Styles::Condensed, 14 ) );
73-
ui->tempelText->setFont( Styles::font( Styles::Regular, 14 ) );
74-
ui->tempelText->hide();
73+
ui->infoText->setFont( Styles::font( Styles::Regular, 14 ) );
74+
ui->infoText->hide();
7575
}
7676

7777
VerifyCert::~VerifyCert()
@@ -109,6 +109,7 @@ void VerifyCert::update()
109109
bool isBlockedPuk = !cardData.isNull() && cardData.retryCount( QSmartCardData::PukType ) == 0;
110110
bool isTempelType = c.type() & SslCertificate::TempelType;
111111
isValidCert = c.isNull() || c.isValid();
112+
ui->infoText->setVisible(isTempelType);
112113

113114
QString txt;
114115
QTextStream cert( &txt );
@@ -155,7 +156,9 @@ void VerifyCert::update()
155156
ui->validUntil->setText(tr("The PUK code is located in your envelope"));
156157
ui->validUntil->setHidden(isBlockedPuk);
157158
ui->changePIN->setText(tr("Change PUK"));
158-
ui->changePIN->setHidden(isBlockedPuk);
159+
ui->changePIN->setHidden(isBlockedPuk || !cardData.isPUKReplacable());
160+
ui->infoText->setVisible(!cardData.isPUKReplacable());
161+
ui->infoText->setText(tr("The PUK-code cannot be changed on the ID-card in the reader"));
159162
ui->forgotPinLink->hide();
160163
ui->details->hide();
161164
ui->checkCert->hide();
@@ -213,7 +216,6 @@ void VerifyCert::update()
213216
ui->nameIcon->setHidden(pinType == QSmartCardData::PukType);
214217
}
215218
ui->error->setHidden(ui->error->text().isEmpty());
216-
ui->tempelText->setVisible(isTempelType);
217219
ui->changePIN->setAccessibleName(ui->changePIN->text().toLower());
218220

219221
if(pinType == QSmartCardData::Pin1Type)

client/widgets/VerifyCert.ui

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,13 @@ background-color: #F8DDA7;</string>
213213
</widget>
214214
</item>
215215
<item>
216-
<widget class="QLabel" name="tempelText">
216+
<widget class="QLabel" name="infoText">
217217
<property name="minimumSize">
218218
<size>
219219
<width>0</width>
220220
<height>30</height>
221221
</size>
222222
</property>
223-
<property name="maximumSize">
224-
<size>
225-
<width>16777215</width>
226-
<height>30</height>
227-
</size>
228-
</property>
229223
<property name="font">
230224
<font>
231225
<family>Roboto</family>

0 commit comments

Comments
 (0)