Skip to content

Commit 91616f6

Browse files
committed
Thales card support
IB-8171 Signed-off-by: Raul Metsma <raul@metsma.ee>
1 parent 59b07c1 commit 91616f6

10 files changed

Lines changed: 194 additions & 20 deletions

File tree

client/QSmartCard.cpp

Lines changed: 148 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

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

292293

293294

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

453-
if(!IDEMIACard::isSupported(selectedReader->atr())) {
593+
std::unique_ptr<Card> card;
594+
if(IDEMIACard::isSupported(selectedReader->atr()))
595+
card = std::make_unique<IDEMIACard>();
596+
else if(THALESCard::isSupported(selectedReader->atr()))
597+
card = std::make_unique<THALESCard>();
598+
else {
454599
qDebug() << "Unsupported card";
455600
return;
456601
}
@@ -460,7 +605,7 @@ void QSmartCard::reloadCard(const TokenData &token, bool reloadCounters)
460605
t = d->t.d;
461606
t->reader = selectedReader->name();
462607
t->pinpad = selectedReader->isPinPad();
463-
d->card = std::make_unique<IDEMIACard>();
608+
d->card = std::move(card);
464609
if(d->card->loadPerso(selectedReader.data(), t))
465610
{
466611
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, 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
};

client/SslCertificate.cpp

Lines changed: 7 additions & 3 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."));
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")) ||
@@ -286,9 +288,11 @@ SslCertificate::CertType SslCertificate::type() const
286288
p.startsWith(QLatin1String("1.3.6.1.4.1.51361.1.2.4")))
287289
return EResidentType;
288290
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")) ||
290291
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")))
292+
p.startsWith(QLatin1String("1.3.6.1.4.1.51361.2.1")) ||
293+
p.startsWith(QLatin1String("1.3.6.1.4.1.51455.1.1")) ||
294+
p.startsWith(QLatin1String("1.3.6.1.4.1.51455.1.2")) ||
295+
p.startsWith(QLatin1String("1.3.6.1.4.1.51455.2.1")))
292296
return EstEidType;
293297
}
294298

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: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2897,6 +2897,10 @@ Additional licenses and components</source>
28972897
<source>The PUK code is located in your envelope</source>
28982898
<translation>PUK-код находится в конверте</translation>
28992899
</message>
2900+
<message>
2901+
<source>Change PUK</source>
2902+
<translation>Изменить PUK</translation>
2903+
</message>
29002904
<message>
29012905
<source>PUK code is blocked because the PUK code has been entered 3 times incorrectly. You can not unblock the PUK code yourself. As long as the PUK code is blocked, all eID options can be used, except PUK code. Please visit the service center to obtain new codes. &lt;a href=&quot;https://www.politsei.ee/en/instructions/applying-for-an-id-card-for-an-adult/reminders-for-id-card-holders/&quot;&gt;Additional information&lt;/a&gt;.</source>
29022906
<translation>PUK заблокирован, потому что PUK введен неверно 3 раза. PUK невозможно разблокировать самому. &lt;br&gt;&lt;br&gt;Пока PUK-код заблокирован, можно использовать все возможности eID, кроме тех которые требуют PUK. &lt;br&gt;&lt;br&gt;Для получения нового PUK-кода для своей ID-карты, посетите центр обслуживания, где вы сможете получить конверт с кодами. &lt;a href=&quot;https://www.politsei.ee/ru/instruktsii/hodataystvo-o-vydache-id-karty-vzroslomu/pamyatka-dlya-polzovatelya-id-karti/&quot;&gt;Дополнительная информация&lt;/a&gt;.</translation>
@@ -2958,8 +2962,8 @@ Additional licenses and components</source>
29582962
<translation>Проверка статуса сертификата не удалась. Пожалуйста проверьте подключение к интернету.</translation>
29592963
</message>
29602964
<message>
2961-
<source>Change PUK</source>
2962-
<translation>Изменить PUK</translation>
2965+
<source>The PUK-code cannot be changed on the ID-card in the reader</source>
2966+
<translation>Невозможно изменить PUK-код ID-карты, находящейся в считывателе</translation>
29632967
</message>
29642968
</context>
29652969
<context>

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>

common

0 commit comments

Comments
 (0)