diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index f9422daf6..2f3989a4c 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -56,6 +56,38 @@ CKey::CKey(QSslCertificate _rcpt_cert) : lock(libcdoc::Lock::PUBLIC_KEY), rcpt_c lock.setBytes(libcdoc::Lock::CERT, std::vector(der.cbegin(), der.cend())); } +bool +CKey::canDecrypt(const QSslCertificate &cert) const +{ + if(!lock.isPKI() || !isSupported()) + return false; + const std::vector &key = lock.getBytes(libcdoc::Lock::RCPT_KEY); + // RCPT_KEY is i2d_PublicKey, not the SubjectPublicKeyInfo QSslKey::toDer() gives + return SslCertificate(cert).publicKeyDer() == + QByteArray::fromRawData((const char*)key.data(), qsizetype(key.size())); +} + +bool +CKey::isSupported() const +{ + if(!lock.isCDoc1()) + return lock.isValid(); + if(lock.encrypted_fmk.empty()) + return false; + const std::string method = lock.getString(libcdoc::Lock::METHOD); + switch(lock.pk_type) + { + case libcdoc::Algorithm::RSA: + return method == libcdoc::Crypto::RSA_MTH; + case libcdoc::Algorithm::ECC: + return !lock.getBytes(libcdoc::Lock::KEY_MATERIAL).empty() && + (method == libcdoc::Crypto::KWAES128_MTH || method == libcdoc::Crypto::KWAES192_MTH || + method == libcdoc::Crypto::KWAES256_MTH); + default: + return false; + } +} + bool CKey::operator==(const CKey& rhs) const { diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index 983d3572a..2cb251d63 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -47,6 +47,8 @@ struct CKey { libcdoc::Lock lock; QSslCertificate rcpt_cert; + bool canDecrypt(const QSslCertificate &cert) const; + bool isSupported() const; bool operator== (const CKey& rhs) const; }; diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index 2468c357c..19b47fb8b 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -74,7 +74,11 @@ MainWindow::MainWindow( QWidget *parent ) selectPage(Pages(page)); }); connect(ui->help, &QToolButton::clicked, qApp, &Application::openHelp); - connect(ui->settings, &QToolButton::clicked, this, [this] { showSettings(SettingsDialog::GeneralSettings); }); + connect(ui->settings, &QToolButton::clicked, this, [this] { + auto page = ui->startScreen->currentIndex(); + bool isEncryption = page == CryptoIntro || page == CryptoDetails; + showSettings(isEncryption ? SettingsDialog::EncryptionSettings : SettingsDialog::GeneralSettings); + }); // Refresh card info on "My EID" page connect(ui->eidPage, &MyEidInfo::dataChanged, this, &MainWindow::updateMyEid); diff --git a/client/QCryptoBackend.cpp b/client/QCryptoBackend.cpp index 60ceb2293..8a649da15 100644 --- a/client/QCryptoBackend.cpp +++ b/client/QCryptoBackend.cpp @@ -74,16 +74,7 @@ QCryptoBackend::getBackend(const TokenData& token, const QString &pin) { if(backend->cert().isNull()) return backend.release(); - Status status; - if(pin.isEmpty()) - { - do { - status = backend->login(token, {}); - } while (status == PinIncorrect); - } - else - status = backend->login(token, pin); - if (status != PinOK) + if(Status status = backend->login(token, pin); status != PinOK) return std::unexpected(status); return backend.release(); @@ -94,14 +85,18 @@ QSslCertificate QCryptoBackend::cert() const return token.cert(); } -QString QCryptoBackend::errorString(Status error) +QString QCryptoBackend::errorString(Status error, const TokenData &token) { switch( error ) { case PinOK: return QString(); case PinCanceled: return tr("PIN entry canceled"); case PinLocked: return tr("PIN locked"); - case PinIncorrect: return tr("PIN incorrect"); + case PinIncorrect: + if(QSmartCardData data = QSmartCard::readCard(token, true); !data.isNull()) + return tr("PIN incorrect") + QStringLiteral(". ") + + tr("Remaining attempts: %1").arg(data.retryCount(QSmartCardData::pinType(token.cert()))); + return tr("PIN incorrect"); case InProgress: return tr("Signing/decrypting is already in progress another window."); case GeneralError: return tr("PKCS11 general error"); case DeviceError: return tr("PKCS11 device error"); @@ -251,7 +246,10 @@ void QCryptoManager::refresh() { QWriteLocker locker(&d->lock); if(!std::ranges::equal(newCache, d->cache, [](const TokenData &a, const TokenData &b) { - return a == b && a.data(QStringLiteral("blocked")) == b.data(QStringLiteral("blocked")); + return a == b && + a.data(QStringLiteral("blocked")) == b.data(QStringLiteral("blocked")) && + a.data(QStringLiteral("pinCountLow")) == b.data(QStringLiteral("pinCountLow")) && + a.data(QStringLiteral("pinFinalTry")) == b.data(QStringLiteral("pinFinalTry")); })) { d->cache = newCache; diff --git a/client/QCryptoBackend.h b/client/QCryptoBackend.h index dc1c337b8..e8b025fb4 100644 --- a/client/QCryptoBackend.h +++ b/client/QCryptoBackend.h @@ -74,7 +74,7 @@ class QCryptoBackend */ mutable Status status = PinOK; - static QString errorString(Status error); + static QString errorString(Status error, const TokenData &token = {}); protected: virtual Status login(const TokenData &cert, const QString &pin) = 0; diff --git a/client/QPKCS11.cpp b/client/QPKCS11.cpp index 0832ae90c..b0e0337e7 100644 --- a/client/QPKCS11.cpp +++ b/client/QPKCS11.cpp @@ -26,7 +26,6 @@ #include "SslCertificate.h" #include "TokenData.h" #include "Utils.h" -#include "dialogs/PinPopup.h" #include #include @@ -387,33 +386,16 @@ QPKCS11::Status QPKCS11::login(const TokenData &t, const QString &pin) if(!d->l->isFinDriver && !(token.flags & CKF_LOGIN_REQUIRED)) return PinOK; - SslCertificate cert(t.cert()); - bool isSign = cert.keyUsage().keys().contains(SslCertificate::NonRepudiation); - PinPopup::TokenFlags f; if(token.flags & CKF_USER_PIN_LOCKED) return PinLocked; - if(token.flags & CKF_USER_PIN_COUNT_LOW) f = PinPopup::PinCountLow; - if(token.flags & CKF_USER_PIN_FINAL_TRY) f = PinPopup::PinFinalTry; - CK_RV err = dispatchToMain([&]{ - if(token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) { - f |= PinPopup::PinpadFlag; - PinPopup p(isSign ? QSmartCardData::Pin2Type : QSmartCardData::Pin1Type, f, cert, Application::mainWindow()); - p.open(); - p.startTimer(); - return waitFor(d->l->f->C_Login, d->session, CKU_USER, nullptr, 0); - } else { - QByteArray pinData; - if(pin.isEmpty()) { - PinPopup p(isSign ? QSmartCardData::Pin2Type : QSmartCardData::Pin1Type, f, cert, Application::mainWindow()); - p.setPinLen(token.ulMinPinLen, token.ulMaxPinLen < 12 ? 12 : token.ulMaxPinLen); - if(!p.exec()) - return CKR_FUNCTION_CANCELED; - pinData = p.pin().toUtf8(); - } else { - pinData = pin.toUtf8(); - } - return d->l->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pinData.constData()), CK_ULONG(pinData.size())); - } - }); + CK_RV err {}; + if(token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) { + err = waitFor(d->l->f->C_Login, d->session, CKU_USER, nullptr, 0); + } else if(pin.isEmpty()) { + return PinCanceled; + } else { + QByteArray pinData = pin.toUtf8(); + err = d->l->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pinData.constData()), CK_ULONG(pinData.size())); + } switch(err) { @@ -477,6 +459,8 @@ QList QPKCS11::tokens() t.setData(QStringLiteral("id"), id); t.setData(QStringLiteral("blocked"), (token.flags & (CKF_USER_PIN_LOCKED|CKF_USER_PIN_TO_BE_CHANGED)) > 0); t.setData(QStringLiteral("pinpad"), (token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) > 0); + t.setData(QStringLiteral("pinCountLow"), (token.flags & CKF_USER_PIN_COUNT_LOW) > 0); + t.setData(QStringLiteral("pinFinalTry"), (token.flags & CKF_USER_PIN_FINAL_TRY) > 0); t.setData(QStringLiteral("pinMin"), int(token.ulMinPinLen)); t.setData(QStringLiteral("pinMax"), int(token.ulMaxPinLen)); diff --git a/client/QSmartCard.cpp b/client/QSmartCard.cpp index 41059a0bf..76514a13f 100644 --- a/client/QSmartCard.cpp +++ b/client/QSmartCard.cpp @@ -24,7 +24,7 @@ #include "QCryptoBackend.h" #include "Settings.h" #include "Utils.h" -#include "dialogs/PinPopup.h" +#include "dialogs/PinpadPopup.h" #include "dialogs/PinUnblock.h" #include "effects/FadeInNotification.h" @@ -78,6 +78,11 @@ quint8 QSmartCardData::minPinLen(QSmartCardData::PinType type) return 4; } +QSmartCardData::PinType QSmartCardData::pinType(const QSslCertificate &cert) +{ + return SslCertificate(cert).keyUsage().contains(SslCertificate::NonRepudiation) ? Pin2Type : Pin1Type; +} + QString QSmartCardData::typeString(QSmartCardData::PinType type) { switch(type) @@ -499,7 +504,7 @@ bool QSmartCard::pinChange(TokenData token, QSmartCardData current, QSmartCardDa if(current.isNull()) return false; auto notify = qScopeGuard([token] { qApp->cryptoManager()->refreshToken(token); }); - std::unique_ptr popup; + std::unique_ptr popup; QByteArray oldPin, newPin; QSmartCardData::PinType src = type; if(action != QSmartCard::ChangeWithPin && action != QSmartCard::ActivateWithPin) @@ -518,30 +523,7 @@ bool QSmartCard::pinChange(TokenData token, QSmartCardData current, QSmartCardDa if(current.isPinpad()) { - QString bodyText; - switch(action) - { - case QSmartCard::ActivateWithPuk: - case QSmartCard::ChangeWithPuk: - bodyText = tr("To change %1 code with the PUK code on a PinPad reader the PUK code has to be entered first and then the %1 code twice.").arg(QSmartCardData::typeString(type)); - break; - case QSmartCard::UnblockWithPuk: - bodyText = tr("To unblock the %1 code on a PinPad reader the PUK code has to be entered first and then the %1 code twice.").arg(QSmartCardData::typeString(type)); - break; - default: - bodyText = tr("To change %1 on a PinPad reader the old %1 code has to be entered first and then the new %1 code twice.").arg(QSmartCardData::typeString(type)); - break; - } - - PinPopup::TokenFlags flags = PinPopup::PinpadChangeFlag; - switch(current.retryCount(src)) - { - case 2: flags |= PinPopup::PinCountLow; break; - case 1: flags |= PinPopup::PinFinalTry; break; - case 0: flags |= PinPopup::PinLocked; break; - default: break; - } - popup.reset(new PinPopup(src, flags, current.authCert(), parent, bodyText)); + popup.reset(new PinpadPopup(type, action, current, parent)); popup->open(); oldPin = card->pinTemplate({}); newPin = card->pinTemplate({}); diff --git a/client/QSmartCard.h b/client/QSmartCard.h index 01f788b6f..b26c2fea6 100644 --- a/client/QSmartCard.h +++ b/client/QSmartCard.h @@ -26,6 +26,7 @@ class SslCertificate; class QSmartCardDataPrivate; +class QSslCertificate; class QSslKey; class TokenData; class QWidget; @@ -73,6 +74,7 @@ class QSmartCardData bool pinLocked(PinType type) const; static quint8 minPinLen(QSmartCardData::PinType type); + static PinType pinType(const QSslCertificate &cert); static QString typeString( PinType type ); private: diff --git a/client/dialogs/DecryptionDialog.cpp b/client/dialogs/DecryptionDialog.cpp new file mode 100644 index 000000000..088365560 --- /dev/null +++ b/client/dialogs/DecryptionDialog.cpp @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "DecryptionDialog.h" +#include "ui_DecryptionDialog.h" + +#include "Application.h" +#include "CryptoDoc.h" +#include "QCryptoBackend.h" +#include "SslCertificate.h" +#include "dialogs/PinLogin.h" +#include "dialogs/WarningDialog.h" +#include "effects/Overlay.h" +#include "widgets/CardListItem.h" + +#include + +#include + +DecryptionDialog::DecryptionDialog(const CKey &key, const QString &recipient, QWidget *parent) + : QDialog(parent) + , m_overlay(std::make_unique(this)) + , m_key(key) + , ui(new Ui::DecryptionDialog) +{ + ui->setupUi(this); + ui->progressPage->hide(); + setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint); +#ifdef Q_OS_WIN + ui->buttonLayout->setDirection(QBoxLayout::RightToLeft); +#endif + + ui->recipientIcon->load(QStringLiteral(":/images/icon_Minu_eID_hover.svg")); + ui->recipientName->setText(recipient); + + ui->noCardIcon->load(QStringLiteral(":/images/icon_IDkaart_red.svg")); + updateCards(); + connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &DecryptionDialog::updateCards); + connect(ui->pin, &QLineEdit::textEdited, this, [this] { + PinLogin::clearError(ui); + ui->decrypt->setEnabled(ui->pin->hasAcceptableInput()); + }); + connect(ui->pin, &QLineEdit::returnPressed, ui->decrypt, &QPushButton::click); + + connect(ui->decrypt, &QPushButton::clicked, this, [this] { + if(performLogin()) + accept(); + }); + connect(ui->cancel, &QPushButton::clicked, this, &QDialog::reject); + connect(this, &QDialog::finished, this, &QDialog::close); +} + +DecryptionDialog::~DecryptionDialog() +{ + delete ui; +} + +std::unique_ptr DecryptionDialog::takeBackend() +{ + return std::move(m_backend); +} + +void DecryptionDialog::changeEvent(QEvent *event) +{ + if(event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + updateNoCard(); + updatePinControls(); + } + QDialog::changeEvent(event); +} + +void DecryptionDialog::reject() +{ + if(!ui->progressPage->isVisible()) + QDialog::reject(); +} + +void DecryptionDialog::updateNoCard() +{ + ui->noCardStatus->setText(CardList::emptyText()); + ui->noCard->setAccessibleDescription(ui->noCardStatus->text()); +} + +void DecryptionDialog::updateCards() +{ + qDeleteAll(ui->cardListContainer->findChildren()); + + auto usableToken = [this](const TokenData &token) { + SslCertificate c(token.cert()); + return c.isValid() && !token.data(QStringLiteral("blocked")).toBool() && m_key.canDecrypt(token.cert()); + }; + + const QList tokens = qApp->cryptoManager()->cache(); + if(std::none_of(tokens.cbegin(), tokens.cend(), + [this](const TokenData &t) { return t == m_selectedToken; })) + { + m_selectedToken = {}; + for(const TokenData &token: tokens) + { + if(!SslCertificate(token.cert()).isEncryptionCert()) + continue; + if(m_selectedToken.isNull()) + m_selectedToken = token; + if(usableToken(token)) + { + m_selectedToken = token; + break; + } + } + } + + bool any = false; + CardListItem *selectedCard = nullptr; + for(const TokenData &token: tokens) { + if(!SslCertificate(token.cert()).isEncryptionCert()) + continue; + auto *w = new CardListItem(ui->cardListContainer); + w->update(token, token == m_selectedToken, m_key.canDecrypt(token.cert()) ? + nullptr : QT_TRANSLATE_NOOP("CardListItem", "This card cannot decrypt the container")); + connect(w, &CardListItem::selected, this, &DecryptionDialog::updateSelection); + ui->cardListContainer->layout()->addWidget(w); + w->show(); + any = true; + if(token == m_selectedToken) + selectedCard = w; + } + + if(!any) + updateNoCard(); + ui->noCard->setVisible(!any); + ui->cardList->setVisible(any); + if(selectedCard) + QMetaObject::invokeMethod(ui->cardList, [list = ui->cardList, card = QPointer(selectedCard)] { + if(card) + list->ensureWidgetVisible(card, 0, 0); + }, Qt::QueuedConnection); + ui->labelCert->setVisible(any); + updateSelection(); +} + +void DecryptionDialog::updateSelection() +{ + PinLogin::updateSelection(ui, m_selectedToken); + updatePinControls(); +} + +void DecryptionDialog::updatePinControls() +{ + ui->decrypt->setEnabled(PinLogin::update(ui, m_selectedToken)); +} + +bool DecryptionDialog::performLogin() +{ + auto val = PinLogin::login(ui, m_selectedToken); + if(!val) { + switch(val.error()) { + case QCryptoBackend::PinIncorrect: + updatePinControls(); + return false; + case QCryptoBackend::PinCanceled: + return false; + default: + WarningDialog::create(this) + ->withTitle(tr("Failed to decrypt document")) + ->withText(QCryptoBackend::errorString(val.error())) + ->exec(); + return false; + } + } + m_backend.reset(val.value()); + return true; +} diff --git a/client/dialogs/DecryptionDialog.h b/client/dialogs/DecryptionDialog.h new file mode 100644 index 000000000..961d10d3f --- /dev/null +++ b/client/dialogs/DecryptionDialog.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once +#include + +#include "CryptoDoc.h" +#include "TokenData.h" + +namespace Ui { class DecryptionDialog; } +class Overlay; + +class DecryptionDialog final : public QDialog +{ + Q_OBJECT +public: + explicit DecryptionDialog(const CKey &key, const QString &recipient, + QWidget *parent = nullptr); + ~DecryptionDialog() final; + std::unique_ptr takeBackend(); + +private: + void changeEvent(QEvent *event) final; + void reject() final; + void updateCards(); + void updateSelection(); + void updatePinControls(); + void updateNoCard(); + bool performLogin(); + + std::unique_ptr m_backend; + std::unique_ptr m_overlay; + CKey m_key; + Ui::DecryptionDialog *ui; + TokenData m_selectedToken; +}; diff --git a/client/dialogs/DecryptionDialog.ui b/client/dialogs/DecryptionDialog.ui new file mode 100644 index 000000000..532a74522 --- /dev/null +++ b/client/dialogs/DecryptionDialog.ui @@ -0,0 +1,574 @@ + + + + + DecryptionDialog + + + Qt::WindowModal + + + + 0 + 0 + 380 + 700 + + + + Decrypt document + + + QWidget { +color: #07142A; +font-family: Roboto, Helvetica; +font-size: 14px; +} +#DecryptionDialog { +background-color: #FFFFFF; +border-radius: 4px; +} +#decryptionTitle { +color: #003168; +font-size: 20px; +font-weight: 700; +} +#recipientName { +font-weight: 700; +} +#labelCert { +color: #07142A; +} +#progressCode { +color: #003168; +font-size: 32px; +font-weight: 700; +} +QProgressBar { +background-color: #F3F5F7; +border: 1px solid #C4CBD8; +border-radius: 4px; +min-height: 6px; +max-height: 6px; +margin: 15px 5px; +} +QProgressBar::chunk { +border-radius: 3px; +background-color: #2F70B6; +} +QLineEdit { +padding: 10px 14px; +border: 1px solid #C4CBD8; +border-radius: 4px; +background-color: #FFFFFF; +placeholder-text-color: #607496; +font-size: 16px; +} +QLineEdit[label="error"] { +border-color: #BE7884; +} +QPushButton { +padding: 12px 12px; +border-radius: 4px; +border: 1px solid #AD2A45; +color: #AD2A45; +font-weight: 700; +} +QPushButton:hover, QPushButton:focus { +background-color: #F5EBED; +} +QPushButton:pressed { +background-color: #E1C1C6; +} +QPushButton:default { +color: #ffffff; +border-color: #2F70B6; +background-color: #2F70B6; +} +QPushButton:default:hover, QPushButton:default:focus { +border-color: #2B66A6; +background-color: #2B66A6; +} +QPushButton:default:pressed { +border-color: #215081; +background-color: #215081; +} +QPushButton:default:disabled { +border-color: #82A9D3; +background-color: #82A9D3; +} + + + + + 0 + + + QLayout::SetFixedSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 32 + + + 40 + + + 32 + + + 40 + + + 32 + + + + + 16 + + + + + + 300 + 0 + + + + Decryption + + + Qt::AlignCenter + + + + + + + 8 + + + + + + 24 + 24 + + + + + 24 + 24 + + + + + + + + Qt::TabFocus + + + true + + + + + + + + + + + + 12 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 20 + + + + Select the certificate to decrypt with + + + true + + + + + + + + 0 + 0 + + + + #cardList, #cardList > QWidget, #cardListContainer { +background-color: transparent; +} +QScrollBar { +border: none; +background: transparent; +} +QScrollBar:vertical { +width: 16px; +padding: 4px 4px 4px 4px; +margin: 0px; +} +QScrollBar::handle:vertical { +background-color: #B5BFCF; +border: none; +margin: 0px; +border-radius: 4px; +min-height: 16px; +} +QScrollBar::add-line:vertical, +QScrollBar::sub-line:vertical { +height: 0px; +border: none; +background: none; +} +QScrollBar::add-page, +QScrollBar::sub-page { +background: transparent; +} + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 8 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + QLabel { +color: #07142A; +font-family: Roboto, Helvetica; +font-size: 14px; +font-weight: 400; +} + + + + 8 + + + 8 + + + 0 + + + 8 + + + 0 + + + + + + 24 + 24 + + + + + 24 + 24 + + + + + + + + Qt::TabFocus + + + No card in reader + + + + + + + + + + Qt::TabFocus + + + Qt::PlainText + + + true + + + + + + + 6 + + + + + + 0 + 20 + + + + pin + + + + + + + + + + + 0 + 20 + + + + Qt::TabFocus + + + + + + + + + + + + 0 + + + + + PointingHandCursor + + + Cancel + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + PointingHandCursor + + + Decrypt + + + true + + + + + + + + + + + + + 24 + + + 40 + + + 32 + + + 40 + + + 32 + + + + + + 300 + 0 + + + + + + + Qt::AlignCenter + + + + + + + Qt::TabFocus + + + Qt::AlignCenter + + + + + + + Qt::AlignCenter + + + true + + + true + + + + + + + + 0 + 40 + + + + 30 + + + 0 + + + false + + + + + + + PointingHandCursor + + + Cancel + + + false + + + + + + + + + + + CardList + QScrollArea +
widgets/CardList.h
+ 1 +
+ + LineEdit + QLineEdit +
widgets/LineEdit.h
+
+ + PinLineEdit + LineEdit +
widgets/PinLineEdit.h
+
+ + QSvgWidget + QWidget +
QSvgWidget
+ 1 +
+
+ + +
diff --git a/client/dialogs/PinLogin.h b/client/dialogs/PinLogin.h new file mode 100644 index 000000000..bb5473f25 --- /dev/null +++ b/client/dialogs/PinLogin.h @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "QCryptoBackend.h" +#include "SslCertificate.h" +#include "widgets/CardListItem.h" + +#include +#include +#include +#include + +#include + +class PinLogin +{ + Q_DECLARE_TR_FUNCTIONS(PinLogin) +public: + template + static void updateSelection(Ui *ui, TokenData &token, bool refresh = true) + { + for(auto *w: ui->cardListContainer->template findChildren()) { + if(!w->isChecked()) + continue; + if(!(w->token() == token)) { + ui->pin->clear(); + clearError(ui); + } + if(refresh) + w->refresh(); + token = w->token(); + break; + } + } + + template + static bool update(Ui *ui, const TokenData &token) + { + const bool usable = std::ranges::any_of(ui->cardListContainer->template findChildren(), + [](CardListItem *w) { return w->isChecked() && w->isEnabled(); }); + bool showPin = false; +#ifndef Q_OS_WIN + showPin = usable && !token.data(QStringLiteral("pinpad")).toBool(); +#endif + if(showPin) + { + const SslCertificate cert(token.cert()); + const bool isSeal = cert.type() & SslCertificate::TempelType; + const int minLen = std::max(token.data(QStringLiteral("pinMin")).toInt(), + int(QSmartCardData::minPinLen(QSmartCardData::pinType(cert)))); + const int maxLen = std::max(token.data(QStringLiteral("pinMax")).toInt(), 12); + ui->pin->setPinLen(minLen, maxLen, !isSeal); + ui->labelPin->setText(tr("Enter %1 code").arg(pinName(token))); + } + ui->labelPin->setVisible(showPin); + ui->pin->setVisible(showPin); + ui->errorPin->setVisible(usable && !ui->errorPin->text().isEmpty()); + ui->layoutPin->invalidate(); + if(!showPin) + ui->pin->clear(); + ui->infoPin->setVisible(usable && !showPin); + if(usable && !showPin) + ui->infoPin->setText(hint(token)); + return usable && (!showPin || ui->pin->hasAcceptableInput()); + } + + template + static void clearError(Ui *ui) + { + ui->pin->setLabel({}); + ui->errorPin->clear(); + ui->errorPin->hide(); + } + + template + static std::expected login(Ui *ui, const TokenData &token) + { + const QString pin = ui->pin->isVisible() ? ui->pin->text() : QString(); + const bool pinpad = pin.isEmpty() && token.data(QStringLiteral("pinpad")).toBool(); + if(pinpad) + clearError(ui); + auto result = pinpad ? loginOnPinpad(ui, token) : QCryptoBackend::getBackend(token, pin); + if(!result && result.error() == QCryptoBackend::PinIncorrect) + { + ui->pin->clear(); + ui->pin->setLabel(QStringLiteral("error")); + ui->errorPin->setText(QCryptoBackend::errorString(result.error(), token)); + ui->errorPin->show(); + if(ui->pin->isVisible()) + ui->pin->setFocus(); + } + return result; + } + +private: + static QString hint(const TokenData &token) + { + return (token.data(QStringLiteral("pinpad")).toBool() ? + tr("After you continue, enter %1 code on the card reader keypad") : + tr("Please enter %1 code in the new window that will appear shortly")).arg(pinName(token)); + } + + template + static std::expected loginOnPinpad(Ui *ui, const TokenData &token) + { + const QString name = pinName(token); + QString text = tr("Enter %1 code on the card reader keypad").arg(name); + if(token.data(QStringLiteral("pinFinalTry")).toBool()) + text += QLatin1Char('\n') + tr("%1 will be locked next failed attempt").arg(name); + else if(token.data(QStringLiteral("pinCountLow")).toBool()) + text += QLatin1Char('\n') + tr("%1 has been entered incorrectly at least once").arg(name); + ui->progressInfo->setText(text); + for(QWidget *w: {(QWidget*)ui->progressLabel, (QWidget*)ui->progressCode, (QWidget*)ui->progressCancel}) + w->hide(); + constexpr int seconds = 30; + ui->progressBar->setRange(0, seconds); + ui->progressBar->setValue(seconds); + QTimeLine countdown(seconds * 1000); + countdown.setEasingCurve(QEasingCurve::Linear); + countdown.setFrameRange(seconds, 0); + QObject::connect(&countdown, &QTimeLine::frameChanged, ui->progressBar, &QProgressBar::setValue); + ui->formPage->hide(); + ui->progressPage->show(); + countdown.start(); + + auto result = QCryptoBackend::getBackend(token); + + ui->progressPage->hide(); + ui->formPage->show(); + for(QWidget *w: {(QWidget*)ui->progressLabel, (QWidget*)ui->progressCode, (QWidget*)ui->progressCancel}) + w->show(); + return result; + } + + static QString pinName(const TokenData &token) + { + SslCertificate cert(token.cert()); + return cert.type() & SslCertificate::TempelType ? QStringLiteral("PIN") : + QSmartCardData::typeString(QSmartCardData::pinType(cert)); + } +}; diff --git a/client/dialogs/PinPopup.cpp b/client/dialogs/PinPopup.cpp deleted file mode 100644 index 5a0646a08..000000000 --- a/client/dialogs/PinPopup.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * QDigiDoc4 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "PinPopup.h" -#include "ui_PinPopup.h" -#include "SslCertificate.h" -#include "effects/Overlay.h" - -#include -#include -#include -#include - -PinPopup::PinPopup(QSmartCardData::PinType type, TokenFlags flags, const SslCertificate &c, QWidget *parent, QString text) - : QDialog(parent) -{ - Ui::PinPopup ui {}; - ui.setupUi(this); - setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint); -#ifdef Q_OS_WIN - ui.buttonLayout->setDirection(QBoxLayout::RightToLeft); -#endif - for(QLineEdit *w: findChildren()) - w->setAttribute(Qt::WA_MacShowFocusRect, false); - ui.pin->setValidator(regexp = new QRegularExpressionValidator(ui.pin)); - new Overlay(this); - - connect(ui.ok, &QPushButton::clicked, this, &PinPopup::accept); - connect(ui.cancel, &QPushButton::clicked, this, &PinPopup::reject); - connect(this, &PinPopup::finished, this, &PinPopup::close ); - connect(ui.pin, &QLineEdit::returnPressed, ui.ok, &QPushButton::click); - - if(!text.isEmpty()) - ui.labelPin->hide(); - else if(type == QSmartCardData::Pin2Type) - { - text = tr("Selected action requires sign certificate."); - ui.labelPin->setText(flags & PinpadFlag ? - tr("For using sign certificate enter PIN2 at the reader") : - tr("For using sign certificate enter PIN2")); - setPinLen(5); - } - else - { - text = tr("Selected action requires authentication certificate."); - ui.labelPin->setText(flags & PinpadFlag ? - tr("For using authentication certificate enter PIN1 at the reader") : - tr("For using authentication certificate enter PIN1")); - setPinLen(4); - } - ui.label->setText(c.toString(c.showCN() ? QStringLiteral("CN, serialNumber") : QStringLiteral("GN SN, serialNumber"))); - ui.text->setText(text); - if(flags & PinFinalTry) - ui.errorPin->setText(tr("%1 will be locked next failed attempt").arg(QSmartCardData::typeString(type))); - else if(flags & PinCountLow) - ui.errorPin->setText(tr("%1 has been entered incorrectly at least once").arg(QSmartCardData::typeString(type))); - else - ui.errorPin->hide(); - - if(flags & PinpadChangeFlag) - { - isPinPad = true; - ui.pin->hide(); - ui.ok->hide(); - ui.cancel->hide(); - ui.errorPin->setAlignment(Qt::AlignCenter); - auto *movie = new QSvgWidget(QStringLiteral(":/images/wait.svg"), this); - movie->setFixedSize(ui.pin->size().height(), ui.pin->size().height()); - movie->show(); - ui.layoutContent->addWidget(movie, 0, Qt::AlignCenter); - } - else if(flags & PinpadFlag) - { - isPinPad = true; - ui.pin->hide(); - ui.ok->hide(); - ui.cancel->hide(); - auto *progress = new QProgressBar(this); - progress->setRange( 0, 30 ); - progress->setValue( progress->maximum() ); - progress->setTextVisible( false ); - progress->resize( 200, 30 ); - progress->move( 153, 122 ); - ui.layoutContent->addWidget(progress); - auto *statusTimer = new QTimeLine(progress->maximum() * 1000, this); - statusTimer->setEasingCurve(QEasingCurve::Linear); - statusTimer->setFrameRange( progress->maximum(), progress->minimum() ); - connect( statusTimer, &QTimeLine::frameChanged, progress, &QProgressBar::setValue ); - connect( this, &PinPopup::startTimer, statusTimer, &QTimeLine::start ); - } - else - { - ui.pin->setFocus(); - ui.pin->setMaxLength( 12 ); - connect(ui.pin, &QLineEdit::textEdited, this, [ok = ui.ok, this](const QString &text) { - ok->setEnabled(regexp->regularExpression().match(text).hasMatch()); - }); - ui.text->setBuddy(ui.pin); - ui.ok->setDisabled(true); - } - if(c.type() & SslCertificate::TempelType) - { - regexp->setRegularExpression(QRegularExpression(QStringLiteral("^.{4,}$"))); - ui.pin->setMaxLength(32767); - } - pinInput = ui.pin; -} - -void PinPopup::setPinLen(unsigned long minLen, unsigned long maxLen) -{ - QString charPattern = regexp->regularExpression().pattern().startsWith(QLatin1String("^.")) ? QStringLiteral(".") : QStringLiteral("\\d"); - regexp->setRegularExpression(QRegularExpression(QStringLiteral("^%1{%2,%3}$").arg(charPattern).arg(minLen).arg(maxLen))); -} - -QString PinPopup::pin() const { return pinInput->text(); } - -void PinPopup::reject() -{ - if (!isPinPad) - QDialog::reject(); -} diff --git a/client/dialogs/PinPopup.h b/client/dialogs/PinPopup.h deleted file mode 100644 index 08f8d9e3a..000000000 --- a/client/dialogs/PinPopup.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * QDigiDoc4 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#pragma once - -#include - -#include "QSmartCard.h" -#include "WaitDialog.h" - -class QLineEdit; -class QRegularExpressionValidator; -class SslCertificate; - -class PinPopup final : public QDialog -{ - Q_OBJECT - -public: - enum TokenFlag - { - PinCountLow = 1<<1, - PinFinalTry = 1<<2, - PinLocked = 1<<3, - PinpadFlag = 1 << 4, - PinpadChangeFlag = 1 << 5, - }; - Q_DECLARE_FLAGS(TokenFlags, TokenFlag) - - PinPopup(QSmartCardData::PinType type, TokenFlags flags, const SslCertificate &cert, QWidget *parent = {}, QString text = {}); - - void setPinLen(unsigned long minLen, unsigned long maxLen = 12); - QString pin() const; - -signals: - void startTimer(); - -private: - void reject() final; - - QLineEdit *pinInput; - QRegularExpressionValidator *regexp {}; - WaitDialogHider hider; - bool isPinPad = false; -}; - diff --git a/client/dialogs/PinPopup.ui b/client/dialogs/PinPopup.ui deleted file mode 100644 index e5efe36b3..000000000 --- a/client/dialogs/PinPopup.ui +++ /dev/null @@ -1,252 +0,0 @@ - - - PinPopup - - - Qt::WindowModal - - - - 0 - 0 - 430 - 348 - - - - QWidget { -color: #07142A; -font-family: Roboto, Helvetica; -font-size: 14px; -} -#PinPopup { -background-color: #FFFFFF; -border-radius: 4px; -} -#label { -color: #003168; -font-size: 20px; -font-weight: 700; -} -#errorPin { -color: #AD2A45; -} -QLineEdit { -padding: 10px 14px; -border: 1px solid #C4CBD8; -border-radius: 4px; -background-color: white; -placeholder-text-color: #607496; -font-size: 16px; -} -QLineEdit[label="error"] { -border-color: #BE7884; -} -QPushButton { -padding: 12px 12px; -border-radius: 4px; -border: 1px solid #AD2A45; -color: #AD2A45; -font-weight: 700; -} -QPushButton:hover, QPushButton:focus { -background-color: #F5EBED; -} -QPushButton:pressed { -background-color: #E1C1C6; -} -QPushButton:default { -color: #ffffff; -border-color: #2F70B6; -background-color: #2F70B6; -} -QPushButton:default:hover, QPushButton:default:focus { -border-color: #2B66A6; -background-color: #2B66A6; -} -QPushButton:default:pressed { -border-color: #215081; -background-color: #215081; -} -QPushButton:default:disabled { -border-color: #82A9D3; -background-color: #82A9D3; -} - - - - 40 - - - QLayout::SetDefaultConstraint - - - 40 - - - 32 - - - 40 - - - 32 - - - - - - 350 - 0 - - - - Qt::TabFocus - - - <b>Mari Maasikas,</b> 48405050123 - - - Qt::AlignCenter - - - true - - - - - - - 24 - - - - - Qt::TabFocus - - - You need to use a personal identification certificate for the selected activity. - - - Qt::AlignCenter - - - true - - - - - - - 6 - - - - - Enter PIN1 to use the certificate - - - pin - - - - - - - QLineEdit::Password - - - - - - - - - - - 0 - 20 - - - - Qt::TabFocus - - - - - - - - - - - 0 - - - - - - 100 - 40 - - - - PointingHandCursor - - - Cancel - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - - 100 - 40 - - - - PointingHandCursor - - - OK - - - true - - - - - - - - - - LineEdit - QLineEdit -
widgets/LineEdit.h
-
-
- - -
diff --git a/client/dialogs/PinpadPopup.cpp b/client/dialogs/PinpadPopup.cpp new file mode 100644 index 000000000..8d8a3a6ae --- /dev/null +++ b/client/dialogs/PinpadPopup.cpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "PinpadPopup.h" +#include "ui_PinpadPopup.h" +#include "SslCertificate.h" +#include "effects/Overlay.h" + +#include + +PinpadPopup::PinpadPopup(QSmartCardData::PinType type, QSmartCard::PinAction action, + const QSmartCardData ¤t, QWidget *parent) + : QDialog(parent) +{ + Ui::PinpadPopup ui {}; + ui.setupUi(this); + setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint); + new Overlay(this); + connect(this, &PinpadPopup::finished, this, &PinpadPopup::close); + + QString bodyText; + switch(action) + { + case QSmartCard::ActivateWithPuk: + case QSmartCard::ChangeWithPuk: + bodyText = tr("To change %1 code with the PUK code on a PinPad reader the PUK code has to be entered first and then the %1 code twice.").arg(QSmartCardData::typeString(type)); + break; + case QSmartCard::UnblockWithPuk: + bodyText = tr("To unblock the %1 code on a PinPad reader the PUK code has to be entered first and then the %1 code twice.").arg(QSmartCardData::typeString(type)); + break; + default: + bodyText = tr("To change %1 on a PinPad reader the old %1 code has to be entered first and then the new %1 code twice.").arg(QSmartCardData::typeString(type)); + break; + } + + const SslCertificate c = current.authCert(); + ui.label->setText(c.toString(c.showCN() ? QStringLiteral("CN, serialNumber") : QStringLiteral("GN SN, serialNumber"))); + ui.text->setText(bodyText); + const auto src = action == QSmartCard::ChangeWithPin || action == QSmartCard::ActivateWithPin ? + type : QSmartCardData::PukType; + const auto retryCount = current.retryCount(src); + if(retryCount < 3) + ui.errorPin->setText((src == QSmartCardData::PukType ? + tr("PUK remaining attempts: %1") : tr("Remaining attempts: %1")).arg(retryCount)); + else + ui.errorPin->hide(); + auto *movie = new QSvgWidget(QStringLiteral(":/images/wait.svg"), this); + movie->setFixedSize(30, 30); + movie->show(); + ui.layoutContent->addWidget(movie, 0, Qt::AlignCenter); +} + +void PinpadPopup::reject() +{ + // The entry runs on the reader and ends there, not with Esc +} diff --git a/client/dialogs/PinpadPopup.h b/client/dialogs/PinpadPopup.h new file mode 100644 index 000000000..fab2acf5a --- /dev/null +++ b/client/dialogs/PinpadPopup.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include + +#include "QSmartCard.h" +#include "WaitDialog.h" + +class PinpadPopup final : public QDialog +{ + Q_OBJECT + +public: + PinpadPopup(QSmartCardData::PinType type, QSmartCard::PinAction action, + const QSmartCardData ¤t, QWidget *parent = {}); + +private: + void reject() final; + + WaitDialogHider hider; +}; + diff --git a/client/dialogs/PinpadPopup.ui b/client/dialogs/PinpadPopup.ui new file mode 100644 index 000000000..685124508 --- /dev/null +++ b/client/dialogs/PinpadPopup.ui @@ -0,0 +1,121 @@ + + + + + PinpadPopup + + + Qt::WindowModal + + + + 0 + 0 + 430 + 348 + + + + QWidget { +color: #07142A; +font-family: Roboto, Helvetica; +font-size: 14px; +} +#PinpadPopup { +background-color: #FFFFFF; +border-radius: 4px; +} +#label { +color: #003168; +font-size: 20px; +font-weight: 700; +} +#errorPin { +color: #AD2A45; +} + + + + 40 + + + QLayout::SetDefaultConstraint + + + 40 + + + 32 + + + 40 + + + 32 + + + + + + 350 + 0 + + + + Qt::TabFocus + + + <b>Mari Maasikas,</b> 48405050123 + + + Qt::AlignCenter + + + true + + + + + + + 24 + + + + + Qt::TabFocus + + + You need to use a personal identification certificate for the selected activity. + + + Qt::AlignCenter + + + true + + + + + + + + 0 + 20 + + + + Qt::TabFocus + + + Qt::AlignCenter + + + + + + + + + + diff --git a/client/dialogs/SigningDialog.cpp b/client/dialogs/SigningDialog.cpp index 2bc2816ea..57d2af7a6 100644 --- a/client/dialogs/SigningDialog.cpp +++ b/client/dialogs/SigningDialog.cpp @@ -7,11 +7,11 @@ #include "Application.h" #include "DigiDoc.h" #include "IKValidator.h" -#include "QPCSC.h" #include "QCryptoBackend.h" #include "QSmartCard.h" #include "Settings.h" #include "SslCertificate.h" +#include "dialogs/PinLogin.h" #include "dialogs/WarningDialog.h" #include "effects/Overlay.h" #include "signer/QSigner.h" @@ -54,7 +54,7 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent) updateCards(qApp->cryptoManager()->cache()); connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &SigningDialog::updateCards); connect(ui->pin, &QLineEdit::textEdited, this, [this] { - setError(ui->pin, ui->errorPin, {}); + PinLogin::clearError(ui); ui->sign->setEnabled(ui->pin->hasAcceptableInput()); }); connect(ui->pin, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); @@ -180,12 +180,7 @@ void SigningDialog::changeEvent(QEvent *event) void SigningDialog::updateNoCard() { - if(!QPCSC::instance().serviceRunning()) - ui->noCardStatus->setText(tr("The PCSC service, required for using the ID-card, is not working. Check your computer settings.")); - else if(QPCSC::instance().readers().isEmpty()) - ui->noCardStatus->setText(tr("No readers found")); - else - ui->noCardStatus->setText(tr("No card in reader")); + ui->noCardStatus->setText(CardList::emptyText()); ui->noCard->setAccessibleDescription(ui->noCardStatus->text()); } @@ -241,49 +236,15 @@ void SigningDialog::updateCards(QList tokens) void SigningDialog::updateSelection() { - for(auto *w: ui->cardListContainer->findChildren()) { - if(!w->isChecked()) - continue; - if(!(w->token() == m_selectedToken)) { - ui->pin->clear(); - setError(ui->pin, ui->errorPin, {}); - } - if(currentMethod() == IDCard) - w->refresh(); - m_selectedToken = w->token(); - break; - } + PinLogin::updateSelection(ui, m_selectedToken, currentMethod() == IDCard); updatePinControls(); } void SigningDialog::updatePinControls() { - bool usable = std::ranges::any_of(ui->cardListContainer->findChildren(), - [](CardListItem *w) { return w->isChecked() && w->isEnabled(); }); - bool isSeal = SslCertificate(m_selectedToken.cert()).type() & SslCertificate::TempelType; - QString pinName = isSeal ? QStringLiteral("PIN") : QSmartCardData::typeString(QSmartCardData::Pin2Type); - bool showPin = false; -#ifndef Q_OS_WIN - if(usable && !m_selectedToken.data(QStringLiteral("pinpad")).toBool()) { - showPin = true; - int minLen = m_selectedToken.data(QStringLiteral("pinMin")).toInt(); - int maxLen = m_selectedToken.data(QStringLiteral("pinMax")).toInt(); - ui->pin->setPinLen(std::max(minLen, 5), std::max(maxLen, 12), !isSeal); - ui->labelPin->setText(tr("Enter %1 code").arg(pinName)); - } -#endif - ui->labelPin->setVisible(showPin); - ui->pin->setVisible(showPin); - ui->errorPin->setVisible(showPin && !ui->errorPin->text().isEmpty()); - ui->layoutPin->invalidate(); - if(!showPin) - ui->pin->clear(); - - ui->infoPin->setVisible(usable && !showPin); - if(usable && !showPin) - ui->infoPin->setText(tr("Please enter %1 code in the new window that will appear shortly").arg(pinName)); + const bool ready = PinLogin::update(ui, m_selectedToken); if(currentMethod() == IDCard) - ui->sign->setEnabled(usable && (!showPin || ui->pin->hasAcceptableInput())); + ui->sign->setEnabled(ready); } void SigningDialog::performSign() @@ -298,20 +259,12 @@ void SigningDialog::performSign() case IDCard: { if(!confirmAlreadySigned()) return reject(); - QString pin = ui->pin->isVisible() ? ui->pin->text() : QString(); - auto val = QCryptoBackend::getBackend(m_selectedToken, pin); + auto val = PinLogin::login(ui, m_selectedToken); if(!val) { switch(val.error()) { - case QCryptoBackend::PinIncorrect: { - QString msg = QCryptoBackend::errorString(QCryptoBackend::PinIncorrect); - if(QSmartCardData data = QSmartCard::readCard(m_selectedToken, true); !data.isNull()) - msg += QStringLiteral(". ") + tr("Remaining attempts: %1").arg(data.retryCount(QSmartCardData::Pin2Type)); - ui->pin->clear(); - setError(ui->pin, ui->errorPin, msg); - ui->pin->setFocus(); - ui->sign->setEnabled(false); + case QCryptoBackend::PinIncorrect: + updatePinControls(); return; - } case QCryptoBackend::PinCanceled: return reject(); default: diff --git a/client/dialogs/SigningDialog.ui b/client/dialogs/SigningDialog.ui index 8b6bfa463..5c95237e4 100644 --- a/client/dialogs/SigningDialog.ui +++ b/client/dialogs/SigningDialog.ui @@ -311,10 +311,10 @@ background: transparent; } - QFrame::Shape::NoFrame + QFrame::NoFrame - Qt::ScrollBarPolicy::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOff true @@ -421,9 +421,6 @@ font-weight: 400; 20 - - Enter PIN2 code - pin diff --git a/client/translations/en.ts b/client/translations/en.ts index fdb48acd9..5cf5c83f5 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -245,6 +245,21 @@ Added file(s) exceeds the maximum size limit of the container (∼120MB). <a href='https://www.id.ee/en/article/encrypting-large-120-mb-files/'>Read more about it</a> + + CardList + + The PCSC service, required for using the ID-card, is not working. Check your computer settings. + The PCSC service, required for using the ID-card, is not working. Check your computer settings. + + + No readers found + No card readers found + + + No card in reader + No card in card reader; check if ID-card is inserted correctly + + CardListItem @@ -255,6 +270,10 @@ Valid to Valid to + + This card cannot decrypt the container + This card cannot decrypt the container + CardWidget @@ -509,10 +528,6 @@ It is removed along with the container. It is removed along with the container. - - Failed to decrypt document - Failed to decrypt document - In order to view Validity Confirmation Sheet there has to be at least one printer installed! In order to view Validity Confirmation Sheet there has to be at least one printer installed! @@ -649,6 +664,33 @@ Cannot read file. + + DecryptionDialog + + Decrypt document + Decrypt document + + + Decryption + Decryption + + + Select the certificate to decrypt with + Select the certificate to decrypt with + + + Cancel + Cancel + + + Decrypt + Decrypt + + + Failed to decrypt document + Failed to decrypt document + + Diagnostics @@ -1791,26 +1833,22 @@ LDAP server is unavailable. - PinPopup + PinLogin - For using sign certificate enter PIN2 at the reader - For using sign certificate enter PIN2 at the reader - - - For using sign certificate enter PIN2 - For using sign certificate enter PIN2 + Enter %1 code + Enter %1 code - Selected action requires sign certificate. - Selected action requires sign certificate. + After you continue, enter %1 code on the card reader keypad + After you continue, enter %1 code on the card reader keypad - For using authentication certificate enter PIN1 at the reader - For using authentication certificate enter PIN1 at the reader + Please enter %1 code in the new window that will appear shortly + Please enter %1 code in the new window that will appear shortly - For using authentication certificate enter PIN1 - For using authentication certificate enter PIN1 + Enter %1 code on the card reader keypad + Enter %1 code on the card reader keypad %1 will be locked next failed attempt @@ -1820,22 +1858,6 @@ LDAP server is unavailable. %1 has been entered incorrectly at least once %1 has been entered incorrectly at least once - - Selected action requires authentication certificate. - Selected action requires authentication certificate. - - - Cancel - Cancel - - - You need to use a personal identification certificate for the selected activity. - You need to use a personal identification certificate for the selected activity. - - - Enter PIN1 to use the certificate - Enter PIN1 to use the certificate - PinUnblock @@ -1976,6 +1998,33 @@ LDAP server is unavailable. Change + + PinpadPopup + + To change %1 code with the PUK code on a PinPad reader the PUK code has to be entered first and then the %1 code twice. + To change %1 code with the PUK code on a PinPad reader<br>the PUK code has to be entered first and then the %1 code twice. + + + To unblock the %1 code on a PinPad reader the PUK code has to be entered first and then the %1 code twice. + To unblock the %1 code on a PinPad reader the PUK<br>code has to be entered first and then the %1 code twice. + + + To change %1 on a PinPad reader the old %1 code has to be entered first and then the new %1 code twice. + To change %1 on a PinPad reader the old %1 code<br>has to be entered first and then the new %1 code twice. + + + PUK remaining attempts: %1 + PUK remaining attempts: %1 + + + Remaining attempts: %1 + Remaining attempts: %1 + + + You need to use a personal identification certificate for the selected activity. + You need to use a personal identification certificate for the selected activity. + + PrintSheet @@ -2089,6 +2138,10 @@ LDAP server is unavailable. PIN locked PIN locked + + Remaining attempts: %1 + Remaining attempts: %1 + Signing/decrypting is already in progress another window. Signing/decrypting is already in progress another window. @@ -2138,18 +2191,6 @@ LDAP server is unavailable. QSmartCard - - To change %1 on a PinPad reader the old %1 code has to be entered first and then the new %1 code twice. - To change %1 on a PinPad reader the old %1 code<br>has to be entered first and then the new %1 code twice. - - - To change %1 code with the PUK code on a PinPad reader the PUK code has to be entered first and then the %1 code twice. - To change %1 code with the PUK code on a PinPad reader<br>the PUK code has to be entered first and then the %1 code twice. - - - To unblock the %1 code on a PinPad reader the PUK code has to be entered first and then the %1 code twice. - To unblock the %1 code on a PinPad reader the PUK<br>code has to be entered first and then the %1 code twice. - Changing %1 failed Changing %1 failed @@ -2787,14 +2828,6 @@ Additional licenses and components Invalid country code Invalid country code - - Enter %1 code - Enter %1 code - - - Please enter %1 code in the new window that will appear shortly - Please enter %1 code in the new window that will appear shortly - The document has already been signed by you The document has already been signed by you @@ -2803,26 +2836,10 @@ Additional licenses and components Continue signing Continue signing - - Remaining attempts: %1 - Remaining attempts: %1 - Failed to sign document Failed to sign document - - No card in reader - No card in card reader; check if ID-card is inserted correctly - - - No readers found - No card readers found - - - The PCSC service, required for using the ID-card, is not working. Check your computer settings. - The PCSC service, required for using the ID-card, is not working. Check your computer settings. - SslCertificate diff --git a/client/translations/et.ts b/client/translations/et.ts index 359bb3912..2cba366a8 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -245,6 +245,21 @@ Lisatud fail(id) ületab turvaümbriku maksimaalset suurust (~120MB). <a href='https://www.id.ee/artikkel/suuremahuliste-120-mb-failide-krupteerimine/'>Loe täpsemalt siit</a> + + CardList + + The PCSC service, required for using the ID-card, is not working. Check your computer settings. + ID-kaardi kasutamiseks vajalik PCSC teenus ei tööta. Kontrolli arvuti seadeid. + + + No readers found + Ühtegi kiipkaardilugejat pole ühendatud + + + No card in reader + Lugejas ei ole kaarti; kontrolli, kas ID-kaart on õiget pidi lugejas + + CardListItem @@ -255,6 +270,10 @@ Valid to Kehtib kuni + + This card cannot decrypt the container + Selle kaardiga ei saa dekrüpteerida + CardWidget @@ -509,10 +528,6 @@ It is removed along with the container. Koos sellega eemaldatakse ka ümbrik. - - Failed to decrypt document - Dokumendi dekrüpteerimine ebaõnnestus - In order to view Validity Confirmation Sheet there has to be at least one printer installed! Digitaalallkirjade kinnituslehe kuvamiseks peab olema arvutis vähemalt üks printer seadistatud! @@ -649,6 +664,33 @@ Faili ei saa lugeda. + + DecryptionDialog + + Decrypt document + Dekrüpteeri dokument + + + Decryption + Dekrüpteerimine + + + Select the certificate to decrypt with + Vali sertifikaat, millega dekrüpteerida + + + Cancel + Katkesta + + + Decrypt + Dekrüpteeri + + + Failed to decrypt document + Dokumendi dekrüpteerimine ebaõnnestus + + Diagnostics @@ -1791,26 +1833,22 @@ LDAP serveriga ei saa ühendust. - PinPopup + PinLogin - For using sign certificate enter PIN2 at the reader - Sertifikaadi kasutamiseks sisesta PIN2 kaardilugeja sõrmistikult - - - For using sign certificate enter PIN2 - Sertifikaadi kasutamiseks sisesta PIN2 + Enter %1 code + Sisesta %1-kood - Selected action requires sign certificate. - Valitud tegevuse jaoks on vaja kasutada allkirjastamise sertifikaati. + After you continue, enter %1 code on the card reader keypad + Pärast jätkamist sisesta %1-kood kaardilugeja sõrmistikult - For using authentication certificate enter PIN1 at the reader - Sertifikaadi kasutamiseks sisesta PIN1 kaardilugeja sõrmistikult + Please enter %1 code in the new window that will appear shortly + Palun sisesta %1-kood peatselt ilmuvas uues aknas - For using authentication certificate enter PIN1 - Sertifikaadi kasutamiseks sisesta PIN1 + Enter %1 code on the card reader keypad + Sisesta %1-kood kaardilugeja sõrmistikult %1 will be locked next failed attempt @@ -1820,22 +1858,6 @@ LDAP serveriga ei saa ühendust. %1 has been entered incorrectly at least once %1 on vähemalt ühe korra valesti sisestatud - - Selected action requires authentication certificate. - Valitud tegevuse jaoks on vaja kasutada isikutuvastuse sertifikaati. - - - Cancel - Katkesta - - - You need to use a personal identification certificate for the selected activity. - Valitud tegevuse jaoks on vaja kasutada isikutuvastuse sertifikaati. - - - Enter PIN1 to use the certificate - Sertifikaadi kasutamiseks sisesta PIN1 - PinUnblock @@ -1976,6 +1998,33 @@ LDAP serveriga ei saa ühendust. Muuda + + PinpadPopup + + To change %1 code with the PUK code on a PinPad reader the PUK code has to be entered first and then the %1 code twice. + PinPad lugejaga %1 muutmiseks tuleb kõigepealt<br>sisestada PUK ning siis kaks korda %1. + + + To unblock the %1 code on a PinPad reader the PUK code has to be entered first and then the %1 code twice. + PinPad lugejaga %1 blokeeringu tühistamiseks<br>tuleb kõigepealt sisestada PUK ning siis kaks korda %1. + + + To change %1 on a PinPad reader the old %1 code has to be entered first and then the new %1 code twice. + PinPad lugejaga %1 muutmiseks<br>tuleb kõigepealt sisestada vana %1 ning siis kaks korda uus %1. + + + PUK remaining attempts: %1 + PUK katseid jäänud: %1 + + + Remaining attempts: %1 + Katseid jäänud: %1 + + + You need to use a personal identification certificate for the selected activity. + Valitud tegevuse jaoks on vaja kasutada isikutuvastuse sertifikaati. + + PrintSheet @@ -2089,6 +2138,10 @@ LDAP serveriga ei saa ühendust. PIN locked PIN on lukus + + Remaining attempts: %1 + Katseid jäänud: %1 + Signing/decrypting is already in progress another window. Allkirjastamine/dekrüpteerimine on juba käimas teises aknas. @@ -2138,18 +2191,6 @@ LDAP serveriga ei saa ühendust. QSmartCard - - To change %1 on a PinPad reader the old %1 code has to be entered first and then the new %1 code twice. - PinPad lugejaga %1 muutmiseks<br>tuleb kõigepealt sisestada vana %1 ning siis kaks korda uus %1. - - - To change %1 code with the PUK code on a PinPad reader the PUK code has to be entered first and then the %1 code twice. - PinPad lugejaga %1 muutmiseks tuleb kõigepealt<br>sisestada PUK ning siis kaks korda %1. - - - To unblock the %1 code on a PinPad reader the PUK code has to be entered first and then the %1 code twice. - PinPad lugejaga %1 blokeeringu tühistamiseks<br>tuleb kõigepealt sisestada PUK ning siis kaks korda %1. - Changing %1 failed %1 muutmine ebaõnnestus @@ -2787,14 +2828,6 @@ Täiendavad litsentsid ja komponendid Invalid country code Vigane riigikood - - Enter %1 code - Sisesta %1-kood - - - Please enter %1 code in the new window that will appear shortly - Palun sisesta %1-kood peatselt ilmuvas uues aknas - The document has already been signed by you Dokument on Sinu poolt juba allkirjastatud @@ -2803,26 +2836,10 @@ Täiendavad litsentsid ja komponendid Continue signing Jätka allkirjastamisega - - Remaining attempts: %1 - Katseid jäänud: %1 - Failed to sign document Dokumendi allkirjastamine ebaõnnestus - - No card in reader - Lugejas ei ole kaarti; kontrolli, kas ID-kaart on õiget pidi lugejas - - - No readers found - Ühtegi kiipkaardilugejat pole ühendatud - - - The PCSC service, required for using the ID-card, is not working. Check your computer settings. - ID-kaardi kasutamiseks vajalik PCSC teenus ei tööta. Kontrolli arvuti seadeid. - SslCertificate diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index e5acf9467..e26635575 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -20,15 +20,10 @@ #include "AddressItem.h" #include "ui_AddressItem.h" -#include "Application.h" #include "CryptoDoc.h" -#include "QCryptoBackend.h" #include "SslCertificate.h" -#include "TokenData.h" #include "dialogs/KeyDialog.h" -#include - using namespace ria::qdigidoc4; struct AddressItem::Private: public Ui::AddressItem @@ -37,7 +32,6 @@ struct AddressItem::Private: public Ui::AddressItem QString code; QString label; QDateTime expireDate; - TokenData token; ContainerState state = Uninitialized; }; @@ -89,14 +83,13 @@ AddressItem::AddressItem(const CKey &key, Type type, QWidget *parent) connect(ui->add, &QToolButton::clicked, this, [this]{ emit add(this);}); connect(ui->remove, &QToolButton::clicked, this, [this]{ emit remove(this);}); - connect(ui->decrypt, &QToolButton::clicked, this, [this] { emit decrypt(ui->token); }); - connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &AddressItem::setDecryptVisible); + connect(ui->decrypt, &QToolButton::clicked, this, &AddressItem::decrypt); setName(); setIdType(); ui->add->setVisible(type == Add); ui->remove->setVisible(type != Add); - setDecryptVisible(qApp->cryptoManager()->cache()); + setDecryptVisible(); } AddressItem::~AddressItem() @@ -140,23 +133,15 @@ void AddressItem::mouseReleaseEvent(QMouseEvent * /*event*/) { (new KeyDialog(ui->key, this))->open(); } -TokenData AddressItem::tokenForLock(const libcdoc::Lock &lock, const QList &tokens) +QString AddressItem::name() const { - if(!lock.isPKI()) - return {}; - const auto &key = lock.getBytes(libcdoc::Lock::RCPT_KEY); - QByteArray rcpt = QByteArray::fromRawData((const char*)key.data(), key.size()); - for(const TokenData &token: tokens) - if(SslCertificate(token.cert()).publicKeyDer() == rcpt) - return token; - return {}; + return QStringLiteral("%1 %2").arg(ui->label, ui->code).trimmed(); } -void AddressItem::setDecryptVisible(const QList &tokens) +void AddressItem::setDecryptVisible() { - ui->token = tokenForLock(ui->key.lock, tokens); - ui->decrypt->setVisible(ui->state == EncryptedContainer && - (ui->key.lock.type == libcdoc::Lock::PASSWORD || !ui->token.isNull())); + ui->decrypt->setVisible(ui->state == EncryptedContainer && (ui->key.lock.isPKI() || + ui->key.lock.type == libcdoc::Lock::PASSWORD)); } void AddressItem::setName() @@ -172,7 +157,7 @@ void AddressItem::stateChange(ContainerState state) ui->state = state; if(ui->add->isHidden()) ui->remove->setVisible(state == UnencryptedContainer); - setDecryptVisible(qApp->cryptoManager()->cache()); + setDecryptVisible(); } void AddressItem::setIdType(const SslCertificate &cert) { diff --git a/client/widgets/AddressItem.h b/client/widgets/AddressItem.h index e2f08d016..7b08f5b85 100644 --- a/client/widgets/AddressItem.h +++ b/client/widgets/AddressItem.h @@ -22,9 +22,6 @@ #include "widgets/Item.h" struct CKey; -class TokenData; - -namespace libcdoc { struct Lock; } class AddressItem final : public Item { @@ -42,23 +39,22 @@ class AddressItem final : public Item ~AddressItem() final; const CKey& getKey() const; + QString name() const; void initTabOrder(QWidget *item) final; QWidget* lastTabWidget() final; void stateChange(ria::qdigidoc4::ContainerState state) final; signals: - void decrypt(const TokenData &token); + void decrypt(); private: void changeEvent(QEvent *event) final; void mouseReleaseEvent(QMouseEvent *event) final; - void setDecryptVisible(const QList &tokens); + void setDecryptVisible(); void setName(); void setIdType(); void setIdType(const SslCertificate& cert); - static TokenData tokenForLock(const libcdoc::Lock &lock, const QList &tokens); - struct Private; Private *ui; }; diff --git a/client/widgets/CardList.cpp b/client/widgets/CardList.cpp index 22d3a6ef5..7e7349e81 100644 --- a/client/widgets/CardList.cpp +++ b/client/widgets/CardList.cpp @@ -2,11 +2,21 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "CardList.h" +#include "QPCSC.h" #include #include #include +QString CardList::emptyText() +{ + auto &pcsc = QPCSC::instance(); + if(!pcsc.serviceRunning()) + return tr("The PCSC service, required for using the ID-card, is not working. Check your computer settings."); + const QStringList readers = pcsc.readers(); + return readers.isEmpty() ? tr("No readers found") : tr("No card in reader"); +} + QSize CardList::sizeHint() const { QLayout *layout = widget() ? widget()->layout() : nullptr; diff --git a/client/widgets/CardList.h b/client/widgets/CardList.h index 9d6f2915b..cf3f84f0b 100644 --- a/client/widgets/CardList.h +++ b/client/widgets/CardList.h @@ -3,14 +3,17 @@ #pragma once +#include #include class CardList final : public QScrollArea { + Q_DECLARE_TR_FUNCTIONS(CardList) public: using QScrollArea::QScrollArea; QSize sizeHint() const final; QSize minimumSizeHint() const final; + static QString emptyText(); protected: bool eventFilter(QObject *watched, QEvent *event) final; diff --git a/client/widgets/CardListItem.cpp b/client/widgets/CardListItem.cpp index 446a2e03d..605df1854 100644 --- a/client/widgets/CardListItem.cpp +++ b/client/widgets/CardListItem.cpp @@ -36,10 +36,10 @@ TokenData CardListItem::token() const void CardListItem::refresh() { + const auto type = QSmartCardData::pinType(t.cert()); if(QSmartCardData data = QSmartCard::readCard(t, true); !data.isNull()) { - t.setData(QStringLiteral("blocked"), - data.retryCount(QSmartCardData::Pin2Type) == 0 || data.pinLocked(QSmartCardData::Pin2Type)); - update(t, isChecked()); + t.setData(QStringLiteral("blocked"), data.retryCount(type) == 0 || data.pinLocked(type)); + update(t, isChecked(), reason); } } @@ -61,22 +61,25 @@ void CardListItem::changeEvent(QEvent *ev) if(ev->type() == QEvent::LanguageChange) { ui->retranslateUi(this); - update(t, isChecked()); + update(t, isChecked(), reason); } } -void CardListItem::update(const TokenData &token, bool selected) +void CardListItem::update(const TokenData &token, bool selected, const char *reason) { t = token; + this->reason = reason; SslCertificate c(t.cert()); bool blocked = t.data(QStringLiteral("blocked")).toBool(); - bool usable = c.isValid() && !blocked; + bool usable = c.isValid() && !blocked && !reason; QString name = !c.subjectInfo("GN").isEmpty() || !c.subjectInfo("SN").isEmpty() ? c.toString(QStringLiteral("GN SN")) : c.toString(QStringLiteral("CN")); ui->cardName->setText(QStringLiteral("%1, %2").arg(name, c.personalCode())); ui->cardName->setAccessibleName(ui->cardName->text().toLower()); ui->cardIssuer->setText(QStringLiteral("%1: %2").arg(tr("Issuer"), c.issuerInfo(QSslCertificate::CommonName))); - if(blocked) + if(reason) + ui->cardValidUntil->setText(tr(reason)); + else if(blocked) ui->cardValidUntil->setText(QCryptoBackend::errorString(QCryptoBackend::PinLocked)); else ui->cardValidUntil->setText(QStringLiteral("%1: %2").arg(tr("Valid to"), diff --git a/client/widgets/CardListItem.h b/client/widgets/CardListItem.h index f7e1025c8..46069bb50 100644 --- a/client/widgets/CardListItem.h +++ b/client/widgets/CardListItem.h @@ -19,7 +19,7 @@ class CardListItem final: public QAbstractButton TokenData token() const; void refresh(); - void update(const TokenData &token, bool selected); + void update(const TokenData &token, bool selected, const char *reason = nullptr); signals: void selected(const TokenData &token); @@ -31,4 +31,5 @@ class CardListItem final: public QAbstractButton private: Ui::CardListItem *ui; TokenData t; + const char *reason = nullptr; }; diff --git a/client/widgets/CardListItem.ui b/client/widgets/CardListItem.ui index e04521764..a5db4274a 100644 --- a/client/widgets/CardListItem.ui +++ b/client/widgets/CardListItem.ui @@ -25,6 +25,10 @@ background-color: #EAF1F8; border-color: #003168; background-color: #EAF1F8; } +#CardListItem:disabled { +border-color: #E7EAEF; +background-color: #F0F0F2; +} #cardName { color: #07142A; font-family: Roboto, Helvetica; @@ -37,6 +41,9 @@ font-family: Roboto, Helvetica; font-size: 12px; font-weight: 400; } +#cardName:disabled, #cardIssuer:disabled { +color: #607496; +} #cardValidUntil { color: #091A36; font-family: Roboto, Helvetica; @@ -46,6 +53,10 @@ padding: 2px 8px; border-radius: 8px; background-color: #F3F5F7; } +#cardValidUntil:disabled { +color: #607496; +background-color: #E7EAEF; +} #cardValidUntil[label="error"] { color: #AD2A45; background-color: #F5EBED; diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 5ffac56d8..f183712df 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -27,8 +27,8 @@ #include "QCryptoBackend.h" #include "Settings.h" #include "SslCertificate.h" -#include "TokenData.h" #include "dialogs/AddRecipients.h" +#include "dialogs/DecryptionDialog.h" #include "dialogs/FileDialog.h" #include "dialogs/PasswordDialog.h" #include "dialogs/WarningDialog.h" @@ -278,7 +278,14 @@ void ContainerPage::transition(CryptoDoc *container) hasUnsupported = hasUnsupported || (key.rcpt_cert.isNull() && !key.lock.isValid()); auto *addr = new AddressItem(key, AddressItem::Icon, ui->rightPane); ui->rightPane->addWidget(addr); - connect(addr, &AddressItem::decrypt, container, [container, key, this](const TokenData &token) { + connect(addr, &AddressItem::decrypt, container, [container, key, addr, this] { + if(!key.isSupported()) { + WarningDialog::create(this) + ->withTitle(CryptoDoc::tr("Failed to decrypt document")) + ->withText(QCoreApplication::translate("AddressItem", "Unsupported cryptographic algorithm or recipient type")) + ->open(); + return; + } if (key.lock.type == libcdoc::Lock::Type::PASSWORD) { PasswordDialog p(PasswordDialog::Mode::DECRYPT, this); auto params = libcdoc::Lock::parseLabel(key.lock.label); @@ -287,13 +294,15 @@ void ContainerPage::transition(CryptoDoc *container) return; decrypt(container, key.lock, p.secret(), {}); } - else if(auto val = QCryptoBackend::getBackend(token)) - decrypt(container, key.lock, {}, std::unique_ptr(val.value())); - else if(val.error() != QCryptoBackend::PinCanceled) { - WarningDialog::create(this) - ->withTitle(tr("Failed to decrypt document")) - ->withText(QCryptoBackend::errorString(val.error())) - ->exec(); + else { + std::unique_ptr backend; + { + DecryptionDialog dialog(key, addr->name(), this); + if(!dialog.exec()) + return; + backend = dialog.takeBackend(); + } + decrypt(container, key.lock, {}, std::move(backend)); } }); }