diff --git a/client/Application.cpp b/client/Application.cpp index e164686e8..333bef813 100644 --- a/client/Application.cpp +++ b/client/Application.cpp @@ -305,6 +305,10 @@ class Application::Private #ifdef Q_OS_WIN QStringList tempFiles; #endif // Q_OS_WIN + + ~Private() { + delete signer; + } }; Application::Application( int &argc, char **argv ) @@ -429,6 +433,7 @@ Application::Application( int &argc, char **argv ) // Clear obsolete registriy settings #ifndef Q_OS_DARWIN Settings::DEFAULT_DIR.clear(); + Settings::CDOC2_NOTIFICATION.clear(); #endif // Actions diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 47a4419d1..ec07b781f 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -93,11 +93,11 @@ CDocSupport::getCDocFileList(const QString &filename) } static libcdoc::result_t -getDecryptStatus(const std::vector& result, QCryptoBackend::PinStatus pin_status) +getDecryptStatus(QCryptoBackend::Status pin_status) { switch (pin_status) { case QCryptoBackend::PinOK: - return (result.empty()) ? DDCryptoBackend::BACKEND_ERROR : libcdoc::OK; + return libcdoc::OK; case QCryptoBackend::PinCanceled: return DDCryptoBackend::PIN_CANCELED; case QCryptoBackend::PinIncorrect: @@ -109,44 +109,62 @@ getDecryptStatus(const std::vector& result, QCryptoBackend::PinStatus p } } +static libcdoc::result_t +getDecryptResultStatus(const std::vector &result, std::unique_ptr backend) +{ + if (!result.empty()) + return libcdoc::OK; + libcdoc::result_t mapped = getDecryptStatus(backend->status); + return mapped == libcdoc::OK ? DDCryptoBackend::BACKEND_ERROR : mapped; +} + libcdoc::result_t -DDCryptoBackend::decryptRSA(std::vector& result, const std::vector &data, bool oaep, unsigned int idx) +DDCryptoBackend::decryptRSA(std::vector& dst, const std::vector &data, bool oaep, unsigned int idx) { - QCryptoBackend::PinStatus pin_status; - QByteArray qkek = qApp->signer()->decrypt([qdata = toByteArray(data), &oaep](QCryptoBackend *backend) { - return backend->decrypt(qdata, oaep); - }, pin_status); - result.assign(qkek.cbegin(), qkek.cend()); - return getDecryptStatus(result, pin_status); + if (!backend) { + auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + if (!val) + return getDecryptStatus(val.error()); + backend.reset(val.value()); + } + QByteArray decryptedKey = backend->decrypt(toByteArray(data), oaep); + dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); + return getDecryptResultStatus(dst, std::move(backend)); } libcdoc::result_t DDCryptoBackend::deriveConcatKDF(std::vector& dst, const std::vector &publicKey, const std::string &digest, const std::vector &algorithmID, const std::vector &partyUInfo, const std::vector &partyVInfo, unsigned int idx) { - QCryptoBackend::PinStatus pin_status; - QByteArray decryptedKey = qApp->signer()->decrypt([&publicKey, &digest, &algorithmID, &partyUInfo, &partyVInfo](QCryptoBackend *backend) { - static const QHash SHA_MTH{ - {"http://www.w3.org/2001/04/xmlenc#sha256", QCryptographicHash::Sha256}, - {"http://www.w3.org/2001/04/xmlenc#sha384", QCryptographicHash::Sha384}, - {"http://www.w3.org/2001/04/xmlenc#sha512", QCryptographicHash::Sha512} - }; - return backend->deriveConcatKDF(toByteArray(publicKey), SHA_MTH.value(digest), - toByteArray(algorithmID), toByteArray(partyUInfo), toByteArray(partyVInfo)); - }, pin_status); + static const QHash SHA_MTH{ + {"http://www.w3.org/2001/04/xmlenc#sha256", QCryptographicHash::Sha256}, + {"http://www.w3.org/2001/04/xmlenc#sha384", QCryptographicHash::Sha384}, + {"http://www.w3.org/2001/04/xmlenc#sha512", QCryptographicHash::Sha512} + }; + if (!backend) { + auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + if (!val) + return getDecryptStatus(val.error()); + backend.reset(val.value()); + } + QByteArray decryptedKey = backend->deriveConcatKDF(toByteArray(publicKey), SHA_MTH.value(digest), + toByteArray(algorithmID), toByteArray(partyUInfo), toByteArray(partyVInfo)); dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); - return getDecryptStatus(dst, pin_status); + return getDecryptResultStatus(dst, std::move(backend)); } libcdoc::result_t DDCryptoBackend::deriveHMACExtract(std::vector& dst, const std::vector &key_material, const std::vector &salt, unsigned int idx) { - QCryptoBackend::PinStatus pin_status; - QByteArray qkekpm = qApp->signer()->decrypt([qkey_material = toByteArray(key_material), qsalt = toByteArray(salt)](QCryptoBackend *backend) { - return backend->deriveHMACExtract(qkey_material, qsalt, ECC_KEY_LEN); - }, pin_status); - dst = std::vector(qkekpm.cbegin(), qkekpm.cend()); - return getDecryptStatus(dst, pin_status); + if (!backend) { + auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + if (!val) + return getDecryptStatus(val.error()); + backend.reset(val.value()); + } + QByteArray decryptedKey = backend->deriveHMACExtract(toByteArray(key_material), toByteArray(salt), ECC_KEY_LEN); + dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); + return getDecryptResultStatus(dst, std::move(backend)); } libcdoc::result_t @@ -281,22 +299,25 @@ libcdoc::result_t DDNetworkBackend::sendKey( }; libcdoc::result_t -DDNetworkBackend::fetchKey(std::vector &result, - const std::string &url, - const std::string &transaction_id) { +DDNetworkBackend::fetchKey(std::vector &result, const std::string &url, const std::string &transaction_id) +{ QNetworkRequest req(QStringLiteral("%1/key-capsules/%2").arg(QString::fromStdString(url), QLatin1String(transaction_id.c_str()))); req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); if(!checkConnection()) { last_error = "No connection"; return BACKEND_ERROR; } - QCryptoBackend::PinStatus pin_status; - auto authKey = dispatchToMain([&] { - return qApp->signer()->key(pin_status); - }); + + TokenData auth = qApp->signer()->tokenauth(); + auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + if (!val.value()) + return getDecryptStatus(val.error()); + std::unique_ptr backend(val.value()); + + auto authKey = backend->getKey(); if (!authKey.handle()) { - last_error = qApp->signer()->getLastErrorStr().toStdString(); - return getDecryptStatus(result, pin_status); + last_error = "Cannot create authentication key"; + return BACKEND_ERROR; } QScopedPointer nam( CheckConnection::setupNAM(req, qApp->signer()->tokenauth().cert(), authKey, Settings::CDOC2_GET_CERT)); @@ -304,9 +325,6 @@ DDNetworkBackend::fetchKey(std::vector &result, QNetworkReply *reply = nam->get(req); connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); e.exec(); - if(authKey.handle()) { - qApp->signer()->logout(); - } if(reply->error() != QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 201) { last_error = reply->errorString().toStdString(); @@ -315,6 +333,9 @@ DDNetworkBackend::fetchKey(std::vector &result, QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); QByteArray key_material = QByteArray::fromBase64(json.value(QLatin1String("ephemeral_key_material")).toString().toLatin1()); result.assign(key_material.cbegin(), key_material.cend()); + + crypto.setBackend(std::move(backend)); + return libcdoc::OK; } diff --git a/client/CDocSupport.h b/client/CDocSupport.h index b3c87bd90..42f6efc12 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -19,6 +19,8 @@ #pragma once +#include "QCryptoBackend.h" + #include #include #include @@ -76,9 +78,14 @@ struct DDCryptoBackend final : public libcdoc::CryptoBackend { unsigned int idx) final; std::string getLastErrorStr(libcdoc::result_t code) const final; + std::unique_ptr backend; std::vector secret; explicit DDCryptoBackend() = default; + + void setBackend(std::unique_ptr &&backend) { + this->backend = std::move(backend); + } }; // @@ -110,8 +117,9 @@ struct DDNetworkBackend final : public libcdoc::NetworkBackend, private QObject return libcdoc::NOT_IMPLEMENTED; } - explicit DDNetworkBackend() = default; + explicit DDNetworkBackend(DDCryptoBackend &_crypto) : crypto(_crypto) {} + DDCryptoBackend &crypto; std::string last_error; }; diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 81af61290..babdfebfa 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -84,6 +84,7 @@ struct CryptoDoc::Private std::vector files; std::vector keys; + explicit Private() : network(crypto) {} bool isEncryptedWarning(const QString &title) const; bool isEncrypted() const { @@ -340,28 +341,6 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) return false; } - if (d->reader->version == 2 && - (lock->type == libcdoc::Lock::Type::SERVER) && - !Settings::CDOC2_NOTIFICATION.isSet()) { - auto *dlg = WarningDialog::create() - ->withTitle(tr("You must enter your PIN code twice in order to decrypt the CDOC2 container")) - ->withText(tr( - "The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. " - "Second PIN entry is required to decrypt the CDOC2 container.")) - ->setCancelText(WarningDialog::Cancel) - ->addButton(WarningDialog::OK, QMessageBox::Ok) - ->addButton(tr("Don't show again"), QMessageBox::Ignore); - switch (dlg->exec()) - { - case QMessageBox::Ok: break; - case QMessageBox::Ignore: - Settings::CDOC2_NOTIFICATION = true; - break; - default: - return false; - } - } - d->crypto.secret.assign(secret.cbegin(), secret.cend()); TempListConsumer cons; @@ -394,13 +373,13 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) str = tr("Cannot read file."); break; case DDCryptoBackend::PIN_CANCELED: - str = tr("PIN entry canceled"); + str = QCryptoBackend::errorString(QCryptoBackend::Status::PinCanceled); break; case DDCryptoBackend::PIN_INCORRECT: - str = tr("PIN incorrect"); + str = QCryptoBackend::errorString(QCryptoBackend::Status::PinIncorrect); break; case DDCryptoBackend::PIN_LOCKED: - str = tr("PIN locked"); + QCryptoBackend::errorString(QCryptoBackend::Status::PinLocked); break; default: str = tr("Please check your internet connection and network settings."); diff --git a/client/QCNG.cpp b/client/QCNG.cpp index 7107299e5..51f9abf76 100644 --- a/client/QCNG.cpp +++ b/client/QCNG.cpp @@ -41,21 +41,30 @@ struct SCOPE constexpr T* operator&() noexcept { return &d; } }; -class QCNG::Private +struct QCNG::Private { -public: - TokenData token; - QCNG::PinStatus err = QCNG::PinOK; + SCOPE prov; + SCOPE key; + bool pss; }; -QCNG::QCNG( QObject *parent ) - : QCryptoBackend(parent) - , d(new Private) -{} +QCNG::QCNG() noexcept = default; + +QCNG::~QCNG() noexcept = default; -QCNG::~QCNG() +QCNG::Status QCNG::login(const TokenData &token) { - delete d; + std::unique_ptr p = std::make_unique(); + if(FAILED(NCryptOpenStorageProvider(&p->prov, LPCWSTR(token.data(u"provider"_s).toString().utf16()), 0))) + return DeviceError; + if(FAILED(NCryptOpenKey(p->prov, &p->key, LPWSTR(token.data(u"key"_s).toString().utf16()), + token.data(u"spec"_s).value(), 0))) + return DeviceError; + // https://docs.microsoft.com/en-us/archive/blogs/alejacma/smart-cards-pin-gets-cached + NCryptSetProperty(p->key, NCRYPT_PIN_PROPERTY, nullptr, 0, 0); + p->pss = token.data(u"PSS"_s).toBool(); + d = std::move(p); + return PinOK; } QByteArray QCNG::decrypt(const QByteArray &data, bool oaep) const @@ -155,33 +164,24 @@ QByteArray QCNG::deriveHMACExtract(const QByteArray &publicKey, const QByteArray template QByteArray QCNG::exec(F &&func) const { - d->err = UnknownError; - SCOPE prov; - if(FAILED(NCryptOpenStorageProvider(&prov, LPCWSTR(d->token.data(u"provider"_s).toString().utf16()), 0))) - return {}; - SCOPE key; - if(FAILED(NCryptOpenKey(prov, &key, LPWSTR(d->token.data(u"key"_s).toString().utf16()), - d->token.data(u"spec"_s).value(), 0))) + if (!d) return {}; - // https://docs.microsoft.com/en-us/archive/blogs/alejacma/smart-cards-pin-gets-cached - NCryptSetProperty(key, NCRYPT_PIN_PROPERTY, nullptr, 0, 0); + status = UnknownError; QByteArray result; - switch(func(prov, key, result)) + switch(func(d->prov, d->key, result)) { case ERROR_SUCCESS: - d->err = PinOK; + status = PinOK; return result; case SCARD_W_CANCELLED_BY_USER: case ERROR_CANCELLED: - d->err = PinCanceled; + status = PinCanceled; default: return {}; } } -QCNG::PinStatus QCNG::lastError() const { return d->err; } - -QList QCNG::tokens() const +QList QCNG::tokens() { QList result; auto prop = [](NCRYPT_HANDLE handle, LPCWSTR param) -> QByteArray { @@ -275,12 +275,6 @@ QList QCNG::tokens() const return result; } -QCNG::PinStatus QCNG::login(const TokenData &token) -{ - d->token = token; - return d->err = QCNG::PinOK; -} - QByteArray QCNG::sign(QCryptographicHash::Algorithm type, const QByteArray &digest) const { return exec([&](NCRYPT_PROV_HANDLE prov, NCRYPT_KEY_HANDLE key, QByteArray &result) { @@ -301,7 +295,7 @@ QByteArray QCNG::sign(QCryptographicHash::Algorithm type, const QByteArray &dige bool isRSA = algo == QLatin1String("RSA"); DWORD padding {}; PVOID paddingInfo {}; - if(isRSA && d->token.data(u"PSS"_s).toBool()) + if(isRSA && d->pss) { padding = BCRYPT_PAD_PSS; paddingInfo = &rsaPSS; diff --git a/client/QCNG.h b/client/QCNG.h index 279860ab5..7b060e9e7 100644 --- a/client/QCNG.h +++ b/client/QCNG.h @@ -20,33 +20,32 @@ #pragma once #include "QCryptoBackend.h" +#include "TokenData.h" #include #include class QCNG final: public QCryptoBackend { - Q_OBJECT public: - explicit QCNG(QObject *parent = nullptr); - ~QCNG() final; + explicit QCNG() noexcept; + ~QCNG() noexcept final; + + Status login(const TokenData &token) final; - QList tokens() const final; QByteArray decrypt(const QByteArray &data, bool oaep) const final; QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const final; QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final; - PinStatus lastError() const final; - PinStatus login(const TokenData &token) final; - void logout() final {} QByteArray sign(QCryptographicHash::Algorithm type, const QByteArray &digest) const final; + static QList tokens(); private: template QByteArray derive(const QByteArray &publicKey, F &&func) const; template QByteArray exec(F &&func) const; - class Private; - Private *d; + struct Private; + std::unique_ptr d; }; diff --git a/client/QCryptoBackend.cpp b/client/QCryptoBackend.cpp index 254ebe201..dd523d2f9 100644 --- a/client/QCryptoBackend.cpp +++ b/client/QCryptoBackend.cpp @@ -19,16 +19,157 @@ #include "QCryptoBackend.h" -QString QCryptoBackend::errorString(PinStatus error) +#include "TokenData.h" +#ifdef Q_OS_WIN +#include "QCNG.h" +#endif +#include "QPKCS11.h" + +#include +#include + +// TODO: Port everything to the new OpenSSL API +#define OPENSSL_SUPPRESS_DEPRECATED + +#include +#include +#include + +std::expected +QCryptoBackend::getBackend(const TokenData& token) { +#ifdef Q_OS_WIN + auto backend = std::make_unique(); +#else + auto backend = std::make_unique(); +#endif + backend->cert = token.cert(); + Status status; + do { + status = backend->login(token); + } while (status == PinIncorrect); + if (status != PinOK) return std::unexpected(status); + return backend.release(); +} + +QList +QCryptoBackend::getTokens() +{ +#ifdef Q_OS_WIN + return QCNG::tokens(); +#else + return QPKCS11::tokens(); +#endif +} + +QString QCryptoBackend::errorString(Status error) { switch( error ) { case PinOK: return QString(); - case PinCanceled: return tr("PIN Canceled"); - case PinLocked: return tr("PIN locked"); - case PinIncorrect: return tr("PIN Incorrect"); - case GeneralError: return tr("PKCS11 general error"); - case DeviceError: return tr("PKCS11 device error"); - default: return tr("Unknown error"); + case PinCanceled: return QCoreApplication::translate("QCryptoBackend", "PIN entry canceled"); + case PinLocked: return QCoreApplication::translate("QCryptoBackend", "PIN locked"); + case PinIncorrect: return QCoreApplication::translate("QCryptoBackend", "PIN incorrect"); + case GeneralError: return QCoreApplication::translate("QCryptoBackend", "PKCS11 general error"); + case DeviceError: return QCoreApplication::translate("QCryptoBackend", "PKCS11 device error"); + default: return QCoreApplication::translate("QCryptoBackend", "Unknown error"); } } + +static int rsa_sign(int type, const unsigned char *m, unsigned int m_len, unsigned char *sigret, unsigned int *siglen, const RSA *rsa) +{ + auto *backend = (QCryptoBackend*) RSA_get_ex_data(rsa, 0); + QCryptographicHash::Algorithm algo = QCryptographicHash::Sha256; + switch(type) + { + case NID_sha224: algo = QCryptographicHash::Sha224; break; + case NID_sha256: algo = QCryptographicHash::Sha256; break; + case NID_sha384: algo = QCryptographicHash::Sha384; break; + case NID_sha512: algo = QCryptographicHash::Sha512; break; + } + QByteArray result = backend->sign(algo, QByteArray::fromRawData((const char*)m, int(m_len))); + if(result.isEmpty()) + return 0; + *siglen = (unsigned int)result.size(); + memcpy(sigret, result.constData(), size_t(result.size())); + return 1; +} + +static ECDSA_SIG* +ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM * /*inv*/, const BIGNUM * /*rp*/, EC_KEY *eckey) +{ + auto *backend = (QCryptoBackend*)EC_KEY_get_ex_data(eckey, 0); + QByteArray result = backend->sign(QCryptographicHash::Sha512, QByteArray::fromRawData((const char*)dgst, dgst_len)); + if(result.isEmpty()) + return nullptr; + QByteArray r = result.left(result.size()/2); + QByteArray s = result.right(result.size()/2); + ECDSA_SIG *sig = ECDSA_SIG_new(); + ECDSA_SIG_set0(sig, + BN_bin2bn((const unsigned char*)r.data(), int(r.size()), nullptr), + BN_bin2bn((const unsigned char*)s.data(), int(s.size()), nullptr)); + return sig; +} + +static RSA_METHOD *get_rsa_method(bool release = false) +{ + static RSA_METHOD *method = nullptr; + if (!method && !release) { + method = RSA_meth_dup(RSA_get_default_method()); + RSA_meth_set1_name(method, "QSmartCard"); + RSA_meth_set_sign(method, rsa_sign); + } else if (method && release) { + RSA_meth_free(method); + method = nullptr; + } + return method; +} + +static EC_KEY_METHOD *get_ec_method(bool release = false) +{ + static EC_KEY_METHOD *method = nullptr; + if(!method && !release) { + method = EC_KEY_METHOD_new(EC_KEY_get_default_method()); + using EC_KEY_sign = int (*)(int type, const unsigned char *dgst, int dlen, unsigned char *sig, + unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *eckey); + using EC_KEY_sign_setup = int (*)(EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp); + EC_KEY_sign sign = nullptr; + EC_KEY_sign_setup sign_setup = nullptr; + EC_KEY_METHOD_get_sign(method, &sign, &sign_setup, nullptr); + EC_KEY_METHOD_set_sign(method, sign, sign_setup, ecdsa_do_sign); + } else if (method && release) { + EC_KEY_METHOD_free(method); + method = nullptr; + } + return method; +} + +QSslKey +QCryptoBackend::getKey() const +{ + QSslKey key = cert.publicKey(); + if(!key.handle()) { + status = GeneralError; + return {}; + } + if(key.algorithm() == QSsl::Ec) + { + auto *ec = (EC_KEY*)key.handle(); + EC_KEY_set_method(ec, get_ec_method()); + EC_KEY_set_ex_data(ec, 0, (void *) this); + } + else + { + RSA *rsa = (RSA*)key.handle(); + RSA_set_method(rsa, get_rsa_method()); + RSA_set_ex_data(rsa, 0, (void *) this); + } + return key; +} + +void +QCryptoBackend::shutDown() +{ + get_rsa_method(true); + get_ec_method(true); +} + diff --git a/client/QCryptoBackend.h b/client/QCryptoBackend.h index 210826b96..9f60ce469 100644 --- a/client/QCryptoBackend.h +++ b/client/QCryptoBackend.h @@ -19,16 +19,17 @@ #pragma once -#include -#include +#include + +#include class TokenData; +class QSslKey; -class QCryptoBackend: public QObject +class QCryptoBackend { - Q_OBJECT public: - enum PinStatus : quint8 + enum Status : quint8 { PinOK, PinCanceled, @@ -39,17 +40,49 @@ class QCryptoBackend: public QObject UnknownError }; - using QObject::QObject; + virtual ~QCryptoBackend() {}; - virtual QList tokens() const = 0; virtual QByteArray decrypt(const QByteArray &data, bool oaep) const = 0; virtual QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const = 0; virtual QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const = 0; - virtual PinStatus lastError() const { return PinOK; } - virtual PinStatus login(const TokenData &cert) = 0; - virtual void logout() = 0; virtual QByteArray sign(QCryptographicHash::Algorithm method, const QByteArray &digest) const = 0; - static QString errorString( PinStatus error ); + /** + * @brief Get the SSL key for the certificate + * + * @return the Qt SSL key + */ + QSslKey getKey() const; + /** + * @brief Get a new Backend object and log in with the given token + * + * @param token the token to use + * @return the new backend object or an error code + */ + static std::expected getBackend(const TokenData& token); + /** + * @brief Shut down all backends + * + * This should be called when the application is about to exit. It releases all static data held by backend(s) (e.g. PKCS11 library) + */ + static void shutDown(); + + /** + * @brief The status of the last operation + */ + mutable Status status = PinOK; + + /** + * @brief Get a list of all available tokens + * + * @return list of all available tokens + */ + static QList getTokens(); + + static QString errorString(Status error); +protected: + virtual Status login(const TokenData &cert) = 0; + + QSslCertificate cert; }; diff --git a/client/QPKCS11.cpp b/client/QPKCS11.cpp index 296bd84f2..4adf9c0ea 100644 --- a/client/QPKCS11.cpp +++ b/client/QPKCS11.cpp @@ -17,7 +17,8 @@ * */ -#include "QPKCS11_p.h" +#include "QPKCS11.h" +#include "pkcs11.h" #include "Application.h" #include "CryptoDoc.h" @@ -29,6 +30,7 @@ #include #include +#include #include @@ -37,6 +39,7 @@ #include #include +#include template static QString toQString(const Container &c) @@ -44,7 +47,138 @@ static QString toQString(const Container &c) return QString::fromLatin1((const char*)std::data(c), std::size(c)); } -QByteArray QPKCS11::Private::attribute(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type) const +struct QPKCS11Library +{ + explicit QPKCS11Library(const QString &driver); + ~QPKCS11Library(); + + QByteArray attribute(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type) const; + std::vector findObject(CK_SESSION_HANDLE session, CK_OBJECT_CLASS cls, const QByteArray &id = {}) const; + static std::shared_ptr current(); + + QLibrary lib; + CK_FUNCTION_LIST_PTR f {}; + bool isFinDriver = false; +}; + +QPKCS11Library::QPKCS11Library(const QString &driver) + : lib(driver) +{ + qWarning() << "Loading:" << driver; + if(auto l = CK_C_GetFunctionList(lib.resolve("C_GetFunctionList")); + !l || l(&f) != CKR_OK) + { + qWarning() << "Failed to resolve symbols" << lib.errorString(); + return; + } + + CK_C_INITIALIZE_ARGS init_args { nullptr, nullptr, nullptr, nullptr, CKF_OS_LOCKING_OK, nullptr }; + CK_RV err = f->C_Initialize(&init_args); + if(err != CKR_OK && err != CKR_CRYPTOKI_ALREADY_INITIALIZED) + { + qWarning() << "Failed to initalize"; + f = nullptr; + return; + } + + CK_INFO info{}; + f->C_GetInfo(&info); + qWarning() + << QStringLiteral("%1 (%2.%3)").arg(toQString(info.manufacturerID)) + .arg(info.cryptokiVersion.major).arg(info.cryptokiVersion.minor) << '\n' + << QStringLiteral("%1 (%2.%3)").arg(toQString(info.libraryDescription)) + .arg(info.libraryVersion.major).arg(info.libraryVersion.minor) << '\n' + << "Flags:" << info.flags; + isFinDriver = toQString(info.libraryDescription).contains(QLatin1String("MPOLLUX"), Qt::CaseInsensitive); +} + +QPKCS11Library::~QPKCS11Library() +{ + if(f) + f->C_Finalize(nullptr); +} + +static std::shared_ptr loadLibrary(const QString &driver) +{ + static std::mutex loadedMutex; + static std::shared_ptr loaded; + std::lock_guard lock(loadedMutex); + if(loaded && loaded->lib.fileName() == driver) + return loaded; + + auto lib = std::make_shared(driver); + if(!lib->f) + return {}; + loaded = lib; + return lib; +} + +std::shared_ptr QPKCS11Library::current() +{ + static const QMultiHash drivers { +#ifdef Q_OS_MAC + { QApplication::applicationDirPath() + "/opensc-pkcs11.so", {} }, + { "/Library/latvia-eid/lib/eidlv-pkcs11.bundle/Contents/MacOS/eidlv-pkcs11", "3BDB960080B1FE451F830012428F536549440F900020" }, // LV-G2 + { "/Library/latvia-eid/lib/eidlv-pkcs11.bundle/Contents/MacOS/eidlv-pkcs11", "3BDC960080B1FE451F830012428F54654944320F900012" }, // LV-G2.1 + { "/Library/mCard/lib/mcard-pkcs11.so", "3B9D188131FC358031C0694D54434F5373020604D1" }, // LT MaskTech 2.6.4 + { "/Library/mCard/lib/mcard-pkcs11.so", "3B9D188131FC358031C0694D54434F5373020605D0" }, // LT MaskTech 2.6.5 + { "/Library/Atostek ID/Atostek-ID-PKCS11.dylib", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 + { "/Library/Atostek ID/Atostek-ID-PKCS11.dylib", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 + { "/Library/Atostek ID/Atostek-ID-PKCS11.dylib", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 + { "/Library/mPolluxDigiSign/libcryptoki.dylib", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 + { "/Library/mPolluxDigiSign/libcryptoki.dylib", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 + { "/Library/mPolluxDigiSign/libcryptoki.dylib", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 + { "/Library/Frameworks/eToken.framework/Versions/Current/libeToken.dylib", "3BD5180081313A7D8073C8211030" }, + { "/Library/Frameworks/eToken.framework/Versions/Current/libeToken.dylib", "3BD518008131FE7D8073C82110F4" }, + { "/Library/Frameworks/eToken.framework/Versions/Current/libeToken.dylib", "3BFF9600008131FE4380318065B0846566FB12017882900085" }, + { "/Library/Frameworks/eToken.framework/Versions/Current/libIDPrimePKCS11.dylib", "3BFF9600008131804380318065B0850300EF120FFE82900066" }, + { "/Library/Frameworks/eToken.framework/Versions/Current/libIDPrimePKCS11.dylib", "3BFF9600008131FE4380318065B0855956FB120FFE82900000" }, +#elif defined(Q_OS_WIN) + { "opensc-pkcs11.dll", {} }, +#else + { "opensc-pkcs11.so", {} }, +#if defined(Q_OS_LINUX) + { "/opt/latvia-eid/lib/eidlv-pkcs11.so", "3BDB960080B1FE451F830012428F536549440F900020" }, // LV-G2 + { "/opt/latvia-eid/lib/eidlv-pkcs11.so", "3BDC960080B1FE451F830012428F54654944320F900012" }, // LV-G2.1 + { "mcard-pkcs11.so", "3B9D188131FC358031C0694D54434F5373020604D1" }, // LT MaskTech 2.6.4 + { "mcard-pkcs11.so", "3B9D188131FC358031C0694D54434F5373020605D0" }, // LT MaskTech 2.6.5 +#if Q_PROCESSOR_WORDSIZE == 8 + { "/usr/lib/Atostek-ID-PKCS11.so", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 + { "/usr/lib/Atostek-ID-PKCS11.so", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 + { "/usr/lib/Atostek-ID-PKCS11.so", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 + { "/usr/lib64/libcryptoki.so", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 + { "/usr/lib64/libcryptoki.so", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 + { "/usr/lib64/libcryptoki.so", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 +#else + { "libcryptoki.so", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 + { "libcryptoki.so", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 + { "libcryptoki.so", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 +#endif + { "/usr/lib/libeTPkcs11.so", "3BD5180081313A7D8073C8211030" }, + { "/usr/lib/libeTPkcs11.so", "3BD518008131FE7D8073C82110F4" }, + { "/usr/lib/libeTPkcs11.so", "3BFF9600008131FE4380318065B0846566FB12017882900085" }, + { "/usr/lib/libIDPrimePKCS11.so", "3BFF9600008131804380318065B0850300EF120FFE82900066" }, + { "/usr/lib/libIDPrimePKCS11.so", "3BFF9600008131FE4380318065B0855956FB120FFE82900000" }, +#endif +#endif + }; + for(const QString &reader: QPCSC::instance().readers()) + { + QPCSCReader r(reader, &QPCSC::instance()); + if(!r.isPresent()) + continue; + QByteArray atr = r.atr(); + for(auto i = drivers.cbegin(); i != drivers.cend(); ++i) { + if(i.value() == atr) { + if(auto lib = loadLibrary(i.key())) + return lib; + } + } + } + return loadLibrary(drivers.key({})); +} + +QByteArray QPKCS11Library::attribute(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type) const { QByteArray data; CK_ATTRIBUTE attr { type, nullptr, 0 }; @@ -57,7 +191,7 @@ QByteArray QPKCS11::Private::attribute(CK_SESSION_HANDLE session, CK_OBJECT_HAND return data; } -std::vector QPKCS11::Private::findObject(CK_SESSION_HANDLE session, CK_OBJECT_CLASS cls, const QByteArray &id) const +std::vector QPKCS11Library::findObject(CK_SESSION_HANDLE session, CK_OBJECT_CLASS cls, const QByteArray &id) const { std::vector result; if(!f) @@ -83,47 +217,73 @@ std::vector QPKCS11::Private::findObject(CK_SESSION_HANDLE ses } +struct QPKCS11::Private +{ + void closeSession() + { + id.clear(); + if(l && l->f && session) { + l->f->C_Logout(session); + l->f->C_CloseSession(session); + } + session = 0; + } + + std::shared_ptr l; + CK_SESSION_HANDLE session = 0; + QByteArray id; + bool isPSS = false; +}; -QPKCS11::QPKCS11( QObject *parent ) - : QCryptoBackend(parent) - , d(new Private) +QPKCS11::QPKCS11() + : QCryptoBackend() + , d(std::make_unique()) { } -QPKCS11::~QPKCS11() +QPKCS11::~QPKCS11() noexcept { - unload(); - delete d; + d->closeSession(); } QByteArray QPKCS11::decrypt(const QByteArray &data, bool oaep) const { - std::vector key = d->findObject(d->session, CKO_PRIVATE_KEY, d->id); - if(key.size() != 1) + auto key = d->l->findObject(d->session, CKO_PRIVATE_KEY, d->id); + if(key.size() != 1){ + status = GeneralError; return {}; + } CK_RSA_PKCS_OAEP_PARAMS params { CKM_SHA256, CKG_MGF1_SHA256, 0, nullptr, 0 }; auto mech = oaep ? CK_MECHANISM{ CKM_RSA_PKCS_OAEP, ¶ms, sizeof(params) } : CK_MECHANISM{ CKM_RSA_PKCS, nullptr, 0 }; - if(d->f->C_DecryptInit(d->session, &mech, key.front()) != CKR_OK) + if(d->l->f->C_DecryptInit(d->session, &mech, key.front()) != CKR_OK) { + status = GeneralError; return {}; + } CK_ULONG size = 0; - if(d->f->C_Decrypt(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), nullptr, &size) != CKR_OK) + if(d->l->f->C_Decrypt(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), nullptr, &size) != CKR_OK) { + status = GeneralError; return {}; - + } QByteArray result(int(size), 0); - if(d->f->C_Decrypt(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), CK_BYTE_PTR(result.data()), &size) != CKR_OK) + if(d->l->f->C_Decrypt(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), CK_BYTE_PTR(result.data()), &size) != CKR_OK) { + status = GeneralError; return {}; + } + return result; } QByteArray QPKCS11::derive(const QByteArray &publicKey) const { - std::vector key = d->findObject(d->session, CKO_PRIVATE_KEY, d->id); - if(key.size() != 1) + std::vector key = d->l->findObject(d->session, CKO_PRIVATE_KEY, d->id); + if(key.size() != 1) { + status = GeneralError; return {}; + } CK_ECDH1_DERIVE_PARAMS ecdh_parms { CKD_NULL, 0, nullptr, CK_ULONG(publicKey.size()), CK_BYTE_PTR(publicKey.data()) }; CK_MECHANISM mech { CKM_ECDH1_DERIVE, &ecdh_parms, sizeof(CK_ECDH1_DERIVE_PARAMS) }; @@ -141,10 +301,12 @@ QByteArray QPKCS11::derive(const QByteArray &publicKey) const {CKA_VALUE_LEN, &value_len, sizeof(value_len)}, }); CK_OBJECT_HANDLE newkey = CK_INVALID_HANDLE; - if(d->f->C_DeriveKey(d->session, &mech, key.front(), newkey_template.data(), CK_ULONG(newkey_template.size()), &newkey) != CKR_OK) + if(d->l->f->C_DeriveKey(d->session, &mech, key.front(), newkey_template.data(), CK_ULONG(newkey_template.size()), &newkey) != CKR_OK) { + status = GeneralError; return {}; + } - return d->attribute(d->session, newkey, CKA_VALUE); + return d->l->attribute(d->session, newkey, CKA_VALUE); } QByteArray QPKCS11::deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, @@ -179,12 +341,13 @@ QByteArray QPKCS11::deriveHMACExtract(const QByteArray &publicKey, const QByteAr auto ctx = libcdoc::make_unique_ptr(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr)); QByteArray out(keySize, 0); auto outlen = size_t(out.length()); - auto isError = [](int err) { + auto isError = [this](int err) { if(err < 1) { unsigned long errorCode = 0; while((errorCode = ERR_get_error())) qCWarning(CRYPTO) << ERR_error_string(errorCode, nullptr); + status = GeneralError; } return err < 1; }; @@ -199,64 +362,30 @@ QByteArray QPKCS11::deriveHMACExtract(const QByteArray &publicKey, const QByteAr return out; } -bool QPKCS11::isLoaded() const -{ - return d->f != nullptr; -} - -bool QPKCS11::load( const QString &driver ) +QPKCS11::Status QPKCS11::login(const TokenData &t) { - if(d->lib.fileName() == driver && isLoaded()) - return true; - qWarning() << "Loading:" << driver; - unload(); - d->lib.setFileName( driver ); - if(auto l = CK_C_GetFunctionList(d->lib.resolve("C_GetFunctionList")); - !l || l(&d->f) != CKR_OK) - { - qWarning() << "Failed to resolve symbols" << d->lib.errorString(); - return false; + if(!d->l) { + d->l = QPKCS11Library::current(); } + if(!d->l || !d->l->f) + return UnknownError; - CK_C_INITIALIZE_ARGS init_args { nullptr, nullptr, nullptr, nullptr, CKF_OS_LOCKING_OK, nullptr }; - CK_RV err = d->f->C_Initialize( &init_args ); - if( err != CKR_OK && err != CKR_CRYPTOKI_ALREADY_INITIALIZED ) - { - qWarning() << "Failed to initalize"; - return false; - } - - CK_INFO info{}; - d->f->C_GetInfo( &info ); - qWarning() - << QStringLiteral("%1 (%2.%3)").arg(toQString(info.manufacturerID)) - .arg(info.cryptokiVersion.major).arg(info.cryptokiVersion.minor) << '\n' - << QStringLiteral("%1 (%2.%3)").arg(toQString(info.libraryDescription)) - .arg(info.libraryVersion.major).arg(info.libraryVersion.minor) << '\n' - << "Flags:" << info.flags; - d->isFinDriver = toQString(info.libraryDescription).contains(QLatin1String("MPOLLUX"), Qt::CaseInsensitive); - return true; -} - -QPKCS11::PinStatus QPKCS11::login(const TokenData &t) -{ - logout(); - + d->closeSession(); auto currentSlot = t.data(QStringLiteral("slot")).value(); d->id = t.data(QStringLiteral("id")).toByteArray(); d->isPSS = t.data(QStringLiteral("PSS")).toBool(); CK_TOKEN_INFO token; - if(d->f->C_GetTokenInfo(currentSlot, &token) != CKR_OK || - d->f->C_OpenSession(currentSlot, CKF_SERIAL_SESSION, nullptr, nullptr, &d->session) != CKR_OK) + if(d->l->f->C_GetTokenInfo(currentSlot, &token) != CKR_OK || + d->l->f->C_OpenSession(currentSlot, CKF_SERIAL_SESSION, nullptr, nullptr, &d->session) != CKR_OK) return UnknownError; - std::vector list = d->findObject(d->session, CKO_CERTIFICATE, d->id); - if(list.size() != 1 || QSslCertificate(d->attribute(d->session, list.front(), CKA_VALUE), QSsl::Der) != t.cert()) + std::vector list = d->l->findObject(d->session, CKO_CERTIFICATE, d->id); + if(list.size() != 1 || QSslCertificate(d->l->attribute(d->session, list.front(), CKA_VALUE), QSsl::Der) != t.cert()) return UnknownError; // Hack: Workaround broken FIN pkcs11 drivers not providing CKF_LOGIN_REQUIRED info - if(!d->isFinDriver && !(token.flags & CKF_LOGIN_REQUIRED)) + if(!d->l->isFinDriver && !(token.flags & CKF_LOGIN_REQUIRED)) return PinOK; SslCertificate cert(t.cert()); @@ -271,48 +400,44 @@ QPKCS11::PinStatus QPKCS11::login(const TokenData &t) PinPopup p(isSign ? QSmartCardData::Pin2Type : QSmartCardData::Pin1Type, f, cert, Application::mainWindow()); p.open(); p.startTimer(); - return waitFor(d->f->C_Login, d->session, CKU_USER, nullptr, 0); + 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->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pin.constData()), CK_ULONG(pin.size())); + return d->l->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pin.constData()), CK_ULONG(pin.size())); } }); - switch( err ) + switch(err) { case CKR_OK: - case CKR_USER_ALREADY_LOGGED_IN: return PinOK; + case CKR_USER_ALREADY_LOGGED_IN: + return PinOK; case CKR_CANCEL: - case CKR_FUNCTION_CANCELED: return PinCanceled; + case CKR_FUNCTION_CANCELED: + return PinCanceled; case CKR_PIN_INCORRECT: - d->f->C_GetTokenInfo(currentSlot, &token); + d->l->f->C_GetTokenInfo(currentSlot, &token); return (token.flags & CKF_USER_PIN_LOCKED) ? PinLocked : PinIncorrect; - case CKR_PIN_LOCKED: return PinLocked; - case CKR_DEVICE_ERROR: return DeviceError; - case CKR_GENERAL_ERROR: return GeneralError; - default: return UnknownError; - } -} - -void QPKCS11::logout() -{ - d->id.clear(); - if( d->f && d->session ) - { - d->f->C_Logout( d->session ); - d->f->C_CloseSession( d->session ); + case CKR_PIN_LOCKED: + return PinLocked; + case CKR_DEVICE_ERROR: + return DeviceError; + case CKR_GENERAL_ERROR: + return GeneralError; + default: + return UnknownError; } - d->session = 0; } -QList QPKCS11::tokens() const +QList QPKCS11::tokens() { QList list; - if(!d->f) + auto d = QPKCS11Library::current(); + if(!d || !d->f) return list; size_t size = 0; if(d->f->C_GetSlotList(CK_TRUE, nullptr, CK_ULONG_PTR(&size)) != CKR_OK) @@ -367,78 +492,17 @@ QList QPKCS11::tokens() const return list; } -bool QPKCS11::reload() -{ - static const QMultiHash drivers { -#ifdef Q_OS_MAC - { QApplication::applicationDirPath() + "/opensc-pkcs11.so", {} }, - { "/Library/latvia-eid/lib/eidlv-pkcs11.bundle/Contents/MacOS/eidlv-pkcs11", "3BDB960080B1FE451F830012428F536549440F900020" }, // LV-G2 - { "/Library/latvia-eid/lib/eidlv-pkcs11.bundle/Contents/MacOS/eidlv-pkcs11", "3BDC960080B1FE451F830012428F54654944320F900012" }, // LV-G2.1 - { "/Library/mCard/lib/mcard-pkcs11.so", "3B9D188131FC358031C0694D54434F5373020604D1" }, // LT MaskTech 2.6.4 - { "/Library/mCard/lib/mcard-pkcs11.so", "3B9D188131FC358031C0694D54434F5373020605D0" }, // LT MaskTech 2.6.5 - { "/Library/Atostek ID/Atostek-ID-PKCS11.dylib", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 - { "/Library/Atostek ID/Atostek-ID-PKCS11.dylib", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 - { "/Library/Atostek ID/Atostek-ID-PKCS11.dylib", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 - { "/Library/mPolluxDigiSign/libcryptoki.dylib", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 - { "/Library/mPolluxDigiSign/libcryptoki.dylib", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 - { "/Library/mPolluxDigiSign/libcryptoki.dylib", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 - { "/Library/Frameworks/eToken.framework/Versions/Current/libeToken.dylib", "3BD5180081313A7D8073C8211030" }, - { "/Library/Frameworks/eToken.framework/Versions/Current/libeToken.dylib", "3BD518008131FE7D8073C82110F4" }, - { "/Library/Frameworks/eToken.framework/Versions/Current/libeToken.dylib", "3BFF9600008131FE4380318065B0846566FB12017882900085" }, - { "/Library/Frameworks/eToken.framework/Versions/Current/libIDPrimePKCS11.dylib", "3BFF9600008131804380318065B0850300EF120FFE82900066" }, - { "/Library/Frameworks/eToken.framework/Versions/Current/libIDPrimePKCS11.dylib", "3BFF9600008131FE4380318065B0855956FB120FFE82900000" }, -#elif defined(Q_OS_WIN) - { "opensc-pkcs11.dll", {} }, -#else - { "opensc-pkcs11.so", {} }, -#if defined(Q_OS_LINUX) - { "/opt/latvia-eid/lib/eidlv-pkcs11.so", "3BDB960080B1FE451F830012428F536549440F900020" }, // LV-G2 - { "/opt/latvia-eid/lib/eidlv-pkcs11.so", "3BDC960080B1FE451F830012428F54654944320F900012" }, // LV-G2.1 - { "mcard-pkcs11.so", "3B9D188131FC358031C0694D54434F5373020604D1" }, // LT MaskTech 2.6.4 - { "mcard-pkcs11.so", "3B9D188131FC358031C0694D54434F5373020605D0" }, // LT MaskTech 2.6.5 -#if Q_PROCESSOR_WORDSIZE == 8 - { "/usr/lib/Atostek-ID-PKCS11.so", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 - { "/usr/lib/Atostek-ID-PKCS11.so", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 - { "/usr/lib/Atostek-ID-PKCS11.so", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 - { "/usr/lib64/libcryptoki.so", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 - { "/usr/lib64/libcryptoki.so", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 - { "/usr/lib64/libcryptoki.so", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 -#else - { "libcryptoki.so", "3B7F9600008031B865B08504021B1200F6829000" }, // FI-G3.1 - { "libcryptoki.so", "3B7F9600008031B865B085050011122460829000" }, // FI-G4 - { "libcryptoki.so", "3B7F9600008031B865B085051024122460829000" }, // FI-G4.1 -#endif - { "/usr/lib/libeTPkcs11.so", "3BD5180081313A7D8073C8211030" }, - { "/usr/lib/libeTPkcs11.so", "3BD518008131FE7D8073C82110F4" }, - { "/usr/lib/libeTPkcs11.so", "3BFF9600008131FE4380318065B0846566FB12017882900085" }, - { "/usr/lib/libIDPrimePKCS11.so", "3BFF9600008131804380318065B0850300EF120FFE82900066" }, - { "/usr/lib/libIDPrimePKCS11.so", "3BFF9600008131FE4380318065B0855956FB120FFE82900000" }, -#endif -#endif - }; - for(const QString &reader: QPCSC::instance().readers()) - { - QPCSCReader r(reader, &QPCSC::instance()); - if(!r.isPresent()) - continue; - QByteArray atr = r.atr(); - for(auto i = drivers.cbegin(); i != drivers.cend(); ++i) { - if(i.value() == atr && load(i.key())) - return true; - } - } - return load(drivers.key({})); -} - QByteArray QPKCS11::sign(QCryptographicHash::Algorithm type, const QByteArray &digest) const { - std::vector key = d->findObject(d->session, CKO_PRIVATE_KEY, d->id); - if(key.size() != 1) + std::vector key = d->l->findObject(d->session, CKO_PRIVATE_KEY, d->id); + if(key.size() != 1) { + status = GeneralError; return {}; + } CK_KEY_TYPE keyType = CKK_RSA; CK_ATTRIBUTE attribute { CKA_KEY_TYPE, &keyType, sizeof(keyType) }; - d->f->C_GetAttributeValue(d->session, key.front(), &attribute, 1); + d->l->f->C_GetAttributeValue(d->session, key.front(), &attribute, 1); CK_RSA_PKCS_PSS_PARAMS pssParams { CKM_SHA256, CKG_MGF1_SHA256, 32 }; CK_MECHANISM mech { keyType == CKK_ECDSA ? CKM_ECDSA : CKM_RSA_PKCS, nullptr, 0 }; @@ -473,22 +537,19 @@ QByteArray QPKCS11::sign(QCryptographicHash::Algorithm type, const QByteArray &d } data.append(digest); - if(d->f->C_SignInit(d->session, &mech, key.front()) != CKR_OK) + if(d->l->f->C_SignInit(d->session, &mech, key.front()) != CKR_OK) { + status = GeneralError; return {}; + } CK_ULONG size = 0; - if(d->f->C_Sign(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), nullptr, &size) != CKR_OK) + if(d->l->f->C_Sign(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), nullptr, &size) != CKR_OK) { + status = GeneralError; return {}; + } QByteArray sig(int(size), 0); - if(d->f->C_Sign(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), CK_BYTE_PTR(sig.data()), &size) != CKR_OK) + if(d->l->f->C_Sign(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), CK_BYTE_PTR(sig.data()), &size) != CKR_OK) { + status = GeneralError; return {}; + } return sig; } - -void QPKCS11::unload() -{ - logout(); - if(d->f) - d->f->C_Finalize(nullptr); - d->f = nullptr; - d->lib.unload(); -} diff --git a/client/QPKCS11.h b/client/QPKCS11.h index f9310f3bd..c01994018 100644 --- a/client/QPKCS11.h +++ b/client/QPKCS11.h @@ -21,27 +21,26 @@ #include "QCryptoBackend.h" +#include + class QPKCS11 final: public QCryptoBackend { - Q_OBJECT public: - explicit QPKCS11(QObject *parent = nullptr); - ~QPKCS11() final; + explicit QPKCS11(); + ~QPKCS11() noexcept final; QByteArray decrypt(const QByteArray &data, bool oaep) const final; QByteArray derive(const QByteArray &publicKey) const; QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const final; QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final; - bool isLoaded() const; - bool load( const QString &driver ); - void unload(); - PinStatus login(const TokenData &t) final; - void logout() final; - bool reload(); QByteArray sign(QCryptographicHash::Algorithm type, const QByteArray &digest) const final; - QList tokens() const final; + + Status login(const TokenData &t) final; + + static QList tokens(); + private: struct Private; - Private *d; + std::unique_ptr d; }; diff --git a/client/QPKCS11_p.h b/client/QPKCS11_p.h deleted file mode 100644 index f95870c94..000000000 --- a/client/QPKCS11_p.h +++ /dev/null @@ -1,41 +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 "QPKCS11.h" - -#include "pkcs11.h" - -#include - -#include - -struct QPKCS11::Private -{ - QByteArray attribute( CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type ) const; - std::vector findObject(CK_SESSION_HANDLE session, CK_OBJECT_CLASS cls, const QByteArray &id = {}) const; - - QLibrary lib; - CK_FUNCTION_LIST_PTR f = nullptr; - bool isFinDriver = false; - CK_SESSION_HANDLE session = 0; - QByteArray id; - bool isPSS = false; -}; diff --git a/client/QSigner.cpp b/client/QSigner.cpp index e8328e74a..ee960d727 100644 --- a/client/QSigner.cpp +++ b/client/QSigner.cpp @@ -49,77 +49,20 @@ static Q_LOGGING_CATEGORY(SLog, "qdigidoc4.QSigner") class QSigner::Private final { public: - QCryptoBackend *backend {}; QSmartCard *smartcard {}; TokenData auth, sign; QList cache; QReadWriteLock lock; QMutex sleepMutex; QWaitCondition sleepCond; - - static ECDSA_SIG* ecdsa_do_sign(const unsigned char *dgst, int dgst_len, - const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey); - static int rsa_sign(int type, const unsigned char *m, unsigned int m_len, - unsigned char *sigret, unsigned int *siglen, const RSA *rsa); - - RSA_METHOD *rsamethod = RSA_meth_dup(RSA_get_default_method()); - EC_KEY_METHOD *ecmethod = EC_KEY_METHOD_new(EC_KEY_get_default_method()); }; -ECDSA_SIG* QSigner::Private::ecdsa_do_sign(const unsigned char *dgst, int dgst_len, - const BIGNUM * /*inv*/, const BIGNUM * /*rp*/, EC_KEY *eckey) -{ - auto *backend = (QCryptoBackend*)EC_KEY_get_ex_data(eckey, 0); - QByteArray result = backend->sign(QCryptographicHash::Sha512, QByteArray::fromRawData((const char*)dgst, dgst_len)); - if(result.isEmpty()) - return nullptr; - QByteArray r = result.left(result.size()/2); - QByteArray s = result.right(result.size()/2); - ECDSA_SIG *sig = ECDSA_SIG_new(); - ECDSA_SIG_set0(sig, - BN_bin2bn((const unsigned char*)r.data(), int(r.size()), nullptr), - BN_bin2bn((const unsigned char*)s.data(), int(s.size()), nullptr)); - return sig; -} - -int QSigner::Private::rsa_sign(int type, const unsigned char *m, unsigned int m_len, - unsigned char *sigret, unsigned int *siglen, const RSA *rsa) -{ - auto *backend = (QCryptoBackend*)RSA_get_ex_data(rsa, 0); - QCryptographicHash::Algorithm algo = QCryptographicHash::Sha256; - switch(type) - { - case NID_sha224: algo = QCryptographicHash::Sha224; break; - case NID_sha256: algo = QCryptographicHash::Sha256; break; - case NID_sha384: algo = QCryptographicHash::Sha384; break; - case NID_sha512: algo = QCryptographicHash::Sha512; break; - } - QByteArray result = backend->sign(algo, QByteArray::fromRawData((const char*)m, int(m_len))); - if(result.isEmpty()) - return 0; - *siglen = (unsigned int)result.size(); - memcpy(sigret, result.constData(), size_t(result.size())); - return 1; -} - - - using namespace digidoc; QSigner::QSigner(QObject *parent) : QThread(parent) , d(new Private) { - RSA_meth_set1_name(d->rsamethod, "QSmartCard"); - RSA_meth_set_sign(d->rsamethod, Private::rsa_sign); - using EC_KEY_sign = int (*)(int type, const unsigned char *dgst, int dlen, unsigned char *sig, - unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *eckey); - using EC_KEY_sign_setup = int (*)(EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp); - EC_KEY_sign sign = nullptr; - EC_KEY_sign_setup sign_setup = nullptr; - EC_KEY_METHOD_get_sign(d->ecmethod, &sign, &sign_setup, nullptr); - EC_KEY_METHOD_set_sign(d->ecmethod, sign, sign_setup, Private::ecdsa_do_sign); - d->smartcard = new QSmartCard(parent); connect(this, &QSigner::error, this, [](const QString &title, const QString &msg) { WarningDialog::create() @@ -156,8 +99,6 @@ QSigner::~QSigner() d->sleepCond.wakeAll(); wait(); delete d->smartcard; - RSA_meth_free(d->rsamethod); - EC_KEY_METHOD_free(d->ecmethod); delete d; } @@ -171,113 +112,6 @@ X509Cert QSigner::cert() const return X509Cert((const unsigned char*)der.constData(), size_t(der.size()), X509Cert::Der); } -QByteArray QSigner::decrypt(std::function &&func, QCryptoBackend::PinStatus& pin_status) -{ - if(!d->lock.tryLockForWrite(10 * 1000)) - { - Q_EMIT error(tr("Failed to decrypt document"), tr("Signing/decrypting is already in progress another window.")); - pin_status = QCryptoBackend::GeneralError; - return {}; - } - - if( d->auth.cert().isNull() ) - { - Q_EMIT error(tr("Failed to decrypt document"), tr("Authentication certificate is not selected.")); - d->lock.unlock(); - pin_status = QCryptoBackend::GeneralError; - return {}; - } - - switch(pin_status = QCryptoBackend::PinStatus(login(d->auth))) - { - case QCryptoBackend::PinOK: break; - case QCryptoBackend::PinCanceled: return {}; - case QCryptoBackend::PinLocked: - Q_EMIT error(tr("Failed to decrypt document"), QCryptoBackend::errorString(pin_status)); - return {}; - default: - Q_EMIT error(tr("Failed to decrypt document"), tr("Failed to login token") + ' ' + QCryptoBackend::errorString(pin_status)); - return {}; - } - QByteArray result = waitFor(func, d->backend); - logout(); - if(d->backend->lastError() == QCryptoBackend::PinCanceled) { - pin_status = QCryptoBackend::PinCanceled; - return {}; - } - - if(result.isEmpty()) - Q_EMIT error(tr("Failed to decrypt document"), {}); - return result; -} - -QSslKey QSigner::key(QCryptoBackend::PinStatus& pin_status) -{ - QSslKey key = d->auth.cert().publicKey(); - if(!key.handle()) { - pin_status = QCryptoBackend::GeneralError; - return {}; - } - if(!d->lock.tryLockForWrite(10 * 1000)) { - Q_EMIT error(tr("Failed to decrypt document"), tr("Signing/decrypting is already in progress another window.")); - pin_status = QCryptoBackend::GeneralError; - return {}; - } - switch(pin_status = QCryptoBackend::PinStatus(login(d->auth))) - { - case QCryptoBackend::PinOK: break; - case QCryptoBackend::PinCanceled: return {}; - case QCryptoBackend::PinLocked: - Q_EMIT error(tr("Failed to decrypt document"), QCryptoBackend::errorString(pin_status)); - return {}; - default: - Q_EMIT error(tr("Failed to decrypt document"), tr("Failed to login token") + ' ' + QCryptoBackend::errorString(pin_status)); - return {}; - } - if(key.algorithm() == QSsl::Ec) - { - auto *ec = (EC_KEY*)key.handle(); - EC_KEY_set_method(ec, d->ecmethod); - EC_KEY_set_ex_data(ec, 0, d->backend); - } - else - { - RSA *rsa = (RSA*)key.handle(); - RSA_set_method(rsa, d->rsamethod); - RSA_set_ex_data(rsa, 0, d->backend); - } - return key; -} - -quint8 QSigner::login(const TokenData &token) const -{ - switch(auto status = d->backend->login(token)) - { - case QCryptoBackend::PinOK: return status; - case QCryptoBackend::PinIncorrect: - dispatchToMain([&]{ - WarningDialog::create() - ->withTitle(SslCertificate(token.cert()).keyUsage().contains(SslCertificate::NonRepudiation) ? tr("Failed to sign document") : tr("Failed to decrypt document")) - ->withText(QCryptoBackend::errorString(status)) - ->exec(); - }); - return login(token); - default: - d->lock.unlock(); - // QSmartCard should also know that PIN is blocked. - d->smartcard->reloadCard(d->smartcard->tokenData(), true); - return status; - } -} - -void QSigner::logout() const -{ - d->backend->logout(); - d->lock.unlock(); - // QSmartCard should also know that PIN1 info is updated - d->smartcard->reloadCard(d->smartcard->tokenData(), true); -} - QCryptographicHash::Algorithm QSigner::methodToNID(const std::string &method) { if(method == "http://www.w3.org/2001/04/xmldsig-more#sha224" || @@ -303,25 +137,11 @@ void QSigner::run() { d->auth.clear(); d->sign.clear(); -#ifdef Q_OS_WIN - d->backend = new QCNG(this); -#else - d->backend = new QPKCS11(this); -#endif - - while(!isInterruptionRequested()) - { - if(d->lock.tryLockForRead()) - { - auto *pkcs11 = qobject_cast(d->backend); - if(pkcs11 && !pkcs11->reload()) - { - Q_EMIT error(tr("Failed to load PKCS#11 module"), {}); - return; - } + while(!isInterruptionRequested()) { + if(d->lock.tryLockForRead()) { QList acards, scards; - QList cache = d->backend->tokens(); + QList cache = QCryptoBackend::getTokens(); if(cache != d->cache) { d->cache = std::move(cache); @@ -374,6 +194,7 @@ void QSigner::run() break; d->sleepCond.wait(&d->sleepMutex, 5000); } + QCryptoBackend::shutDown(); } void QSigner::selectCard(const TokenData &token) @@ -415,24 +236,38 @@ std::vector QSigner::sign(const std::string &method, const std::v throwException(tr("Signing certificate is not selected."), Exception::General) } - switch(auto status = QCryptoBackend::PinStatus(login(d->sign))) - { - case QCryptoBackend::PinOK: break; - case QCryptoBackend::PinCanceled: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(status)), Exception::PINCanceled); - case QCryptoBackend::PinLocked: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(status)), Exception::PINLocked); - default: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(status)), Exception::PINFailed); + auto unlock = qScopeGuard([this] { + d->lock.unlock(); + // QSmartCard should also know that PIN2 info is updated + d->smartcard->reloadCard(d->smartcard->tokenData(), true); + }); + + auto val = QCryptoBackend::getBackend(d->sign); + if (!val) { + switch(val.error()) { + case QCryptoBackend::PinCanceled: + throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::PINCanceled); + case QCryptoBackend::PinLocked: + throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::PINLocked); + default: + throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::PINFailed); + } } - QByteArray sig = waitFor(&QCryptoBackend::sign, d->backend, - methodToNID(method), QByteArray::fromRawData((const char*)digest.data(), int(digest.size()))); - logout(); - if(d->backend->lastError() == QCryptoBackend::PinCanceled) - throwException(tr("Failed to login token"), Exception::PINCanceled) + std::unique_ptr backend(val.value()); - if( sig.isEmpty() ) + QByteArray sig = waitFor(&QCryptoBackend::sign, backend.get(), + methodToNID(method), QByteArray::fromRawData((const char*)digest.data(), int(digest.size()))); + if (sig.isEmpty()) { + switch(backend->status) { + case QCryptoBackend::PinCanceled: + throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(backend->status)), Exception::PINCanceled); + case QCryptoBackend::PinLocked: + throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(backend->status)), Exception::PINLocked) + default: + break; + } throwException(tr("Failed to sign document"), Exception::General) + } return {sig.constBegin(), sig.constEnd()}; } @@ -443,6 +278,5 @@ TokenData QSigner::tokensign() const { return d->sign; } QString QSigner::getLastErrorStr() const { - QCryptoBackend::PinStatus status = d->backend->lastError(); - return d->backend->errorString(status); + return "Backend error"; } diff --git a/client/QSigner.h b/client/QSigner.h index 286320631..8a08150ea 100644 --- a/client/QSigner.h +++ b/client/QSigner.h @@ -41,9 +41,6 @@ class QSigner final: public QThread, public digidoc::Signer QList cache() const; digidoc::X509Cert cert() const final; - QByteArray decrypt(std::function &&func, QCryptoBackend::PinStatus& pin_status); - QSslKey key(QCryptoBackend::PinStatus& pin_status); - void logout() const; void selectCard(const TokenData &token); std::vector sign( const std::string &method, const std::vector &digest) const final; @@ -59,7 +56,6 @@ class QSigner final: public QThread, public digidoc::Signer void error(const QString &title, const QString &text); private: - quint8 login(const TokenData &token) const; static QCryptographicHash::Algorithm methodToNID(const std::string &method); void run() final; diff --git a/client/Settings.cpp b/client/Settings.cpp index f0ed78eef..7952cd169 100644 --- a/client/Settings.cpp +++ b/client/Settings.cpp @@ -30,6 +30,7 @@ using Option = Settings::Option; const Option Settings::CDOC2_DEFAULT { QStringLiteral("CDOC2-DEFAULT"), [] { return Application::confValue(QLatin1String("CDOC2-DEFAULT")).toBool(false); }}; +// TODO: Remove this after 2 releases const Option Settings::CDOC2_NOTIFICATION { QStringLiteral("CDOC2-NOTIFICATION"), false }; const Option Settings::CDOC2_USE_KEYSERVER { QStringLiteral("CDOC2-USE-KEYSERVER"), [] { return Application::confValue(QLatin1String("CDOC2-USE-KEYSERVER")).toBool(true); diff --git a/client/Settings.h b/client/Settings.h index d55a03695..b5d0af5dc 100644 --- a/client/Settings.h +++ b/client/Settings.h @@ -75,6 +75,7 @@ struct Settings }; static const Option CDOC2_DEFAULT; + // TODO: Remove this after 2 releases static const Option CDOC2_NOTIFICATION; static const Option CDOC2_USE_KEYSERVER; static const Option CDOC2_DEFAULT_KEYSERVER; diff --git a/client/translations/en.ts b/client/translations/en.ts index db28da8f2..4638247d4 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -576,14 +576,6 @@ Failed to add key Failed to add key - - You must enter your PIN code twice in order to decrypt the CDOC2 container - You must enter your PIN code twice in order to decrypt the CDOC2 container - - - The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. Second PIN entry is required to decrypt the CDOC2 container. - The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. Second PIN entry is required to decrypt the CDOC2 container. - Please check your internet connection and network settings. Please check your internet connection and network settings. @@ -600,10 +592,6 @@ Failed to open document Failed to open document - - Don't show again - Don't show again - Wrong password. Wrong password. @@ -2120,18 +2108,10 @@ ID-Card QCryptoBackend - - PIN Canceled - PIN Canceled - PIN locked PIN locked - - PIN Incorrect - PIN Incorrect - PKCS11 general error PKCS11 general error @@ -2144,6 +2124,14 @@ ID-Card Unknown error Unknown error + + PIN incorrect + PIN Incorrect + + + PIN entry canceled + PIN entry canceled + QFileDialog @@ -2154,10 +2142,6 @@ ID-Card QSigner - - Failed to load PKCS#11 module - Failed to load PKCS#11 module - Signing certificate is not selected. Signing certificate is not selected. @@ -2178,10 +2162,6 @@ ID-Card Signing/decrypting is already in progress another window. Signing/decrypting is already in progress another window. - - Authentication certificate is not selected. - Authentication certificate is not selected. - Failed to decrypt document Failed to decrypt document diff --git a/client/translations/et.ts b/client/translations/et.ts index 1e9b3e11d..85d873a0f 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -576,14 +576,6 @@ Failed to add key Võtme lisamine ebaõnnestus - - You must enter your PIN code twice in order to decrypt the CDOC2 container - CDOC2 ümbriku dekrüpteerimiseks tuleb sisestada PIN-koodi kaks korda - - - The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. Second PIN entry is required to decrypt the CDOC2 container. - Esimene PIN-koodi sisestamine on vajalik autentimiseks CDOC2 ümbrikus viidatud võtmeedastusserverisse. Teine PIN-koodi sisestamine on vajalik CDOC2 ümbriku dekrüpteerimiseks. - Please check your internet connection and network settings. Palun kontrolli internetiühendust ja võrgu sätteid. @@ -600,10 +592,6 @@ Failed to open document Dokumendi avamine ebaõnnestus - - Don't show again - Ära rohkem näita - Wrong password. Vale parool. @@ -2120,18 +2108,10 @@ ID-kaardiga QCryptoBackend - - PIN Canceled - PIN katkestatud - PIN locked PIN on lukus - - PIN Incorrect - Vale PIN - PKCS11 general error PKCS11 üldine viga @@ -2144,6 +2124,14 @@ ID-kaardiga Unknown error Tundmatu viga + + PIN incorrect + Vale PIN + + + PIN entry canceled + PIN sisestus katkestatus + QFileDialog @@ -2154,10 +2142,6 @@ ID-kaardiga QSigner - - Failed to load PKCS#11 module - PKCS#11 mooduli laadimine ebaõnnestus - Signing certificate is not selected. Allkirjastamise sertifikaat ei ole valitud. @@ -2178,10 +2162,6 @@ ID-kaardiga Signing/decrypting is already in progress another window. Allkirjastamine/dekrüpteerimine on juba käimas teises aknas. - - Authentication certificate is not selected. - Isikutuvastamise sertifikaat ei ole valitud. - Failed to decrypt document Dokumendi dekrüpteerimine ebaõnnestus diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 289eb2a6d..d5bbb4fa8 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -232,7 +232,7 @@ void ContainerPage::deleteConfirm(C *c, int index) if (dlg->exec() != QMessageBox::Ok) return; window()->setWindowFilePath({}); - window()->setWindowTitle(tr("DigiDoc4 Client")); + window()->setWindowTitle(QCoreApplication::translate("MainWindow", "DigiDoc4 Client")); if(QFile::exists(c->fileName())) QFile::remove(c->fileName()); emit action(ContainerClose);