Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cb31379
Removed decryption methods from QSigner, reworked QCryptBackend lifec…
Apr 17, 2026
d5cac43
More cleanups
Apr 17, 2026
5b4494c
More cleanup, use std::expected
Apr 20, 2026
940727f
QCNG cleanup
Apr 22, 2026
4a431ca
Share backend between network/crypto
Apr 22, 2026
513cabf
Remove extra status
Apr 22, 2026
f5d5aff
Use const/mutable
Apr 22, 2026
aebc56b
Merge branch 'backend-cleanup' of github.com:lauris71/DigiDoc4-Client…
Apr 22, 2026
a4011ce
Include TokenData.h
Apr 22, 2026
53ac965
Pass Backend as unique ptr
Apr 24, 2026
4547a50
Open QCNG keys on login and keep until logout/delete
Apr 27, 2026
9bb3329
Removed notification about entering PIN twice for CDoc2 online container
Apr 27, 2026
febac3b
Fixed translations etc.
Apr 28, 2026
859db74
Add nack Diagnostics translations
Apr 29, 2026
a5b06ba
Put Diagnostics translations back
Apr 29, 2026
f93ec1d
Update client/CMakeLists.txt
metsma Apr 29, 2026
682f40d
Removed decryption methods from QSigner, reworked QCryptBackend lifec…
Apr 17, 2026
265f3ec
More cleanups
Apr 17, 2026
6fb2d6a
More cleanup, use std::expected
Apr 20, 2026
920df7b
QCNG cleanup
Apr 22, 2026
8c4a090
Share backend between network/crypto
Apr 22, 2026
d0c44e9
Use const/mutable
Apr 22, 2026
d64317a
Remove extra status
Apr 22, 2026
ec1b019
Include TokenData.h
Apr 22, 2026
b7cea1c
Pass Backend as unique ptr
Apr 24, 2026
3871c4c
Open QCNG keys on login and keep until logout/delete
Apr 27, 2026
060dca4
Removed notification about entering PIN twice for CDoc2 online container
Apr 27, 2026
ab18ed8
Fixed translations etc.
Apr 28, 2026
dd0a1a8
Add nack Diagnostics translations
Apr 29, 2026
c3307cb
Update client/CMakeLists.txt
metsma Apr 29, 2026
ea75370
Fix translations and code review
metsma Jun 1, 2026
f2e200e
Merge branch 'backend-cleanup' of github.com:lauris71/DigiDoc4-Client…
Jun 9, 2026
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
5 changes: 5 additions & 0 deletions client/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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
Expand Down
97 changes: 59 additions & 38 deletions client/CDocSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ CDocSupport::getCDocFileList(const QString &filename)
}

static libcdoc::result_t
getDecryptStatus(const std::vector<uint8_t>& 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:
Expand All @@ -109,44 +109,62 @@ getDecryptStatus(const std::vector<uint8_t>& result, QCryptoBackend::PinStatus p
}
}

static libcdoc::result_t
getDecryptResultStatus(const std::vector<uint8_t> &result, std::unique_ptr<QCryptoBackend> 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<uint8_t>& result, const std::vector<uint8_t> &data, bool oaep, unsigned int idx)
DDCryptoBackend::decryptRSA(std::vector<uint8_t>& dst, const std::vector<uint8_t> &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<uint8_t>& dst, const std::vector<uint8_t> &publicKey, const std::string &digest,
const std::vector<uint8_t> &algorithmID, const std::vector<uint8_t> &partyUInfo, const std::vector<uint8_t> &partyVInfo, unsigned int idx)
{
QCryptoBackend::PinStatus pin_status;
QByteArray decryptedKey = qApp->signer()->decrypt([&publicKey, &digest, &algorithmID, &partyUInfo, &partyVInfo](QCryptoBackend *backend) {
static const QHash<std::string_view, QCryptographicHash::Algorithm> 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<std::string_view, QCryptographicHash::Algorithm> 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<uint8_t>& dst, const std::vector<uint8_t> &key_material, const std::vector<uint8_t> &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<uint8_t>(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
Expand Down Expand Up @@ -281,32 +299,32 @@ libcdoc::result_t DDNetworkBackend::sendKey(
};

libcdoc::result_t
DDNetworkBackend::fetchKey(std::vector<uint8_t> &result,
const std::string &url,
const std::string &transaction_id) {
DDNetworkBackend::fetchKey(std::vector<uint8_t> &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<QCryptoBackend> 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<QNetworkAccessManager,QScopedPointerDeleteLater> nam(
CheckConnection::setupNAM(req, qApp->signer()->tokenauth().cert(), authKey, Settings::CDOC2_GET_CERT));
QEventLoop e;
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();
Expand All @@ -315,6 +333,9 @@ DDNetworkBackend::fetchKey(std::vector<uint8_t> &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;
}

Expand Down
10 changes: 9 additions & 1 deletion client/CDocSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#pragma once

#include "QCryptoBackend.h"

#include <QtCore/QObject>
#include <QtCore/QIODevice>
#include <QtCore/QFile>
Expand Down Expand Up @@ -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<QCryptoBackend> backend;
std::vector<uint8_t> secret;

explicit DDCryptoBackend() = default;

void setBackend(std::unique_ptr<QCryptoBackend> &&backend) {
this->backend = std::move(backend);
}
};

//
Expand Down Expand Up @@ -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;
};

Expand Down
29 changes: 4 additions & 25 deletions client/CryptoDoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ struct CryptoDoc::Private
std::vector<IOEntry> files;
std::vector<CKey> keys;

explicit Private() : network(crypto) {}
bool isEncryptedWarning(const QString &title) const;

bool isEncrypted() const {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.");
Expand Down
58 changes: 26 additions & 32 deletions client/QCNG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<NCRYPT_PROV_HANDLE> prov;
SCOPE<NCRYPT_KEY_HANDLE> 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<Private> p = std::make_unique<Private>();
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<DWORD>(), 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
Expand Down Expand Up @@ -155,33 +164,24 @@ QByteArray QCNG::deriveHMACExtract(const QByteArray &publicKey, const QByteArray
template<typename F>
QByteArray QCNG::exec(F &&func) const
{
d->err = UnknownError;
SCOPE<NCRYPT_PROV_HANDLE> prov;
if(FAILED(NCryptOpenStorageProvider(&prov, LPCWSTR(d->token.data(u"provider"_s).toString().utf16()), 0)))
return {};
SCOPE<NCRYPT_KEY_HANDLE> key;
if(FAILED(NCryptOpenKey(prov, &key, LPWSTR(d->token.data(u"key"_s).toString().utf16()),
d->token.data(u"spec"_s).value<DWORD>(), 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<TokenData> QCNG::tokens() const
QList<TokenData> QCNG::tokens()
{
QList<TokenData> result;
auto prop = [](NCRYPT_HANDLE handle, LPCWSTR param) -> QByteArray {
Expand Down Expand Up @@ -275,12 +275,6 @@ QList<TokenData> 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) {
Expand All @@ -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;
Expand Down
Loading
Loading