Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/QCNG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ QCNG::QCNG() noexcept = default;

QCNG::~QCNG() noexcept = default;

QCNG::Status QCNG::login(const TokenData &token)
QCNG::Status QCNG::login(const TokenData &token, const QString & /*pin*/)
{
std::unique_ptr<Private> p = std::make_unique<Private>();
if(FAILED(NCryptOpenStorageProvider(&p->prov, LPCWSTR(token.data(u"provider"_s).toString().utf16()), 0)))
Expand Down
2 changes: 1 addition & 1 deletion client/QCNG.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class QCNG final: public QCryptoBackend
explicit QCNG() noexcept;
~QCNG() noexcept final;

Status login(const TokenData &token) final;
Status login(const TokenData &token, const QString &pin) final;

QByteArray decrypt(const QByteArray &data, bool oaep) const final;
QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest,
Expand Down
13 changes: 9 additions & 4 deletions client/QCryptoBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ QCryptoBackend::~QCryptoBackend()
}

std::expected<QCryptoBackend *,QCryptoBackend::Status>
QCryptoBackend::getBackend(const TokenData& token) {
QCryptoBackend::getBackend(const TokenData& token, const QString &pin) {
if(!qApp->cryptoManager()->d->operationLock.tryLockForWrite(10 * 1000))
return std::unexpected(InProgress);
#ifdef Q_OS_WIN
Expand All @@ -79,9 +79,14 @@ QCryptoBackend::getBackend(const TokenData& token) {
return backend.release();

Status status;
do {
status = backend->login(token);
} while (status == PinIncorrect);
if(pin.isEmpty())
{
do {
status = backend->login(token, {});
} while (status == PinIncorrect);
}
else
status = backend->login(token, pin);
if (status != PinOK)
return std::unexpected(status);

Expand Down
4 changes: 2 additions & 2 deletions client/QCryptoBackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class QCryptoBackend
* @param token the token to use
* @return the new backend object or an error code
*/
static std::expected<QCryptoBackend *,Status> getBackend(const TokenData &token);
static std::expected<QCryptoBackend *,Status> getBackend(const TokenData &token, const QString &pin = {});

/**
* @brief The status of the last operation
Expand All @@ -77,7 +77,7 @@ class QCryptoBackend
static QString errorString(Status error);

protected:
virtual Status login(const TokenData &cert) = 0;
virtual Status login(const TokenData &cert, const QString &pin) = 0;

private:
TokenData token;
Expand Down
22 changes: 15 additions & 7 deletions client/QPKCS11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ QByteArray QPKCS11::deriveHMACExtract(const QByteArray &publicKey, const QByteAr
return out;
}

QPKCS11::Status QPKCS11::login(const TokenData &t)
QPKCS11::Status QPKCS11::login(const TokenData &t, const QString &pin)
{
if(!d->l) {
d->l = QPKCS11Library::current();
Expand Down Expand Up @@ -401,12 +401,17 @@ QPKCS11::Status QPKCS11::login(const TokenData &t)
p.startTimer();
return waitFor(d->l->f->C_Login, d->session, CKU_USER, nullptr, 0);
} else {
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;
QByteArray pin = p.pin().toUtf8();
return d->l->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pin.constData()), CK_ULONG(pin.size()));
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()));
}
});

Expand Down Expand Up @@ -471,6 +476,9 @@ QList<TokenData> QPKCS11::tokens()
t.setData(QStringLiteral("slot"), QVariant::fromValue(slot));
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("pinMin"), int(token.ulMinPinLen));
t.setData(QStringLiteral("pinMax"), int(token.ulMaxPinLen));

CK_KEY_TYPE keyType = CKK_RSA;
CK_ATTRIBUTE attribute { CKA_KEY_TYPE, &keyType, sizeof(keyType) };
Expand Down
2 changes: 1 addition & 1 deletion client/QPKCS11.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class QPKCS11 final: public QCryptoBackend
QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final;
QByteArray sign(QCryptographicHash::Algorithm type, const QByteArray &digest) const final;

Status login(const TokenData &t) final;
Status login(const TokenData &t, const QString &pin) final;

static QList<TokenData> tokens();

Expand Down
171 changes: 104 additions & 67 deletions client/dialogs/SigningDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@

#include <QtWidgets/QMessageBox>

static void setError(LineEdit *input, QLabel *error, const QString &msg)
{
input->setLabel(msg.isEmpty() ? QString() : QStringLiteral("error"));
error->setText(msg);
error->setHidden(msg.isEmpty());
}

SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent)
: QDialog(parent)
, digiDoc(doc)
, ui(new Ui::SigningDialog)
{
static const QStringList countryCodes {QStringLiteral("372"), QStringLiteral("370")};
new Overlay(this);
ui->setupUi(this);
ui->progressPage->hide();
Expand All @@ -46,19 +52,18 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent)
updateCards();
connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &SigningDialog::updateCards);
connect(qApp->cryptoManager()->smartcard(), &QSmartCard::dataChanged, this, &SigningDialog::updateCards);
connect(ui->pin, &QLineEdit::textEdited, this, [this] {
setError(ui->pin, ui->errorPin, {});
ui->sign->setEnabled(ui->pin->hasAcceptableInput());
});
connect(ui->pin, &QLineEdit::returnPressed, ui->sign, &QPushButton::click);
connect(ui->tabs, &QTabBar::currentChanged, this, [this](int index) {
if(index == IDCard)
updateCards();
else
ui->sign->setEnabled(true);
});

auto setError = [](LineEdit *input, QLabel *error, const QString &msg) {
input->setLabel(msg.isEmpty() ? QString() : QStringLiteral("error"));
error->setText(msg);
error->setHidden(msg.isEmpty());
};

// Mobile-ID tab
ui->idCodeMID->setValidator(new NumberValidator(ui->idCodeMID));
ui->idCodeMID->setText(Settings::MOBILEID_CODE);
Expand All @@ -78,12 +83,12 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent)
};
connect(ui->idCodeMID, &QLineEdit::returnPressed, ui->sign, &QPushButton::click);
connect(ui->idCodeMID, &QLineEdit::textEdited, this, saveMIDSettings);
connect(ui->idCodeMID, &QLineEdit::textEdited, this, [this, setError] {
connect(ui->idCodeMID, &QLineEdit::textEdited, this, [this] {
setError(ui->idCodeMID, ui->errorCodeMID, {});
});
connect(ui->phoneNo, &QLineEdit::returnPressed, ui->sign, &QPushButton::click);
connect(ui->phoneNo, &QLineEdit::textEdited, this, saveMIDSettings);
connect(ui->phoneNo, &QLineEdit::textEdited, this, [this, setError] {
connect(ui->phoneNo, &QLineEdit::textEdited, this, [this] {
setError(ui->phoneNo, ui->errorPhone, {});
});
connect(ui->cbRememberMID, &QCheckBox::clicked, this, saveMIDSettings);
Expand All @@ -108,7 +113,7 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent)
};
connect(ui->idCodeSID, &QLineEdit::returnPressed, ui->sign, &QPushButton::click);
connect(ui->idCodeSID, &QLineEdit::textEdited, this, saveSIDSettings);
connect(ui->idCodeSID, &QLineEdit::textEdited, this, [this, setError] {
connect(ui->idCodeSID, &QLineEdit::textEdited, this, [this] {
setError(ui->idCodeSID, ui->errorCodeSID, {});
});
connect(ui->idCountry, &QComboBox::currentTextChanged, this, [this, ik, saveSIDSettings] {
Expand All @@ -118,43 +123,7 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent)
});
connect(ui->cbRememberSID, &QCheckBox::clicked, this, saveSIDSettings);

// Sign button — validates per-tab then delegates to performSign()
connect(ui->sign, &QPushButton::clicked, this, [this, setError] {
switch(currentMethod()) {
case IDCard:
if(performSign()) accept();
else reject();
break;
case MobileID: {
const QString code = ui->idCodeMID->text();
const QString phone = ui->phoneNo->text();
if(!IKValidator::isValid(code))
setError(ui->idCodeMID, ui->errorCodeMID, tr("Personal code is not valid"));
else
setError(ui->idCodeMID, ui->errorCodeMID, {});
if(phone.size() < 8 || countryCodes.contains(phone))
setError(ui->phoneNo, ui->errorPhone, tr("Phone number is not entered"));
else if(!countryCodes.contains(phone.left(3)))
setError(ui->phoneNo, ui->errorPhone, tr("Invalid country code"));
else
setError(ui->phoneNo, ui->errorPhone, {});
if(ui->errorCodeMID->text().isEmpty() && ui->errorPhone->text().isEmpty()) {
if(performSign()) accept();
else reject();
}
break;
}
case SmartID:
if(ui->idCodeSID->validator() && !IKValidator::isValid(ui->idCodeSID->text()))
setError(ui->idCodeSID, ui->errorCodeSID, tr("Personal code is not valid"));
else {
setError(ui->idCodeSID, ui->errorCodeSID, {});
if(performSign()) accept();
else reject();
}
break;
}
});
connect(ui->sign, &QPushButton::clicked, this, &SigningDialog::performSign);
connect(ui->cancel, &QPushButton::clicked, this, &QDialog::reject);
connect(this, &QDialog::finished, this, &QDialog::close);
}
Expand Down Expand Up @@ -200,11 +169,19 @@ void SigningDialog::updateCards()
{
qDeleteAll(ui->cardListContainer->findChildren<CardListItem*>());

auto select = [this](const TokenData &t) {
if(!(t == m_selectedToken)) {
ui->pin->clear();
setError(ui->pin, ui->errorPin, {});
}
m_selectedToken = t;
};

QList<TokenData> tokens = qApp->cryptoManager()->cache();
bool localValid = std::any_of(tokens.cbegin(), tokens.cend(),
[this](const TokenData &t) { return t == m_selectedToken; });
if(!localValid)
m_selectedToken = qApp->cryptoManager()->tokensign();
select(qApp->cryptoManager()->tokensign());

const QSmartCardData data = qApp->cryptoManager()->smartcard()->data();
bool any = false;
Expand All @@ -220,8 +197,8 @@ void SigningDialog::updateCards()
bool usable = c.isValid() && !blocked;
auto *w = new CardListItem(ui->cardListContainer);
w->update(token, token.card() == m_selectedToken.card());
connect(w, &CardListItem::selected, this, [this](const TokenData &t) {
m_selectedToken = t;
connect(w, &CardListItem::selected, this, [this, select](const TokenData &t) {
select(t);
updateSelection(true);
});
ui->cardListContainer->layout()->addWidget(w);
Expand All @@ -245,20 +222,33 @@ void SigningDialog::updateCards()

void SigningDialog::updateSelection(bool usable)
{
ui->infoPin->setVisible(usable);
if(usable)
ui->infoPin->setText(tr("Please enter %1 code in the new window that will appear shortly").arg(
SslCertificate(m_selectedToken.cert()).type() & SslCertificate::TempelType ?
QStringLiteral("PIN") : QSmartCardData::typeString(QSmartCardData::Pin2Type)));
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());
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));
if(currentMethod() == IDCard)
ui->sign->setEnabled(usable);
ui->sign->setEnabled(usable && (!showPin || ui->pin->hasAcceptableInput()));
}

bool SigningDialog::performSign()
void SigningDialog::performSign()
{
if(!confirmAlreadySigned())
return false;

auto showPage = [this](QWidget *page)
{
ui->formPage->setVisible(page == ui->formPage);
Expand All @@ -267,23 +257,70 @@ bool SigningDialog::performSign()

switch(currentMethod()) {
case IDCard: {
QSigner signer(m_selectedToken);
return digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &signer);
if(!confirmAlreadySigned())
return reject();
QString pin = ui->pin->isVisible() ? ui->pin->text() : QString();
auto val = QCryptoBackend::getBackend(m_selectedToken, pin);
if(!val) {
switch(val.error()) {
case QCryptoBackend::PinIncorrect: {
QString msg = QCryptoBackend::errorString(QCryptoBackend::PinIncorrect);
if(QSmartCardData data = qApp->cryptoManager()->smartcard()->data(); data.card() == m_selectedToken.card())
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);
return;
}
case QCryptoBackend::PinCanceled:
return reject();
default:
WarningDialog::create(this)
->withTitle(tr("Failed to sign document"))
->withText(QCryptoBackend::errorString(val.error()))
->exec();
return reject();
}
}
QSigner signer(std::unique_ptr<QCryptoBackend>(val.value()), m_selectedToken);
return digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &signer) ? accept() : reject();
}
case MobileID: {
static const QStringList countryCodes {QStringLiteral("372"), QStringLiteral("370")};
const QString code = ui->idCodeMID->text();
const QString phone = ui->phoneNo->text();
setError(ui->idCodeMID, ui->errorCodeMID, IKValidator::isValid(code) ? QString() : tr("Personal code is not valid"));
if(phone.size() < 8 || countryCodes.contains(phone))
setError(ui->phoneNo, ui->errorPhone, tr("Phone number is not entered"));
else if(!countryCodes.contains(phone.left(3)))
setError(ui->phoneNo, ui->errorPhone, tr("Invalid country code"));
else
setError(ui->phoneNo, ui->errorPhone, {});
if(!ui->errorCodeMID->text().isEmpty() || !ui->errorPhone->text().isEmpty())
return;
if(!confirmAlreadySigned())
return reject();
SigningProgressUI pui { ui->progressInfo, ui->progressCode, ui->progressLabel, ui->progressBar, ui->progressCancel, this };
showPage(ui->progressPage);
MobileProgress m(pui);
return m.init(ui->idCodeMID->text(), ui->phoneNo->text()) &&
digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &m);
return m.init(code, phone) &&
digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &m) ? accept() : reject();
}
case SmartID: {
const QString code = ui->idCodeSID->text();
if(ui->idCodeSID->validator() && !IKValidator::isValid(code)) {
setError(ui->idCodeSID, ui->errorCodeSID, tr("Personal code is not valid"));
return;
}
setError(ui->idCodeSID, ui->errorCodeSID, {});
if(!confirmAlreadySigned())
return reject();
SigningProgressUI pui { ui->progressInfo, ui->progressCode, ui->progressLabel, ui->progressBar, ui->progressCancel, this };
showPage(ui->progressPage);
SmartIDProgress s(pui);
return s.init(ui->idCountry->currentData().toString(), ui->idCodeSID->text(), digiDoc->fileName()) &&
digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &s);
return s.init(ui->idCountry->currentData().toString(), code, digiDoc->fileName()) &&
digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &s) ? accept() : reject();
}
}
return false;
}
Loading
Loading