From 4ba0798806f6f42f32a212f3b714b0ed04aca0a1 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 6 Aug 2025 13:17:19 +0300 Subject: [PATCH 01/44] Latest DigiDoc/CDoc --- .github/workflows/build.yml | 9 +- .gitmodules | 3 + CMakeLists.txt | 6 - client/Application.cpp | 2 + client/CDoc1.cpp | 627 ------------------------ client/CDoc1.h | 66 --- client/CDoc2.cpp | 778 ------------------------------ client/CDoc2.h | 45 -- client/CDocSupport.cpp | 394 +++++++++++++++ client/CDocSupport.h | 173 +++++++ client/CMakeLists.txt | 36 +- client/CheckConnection.cpp | 2 +- client/Crypto.cpp | 23 +- client/Crypto.h | 4 +- client/CryptoDoc.cpp | 507 +++++++++++-------- client/CryptoDoc.h | 80 +-- client/MainWindow.cpp | 87 +++- client/MainWindow.h | 9 +- client/Utils.h | 29 ++ client/common_enums.h | 1 + client/dialogs/AddRecipients.cpp | 48 +- client/dialogs/AddRecipients.h | 6 +- client/dialogs/KeyDialog.cpp | 43 +- client/dialogs/KeyDialog.h | 5 +- client/dialogs/PasswordDialog.cpp | 136 ++++++ client/dialogs/PasswordDialog.h | 47 ++ client/dialogs/PasswordDialog.ui | 175 +++++++ client/widgets/AddressItem.cpp | 155 ++++-- client/widgets/AddressItem.h | 13 +- client/widgets/AddressItem.ui | 8 + client/widgets/ContainerPage.cpp | 37 +- client/widgets/ContainerPage.h | 6 +- client/widgets/MainAction.cpp | 1 + schema/header.fbs | 59 --- schema/recipients.fbs | 82 ---- 35 files changed, 1617 insertions(+), 2085 deletions(-) delete mode 100644 client/CDoc1.cpp delete mode 100644 client/CDoc1.h delete mode 100644 client/CDoc2.cpp delete mode 100644 client/CDoc2.h create mode 100644 client/CDocSupport.cpp create mode 100644 client/CDocSupport.h create mode 100644 client/dialogs/PasswordDialog.cpp create mode 100644 client/dialogs/PasswordDialog.h create mode 100644 client/dialogs/PasswordDialog.ui delete mode 100644 schema/header.fbs delete mode 100644 schema/recipients.fbs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ffd2e060e..415aca4c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ permissions: env: BUILD_NUMBER: ${{ github.run_number }} CMAKE_BUILD_PARALLEL_LEVEL: 4 - UBUNTU_DEPS: ./libdigidocpp-pkg/*.deb cmake libldap2-dev gettext libpcsclite-dev libssl-dev libgl-dev libqt6svg6-dev qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools libflatbuffers-dev zlib1g-dev + UBUNTU_DEPS: ./libdigidocpp-pkg/*.deb cmake libldap2-dev gettext libpcsclite-dev libssl-dev libgl-dev libqt6svg6-dev qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools libflatbuffers-dev libxml2-dev zlib1g-dev jobs: macos: name: Build on macOS @@ -154,7 +154,7 @@ jobs: Rename-Item "libdigidocpp*.msi" libdigidocpp.msi msiexec /qn /i libdigidocpp.msi - name: Prepare vcpkg - uses: lukka/run-vcpkg@v7 + uses: lukka/run-vcpkg@v11 with: vcpkgArguments: openssl zlib flatbuffers vcpkgGitCommitId: 4008642a50a01a7115c2406b04d5273898e7fe1c @@ -173,9 +173,10 @@ jobs: dotnet tool install -g wix --version 5.0.2 wix extension -g add WixToolset.UI.wixext/5.0.2 - name: Build + env: + VCPKG_MANIFEST_DIR: ${{ github.workspace }}/client/libcdoc run: | - cmake "-GNinja" -B build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo ` - -DCMAKE_TOOLCHAIN_FILE=${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake + cmake "-GNinja" -B build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo "-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DVCPKG_MANIFEST_DIR=${{ github.workspace }}/client/libcdoc cmake --build build --target msi cmake --build build --target msishellext cmake --build build --target appx diff --git a/.gitmodules b/.gitmodules index eed6069ba..a73e2acd7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "common"] path = common url = ../qt-common +[submodule "client/libcdoc"] + path = client/libcdoc + url = git@github.com:open-eid/libcdoc.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b030881c..063b514ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,10 +30,6 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION YES) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUG NO) -if(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-object_path_lto,lto.o") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-object_path_lto,lto.o") -endif() set(CPACK_PACKAGE_VERSION ${VERSION}) set(CPACK_GENERATOR RPM) set(CPACK_PACKAGE_CONTACT "RIA ") @@ -48,8 +44,6 @@ find_package(libdigidocpp 4.2.0 REQUIRED HINTS /Library) message("-- Found libdigidocpp: ${libdigidocpp_DIR} (found version \"${libdigidocpp_VERSION}\")") find_package(LDAP REQUIRED) find_package(Qt6 6.2.0 REQUIRED COMPONENTS Core Widgets Network PrintSupport SvgWidgets LinguistTools) -find_package(FlatBuffers CONFIG REQUIRED NAMES FlatBuffers Flatbuffers) -find_package(ZLIB REQUIRED) if(APPLE) add_subdirectory(extensions/DigiDocQL) diff --git a/client/Application.cpp b/client/Application.cpp index c97902049..44e9ea3ad 100644 --- a/client/Application.cpp +++ b/client/Application.cpp @@ -23,6 +23,7 @@ #include "Common.h" #include "Configuration.h" +#include "CDocSupport.h" #include "MainWindow.h" #include "QSigner.h" #include "QSmartCard.h" @@ -462,6 +463,7 @@ Application::Application( int &argc, char **argv ) setQuitOnLastWindowClosed( true ); return; } + DDCDocLogger::setUpLogger(); QMetaObject::invokeMethod(this, [this] { #ifdef Q_OS_MAC diff --git a/client/CDoc1.cpp b/client/CDoc1.cpp deleted file mode 100644 index 338797151..000000000 --- a/client/CDoc1.cpp +++ /dev/null @@ -1,627 +0,0 @@ -/* - * QDigiDocClient - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "CDoc1.h" - -#include "Application.h" -#include "Crypto.h" -#include "QCryptoBackend.h" -#include "QSigner.h" -#include "Utils.h" -#include "dialogs/FileDialog.h" - -#include -#include -#include -#include -#include - -#include - -const QString CDoc1::AES128CBC_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#aes128-cbc"); -const QString CDoc1::AES192CBC_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#aes192-cbc"); -const QString CDoc1::AES256CBC_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#aes256-cbc"); -const QString CDoc1::AES128GCM_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#aes128-gcm"); -const QString CDoc1::AES192GCM_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#aes192-gcm"); -const QString CDoc1::AES256GCM_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#aes256-gcm"); -const QString CDoc1::RSA_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#rsa-1_5"); -const QString CDoc1::KWAES256_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#kw-aes256"); -const QString CDoc1::CONCATKDF_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#ConcatKDF"); -const QString CDoc1::AGREEMENT_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#ECDH-ES"); -const QString CDoc1::SHA256_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha256"); -const QString CDoc1::SHA384_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha384"); -const QString CDoc1::SHA512_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha512"); - -const QString CDoc1::DS = QStringLiteral("http://www.w3.org/2000/09/xmldsig#"); -const QString CDoc1::DENC = QStringLiteral("http://www.w3.org/2001/04/xmlenc#"); -const QString CDoc1::DSIG11 = QStringLiteral("http://www.w3.org/2009/xmldsig11#"); -const QString CDoc1::XENC11 = QStringLiteral("http://www.w3.org/2009/xmlenc11#"); - -const QString CDoc1::MIME_ZLIB = QStringLiteral("http://www.isi.edu/in-noes/iana/assignments/media-types/application/zip"); -const QString CDoc1::MIME_DDOC = QStringLiteral("http://www.sk.ee/DigiDoc/v1.3.0/digidoc.xsd"); -const QString CDoc1::MIME_DDOC_OLD = QStringLiteral("http://www.sk.ee/DigiDoc/1.3.0/digidoc.xsd"); - -const QHash CDoc1::ENC_MTH{ - {AES128CBC_MTH, EVP_aes_128_cbc()}, {AES192CBC_MTH, EVP_aes_192_cbc()}, {AES256CBC_MTH, EVP_aes_256_cbc()}, - {AES128GCM_MTH, EVP_aes_128_gcm()}, {AES192GCM_MTH, EVP_aes_192_gcm()}, {AES256GCM_MTH, EVP_aes_256_gcm()}, -}; -const QHash CDoc1::SHA_MTH{ - {SHA256_MTH, QCryptographicHash::Sha256}, {SHA384_MTH, QCryptographicHash::Sha384}, {SHA512_MTH, QCryptographicHash::Sha512} -}; - -CDoc1::CDoc1(const QString &path) - : QFile(path) -{ - setLastError(CryptoDoc::tr("An error occurred while opening the document.")); - if(!open(QFile::ReadOnly)) - return; - readXML(this, [this](QXmlStreamReader &xml) { - // EncryptedData - if(xml.name() == QLatin1String("EncryptedData")) - mime = xml.attributes().value(QLatin1String("MimeType")).toString(); - // EncryptedData/EncryptionProperties/EncryptionProperty - else if(xml.name() == QLatin1String("EncryptionProperty")) - { - for(const QXmlStreamAttribute &attr: xml.attributes()) - { - if(attr.name() != QLatin1String("Name")) - continue; - if(attr.value() == QLatin1String("orig_file")) - { - QStringList fileparts = xml.readElementText().split('|'); - files.push_back({ - fileparts.value(0), - fileparts.value(3), - fileparts.value(2), - fileparts.value(1).toUInt(), - {} - }); - } - else - properties[attr.value().toString()] = xml.readElementText(); - } - } - // EncryptedData/EncryptionMethod - else if(xml.name() == QLatin1String("EncryptionMethod")) - method = xml.attributes().value(QLatin1String("Algorithm")).toString(); - // EncryptedData/KeyInfo/EncryptedKey - if(xml.name() != QLatin1String("EncryptedKey")) - return; - - CKey key; - key.recipient = xml.attributes().value(QLatin1String("Recipient")).toString(); - while(!xml.atEnd()) - { - xml.readNext(); - if(xml.name() == QLatin1String("EncryptedKey") && xml.isEndElement()) - break; - if(!xml.isStartElement()) - continue; - if(xml.name() == QLatin1String("EncryptionMethod")) - { - auto method = xml.attributes().value(QLatin1String("Algorithm")); - key.unsupported = std::max(key.unsupported, method != KWAES256_MTH && method != RSA_MTH); - } - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod - else if(xml.name() == QLatin1String("AgreementMethod")) - key.unsupported = std::max(key.unsupported, xml.attributes().value(QLatin1String("Algorithm")) != AGREEMENT_MTH); - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod - else if(xml.name() == QLatin1String("KeyDerivationMethod")) - key.unsupported = std::max(key.unsupported, xml.attributes().value(QLatin1String("Algorithm")) != CONCATKDF_MTH); - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod/ConcatKDFParams - else if(xml.name() == QLatin1String("ConcatKDFParams")) - { - key.AlgorithmID = QByteArray::fromHex(xml.attributes().value(QLatin1String("AlgorithmID")).toUtf8()); - if(key.AlgorithmID.front() == char(0x00)) key.AlgorithmID.remove(0, 1); - key.PartyUInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyUInfo")).toUtf8()); - if(key.PartyUInfo.front() == char(0x00)) key.PartyUInfo.remove(0, 1); - key.PartyVInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyVInfo")).toUtf8()); - if(key.PartyVInfo.front() == char(0x00)) key.PartyVInfo.remove(0, 1); - } - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod/ConcatKDFParams/DigestMethod - else if(xml.name() == QLatin1String("DigestMethod")) - key.concatDigest = xml.attributes().value(QLatin1String("Algorithm")).toString(); - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/OriginatorKeyInfo/KeyValue/ECKeyValue/PublicKey - else if(xml.name() == QLatin1String("PublicKey")) - { - xml.readNext(); - key.publicKey = fromBase64(xml.text()); - } - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/X509Data/X509Certificate - else if(xml.name() == QLatin1String("X509Certificate")) - { - xml.readNext(); - key.setCert(QSslCertificate(fromBase64(xml.text()), QSsl::Der)); - } - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/CipherData/CipherValue - else if(xml.name() == QLatin1String("CipherValue")) - { - xml.readNext(); - key.cipher = fromBase64(xml.text()); - } - } - keys.append(std::move(key)); - }); - if(!keys.isEmpty()) - setLastError({}); - - if(files.empty() && properties.contains(QStringLiteral("Filename"))) - { - files.push_back({ - properties.value(QStringLiteral("Filename")), - {}, - mime == MIME_ZLIB ? properties.value(QStringLiteral("OriginalMimeType")) : mime, - properties.value(QStringLiteral("OriginalSize")).toUInt(), - {} - }); - } -} - -bool CDoc1::decryptPayload(const QByteArray &key) -{ - if(!isOpen()) - return false; - setLastError({}); - QByteArray data; - seek(0); - readXML(this, [&data](QXmlStreamReader &xml) { - // EncryptedData/KeyInfo - if(xml.name() == QLatin1String("KeyInfo")) - xml.skipCurrentElement(); - // EncryptedData/CipherData/CipherValue - else if(xml.name() == QLatin1String("CipherValue")) - { - xml.readNext(); - data = fromBase64(xml.text()); - } - }); - if(data.isEmpty()) - return setLastError(CryptoDoc::tr("Error parsing document")); - data = Crypto::cipher(ENC_MTH[method], key, data, false); - if(data.isEmpty()) - return setLastError(CryptoDoc::tr("Failed to decrypt document")); - - // remove ANSIX923 padding - if(data.size() > 0 && method == AES128CBC_MTH) - { - QByteArray ansix923(data[data.size()-1], 0); - ansix923[ansix923.size()-1] = char(ansix923.size()); - if(data.right(ansix923.size()) == ansix923) - { - qCDebug(CRYPTO) << "Removing ANSIX923 padding size:" << ansix923.size(); - data.resize(data.size() - ansix923.size()); - } - } - - if(mime == MIME_ZLIB) - { - // Add size header for qUncompress compatibilty - unsigned origsize = std::max(properties.value(QStringLiteral("OriginalSize")).toUInt(), 1); - qCDebug(CRYPTO) << "Decompressing zlib content size" << origsize; - QByteArray size(4, 0); - size[0] = char((origsize & 0xff000000) >> 24); - size[1] = char((origsize & 0x00ff0000) >> 16); - size[2] = char((origsize & 0x0000ff00) >> 8); - size[3] = char((origsize & 0x000000ff)); - data = qUncompress(size + data); - mime = properties[QStringLiteral("OriginalMimeType")]; - } - - if(mime == MIME_DDOC || mime == MIME_DDOC_OLD) - { - qCDebug(CRYPTO) << "Contains DDoc content" << mime; - QTemporaryFile ddoc(QDir::tempPath() + "/XXXXXX"); - if(!ddoc.open()) - return setLastError(CryptoDoc::tr("Failed to create temporary files
%1").arg(ddoc.errorString())); - ddoc.write(data); - ddoc.flush(); - ddoc.reset(); - files = readDDoc(&ddoc); - return !files.empty(); - } - - auto buffer = std::make_unique(); - buffer->setData(data); - if(!buffer->open(QBuffer::ReadWrite)) - return false; - qCDebug(CRYPTO) << "Contains raw file" << mime; - if(!files.empty()) - { - files[0].size = data.size(); - files[0].data = std::move(buffer); - } - else if(properties.contains(QStringLiteral("Filename"))) - { - files.push_back({ - properties.value(QStringLiteral("Filename")), - {}, - mime, - data.size(), - std::move(buffer), - }); - } - else - return setLastError(CryptoDoc::tr("Error parsing document")); - - return !files.empty(); -} - -CKey CDoc1::canDecrypt(const QSslCertificate &cert) const -{ - for(const CKey &k: qAsConst(keys)) - { - if(!ENC_MTH.contains(method) || - k.cert != cert || - k.cipher.isEmpty() || - k.unsupported) - continue; - if(cert.publicKey().algorithm() == QSsl::Rsa) - return k; - if(cert.publicKey().algorithm() == QSsl::Ec && - !k.publicKey.isEmpty()) - return k; - } - return {}; -} - -QByteArray CDoc1::fromBase64(QStringView data) -{ - unsigned int buf = 0; - int nbits = 0; - QByteArray result((data.size() * 3) / 4, Qt::Uninitialized); - - int offset = 0; - for(const QChar &i: data) - { - int ch = int(i.toLatin1()); - int d {}; - - if(ch >= 'A' && ch <= 'Z') - d = ch - 'A'; - else if(ch >= 'a' && ch <= 'z') - d = ch - 'a' + 26; - else if(ch >= '0' && ch <= '9') - d = ch - '0' + 52; - else if(ch == '+') - d = 62; - else if(ch == '/') - d = 63; - else - continue; - - buf = (buf << 6) | uint(d); - nbits += 6; - if(nbits >= 8) - { - nbits -= 8; - result[offset++] = char(buf >> nbits); - buf &= (1 << nbits) - 1; - } - } - - result.truncate(offset); - return result; -} - -std::vector CDoc1::readDDoc(QIODevice *ddoc) -{ - qCDebug(CRYPTO) << "Parsing DDOC container"; - std::vector files; - readXML(ddoc, [&files] (QXmlStreamReader &x) { - if(x.name() == QLatin1String("DataFile")) - { - File file; - file.name = x.attributes().value(QLatin1String("Filename")).toString().normalized(QString::NormalizationForm_C); - file.id = x.attributes().value(QLatin1String("Id")).toString().normalized(QString::NormalizationForm_C); - file.mime = x.attributes().value(QLatin1String("MimeType")).toString().normalized(QString::NormalizationForm_C); - x.readNext(); - auto buffer = std::make_unique(); - buffer->setData(fromBase64(x.text())); - buffer->open(QBuffer::ReadWrite); - file.size = buffer->data().size(); - file.data = std::move(buffer); - files.push_back(std::move(file)); - } - }); - return files; -} - -void CDoc1::readXML(QIODevice *io, const std::function &f) -{ - QXmlStreamReader r(io); - while(!r.atEnd()) - { - switch(r.readNext()) - { - case QXmlStreamReader::DTD: - qCWarning(CRYPTO) << "XML DTD Declarations are not supported"; - return; - case QXmlStreamReader::EntityReference: - qCWarning(CRYPTO) << "XML ENTITY References are not supported"; - return; - case QXmlStreamReader::StartElement: - f(r); - break; - default: - break; - } - } -} - -bool CDoc1::save(const QString &path) -{ - setLastError({}); - QFile cdoc(path); - if(!cdoc.open(QFile::WriteOnly)) - return setLastError(cdoc.errorString()); - - QBuffer data; - if(!data.open(QBuffer::WriteOnly)) - return false; - - QString mime, name; - if(files.size() > 1) - { - qCDebug(CRYPTO) << "Creating DDoc container"; - writeDDoc(&data); - mime = MIME_DDOC; - name = QStringLiteral("payload.ddoc"); - } - else - { - qCDebug(CRYPTO) << "Adding raw file"; - files[0].data->seek(0); - copyIODevice(files[0].data.get(), &data); - mime = files[0].mime; - name = files[0].name; - } - - QString method = AES256GCM_MTH; - QByteArray transportKey = Crypto::genKey(ENC_MTH[method]); - if(transportKey.isEmpty()) - return setLastError(QStringLiteral("Failed to generate transport key")); -#ifndef NDEBUG - qDebug() << "ENC Transport Key" << transportKey.toHex(); -#endif - - qCDebug(CRYPTO) << "Writing CDOC file ver 1.1 mime" << mime; - QMultiHash props { - { QStringLiteral("DocumentFormat"), QStringLiteral("ENCDOC-XML|1.1") }, - { QStringLiteral("LibraryVersion"), Application::applicationName() + "|" + Application::applicationVersion() }, - { QStringLiteral("Filename"), name }, - }; - for(const File &f: qAsConst(files)) - props.insert(QStringLiteral("orig_file"), QStringLiteral("%1|%2|%3|%4").arg(f.name).arg(f.size).arg(f.mime).arg(f.id)); - - QXmlStreamWriter w(&cdoc); - w.setAutoFormatting(true); - w.writeStartDocument(); - w.writeNamespace(DENC, QStringLiteral("denc")); - writeElement(w, DENC, QStringLiteral("EncryptedData"), [&]{ - if(!mime.isEmpty()) - w.writeAttribute(QStringLiteral("MimeType"), mime); - writeElement(w, DENC, QStringLiteral("EncryptionMethod"), { - {QStringLiteral("Algorithm"), method}, - }); - w.writeNamespace(DS, QStringLiteral("ds")); - writeElement(w, DS, QStringLiteral("KeyInfo"), [&]{ - for(const CKey &k: qAsConst(keys)) - { - writeElement(w, DENC, QStringLiteral("EncryptedKey"), [&]{ - if(!k.recipient.isEmpty()) - w.writeAttribute(QStringLiteral("Recipient"), k.recipient); - QByteArray cipher; - if(k.isRSA) - { - cipher = Crypto::encrypt(X509_get0_pubkey((const X509*)k.cert.handle()), RSA_PKCS1_PADDING, transportKey); - if(cipher.isEmpty()) - return; - writeElement(w, DENC, QStringLiteral("EncryptionMethod"), { - {QStringLiteral("Algorithm"), RSA_MTH}, - }); - writeElement(w, DS, QStringLiteral("KeyInfo"), [&]{ - writeElement(w, DS, QStringLiteral("X509Data"), [&]{ - writeBase64Element(w, DS, QStringLiteral("X509Certificate"), k.cert.toDer()); - }); - }); - } - else - { - EVP_PKEY *peerPKey = X509_get0_pubkey((const X509*)k.cert.handle()); - auto priv = Crypto::genECKey(peerPKey); - QByteArray sharedSecret = Crypto::derive(priv.get(), peerPKey); - if(sharedSecret.isEmpty()) - return; - - QByteArray oid = Crypto::curve_oid(peerPKey); - QByteArray SsDer = Crypto::toPublicKeyDer(priv.get()); - - QString concatDigest = SHA384_MTH; - switch((SsDer.size() - 1) / 2) { - case 32: concatDigest = SHA256_MTH; break; - case 48: concatDigest = SHA384_MTH; break; - default: concatDigest = SHA512_MTH; break; - } - QByteArray encryptionKey = Crypto::concatKDF(SHA_MTH[concatDigest], - sharedSecret, props.value(QStringLiteral("DocumentFormat")).toUtf8() + SsDer + k.cert.toDer()); -#ifndef NDEBUG - qDebug() << "ENC Ss" << SsDer.toHex(); - qDebug() << "ENC Ksr" << sharedSecret.toHex(); - qDebug() << "ENC ConcatKDF" << encryptionKey.toHex(); -#endif - - cipher = Crypto::aes_wrap(encryptionKey, transportKey, true); - if(cipher.isEmpty()) - return; - - writeElement(w, DENC, QStringLiteral("EncryptionMethod"), { - {QStringLiteral("Algorithm"), KWAES256_MTH}, - }); - writeElement(w, DS, QStringLiteral("KeyInfo"), [&]{ - writeElement(w, DENC, QStringLiteral("AgreementMethod"), { - {QStringLiteral("Algorithm"), AGREEMENT_MTH}, - }, [&]{ - w.writeNamespace(XENC11, QStringLiteral("xenc11")); - writeElement(w, XENC11, QStringLiteral("KeyDerivationMethod"), { - {QStringLiteral("Algorithm"), CONCATKDF_MTH}, - }, [&]{ - writeElement(w, XENC11, QStringLiteral("ConcatKDFParams"), { - {QStringLiteral("AlgorithmID"), QStringLiteral("00") + props.value(QStringLiteral("DocumentFormat")).toUtf8().toHex()}, - {QStringLiteral("PartyUInfo"), QStringLiteral("00") + SsDer.toHex()}, - {QStringLiteral("PartyVInfo"), QStringLiteral("00") + k.cert.toDer().toHex()}, - }, [&]{ - writeElement(w, DS, QStringLiteral("DigestMethod"), { - {QStringLiteral("Algorithm"), concatDigest}, - }); - }); - }); - writeElement(w, DENC, QStringLiteral("OriginatorKeyInfo"), [&]{ - writeElement(w, DS, QStringLiteral("KeyValue"), [&]{ - w.writeNamespace(DSIG11, QStringLiteral("dsig11")); - writeElement(w, DSIG11, QStringLiteral("ECKeyValue"), [&]{ - writeElement(w, DSIG11, QStringLiteral("NamedCurve"), { - {QStringLiteral("URI"), QStringLiteral("urn:oid:") + oid}, - }); - writeBase64Element(w, DSIG11, QStringLiteral("PublicKey"), SsDer); - }); - }); - }); - writeElement(w, DENC, QStringLiteral("RecipientKeyInfo"), [&]{ - writeElement(w, DS, QStringLiteral("X509Data"), [&]{ - writeBase64Element(w, DS, QStringLiteral("X509Certificate"), k.cert.toDer()); - }); - }); - }); - }); - } - writeElement(w, DENC, QStringLiteral("CipherData"), [&]{ - writeBase64Element(w, DENC, QStringLiteral("CipherValue"), cipher); - }); - }); - }}); - writeElement(w,DENC, QStringLiteral("CipherData"), [&]{ - writeBase64Element(w, DENC, QStringLiteral("CipherValue"), - Crypto::cipher(ENC_MTH[method], transportKey, data.buffer(), true) - ); - }); - writeElement(w, DENC, QStringLiteral("EncryptionProperties"), [&]{ - for(QMultiHash::const_iterator i = props.constBegin(); i != props.constEnd(); ++i) - writeElement(w, DENC, QStringLiteral("EncryptionProperty"), { - {QStringLiteral("Name"), i.key()}, - }, [&]{ - w.writeCharacters(i.value()); - }); - }); - }); - w.writeEndDocument(); - return true; -} - -QByteArray CDoc1::transportKey(const CKey &key) -{ - setLastError({}); - QByteArray decryptedKey = qApp->signer()->decrypt([&key](QCryptoBackend *backend) { - if(key.isRSA) - return backend->decrypt(key.cipher, false); - return backend->deriveConcatKDF(key.publicKey, SHA_MTH[key.concatDigest], - key.AlgorithmID, key.PartyUInfo, key.PartyVInfo); - }); - if(decryptedKey.isEmpty()) - { - setLastError(QStringLiteral("Failed to decrypt/derive key")); - return {}; - } - if(key.isRSA) - return decryptedKey; -#ifndef NDEBUG - qDebug() << "DEC Ss" << key.publicKey.toHex(); - qDebug() << "DEC ConcatKDF" << decryptedKey.toHex(); -#endif - return Crypto::aes_wrap(decryptedKey, key.cipher, false); -} - -int CDoc1::version() -{ - return 1; -} - -void CDoc1::writeAttributes(QXmlStreamWriter &x, const QMap &attrs) -{ - for(QMap::const_iterator i = attrs.cbegin(), end = attrs.cend(); i != end; ++i) - x.writeAttribute(i.key(), i.value()); -} - -void CDoc1::writeBase64Element(QXmlStreamWriter &x, const QString &ns, const QString &name, const QByteArray &data) -{ - x.writeStartElement(ns, name); - for(int i = 0; i < data.size(); i+=48) - x.writeCharacters(data.mid(i, 48).toBase64() + '\n'); - x.writeEndElement(); -} - -void CDoc1::writeDDoc(QIODevice *ddoc) -{ - qCDebug(CRYPTO) << "Creating DDOC container"; - QXmlStreamWriter x(ddoc); - x.setAutoFormatting(true); - x.writeStartDocument(); - x.writeDefaultNamespace(QStringLiteral("http://www.sk.ee/DigiDoc/v1.3.0#")); - x.writeStartElement(QStringLiteral("SignedDoc")); - writeAttributes(x, { - {QStringLiteral("format"), QStringLiteral("DIGIDOC-XML")}, - {QStringLiteral("version"), QStringLiteral("1.3")}, - }); - - for(const File &file: qAsConst(files)) - { - x.writeStartElement(QStringLiteral("DataFile")); - writeAttributes(x, { - {QStringLiteral("ContentType"), QStringLiteral("EMBEDDED_BASE64")}, - {QStringLiteral("Filename"), file.name}, - {QStringLiteral("Id"), file.id}, - {QStringLiteral("MimeType"), file.mime}, - {QStringLiteral("Size"), QString::number(file.size)}, - }); - std::array buf{}; - file.data->seek(0); - for(auto size = file.data->read(buf.data(), buf.size()); size > 0; size = file.data->read(buf.data(), buf.size())) - x.writeCharacters(QByteArray::fromRawData(buf.data(), size).toBase64() + '\n'); - x.writeEndElement(); //DataFile - } - - x.writeEndElement(); //SignedDoc - x.writeEndDocument(); -} - -void CDoc1::writeElement(QXmlStreamWriter &x, const QString &ns, const QString &name, std::function &&f) -{ - x.writeStartElement(ns, name); - if(f) - f(); - x.writeEndElement(); -} - -void CDoc1::writeElement(QXmlStreamWriter &x, const QString &ns, const QString &name, const QMap &attrs, std::function &&f) -{ - x.writeStartElement(ns, name); - writeAttributes(x, attrs); - if(f) - f(); - x.writeEndElement(); -} diff --git a/client/CDoc1.h b/client/CDoc1.h deleted file mode 100644 index 5cf1f46a6..000000000 --- a/client/CDoc1.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * QDigiDocClient - * - * 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 "CryptoDoc.h" - -#include -#include - -class QXmlStreamReader; -class QXmlStreamWriter; - -using EVP_CIPHER = struct evp_cipher_st; - -class CDoc1 final: public CDoc, private QFile -{ -public: - CDoc1() = default; - CDoc1(const QString &path); - CKey canDecrypt(const QSslCertificate &cert) const final; - bool decryptPayload(const QByteArray &key) final; - bool save(const QString &path) final; - QByteArray transportKey(const CKey &key) final; - int version() final; - -private: - void writeDDoc(QIODevice *ddoc); - - static QByteArray fromBase64(QStringView data); - static std::vector readDDoc(QIODevice *ddoc); - static void readXML(QIODevice *io, const std::function &f); - static void writeAttributes(QXmlStreamWriter &x, const QMap &attrs); - static void writeBase64Element(QXmlStreamWriter &x, const QString &ns, const QString &name, const QByteArray &data); - static void writeElement(QXmlStreamWriter &x, const QString &ns, const QString &name, std::function &&f = {}); - static void writeElement(QXmlStreamWriter &x, const QString &ns, const QString &name, const QMap &attrs, std::function &&f = {}); - - QString method, mime; - QHash properties; - - static const QString - AES128CBC_MTH, AES192CBC_MTH, AES256CBC_MTH, - AES128GCM_MTH, AES192GCM_MTH, AES256GCM_MTH, - SHA256_MTH, SHA384_MTH, SHA512_MTH, - RSA_MTH, CONCATKDF_MTH, AGREEMENT_MTH, KWAES256_MTH; - static const QString DS, DENC, DSIG11, XENC11; - static const QString MIME_ZLIB, MIME_DDOC, MIME_DDOC_OLD; - static const QHash ENC_MTH; - static const QHash SHA_MTH; -}; diff --git a/client/CDoc2.cpp b/client/CDoc2.cpp deleted file mode 100644 index 12a9b6376..000000000 --- a/client/CDoc2.cpp +++ /dev/null @@ -1,778 +0,0 @@ -/* - * QDigiDocClient - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "CDoc2.h" - -#include "Application.h" -#include "CheckConnection.h" -#include "Crypto.h" -#include "QCryptoBackend.h" -#include "QSigner.h" -#include "Settings.h" -#include "TokenData.h" -#include "Utils.h" -#include "header_generated.h" -#include "effects/FadeInNotification.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -const QByteArray CDoc2::LABEL = "CDOC\x02"; -const QByteArray CDoc2::CEK = "CDOC20cek"; -const QByteArray CDoc2::HMAC = "CDOC20hmac"; -const QByteArray CDoc2::KEK = "CDOC20kek"; -const QByteArray CDoc2::KEKPREMASTER = "CDOC20kekpremaster"; -const QByteArray CDoc2::PAYLOAD = "CDOC20payload"; -const QByteArray CDoc2::SALT = "CDOC20salt"; - -namespace cdoc20 { - bool checkConnection() { - if(CheckConnection().check()) - return true; - return dispatchToMain([] { - FadeInNotification::error(Application::mainWindow()->findChild(QStringLiteral("topBar")), - QCoreApplication::translate("MainWindow", "Check internet connection")); - return false; - }); - } - - QNetworkRequest req(const QString &keyserver_id, const QString &transaction_id = {}) { -#ifdef CONFIG_URL - QJsonObject list = Application::confValue(QLatin1String("CDOC2-CONF")).toObject(); - QJsonObject data = list.value(keyserver_id).toObject(); - QString url = transaction_id.isEmpty() ? - data.value(QLatin1String("POST")).toString(Settings::CDOC2_POST) : - data.value(QLatin1String("FETCH")).toString(Settings::CDOC2_GET); -#else - QString url = transaction_id.isEmpty() ? Settings::CDOC2_POST : Settings::CDOC2_GET; -#endif - if(url.isEmpty()) - return QNetworkRequest{}; - QNetworkRequest req(QStringLiteral("%1/key-capsules%2").arg(url, - transaction_id.isEmpty() ? QString(): QStringLiteral("/%1").arg(transaction_id))); - req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); - return req; - } - - struct stream final: public QIODevice - { - static constexpr qint64 CHUNK = 16LL * 1024LL; - QIODevice *io {}; - Crypto::Cipher *cipher {}; - z_stream s {}; - QByteArray buf; - int flush = Z_NO_FLUSH; - - stream(QIODevice *_io, Crypto::Cipher *_cipher) - : io(_io) - , cipher(_cipher) - { - if(io->isReadable()) - { - if(inflateInit2(&s, MAX_WBITS) == Z_OK) - open(QIODevice::ReadOnly); - } - if(io->isWritable()) - { - if(deflateInit(&s, Z_DEFAULT_COMPRESSION) == Z_OK) - open(QIODevice::WriteOnly); - } - } - - ~stream() final - { - if(io->isReadable()) - inflateEnd(&s); - if(io->isWritable()) - { - flush = Z_FINISH; - writeData(nullptr, 0); - deflateEnd(&s); - } - } - - bool isSequential() const final - { - return true; - } - - qint64 bytesAvailable() const final - { - return (io->bytesAvailable() - Crypto::Cipher::tagLen()) + buf.size() + QIODevice::bytesAvailable(); - } - - qint64 readData(char *data, qint64 maxlen) final - { - s.next_out = (Bytef*)data; - s.avail_out = uInt(maxlen); - std::array in{}; - for(int res = Z_OK; s.avail_out > 0 && res == Z_OK;) - { - if(auto insize = io->bytesAvailable() - Crypto::Cipher::tagLen(), - size = io->read(in.data(), qMin(insize, in.size())); size > 0) - { - if(!cipher->update(in.data(), int(size))) - return -1; - buf.append(in.data(), size); - } - s.next_in = (z_const Bytef*)buf.data(); - s.avail_in = uInt(buf.size()); - switch(res = inflate(&s, flush)) - { - case Z_OK: - buf = buf.right(s.avail_in); - break; - case Z_STREAM_END: - buf.clear(); - break; - default: return -1; - } - } - return qint64(maxlen - s.avail_out); - } - - qint64 writeData(const char *data, qint64 len) final - { - s.next_in = (z_const Bytef *)data; - s.avail_in = uInt(len); - std::array out{}; - while(true) { - s.next_out = (Bytef *)out.data(); - s.avail_out = out.size(); - int res = deflate(&s, flush); - if(res == Z_STREAM_ERROR) - return -1; - if(auto size = out.size() - s.avail_out; size > 0) - { - if(!cipher->update(out.data(), int(size)) || - io->write(out.data(), size) != size) - return -1; - } - if(res == Z_STREAM_END) - break; - if(flush == Z_FINISH) - continue; - if(s.avail_in == 0) - break; - } - return len; - } - }; - - struct TAR { - std::unique_ptr io; - explicit TAR(std::unique_ptr &&in) - : io(std::move(in)) - {} - - bool save(const std::vector &files) - { - auto writeHeader = [this](Header &h, qint64 size) { - h.chksum.fill(' '); - toOctal(h.size, size); - toOctal(h.chksum, h.checksum().first); - return io->write((const char*)&h, Header::Size) == Header::Size; - }; - auto writePadding = [this](qint64 size) { - QByteArray pad(padding(size), 0); - return io->write(pad) == pad.size(); - }; - auto toPaxRecord = [](const QByteArray &keyword, const QByteArray &value) { - QByteArray record = ' ' + keyword + '=' + value + '\n'; - QByteArray result; - for(auto len = record.size(); result.size() != len; ++len) - result = QByteArray::number(len + 1) + record; - return result; - }; - for(const CDoc::File &file: files) - { - Header h {}; - QByteArray filename = file.name.toUtf8(); - QByteArray filenameTruncated = filename.left(h.name.size()); - std::copy(filenameTruncated.cbegin(), filenameTruncated.cend(), h.name.begin()); - - if(filename.size() > 100 || - file.size > 07777777) - { - h.typeflag = 'x'; - QByteArray paxData; - if(filename.size() > 100) - paxData += toPaxRecord("path", filename); - if(file.size > 07777777) - paxData += toPaxRecord("size", QByteArray::number(file.size)); - if(!writeHeader(h, paxData.size()) || - io->write(paxData) != paxData.size() || - !writePadding(paxData.size())) - return false; - } - - h.typeflag = '0'; - if(!writeHeader(h, file.size)) - return false; - file.data->seek(0); - if(auto size = copyIODevice(file.data.get(), io.get()); size < 0 || !writePadding(size)) - return false; - } - return io->write((const char*)&Header::Empty, Header::Size) == Header::Size && - io->write((const char*)&Header::Empty, Header::Size) == Header::Size; - } - - std::vector files(bool &warning) const - { - std::vector result; - Header h {}; - auto readHeader = [&h, this] { return io->read((char*)&h, Header::Size) == Header::Size; }; - while(io->bytesAvailable() > 0) - { - if(!readHeader()) - return {}; - if(h.isNull()) - { - if(!readHeader() && !h.isNull()) - return {}; - warning = io->bytesAvailable() > 0; - return result; - } - if(!h.verify()) - return {}; - - CDoc::File f; - f.name = QString::fromUtf8(h.name.data(), std::min(h.name.size(), int(strlen(h.name.data())))); - f.size = fromOctal(h.size); - if(h.typeflag == 'x') - { - QByteArray paxData = io->read(f.size); - if(paxData.size() != f.size) - return {}; - io->skip(padding(f.size)); - if(!readHeader() || h.isNull() || !h.verify()) - return {}; - if(f.name.startsWith(QLatin1String("./PaxHeaders.X"))) - f.name = QString::fromUtf8(h.name.data(), std::min(h.name.size(), int(strlen(h.name.data())))); - f.size = fromOctal(h.size); - for(const QByteArray &data: paxData.split('\n')) - { - if(data.isEmpty()) - break; - const auto &headerValue = data.split('='); - const auto &lenKeyword = headerValue[0].split(' '); - if(data.size() + 1 != lenKeyword[0].toUInt()) - return {}; - if(lenKeyword[1] == "path") - f.name = QString::fromUtf8(headerValue[1]); - if(lenKeyword[1] == "size") - f.size = headerValue[1].toUInt(); - } - } - - if(h.typeflag == '0' || h.typeflag == 0) - { - if(f.size > 500L * 1024L * 1024L) - f.data = std::make_unique(); - else - f.data = std::make_unique(); - f.data->open(QIODevice::ReadWrite); - if(f.size != copyIODevice(io.get(), f.data.get(), f.size)) - return {}; - io->skip(padding(f.size)); - result.push_back(std::move(f)); - } - else - io->skip(f.size + padding(f.size)); - } - return result; - } - - private: - struct Header { - std::array name; - std::array mode; - std::array uid; - std::array gid; - std::array size; - std::array mtime; - std::array chksum; - char typeflag; - std::array linkname; - std::array magic; - std::array version; - std::array uname; - std::array gname; - std::array devmajor; - std::array devminor; - std::array prefix; - std::array padding; - - std::pair checksum() const - { - int64_t unsignedSum = 0; - int64_t signedSum = 0; - for (size_t i = 0, size = sizeof(Header); i < size; i++) { - unsignedSum += ((unsigned char*) this)[i]; - signedSum += ((signed char*) this)[i]; - } - return {unsignedSum, signedSum}; - } - - bool isNull() { - return memcmp(this, &Empty, sizeof(Header)) == 0; - } - - bool verify() { - auto copy = chksum; - chksum.fill(' '); - auto checkSum = checksum(); - chksum.swap(copy); - int64_t referenceChecksum = fromOctal(chksum); - return referenceChecksum == checkSum.first || - referenceChecksum == checkSum.second; - } - - static const Header Empty; - static const int Size; - }; - - template - static int64_t fromOctal(const std::array &data) - { - int64_t i = 0; - for(const char c: data) - { - if(c < '0' || c > '7') - continue; - i <<= 3; - i += c - '0'; - } - return i; - } - - template - static void toOctal(std::array &data, int64_t value) - { - data.fill(' '); - for(auto it = data.rbegin() + 1; it != data.rend(); ++it) - { - *it = char(value & 7) + '0'; - value >>= 3; - } - } - - static constexpr int padding(int64_t size) - { - return Header::Size - size % Header::Size; - } - }; - - const TAR::Header TAR::Header::Empty {}; - const int TAR::Header::Size = int(sizeof(TAR::Header)); -} - -CDoc2::CDoc2(const QString &path) - : QFile(path) -{ - using namespace cdoc20::Recipients; - using namespace cdoc20::Header; - setLastError(QStringLiteral("Invalid CDoc 2.0 header")); - uint32_t header_len = 0; - if(!open(QFile::ReadOnly) || - read(LABEL.length()) != LABEL || - read((char*)&header_len, int(sizeof(header_len))) != int(sizeof(header_len))) - return; - header_data = read(qFromBigEndian(header_len)); - if(header_data.size() != qFromBigEndian(header_len)) - return; - headerHMAC = read(KEY_LEN); - if(headerHMAC.size() != KEY_LEN) - return; - noncePos = pos(); - flatbuffers::Verifier verifier(reinterpret_cast(header_data.data()), header_data.size()); - if(!VerifyHeaderBuffer(verifier)) - return; - const auto *header = GetHeader(header_data.constData()); - if(!header) - return; - if(header->payload_encryption_method() != PayloadEncryptionMethod::CHACHA20POLY1305) - return; - const auto *recipients = header->recipients(); - if(!recipients) - return; - setLastError({}); - - auto toByteArray = [](const flatbuffers::Vector *data) -> QByteArray { - return data ? QByteArray((const char*)data->Data(), int(data->size())) : QByteArray(); - }; - auto toString = [](const flatbuffers::String *data) -> QString { - return data ? QString::fromUtf8(data->c_str(), data->size()) : QString(); - }; - for(const auto *recipient: *recipients){ - if(recipient->fmk_encryption_method() != FMKEncryptionMethod::XOR) - { - keys.append(CKey::Unsupported); - qWarning() << "Unsupported FMK encryption method: skipping"; - continue; - } - auto fillRecipient = [&] (auto key, bool isRSA, bool unsupported = false) { - CKey k(toByteArray(key->recipient_public_key()), isRSA); - k.recipient = toString(recipient->key_label()); - k.cipher = toByteArray(recipient->encrypted_fmk()); - k.unsupported = unsupported; - return k; - }; - switch(recipient->capsule_type()) - { - case Capsule::ECCPublicKeyCapsule: - if(const auto *key = recipient->capsule_as_ECCPublicKeyCapsule()) - { - CKey k = fillRecipient(key, false, key->curve() != EllipticCurve::secp384r1); - k.publicKey = toByteArray(key->sender_public_key()); - keys.append(std::move(k)); - } - break; - case Capsule::RSAPublicKeyCapsule: - if(const auto *key = recipient->capsule_as_RSAPublicKeyCapsule()) - { - CKey k = fillRecipient(key, true); - k.encrypted_kek = toByteArray(key->encrypted_kek()); - keys.append(std::move(k)); - } - break; - case Capsule::KeyServerCapsule: - if(const auto *server = recipient->capsule_as_KeyServerCapsule()) - { - auto fillKeyServer = [&] (auto key, bool isRSA, bool unsupported = false) { - CKey k = fillRecipient(key, isRSA, unsupported); - k.keyserver_id = toString(server->keyserver_id()); - k.transaction_id = toString(server->transaction_id()); - return k; - }; - switch(server->recipient_key_details_type()) - { - case ServerDetailsUnion::ServerEccDetails: - if(const auto *eccDetails = server->recipient_key_details_as_ServerEccDetails()) - keys.append(fillKeyServer(eccDetails, false, eccDetails->curve() != EllipticCurve::secp384r1)); - break; - case ServerDetailsUnion::ServerRsaDetails: - if(const auto *rsaDetails = server->recipient_key_details_as_ServerRsaDetails()) - keys.append(fillKeyServer(rsaDetails, true)); - break; - default: - keys.append(CKey::Unsupported); - qWarning() << "Unsupported Key Server Details: skipping"; - } - } - break; - default: - keys.append(CKey::Unsupported); - qWarning() << "Unsupported Key Details: skipping"; - } - } -} - -CKey CDoc2::canDecrypt(const QSslCertificate &cert) const -{ - auto key = keys.value(keys.indexOf(CKey(cert))); - if(key.unsupported || (!key.transaction_id.isEmpty() && cert.expiryDate() <= QDateTime::currentDateTimeUtc())) - return {}; - return key; -} - -bool CDoc2::decryptPayload(const QByteArray &fmk) -{ - if(!isOpen() || noncePos == -1) - return false; - setLastError({}); - seek(noncePos); - QByteArray cek = Crypto::expand(fmk, CEK); - QByteArray nonce = read(NONCE_LEN); -#ifndef NDEBUG - qDebug() << "cek" << cek.toHex(); - qDebug() << "nonce" << nonce.toHex(); -#endif - Crypto::Cipher dec(EVP_chacha20_poly1305(), cek, nonce, false); - if(!dec.updateAAD(PAYLOAD + header_data + headerHMAC)) - return false; - bool warning = false; - files = cdoc20::TAR(std::unique_ptr(new cdoc20::stream(this, &dec))).files(warning); - if(warning) - setLastError(tr("CDoc contains additional payload data that is not part of content")); - QByteArray tag = read(16); -#ifndef NDEBUG - qDebug() << "tag" << tag.toHex(); -#endif - dec.setTag(tag); - if(!dec.result()) - files.clear(); - return !files.empty(); -} - -bool CDoc2::save(const QString &path) -{ - setLastError({}); - QByteArray fmk = Crypto::extract(Crypto::random(KEY_LEN), SALT); - QByteArray cek = Crypto::expand(fmk, CEK); - QByteArray hhk = Crypto::expand(fmk, HMAC); -#ifndef NDEBUG - qDebug() << "fmk" << fmk.toHex(); - qDebug() << "cek" << cek.toHex(); - qDebug() << "hhk" << hhk.toHex(); -#endif - - flatbuffers::FlatBufferBuilder builder; - std::vector> recipients; - auto toVector = [&builder](const QByteArray &data) { - return builder.CreateVector((const uint8_t*)data.data(), size_t(data.length())); - }; - auto toString = [&builder](const QString &data) { - QByteArray utf8 = data.toUtf8(); - return builder.CreateString(utf8.data(), size_t(utf8.length())); - }; - auto sendToServer = [this](CKey &key, const QByteArray &recipient_id, const QByteArray &key_material, QLatin1String type) { - key.keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; - if(key.keyserver_id.isEmpty()) - return setLastError(QStringLiteral("keyserver_id cannot be empty")); - QNetworkRequest req = cdoc20::req(key.keyserver_id); - if(req.url().isEmpty()) - return setLastError(QStringLiteral("No valid config found for keyserver_id: %1").arg(key.keyserver_id)); - if(!cdoc20::checkConnection()) - return false; - QScopedPointer nam(CheckConnection::setupNAM(req, Settings::CDOC2_POST_CERT)); - req.setRawHeader("x-expiry-time", QDateTime::currentDateTimeUtc().addMonths(6).toString(Qt::ISODate).toLatin1()); - QEventLoop e; - QNetworkReply *reply = nam->post(req, QJsonDocument({ - {QLatin1String("recipient_id"), QLatin1String(recipient_id.toBase64())}, - {QLatin1String("ephemeral_key_material"), QLatin1String(key_material.toBase64())}, - {QLatin1String("capsule_type"), type}, - }).toJson()); - connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); - e.exec(); - if(reply->error() == QNetworkReply::NoError && - reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 201) - key.transaction_id = QString::fromLatin1(reply->rawHeader("Location")).remove(QLatin1String("/key-capsules/")); - else - return setLastError(reply->errorString()); - if(key.transaction_id.isEmpty()) - return setLastError(QStringLiteral("Failed to post key capsule")); - return true; - }; - - for(CKey &key: keys) - { - if(key.isRSA) - { - QByteArray kek = Crypto::random(fmk.size()); - QByteArray xor_key = Crypto::xor_data(fmk, kek); - auto publicKey = Crypto::fromRSAPublicKeyDer(key.key); - if(!publicKey) - return false; - QByteArray encrytpedKek = Crypto::encrypt(publicKey.get(), RSA_PKCS1_OAEP_PADDING, kek); -#ifndef NDEBUG - qDebug() << "publicKeyDer" << key.key.toHex(); - qDebug() << "kek" << kek.toHex(); - qDebug() << "xor" << xor_key.toHex(); - qDebug() << "encrytpedKek" << encrytpedKek.toHex(); -#endif - if(!Settings::CDOC2_USE_KEYSERVER) - { - auto rsaPublicKey = cdoc20::Recipients::CreateRSAPublicKeyCapsule(builder, - toVector(key.key), toVector(encrytpedKek)); - recipients.push_back(cdoc20::Header::CreateRecipientRecord(builder, - cdoc20::Recipients::Capsule::RSAPublicKeyCapsule, rsaPublicKey.Union(), - toString(key.toKeyLabel()), toVector(xor_key), cdoc20::Header::FMKEncryptionMethod::XOR)); - continue; - } - - if(!sendToServer(key, key.key, encrytpedKek, QLatin1String("rsa"))) - return false; - auto rsaKeyServer = cdoc20::Recipients::CreateServerRsaDetails(builder, toVector(key.key)); - auto keyServer = cdoc20::Recipients::CreateKeyServerCapsule(builder, - cdoc20::Recipients::ServerDetailsUnion::ServerRsaDetails, - rsaKeyServer.Union(), toString(key.keyserver_id), toString(key.transaction_id)); - recipients.push_back(cdoc20::Header::CreateRecipientRecord(builder, - cdoc20::Recipients::Capsule::KeyServerCapsule, keyServer.Union(), - toString(key.toKeyLabel()), toVector(xor_key), cdoc20::Header::FMKEncryptionMethod::XOR)); - continue; - } - - auto publicKey = Crypto::fromECPublicKeyDer(key.key, NID_secp384r1); - if(!publicKey) - return false; - auto ephKey = Crypto::genECKey(publicKey.get()); - QByteArray sharedSecret = Crypto::derive(ephKey.get(), publicKey.get()); - QByteArray ephPublicKeyDer = Crypto::toPublicKeyDer(ephKey.get()); - QByteArray kekPm = Crypto::extract(sharedSecret, KEKPREMASTER); - QByteArray info = KEK + cdoc20::Header::EnumNameFMKEncryptionMethod(cdoc20::Header::FMKEncryptionMethod::XOR) + key.key + ephPublicKeyDer; - QByteArray kek = Crypto::expand(kekPm, info, fmk.size()); - QByteArray xor_key = Crypto::xor_data(fmk, kek); -#ifndef NDEBUG - qDebug() << "publicKeyDer" << key.key.toHex(); - qDebug() << "ephPublicKeyDer" << ephPublicKeyDer.toHex(); - qDebug() << "sharedSecret" << sharedSecret.toHex(); - qDebug() << "kekPm" << kekPm.toHex(); - qDebug() << "kek" << kek.toHex(); - qDebug() << "xor" << xor_key.toHex(); -#endif - if(!Settings::CDOC2_USE_KEYSERVER) - { - auto eccPublicKey = cdoc20::Recipients::CreateECCPublicKeyCapsule(builder, - cdoc20::Recipients::EllipticCurve::secp384r1, toVector(key.key), toVector(ephPublicKeyDer)); - recipients.push_back(cdoc20::Header::CreateRecipientRecord(builder, - cdoc20::Recipients::Capsule::ECCPublicKeyCapsule, eccPublicKey.Union(), - toString(key.toKeyLabel()), toVector(xor_key), cdoc20::Header::FMKEncryptionMethod::XOR)); - continue; - } - - if(!sendToServer(key, key.key, ephPublicKeyDer, QLatin1String("ecc_secp384r1"))) - return false; - auto eccKeyServer = cdoc20::Recipients::CreateServerEccDetails(builder, - cdoc20::Recipients::EllipticCurve::secp384r1, toVector(key.key)); - auto keyServer = cdoc20::Recipients::CreateKeyServerCapsule(builder, - cdoc20::Recipients::ServerDetailsUnion::ServerEccDetails, - eccKeyServer.Union(), toString(key.keyserver_id), toString(key.transaction_id)); - recipients.push_back(cdoc20::Header::CreateRecipientRecord(builder, - cdoc20::Recipients::Capsule::KeyServerCapsule, keyServer.Union(), - toString(key.toKeyLabel()), toVector(xor_key), cdoc20::Header::FMKEncryptionMethod::XOR)); - } - - auto offset = cdoc20::Header::CreateHeader(builder, builder.CreateVector(recipients), - cdoc20::Header::PayloadEncryptionMethod::CHACHA20POLY1305); - builder.Finish(offset); - - QByteArray header = QByteArray::fromRawData((const char*)builder.GetBufferPointer(), int(builder.GetSize())); - QByteArray headerHMAC = Crypto::sign_hmac(hhk, header); - QByteArray nonce = Crypto::random(NONCE_LEN); -#ifndef NDEBUG - qDebug() << "hmac" << headerHMAC.toHex(); - qDebug() << "nonce" << nonce.toHex(); -#endif - Crypto::Cipher enc(EVP_chacha20_poly1305(), cek, nonce, true); - enc.updateAAD(PAYLOAD + header + headerHMAC); - auto header_len = qToBigEndian(uint32_t(header.size())); - remove(); - QFile file(path); - if(!file.open(QFile::WriteOnly)) - return setLastError(file.errorString()); - file.write(LABEL); - file.write((const char*)&header_len, int(sizeof(header_len))); - file.write(header); - file.write(headerHMAC); - file.write(nonce); - if(!cdoc20::TAR(std::unique_ptr(new cdoc20::stream(&file, &enc))).save(files)) - { - file.remove(); - return false; - } - if(!enc.result()) - { - file.remove(); - return false; - } - QByteArray tag = enc.tag(); -#ifndef NDEBUG - qDebug() << "tag" << tag.toHex(); -#endif - file.write(tag); - return true; -} - -QByteArray CDoc2::transportKey(const CKey &_key) -{ - setLastError({}); - CKey key = _key; - if(!key.transaction_id.isEmpty()) - { - QNetworkRequest req = cdoc20::req(key.keyserver_id, key.transaction_id); - if(req.url().isEmpty()) - { - setLastError(QStringLiteral("No valid config found for keyserver_id: %1").arg(key.keyserver_id)); - return {}; - } - if(!cdoc20::checkConnection()) - return {}; - auto authKey = dispatchToMain(&QSigner::key, qApp->signer()); - QScopedPointer 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) - { - setLastError(reply->errorString()); - return {}; - } - QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); - QByteArray key_material = QByteArray::fromBase64( - json.value(QLatin1String("ephemeral_key_material")).toString().toLatin1()); - if(json.value(QLatin1String("capsule_type")) == QLatin1String("rsa")) - key.encrypted_kek = std::move(key_material); - else - key.publicKey = std::move(key_material); - } -#ifndef NDEBUG - qDebug() << "publicKeyDer" << key.key.toHex(); - qDebug() << "ephPublicKeyDer" << key.publicKey.toHex(); -#endif - QByteArray kek = qApp->signer()->decrypt([&key](QCryptoBackend *backend) { - if(key.isRSA) - return backend->decrypt(key.encrypted_kek, true); - QByteArray kekPm = backend->deriveHMACExtract(key.publicKey, KEKPREMASTER, KEY_LEN); -#ifndef NDEBUG - qDebug() << "kekPm" << kekPm.toHex(); -#endif - QByteArray info = KEK + cdoc20::Header::EnumNameFMKEncryptionMethod(cdoc20::Header::FMKEncryptionMethod::XOR) + key.key + key.publicKey; - return Crypto::expand(kekPm, info, KEY_LEN); - }); - if(kek.isEmpty()) - { - setLastError(QStringLiteral("Failed to derive key")); - return {}; - } - QByteArray fmk = Crypto::xor_data(key.cipher, kek); - QByteArray hhk = Crypto::expand(fmk, HMAC); -#ifndef NDEBUG - qDebug() << "kek" << kek.toHex(); - qDebug() << "xor" << key.cipher.toHex(); - qDebug() << "fmk" << fmk.toHex(); - qDebug() << "hhk" << hhk.toHex(); - qDebug() << "hmac" << headerHMAC.toHex(); -#endif - if(Crypto::sign_hmac(hhk, header_data) == headerHMAC) - return fmk; - setLastError(QStringLiteral("CDoc 2.0 hash mismatch")); - return {}; -} - -int CDoc2::version() -{ - return 2; -} diff --git a/client/CDoc2.h b/client/CDoc2.h deleted file mode 100644 index 54bcf58d6..000000000 --- a/client/CDoc2.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * QDigiDocClient - * - * 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 "CryptoDoc.h" - -#include - -class CDoc2 final: public CDoc, private QFile { -public: - explicit CDoc2() = default; - explicit CDoc2(const QString &path); - - CKey canDecrypt(const QSslCertificate &cert) const final; - bool decryptPayload(const QByteArray &fmk) final; - QByteArray deriveFMK(const QByteArray &priv, const CKey &key); - bool isSupported(); - bool save(const QString &path) final; - QByteArray transportKey(const CKey &key) final; - int version() final; - -private: - QByteArray header_data, headerHMAC; - qint64 noncePos = -1; - - static const QByteArray LABEL, CEK, HMAC, KEK, KEKPREMASTER, PAYLOAD, SALT; - static constexpr int KEY_LEN = 32, NONCE_LEN = 12; -}; diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp new file mode 100644 index 000000000..391edf07c --- /dev/null +++ b/client/CDocSupport.cpp @@ -0,0 +1,394 @@ +#define __CDOCSUPPORT_CPP__ + +/* + * QDigiDocCrypto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "CheckConnection.h" +#include "QCryptoBackend.h" +#include "QSigner.h" +#include "Settings.h" +#include "TokenData.h" +#include "Utils.h" +#include "effects/FadeInNotification.h" +#include + +#include "CDocSupport.h" + +std::vector +CDocSupport::getCDocFileList(QString filename) +{ + std::vector files; + int version = libcdoc::CDocReader::getCDocFileVersion(filename.toStdString()); + if (version != 1) return files; + QFile ifs(filename); + ifs.open(QIODevice::ReadOnly); + QXmlStreamReader xml(&ifs); + while (xml.readNextStartElement()) { + if (xml.name() == QStringLiteral("EncryptedData")) { + while (xml.readNextStartElement()) { + if (xml.name() == QStringLiteral("EncryptionProperties")) { + while (xml.readNextStartElement()) { + if (xml.name() == QStringLiteral("EncryptionProperty")) { + if (xml.attributes().value(QStringLiteral("Name")) == QStringLiteral("orig_file")) { + QString content = xml.readElementText(); + auto list = content.split("|"); + files.push_back({list.at(0).toStdString(), list.at(1).toInt()}); + } else { + xml.skipCurrentElement(); + } + } else { + xml.skipCurrentElement(); + } + } + } else { + xml.skipCurrentElement(); + } + } + } else { + xml.skipCurrentElement(); + } + } + ifs.close(); + + return files; +} + +libcdoc::result_t +DDCryptoBackend::decryptRSA(std::vector& result, const std::vector &data, bool oaep, unsigned int idx) +{ + QByteArray qdata(reinterpret_cast(data.data()), data.size()); + QByteArray qkek = qApp->signer()->decrypt([&qdata, &oaep](QCryptoBackend *backend) { + return backend->decrypt(qdata, oaep); + }); + result.assign(qkek.cbegin(), qkek.cend()); + return (result.empty()) ? OPENSSL_ERROR : libcdoc::OK; +} + +const QString SHA256_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha256"); +const QString SHA384_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha384"); +const QString SHA512_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha512"); +const QHash SHA_MTH{ + {SHA256_MTH, QCryptographicHash::Sha256}, {SHA384_MTH, QCryptographicHash::Sha384}, {SHA512_MTH, QCryptographicHash::Sha512} +}; + +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) +{ + QByteArray decryptedKey = qApp->signer()->decrypt([&publicKey, &digest, &algorithmID, &partyUInfo, &partyVInfo](QCryptoBackend *backend) { + QByteArray ba(reinterpret_cast(publicKey.data()), publicKey.size()); + return backend->deriveConcatKDF(ba, SHA_MTH[QString::fromStdString(digest)], + QByteArray(reinterpret_cast(algorithmID.data()), algorithmID.size()), + QByteArray(reinterpret_cast(partyUInfo.data()), partyUInfo.size()), + QByteArray(reinterpret_cast(partyVInfo.data()), partyVInfo.size())); + }); + dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); + return (dst.empty()) ? OPENSSL_ERROR : libcdoc::OK; +} + +libcdoc::result_t +DDCryptoBackend::deriveHMACExtract(std::vector& dst, const std::vector &key_material, const std::vector &salt, unsigned int idx) +{ + QByteArray qkey_material(reinterpret_cast(key_material.data()), key_material.size()); + QByteArray qsalt(reinterpret_cast(salt.data()), salt.size()); + QByteArray qkekpm = qApp->signer()->decrypt([&qkey_material, &qsalt](QCryptoBackend *backend) { + return backend->deriveHMACExtract(qkey_material, qsalt, ECC_KEY_LEN); + }); + dst = std::vector(qkekpm.cbegin(), qkekpm.cend()); + return (dst.empty()) ? OPENSSL_ERROR : libcdoc::OK; +} + +libcdoc::result_t +DDCryptoBackend::getSecret(std::vector& _secret, unsigned int idx) +{ + _secret = secret; + return libcdoc::OK; +} + +bool +checkConnection() +{ + if(CheckConnection().check()) { + return true; + } + return dispatchToMain([] { + FadeInNotification::error(Application::mainWindow()->findChild(QStringLiteral("topBar")), + QCoreApplication::translate("MainWindow", "Check internet connection")); + return false; + }); +} + +std::string +DDConfiguration::getValue(std::string_view domain, std::string_view param) const +{ + std::string def = Settings::CDOC2_DEFAULT_KEYSERVER; + if (domain == def) { + if (param == libcdoc::Configuration::KEYSERVER_SEND_URL) { +#ifdef CONFIG_URL + QJsonObject list = Application::confValue(QLatin1String("CDOC2-CONF")).toObject(); + QJsonObject data = list.value(QString::fromUtf8(domain.data(), domain.size())).toObject(); + QString url = data.value(QLatin1String("POST")).toString(Settings::CDOC2_POST); + return url.toStdString(); +#else + QString url = Settings::CDOC2_POST; + return url.toStdString(); +#endif + } else if (param == libcdoc::Configuration::KEYSERVER_FETCH_URL) { +#ifdef CONFIG_URL + QJsonObject list = Application::confValue(QLatin1String("CDOC2-CONF")).toObject(); + QJsonObject data = list.value(QString::fromUtf8(domain.data(), domain.size())).toObject(); + QString url = data.value(QLatin1String("FETCH")).toString(Settings::CDOC2_GET); + return url.toStdString(); +#else + QString url = Settings::CDOC2_GET; + return url.toStdString(); +#endif + } + } + return {}; +} + +std::string +DDNetworkBackend::getLastErrorStr(libcdoc::result_t code) const +{ + if (code == BACKEND_ERROR) return last_error; + return libcdoc::NetworkBackend::getLastErrorStr(code); +} + +libcdoc::result_t DDNetworkBackend::sendKey( + libcdoc::NetworkBackend::CapsuleInfo &dst, const std::string &url, + const std::vector &rcpt_key, + const std::vector &key_material, const std::string &type) { + QNetworkRequest req(QString::fromStdString(url + "/key-capsules")); + req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); + if (!checkConnection()) { + last_error = "No connection"; + return BACKEND_ERROR; + } + QScopedPointer nam(CheckConnection::setupNAM(req, Settings::CDOC2_POST_CERT)); + QEventLoop e; + QNetworkReply *reply = nam->post(req, + QJsonDocument({ + {QLatin1String("recipient_id"), + QLatin1String( + QByteArray(reinterpret_cast(rcpt_key.data()), + rcpt_key.size()) + .toBase64())}, + {QLatin1String("ephemeral_key_material"), + QLatin1String(QByteArray(reinterpret_cast( + key_material.data()), + key_material.size()) + .toBase64())}, + {QLatin1String("capsule_type"), QLatin1String(type.c_str())}, + }).toJson()); + connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); + e.exec(); + QString tr_id; + if (reply->error() == QNetworkReply::NoError && + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == + 201) { + tr_id = QString::fromLatin1(reply->rawHeader("Location")) + .remove(QLatin1String("/key-capsules/")); + } else { + last_error = reply->errorString().toStdString(); + return BACKEND_ERROR; + } + if (tr_id.isEmpty()) { + last_error = "Failed to post key capsule"; + return BACKEND_ERROR; + } + dst.transaction_id = tr_id.toStdString(); + QDateTime dt = QDateTime::currentDateTimeUtc(); + dt = dt.addMonths(6); + dst.expiry_time = dt.toSecsSinceEpoch(); + return libcdoc::OK; +}; + +libcdoc::result_t +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), QString::fromStdString(transaction_id))); + req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); + if(!checkConnection()) { + last_error = "No connection"; + return BACKEND_ERROR; + } + auto authKey = dispatchToMain(&QSigner::key, qApp->signer()); + QScopedPointer 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(); + return BACKEND_ERROR; + } + 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()); + return libcdoc::OK; +} + +Q_DECLARE_LOGGING_CATEGORY(LOG_CDOC) +Q_LOGGING_CATEGORY(LOG_CDOC, "libcdoc") + +void DDCDocLogger::LogMessage(libcdoc::ILogger::LogLevel level, std::string_view file, int line, std::string_view message) { + switch (level) { + case libcdoc::ILogger::LogLevel::LEVEL_FATAL: + qCFatal(LOG_CDOC) << message; + break; + case libcdoc::ILogger::LogLevel::LEVEL_ERROR: + qCCritical(LOG_CDOC) << message; + break; + case libcdoc::ILogger::LogLevel::LEVEL_WARNING: + qCWarning(LOG_CDOC) << message; + break; + case libcdoc::ILogger::LogLevel::LEVEL_INFO: + qCInfo(LOG_CDOC) << message; + break; + case libcdoc::ILogger::LogLevel::LEVEL_DEBUG: + qCDebug(LOG_CDOC) << message; + break; + default: + // Trace, if present goes to debug categrory + qCDebug(LOG_CDOC) << message; + break; + } +} + +void DDCDocLogger::setUpLogger() { + static DDCDocLogger *logger = nullptr; + if (logger) { + logger = new DDCDocLogger(); + logger->SetMinLogLevel(libcdoc::ILogger::LogLevel::LEVEL_TRACE); + libcdoc::ILogger::addLogger(logger); + } +} + +TempListConsumer::~TempListConsumer() +{ + if (!files.empty()) { + IOEntry& file = files.back(); + file.data->close(); + } +} + +libcdoc::result_t TempListConsumer::write(const uint8_t *src, size_t size) { + if (files.empty()) + return libcdoc::OUTPUT_ERROR; + IOEntry &file = files.back(); + if (!file.data->isWritable()) + return libcdoc::OUTPUT_ERROR; + if (file.data->write((const char *)src, size) != size) + return libcdoc::OUTPUT_STREAM_ERROR; + file.size += size; + return size; +} + +libcdoc::result_t TempListConsumer::close() { + if (files.empty()) + return libcdoc::OUTPUT_ERROR; + IOEntry &file = files.back(); + if (!file.data->isWritable()) + return libcdoc::OUTPUT_ERROR; + return libcdoc::OK; +} + +bool +TempListConsumer::isError() +{ + if (files.empty()) return false; + IOEntry& file = files.back(); + return !file.data->isWritable(); +} + +libcdoc::result_t +TempListConsumer::open(const std::string& name, int64_t size) +{ + IOEntry io({name, "application/octet-stream", 0, {}}); + if ((size < 0) || (size > MAX_VEC_SIZE)) { + io.data = std::make_unique(); + } else { + io.data = std::make_unique(); + } + io.data->open(QIODevice::ReadWrite); + files.push_back(std::move(io)); + return libcdoc::OK; +} + +StreamListSource::StreamListSource(const std::vector& files) : _files(files), _current(-1) +{ +} + +libcdoc::result_t +StreamListSource::read(uint8_t *dst, size_t size) +{ + if ((_current < 0) || (_current >= _files.size())) return 0; + return _files[_current].data->read((char *) dst, size); +} + +bool +StreamListSource::isError() +{ + if ((_current < 0) || (_current >= _files.size())) return 0; + return _files[_current].data->isReadable(); +} + +bool +StreamListSource::isEof() +{ + if (_current < 0) return false; + if (_current >= _files.size()) return true; + return _files[_current].data->atEnd(); +} + +libcdoc::result_t +StreamListSource::getNumComponents() +{ + return _files.size(); +} + +libcdoc::result_t +StreamListSource::next(std::string& name, int64_t& size) +{ + ++_current; + if (_current >= _files.size()) return libcdoc::END_OF_STREAM; + _files[_current].data->seek(0); + name = _files[_current].name; + size = _files[_current].size; + return libcdoc::OK; +} diff --git a/client/CDocSupport.h b/client/CDocSupport.h new file mode 100644 index 000000000..5b719f61c --- /dev/null +++ b/client/CDocSupport.h @@ -0,0 +1,173 @@ +#ifndef __CDOCSUPPORT_H__ +#define __CDOCSUPPORT_H__ + +/* + * QDigiDocCrypto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include +#include +#include +#include +#include + +// +// libcdoc handler implementations +// + +// +// Configuration +// +// Provides KEYSERVER_SEND_URL and KEYSERVER_FETCH_URL +// + +struct DDConfiguration : public libcdoc::Configuration { + std::string getValue(std::string_view domain, + std::string_view param) const final; + + explicit DDConfiguration() = default; +}; + +// +// CryptoBackend +// +// Bridges to qApp->signer() +// + +struct DDCryptoBackend : public libcdoc::CryptoBackend { + libcdoc::result_t decryptRSA(std::vector &result, + const std::vector &data, bool oaep, + unsigned int idx) override final; + libcdoc::result_t 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) override final; + libcdoc::result_t deriveHMACExtract(std::vector &dst, + const std::vector &publicKey, + const std::vector &salt, + unsigned int idx) override final; + libcdoc::result_t getSecret(std::vector &secret, + unsigned int idx) override final; + + std::vector secret; + + explicit DDCryptoBackend() = default; +}; + +// +// NetworkBackend +// +// Bridges to QNetworkAccessManager +// + +struct DDNetworkBackend : public libcdoc::NetworkBackend, private QObject { + static constexpr int BACKEND_ERROR = -303; + + std::string getLastErrorStr(libcdoc::result_t code) const final; + libcdoc::result_t sendKey(libcdoc::NetworkBackend::CapsuleInfo &dst, + const std::string &url, + const std::vector &rcpt_key, + const std::vector &key_material, + const std::string &type) override final; + libcdoc::result_t + fetchKey(std::vector &result, const std::string &keyserver_id, + const std::string &transaction_id) override final; + + libcdoc::result_t + getClientTLSCertificate(std::vector &dst) override final { + return libcdoc::NOT_IMPLEMENTED; + } + libcdoc::result_t getPeerTLSCertificates( + std::vector> &dst) override final { + return libcdoc::NOT_IMPLEMENTED; + } + + explicit DDNetworkBackend() = default; + + std::string last_error; +}; + +// +// ILogger +// +// Bridges to Qt logging system +// + +class DDCDocLogger : private libcdoc::ILogger { + public: + static void setUpLogger(); + + private: + DDCDocLogger() = default; + ~DDCDocLogger() = default; + void LogMessage(libcdoc::ILogger::LogLevel level, std::string_view file, int line, + std::string_view message) override final; +}; + +class CDocSupport { +public: + static std::vector getCDocFileList(QString filename); +}; + +// +// DataSource and consumer that can share temporary files/buffwers +// + +struct IOEntry +{ + std::string name, mime; + int64_t size; + std::unique_ptr data; +}; + +struct TempListConsumer : public libcdoc::MultiDataConsumer { + static constexpr int64_t MAX_VEC_SIZE = 500L * 1024L * 1024L; + + explicit TempListConsumer(size_t max_memory_size = 500L * 1024L * 1024L) + : _max_memory_size(max_memory_size) {} + ~TempListConsumer(); + + libcdoc::result_t write(const uint8_t *src, size_t size) override final; + libcdoc::result_t close() override final; + bool isError() override final; + libcdoc::result_t open(const std::string &name, + int64_t size) override final; + + size_t _max_memory_size; + std::vector files; +}; + +struct StreamListSource : public libcdoc::MultiDataSource { + StreamListSource(const std::vector &files); + + int64_t read(uint8_t *dst, size_t size) override final; + bool isError() override final; + bool isEof() override final; + libcdoc::result_t getNumComponents() override final; + libcdoc::result_t next(std::string &name, int64_t &size) override final; + + const std::vector &_files; + int64_t _current; +}; + +#endif // __CDOCSUPPORT_H__ diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 5f138107b..3118451f1 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,3 +1,5 @@ +set(CMAKE_CXX_STANDARD 20) + get_target_property(qtCore_install_prefix Qt6::qmake IMPORTED_LOCATION) get_filename_component(qtCore_install_prefix ${qtCore_install_prefix} DIRECTORY) get_filename_component(TSL_FILENAME ${TSL_URL} NAME_WLE) @@ -20,6 +22,9 @@ else() ) endif() +set(BUILD_SHARED_LIBS NO) +add_subdirectory(libcdoc EXCLUDE_FROM_ALL) + file(GLOB WIDGETS CONFIGURE_DEPENDS "dialogs/*.cpp" "dialogs/*.h" "dialogs/*.ui" "effects/*.cpp" "effects/*.h" "effects/*.ui" @@ -38,10 +43,6 @@ add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE main.cpp Application.cpp Application.h - CDoc1.cpp - CDoc1.h - CDoc2.cpp - CDoc2.h CheckConnection.cpp CheckConnection.h Crypto.cpp @@ -89,6 +90,8 @@ add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE TokenData.cpp TokenData.h Utils.h + dialogs/PasswordDialog.h dialogs/PasswordDialog.cpp dialogs/PasswordDialog.ui + CDocSupport.cpp CDocSupport.h ) qt_add_translations(${PROJECT_NAME} TS_FILES translations/en.ts @@ -109,9 +112,7 @@ target_link_libraries(${PROJECT_NAME} Qt6::SvgWidgets digidocpp::digidocpp ${LDAP_LIBRARIES} - $ - $ - ZLIB::ZLIB + cdoc ) if(NOT BUILD_DATE) @@ -129,6 +130,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_ICON_FILE Icon.icns MACOSX_BUNDLE_GUI_IDENTIFIER "ee.ria.${PROJECT_NAME}" ) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}) + target_compile_definitions(${PROJECT_NAME} PRIVATE CDOC2_GET_URL="${CDOC2_GET_URL}" CDOC2_POST_URL="${CDOC2_POST_URL}" @@ -139,25 +142,6 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE VERSION_STR="${VERSION}" ) -foreach(SCHEMA ${SCHEMAS}) - get_filename_component(stem ${SCHEMA} NAME_WE) - get_filename_component(name ${SCHEMA} NAME) - set(GENERATED_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/${stem}_generated.h) - add_custom_command( - OUTPUT ${GENERATED_INCLUDE} - COMMENT "Compiling flatbuffer for ${name}" - COMMAND flatbuffers::flatc - --cpp - --scoped-enums - -o ${CMAKE_CURRENT_BINARY_DIR} - -I ${CMAKE_CURRENT_SOURCE_DIR} - ${SCHEMA} - DEPENDS flatbuffers::flatc ${SCHEMA} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) - target_sources(${PROJECT_NAME} PRIVATE ${SCHEMA} ${GENERATED_INCLUDE}) -endforeach() - if( APPLE ) file(GLOB_RECURSE RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/mac/Resources/*.icns diff --git a/client/CheckConnection.cpp b/client/CheckConnection.cpp index 689521e9a..45f26105c 100644 --- a/client/CheckConnection.cpp +++ b/client/CheckConnection.cpp @@ -86,7 +86,7 @@ QNetworkAccessManager* CheckConnection::setupNAM(QNetworkRequest &req, const QBy qDebug() << "SSL Error:" << error.error() << error.certificate().subjectInfo(QSslCertificate::CommonName); } } - reply->ignoreSslErrors(ignore); + reply->ignoreSslErrors(errors); }); return nam; } diff --git a/client/Crypto.cpp b/client/Crypto.cpp index e41983f1e..17a9dfe40 100644 --- a/client/Crypto.cpp +++ b/client/Crypto.cpp @@ -91,9 +91,17 @@ bool Crypto::Cipher::setTag(const QByteArray &data) const return !isError(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, int(data.size()), const_cast(data.data()))); } -QByteArray Crypto::aes_wrap(const QByteArray &key, const QByteArray &data, bool encrypt) +QByteArray Crypto::aes_wrap(const QByteArray &key, const QByteArray &data) { - Cipher c(key.size() == 32 ? EVP_aes_256_wrap() : EVP_aes_128_wrap(), key, {}, encrypt); + Cipher c(key.size() == 32 ? EVP_aes_256_wrap() : EVP_aes_128_wrap(), key, {}, true); + if(QByteArray result = c.update(data); c.result()) + return result; + return {}; +} + +QByteArray Crypto::aes_unwrap(const QByteArray &key, const QByteArray &data) +{ + Cipher c(key.size() == 32 ? EVP_aes_256_wrap() : EVP_aes_128_wrap(), key, {}, false); if(QByteArray result = c.update(data); c.result()) return result; return {}; @@ -366,3 +374,14 @@ QByteArray Crypto::xor_data(const QByteArray &a, const QByteArray &b) result[i] = char(a[i] ^ b[i]); return result; } + +QByteArray +Crypto::pbkdf2_sha256(const QByteArray& pw, const QByteArray& salt, uint32_t iter) +{ + QByteArray key(32, 0); + PKCS5_PBKDF2_HMAC(pw.data(), pw.length(), + (const unsigned char *) salt.data(), int(salt.length()), + iter, EVP_sha256(), int(key.size()), (unsigned char *)key.data()); + return key; +} + diff --git a/client/Crypto.h b/client/Crypto.h index 7ed6d00ca..5ea04f800 100644 --- a/client/Crypto.h +++ b/client/Crypto.h @@ -50,7 +50,8 @@ class Crypto bool setTag(const QByteArray &data) const; }; - static QByteArray aes_wrap(const QByteArray &key, const QByteArray &data, bool encrypt); + static QByteArray aes_wrap(const QByteArray &key, const QByteArray &data); + static QByteArray aes_unwrap(const QByteArray &key, const QByteArray &data); static QByteArray cipher(const EVP_CIPHER *cipher, const QByteArray &key, QByteArray &data, bool encrypt); static QByteArray curve_oid(EVP_PKEY *key); static QByteArray concatKDF(QCryptographicHash::Algorithm digestMethod, const QByteArray &z, const QByteArray &otherInfo); @@ -70,6 +71,7 @@ class Crypto static QByteArray random(int len = 32); static QByteArray xor_data(const QByteArray &a, const QByteArray &b); + static QByteArray pbkdf2_sha256(const QByteArray& pw, const QByteArray& salt, uint32_t iter); private: static bool isError(int err); }; diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 3e59d9ab4..4bd6e27c6 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -20,9 +20,8 @@ #include "CryptoDoc.h" #include "Application.h" -#include "CDoc1.h" -#include "CDoc2.h" #include "Crypto.h" +#include "CDocSupport.h" #include "TokenData.h" #include "QCryptoBackend.h" #include "QSigner.h" @@ -43,13 +42,45 @@ #include #include +#include +#include +#include +#include + using namespace ria::qdigidoc4; +auto toHex = [](const std::vector& data) -> QString { + QByteArray ba(reinterpret_cast(data.data()), data.size()); + return ba.toHex(); +}; + +std::string +CryptoDoc::labelFromCertificate(const std::vector& cert) +{ + QSslCertificate kcert(QByteArray(reinterpret_cast(cert.data()), cert.size()), QSsl::Der); + return [](const SslCertificate &c) { + QString cn = c.subjectInfo(QSslCertificate::CommonName); + QString gn = c.subjectInfo("GN"); + QString sn = c.subjectInfo("SN"); + if(!gn.isEmpty() || !sn.isEmpty()) + cn = QStringLiteral("%1 %2 %3").arg(gn, sn, c.personalCode()); + + int certType = c.type(); + if(certType & SslCertificate::EResidentSubType) + return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("Digi-ID E-RESIDENT")).toStdString(); + if(certType & SslCertificate::DigiIDType) + return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("Digi-ID")).toStdString(); + if(certType & SslCertificate::EstEidType) + return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("ID-CARD")).toStdString(); + return cn.toStdString(); + }(kcert); +} + class CryptoDoc::Private final: public QThread { Q_OBJECT public: - bool isEncryptedWarning() const; + bool warnIfNotWritable() const; void run() final; inline void waitForFinished() { @@ -59,78 +90,168 @@ class CryptoDoc::Private final: public QThread e.exec(); } - std::unique_ptr cdoc; + std::unique_ptr reader; + std::string writer_last_error; + QString fileName; - QByteArray key; - bool isEncrypted = false; + bool isEncrypted() const { return reader != nullptr; } CDocumentModel *documents = new CDocumentModel(this); QStringList tempFiles; + // Decryption data + QByteArray fmk; + // Encryption data + QString label; + uint32_t kdf_iter; + + // libcdoc handlers + DDConfiguration conf; + DDCryptoBackend crypto; + DDNetworkBackend network; + + std::vector files; + std::vector keys; + + const std::vector &getFiles() { + return files; + } + std::unique_ptr createCDocReader(const std::string& filename) { + libcdoc::CDocReader *r = libcdoc::CDocReader::createReader(filename, &conf, &crypto, &network); + if (!r) { + WarningDialog::show(tr("Failed to open document"), tr("Unsupported file format")); + return nullptr; + } + keys.clear(); + for (auto& key : r->getLocks()) { + keys.push_back({key, QSslCertificate()}); + } + return std::unique_ptr(r); + } +private: + bool encrypt(); }; -bool CryptoDoc::Private::isEncryptedWarning() const +bool CryptoDoc::Private::warnIfNotWritable() const { - if( fileName.isEmpty() ) + if(fileName.isEmpty()) { WarningDialog::show(CryptoDoc::tr("Container is not open")); - if(isEncrypted) + } else if(reader) { WarningDialog::show(CryptoDoc::tr("Container is encrypted")); - return fileName.isEmpty() || isEncrypted; + } else { + return false; + } + return true; } void CryptoDoc::Private::run() { - if(isEncrypted) - { + if(reader) { qCDebug(CRYPTO) << "Decrypt" << fileName; - isEncrypted = !cdoc->decryptPayload(key); + std::vector pfmk(fmk.cbegin(), fmk.cend()); + + TempListConsumer cons; + if (reader->decrypt(pfmk, &cons) == libcdoc::OK) { + files = std::move(cons.files); + // Success, immediately create writer from reader + keys.clear(); + writer_last_error.clear(); + reader.reset(); + } + } else { + if (encrypt()) { + // Encryption successful, open new reader + reader = createCDocReader(fileName.toStdString()); + if (!reader) return; + } } - else - { - qCDebug(CRYPTO) << "Encrypt" << fileName; - isEncrypted = cdoc->save(fileName); +} + +bool CryptoDoc::Private::encrypt() { + qCDebug(CRYPTO) << "Encrypt" << fileName; + + libcdoc::OStreamConsumer ofs(fileName.toStdString()); + if (ofs.isError()) + return false; + + StreamListSource slsrc(files); + std::vector enc_keys; + + std::string keyserver_id; + if (Settings::CDOC2_DEFAULT && Settings::CDOC2_USE_KEYSERVER) { + keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; } + for (auto &key : keys) { + QByteArray ba = key.rcpt_cert.toDer(); + std::vector cert_der = + std::vector(ba.cbegin(), ba.cend()); + QSslKey qkey = key.rcpt_cert.publicKey(); + ba = Crypto::toPublicKeyDer(qkey); + std::vector key_der(ba.cbegin(), ba.cend()); + libcdoc::Recipient::PKType pk_type = + (qkey.algorithm() == QSsl::KeyAlgorithm::Rsa) + ? libcdoc::Recipient::PKType::RSA + : libcdoc::Recipient::PKType::ECC; + if (!keyserver_id.empty()) { + std::string label = CryptoDoc::labelFromCertificate(cert_der); + enc_keys.push_back(libcdoc::Recipient::makeServer( + label, key_der, pk_type, keyserver_id)); + } else { + std::string label = CryptoDoc::labelFromCertificate(cert_der); + enc_keys.push_back( + libcdoc::Recipient::makeCertificate(label, cert_der)); + } + } + if (!crypto.secret.empty()) { + auto key = + libcdoc::Recipient::makeSymmetric(label.toStdString(), kdf_iter); + enc_keys.push_back(key); + } + + libcdoc::CDocWriter *writer = libcdoc::CDocWriter::createWriter( + Settings::CDOC2_DEFAULT ? 2 : 1, &ofs, false, &conf, &crypto, &network); + int result = writer->encrypt(slsrc, enc_keys); + if (result != libcdoc::OK) { + writer_last_error = writer->getLastErrorStr(); + std::filesystem::remove(std::filesystem::path(fileName.toStdString())); + } + delete writer; + ofs.close(); + return (result == libcdoc::OK); } -CDocumentModel::CDocumentModel(CryptoDoc::Private *doc) -: d( doc ) -{} +CDocumentModel::CDocumentModel(CryptoDoc::Private *doc) : d(doc) {} bool CDocumentModel::addFile(const QString &file, const QString &mime) { - if( d->isEncryptedWarning() ) - return false; + if(d->warnIfNotWritable()) return false; QFileInfo info(file); - if(info.size() == 0) - { + if(info.size() == 0) { WarningDialog::show(DocumentModel::tr("Cannot add empty file to the container.")); return false; } - if(d->cdoc->version() == 1 && info.size() > 120*1024*1024) - { + if(!Settings::CDOC2_DEFAULT && info.size() > 120*1024*1024) { WarningDialog::show(tr("Added file(s) exceeds the maximum size limit of the container (∼120MB). " "Read more about it")); return false; } - - QString fileName(info.fileName()); - if(std::any_of(d->cdoc->files.cbegin(), d->cdoc->files.cend(), + std::string fileName(info.fileName().toStdString()); + if(std::any_of(d->files.cbegin(), d->files.cend(), [&fileName](const auto &containerFile) { return containerFile.name == fileName; })) { WarningDialog::show(DocumentModel::tr("Cannot add the file to the envelope. File '%1' is already in container.") - .arg(FileDialog::normalized(fileName))); + .arg(FileDialog::normalized(info.fileName()))); return false; } auto data = std::make_unique(file); data->open(QFile::ReadOnly); - d->cdoc->files.push_back({ - QFileInfo(file).fileName(), - QStringLiteral("D%1").arg(d->cdoc->files.size()), - mime, + d->files.push_back({ + QFileInfo(file).fileName().toStdString(), + mime.toStdString(), data->size(), std::move(data), }); - emit added(FileDialog::normalized(d->cdoc->files.back().name)); + emit added(FileDialog::normalized(info.fileName())); return true; } @@ -141,7 +262,7 @@ void CDocumentModel::addTempReference(const QString &file) QString CDocumentModel::copy(int row, const QString &dst) const { - const CDoc::File &file = d->cdoc->files.at(row); + auto &file = d->files.at(row); if( QFile::exists( dst ) ) QFile::remove( dst ); file.data->seek(0); @@ -153,22 +274,22 @@ QString CDocumentModel::copy(int row, const QString &dst) const QString CDocumentModel::data(int row) const { - return FileDialog::normalized(d->cdoc->files.at(row).name); + return FileDialog::normalized(QString::fromStdString(d->getFiles().at(row).name)); } quint64 CDocumentModel::fileSize(int row) const { - return d->cdoc->files.at(row).size; + return d->getFiles().at(row).size; } QString CDocumentModel::mime(int row) const { - return FileDialog::normalized(d->cdoc->files.at(row).mime); + return FileDialog::normalized(QString::fromStdString(d->getFiles().at(row).mime)); } void CDocumentModel::open(int row) { - if(d->isEncrypted) + if(d->isEncrypted()) return; QString path = FileDialog::tempPath(FileDialog::safeName(data(row))); if(!verifyFile(path)) @@ -185,28 +306,27 @@ void CDocumentModel::open(int row) bool CDocumentModel::removeRow(int row) { - if(d->isEncryptedWarning()) + if(d->warnIfNotWritable()) return false; - if(d->cdoc->files.empty() || row >= d->cdoc->files.size()) - { + if(row >= d->files.size()) { WarningDialog::show(DocumentModel::tr("Internal error")); return false; } - d->cdoc->files.erase(d->cdoc->files.cbegin() + row); + d->files.erase(d->files.begin() + row); emit removed(row); return true; } int CDocumentModel::rowCount() const { - return int(d->cdoc->files.size()); + return int(d->getFiles().size()); } QString CDocumentModel::save(int row, const QString &path) const { - if(d->isEncrypted) + if(d->isEncrypted()) return {}; int zone = FileDialog::fileZone(d->fileName); @@ -218,95 +338,6 @@ QString CDocumentModel::save(int row, const QString &path) const return fileName; } -CKey::CKey(Tag) - : unsupported(true) -{} - -CKey::CKey(const QSslCertificate &c) -{ - setCert(c); - recipient = [](const SslCertificate &c) { - QString cn = c.subjectInfo(QSslCertificate::CommonName); - QString gn = c.subjectInfo("GN"); - QString sn = c.subjectInfo("SN"); - if(!gn.isEmpty() || !sn.isEmpty()) - cn = QStringLiteral("%1 %2 %3").arg(gn, sn, c.personalCode()); - - int certType = c.type(); - if(certType & SslCertificate::EResidentSubType) - return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("Digi-ID E-RESIDENT")); - if(certType & SslCertificate::DigiIDType) - return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("Digi-ID")); - if(certType & SslCertificate::EstEidType) - return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("ID-CARD")); - return cn; - }(c); -} - -void CKey::setCert(const QSslCertificate &c) -{ - QSslKey k = c.publicKey(); - cert = c; - key = Crypto::toPublicKeyDer(k); - isRSA = k.algorithm() == QSsl::Rsa; -} - -QHash CKey::fromKeyLabel() const -{ - QHash result; - if(!recipient.startsWith(QLatin1String("data:"), Qt::CaseInsensitive)) - return result; - QString payload = recipient.mid(5); - QString mimeType; - QString encoding; - if(auto pos = payload.indexOf(','); pos != -1) - { - mimeType = payload.left(pos); - payload = payload.mid(pos + 1); - if(auto header = mimeType.split(';'); header.size() == 2) - { - mimeType = header.value(0); - encoding = header.value(1); - } - } - if(!mimeType.isEmpty() && mimeType != QLatin1String("application/x-www-form-urlencoded")) - return result; - if(encoding == QLatin1String("base64")) - payload = QByteArray::fromBase64(payload.toLatin1()); - ; - for(const auto &[key,value]: QUrlQuery(payload).queryItems(QUrl::FullyDecoded)) - result[key.toLower()] = value; - if(!result.contains(QStringLiteral("type")) || !result.contains(QStringLiteral("v"))) - result.clear(); - return result; -} - -QString CKey::toKeyLabel() const -{ - if(cert.isNull()) - return recipient; - QDateTime exp = cert.expiryDate(); - if(Settings::CDOC2_USE_KEYSERVER) - exp = std::min(exp, QDateTime::currentDateTimeUtc().addMonths(6)); - auto escape = [](QString data) { return data.replace(',', QLatin1String("%2C")); }; - QString type = QStringLiteral("ID-card"); - if(auto t = SslCertificate(cert).type(); t & SslCertificate::EResidentSubType) - type = QStringLiteral("Digi-ID E-RESIDENT"); - else if(t & SslCertificate::DigiIDType) - type = QStringLiteral("Digi-ID"); - QUrlQuery q; - q.setQueryItems({ - {QStringLiteral("v"), QString::number(1)}, - {QStringLiteral("type"), type}, - {QStringLiteral("serial_number"), escape(cert.subjectInfo("serialNumber").join(','))}, - {QStringLiteral("cn"), escape(cert.subjectInfo("CN").join(','))}, - {QStringLiteral("server_exp"), QString::number(exp.toSecsSinceEpoch())}, - }); - return "data:" + q.query(QUrl::FullyEncoded); -} - - - CryptoDoc::CryptoDoc( QObject *parent ) : QObject(parent) , d(new Private) @@ -317,22 +348,34 @@ CryptoDoc::CryptoDoc( QObject *parent ) CryptoDoc::~CryptoDoc() { clear(); delete d; } -bool CryptoDoc::addKey( const CKey &key ) +bool +CryptoDoc::supportsSymmetricKeys() const { - if(d->isEncryptedWarning()) - return false; - if(d->cdoc->keys.contains(key)) - { - WarningDialog::show(tr("Key already exists")); + return !d->reader && Settings::CDOC2_DEFAULT; +} + +bool CryptoDoc::addEncryptionKey(const QSslCertificate &cert) { + if (d->warnIfNotWritable()) { return false; } - d->cdoc->keys.append(key); + for (auto &k : d->keys) { + if (k.rcpt_cert == cert) { + WarningDialog::show( + tr("Recipient with the same key already exists")); + return false; + } + } + d->keys.push_back({{}, cert}); return true; } -bool CryptoDoc::canDecrypt(const QSslCertificate &cert) -{ - return !d->cdoc->canDecrypt(cert).key.isEmpty(); +bool CryptoDoc::canDecrypt(const QSslCertificate &cert) { + if (!d->reader) + return false; + QByteArray der = cert.toDer(); + libcdoc::Lock lock; + return d->reader->getLockForCert( + std::vector(der.cbegin(), der.cend())) >= 0; } void CryptoDoc::clear( const QString &file ) @@ -344,41 +387,63 @@ void CryptoDoc::clear( const QString &file ) QFile::remove(f); } d->tempFiles.clear(); - d->isEncrypted = false; + d->reader.reset(); d->fileName = file; - if(Settings::CDOC2_DEFAULT) - d->cdoc = std::make_unique(); - else - d->cdoc = std::make_unique(); + d->writer_last_error.clear(); + d->files.clear(); } ContainerState CryptoDoc::state() const { - return d->isEncrypted ? EncryptedContainer : UnencryptedContainer; + return d->isEncrypted() ? EncryptedContainer : UnencryptedContainer; } -bool CryptoDoc::decrypt() -{ - if( d->fileName.isEmpty() ) - { +bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray &secret) { + if (d->fileName.isEmpty()) { WarningDialog::show(tr("Container is not open")); return false; } - if(!d->isEncrypted) + if (!d->reader) return true; - CKey key = d->cdoc->canDecrypt(qApp->signer()->tokenauth().cert()); - if(key.key.isEmpty()) - { - WarningDialog::show(tr("You do not have the key to decrypt this document")); + int lock_idx = -1; + const std::vector locks = d->reader->getLocks(); + if (lock == nullptr) { + QByteArray der = qApp->signer()->tokenauth().cert().toDer(); + lock_idx = d->reader->getLockForCert( + std::vector(der.cbegin(), der.cend())); + if (lock_idx < 0) { + WarningDialog::show( + tr("You do not have the key to decrypt this document")); + return false; + } + lock = &locks.at(lock_idx); + } else { + for (lock_idx = 0; lock_idx < locks.size(); lock_idx++) { + if (lock->label == locks[lock_idx].label) { + lock = &locks.at(lock_idx); + break; + } + } + if (lock_idx >= locks.size()) + lock_idx = -1; + } + if (!lock || (lock->isSymmetric() && secret.isEmpty())) { + WarningDialog::show( + tr("You do not have the key to decrypt this document")); return false; } - if(d->cdoc->version() == 2 && !key.transaction_id.isEmpty() && !Settings::CDOC2_NOTIFICATION.isSet()) - { - auto *dlg = new WarningDialog(tr("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."), Application::mainWindow()); + if (d->reader->version == 2 && + (lock->type == libcdoc::Lock::Type::SERVER) && + !Settings::CDOC2_NOTIFICATION.isSet()) { + auto *dlg = new WarningDialog( + tr("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."), + Application::mainWindow()); dlg->setCancelText(WarningDialog::Cancel); dlg->addButton(WarningDialog::OK, QMessageBox::Ok); dlg->addButton(tr("Don't show again"), QMessageBox::Ignore); @@ -388,66 +453,82 @@ bool CryptoDoc::decrypt() case QMessageBox::Ignore: Settings::CDOC2_NOTIFICATION = true; break; - default: return false; + default: + return false; } } - d->key = d->cdoc->transportKey(key); -#ifndef NDEBUG - qDebug() << "Transport key" << d->key.toHex(); -#endif - if(d->key.isEmpty()) - { - WarningDialog::show(tr("Failed to decrypt document. Please check your internet connection and network settings."), d->cdoc->lastError); + d->crypto.secret.assign(secret.cbegin(), secret.cend()); + std::vector fmk; + if (d->reader->getFMK(fmk, lock_idx) != libcdoc::OK) + return false; + d->fmk = QByteArray(reinterpret_cast(fmk.data()), fmk.size()); + if (d->fmk.isEmpty()) { + const std::string &msg = d->reader->getLastErrorStr(); + WarningDialog::show(tr("Failed to decrypt document. Please check your " + "internet connection and network settings."), + QString::fromStdString(msg)); return false; } d->waitForFinished(); - if(d->isEncrypted) - WarningDialog::show(tr("Error parsing document")); - if(!d->cdoc->lastError.isEmpty()) - WarningDialog::show(d->cdoc->lastError); - - return !d->isEncrypted; + if (d->reader) { + const std::string &msg = d->reader->getLastErrorStr(); + if (msg.empty()) { + WarningDialog::show(tr("Error parsing document")); + } else { + WarningDialog::show(QString::fromStdString(msg)); + } + } + return !d->isEncrypted(); } -DocumentModel* CryptoDoc::documentModel() const { return d->documents; } +DocumentModel *CryptoDoc::documentModel() const { return d->documents; } -bool CryptoDoc::encrypt( const QString &filename ) +bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QByteArray& secret, uint32_t kdf_iter) { - if( !filename.isEmpty() ) - d->fileName = filename; - if( d->fileName.isEmpty() ) - { + if(!filename.isEmpty()) d->fileName = filename; + if(d->fileName.isEmpty()) { WarningDialog::show(tr("Container is not open")); return false; } - if(d->isEncrypted) - return true; - if(d->cdoc->keys.isEmpty()) - { - WarningDialog::show(tr("No keys specified")); - return false; + // I think the correct semantics is to fail if container is already encrypted + if(d->reader) return false; + if (secret.isEmpty()) { + // Encrypt for address list + if(d->keys.empty()) + { + WarningDialog::show(tr("No keys specified")); + return false; + } + } else { + // Encrypt with symmetric key + d->label = label; + d->crypto.secret.assign(secret.cbegin(), secret.cend()); + d->kdf_iter = kdf_iter; } - d->waitForFinished(); - if(d->isEncrypted) + d->label.clear(); + d->crypto.secret.clear(); + if(d->isEncrypted()) { open(d->fileName); - else - WarningDialog::show(tr("Failed to encrypt document. Please check your internet connection and network settings."), d->cdoc->lastError); - return d->isEncrypted; + } else { + WarningDialog::show(tr("Failed to encrypt document. Please check your internet connection and network settings."), QString::fromStdString(d->writer_last_error)); + } + return d->isEncrypted(); } QString CryptoDoc::fileName() const { return d->fileName; } -QList CryptoDoc::keys() const +const std::vector& +CryptoDoc::keys() const { - return d->cdoc->keys; + return d->keys; } bool CryptoDoc::move(const QString &to) { - if(!d->isEncrypted) + if(!d->isEncrypted()) { d->fileName = to; return true; @@ -456,26 +537,32 @@ bool CryptoDoc::move(const QString &to) return false; } -bool CryptoDoc::open( const QString &file ) +bool CryptoDoc::open(const QString &file) { + d->writer_last_error.clear(); clear(file); - d->cdoc = std::make_unique(d->fileName); - if(d->cdoc->keys.isEmpty()) - d->cdoc = std::make_unique(d->fileName); - d->isEncrypted = bool(d->cdoc); - if(!d->isEncrypted || d->cdoc->keys.isEmpty()) - { - WarningDialog::show(tr("Failed to open document"), d->cdoc->lastError); + d->reader = d->createCDocReader(file.toStdString()); + if (!d->reader) { return false; } + std::vector files = CDocSupport::getCDocFileList(file); + for (auto& f : files) { + d->files.push_back({f.name, {}, f.size, {}}); + } Application::addRecent( file ); return true; } -void CryptoDoc::removeKey( int id ) +void CryptoDoc::removeKey(unsigned int id) +{ + if(!d->warnIfNotWritable()) + d->keys.erase(d->keys.begin() + id); +} + +void CryptoDoc::clearKeys() { - if(!d->isEncryptedWarning()) - d->cdoc->keys.removeAt(id); + if(!d->warnIfNotWritable()) + d->keys.clear(); } bool CryptoDoc::saveCopy(const QString &filename) diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index cdf1683cf..87ed8d97a 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -25,62 +25,23 @@ #include #include -#include +#include +#include +#include +#include class QSslKey; -class CKey -{ -public: - enum Tag - { - Unsupported, - }; - CKey() = default; - CKey(Tag); - CKey(QByteArray _key, bool _isRSA): key(std::move(_key)), isRSA(_isRSA) {} - CKey(const QSslCertificate &cert); - bool operator==(const CKey &other) const { return other.key == key; } - - void setCert(const QSslCertificate &c); - QHash fromKeyLabel() const; - QString toKeyLabel() const; - - QByteArray key, cipher, publicKey; - QSslCertificate cert; - bool isRSA = false, unsupported = false; - QString recipient; - // CDoc1 - QString concatDigest; - QByteArray AlgorithmID, PartyUInfo, PartyVInfo; - // CDoc2 - QByteArray encrypted_kek; - QString keyserver_id, transaction_id; -}; - +// +// A wrapper structure for UI that contains either: +// - lock information for decryption +// - recipient certificate for encryption +// - -class CDoc -{ -public: - struct File - { - QString name, id, mime; - qint64 size; - std::unique_ptr data; - }; - - virtual ~CDoc() = default; - virtual CKey canDecrypt(const QSslCertificate &cert) const = 0; - virtual bool decryptPayload(const QByteArray &key) = 0; - virtual bool save(const QString &path) = 0; - bool setLastError(const QString &msg) { return (lastError = msg).isEmpty(); } - virtual QByteArray transportKey(const CKey &key) = 0; - virtual int version() = 0; - - QList keys; - std::vector files; - QString lastError; +struct CDKey { + libcdoc::Lock lock; + QSslCertificate rcpt_cert; + bool operator== (const CDKey& rhs) const = default; }; class CryptoDoc final: public QObject @@ -90,20 +51,23 @@ class CryptoDoc final: public QObject CryptoDoc(QObject *parent = nullptr); ~CryptoDoc() final; - bool addKey( const CKey &key ); + bool supportsSymmetricKeys() const; + bool addEncryptionKey(const QSslCertificate& cert); bool canDecrypt(const QSslCertificate &cert); void clear(const QString &file = {}); - bool decrypt(); + bool decrypt(const libcdoc::Lock *lock, const QByteArray& secret); + bool encrypt(const QString &filename = {}, const QString& label = {}, const QByteArray& secret = {}, uint32_t kdf_iter = 0); DocumentModel* documentModel() const; - bool encrypt(const QString &filename = {}); QString fileName() const; - QList keys() const; + const std::vector& keys() const; bool move(const QString &to); - bool open( const QString &file ); - void removeKey( int id ); + bool open(const QString &file); + void removeKey(unsigned int id); + void clearKeys(); bool saveCopy(const QString &filename); ria::qdigidoc4::ContainerState state() const; + static std::string labelFromCertificate(const std::vector& cert); private: class Private; Private *d; diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index 472c0cb38..1991d7fcc 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -35,6 +35,7 @@ #include "effects/Overlay.h" #include "dialogs/FileDialog.h" #include "dialogs/MobileProgress.h" +#include "dialogs/PasswordDialog.h" #include "dialogs/RoleAddressDialog.h" #include "dialogs/SettingsDialog.h" #include "dialogs/SmartIDProgress.h" @@ -149,6 +150,8 @@ MainWindow::MainWindow( QWidget *parent ) ui->crypto->warningIcon(true); }); + connect(ui->cryptoContainerPage, &ContainerPage::decryptReq, this, &MainWindow::decryptClicked); + connect(ui->accordion, &Accordion::changePin1Clicked, this, &MainWindow::changePin1Clicked); connect(ui->accordion, &Accordion::changePin2Clicked, this, &MainWindow::changePin2Clicked); connect(ui->accordion, &Accordion::changePukClicked, this, &MainWindow::changePukClicked); @@ -228,14 +231,38 @@ ContainerState MainWindow::currentState() return ContainerState::Uninitialized; } -bool MainWindow::decrypt() -{ - if(!cryptoDoc) +bool MainWindow::decrypt(const libcdoc::Lock *lock) { + if (!cryptoDoc) return false; + QByteArray secret; + if (lock && (lock->type == libcdoc::Lock::Type::SYMMETRIC_KEY || + lock->type == libcdoc::Lock::Type::PASSWORD)) { + PasswordDialog p; + p.setLabel(QString::fromStdString(lock->label)); + if (lock->type == libcdoc::Lock::Type::PASSWORD) { + p.setMode(PasswordDialog::Mode::DECRYPT, + PasswordDialog::Type::PASSWORD); + if (!p.exec()) + return false; + secret = p.secret(); + } else { + p.setMode(PasswordDialog::Mode::DECRYPT, PasswordDialog::Type::KEY); + if (!p.exec()) + return false; + secret = p.secret(); + } + } + WaitDialogHolder waitDialog(this, tr("Decrypting")); - return cryptoDoc->decrypt(); + if (cryptoDoc->decrypt(lock, secret)) { + ui->cryptoContainerPage->transition(cryptoDoc.get(), + qApp->signer()->tokenauth().cert()); + FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); + return true; + } + return false; } void MainWindow::dragEnterEvent(QDragEnterEvent *event) @@ -283,7 +310,7 @@ QStringList MainWindow::dropEventFiles(QDropEvent *event) return files; } -bool MainWindow::encrypt() +bool MainWindow::encrypt(bool askForKey) { if(!cryptoDoc) return false; @@ -294,14 +321,28 @@ bool MainWindow::encrypt() dlg->addButton(WarningDialog::YES, QMessageBox::Yes); if(dlg->exec() == QMessageBox::Yes) { moveCryptoContainer(); - return encrypt(); + return encrypt(askForKey); } return false; } - WaitDialogHolder waitDialog(this, tr("Encrypting")); - - return cryptoDoc->encrypt(); + if (askForKey) { + PasswordDialog p; + p.setMode(PasswordDialog::Mode::ENCRYPT, PasswordDialog::Type::PASSWORD); + if(!p.exec()) return false; + QString label = p.label(); + QByteArray secret = p.secret(); + if (p.type == PasswordDialog::Type::PASSWORD) { + WaitDialogHolder waitDialog(this, tr("Encrypting")); + return cryptoDoc->encrypt(cryptoDoc->fileName(), label, secret, 65536); + } else { + WaitDialogHolder waitDialog(this, tr("Encrypting")); + return cryptoDoc->encrypt(cryptoDoc->fileName(), label, secret, 0); + } + } else { + WaitDialogHolder waitDialog(this, tr("Encrypting")); + return cryptoDoc->encrypt(); + } } void MainWindow::mouseReleaseEvent(QMouseEvent *event) @@ -455,15 +496,17 @@ void MainWindow::convertToCDoc() auto cryptoContainer = std::make_unique(this); cryptoContainer->clear(filename); - // If signed, add whole signed document to cryptocontainer; otherwise content only - if(digiDoc->state() == SignedContainer) + // If signed, add whole signed document to cryptocontainer; otherwise + // content only + if (digiDoc->state() == SignedContainer) cryptoContainer->documentModel()->addFile(digiDoc->fileName()); else cryptoContainer->documentModel()->addTempFiles(digiDoc->documentModel()->tempFiles()); auto cardData = qApp->signer()->tokenauth(); - if(!cardData.cert().isNull()) - cryptoContainer->addKey(CKey(cardData.cert())); + if (!cardData.cert().isNull()) { + cryptoContainer->addEncryptionKey(cardData.cert()); + } resetCryptoDoc(std::move(cryptoContainer)); resetDigiDoc(nullptr, false); @@ -501,7 +544,7 @@ void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString break; case DecryptContainer: case DecryptToken: - if(decrypt()) + if(decrypt(nullptr)) { ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->signer()->tokenauth().cert()); FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); @@ -514,7 +557,13 @@ void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString FadeInNotification::success(ui->topBar, tr("Encryption succeeded!")); } break; - case ContainerSaveAs: + case EncryptLT: + if(encrypt(true)) { + ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->signer()->tokenauth().cert()); + FadeInNotification::success(ui->topBar, tr("Encryption succeeded!")); + } + break; + case ContainerSaveAs: { if(!cryptoDoc) break; @@ -1069,3 +1118,11 @@ void MainWindow::containerSummary() dialog->exec(); dialog->deleteLater(); } + +void +MainWindow::decryptClicked(const libcdoc::Lock *lock) +{ + qDebug() << "Decrypt clicked:"; + decrypt(lock); +} + diff --git a/client/MainWindow.h b/client/MainWindow.h index b63e9fe90..a3390ea97 100644 --- a/client/MainWindow.h +++ b/client/MainWindow.h @@ -23,12 +23,13 @@ #include "common_enums.h" #include "QSmartCard.h" +#include "cdoc/Lock.h" namespace Ui { class MainWindow; } -class CKey; +struct CDKey; class CryptoDoc; class DigiDoc; class DocumentModel; @@ -71,8 +72,8 @@ private Q_SLOTS: void convertToBDoc(); void convertToCDoc(); ria::qdigidoc4::ContainerState currentState(); - bool decrypt(); - bool encrypt(); + bool decrypt(const libcdoc::Lock *lock); + bool encrypt(bool askForKey = false); void loadPicture(); void moveCryptoContainer(); void moveSignatureContainer(); @@ -114,4 +115,6 @@ private Q_SLOTS: std::unique_ptr cryptoDoc; DigiDoc* digiDoc = nullptr; Ui::MainWindow *ui; + + void decryptClicked(const libcdoc::Lock *lock); }; diff --git a/client/Utils.h b/client/Utils.h index 4038e5f53..072d09e60 100644 --- a/client/Utils.h +++ b/client/Utils.h @@ -24,6 +24,9 @@ #include #include #include +#include + +#include "Application.h" namespace { template @@ -81,6 +84,32 @@ namespace { return escaped; } + inline qint64 copyIODevice(std::basic_istream *from, QIODevice *to, qint64 max = std::numeric_limits::max()) + { + std::array buf{}; + size_t total_read = 0; + while ((total_read < max) && !from->eof() && !from->bad()) { + size_t to_read = std::min(max, buf.size()); + from->read(buf.data(), to_read); + size_t n_read = from->gcount(); + if(to->write(buf.data(), n_read) != n_read) return -1; + total_read += n_read; + } + return total_read; + } + + inline qint64 copyIODevice(QIODevice *from, std::streambuf *to, qint64 max = std::numeric_limits::max()) + { + std::array buf{}; + qint64 size = 0, i = 0; + for(; (i = from->read(buf.data(), std::min(max, buf.size()))) > 0; size += i, max -= i) + { + if(to->sputn(buf.data(), i) != i) + return -1; + } + return i < 0 ? i : size; + } + inline qint64 copyIODevice(QIODevice *from, QIODevice *to, qint64 max = std::numeric_limits::max()) { std::array buf{}; diff --git a/client/common_enums.h b/client/common_enums.h index c9529e7cf..52aa2cfa3 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -56,6 +56,7 @@ enum Actions { SignatureToken, ClearSignatureWarning, ClearCryptoWarning, + EncryptLT }; enum ItemType { diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index c79311ffd..71341aaff 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -19,6 +19,7 @@ #include "AddRecipients.h" +#include "dialogs/PasswordDialog.h" #include "ui_AddRecipients.h" #include "Application.h" @@ -32,12 +33,14 @@ #include "TokenData.h" #include "dialogs/WarningDialog.h" #include "effects/Overlay.h" +#include "Crypto.h" #include #include #include #include #include +#include #include AddRecipients::AddRecipients(ItemList* itemList, QWidget *parent) @@ -96,7 +99,6 @@ AddRecipients::~AddRecipients() delete ui; } - void AddRecipients::addRecipientFromFile() { QString file = FileDialog::getOpenFileName(this, windowTitle(), {}, @@ -148,12 +150,23 @@ void AddRecipients::addRecipientFromHistory() void AddRecipients::addRecipient(const QSslCertificate& cert, bool select) { - AddressItem *leftItem = itemListValue(ui->leftPane, cert); + CDKey key = { {}, cert }; + AddressItem *leftItem = itemListValue(ui->leftPane, key); if(!leftItem) { - leftItem = new AddressItem(cert, AddressItem::Add, ui->leftPane); + QByteArray qder = cert.toDer(); + std::vector sder = std::vector(qder.cbegin(), qder.cend()); + + leftItem = new AddressItem(key, AddressItem::Add, ui->leftPane); ui->leftPane->addWidget(leftItem); - bool contains = rightList.contains(cert); + + bool contains = false; + for (auto rhs: rightList) { + if (rhs.rcpt_cert == cert) { + contains = true; + break; + } + } leftItem->setDisabled(contains); connect(leftItem, &AddressItem::add, this, [this](Item *item) { addRecipientToRightPane(item); }); if(auto *add = ui->leftPane->findChild(QStringLiteral("add"))) @@ -167,13 +180,16 @@ void AddRecipients::addRecipient(const QSslCertificate& cert, bool select) void AddRecipients::addRecipientToRightPane(Item *item, bool update) { auto *address = qobject_cast(item); - if(!address || rightList.contains(address->getKey())) - return; + const CDKey& key = address->getKey(); + if(!address) return; + for (auto &rhs : rightList) { + if (key.rcpt_cert == rhs.rcpt_cert) + return; + } - const auto &key = address->getKey(); if(update) { - if(auto expiryDate = key.cert.expiryDate(); expiryDate <= QDateTime::currentDateTime()) + if(auto expiryDate = key.rcpt_cert.expiryDate(); expiryDate <= QDateTime::currentDateTime()) { if(Settings::CDOC2_DEFAULT && Settings::CDOC2_USE_KEYSERVER) { @@ -190,9 +206,9 @@ void AddRecipients::addRecipientToRightPane(Item *item, bool update) } QSslConfiguration backup = QSslConfiguration::defaultConfiguration(); QSslConfiguration::setDefaultConfiguration(CheckConnection::sslConfiguration()); - QList errors = QSslCertificate::verify({ key.cert }); + QList errors = QSslCertificate::verify({key.rcpt_cert}); QSslConfiguration::setDefaultConfiguration(backup); - errors.removeAll(QSslError(QSslError::CertificateExpired, key.cert)); + errors.removeAll(QSslError(QSslError::CertificateExpired, key.rcpt_cert)); if(!errors.isEmpty()) { auto *dlg = new WarningDialog(tr("Recipient’s certification chain contains certificates that are not trusted. Continue with encryption?"), this); @@ -209,7 +225,7 @@ void AddRecipients::addRecipientToRightPane(Item *item, bool update) auto *rightItem = new AddressItem(key, AddressItem::Remove, ui->rightPane); connect(rightItem, &AddressItem::remove, this, [this](Item *item) { auto *rightItem = qobject_cast(item); - if(auto *leftItem = itemListValue(ui->leftPane, rightItem->getKey().cert)) + if(auto *leftItem = itemListValue(ui->leftPane, rightItem->getKey())) leftItem->setDisabled(false); rightList.removeAll(rightItem->getKey()); updated = true; @@ -217,7 +233,7 @@ void AddRecipients::addRecipientToRightPane(Item *item, bool update) }); ui->rightPane->addWidget(rightItem); ui->confirm->setEnabled(true); - historyCertData.addAndSave(key.cert); + historyCertData.addAndSave(key.rcpt_cert); if(auto *leftItem = itemListValue(ui->leftPane, key)) leftItem->setDisabled(true); } @@ -232,19 +248,19 @@ bool AddRecipients::isUpdated() const return updated; } -AddressItem* AddRecipients::itemListValue(ItemList *list, const CKey &cert) +AddressItem* AddRecipients::itemListValue(ItemList *list, const CDKey &key) { for(auto *item: list->items) { - if(auto *address = qobject_cast(item); address && address->getKey() == cert) + if(auto *address = qobject_cast(item); address && address->getKey() == key) return address; } return nullptr; } -QList AddRecipients::keys() +QList AddRecipients::keys() { - QList recipients; + QList recipients; for(auto *item: ui->rightPane->items) { if(auto *address = qobject_cast(item)) diff --git a/client/dialogs/AddRecipients.h b/client/dialogs/AddRecipients.h index 0c9fb0013..5f55e9067 100644 --- a/client/dialogs/AddRecipients.h +++ b/client/dialogs/AddRecipients.h @@ -41,7 +41,7 @@ class AddRecipients final : public QDialog explicit AddRecipients(ItemList* itemList, QWidget *parent = nullptr); ~AddRecipients() final; - QList keys(); + QList keys(); bool isUpdated() const; private: @@ -50,7 +50,7 @@ class AddRecipients final : public QDialog void addRecipient(const QSslCertificate& cert, bool select = true); void addRecipientToRightPane(Item *item, bool update = true); - AddressItem* itemListValue(ItemList *list, const CKey &cert); + AddressItem* itemListValue(ItemList *list, const CDKey &key); void search(const QString &term, bool select = false, const QString &type = {}); void showError(const QString &msg, const QString &details = {}); void showResult(const QList &result, int resultCount, const QVariantMap &userData); @@ -58,7 +58,7 @@ class AddRecipients final : public QDialog static QString defaultUrl(QLatin1String key, const QString &defaultValue); Ui::AddRecipients *ui; - QList rightList; + QList rightList; LdapSearch *ldap_person, *ldap_corp; bool updated = false; diff --git a/client/dialogs/KeyDialog.cpp b/client/dialogs/KeyDialog.cpp index 69f43f9ce..a6e5edb3b 100644 --- a/client/dialogs/KeyDialog.cpp +++ b/client/dialogs/KeyDialog.cpp @@ -24,7 +24,7 @@ #include "effects/Overlay.h" #include "dialogs/CertificateDetails.h" -KeyDialog::KeyDialog( const CKey &k, QWidget *parent ) +KeyDialog::KeyDialog(const CDKey &k, QWidget *parent ) : QDialog( parent ) { Ui::KeyDialog d; @@ -37,10 +37,21 @@ KeyDialog::KeyDialog( const CKey &k, QWidget *parent ) new Overlay(this); connect(d.close, &QPushButton::clicked, this, &KeyDialog::accept); - connect(d.showCert, &QPushButton::clicked, this, [this, cert=k.cert] { - CertificateDetails::showCertificate(cert, this); - }); - d.showCert->setHidden(k.cert.isNull()); + if (!k.rcpt_cert.isNull()) { + connect(d.showCert, &QPushButton::clicked, this, [this, cert = k.rcpt_cert] { + CertificateDetails::showCertificate(cert, this); + }); + d.showCert->setHidden(false); + } else if (k.lock.isCertificate()) { + std::vector cert = k.lock.getBytes(libcdoc::Lock::Params::CERT); + QSslCertificate kcert(QByteArray(reinterpret_cast(cert.data()), cert.size()), QSsl::Der); + connect(d.showCert, &QPushButton::clicked, this, [this, c = kcert] { + CertificateDetails::showCertificate(c, this); + }); + d.showCert->setHidden(kcert.isNull()); + } else { + d.showCert->setHidden(true); + } auto addItem = [view = d.view](const QString ¶meter, const QString &value) { if(value.isEmpty()) @@ -51,12 +62,22 @@ KeyDialog::KeyDialog( const CKey &k, QWidget *parent ) view->addTopLevelItem(i); }; - addItem(tr("Recipient"), k.recipient); - addItem(tr("ConcatKDF digest method"), k.concatDigest); - addItem(tr("Key server ID"), k.keyserver_id); - addItem(tr("Transaction ID"), k.transaction_id); - addItem(tr("Expiry date"), k.cert.expiryDate().toLocalTime().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"))); - addItem(tr("Issuer"), k.cert.issuerInfo(QSslCertificate::CommonName).join(' ')); + addItem(tr("Recipient"), QString::fromStdString(k.lock.label)); + if (k.lock.isCertificate()) { + std::vector cert = k.lock.getBytes(libcdoc::Lock::Params::CERT); + QSslCertificate kcert(QByteArray(reinterpret_cast(cert.data()), cert.size()), QSsl::Der); + if (k.lock.isCDoc1()) { + std::string cdigest = k.lock.getString(libcdoc::Lock::Params::CONCAT_DIGEST); + addItem(tr("ConcatKDF digest method"), QString::fromStdString(cdigest)); + } + addItem(tr("Expiry date"), kcert.expiryDate().toLocalTime().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"))); + auto iss = kcert.issuerInfo(QSslCertificate::CommonName); + addItem(tr("Issuer"), iss.join(" ")); + d.view->resizeColumnToContents(0); + } else if (k.lock.type == libcdoc::Lock::SERVER) { + addItem(tr("Key server ID"), QString::fromUtf8(k.lock.getString(libcdoc::Lock::Params::KEYSERVER_ID))); + addItem(tr("Transaction ID"), QString::fromUtf8(k.lock.getString(libcdoc::Lock::Params::TRANSACTION_ID))); + } d.view->resizeColumnToContents( 0 ); adjustSize(); } diff --git a/client/dialogs/KeyDialog.h b/client/dialogs/KeyDialog.h index f65afe7c7..44efdd376 100644 --- a/client/dialogs/KeyDialog.h +++ b/client/dialogs/KeyDialog.h @@ -21,11 +21,12 @@ #include -class CKey; +struct CDKey; + class KeyDialog final: public QDialog { Q_OBJECT public: - KeyDialog(const CKey &key, QWidget *parent = nullptr); + KeyDialog(const CDKey &key, QWidget *parent = nullptr); }; diff --git a/client/dialogs/PasswordDialog.cpp b/client/dialogs/PasswordDialog.cpp new file mode 100644 index 000000000..c34dc0074 --- /dev/null +++ b/client/dialogs/PasswordDialog.cpp @@ -0,0 +1,136 @@ +#include "PasswordDialog.h" +#include "Crypto.h" +#include "ui_PasswordDialog.h" + +PasswordDialog::PasswordDialog(QWidget *parent) + : QDialog(parent), mode(Mode::DECRYPT), type(Type::PASSWORD) + , ui(new Ui::PasswordDialog) +{ + ui->setupUi(this); + connect(ui->generateKey, &QPushButton::clicked, this, &PasswordDialog::genKeyClicked); + connect(ui->typeSelector, &QTabWidget::currentChanged, this, &PasswordDialog::typeChanged); + connect(ui->passwordLine, &QLineEdit::textChanged, this, &PasswordDialog::lineChanged); + connect(ui->password2Line, &QLineEdit::textChanged, this, &PasswordDialog::lineChanged); + connect(ui->keyEdit, &QPlainTextEdit::textChanged, this, &PasswordDialog::editChanged); +} + +PasswordDialog::~PasswordDialog() +{ + delete ui; +} + +void +PasswordDialog::setMode(Mode _mode, Type _type) +{ + mode = _mode; + type = _type; + updateUI(); +} + +void +PasswordDialog::setLabel(const QString& label) +{ + ui->labelLine->setText(label); +} + +QString +PasswordDialog::label() +{ + return ui->labelLine->text(); +} + +QByteArray +PasswordDialog::secret() const +{ + if (type == Type::PASSWORD) { + return ui->passwordLine->text().toUtf8(); + } else { + QString hex = ui->keyEdit->toPlainText(); + return QByteArray::fromHex(hex.toUtf8()); + } +} + +void +PasswordDialog::typeChanged(int index) +{ + Type new_type = static_cast(index); + if (new_type != type) setMode(mode, new_type); +} + +void +PasswordDialog::lineChanged(const QString& text) +{ + updateOK(); +} + +void +PasswordDialog::editChanged() +{ + updateOK(); +} + +void +PasswordDialog::genKeyClicked() +{ + QByteArray key = Crypto::random(); + ui->keyEdit->clear(); + ui->keyEdit->appendPlainText(key.toHex()); +} + +void +PasswordDialog::updateUI() +{ + ui->typeSelector->setCurrentIndex(type); + if (mode == Mode::DECRYPT) { + ui->labelLine->setReadOnly(true); + if (type == Type::PASSWORD) { + ui->typeSelector->setTabEnabled(Type::PASSWORD, true); + ui->typeSelector->setTabEnabled(Type::KEY, false); + ui->passwordLabel->setText("Enter password to decrypt the document"); + ui->passwordLine->setEchoMode(QLineEdit::EchoMode::Password); + ui->password2Label->hide(); + ui->password2Line->hide(); + } else { + ui->typeSelector->setTabEnabled(Type::PASSWORD, false); + ui->typeSelector->setTabEnabled(Type::KEY, true); + ui->keyLabel->setText("Enter key to decrypt the document"); + ui->generateKey->hide(); + } + } else { + ui->labelLine->setReadOnly(false); + if (type == Type::PASSWORD) { + ui->typeSelector->setTabEnabled(Type::PASSWORD, true); + ui->typeSelector->setTabEnabled(Type::KEY, true); + ui->passwordLabel->setText("Enter a password to encrypt the document"); + ui->passwordLine->setEchoMode(QLineEdit::EchoMode::Password); + ui->password2Label->show(); + ui->password2Line->show(); + } else { + ui->typeSelector->setTabEnabled(Type::PASSWORD, true); + ui->typeSelector->setTabEnabled(Type::KEY, true); + ui->keyLabel->setText("Enter a key to encrypt the document"); + ui->generateKey->show(); + } + } + updateOK(); +} + +void +PasswordDialog::updateOK() +{ + bool active = false; + if (mode == Mode::DECRYPT) { + if (type == Type::PASSWORD) { + active = !ui->passwordLine->text().isEmpty(); + } else { + active = !ui->keyEdit->toPlainText().isEmpty(); + } + } else { + if (type == Type::PASSWORD) { + active = !ui->passwordLine->text().isEmpty() && ui->passwordLine->text() == ui->password2Line->text(); + } else { + active = !ui->keyEdit->toPlainText().isEmpty(); + } + } + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(active); +} diff --git a/client/dialogs/PasswordDialog.h b/client/dialogs/PasswordDialog.h new file mode 100644 index 000000000..451144c25 --- /dev/null +++ b/client/dialogs/PasswordDialog.h @@ -0,0 +1,47 @@ +#ifndef PASSWORDDIALOG_H +#define PASSWORDDIALOG_H + +#include + +namespace Ui { +class PasswordDialog; +} + +class PasswordDialog : public QDialog +{ + Q_OBJECT + +public: + enum Mode { + ENCRYPT, + DECRYPT + }; + + enum Type { + PASSWORD, + KEY + }; + + Mode mode; + Type type; + + explicit PasswordDialog(QWidget *parent = nullptr); + ~PasswordDialog(); + + void setMode(Mode mode, Type type); + + void setLabel(const QString& label); + QString label(); + QByteArray secret() const; +private: + Ui::PasswordDialog *ui; + + void typeChanged(int index); + void lineChanged(const QString& text); + void editChanged(); + void genKeyClicked(); + void updateUI(); + void updateOK(); +}; + +#endif // PASSWORDDIALOG_H diff --git a/client/dialogs/PasswordDialog.ui b/client/dialogs/PasswordDialog.ui new file mode 100644 index 000000000..dd8d20494 --- /dev/null +++ b/client/dialogs/PasswordDialog.ui @@ -0,0 +1,175 @@ + + + PasswordDialog + + + + 0 + 0 + 400 + 322 + + + + Dialog + + + + + + Key label (recipient name or id) + + + Qt::AlignCenter + + + + + + + + + + 0 + + + + Password + + + + + + Password + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + QLineEdit::Password + + + + + + + Repeat password + + + + + + + QLineEdit::Password + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Symmetric key + + + + + + Symmetric key + + + + + + + + + + Generate Key + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + + buttonBox + accepted() + PasswordDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PasswordDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index 2bdeb0192..82c15aad6 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -17,6 +17,10 @@ * */ +#include +#include +#include + #include "AddressItem.h" #include "ui_AddressItem.h" @@ -30,16 +34,16 @@ class AddressItem::Private: public Ui::AddressItem { public: QString code; - CKey key; + CDKey key; QString label; bool yourself = false; }; -AddressItem::AddressItem(CKey k, Type type, QWidget *parent) +AddressItem::AddressItem(const CDKey &key, Type type, QWidget *parent) : Item(parent) , ui(new Private) { - ui->key = std::move(k); + ui->key = key; ui->setupUi(this); if(type == Icon) ui->icon->load(QStringLiteral(":/images/icon_Krypto_small.svg")); @@ -47,18 +51,47 @@ AddressItem::AddressItem(CKey k, Type type, QWidget *parent) ui->name->setAttribute(Qt::WA_TransparentForMouseEvents, true); ui->expire->setAttribute(Qt::WA_TransparentForMouseEvents, true); ui->idType->setAttribute(Qt::WA_TransparentForMouseEvents, true); - if(!ui->key.unsupported) + + bool unsupported = false; + if (!ui->key.rcpt_cert.isNull()) { + // Recipient certificate + ui->code = SslCertificate(ui->key.rcpt_cert).personalCode(); + ui->label = !ui->key.rcpt_cert.subjectInfo("GN").isEmpty() && + !ui->key.rcpt_cert.subjectInfo("SN").isEmpty() + ? ui->key.rcpt_cert.subjectInfo("GN").join(' ') + ' ' + + ui->key.rcpt_cert.subjectInfo("SN").join(' ') + : ui->key.rcpt_cert.subjectInfo("CN").join(' '); + ui->decrypt->hide(); + } else if (ui->key.lock.isValid()) { + // Known lock type + ui->code.clear(); + auto map = libcdoc::Recipient::parseLabel(ui->key.lock.label); + if (map.contains("cn")) { + ui->label = QString::fromStdString(map["cn"]); + } else { + ui->label = QString::fromStdString(ui->key.lock.label); + } + if (ui->key.lock.isSymmetric()) { + ui->decrypt->show(); + connect(ui->decrypt, &QToolButton::clicked, this, + [this] { emit decrypt(&ui->key.lock); }); + } else { + ui->decrypt->hide(); + } + } else { + // No rcpt, lock is invalid = unsupported lock + unsupported = true; + ui->code.clear(); + ui->label = tr("Unsupported cryptographic algorithm or recipient type"); + ui->decrypt->hide(); + } + + if(!unsupported) setCursor(Qt::PointingHandCursor); connect(ui->add, &QToolButton::clicked, this, [this]{ emit add(this);}); connect(ui->remove, &QToolButton::clicked, this, [this]{ emit remove(this);}); - ui->code = SslCertificate(ui->key.cert).personalCode(); - ui->label = !ui->key.cert.subjectInfo("GN").isEmpty() && !ui->key.cert.subjectInfo("SN").isEmpty() ? - ui->key.cert.subjectInfo("GN").join(' ') + ' ' + ui->key.cert.subjectInfo("SN").join(' ') : - ui->key.cert.subjectInfo("CN").join(' '); - if(ui->label.isEmpty()) - ui->label = ui->key.fromKeyLabel().value(QStringLiteral("cn"), ui->key.recipient); setIdType(); ui->add->setVisible(type == Add); ui->remove->setVisible(type != Add); @@ -82,15 +115,21 @@ void AddressItem::changeEvent(QEvent* event) QWidget::changeEvent(event); } -const CKey& AddressItem::getKey() const +const CDKey& AddressItem::getKey() const { return ui->key; } -void AddressItem::idChanged(const SslCertificate &cert) -{ - CKey key(cert); - ui->yourself = !key.key.isNull() && ui->key == key; +void AddressItem::idChanged(const SslCertificate &cert) { + QByteArray qder = cert.toDer(); + std::vector sder = std::vector(qder.cbegin(), qder.cend()); + + if (ui->key.lock.isValid()) { + QSslKey pkey = cert.publicKey(); + QByteArray der = pkey.toDer(); + ui->yourself = ui->key.lock.hasTheSameKey( + std::vector(der.cbegin(), der.cend())); + } setName(); } @@ -108,10 +147,9 @@ QWidget* AddressItem::lastTabWidget() return ui->add; } -void AddressItem::mouseReleaseEvent(QMouseEvent * /*event*/) -{ - if(!ui->key.unsupported) - (new KeyDialog(ui->key, this))->open(); +void AddressItem::mouseReleaseEvent(QMouseEvent * /*event*/) { + if (ui->key.rcpt_cert.isNull() && !ui->key.lock.isValid()) + (new KeyDialog(ui->key))->open(); } void AddressItem::setName() @@ -128,17 +166,9 @@ void AddressItem::stateChange(ContainerState state) ui->remove->setVisible(state == UnencryptedContainer); } -void AddressItem::setIdType() -{ - ui->expire->clear(); - SslCertificate cert(ui->key.cert); +void AddressItem::setIdType(const SslCertificate &cert) { SslCertificate::CertType type = cert.type(); - if(ui->key.unsupported) - { - ui->label = tr("Unsupported cryptographic algorithm or recipient type"); - ui->idType->clear(); - } - else if(type & SslCertificate::DigiIDType) + if(type & SslCertificate::DigiIDType) ui->idType->setText(tr("digi-ID")); else if(type & SslCertificate::EstEidType) ui->idType->setText(tr("ID-card")); @@ -153,30 +183,55 @@ void AddressItem::setIdType() else ui->idType->setText(tr("Certificate for Encryption")); } - else - { - auto items = ui->key.fromKeyLabel(); + ui->expire->setProperty("label", QStringLiteral("default")); + ui->expire->setText(QStringLiteral("%1 %2").arg( + cert.isValid() ? tr("Expires on") : tr("Expired on"), + cert.expiryDate().toLocalTime().toString( + QStringLiteral("dd.MM.yyyy")))); +} + +void AddressItem::setIdType() { + ui->expire->clear(); + + if (!ui->key.rcpt_cert.isNull()) { + // Recipient certificate + SslCertificate cert(ui->key.rcpt_cert); + setIdType(cert); + } else if (ui->key.lock.isValid()) { + // Known lock type + // Needed to include translation for "ID-CARD" void(QT_TR_NOOP("ID-CARD")); - ui->idType->setText(tr(items[QStringLiteral("type")].toUtf8().data())); - if(QString server_exp = items[QStringLiteral("server_exp")]; !server_exp.isEmpty()) - { - auto date = QDateTime::fromSecsSinceEpoch(server_exp.toLongLong(), Qt::UTC); - bool canDecrypt = QDateTime::currentDateTimeUtc() < date; - ui->expire->setProperty("label", canDecrypt ? QStringLiteral("good") : QStringLiteral("error")); - ui->expire->setText(canDecrypt ? QStringLiteral("%1 %2").arg( - tr("Decryption is possible until:"), date.toLocalTime().toString(QStringLiteral("dd.MM.yyyy"))) : - tr("Decryption has expired")); + auto items = libcdoc::Recipient::parseLabel(ui->key.lock.label); + if (ui->key.lock.isCertificate()) { + auto bytes = ui->key.lock.getBytes(libcdoc::Lock::CERT); + QByteArray qbytes((const char *)bytes.data(), bytes.size()); + SslCertificate cert(qbytes, QSsl::Der); + setIdType(cert); + } else { + ui->idType->setText(tr(items["type"].data())); } + if (ui->key.lock.type == libcdoc::Lock::SERVER) { + std::string server_exp = items["server_exp"]; + if (!server_exp.empty()) { + uint64_t seconds = std::stoull(server_exp); + auto date = QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC); + bool canDecrypt = QDateTime::currentDateTimeUtc() < date; + ui->expire->setProperty("label", canDecrypt + ? QStringLiteral("good") + : QStringLiteral("error")); + ui->expire->setText( + canDecrypt ? QStringLiteral("%1 %2").arg( + tr("Decryption is possible until:"), + date.toLocalTime().toString( + QStringLiteral("dd.MM.yyyy"))) + : tr("Decryption has expired")); + } + } + } else { + // No rcpt, lock is invalid = unsupported lock + ui->idType->setText("Unsupported"); + ui->expire->setHidden(true); } - - if(!cert.isNull()) - { - ui->expire->setProperty("label", QStringLiteral("default")); - ui->expire->setText(QStringLiteral("%1 %2").arg( - cert.isValid() ? tr("Expires on") : tr("Expired on"), - cert.expiryDate().toLocalTime().toString(QStringLiteral("dd.MM.yyyy")))); - } - ui->idType->setHidden(ui->idType->text().isEmpty()); ui->expire->setHidden(ui->expire->text().isEmpty()); } diff --git a/client/widgets/AddressItem.h b/client/widgets/AddressItem.h index 507c64497..fe0d949e8 100644 --- a/client/widgets/AddressItem.h +++ b/client/widgets/AddressItem.h @@ -19,9 +19,12 @@ #pragma once +#include + #include "widgets/Item.h" +#include "cdoc/Lock.h" -class CKey; +struct CDKey; class AddressItem final : public Item { @@ -35,20 +38,24 @@ class AddressItem final : public Item Icon, }; - explicit AddressItem(CKey k, Type type, QWidget *parent = {}); + explicit AddressItem(const CDKey &k, Type type, QWidget *parent = {}); ~AddressItem() final; - const CKey& getKey() const; + const CDKey& getKey() const; void idChanged(const SslCertificate &cert) final; void initTabOrder(QWidget *item) final; QWidget* lastTabWidget() final; void stateChange(ria::qdigidoc4::ContainerState state) final; +signals: + void decrypt(const libcdoc::Lock *lock); + private: void changeEvent(QEvent *event) final; void mouseReleaseEvent(QMouseEvent *event) final; void setName(); void setIdType(); + void setIdType(const SslCertificate& cert); class Private; Private *ui; diff --git a/client/widgets/AddressItem.ui b/client/widgets/AddressItem.ui index af93507cb..64c355036 100644 --- a/client/widgets/AddressItem.ui +++ b/client/widgets/AddressItem.ui @@ -188,6 +188,14 @@ color: #727679; ++ ++ + +- Add ++ DECRYPT + + + diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 1fdab787e..7fb2dc2cf 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -221,9 +221,10 @@ void ContainerPage::showMainAction(const QList &actions) bool isSignCard = actions.contains(SignatureAdd) || actions.contains(SignatureToken); bool isSignMobile = !isSignCard && (actions.contains(SignatureMobile) || actions.contains(SignatureSmartID)); bool isEncrypt = actions.contains(EncryptContainer) && !ui->rightPane->findChildren().isEmpty(); + bool isEncryptLT = actions.contains(EncryptLT); bool isDecrypt = !isBlocked && (actions.contains(DecryptContainer) || actions.contains(DecryptToken)); mainAction->setButtonEnabled(isSupported && !hasEmptyFile && - (isEncrypt || isDecrypt || isSignMobile || (isSignCard && !isBlocked && !isExpired))); + (isEncrypt || isEncryptLT || isDecrypt || isSignMobile || (isSignCard && !isBlocked && !isExpired))); ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); ui->navigationArea->layout()->invalidate(); } @@ -252,12 +253,11 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert AddRecipients dlg(ui->rightPane, this); if(!dlg.exec() || !dlg.isUpdated()) return; - for(auto i = container->keys().size() - 1; i >= 0; i--) - container->removeKey(i); + container->clearKeys(); ui->rightPane->clear(); for(const auto &key: dlg.keys()) { - container->addKey(key); + container->addEncryptionKey(key.rcpt_cert); ui->rightPane->addWidget(new AddressItem(key, AddressItem::Icon, ui->rightPane)); } showMainAction({ EncryptContainer }); @@ -279,17 +279,19 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert clear(); emit action(ClearCryptoWarning); - isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); + isSupported = container->state() & UnencryptedContainer || + container->canDecrypt(cert); setHeader(container->fileName()); bool hasUnsupported = false; - for(const CKey &key: container->keys()) - { - hasUnsupported = std::max(hasUnsupported, key.unsupported); - ui->rightPane->addWidget(new AddressItem(key, AddressItem::Icon, ui->rightPane)); + for (auto &key : container->keys()) { + hasUnsupported = hasUnsupported || (key.rcpt_cert.isNull() && !key.lock.isValid()); + AddressItem *addr = new AddressItem(key, AddressItem::Icon, ui->rightPane); + ui->rightPane->addWidget(addr); + connect(addr, &AddressItem::decrypt, this, [this, key] { emit decryptReq(&key.lock); }); } - if(hasUnsupported) + if (hasUnsupported) emit warning({UnsupportedCDocWarning}); - updatePanes(container->state()); + updatePanes(container->state(), container); ui->leftPane->setModel(container->documentModel()); } @@ -349,7 +351,7 @@ void ContainerPage::transition(DigiDoc* container) showSigningButton(); ui->leftPane->setModel(container->documentModel()); - updatePanes(container->state()); + updatePanes(container->state(), nullptr); } void ContainerPage::updateDecryptionButton() @@ -357,7 +359,7 @@ void ContainerPage::updateDecryptionButton() showMainAction({ isSeal ? DecryptToken : DecryptContainer }); } -void ContainerPage::updatePanes(ContainerState state) +void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container) { ui->leftPane->stateChange(state); ui->rightPane->stateChange(state); @@ -367,7 +369,7 @@ void ContainerPage::updatePanes(ContainerState state) for(QWidget *button: buttons) button->setVisible(visible); }; - switch( state ) + switch(state) { case UnsignedContainer: cancelText = QT_TR_NOOP("Cancel"); @@ -403,7 +405,11 @@ void ContainerPage::updatePanes(ContainerState state) ui->changeLocation->show(); ui->leftPane->init(fileName, QT_TRANSLATE_NOOP("ItemList", "Encrypted files")); ui->rightPane->init(ItemAddress, QT_TRANSLATE_NOOP("ItemList", "Recipients")); - showMainAction({ EncryptContainer }); + if (crypto_container && crypto_container->supportsSymmetricKeys()) { + showMainAction({ EncryptContainer, EncryptLT }); + } else { + showMainAction({ EncryptContainer }); + } setButtonsVisible({ ui->saveAs, ui->email }, false); break; case EncryptedContainer: @@ -434,3 +440,4 @@ void ContainerPage::translateLabels() ui->cancel->setText(tr(cancelText)); ui->convert->setText(tr(convertText)); } + diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index 47ba67afc..ca128be72 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -21,6 +21,7 @@ #include "common_enums.h" #include "widgets/MainAction.h" +#include "CryptoDoc.h" #include @@ -28,7 +29,6 @@ namespace Ui { class ContainerPage; } -class CKey; class CryptoDoc; class DigiDoc; class QSslCertificate; @@ -61,6 +61,8 @@ class ContainerPage final : public QWidget void removed(int row); void warning(const WarningText &warningText); + void decryptReq(const libcdoc::Lock *key); + private: void changeEvent(QEvent* event) final; bool checkAction(int code, const QString& selectedCard, const QString& selectedMobile); @@ -70,7 +72,7 @@ class ContainerPage final : public QWidget void showMainAction(const QList &actions); void showSigningButton(); void updateDecryptionButton(); - void updatePanes(ria::qdigidoc4::ContainerState state); + void updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container); void translateLabels(); Ui::ContainerPage *ui; diff --git a/client/widgets/MainAction.cpp b/client/widgets/MainAction.cpp index 0305411c9..65223aa15 100644 --- a/client/widgets/MainAction.cpp +++ b/client/widgets/MainAction.cpp @@ -135,6 +135,7 @@ QString MainAction::label(Actions action) case SignatureSmartID: return tr("Sign with\nSmart-ID"); case SignatureToken: return tr("Sign with\nE-Seal"); case EncryptContainer: return tr("Encrypt"); + case EncryptLT: return tr("Encrypt long-term"); case DecryptContainer: return tr("Decrypt with\nID-Card"); case DecryptToken: return tr("Decrypt"); default: return tr("Sign with\nID-Card"); diff --git a/schema/header.fbs b/schema/header.fbs deleted file mode 100644 index 6a95a6685..000000000 --- a/schema/header.fbs +++ /dev/null @@ -1,59 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023 Open Electronic Identity - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -include "recipients.fbs"; - -namespace cdoc20.Header; - -// FMK encryption method enum. -enum FMKEncryptionMethod:byte { - UNKNOWN, - XOR -} - -// Payload encryption method enum. -enum PayloadEncryptionMethod:byte { - UNKNOWN, - CHACHA20POLY1305 -} - -// Intermediate record, some languages act very poorly when it comes -// to an array of unions. -// Thus it is better to have an an array of tables that -// contains the union as a field. -table RecipientRecord { - capsule: cdoc20.Recipients.Capsule; - key_label: string (required); - encrypted_fmk: [ubyte] (required); - fmk_encryption_method: FMKEncryptionMethod = UNKNOWN; -} - -// Header structure. -table Header { - recipients: [RecipientRecord]; - payload_encryption_method: PayloadEncryptionMethod = UNKNOWN; -} - -root_type Header; diff --git a/schema/recipients.fbs b/schema/recipients.fbs deleted file mode 100644 index 8325864cd..000000000 --- a/schema/recipients.fbs +++ /dev/null @@ -1,82 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023 Open Electronic Identity - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -namespace cdoc20.Recipients; - -// Union for communicating the recipient type -union Capsule { - ECCPublicKeyCapsule, - RSAPublicKeyCapsule, - KeyServerCapsule, - SymmetricKeyCapsule -} - -//for future proofing and data type -union ServerDetailsUnion { - ServerEccDetails, - ServerRsaDetails -} - -// Elliptic curve type enum for ECCPublicKey recipient -enum EllipticCurve:byte { - UNKNOWN, - secp384r1 -} - -table ServerRsaDetails { - //RSA pub key in DER - recipient_public_key: [ubyte] (required); -} - -table ServerEccDetails { - // Elliptic curve type enum - curve: EllipticCurve = UNKNOWN; - //EC pub key in TLS format - //for secp384r1 curve: 0x04 + X 48 coord bytes + Y coord 48 bytes) - recipient_public_key: [ubyte] (required); -} - -// ECC public key recipient -table ECCPublicKeyCapsule { - curve: EllipticCurve = UNKNOWN; - recipient_public_key: [ubyte] (required); - sender_public_key: [ubyte] (required); -} - -table RSAPublicKeyCapsule { - recipient_public_key: [ubyte] (required); - encrypted_kek: [ubyte] (required); -} - -table KeyServerCapsule { - recipient_key_details: ServerDetailsUnion; - keyserver_id: string (required); - transaction_id: string (required); -} - -// symmetric long term crypto -table SymmetricKeyCapsule { - salt: [ubyte] (required); -} From 5ad7abf536cfa15e76e6ef9cd04bdd2f1d182aa1 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 6 Aug 2025 13:38:22 +0300 Subject: [PATCH 02/44] Attached libcdoc submodule --- client/libcdoc | 1 + 1 file changed, 1 insertion(+) create mode 160000 client/libcdoc diff --git a/client/libcdoc b/client/libcdoc new file mode 160000 index 000000000..594c6eb32 --- /dev/null +++ b/client/libcdoc @@ -0,0 +1 @@ +Subproject commit 594c6eb325fd5aa2b73da8c71f4df11c26413852 From cb5d796c5afe8a16697cdd99f74515313b398580 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 6 Aug 2025 14:03:54 +0300 Subject: [PATCH 03/44] Fixed logging for Qt < 6.5 --- client/CDocSupport.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 391edf07c..67c397fb0 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -42,6 +42,12 @@ #include "CDocSupport.h" +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) +#define SV2S(m) QUtf8StringView(m) +#else +#define SV2S(m) (m) +#endif + std::vector CDocSupport::getCDocFileList(QString filename) { @@ -269,23 +275,23 @@ Q_LOGGING_CATEGORY(LOG_CDOC, "libcdoc") void DDCDocLogger::LogMessage(libcdoc::ILogger::LogLevel level, std::string_view file, int line, std::string_view message) { switch (level) { case libcdoc::ILogger::LogLevel::LEVEL_FATAL: - qCFatal(LOG_CDOC) << message; + qFatal(LOG_CDOC, "%s", std::string(message).c_str()); break; case libcdoc::ILogger::LogLevel::LEVEL_ERROR: - qCCritical(LOG_CDOC) << message; + qCCritical(LOG_CDOC) << SV2S(message); break; case libcdoc::ILogger::LogLevel::LEVEL_WARNING: - qCWarning(LOG_CDOC) << message; + qCWarning(LOG_CDOC) << SV2S(message); break; case libcdoc::ILogger::LogLevel::LEVEL_INFO: - qCInfo(LOG_CDOC) << message; + qCInfo(LOG_CDOC) << SV2S(message); break; case libcdoc::ILogger::LogLevel::LEVEL_DEBUG: - qCDebug(LOG_CDOC) << message; + qCDebug(LOG_CDOC) << SV2S(message); break; default: // Trace, if present goes to debug categrory - qCDebug(LOG_CDOC) << message; + qCDebug(LOG_CDOC) << SV2S(message); break; } } From 670c1d5325047b6f54d946dfcecc629b2e5259d9 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 6 Aug 2025 14:14:23 +0300 Subject: [PATCH 04/44] qFatal fix for Qt < 6.5 --- client/CDocSupport.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 67c397fb0..0e9ee4e0c 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -44,8 +44,10 @@ #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) #define SV2S(m) QUtf8StringView(m) +#define Q_FATAL(m) QFatal("%s", std::string(m).c_str()) #else #define SV2S(m) (m) +#define Q_FATAL(m) qCFatal(LOG_CDOC) << (m) #endif std::vector @@ -275,7 +277,7 @@ Q_LOGGING_CATEGORY(LOG_CDOC, "libcdoc") void DDCDocLogger::LogMessage(libcdoc::ILogger::LogLevel level, std::string_view file, int line, std::string_view message) { switch (level) { case libcdoc::ILogger::LogLevel::LEVEL_FATAL: - qFatal(LOG_CDOC, "%s", std::string(message).c_str()); + Q_FATAL(message); break; case libcdoc::ILogger::LogLevel::LEVEL_ERROR: qCCritical(LOG_CDOC) << SV2S(message); From 2026f4e7a428dd553bd5494b4af333faf166e183 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 6 Aug 2025 14:19:28 +0300 Subject: [PATCH 05/44] QFatal -> qFatal --- client/CDocSupport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 0e9ee4e0c..5755e1b02 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -44,7 +44,7 @@ #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) #define SV2S(m) QUtf8StringView(m) -#define Q_FATAL(m) QFatal("%s", std::string(m).c_str()) +#define Q_FATAL(m) qFatal("%s", std::string(m).c_str()) #else #define SV2S(m) (m) #define Q_FATAL(m) qCFatal(LOG_CDOC) << (m) From 345175d3b03940badd66fa228e06dd934436187e Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 6 Nov 2025 15:36:12 +0200 Subject: [PATCH 06/44] Merge fixes Signed-off-by: Lauris Kaplinski --- client/dialogs/AddRecipients.cpp | 5 +++-- client/dialogs/AddRecipients.h | 4 +--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index edd6df2b0..fe554c84f 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -33,6 +33,7 @@ #include "dialogs/WarningDialog.h" #include "effects/Overlay.h" #include "Crypto.h" +#include "widgets/AddressItem.h" #include #include @@ -228,7 +229,7 @@ void AddRecipients::addRecipientToRightPane(Item *item, bool update) rightList.append(key); - auto *rightItem = new AddressItem(CKey(key), AddressItem::Remove, ui->rightPane); + auto *rightItem = new AddressItem(key, AddressItem::Remove, ui->rightPane); connect(rightItem, &AddressItem::remove, this, [this](Item *item) { auto *rightItem = qobject_cast(item); if(auto *leftItem = itemListValue(ui->leftPane, rightItem->getKey())) @@ -258,7 +259,7 @@ AddressItem* AddRecipients::itemListValue(ItemList *list, const CDKey &key) return nullptr; } -QList AddRecipients::keys() const +QList AddRecipients::keys() const { QList recipients; for(auto *item: ui->rightPane->items) diff --git a/client/dialogs/AddRecipients.h b/client/dialogs/AddRecipients.h index 30a8b78ef..4a74fe881 100644 --- a/client/dialogs/AddRecipients.h +++ b/client/dialogs/AddRecipients.h @@ -41,7 +41,7 @@ class AddRecipients final : public QDialog explicit AddRecipients(ItemList* itemList, QWidget *parent = nullptr); ~AddRecipients() final; - QList keys() const; + QList keys() const; bool isUpdated() const; private: @@ -55,8 +55,6 @@ class AddRecipients final : public QDialog void showError(const QString &msg, const QString &details = {}); void showResult(const QList &result, int resultCount, const QVariantMap &userData); - static AddressItem* itemListValue(ItemList *list, const CKey &cert); - Ui::AddRecipients *ui; QList rightList; QList ldap_person; From 8aad0345060c8528f1fbe4ec341451b0bb7e90f5 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 7 Nov 2025 09:43:33 +0200 Subject: [PATCH 07/44] Fixed build.yml Signed-off-by: Lauris Kaplinski --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d2c1c3975..88ffbb198 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -162,8 +162,8 @@ jobs: run: | Rename-Item "libdigidocpp*.msi" libdigidocpp.msi msiexec /qn /i libdigidocpp.msi - - name: Prepare vcpkg - uses: lukka/run-vcpkg@v11 + - name: Cache vcpkg + uses: actions/cache@v4 with: path: ${{ github.workspace }}/vcpkg_cache key: vcpkg-${{ matrix.vcver }}-${{ hashFiles('vcpkg.json') }} From 423ee029d1c3742a54146d5434d1e36570794e67 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 7 Nov 2025 10:22:41 +0200 Subject: [PATCH 08/44] Updated libcdoc Signed-off-by: Lauris Kaplinski --- client/libcdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/libcdoc b/client/libcdoc index 594c6eb32..3b0d12726 160000 --- a/client/libcdoc +++ b/client/libcdoc @@ -1 +1 @@ -Subproject commit 594c6eb325fd5aa2b73da8c71f4df11c26413852 +Subproject commit 3b0d12726fde786a9e1c5b9a0baeeda5edeec2ce From 5063b064e8590130bdb782f4b736de4b84b9a3a4 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 7 Nov 2025 10:39:21 +0200 Subject: [PATCH 09/44] Updated sendKey and timestamp calculation Signed-off-by: Lauris Kaplinski --- client/CDocSupport.cpp | 9 ++++++--- client/CDocSupport.h | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 5755e1b02..6714338d3 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -194,9 +194,12 @@ DDNetworkBackend::getLastErrorStr(libcdoc::result_t code) const libcdoc::result_t DDNetworkBackend::sendKey( libcdoc::NetworkBackend::CapsuleInfo &dst, const std::string &url, const std::vector &rcpt_key, - const std::vector &key_material, const std::string &type) { + const std::vector &key_material, const std::string &type, uint64_t expiry_ts) { QNetworkRequest req(QString::fromStdString(url + "/key-capsules")); req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); + if (expiry_ts) { + req.setRawHeader("x-expiry-time", QDateTime::fromSecsSinceEpoch(expiry_ts).toString(Qt::ISODate).toLatin1()); + } if (!checkConnection()) { last_error = "No connection"; return BACKEND_ERROR; @@ -234,8 +237,8 @@ libcdoc::result_t DDNetworkBackend::sendKey( return BACKEND_ERROR; } dst.transaction_id = tr_id.toStdString(); - QDateTime dt = QDateTime::currentDateTimeUtc(); - dt = dt.addMonths(6); + + QDateTime dt = QDateTime::fromString(QString::fromLatin1(reply->rawHeader("x-expiry-time"))); dst.expiry_time = dt.toSecsSinceEpoch(); return libcdoc::OK; }; diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 5b719f61c..b382a5bc1 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -88,7 +88,8 @@ struct DDNetworkBackend : public libcdoc::NetworkBackend, private QObject { const std::string &url, const std::vector &rcpt_key, const std::vector &key_material, - const std::string &type) override final; + const std::string &type, + uint64_t expiry_ts) override final; libcdoc::result_t fetchKey(std::vector &result, const std::string &keyserver_id, const std::string &transaction_id) override final; From d72f4fbf8d230f578bd558d63481e282458fb4ac Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 7 Nov 2025 11:10:05 +0200 Subject: [PATCH 10/44] Update MacOS deployment target Signed-off-by: Lauris Kaplinski --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 88ffbb198..63d80de85 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: name: Build on macOS runs-on: macos-latest env: - MACOSX_DEPLOYMENT_TARGET: 13.0 + MACOSX_DEPLOYMENT_TARGET: 13.3 LIBS_PATH: ${{ github.workspace }}/cache steps: - name: Checkout From e8d1c31652124c1c3f5d4e16028f3b265a7b78be Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Tue, 19 Aug 2025 12:50:40 +0300 Subject: [PATCH 11/44] Improve --- client/CDocSupport.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 6714338d3..efa8e8f27 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include @@ -43,10 +42,11 @@ #include "CDocSupport.h" #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) -#define SV2S(m) QUtf8StringView(m) +QDebug operator<<(QDebug d, std::string_view str) { + return d << QUtf8StringView(str); +} #define Q_FATAL(m) qFatal("%s", std::string(m).c_str()) #else -#define SV2S(m) (m) #define Q_FATAL(m) qCFatal(LOG_CDOC) << (m) #endif @@ -283,27 +283,27 @@ void DDCDocLogger::LogMessage(libcdoc::ILogger::LogLevel level, std::string_view Q_FATAL(message); break; case libcdoc::ILogger::LogLevel::LEVEL_ERROR: - qCCritical(LOG_CDOC) << SV2S(message); + qCCritical(LOG_CDOC) << message; break; case libcdoc::ILogger::LogLevel::LEVEL_WARNING: - qCWarning(LOG_CDOC) << SV2S(message); + qCWarning(LOG_CDOC) << message; break; case libcdoc::ILogger::LogLevel::LEVEL_INFO: - qCInfo(LOG_CDOC) << SV2S(message); + qCInfo(LOG_CDOC) << message; break; case libcdoc::ILogger::LogLevel::LEVEL_DEBUG: - qCDebug(LOG_CDOC) << SV2S(message); + qCDebug(LOG_CDOC) << message; break; default: // Trace, if present goes to debug categrory - qCDebug(LOG_CDOC) << SV2S(message); + qCDebug(LOG_CDOC) << message; break; } } void DDCDocLogger::setUpLogger() { static DDCDocLogger *logger = nullptr; - if (logger) { + if (!logger) { logger = new DDCDocLogger(); logger->SetMinLogLevel(libcdoc::ILogger::LogLevel::LEVEL_TRACE); libcdoc::ILogger::addLogger(logger); From 340f66b1be0cd5a2af2ce0da0955dc51c0900a30 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 7 Nov 2025 11:16:03 +0200 Subject: [PATCH 12/44] Use new Expire date Signed-off-by: Raul Metsma --- client/CryptoDoc.cpp | 2 +- client/widgets/AddressItem.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 4bd6e27c6..a70ed201e 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -195,7 +195,7 @@ bool CryptoDoc::Private::encrypt() { enc_keys.push_back(libcdoc::Recipient::makeServer( label, key_der, pk_type, keyserver_id)); } else { - std::string label = CryptoDoc::labelFromCertificate(cert_der); + std::string label;// = CryptoDoc::labelFromCertificate(cert_der); enc_keys.push_back( libcdoc::Recipient::makeCertificate(label, cert_der)); } diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index 82c15aad6..f2ff2ff78 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -36,6 +36,7 @@ class AddressItem::Private: public Ui::AddressItem QString code; CDKey key; QString label; + QDateTime expireDate; bool yourself = false; }; @@ -71,6 +72,9 @@ AddressItem::AddressItem(const CDKey &key, Type type, QWidget *parent) } else { ui->label = QString::fromStdString(ui->key.lock.label); } + if (map.contains("x-expiry-time")) { + ui->expireDate = QDateTime::fromSecsSinceEpoch(QString::fromStdString(map["x-expiry-time"]).toLongLong()); + } if (ui->key.lock.isSymmetric()) { ui->decrypt->show(); connect(ui->decrypt, &QToolButton::clicked, this, @@ -226,6 +230,16 @@ void AddressItem::setIdType() { QStringLiteral("dd.MM.yyyy"))) : tr("Decryption has expired")); } + } else if(!ui->expireDate.isNull()) { + bool canDecrypt = ui->expireDate > QDateTime::currentDateTime(); + ui->expire->setProperty("label", canDecrypt + ? QStringLiteral("good") + : QStringLiteral("warn")); + ui->expire->setText( + canDecrypt ? QStringLiteral("%1 %2").arg( + tr("Decryption is possible until:"), + ui->expireDate.toLocalTime().toString(QStringLiteral("dd.MM.yyyy"))) + : tr("Decryption has expired")); } } else { // No rcpt, lock is invalid = unsupported lock From 36f1b9f9666f7b81f0fc805cbb62f1c7a4b25739 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 18 Dec 2025 23:35:41 +0200 Subject: [PATCH 13/44] Clean up error handling and background processing --- client/CDocSupport.cpp | 15 ++++-- client/CDocSupport.h | 2 + client/CryptoDoc.cpp | 103 +++++++++++++++++++++-------------------- client/QPKCS11.cpp | 36 +++++++------- client/QSigner.cpp | 7 +++ client/QSigner.h | 1 + 6 files changed, 95 insertions(+), 69 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index efa8e8f27..a0d736119 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -97,7 +97,7 @@ DDCryptoBackend::decryptRSA(std::vector& result, const std::vectordecrypt(qdata, oaep); }); result.assign(qkek.cbegin(), qkek.cend()); - return (result.empty()) ? OPENSSL_ERROR : libcdoc::OK; + return (result.empty()) ? BACKEND_ERROR : libcdoc::OK; } const QString SHA256_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha256"); @@ -119,7 +119,7 @@ DDCryptoBackend::deriveConcatKDF(std::vector& dst, const std::vector(partyVInfo.data()), partyVInfo.size())); }); dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); - return (dst.empty()) ? OPENSSL_ERROR : libcdoc::OK; + return (dst.empty()) ? BACKEND_ERROR : libcdoc::OK; } libcdoc::result_t @@ -131,7 +131,7 @@ DDCryptoBackend::deriveHMACExtract(std::vector& dst, const std::vector< return backend->deriveHMACExtract(qkey_material, qsalt, ECC_KEY_LEN); }); dst = std::vector(qkekpm.cbegin(), qkekpm.cend()); - return (dst.empty()) ? OPENSSL_ERROR : libcdoc::OK; + return (dst.empty()) ? BACKEND_ERROR : libcdoc::OK; } libcdoc::result_t @@ -141,6 +141,15 @@ DDCryptoBackend::getSecret(std::vector& _secret, unsigned int idx) return libcdoc::OK; } +std::string +DDCryptoBackend::getLastErrorStr(libcdoc::result_t code) const +{ + if (code == BACKEND_ERROR) { + return qApp->signer()->getLastErrorStr().toStdString(); + } + return libcdoc::CryptoBackend::getLastErrorStr(code); +} + bool checkConnection() { diff --git a/client/CDocSupport.h b/client/CDocSupport.h index b382a5bc1..4019c8d52 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -52,6 +52,7 @@ struct DDConfiguration : public libcdoc::Configuration { // struct DDCryptoBackend : public libcdoc::CryptoBackend { + static constexpr int BACKEND_ERROR = -303; libcdoc::result_t decryptRSA(std::vector &result, const std::vector &data, bool oaep, unsigned int idx) override final; @@ -68,6 +69,7 @@ struct DDCryptoBackend : public libcdoc::CryptoBackend { unsigned int idx) override final; libcdoc::result_t getSecret(std::vector &secret, unsigned int idx) override final; + std::string getLastErrorStr(libcdoc::result_t code) const final; std::vector secret; diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index a70ed201e..dec835577 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -89,6 +89,40 @@ class CryptoDoc::Private final: public QThread start(); e.exec(); } + inline libcdoc::result_t decrypt(unsigned int lock_idx) { + TempListConsumer cons; + libcdoc::result_t result = waitFor([&]{ + std::vector fmk; + libcdoc::result_t result = reader->getFMK(fmk, lock_idx); + qDebug() << "getFMK result: " << result << " " << reader->getLastErrorStr(); + if (result != libcdoc::OK) return result; + result = reader->decrypt(fmk, &cons); + std::fill(fmk.begin(), fmk.end(), 0); + qDebug() << "Decryption result: " << result << " " << reader->getLastErrorStr(); + return result; + }); + if (result == libcdoc::OK) { + files = std::move(cons.files); + // Success, immediately create writer from reader + keys.clear(); + writer_last_error.clear(); + reader.reset(); + } + return result; + } + + inline libcdoc::result_t encrypt(unsigned int lock_idx) { + libcdoc::result_t result = waitFor([&]{ + libcdoc::result_t result = encrypt(); + qDebug() << "Encryption result: " << result << " " << reader->getLastErrorStr(); + if (result == libcdoc::OK) { + // Encryption successful, open new reader + reader = createCDocReader(fileName.toStdString()); + } + return result; + }); + return result; + } std::unique_ptr reader; std::string writer_last_error; @@ -97,8 +131,6 @@ class CryptoDoc::Private final: public QThread bool isEncrypted() const { return reader != nullptr; } CDocumentModel *documents = new CDocumentModel(this); QStringList tempFiles; - // Decryption data - QByteArray fmk; // Encryption data QString label; uint32_t kdf_iter; @@ -126,8 +158,7 @@ class CryptoDoc::Private final: public QThread } return std::unique_ptr(r); } -private: - bool encrypt(); + libcdoc::result_t encrypt(); }; bool CryptoDoc::Private::warnIfNotWritable() const @@ -144,33 +175,20 @@ bool CryptoDoc::Private::warnIfNotWritable() const void CryptoDoc::Private::run() { - if(reader) { - qCDebug(CRYPTO) << "Decrypt" << fileName; - std::vector pfmk(fmk.cbegin(), fmk.cend()); - - TempListConsumer cons; - if (reader->decrypt(pfmk, &cons) == libcdoc::OK) { - files = std::move(cons.files); - // Success, immediately create writer from reader - keys.clear(); - writer_last_error.clear(); - reader.reset(); - } - } else { - if (encrypt()) { - // Encryption successful, open new reader - reader = createCDocReader(fileName.toStdString()); - if (!reader) return; - } + if (encrypt() == libcdoc::OK) { + // Encryption successful, open new reader + reader = createCDocReader(fileName.toStdString()); + if (!reader) return; } } -bool CryptoDoc::Private::encrypt() { +libcdoc::result_t +CryptoDoc::Private::encrypt() { qCDebug(CRYPTO) << "Encrypt" << fileName; libcdoc::OStreamConsumer ofs(fileName.toStdString()); if (ofs.isError()) - return false; + return libcdoc::OUTPUT_ERROR; StreamListSource slsrc(files); std::vector enc_keys; @@ -195,7 +213,7 @@ bool CryptoDoc::Private::encrypt() { enc_keys.push_back(libcdoc::Recipient::makeServer( label, key_der, pk_type, keyserver_id)); } else { - std::string label;// = CryptoDoc::labelFromCertificate(cert_der); + std::string label; enc_keys.push_back( libcdoc::Recipient::makeCertificate(label, cert_der)); } @@ -215,7 +233,7 @@ bool CryptoDoc::Private::encrypt() { } delete writer; ofs.close(); - return (result == libcdoc::OK); + return result; } CDocumentModel::CDocumentModel(CryptoDoc::Private *doc) : d(doc) {} @@ -459,26 +477,12 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray &secret) { } d->crypto.secret.assign(secret.cbegin(), secret.cend()); - std::vector fmk; - if (d->reader->getFMK(fmk, lock_idx) != libcdoc::OK) - return false; - d->fmk = QByteArray(reinterpret_cast(fmk.data()), fmk.size()); - if (d->fmk.isEmpty()) { - const std::string &msg = d->reader->getLastErrorStr(); - WarningDialog::show(tr("Failed to decrypt document. Please check your " - "internet connection and network settings."), - QString::fromStdString(msg)); - return false; - } - d->waitForFinished(); - if (d->reader) { + libcdoc::result_t result = d->decrypt(lock_idx); + if (result != libcdoc::OK) { const std::string &msg = d->reader->getLastErrorStr(); - if (msg.empty()) { - WarningDialog::show(tr("Error parsing document")); - } else { - WarningDialog::show(QString::fromStdString(msg)); - } + WarningDialog::show(tr("Failed to decrypt document"), + QString::fromStdString(d->reader->getLastErrorStr())); } return !d->isEncrypted(); } @@ -507,14 +511,15 @@ bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QB d->crypto.secret.assign(secret.cbegin(), secret.cend()); d->kdf_iter = kdf_iter; } - d->waitForFinished(); + libcdoc::result_t result = d->encrypt(); d->label.clear(); d->crypto.secret.clear(); - if(d->isEncrypted()) { - open(d->fileName); - } else { - WarningDialog::show(tr("Failed to encrypt document. Please check your internet connection and network settings."), QString::fromStdString(d->writer_last_error)); + if (result != libcdoc::OK) { + const std::string &msg = d->reader->getLastErrorStr(); + WarningDialog::show(tr("Failed to encrypt document"), + QString::fromStdString(d->writer_last_error)); } + return d->isEncrypted(); } diff --git a/client/QPKCS11.cpp b/client/QPKCS11.cpp index 7592db622..2cab3d751 100644 --- a/client/QPKCS11.cpp +++ b/client/QPKCS11.cpp @@ -24,6 +24,7 @@ #include "QPCSC.h" #include "SslCertificate.h" #include "TokenData.h" +#include "Utils.h" #include "dialogs/PinPopup.h" #include @@ -222,23 +223,24 @@ QPKCS11::PinStatus QPKCS11::login(const TokenData &t) if(token.flags & CKF_USER_PIN_LOCKED) return PinLocked; if(token.flags & CKF_USER_PIN_COUNT_LOW) f = PinPopup::PinCountLow; if(token.flags & CKF_USER_PIN_FINAL_TRY) f = PinPopup::PinFinalTry; - if(token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) - { - PinPopup p(isSign ? PinPopup::Pin2PinpadType : PinPopup::Pin1PinpadType, cert, f, Application::mainWindow()); - connect(d, &Private::started, &p, &PinPopup::startTimer); - connect(d, &Private::finished, &p, &PinPopup::accept); - d->start(); - p.exec(); - err = d->result; - } - else - { - PinPopup p(isSign ? PinPopup::Pin2Type : PinPopup::Pin1Type, cert, f, Application::mainWindow()); - p.setPinLen(token.ulMinPinLen, token.ulMaxPinLen < 12 ? 12 : token.ulMaxPinLen); - if( !p.exec() ) - return PinCanceled; - QByteArray pin = p.pin().toUtf8(); - err = d->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pin.constData()), CK_ULONG(pin.size())); + if(token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) { + err = dispatchToMain([&]{ + PinPopup p(isSign ? PinPopup::Pin2PinpadType : PinPopup::Pin1PinpadType, cert, f, Application::mainWindow()); + connect(d, &Private::started, &p, &PinPopup::startTimer); + connect(d, &Private::finished, &p, &PinPopup::accept); + d->start(); + p.exec(); + return d->result; + }); + } else { + err = dispatchToMain([&]{ + PinPopup p(isSign ? PinPopup::Pin2Type : PinPopup::Pin1Type, cert, f, 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())); + }); } d->f->C_GetTokenInfo(currentSlot, &token); diff --git a/client/QSigner.cpp b/client/QSigner.cpp index 83e721c8b..b8d12ccfe 100644 --- a/client/QSigner.cpp +++ b/client/QSigner.cpp @@ -438,3 +438,10 @@ std::vector QSigner::sign(const std::string &method, const std::v QSmartCard * QSigner::smartcard() const { return d->smartcard; } TokenData QSigner::tokenauth() const { return d->auth; } TokenData QSigner::tokensign() const { return d->sign; } + +QString +QSigner::getLastErrorStr() const +{ + QCryptoBackend::PinStatus status = d->backend->lastError(); + return d->backend->errorString(status); +} diff --git a/client/QSigner.h b/client/QSigner.h index e15d2e012..12d1aa88d 100644 --- a/client/QSigner.h +++ b/client/QSigner.h @@ -50,6 +50,7 @@ class QSigner final: public QThread, public digidoc::Signer QSmartCard * smartcard() const; TokenData tokenauth() const; TokenData tokensign() const; + QString getLastErrorStr() const; Q_SIGNALS: void cacheChanged(); From 28314fec43f27374503416ee2d23f0fb052d7850 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 2 Jan 2026 11:13:04 +0200 Subject: [PATCH 14/44] Remove invalid XML1.0 characters from Role/City... fields --- client/dialogs/RoleAddressDialog.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/client/dialogs/RoleAddressDialog.cpp b/client/dialogs/RoleAddressDialog.cpp index 362e31a2b..6959d8333 100644 --- a/client/dialogs/RoleAddressDialog.cpp +++ b/client/dialogs/RoleAddressDialog.cpp @@ -28,6 +28,18 @@ class RoleAddressDialog::Private: public Ui::RoleAddressDialog {}; +static QString cleanUp(const QString& src) { + QString dst(src.size(), QChar(0)); + size_t dlen = 0; + for (auto s = 0; s < src.size(); s++) { + if ((src[s] <= ' ') && (src[s] != QChar(0x9)) && (src[s] != QChar(0xa)) && (src[s] != QChar(0xd))) continue; + if ((src[s] == QChar(0xfffe)) || (src[s] == QChar(0xffff))) continue; + dst[dlen++] = src[s]; + } + dst.resize(dlen); + return dst; +} + RoleAddressDialog::RoleAddressDialog(QWidget *parent) : QDialog(parent) , d(new Private) @@ -57,8 +69,9 @@ RoleAddressDialog::RoleAddressDialog(QWidget *parent) line->setCompleter(completer); connect(line, &QLineEdit::editingFinished, this, [line, s = std::move(s)] { QStringList list = s; - list.removeAll(line->text()); - list.insert(0, line->text()); + QString text = cleanUp(line->text()); + list.removeAll(text); + list.insert(0, text); if(list.size() > 10) list.removeLast(); s.clear(); // Uses on Windows MULTI_STRING registry @@ -81,10 +94,10 @@ int RoleAddressDialog::get(QString &city, QString &country, QString &state, QStr int result = QDialog::exec(); if(result == QDialog::Rejected) return result; - role = d->Role->text(); - city = d->City->text(); - state = d->State->text(); - country = d->Country->text(); - zip = d->Zip->text(); + role = cleanUp(d->Role->text()); + city = cleanUp(d->City->text()); + state = cleanUp(d->State->text()); + country = cleanUp(d->Country->text()); + zip = cleanUp(d->Zip->text()); return result; } From a562d02417f530b8f87fbd86a07e27fe65203804 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 2 Jan 2026 13:51:53 +0200 Subject: [PATCH 15/44] Use iterator --- client/dialogs/RoleAddressDialog.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/dialogs/RoleAddressDialog.cpp b/client/dialogs/RoleAddressDialog.cpp index 6959d8333..f52fb71b9 100644 --- a/client/dialogs/RoleAddressDialog.cpp +++ b/client/dialogs/RoleAddressDialog.cpp @@ -31,10 +31,10 @@ class RoleAddressDialog::Private: public Ui::RoleAddressDialog {}; static QString cleanUp(const QString& src) { QString dst(src.size(), QChar(0)); size_t dlen = 0; - for (auto s = 0; s < src.size(); s++) { - if ((src[s] <= ' ') && (src[s] != QChar(0x9)) && (src[s] != QChar(0xa)) && (src[s] != QChar(0xd))) continue; - if ((src[s] == QChar(0xfffe)) || (src[s] == QChar(0xffff))) continue; - dst[dlen++] = src[s]; + for (auto s = src.cbegin(); s != src.cend(); s++) { + if ((*s <= ' ') && (*s != QChar(0x9)) && (*s != QChar(0xa)) && (*s != QChar(0xd))) continue; + if ((*s == QChar(0xfffe)) || (*s == QChar(0xffff))) continue; + dst[dlen++] = *s; } dst.resize(dlen); return dst; From 7c5d3306a7fa79f8c7079121d41a0b9df35f6860 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 2 Jan 2026 14:01:55 +0200 Subject: [PATCH 16/44] Use reserve/append instead of resize --- client/dialogs/RoleAddressDialog.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/dialogs/RoleAddressDialog.cpp b/client/dialogs/RoleAddressDialog.cpp index f52fb71b9..1c12e3a33 100644 --- a/client/dialogs/RoleAddressDialog.cpp +++ b/client/dialogs/RoleAddressDialog.cpp @@ -29,12 +29,13 @@ class RoleAddressDialog::Private: public Ui::RoleAddressDialog {}; static QString cleanUp(const QString& src) { - QString dst(src.size(), QChar(0)); + QString dst; + dst.reserve(src.size()); size_t dlen = 0; for (auto s = src.cbegin(); s != src.cend(); s++) { if ((*s <= ' ') && (*s != QChar(0x9)) && (*s != QChar(0xa)) && (*s != QChar(0xd))) continue; if ((*s == QChar(0xfffe)) || (*s == QChar(0xffff))) continue; - dst[dlen++] = *s; + dst.append(*s); } dst.resize(dlen); return dst; From ee688cade024443168d50d4586a38bea2091ea97 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 5 Jan 2026 13:34:54 +0200 Subject: [PATCH 17/44] Use libcdoc labels handling Signed-off-by: Raul Metsma --- client/CryptoDoc.cpp | 41 ++++------------------------------------- client/CryptoDoc.h | 1 - client/libcdoc | 2 +- 3 files changed, 5 insertions(+), 39 deletions(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index dec835577..45a42c5fb 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -54,28 +54,6 @@ auto toHex = [](const std::vector& data) -> QString { return ba.toHex(); }; -std::string -CryptoDoc::labelFromCertificate(const std::vector& cert) -{ - QSslCertificate kcert(QByteArray(reinterpret_cast(cert.data()), cert.size()), QSsl::Der); - return [](const SslCertificate &c) { - QString cn = c.subjectInfo(QSslCertificate::CommonName); - QString gn = c.subjectInfo("GN"); - QString sn = c.subjectInfo("SN"); - if(!gn.isEmpty() || !sn.isEmpty()) - cn = QStringLiteral("%1 %2 %3").arg(gn, sn, c.personalCode()); - - int certType = c.type(); - if(certType & SslCertificate::EResidentSubType) - return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("Digi-ID E-RESIDENT")).toStdString(); - if(certType & SslCertificate::DigiIDType) - return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("Digi-ID")).toStdString(); - if(certType & SslCertificate::EstEidType) - return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("ID-CARD")).toStdString(); - return cn.toStdString(); - }(kcert); -} - class CryptoDoc::Private final: public QThread { Q_OBJECT @@ -199,23 +177,12 @@ CryptoDoc::Private::encrypt() { } for (auto &key : keys) { QByteArray ba = key.rcpt_cert.toDer(); - std::vector cert_der = - std::vector(ba.cbegin(), ba.cend()); - QSslKey qkey = key.rcpt_cert.publicKey(); - ba = Crypto::toPublicKeyDer(qkey); - std::vector key_der(ba.cbegin(), ba.cend()); - libcdoc::Recipient::PKType pk_type = - (qkey.algorithm() == QSsl::KeyAlgorithm::Rsa) - ? libcdoc::Recipient::PKType::RSA - : libcdoc::Recipient::PKType::ECC; - if (!keyserver_id.empty()) { - std::string label = CryptoDoc::labelFromCertificate(cert_der); - enc_keys.push_back(libcdoc::Recipient::makeServer( - label, key_der, pk_type, keyserver_id)); + if (keyserver_id.empty()) { + enc_keys.push_back( + libcdoc::Recipient::makeCertificate({}, {ba.cbegin(), ba.cend()})); } else { - std::string label; enc_keys.push_back( - libcdoc::Recipient::makeCertificate(label, cert_der)); + libcdoc::Recipient::makeServer({}, {ba.cbegin(), ba.cend()}, keyserver_id)); } } if (!crypto.secret.empty()) { diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index 87ed8d97a..72c3db26b 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -67,7 +67,6 @@ class CryptoDoc final: public QObject bool saveCopy(const QString &filename); ria::qdigidoc4::ContainerState state() const; - static std::string labelFromCertificate(const std::vector& cert); private: class Private; Private *d; diff --git a/client/libcdoc b/client/libcdoc index 3b0d12726..2c70edb16 160000 --- a/client/libcdoc +++ b/client/libcdoc @@ -1 +1 @@ -Subproject commit 3b0d12726fde786a9e1c5b9a0baeeda5edeec2ce +Subproject commit 2c70edb1600160ea50c1dd3045ef7ae3c803a8e1 From 239bdf821d3895029a787793b86bb3bd76cb994c Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 5 Jan 2026 13:39:17 +0200 Subject: [PATCH 18/44] Avoid allocations and handle servers without x-expiry-time support Signed-off-by: Raul Metsma --- client/CDocSupport.cpp | 68 +++++++++++++++++------------------------- client/CDocSupport.h | 2 +- 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index a0d736119..4868d0440 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -50,6 +50,10 @@ QDebug operator<<(QDebug d, std::string_view str) { #define Q_FATAL(m) qCFatal(LOG_CDOC) << (m) #endif +static QByteArray toByteArray(const std::vector &data) { + return QByteArray::fromRawData(reinterpret_cast(data.data()), data.size()); +} + std::vector CDocSupport::getCDocFileList(QString filename) { @@ -92,18 +96,17 @@ CDocSupport::getCDocFileList(QString filename) libcdoc::result_t DDCryptoBackend::decryptRSA(std::vector& result, const std::vector &data, bool oaep, unsigned int idx) { - QByteArray qdata(reinterpret_cast(data.data()), data.size()); - QByteArray qkek = qApp->signer()->decrypt([&qdata, &oaep](QCryptoBackend *backend) { - return backend->decrypt(qdata, oaep); + QByteArray qkek = qApp->signer()->decrypt([qdata = toByteArray(data), &oaep](QCryptoBackend *backend) { + return backend->decrypt(qdata, oaep); }); result.assign(qkek.cbegin(), qkek.cend()); return (result.empty()) ? BACKEND_ERROR : libcdoc::OK; } -const QString SHA256_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha256"); -const QString SHA384_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha384"); -const QString SHA512_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha512"); -const QHash SHA_MTH{ +constexpr std::string_view SHA256_MTH {"http://www.w3.org/2001/04/xmlenc#sha256"}; +constexpr std::string_view SHA384_MTH {"http://www.w3.org/2001/04/xmlenc#sha384"}; +constexpr std::string_view SHA512_MTH {"http://www.w3.org/2001/04/xmlenc#sha512"}; +const QHash SHA_MTH{ {SHA256_MTH, QCryptographicHash::Sha256}, {SHA384_MTH, QCryptographicHash::Sha384}, {SHA512_MTH, QCryptographicHash::Sha512} }; @@ -112,11 +115,8 @@ DDCryptoBackend::deriveConcatKDF(std::vector& dst, const std::vector &algorithmID, const std::vector &partyUInfo, const std::vector &partyVInfo, unsigned int idx) { QByteArray decryptedKey = qApp->signer()->decrypt([&publicKey, &digest, &algorithmID, &partyUInfo, &partyVInfo](QCryptoBackend *backend) { - QByteArray ba(reinterpret_cast(publicKey.data()), publicKey.size()); - return backend->deriveConcatKDF(ba, SHA_MTH[QString::fromStdString(digest)], - QByteArray(reinterpret_cast(algorithmID.data()), algorithmID.size()), - QByteArray(reinterpret_cast(partyUInfo.data()), partyUInfo.size()), - QByteArray(reinterpret_cast(partyVInfo.data()), partyVInfo.size())); + return backend->deriveConcatKDF(toByteArray(publicKey), SHA_MTH[digest], + toByteArray(algorithmID), toByteArray(partyUInfo), toByteArray(partyVInfo)); }); dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); return (dst.empty()) ? BACKEND_ERROR : libcdoc::OK; @@ -125,9 +125,7 @@ DDCryptoBackend::deriveConcatKDF(std::vector& dst, const std::vector& dst, const std::vector &key_material, const std::vector &salt, unsigned int idx) { - QByteArray qkey_material(reinterpret_cast(key_material.data()), key_material.size()); - QByteArray qsalt(reinterpret_cast(salt.data()), salt.size()); - QByteArray qkekpm = qApp->signer()->decrypt([&qkey_material, &qsalt](QCryptoBackend *backend) { + QByteArray qkekpm = qApp->signer()->decrypt([qkey_material = toByteArray(key_material), qsalt = toByteArray(salt)](QCryptoBackend *backend) { return backend->deriveHMACExtract(qkey_material, qsalt, ECC_KEY_LEN); }); dst = std::vector(qkekpm.cbegin(), qkekpm.cend()); @@ -171,7 +169,7 @@ DDConfiguration::getValue(std::string_view domain, std::string_view param) const if (param == libcdoc::Configuration::KEYSERVER_SEND_URL) { #ifdef CONFIG_URL QJsonObject list = Application::confValue(QLatin1String("CDOC2-CONF")).toObject(); - QJsonObject data = list.value(QString::fromUtf8(domain.data(), domain.size())).toObject(); + QJsonObject data = list.value(QLatin1String(domain.data(), domain.size())).toObject(); QString url = data.value(QLatin1String("POST")).toString(Settings::CDOC2_POST); return url.toStdString(); #else @@ -181,7 +179,7 @@ DDConfiguration::getValue(std::string_view domain, std::string_view param) const } else if (param == libcdoc::Configuration::KEYSERVER_FETCH_URL) { #ifdef CONFIG_URL QJsonObject list = Application::confValue(QLatin1String("CDOC2-CONF")).toObject(); - QJsonObject data = list.value(QString::fromUtf8(domain.data(), domain.size())).toObject(); + QJsonObject data = list.value(QLatin1String(domain.data(), domain.size())).toObject(); QString url = data.value(QLatin1String("FETCH")).toString(Settings::CDOC2_GET); return url.toStdString(); #else @@ -214,27 +212,14 @@ libcdoc::result_t DDNetworkBackend::sendKey( return BACKEND_ERROR; } QScopedPointer nam(CheckConnection::setupNAM(req, Settings::CDOC2_POST_CERT)); - QEventLoop e; - QNetworkReply *reply = nam->post(req, - QJsonDocument({ - {QLatin1String("recipient_id"), - QLatin1String( - QByteArray(reinterpret_cast(rcpt_key.data()), - rcpt_key.size()) - .toBase64())}, - {QLatin1String("ephemeral_key_material"), - QLatin1String(QByteArray(reinterpret_cast( - key_material.data()), - key_material.size()) - .toBase64())}, - {QLatin1String("capsule_type"), QLatin1String(type.c_str())}, - }).toJson()); - connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); - e.exec(); + QNetworkReply *reply = waitFor(qOverload(&QNetworkAccessManager::post), nam.get(), req, QJsonDocument({ + {QLatin1String("recipient_id"), QLatin1String(toByteArray(rcpt_key).toBase64())}, + {QLatin1String("ephemeral_key_material"), QLatin1String(toByteArray(key_material).toBase64())}, + {QLatin1String("capsule_type"), QLatin1String(type.c_str())}, + }).toJson()); QString tr_id; if (reply->error() == QNetworkReply::NoError && - reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == - 201) { + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 201) { tr_id = QString::fromLatin1(reply->rawHeader("Location")) .remove(QLatin1String("/key-capsules/")); } else { @@ -247,8 +232,11 @@ libcdoc::result_t DDNetworkBackend::sendKey( } dst.transaction_id = tr_id.toStdString(); - QDateTime dt = QDateTime::fromString(QString::fromLatin1(reply->rawHeader("x-expiry-time"))); - dst.expiry_time = dt.toSecsSinceEpoch(); + if (const QByteArray &expiry = reply->rawHeader("x-expiry-time"); !expiry.isEmpty()) { + dst.expiry_time = QDateTime::fromString(QLatin1String(expiry)).toSecsSinceEpoch(); + } else { + dst.expiry_time = expiry_ts; + } return libcdoc::OK; }; @@ -256,7 +244,7 @@ libcdoc::result_t 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), QString::fromStdString(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"; @@ -370,7 +358,7 @@ TempListConsumer::open(const std::string& name, int64_t size) return libcdoc::OK; } -StreamListSource::StreamListSource(const std::vector& files) : _files(files), _current(-1) +StreamListSource::StreamListSource(const std::vector& files) : _files(files) { } diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 4019c8d52..d5c905714 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -170,7 +170,7 @@ struct StreamListSource : public libcdoc::MultiDataSource { libcdoc::result_t next(std::string &name, int64_t &size) override final; const std::vector &_files; - int64_t _current; + int64_t _current = -1; }; #endif // __CDOCSUPPORT_H__ From aad42d90976ccd9e5c70c656c9e9e3774b2b9859 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 5 Jan 2026 13:40:55 +0200 Subject: [PATCH 19/44] Show certificate and remove unused code Signed-off-by: Raul Metsma --- client/Crypto.cpp | 281 +-------------------------------- client/Crypto.h | 39 +---- client/widgets/AddressItem.cpp | 4 +- 3 files changed, 9 insertions(+), 315 deletions(-) diff --git a/client/Crypto.cpp b/client/Crypto.cpp index 17a9dfe40..5b1127ad4 100644 --- a/client/Crypto.cpp +++ b/client/Crypto.cpp @@ -19,136 +19,23 @@ #include "Crypto.h" -#include #include -#include -#include +#include + #include -#include #include #include #include #include -#include -#include #include #include -Q_LOGGING_CATEGORY(CRYPTO,"CRYPTO") +using puchar = uchar *; +using pcuchar = const uchar *; -Crypto::Cipher::Cipher(const EVP_CIPHER *cipher, const QByteArray &key, const QByteArray &iv, bool encrypt) - : ctx(SCOPE(EVP_CIPHER_CTX, EVP_CIPHER_CTX_new())) -{ - EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); - Q_UNUSED(isError(EVP_CipherInit_ex(ctx.get(), cipher, nullptr, pcuchar(key.data()), iv.isEmpty() ? nullptr : pcuchar(iv.data()), int(encrypt)))); -} - -bool Crypto::Cipher::updateAAD(const QByteArray &data) const -{ - int len = 0; - return !isError(EVP_CipherUpdate(ctx.get(), nullptr, &len, pcuchar(data.data()), int(data.size()))); -} - -QByteArray Crypto::Cipher::update(const QByteArray &data) const -{ - QByteArray result(data.size() + EVP_CIPHER_CTX_block_size(ctx.get()), 0); - int len = int(result.size()); - if(isError(EVP_CipherUpdate(ctx.get(), puchar(result.data()), &len, pcuchar(data.data()), int(data.size())))) - return {}; - result.resize(len); - return result; -} - -bool Crypto::Cipher::update(char *data, int size) const -{ - int len = 0; - return !isError(EVP_CipherUpdate(ctx.get(), puchar(data), &len, pcuchar(data), size)); -} - -bool Crypto::Cipher::result() const -{ - QByteArray result(EVP_CIPHER_CTX_block_size(ctx.get()), 0); - int len = int(result.size()); - if(isError(EVP_CipherFinal(ctx.get(), puchar(result.data()), &len))) - return false; - if(result.size() != len) - result.resize(len); - return true; -} - -QByteArray Crypto::Cipher::tag() const -{ - if(QByteArray result(tagLen(), 0); - !isError(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, int(result.size()), result.data()))) - return result; - return {}; -} - -bool Crypto::Cipher::setTag(const QByteArray &data) const -{ - return !isError(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, int(data.size()), const_cast(data.data()))); -} - -QByteArray Crypto::aes_wrap(const QByteArray &key, const QByteArray &data) -{ - Cipher c(key.size() == 32 ? EVP_aes_256_wrap() : EVP_aes_128_wrap(), key, {}, true); - if(QByteArray result = c.update(data); c.result()) - return result; - return {}; -} - -QByteArray Crypto::aes_unwrap(const QByteArray &key, const QByteArray &data) -{ - Cipher c(key.size() == 32 ? EVP_aes_256_wrap() : EVP_aes_128_wrap(), key, {}, false); - if(QByteArray result = c.update(data); c.result()) - return result; - return {}; -} - -QByteArray Crypto::cipher(const EVP_CIPHER *cipher, const QByteArray &key, QByteArray &data, bool encrypt) -{ - QByteArray iv(EVP_CIPHER_iv_length(cipher), 0), tag; - if(!encrypt) - { - iv = data.left(iv.length()); - data.remove(0, iv.length()); - if(EVP_CIPHER_mode(cipher) == EVP_CIPH_GCM_MODE) - tag = data.right(16); - data.resize(data.size() - tag.size()); - } - - auto ctx = SCOPE(EVP_CIPHER_CTX, EVP_CIPHER_CTX_new()); - if(isError(EVP_CipherInit(ctx.get(), cipher, pcuchar(key.constData()), pcuchar(iv.constData()), encrypt))) - return {}; - - int dataSize = int(data.size()); - data.resize(data.size() + EVP_CIPHER_CTX_block_size(ctx.get())); - int size = int(data.size()); - auto resultPointer = puchar(data.data()); //Detach only once - if(isError(EVP_CipherUpdate(ctx.get(), resultPointer, &size, pcuchar(data.constData()), dataSize))) - return {}; - - if(!encrypt && EVP_CIPHER_mode(cipher) == EVP_CIPH_GCM_MODE) - EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, int(tag.size()), tag.data()); - - int size2 = 0; - if(isError(EVP_CipherFinal(ctx.get(), resultPointer + size, &size2))) - return {}; - data.resize(size + size2); - if(encrypt) - { - data.prepend(iv); - if(EVP_CIPHER_mode(cipher) == EVP_CIPH_GCM_MODE) - { - tag = QByteArray(16, 0); - EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, int(tag.size()), tag.data()); - data.append(tag); - } - } - return data; -} +Q_LOGGING_CATEGORY(CRYPTO,"CRYPTO") QByteArray Crypto::concatKDF(QCryptographicHash::Algorithm hashAlg, const QByteArray &z, const QByteArray &otherInfo) { @@ -189,115 +76,14 @@ QByteArray Crypto::curve_oid(EVP_PKEY *key) return buf; } -QByteArray Crypto::derive(EVP_PKEY *priv, EVP_PKEY *pub) -{ - if(!priv || !pub) - return {}; - auto ctx = SCOPE(EVP_PKEY_CTX, EVP_PKEY_CTX_new(priv, nullptr)); - size_t sharedSecretLen = 0; - if(!ctx || - isError(EVP_PKEY_derive_init(ctx.get())) || - isError(EVP_PKEY_derive_set_peer(ctx.get(), pub)) || - isError(EVP_PKEY_derive(ctx.get(), nullptr, &sharedSecretLen))) - return {}; - QByteArray sharedSecret(int(sharedSecretLen), 0); - if(isError(EVP_PKEY_derive(ctx.get(), puchar(sharedSecret.data()), &sharedSecretLen))) - sharedSecret.clear(); - return sharedSecret; -} - -QByteArray Crypto::encrypt(EVP_PKEY *pub, int padding, const QByteArray &data) -{ - auto ctx = SCOPE(EVP_PKEY_CTX, EVP_PKEY_CTX_new(pub, nullptr)); - size_t size = 0; - if(isError(EVP_PKEY_encrypt_init(ctx.get())) || - isError(EVP_PKEY_CTX_set_rsa_padding(ctx.get(), padding)) || - isError(EVP_PKEY_encrypt(ctx.get(), nullptr, &size, - pcuchar(data.constData()), size_t(data.size())))) - return {}; - if(padding == RSA_PKCS1_OAEP_PADDING) - { - if(isError(EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), EVP_sha256())) || - isError(EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), EVP_sha256()))) - return {}; - } - QByteArray result(int(size), 0); - if(isError(EVP_PKEY_encrypt(ctx.get(), puchar(result.data()), &size, - pcuchar(data.constData()), size_t(data.size())))) - return {}; - return result; -} - -QByteArray Crypto::expand(const QByteArray &key, const QByteArray &info, int len) -{ - return hkdf(key, {}, info, len, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY); -} - QByteArray Crypto::extract(const QByteArray &key, const QByteArray &salt, int len) { return hkdf(key, salt, {}, len, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY); } -std::unique_ptr Crypto::fromPUBKeyDer(const QByteArray &key) -{ - const auto *p = pcuchar(key.data()); - return SCOPE(EVP_PKEY, d2i_PUBKEY(nullptr, &p, long(key.length()))); -} - -std::unique_ptr Crypto::fromECPublicKeyDer(const QByteArray &key, int curveName) -{ - EVP_PKEY *params = nullptr; - if(auto ctx = SCOPE(EVP_PKEY_CTX, EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr)); - !ctx || - isError(EVP_PKEY_paramgen_init(ctx.get())) || - isError(EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), curveName)) || - isError(EVP_PKEY_CTX_set_ec_param_enc(ctx.get(), OPENSSL_EC_NAMED_CURVE)) || - isError(EVP_PKEY_paramgen(ctx.get(), ¶ms))) - return SCOPE(EVP_PKEY, nullptr); - const auto *p = pcuchar(key.constData()); - return SCOPE(EVP_PKEY, d2i_PublicKey(EVP_PKEY_EC, ¶ms, &p, long(key.length()))); -} - -std::unique_ptr Crypto::fromRSAPublicKeyDer(const QByteArray &key) -{ - const auto *p = pcuchar(key.constData()); - return SCOPE(EVP_PKEY, d2i_PublicKey(EVP_PKEY_RSA, nullptr, &p, long(key.length()))); -} - -std::unique_ptr Crypto::genECKey(EVP_PKEY *params) -{ - EVP_PKEY *key = nullptr; - auto ctx = SCOPE(EVP_PKEY_CTX, EVP_PKEY_CTX_new(params, nullptr)); - auto result = SCOPE(EVP_PKEY, nullptr); - if(ctx && - !isError(EVP_PKEY_keygen_init(ctx.get())) && - !isError(EVP_PKEY_keygen(ctx.get(), &key))) - result.reset(key); - return result; -} - -QByteArray Crypto::genKey(const EVP_CIPHER *cipher) -{ -#ifdef WIN32 - RAND_poll(); -#else - RAND_load_file("/dev/urandom", 1024); -#endif - QByteArray iv(EVP_CIPHER_iv_length(cipher), 0); - QByteArray key(EVP_CIPHER_key_length(cipher), 0); - std::array salt{}; - std::array indata{}; - RAND_bytes(salt.data(), int(salt.size())); - RAND_bytes(indata.data(), int(indata.size())); - if(isError(EVP_BytesToKey(cipher, EVP_sha256(), salt.data(), indata.data(), - int(indata.size()), 1, puchar(key.data()), puchar(iv.data())))) - return {}; - return key; -} - QByteArray Crypto::hkdf(const QByteArray &key, const QByteArray &salt, const QByteArray &info, int len, int mode) { - auto ctx = SCOPE(EVP_PKEY_CTX, EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr)); + auto ctx = libcdoc::make_unique_ptr(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr)); QByteArray out(len, 0); auto outlen = size_t(out.length()); if(!ctx || @@ -323,40 +109,6 @@ bool Crypto::isError(int err) return err < 1; } -QByteArray Crypto::sign_hmac(const QByteArray &key, const QByteArray &data) -{ - EVP_PKEY *pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, nullptr, puchar(key.data()), int(key.size())); - size_t req = 0; - auto ctx = SCOPE(EVP_MD_CTX, EVP_MD_CTX_new()); - if(!ctx || - isError(EVP_DigestSignInit(ctx.get(), nullptr, EVP_sha256(), nullptr, pkey)) || - isError(EVP_DigestSignUpdate(ctx.get(), data.data(), size_t(data.length()))) || - isError(EVP_DigestSignFinal(ctx.get(), nullptr, &req))) - return {}; - QByteArray sig(int(req), 0); - if(isError(EVP_DigestSignFinal(ctx.get(), puchar(sig.data()), &req))) - sig.clear(); - return sig; -} - -QByteArray Crypto::toPublicKeyDer(EVP_PKEY *key) -{ - if(!key) - return {}; - QByteArray der(i2d_PublicKey(key, nullptr), 0); - auto *p = puchar(der.data()); - if(i2d_PublicKey(key, &p) != der.size()) - der.clear(); - return der; -} - -QByteArray Crypto::toPublicKeyDer(const QSslKey &key) -{ - if(auto publicKey = fromPUBKeyDer(key.toDer())) - return toPublicKeyDer(publicKey.get()); - return {}; -} - QByteArray Crypto::random(int len) { QByteArray out(len, 0); @@ -364,24 +116,3 @@ QByteArray Crypto::random(int len) out.clear(); return out; } - -QByteArray Crypto::xor_data(const QByteArray &a, const QByteArray &b) -{ - if(a.length() != b.length()) - return {}; - QByteArray result = a; - for(int i = 0; i < a.length(); ++i) - result[i] = char(a[i] ^ b[i]); - return result; -} - -QByteArray -Crypto::pbkdf2_sha256(const QByteArray& pw, const QByteArray& salt, uint32_t iter) -{ - QByteArray key(32, 0); - PKCS5_PBKDF2_HMAC(pw.data(), pw.length(), - (const unsigned char *) salt.data(), int(salt.length()), - iter, EVP_sha256(), int(key.size()), (unsigned char *)key.data()); - return key; -} - diff --git a/client/Crypto.h b/client/Crypto.h index 5ea04f800..58f1f610a 100644 --- a/client/Crypto.h +++ b/client/Crypto.h @@ -22,56 +22,19 @@ #include #include -#include - -using EVP_CIPHER = struct evp_cipher_st; -using EVP_CIPHER_CTX = struct evp_cipher_ctx_st; using EVP_PKEY = struct evp_pkey_st; -using puchar = uchar *; -using pcuchar = const uchar *; -class QSslKey; - -#define SCOPE(TYPE, DATA) std::unique_ptr(static_cast(DATA), TYPE##_free) Q_DECLARE_LOGGING_CATEGORY(CRYPTO) class Crypto { public: - struct Cipher { - std::unique_ptr ctx; - Cipher(const EVP_CIPHER *cipher, const QByteArray &key, const QByteArray &iv, bool encrypt = true); - bool updateAAD(const QByteArray &data) const; - QByteArray update(const QByteArray &data) const; - bool update(char *data, int size) const; - bool result() const; - QByteArray tag() const; - static constexpr int tagLen() { return 16; } - bool setTag(const QByteArray &data) const; - }; - - static QByteArray aes_wrap(const QByteArray &key, const QByteArray &data); - static QByteArray aes_unwrap(const QByteArray &key, const QByteArray &data); - static QByteArray cipher(const EVP_CIPHER *cipher, const QByteArray &key, QByteArray &data, bool encrypt); static QByteArray curve_oid(EVP_PKEY *key); static QByteArray concatKDF(QCryptographicHash::Algorithm digestMethod, const QByteArray &z, const QByteArray &otherInfo); - static QByteArray derive(EVP_PKEY *priv, EVP_PKEY *pub); - static QByteArray encrypt(EVP_PKEY *pub, int padding, const QByteArray &data); - static QByteArray expand(const QByteArray &key, const QByteArray &info, int len = 32); static QByteArray extract(const QByteArray &key, const QByteArray &salt, int len = 32); - static std::unique_ptr fromPUBKeyDer(const QByteArray &key); - static std::unique_ptr fromECPublicKeyDer(const QByteArray &key, int curveName); - static std::unique_ptr fromRSAPublicKeyDer(const QByteArray &key); - static std::unique_ptr genECKey(EVP_PKEY *params); - static QByteArray genKey(const EVP_CIPHER *cipher); - static QByteArray hkdf(const QByteArray &key, const QByteArray &salt, const QByteArray &info, int len = 32, int mode = 0); - static QByteArray sign_hmac(const QByteArray &key, const QByteArray &data); - static QByteArray toPublicKeyDer(EVP_PKEY *key); - static QByteArray toPublicKeyDer(const QSslKey &key); static QByteArray random(int len = 32); - static QByteArray xor_data(const QByteArray &a, const QByteArray &b); - static QByteArray pbkdf2_sha256(const QByteArray& pw, const QByteArray& salt, uint32_t iter); private: + static QByteArray hkdf(const QByteArray &key, const QByteArray &salt, const QByteArray &info, int len = 32, int mode = 0); static bool isError(int err); }; diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index f2ff2ff78..4fe1d373c 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -152,8 +152,8 @@ QWidget* AddressItem::lastTabWidget() } void AddressItem::mouseReleaseEvent(QMouseEvent * /*event*/) { - if (ui->key.rcpt_cert.isNull() && !ui->key.lock.isValid()) - (new KeyDialog(ui->key))->open(); + if (!ui->key.rcpt_cert.isNull()) + (new KeyDialog(ui->key, this))->open(); } void AddressItem::setName() From f8d124fd50291a576bbf32225c52e89946baba9f Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Mon, 5 Jan 2026 15:13:56 +0200 Subject: [PATCH 20/44] Merge fixes Signed-off-by: Lauris Kaplinski --- client/MainWindow.cpp | 21 --------------------- client/QPKCS11_p.h | 6 +++++- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index f59e371e9..413715637 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -968,27 +968,6 @@ void MainWindow::updateSelectorData(TokenData data) }); } -void MainWindow::containerSummary() -{ -#ifdef Q_OS_WIN - if( QPrinterInfo::availablePrinterNames().isEmpty() ) - { - WarningDialog::show(this, - tr("In order to view Validity Confirmation Sheet there has to be at least one printer installed!")); - return; - } -#endif - auto *dialog = new QPrintPreviewDialog( this ); - dialog->printer()->setPageSize( QPageSize( QPageSize::A4 ) ); - dialog->printer()->setPageOrientation( QPageLayout::Portrait ); - dialog->setMinimumHeight( 700 ); - connect(dialog, &QPrintPreviewDialog::paintRequested, digiDoc, [this](QPrinter *printer) { - PrintSheet(digiDoc, printer); - }); - dialog->exec(); - dialog->deleteLater(); -} - void MainWindow::decryptClicked(const libcdoc::Lock *lock) { diff --git a/client/QPKCS11_p.h b/client/QPKCS11_p.h index c80e6b21f..bdac5a27f 100644 --- a/client/QPKCS11_p.h +++ b/client/QPKCS11_p.h @@ -24,10 +24,11 @@ #include "pkcs11.h" #include +#include #include -class QPKCS11::Private: public QObject +class QPKCS11::Private: public QThread { Q_OBJECT public: @@ -40,4 +41,7 @@ class QPKCS11::Private: public QObject CK_SESSION_HANDLE session = 0; QByteArray id; bool isPSS = false; + + void run() override; + CK_RV result = CKR_OK; }; From c79e04ede2c7aa9fa95c24cf4bce866d14f07db5 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Mon, 5 Jan 2026 15:23:30 +0200 Subject: [PATCH 21/44] Merge fixes Signed-off-by: Lauris Kaplinski --- client/CDocSupport.h | 1 + client/CMakeLists.txt | 2 -- client/QPKCS11.cpp | 5 +++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/CDocSupport.h b/client/CDocSupport.h index d5c905714..deadace84 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -21,6 +21,7 @@ */ #include +#include #include #include diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 886094d50..f35d19e15 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,5 +1,3 @@ -set(CMAKE_CXX_STANDARD 20) - get_target_property(qtCore_install_prefix Qt6::qmake IMPORTED_LOCATION) get_filename_component(qtCore_install_prefix ${qtCore_install_prefix} DIRECTORY) get_filename_component(TSL_FILENAME ${TSL_URL} NAME_WLE) diff --git a/client/QPKCS11.cpp b/client/QPKCS11.cpp index 40fd8ed0a..40f27e036 100644 --- a/client/QPKCS11.cpp +++ b/client/QPKCS11.cpp @@ -38,6 +38,11 @@ static QString toQString(const Container &c) return QString::fromLatin1((const char*)std::data(c), std::size(c)); } +void QPKCS11::Private::run() +{ + result = f->C_Login(session, CKU_USER, nullptr, 0); +} + QByteArray QPKCS11::Private::attribute(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type) const { QByteArray data; From 984d297cfbd25e21c4495b58d8a98a070f1a6bdd Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 5 Jan 2026 15:24:38 +0200 Subject: [PATCH 22/44] Dev Signed-off-by: Raul Metsma --- client/CDocSupport.cpp | 7 ++++--- client/CDocSupport.h | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 4868d0440..1df9b60ab 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -61,7 +61,8 @@ CDocSupport::getCDocFileList(QString filename) int version = libcdoc::CDocReader::getCDocFileVersion(filename.toStdString()); if (version != 1) return files; QFile ifs(filename); - ifs.open(QIODevice::ReadOnly); + if(!ifs.open(QIODevice::ReadOnly)) + return files; QXmlStreamReader xml(&ifs); while (xml.readNextStartElement()) { if (xml.name() == QStringLiteral("EncryptedData")) { @@ -315,7 +316,7 @@ TempListConsumer::~TempListConsumer() } } -libcdoc::result_t TempListConsumer::write(const uint8_t *src, size_t size) { +libcdoc::result_t TempListConsumer::write(const uint8_t *src, size_t size) noexcept { if (files.empty()) return libcdoc::OUTPUT_ERROR; IOEntry &file = files.back(); @@ -363,7 +364,7 @@ StreamListSource::StreamListSource(const std::vector& files) : _files(f } libcdoc::result_t -StreamListSource::read(uint8_t *dst, size_t size) +StreamListSource::read(uint8_t *dst, size_t size) noexcept { if ((_current < 0) || (_current >= _files.size())) return 0; return _files[_current].data->read((char *) dst, size); diff --git a/client/CDocSupport.h b/client/CDocSupport.h index d5c905714..d554e3a97 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -150,7 +150,7 @@ struct TempListConsumer : public libcdoc::MultiDataConsumer { : _max_memory_size(max_memory_size) {} ~TempListConsumer(); - libcdoc::result_t write(const uint8_t *src, size_t size) override final; + libcdoc::result_t write(const uint8_t *src, size_t size) noexcept final; libcdoc::result_t close() override final; bool isError() override final; libcdoc::result_t open(const std::string &name, @@ -163,7 +163,7 @@ struct TempListConsumer : public libcdoc::MultiDataConsumer { struct StreamListSource : public libcdoc::MultiDataSource { StreamListSource(const std::vector &files); - int64_t read(uint8_t *dst, size_t size) override final; + libcdoc::result_t read(uint8_t *dst, size_t size) noexcept final; bool isError() override final; bool isEof() override final; libcdoc::result_t getNumComponents() override final; From 7277c9bc5282354373448bbc3964a0d8774f9858 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 7 Jan 2026 13:28:35 +0200 Subject: [PATCH 23/44] Update to latest versions Signed-off-by: Lauris Kaplinski --- client/CryptoDoc.cpp | 88 +++++++++++++++++++------------------------- client/libcdoc | 2 +- 2 files changed, 39 insertions(+), 51 deletions(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 45a42c5fb..4c54f4e08 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -89,17 +89,50 @@ class CryptoDoc::Private final: public QThread return result; } - inline libcdoc::result_t encrypt(unsigned int lock_idx) { - libcdoc::result_t result = waitFor([&]{ - libcdoc::result_t result = encrypt(); - qDebug() << "Encryption result: " << result << " " << reader->getLastErrorStr(); + inline libcdoc::result_t encrypt() { + libcdoc::result_t res = waitFor([&]{ + qCDebug(CRYPTO) << "Encrypt" << fileName; + libcdoc::OStreamConsumer ofs(fileName.toStdString()); + if (ofs.isError()) + return (libcdoc::result_t) libcdoc::OUTPUT_ERROR; + StreamListSource slsrc(files); + std::vector enc_keys; + std::string keyserver_id; + if (Settings::CDOC2_DEFAULT && Settings::CDOC2_USE_KEYSERVER) { + keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; + } + for (auto &key : keys) { + QByteArray ba = key.rcpt_cert.toDer(); + if (keyserver_id.empty()) { + enc_keys.push_back( + libcdoc::Recipient::makeCertificate({}, {ba.cbegin(), ba.cend()})); + } else { + enc_keys.push_back( + libcdoc::Recipient::makeServer({}, {ba.cbegin(), ba.cend()}, keyserver_id)); + } + } + if (!crypto.secret.empty()) { + auto key = + libcdoc::Recipient::makeSymmetric(label.toStdString(), kdf_iter); + enc_keys.push_back(key); + } + libcdoc::CDocWriter *writer = libcdoc::CDocWriter::createWriter( + Settings::CDOC2_DEFAULT ? 2 : 1, &ofs, false, &conf, &crypto, &network); + libcdoc::result_t result = writer->encrypt(slsrc, enc_keys); + if (result != libcdoc::OK) { + writer_last_error = writer->getLastErrorStr(); + std::filesystem::remove(std::filesystem::path(fileName.toStdString())); + } + qDebug() << "Encryption result: " << result << " " << writer->getLastErrorStr(); + delete writer; + ofs.close(); if (result == libcdoc::OK) { // Encryption successful, open new reader reader = createCDocReader(fileName.toStdString()); } return result; }); - return result; + return res; } std::unique_ptr reader; @@ -136,7 +169,6 @@ class CryptoDoc::Private final: public QThread } return std::unique_ptr(r); } - libcdoc::result_t encrypt(); }; bool CryptoDoc::Private::warnIfNotWritable() const @@ -160,49 +192,6 @@ void CryptoDoc::Private::run() } } -libcdoc::result_t -CryptoDoc::Private::encrypt() { - qCDebug(CRYPTO) << "Encrypt" << fileName; - - libcdoc::OStreamConsumer ofs(fileName.toStdString()); - if (ofs.isError()) - return libcdoc::OUTPUT_ERROR; - - StreamListSource slsrc(files); - std::vector enc_keys; - - std::string keyserver_id; - if (Settings::CDOC2_DEFAULT && Settings::CDOC2_USE_KEYSERVER) { - keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; - } - for (auto &key : keys) { - QByteArray ba = key.rcpt_cert.toDer(); - if (keyserver_id.empty()) { - enc_keys.push_back( - libcdoc::Recipient::makeCertificate({}, {ba.cbegin(), ba.cend()})); - } else { - enc_keys.push_back( - libcdoc::Recipient::makeServer({}, {ba.cbegin(), ba.cend()}, keyserver_id)); - } - } - if (!crypto.secret.empty()) { - auto key = - libcdoc::Recipient::makeSymmetric(label.toStdString(), kdf_iter); - enc_keys.push_back(key); - } - - libcdoc::CDocWriter *writer = libcdoc::CDocWriter::createWriter( - Settings::CDOC2_DEFAULT ? 2 : 1, &ofs, false, &conf, &crypto, &network); - int result = writer->encrypt(slsrc, enc_keys); - if (result != libcdoc::OK) { - writer_last_error = writer->getLastErrorStr(); - std::filesystem::remove(std::filesystem::path(fileName.toStdString())); - } - delete writer; - ofs.close(); - return result; -} - CDocumentModel::CDocumentModel(CryptoDoc::Private *doc) : d(doc) {} bool CDocumentModel::addFile(const QString &file, const QString &mime) @@ -482,7 +471,6 @@ bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QB d->label.clear(); d->crypto.secret.clear(); if (result != libcdoc::OK) { - const std::string &msg = d->reader->getLastErrorStr(); WarningDialog::show(tr("Failed to encrypt document"), QString::fromStdString(d->writer_last_error)); } diff --git a/client/libcdoc b/client/libcdoc index 2c70edb16..f33f41ae1 160000 --- a/client/libcdoc +++ b/client/libcdoc @@ -1 +1 @@ -Subproject commit 2c70edb1600160ea50c1dd3045ef7ae3c803a8e1 +Subproject commit f33f41ae1409d66f116550fd5de1a50922566e65 From 22be11a1ba909aaea5055c7dcd76e3f8e4e0b58c Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 7 Jan 2026 13:41:36 +0200 Subject: [PATCH 24/44] Fix Ubuntu compilation Signed-off-by: Lauris Kaplinski --- client/CryptoDoc.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index a36da8df2..3aad013fc 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -72,11 +72,11 @@ class CryptoDoc::Private final: public QThread libcdoc::result_t result = waitFor([&]{ std::vector fmk; libcdoc::result_t result = reader->getFMK(fmk, lock_idx); - qDebug() << "getFMK result: " << result << " " << reader->getLastErrorStr(); + qDebug() << "getFMK result: " << result << " " << QString::fromStdString(reader->getLastErrorStr()); if (result != libcdoc::OK) return result; result = reader->decrypt(fmk, &cons); std::fill(fmk.begin(), fmk.end(), 0); - qDebug() << "Decryption result: " << result << " " << reader->getLastErrorStr(); + qDebug() << "Decryption result: " << result << " " << QString::fromStdString(reader->getLastErrorStr()); return result; }); if (result == libcdoc::OK) { @@ -123,7 +123,7 @@ class CryptoDoc::Private final: public QThread writer_last_error = writer->getLastErrorStr(); std::filesystem::remove(std::filesystem::path(fileName.toStdString())); } - qDebug() << "Encryption result: " << result << " " << writer->getLastErrorStr(); + qDebug() << "Encryption result: " << result << " " << QString::fromStdString(writer->getLastErrorStr()); delete writer; ofs.close(); if (result == libcdoc::OK) { From b9430d288c0f5fcaea1d364b8f08b0df9fa0616d Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Tue, 20 Jan 2026 14:10:21 +0200 Subject: [PATCH 25/44] Mark functions noexcept that cannot throw Signed-off-by: Raul Metsma --- client/CDocSupport.cpp | 10 +++++----- client/CDocSupport.h | 8 ++++---- client/libcdoc | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 1df9b60ab..b75d20f88 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -328,7 +328,7 @@ libcdoc::result_t TempListConsumer::write(const uint8_t *src, size_t size) noexc return size; } -libcdoc::result_t TempListConsumer::close() { +libcdoc::result_t TempListConsumer::close() noexcept { if (files.empty()) return libcdoc::OUTPUT_ERROR; IOEntry &file = files.back(); @@ -338,10 +338,10 @@ libcdoc::result_t TempListConsumer::close() { } bool -TempListConsumer::isError() +TempListConsumer::isError() noexcept { if (files.empty()) return false; - IOEntry& file = files.back(); + const IOEntry& file = files.back(); return !file.data->isWritable(); } @@ -371,14 +371,14 @@ StreamListSource::read(uint8_t *dst, size_t size) noexcept } bool -StreamListSource::isError() +StreamListSource::isError() noexcept { if ((_current < 0) || (_current >= _files.size())) return 0; return _files[_current].data->isReadable(); } bool -StreamListSource::isEof() +StreamListSource::isEof() noexcept { if (_current < 0) return false; if (_current >= _files.size()) return true; diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 6e64e70c0..f7a64777b 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -152,8 +152,8 @@ struct TempListConsumer : public libcdoc::MultiDataConsumer { ~TempListConsumer(); libcdoc::result_t write(const uint8_t *src, size_t size) noexcept final; - libcdoc::result_t close() override final; - bool isError() override final; + libcdoc::result_t close() noexcept final; + bool isError() noexcept final; libcdoc::result_t open(const std::string &name, int64_t size) override final; @@ -165,8 +165,8 @@ struct StreamListSource : public libcdoc::MultiDataSource { StreamListSource(const std::vector &files); libcdoc::result_t read(uint8_t *dst, size_t size) noexcept final; - bool isError() override final; - bool isEof() override final; + bool isError() noexcept final; + bool isEof() noexcept final; libcdoc::result_t getNumComponents() override final; libcdoc::result_t next(std::string &name, int64_t &size) override final; diff --git a/client/libcdoc b/client/libcdoc index f33f41ae1..7f658d21f 160000 --- a/client/libcdoc +++ b/client/libcdoc @@ -1 +1 @@ -Subproject commit f33f41ae1409d66f116550fd5de1a50922566e65 +Subproject commit 7f658d21fa28f78912197ace82f9fbeb18ae975a From 7245985ac7696cd79d5d3c467ce5a137db0f7122 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 28 Jan 2026 13:15:02 +0200 Subject: [PATCH 26/44] Compilation fixes --- client/CryptoDoc.cpp | 6 ++++++ client/MainWindow.cpp | 21 --------------------- client/widgets/ContainerPage.cpp | 2 +- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 3403196a9..c56d0b969 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -548,6 +548,12 @@ void CryptoDoc::removeKey(unsigned int id) d->keys.erase(d->keys.begin() + id); } +void CryptoDoc::clearKeys() +{ + if(!d->isEncryptedWarning(tr("Failed to remove key"))) + d->keys.clear(); +} + bool CryptoDoc::saveCopy(const QString &filename) { if(QFileInfo(filename) == QFileInfo(d->fileName)) diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index 287f0ee0a..5c97aa3e8 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -463,7 +463,6 @@ void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); } break; - } case EncryptContainer: if(encrypt()) { @@ -477,26 +476,6 @@ void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString FadeInNotification::success(ui->topBar, tr("Encryption succeeded!")); } break; - case ContainerSaveAs: - { - if(!cryptoDoc) - break; - QString target = FileDialog::getSaveFileName(this, tr("Save file"), cryptoDoc->fileName()); - if(target.isEmpty()) - break; - if( !FileDialog::fileIsWritable(target)) - { - auto *dlg = new WarningDialog(tr("Cannot alter container %1. Save different location?").arg(target), this); - dlg->addButton(WarningDialog::YES, QMessageBox::Yes); - if(dlg->exec() == QMessageBox::Yes) { - QString file = FileDialog::getSaveFileName(this, tr("Save file"), target); - if(!file.isEmpty()) - cryptoDoc->saveCopy(file); - } - } - cryptoDoc->saveCopy(target); - break; - } case ClearCryptoWarning: ui->crypto->warningIcon(false); ui->warnings->closeWarnings(CryptoDetails); diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index a2b0ef76e..c022ce913 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -332,7 +332,7 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert if (hasUnsupported) emit warning({UnsupportedCDocWarning}); ui->leftPane->setModel(container->documentModel()); - updatePanes(container->state()); + updatePanes(container->state(), nullptr); } void ContainerPage::transition(DigiDoc* container) From ea84c2564e0136dafb4f86232f76d35fc4e1d118 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 29 Jan 2026 15:11:49 +0200 Subject: [PATCH 27/44] Removed waitFor from sendKey implementation --- client/CDocSupport.cpp | 22 +++++++++++++++------- client/CryptoDoc.cpp | 1 + 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index b75d20f88..4e8277f45 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -203,6 +203,7 @@ libcdoc::result_t DDNetworkBackend::sendKey( libcdoc::NetworkBackend::CapsuleInfo &dst, const std::string &url, const std::vector &rcpt_key, const std::vector &key_material, const std::string &type, uint64_t expiry_ts) { + qDebug() << "sendKey, thread id: " << QThread::currentThreadId(); QNetworkRequest req(QString::fromStdString(url + "/key-capsules")); req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); if (expiry_ts) { @@ -213,20 +214,27 @@ libcdoc::result_t DDNetworkBackend::sendKey( return BACKEND_ERROR; } QScopedPointer nam(CheckConnection::setupNAM(req, Settings::CDOC2_POST_CERT)); - QNetworkReply *reply = waitFor(qOverload(&QNetworkAccessManager::post), nam.get(), req, QJsonDocument({ + QNetworkReply *reply = nam->post(req, QJsonDocument({ {QLatin1String("recipient_id"), QLatin1String(toByteArray(rcpt_key).toBase64())}, {QLatin1String("ephemeral_key_material"), QLatin1String(toByteArray(key_material).toBase64())}, {QLatin1String("capsule_type"), QLatin1String(type.c_str())}, }).toJson()); - QString tr_id; - if (reply->error() == QNetworkReply::NoError && - reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 201) { - tr_id = QString::fromLatin1(reply->rawHeader("Location")) - .remove(QLatin1String("/key-capsules/")); - } else { + QNetworkReply::NetworkError n_err = reply->error(); + if (n_err != QNetworkReply::NoError) { last_error = reply->errorString().toStdString(); return BACKEND_ERROR; } + QByteArrayList lst = reply->rawHeaderList(); + for (auto hdr : lst) { + qDebug() << QString::fromUtf8(hdr); + } + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != 201) { + last_error = reply->errorString().toStdString(); + return BACKEND_ERROR; + } + QString tr_id; + tr_id = QString::fromLatin1(reply->rawHeader("Location")).remove(QLatin1String("/key-capsules/")); if (tr_id.isEmpty()) { last_error = "Failed to post key capsule"; return BACKEND_ERROR; diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index c56d0b969..f85b0287f 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -94,6 +94,7 @@ class CryptoDoc::Private final: public QThread inline libcdoc::result_t encrypt() { libcdoc::result_t res = waitFor([&]{ + qDebug() << "CryptoDoc::Private::encrypt, thread id: " << QThread::currentThreadId(); qCDebug(CRYPTO) << "Encrypt" << fileName; libcdoc::OStreamConsumer ofs(fileName.toStdString()); if (ofs.isError()) From f34e3a1c8cc06a67fd560100fb0650dc66eddabd Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 29 Jan 2026 15:38:07 +0200 Subject: [PATCH 28/44] Fixed encrypting with keyserver --- client/CDocSupport.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 4e8277f45..7673edfa4 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -203,7 +203,6 @@ libcdoc::result_t DDNetworkBackend::sendKey( libcdoc::NetworkBackend::CapsuleInfo &dst, const std::string &url, const std::vector &rcpt_key, const std::vector &key_material, const std::string &type, uint64_t expiry_ts) { - qDebug() << "sendKey, thread id: " << QThread::currentThreadId(); QNetworkRequest req(QString::fromStdString(url + "/key-capsules")); req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); if (expiry_ts) { @@ -219,15 +218,14 @@ libcdoc::result_t DDNetworkBackend::sendKey( {QLatin1String("ephemeral_key_material"), QLatin1String(toByteArray(key_material).toBase64())}, {QLatin1String("capsule_type"), QLatin1String(type.c_str())}, }).toJson()); + QEventLoop e; + connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); + e.exec(); QNetworkReply::NetworkError n_err = reply->error(); if (n_err != QNetworkReply::NoError) { last_error = reply->errorString().toStdString(); return BACKEND_ERROR; } - QByteArrayList lst = reply->rawHeaderList(); - for (auto hdr : lst) { - qDebug() << QString::fromUtf8(hdr); - } int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (status != 201) { last_error = reply->errorString().toStdString(); From 0a0895e49074a099dcfeaa6b0c3b7906d25a65eb Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 30 Jan 2026 10:16:00 +0200 Subject: [PATCH 29/44] CodeQL fix --- client/CryptoDoc.cpp | 2 +- client/libcdoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index f85b0287f..4d5ac2b48 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -453,7 +453,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) WarningDialog::create() ->withTitle(QSigner::tr("Failed to decrypt document")) ->withText(tr("Please check your internet connection and network settings.")) - ->withDetails(QString::fromStdString(d->reader->getLastErrorStr())) + ->withDetails(QString::fromStdString(msg)) ->open(); return false; } diff --git a/client/libcdoc b/client/libcdoc index 7f658d21f..fc41f1d31 160000 --- a/client/libcdoc +++ b/client/libcdoc @@ -1 +1 @@ -Subproject commit 7f658d21fa28f78912197ace82f9fbeb18ae975a +Subproject commit fc41f1d31c1e242cbd0454407f9b1153effbd1b8 From 52e89bc0666cfc5e5ca7bbd0b902648595abcdba Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 30 Jan 2026 10:43:59 +0200 Subject: [PATCH 30/44] Remove Crypto class Signed-off-by: Raul Metsma --- client/CMakeLists.txt | 2 - client/Crypto.cpp | 118 ------------------------------ client/Crypto.h | 40 ---------- client/CryptoDoc.cpp | 13 ++-- client/CryptoDoc.h | 3 + client/QPKCS11.cpp | 54 +++++++++++++- client/SslCertificate.cpp | 34 ++++++--- client/dialogs/PasswordDialog.cpp | 11 ++- 8 files changed, 91 insertions(+), 184 deletions(-) delete mode 100644 client/Crypto.cpp delete mode 100644 client/Crypto.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 01da1833b..9e97faf2a 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -44,8 +44,6 @@ add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE Application.h CheckConnection.cpp CheckConnection.h - Crypto.cpp - Crypto.h CryptoDoc.cpp CryptoDoc.h DateTime.cpp diff --git a/client/Crypto.cpp b/client/Crypto.cpp deleted file mode 100644 index 5b1127ad4..000000000 --- a/client/Crypto.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - * QDigiDocClient - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "Crypto.h" - -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include - -using puchar = uchar *; -using pcuchar = const uchar *; - -Q_LOGGING_CATEGORY(CRYPTO,"CRYPTO") - -QByteArray Crypto::concatKDF(QCryptographicHash::Algorithm hashAlg, const QByteArray &z, const QByteArray &otherInfo) -{ - if(z.isEmpty()) - return z; - quint32 keyDataLen = 32; - auto hashLen = quint32(QCryptographicHash::hashLength(hashAlg)); - auto reps = quint32(std::ceil(double(keyDataLen) / double(hashLen))); - QCryptographicHash md(hashAlg); - QByteArray key; - for(quint32 i = 1; i <= reps; i++) - { - quint32 intToFourBytes = qToBigEndian(i); - md.reset(); - md.addData((const char*)&intToFourBytes, 4); - md.addData(z); - md.addData(otherInfo); - key += md.result(); - } - return key.left(int(keyDataLen)); -} - -QByteArray Crypto::curve_oid(EVP_PKEY *key) -{ - QByteArray buf(50, 0); - std::array group{}; - size_t size = group.size(); - if(EVP_PKEY_get_utf8_string_param(key, OSSL_PKEY_PARAM_GROUP_NAME, group.data(), group.size(), &size) != 1) - { - buf.clear(); - return buf; - } - ASN1_OBJECT *obj = OBJ_nid2obj(OBJ_sn2nid(group.data())); - if(int size = OBJ_obj2txt(buf.data(), buf.size(), obj, 1); size != NID_undef) - buf.resize(size); - else - buf.clear(); - return buf; -} - -QByteArray Crypto::extract(const QByteArray &key, const QByteArray &salt, int len) -{ - return hkdf(key, salt, {}, len, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY); -} - -QByteArray Crypto::hkdf(const QByteArray &key, const QByteArray &salt, const QByteArray &info, int len, int mode) -{ - auto ctx = libcdoc::make_unique_ptr(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr)); - QByteArray out(len, 0); - auto outlen = size_t(out.length()); - if(!ctx || - isError(EVP_PKEY_derive_init(ctx.get())) || - isError(EVP_PKEY_CTX_hkdf_mode(ctx.get(), mode)) || - isError(EVP_PKEY_CTX_set_hkdf_md(ctx.get(), EVP_sha256())) || - isError(EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), pcuchar(key.data()), int(key.size()))) || - isError(EVP_PKEY_CTX_set1_hkdf_salt(ctx.get(), pcuchar(salt.data()), int(salt.size()))) || - isError(EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), pcuchar(info.data()), int(info.size()))) || - isError(EVP_PKEY_derive(ctx.get(), puchar(out.data()), &outlen))) - return {}; - return out; -} - -bool Crypto::isError(int err) -{ - if(err < 1) - { - unsigned long errorCode = 0; - while((errorCode = ERR_get_error())) - qCWarning(CRYPTO) << ERR_error_string(errorCode, nullptr); - } - return err < 1; -} - -QByteArray Crypto::random(int len) -{ - QByteArray out(len, 0); - if(isError(RAND_bytes(puchar(out.data()), int(out.size())))) - out.clear(); - return out; -} diff --git a/client/Crypto.h b/client/Crypto.h deleted file mode 100644 index 58f1f610a..000000000 --- a/client/Crypto.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * QDigiDocClient - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#pragma once - -#include -#include - -using EVP_PKEY = struct evp_pkey_st; - -Q_DECLARE_LOGGING_CATEGORY(CRYPTO) - -class Crypto -{ -public: - static QByteArray curve_oid(EVP_PKEY *key); - static QByteArray concatKDF(QCryptographicHash::Algorithm digestMethod, const QByteArray &z, const QByteArray &otherInfo); - static QByteArray extract(const QByteArray &key, const QByteArray &salt, int len = 32); - static QByteArray random(int len = 32); - -private: - static QByteArray hkdf(const QByteArray &key, const QByteArray &salt, const QByteArray &info, int len = 32, int mode = 0); - static bool isError(int err); -}; diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 4d5ac2b48..77ac5647c 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -20,13 +20,11 @@ #include "CryptoDoc.h" #include "Application.h" -#include "Crypto.h" #include "CDocSupport.h" #include "TokenData.h" #include "QCryptoBackend.h" #include "QSigner.h" #include "Settings.h" -#include "SslCertificate.h" #include "Utils.h" #include "dialogs/FileDialog.h" #include "dialogs/WarningDialog.h" @@ -49,6 +47,8 @@ using namespace ria::qdigidoc4; +Q_LOGGING_CATEGORY(CRYPTO, "CRYPTO") + auto toHex = [](const std::vector& data) -> QString { QByteArray ba(reinterpret_cast(data.data()), data.size()); return ba.toHex(); @@ -75,11 +75,11 @@ class CryptoDoc::Private final: public QThread libcdoc::result_t result = waitFor([&]{ std::vector fmk; libcdoc::result_t result = reader->getFMK(fmk, lock_idx); - qDebug() << "getFMK result: " << result << " " << QString::fromStdString(reader->getLastErrorStr()); + qCDebug(CRYPTO) << "getFMK result: " << result << " " << QString::fromStdString(reader->getLastErrorStr()); if (result != libcdoc::OK) return result; result = reader->decrypt(fmk, &cons); std::fill(fmk.begin(), fmk.end(), 0); - qDebug() << "Decryption result: " << result << " " << QString::fromStdString(reader->getLastErrorStr()); + qCDebug(CRYPTO) << "Decryption result: " << result << " " << QString::fromStdString(reader->getLastErrorStr()); return result; }); if (result == libcdoc::OK) { @@ -94,7 +94,7 @@ class CryptoDoc::Private final: public QThread inline libcdoc::result_t encrypt() { libcdoc::result_t res = waitFor([&]{ - qDebug() << "CryptoDoc::Private::encrypt, thread id: " << QThread::currentThreadId(); + qCDebug(CRYPTO) << "CryptoDoc::Private::encrypt, thread id: " << QThread::currentThreadId(); qCDebug(CRYPTO) << "Encrypt" << fileName; libcdoc::OStreamConsumer ofs(fileName.toStdString()); if (ofs.isError()) @@ -218,7 +218,8 @@ bool CDocumentModel::addFile(const QString &file, const QString &mime) } auto data = std::make_unique(file); - data->open(QFile::ReadOnly); + if(!data->open(QFile::ReadOnly)) + return false; d->files.push_back({ QFileInfo(file).fileName().toStdString(), mime.toStdString(), diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index 72c3db26b..87b696458 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -23,6 +23,7 @@ #include "DocumentModel.h" #include +#include #include #include @@ -32,6 +33,8 @@ class QSslKey; +Q_DECLARE_LOGGING_CATEGORY(CRYPTO) + // // A wrapper structure for UI that contains either: // - lock information for decryption diff --git a/client/QPKCS11.cpp b/client/QPKCS11.cpp index 84fbcea15..0c8375232 100644 --- a/client/QPKCS11.cpp +++ b/client/QPKCS11.cpp @@ -20,7 +20,7 @@ #include "QPKCS11_p.h" #include "Application.h" -#include "Crypto.h" +#include "CryptoDoc.h" #include "QPCSC.h" #include "SslCertificate.h" #include "TokenData.h" @@ -29,8 +29,13 @@ #include +#include + +#include +#include +#include + #include -#include template static QString toQString(const Container &c) @@ -149,12 +154,53 @@ QByteArray QPKCS11::derive(const QByteArray &publicKey) const QByteArray QPKCS11::deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const { - return Crypto::concatKDF(digest, derive(publicKey), algorithmID + partyUInfo + partyVInfo); + QByteArray z = derive(publicKey); + if(z.isEmpty()) + return z; + QByteArray otherInfo = algorithmID + partyUInfo + partyVInfo; + quint32 keyDataLen = 32; + auto hashLen = quint32(QCryptographicHash::hashLength(digest)); + auto reps = quint32(std::ceil(double(keyDataLen) / double(hashLen))); + QCryptographicHash md(digest); + QByteArray key; + for(quint32 i = 1; i <= reps; i++) + { + quint32 intToFourBytes = qToBigEndian(i); + md.reset(); + md.addData((const char*)&intToFourBytes, 4); + md.addData(z); + md.addData(otherInfo); + key += md.result(); + } + return key.left(int(keyDataLen)); } QByteArray QPKCS11::deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const { - return Crypto::extract(derive(publicKey), salt, keySize); + QByteArray key = derive(publicKey); + if(key.isEmpty()) + return key; + 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) { + if(err < 1) + { + unsigned long errorCode = 0; + while((errorCode = ERR_get_error())) + qCWarning(CRYPTO) << ERR_error_string(errorCode, nullptr); + } + return err < 1; + }; + if(!ctx || + isError(EVP_PKEY_derive_init(ctx.get())) || + isError(EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY)) || + isError(EVP_PKEY_CTX_set_hkdf_md(ctx.get(), EVP_sha256())) || + isError(EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), (const unsigned char*)key.data(), int(key.size()))) || + isError(EVP_PKEY_CTX_set1_hkdf_salt(ctx.get(), (const unsigned char*)salt.data(), int(salt.size()))) || + isError(EVP_PKEY_derive(ctx.get(), (unsigned char*)out.data(), &outlen))) + out.clear(); + return out; } bool QPKCS11::isLoaded() const diff --git a/client/SslCertificate.cpp b/client/SslCertificate.cpp index f262aa76e..b33af953e 100644 --- a/client/SslCertificate.cpp +++ b/client/SslCertificate.cpp @@ -20,7 +20,6 @@ #include "SslCertificate.h" #include "Common.h" -#include "Crypto.h" #include #include @@ -32,6 +31,7 @@ #include #include +#include #include #include @@ -60,7 +60,8 @@ struct free_argument }; template -constexpr auto make_unique_ptr(T *t) +[[nodiscard]] +constexpr auto make_unique_ptr(T *t) noexcept { return std::unique_ptr>(t); } @@ -71,15 +72,15 @@ static auto toQByteArray(T &x) return QByteArray((const char*)x->data, x->length); } -template -static QByteArray i2dDer(Func func, Arg arg) +template +static QByteArray i2dDer(Arg arg) { + QByteArray der; if(!arg) - return {}; - QByteArray der(func(arg, nullptr), 0); - auto *p = (unsigned char*)der.data(); - if(der.isEmpty() || func(arg, &p) != der.size()) - return {}; + return der; + der.resize(Func(arg, nullptr), 0); + if(auto *p = (unsigned char*)der.data(); der.isEmpty() || Func(arg, &p) != der.size()) + der.clear(); return der; } @@ -109,7 +110,7 @@ template auto SslCertificate::extension(int nid) const { using T = typename free_argument::type; - return std::unique_ptr>(static_cast(handle() ? X509_get_ext_d2i((X509*)handle(), nid, nullptr, nullptr) : nullptr)); + return make_unique_ptr(static_cast(handle() ? X509_get_ext_d2i((X509*)handle(), nid, nullptr, nullptr) : nullptr)); } QMultiHash SslCertificate::authorityInfoAccess() const @@ -188,7 +189,16 @@ QString SslCertificate::keyName() const default: #ifndef OPENSSL_NO_ECDSA if(X509 *c = (X509*)handle()) - return Crypto::curve_oid(X509_get0_pubkey(c)); + { + QByteArray group(64, 0); + size_t size = group.size(); + EVP_PKEY *key = X509_get0_pubkey(c); + if(EVP_PKEY_get_utf8_string_param(key, OSSL_PKEY_PARAM_GROUP_NAME, group.data(), group.size(), &size) == 1) + { + group.resize(size); + return group; + } + } #endif } return tr("Unknown"); @@ -376,7 +386,7 @@ SslCertificate::Validity SslCertificate::validateOnline() const // Send request r.setUrl(urls.values(SslCertificate::ad_OCSP).first()); r.setHeader(QNetworkRequest::ContentTypeHeader, "application/ocsp-request"); - repl = m.post(r, i2dDer(i2d_OCSP_REQUEST, ocspReq.get())); + repl = m.post(r, i2dDer(ocspReq.get())); e.exec(); // Parse response diff --git a/client/dialogs/PasswordDialog.cpp b/client/dialogs/PasswordDialog.cpp index c34dc0074..6f35fa9e0 100644 --- a/client/dialogs/PasswordDialog.cpp +++ b/client/dialogs/PasswordDialog.cpp @@ -1,7 +1,8 @@ #include "PasswordDialog.h" -#include "Crypto.h" #include "ui_PasswordDialog.h" +#include + PasswordDialog::PasswordDialog(QWidget *parent) : QDialog(parent), mode(Mode::DECRYPT), type(Type::PASSWORD) , ui(new Ui::PasswordDialog) @@ -72,7 +73,13 @@ PasswordDialog::editChanged() void PasswordDialog::genKeyClicked() { - QByteArray key = Crypto::random(); + QByteArray key(32, 0); + auto *g = QRandomGenerator::system(); + for (int i = 0; i < key.size(); i += 4) { + const quint32 r = g->generate(); + const int n = qMin(4, key.size() - i); + memcpy(key.data() + i, &r, n); + } ui->keyEdit->clear(); ui->keyEdit->appendPlainText(key.toHex()); } From 2628c0335116008d1a6c2ef255cbb9486091a819 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 30 Jan 2026 11:06:08 +0200 Subject: [PATCH 31/44] Remove unused QThread Signed-off-by: Raul Metsma --- client/CryptoDoc.cpp | 32 ++++---------------------------- client/CryptoDoc.h | 2 +- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 77ac5647c..39edd0f89 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -54,22 +54,9 @@ auto toHex = [](const std::vector& data) -> QString { return ba.toHex(); }; -class CryptoDoc::Private final: public QThread +struct CryptoDoc::Private { - Q_OBJECT -public: - static unsigned int getCDocVersion() { - return (Settings::CDOC2_DEFAULT) ? 2 : 1; - } bool isEncryptedWarning(const QString &title) const; - void run() final; - inline void waitForFinished() - { - QEventLoop e; - connect(this, &Private::finished, &e, &QEventLoop::quit); - start(); - e.exec(); - } inline libcdoc::result_t decrypt(unsigned int lock_idx) { TempListConsumer cons; libcdoc::result_t result = waitFor([&]{ @@ -165,8 +152,8 @@ class CryptoDoc::Private final: public QThread libcdoc::CDocReader *r = libcdoc::CDocReader::createReader(filename, &conf, &crypto, &network); if (!r) { WarningDialog::create() - ->withTitle(tr("Failed to open document")) - ->withText(tr("Unsupported file format")) + ->withTitle(CryptoDoc::tr("Failed to open document")) + ->withText(CryptoDoc::tr("Unsupported file format")) ->open(); return nullptr; } @@ -187,15 +174,6 @@ bool CryptoDoc::Private::isEncryptedWarning(const QString &title) const return fileName.isEmpty() || isEncrypted(); } -void CryptoDoc::Private::run() -{ - if (encrypt() == libcdoc::OK) { - // Encryption successful, open new reader - reader = createCDocReader(fileName.toStdString()); - if (!reader) return; - } -} - CDocumentModel::CDocumentModel(CryptoDoc::Private *doc) : d(doc) {} bool CDocumentModel::addFile(const QString &file, const QString &mime) @@ -207,7 +185,7 @@ bool CDocumentModel::addFile(const QString &file, const QString &mime) if(!addFileCheck(d->fileName, info)) return false; - if(d->getCDocVersion() == 1 && info.size() > 120*1024*1024) + if(d->fileName.last(3) == QLatin1String("cdoc") && info.size() > 120*1024*1024) { WarningDialog::create() ->withTitle(DocumentModel::tr("Failed to add file")) @@ -564,5 +542,3 @@ bool CryptoDoc::saveCopy(const QString &filename) QFile::remove(filename); return QFile::copy(d->fileName, filename); } - -#include "CryptoDoc.moc" diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index 87b696458..3eae9b228 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -71,7 +71,7 @@ class CryptoDoc final: public QObject ria::qdigidoc4::ContainerState state() const; private: - class Private; + struct Private; Private *d; friend class CDocumentModel; From 28abe4be9fa541aa8dc9b077a4da7854527e835d Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 30 Jan 2026 11:43:50 +0200 Subject: [PATCH 32/44] Show Issuer info in key dialog Signed-off-by: Raul Metsma --- client/CryptoDoc.cpp | 10 ++-------- client/dialogs/KeyDialog.cpp | 36 +++++++++++++++--------------------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 39edd0f89..25d722297 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -50,8 +50,7 @@ using namespace ria::qdigidoc4; Q_LOGGING_CATEGORY(CRYPTO, "CRYPTO") auto toHex = [](const std::vector& data) -> QString { - QByteArray ba(reinterpret_cast(data.data()), data.size()); - return ba.toHex(); + return QByteArray::fromRawData(reinterpret_cast(data.data()), data.size()).toHex(); }; struct CryptoDoc::Private @@ -507,13 +506,8 @@ bool CryptoDoc::open(const QString &file) d->writer_last_error.clear(); clear(file); d->reader = d->createCDocReader(file.toStdString()); - if (!d->reader) { - WarningDialog::create() - ->withTitle(tr("Failed to open document")) - ->withDetails(tr("Cannot rerad file or file is not a CDoc container")) - ->open(); + if (!d->reader) return false; - } std::vector files = CDocSupport::getCDocFileList(file); for (auto& f : files) { d->files.push_back({f.name, {}, f.size, {}}); diff --git a/client/dialogs/KeyDialog.cpp b/client/dialogs/KeyDialog.cpp index a6e5edb3b..3a48c1e0f 100644 --- a/client/dialogs/KeyDialog.cpp +++ b/client/dialogs/KeyDialog.cpp @@ -29,6 +29,7 @@ KeyDialog::KeyDialog(const CDKey &k, QWidget *parent ) { Ui::KeyDialog d; d.setupUi(this); + d.showCert->hide(); #if defined (Q_OS_WIN) d.buttonLayout->setDirection(QBoxLayout::RightToLeft); #endif @@ -37,21 +38,6 @@ KeyDialog::KeyDialog(const CDKey &k, QWidget *parent ) new Overlay(this); connect(d.close, &QPushButton::clicked, this, &KeyDialog::accept); - if (!k.rcpt_cert.isNull()) { - connect(d.showCert, &QPushButton::clicked, this, [this, cert = k.rcpt_cert] { - CertificateDetails::showCertificate(cert, this); - }); - d.showCert->setHidden(false); - } else if (k.lock.isCertificate()) { - std::vector cert = k.lock.getBytes(libcdoc::Lock::Params::CERT); - QSslCertificate kcert(QByteArray(reinterpret_cast(cert.data()), cert.size()), QSsl::Der); - connect(d.showCert, &QPushButton::clicked, this, [this, c = kcert] { - CertificateDetails::showCertificate(c, this); - }); - d.showCert->setHidden(kcert.isNull()); - } else { - d.showCert->setHidden(true); - } auto addItem = [view = d.view](const QString ¶meter, const QString &value) { if(value.isEmpty()) @@ -63,16 +49,24 @@ KeyDialog::KeyDialog(const CDKey &k, QWidget *parent ) }; addItem(tr("Recipient"), QString::fromStdString(k.lock.label)); - if (k.lock.isCertificate()) { - std::vector cert = k.lock.getBytes(libcdoc::Lock::Params::CERT); - QSslCertificate kcert(QByteArray(reinterpret_cast(cert.data()), cert.size()), QSsl::Der); + QSslCertificate cert; + if (!k.rcpt_cert.isNull()) { + cert = k.rcpt_cert; + } else if (k.lock.isCertificate()) { + std::vector certData = k.lock.getBytes(libcdoc::Lock::Params::CERT); + cert = QSslCertificate(QByteArray::fromRawData(reinterpret_cast(certData.data()), certData.size()), QSsl::Der); + } + if (!cert.isNull()) { + connect(d.showCert, &QPushButton::clicked, this, [this, cert] { + CertificateDetails::showCertificate(cert, this); + }); + d.showCert->show(); if (k.lock.isCDoc1()) { std::string cdigest = k.lock.getString(libcdoc::Lock::Params::CONCAT_DIGEST); addItem(tr("ConcatKDF digest method"), QString::fromStdString(cdigest)); } - addItem(tr("Expiry date"), kcert.expiryDate().toLocalTime().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"))); - auto iss = kcert.issuerInfo(QSslCertificate::CommonName); - addItem(tr("Issuer"), iss.join(" ")); + addItem(tr("Expiry date"), cert.expiryDate().toLocalTime().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"))); + addItem(tr("Issuer"), cert.issuerInfo(QSslCertificate::CommonName).join(" ")); d.view->resizeColumnToContents(0); } else if (k.lock.type == libcdoc::Lock::SERVER) { addItem(tr("Key server ID"), QString::fromUtf8(k.lock.getString(libcdoc::Lock::Params::KEYSERVER_ID))); From 383e73d6811728a2fec056e46d3a32e93c17a1c5 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 30 Jan 2026 11:46:43 +0200 Subject: [PATCH 33/44] Show certificate dialog from lock --- client/widgets/AddressItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index 4fe1d373c..a472f35ae 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -152,7 +152,7 @@ QWidget* AddressItem::lastTabWidget() } void AddressItem::mouseReleaseEvent(QMouseEvent * /*event*/) { - if (!ui->key.rcpt_cert.isNull()) + if (!ui->key.rcpt_cert.isNull() || ui->key.lock.isCertificate()) (new KeyDialog(ui->key, this))->open(); } From 5bdf2385c267fc09b20bbb3f4e5df42afa85f406 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 30 Jan 2026 12:22:48 +0200 Subject: [PATCH 34/44] Fix build Signed-off-by: Raul Metsma --- client/CryptoDoc.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 25d722297..62b664461 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -148,7 +149,7 @@ struct CryptoDoc::Private return files; } std::unique_ptr createCDocReader(const std::string& filename) { - libcdoc::CDocReader *r = libcdoc::CDocReader::createReader(filename, &conf, &crypto, &network); + std::unique_ptr r(libcdoc::CDocReader::createReader(filename, &conf, &crypto, &network)); if (!r) { WarningDialog::create() ->withTitle(CryptoDoc::tr("Failed to open document")) @@ -160,7 +161,7 @@ struct CryptoDoc::Private for (auto& key : r->getLocks()) { keys.push_back({key, QSslCertificate()}); } - return std::unique_ptr(r); + return r; } }; From 4c4af9072f5871470c9fdd917ffe92c7f2dcbbe9 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 30 Jan 2026 12:36:48 +0200 Subject: [PATCH 35/44] Fix expire date and AddressItem layout Signed-off-by: Raul Metsma --- client/translations/en.ts | 4 ++ client/translations/et.ts | 4 ++ client/translations/ru.ts | 4 ++ client/widgets/AddressItem.cpp | 9 ++-- client/widgets/AddressItem.ui | 96 +++++++++++++++++----------------- 5 files changed, 65 insertions(+), 52 deletions(-) diff --git a/client/translations/en.ts b/client/translations/en.ts index 0b3b1a495..28a22b615 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -101,6 +101,10 @@ Added Added + + Decrypt + Decrypt + Add Add diff --git a/client/translations/et.ts b/client/translations/et.ts index 355d4c340..fc8c08a80 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -101,6 +101,10 @@ Added Lisatud + + Decrypt + Dekrüpteeri + Add Lisa diff --git a/client/translations/ru.ts b/client/translations/ru.ts index 1463122e0..49499c263 100644 --- a/client/translations/ru.ts +++ b/client/translations/ru.ts @@ -101,6 +101,10 @@ Added Добавлен + + Decrypt + Расшифровать + Add Добавить diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index a472f35ae..b33fbb6ec 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -72,13 +72,14 @@ AddressItem::AddressItem(const CDKey &key, Type type, QWidget *parent) } else { ui->label = QString::fromStdString(ui->key.lock.label); } - if (map.contains("x-expiry-time")) { - ui->expireDate = QDateTime::fromSecsSinceEpoch(QString::fromStdString(map["x-expiry-time"]).toLongLong()); + if (map.contains("server_exp")) { + ui->expireDate = QDateTime::fromSecsSinceEpoch(QString::fromStdString(map["server_exp"]).toLongLong()); } if (ui->key.lock.isSymmetric()) { ui->decrypt->show(); - connect(ui->decrypt, &QToolButton::clicked, this, - [this] { emit decrypt(&ui->key.lock); }); + connect(ui->decrypt, &QToolButton::clicked, this, [this] { + emit decrypt(&ui->key.lock); + }); } else { ui->decrypt->hide(); } diff --git a/client/widgets/AddressItem.ui b/client/widgets/AddressItem.ui index a0d92eedd..15d5ce233 100644 --- a/client/widgets/AddressItem.ui +++ b/client/widgets/AddressItem.ui @@ -7,7 +7,7 @@ 0 0 619 - 52 + 54 @@ -47,7 +47,7 @@ QLabel[label="good"] { color: #1A641B; background: #EAF8EA; } -#add { +#add, #decrypt { padding: 9px 8px; font-size: 12px; font-weight: 700; @@ -55,10 +55,11 @@ border-radius: 4px; border: none; color: #2F70B6; } -#add:hover { +#add:hover, #add:focus, +#decrypt:hover, #decrypt:focus { background-color: #EAF1F8; } -#add:pressed { +#add:pressed, #decrypt:pressed { background-color: #BFD3E8; } @@ -110,42 +111,6 @@ background-color: #BFD3E8; - - - - Qt::TabFocus - - - ID-card - - - - - - - Qt::TabFocus - - - Expire - - - default - - - - - - - Qt::Horizontal - - - - 40 - 0 - - - - @@ -176,6 +141,13 @@ background-color: #BFD3E8; + + + Decrypt + + + + PointingHandCursor @@ -185,14 +157,42 @@ background-color: #BFD3E8; -+ -+ - -- Add -+ DECRYPT - - - + + + + Qt::TabFocus + + + ID-card + + + + + + + Qt::TabFocus + + + Expire + + + default + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + From f8b65fbcda3f70a7c57f691108a211fcfdd330ef Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 30 Jan 2026 12:51:29 +0200 Subject: [PATCH 36/44] Show all lock types Signed-off-by: Raul Metsma --- client/dialogs/KeyDialog.cpp | 11 +++++++++++ client/widgets/AddressItem.cpp | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/client/dialogs/KeyDialog.cpp b/client/dialogs/KeyDialog.cpp index 3a48c1e0f..945ad2a88 100644 --- a/client/dialogs/KeyDialog.cpp +++ b/client/dialogs/KeyDialog.cpp @@ -49,6 +49,17 @@ KeyDialog::KeyDialog(const CDKey &k, QWidget *parent ) }; addItem(tr("Recipient"), QString::fromStdString(k.lock.label)); + addItem(tr("Lock type"), [type = k.lock.type] { + switch(type) { + case libcdoc::Lock::SYMMETRIC_KEY: return QStringLiteral("SYMMETRIC_KEY"); + case libcdoc::Lock::PASSWORD: return QStringLiteral("PASSWORD"); + case libcdoc::Lock::PUBLIC_KEY: return QStringLiteral("PUBLIC_KEY"); + case libcdoc::Lock::CDOC1: return QStringLiteral("CDOC1"); + case libcdoc::Lock::SERVER: return QStringLiteral("SERVER"); + case libcdoc::Lock::SHARE_SERVER: return QStringLiteral("SHARE_SERVER"); + default: return QStringLiteral("INVALID"); + } + }()); QSslCertificate cert; if (!k.rcpt_cert.isNull()) { cert = k.rcpt_cert; diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index b33fbb6ec..597d15c29 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -153,8 +153,7 @@ QWidget* AddressItem::lastTabWidget() } void AddressItem::mouseReleaseEvent(QMouseEvent * /*event*/) { - if (!ui->key.rcpt_cert.isNull() || ui->key.lock.isCertificate()) - (new KeyDialog(ui->key, this))->open(); + (new KeyDialog(ui->key, this))->open(); } void AddressItem::setName() From 50586cec752079447e0268ab1f7e4bc774372b5d Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 30 Jan 2026 13:05:46 +0200 Subject: [PATCH 37/44] Enable password encryption Signed-off-by: Raul Metsma --- client/CryptoDoc.cpp | 28 ++++++++++++---------------- client/QPKCS11.cpp | 1 + client/widgets/ContainerPage.cpp | 2 +- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 62b664461..7427cdf38 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -29,11 +29,9 @@ #include "dialogs/FileDialog.h" #include "dialogs/WarningDialog.h" -#include #include #include #include -#include #include #include #include @@ -114,7 +112,7 @@ struct CryptoDoc::Private writer_last_error = writer->getLastErrorStr(); std::filesystem::remove(std::filesystem::path(fileName.toStdString())); } - qDebug() << "Encryption result: " << result << " " << QString::fromStdString(writer->getLastErrorStr()); + qCDebug(CRYPTO) << "Encryption result: " << result << " " << QString::fromStdString(writer->getLastErrorStr()); delete writer; ofs.close(); if (result == libcdoc::OK) { @@ -332,9 +330,8 @@ bool CryptoDoc::canDecrypt(const QSslCertificate &cert) { if (!d->reader) return false; QByteArray der = cert.toDer(); - libcdoc::Lock lock; return d->reader->getLockForCert( - std::vector(der.cbegin(), der.cend())) >= 0; + std::vector(der.cbegin(), der.cend())) >= 0; } void CryptoDoc::clear( const QString &file ) @@ -456,22 +453,21 @@ bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QB } // I think the correct semantics is to fail if container is already encrypted if(d->reader) return false; - if (secret.isEmpty()) { - // Encrypt for address list - if(d->keys.empty()) - { - WarningDialog::create() - ->withTitle(tr("Failed to encrypt document")) - ->withText(tr("No keys specified")) - ->open(); - return false; - } - } else { + if (!secret.isEmpty()) { // Encrypt with symmetric key d->label = label; d->crypto.secret.assign(secret.cbegin(), secret.cend()); d->kdf_iter = kdf_iter; } + // Encrypt for address list + else if(d->keys.empty()) + { + WarningDialog::create() + ->withTitle(tr("Failed to encrypt document")) + ->withText(tr("No keys specified")) + ->open(); + return false; + } libcdoc::result_t result = d->encrypt(); d->label.clear(); d->crypto.secret.clear(); diff --git a/client/QPKCS11.cpp b/client/QPKCS11.cpp index 0c8375232..d978cf503 100644 --- a/client/QPKCS11.cpp +++ b/client/QPKCS11.cpp @@ -28,6 +28,7 @@ #include "dialogs/PinPopup.h" #include +#include #include diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index c022ce913..033b8d5cf 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -332,7 +332,7 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert if (hasUnsupported) emit warning({UnsupportedCDocWarning}); ui->leftPane->setModel(container->documentModel()); - updatePanes(container->state(), nullptr); + updatePanes(container->state(), container); } void ContainerPage::transition(DigiDoc* container) From dbb5cf6e6506b070c80e5be02e8d6655e16f5890 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 30 Jan 2026 13:43:44 +0200 Subject: [PATCH 38/44] Fix build Signed-off-by: Raul Metsma --- client/SslCertificate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/SslCertificate.cpp b/client/SslCertificate.cpp index b33af953e..48471098d 100644 --- a/client/SslCertificate.cpp +++ b/client/SslCertificate.cpp @@ -78,7 +78,7 @@ static QByteArray i2dDer(Arg arg) QByteArray der; if(!arg) return der; - der.resize(Func(arg, nullptr), 0); + der.resize(Func(arg, nullptr)); if(auto *p = (unsigned char*)der.data(); der.isEmpty() || Func(arg, &p) != der.size()) der.clear(); return der; From 1ae8164a735acfa2447bd069c08fc7c792dfdb9d Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 2 Feb 2026 12:10:26 +0200 Subject: [PATCH 39/44] Fix build Signed-off-by: Raul Metsma --- client/widgets/ContainerPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 6af80298a..c0a3365e0 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -374,7 +374,7 @@ void ContainerPage::transition(DigiDoc* container) disconnect(ui->save, &QPushButton::clicked, container, nullptr); connect(ui->save, &QPushButton::clicked, container, [container, this] { if(container->save()) - updatePanes(container->state()); + updatePanes(container->state(), nullptr); }); disconnect(ui->saveAs, &QPushButton::clicked, container, nullptr); connect(ui->saveAs, &QPushButton::clicked, container, [container, this] { From b7ff162b8e3001dd2bc77025443292a922acea20 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 4 Feb 2026 16:36:53 +0200 Subject: [PATCH 40/44] Handle PIN status in QSigner::decrypt and DDCryptoBackend --- client/CDocSupport.cpp | 43 ++++++++++++++++++++++++++++++++++-------- client/CDocSupport.h | 3 +++ client/QSigner.cpp | 14 +++++++++----- client/QSigner.h | 4 ++-- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 7673edfa4..0f577399f 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -94,14 +94,32 @@ CDocSupport::getCDocFileList(QString filename) return files; } +static libcdoc::result_t +getDecryptStatus(const std::vector& result, QCryptoBackend::PinStatus pin_status) +{ + switch (pin_status) { + case QCryptoBackend::PinOK: + return (result.empty()) ? DDCryptoBackend::BACKEND_ERROR : libcdoc::OK; + case QCryptoBackend::PinCanceled: + return DDCryptoBackend::PIN_CANCELED; + case QCryptoBackend::PinIncorrect: + return DDCryptoBackend::PIN_INCORRECT; + case QCryptoBackend::PinLocked: + return DDCryptoBackend::PIN_LOCKED; + default: + return DDCryptoBackend::BACKEND_ERROR; + } +} + libcdoc::result_t DDCryptoBackend::decryptRSA(std::vector& result, 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 (result.empty()) ? BACKEND_ERROR : libcdoc::OK; + return getDecryptStatus(result, pin_status); } constexpr std::string_view SHA256_MTH {"http://www.w3.org/2001/04/xmlenc#sha256"}; @@ -115,22 +133,24 @@ 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) { return backend->deriveConcatKDF(toByteArray(publicKey), SHA_MTH[digest], toByteArray(algorithmID), toByteArray(partyUInfo), toByteArray(partyVInfo)); - }); + }, pin_status); dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); - return (dst.empty()) ? BACKEND_ERROR : libcdoc::OK; + return getDecryptStatus(dst, pin_status); } 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 (dst.empty()) ? BACKEND_ERROR : libcdoc::OK; + return getDecryptStatus(dst, pin_status); } libcdoc::result_t @@ -143,8 +163,15 @@ DDCryptoBackend::getSecret(std::vector& _secret, unsigned int idx) std::string DDCryptoBackend::getLastErrorStr(libcdoc::result_t code) const { - if (code == BACKEND_ERROR) { - return qApp->signer()->getLastErrorStr().toStdString(); + switch (code) { + case PIN_CANCELED: + return "PIN entry canceled"; + case PIN_INCORRECT: + return "PIN incorrect"; + case PIN_LOCKED: + return "PIN locked"; + case BACKEND_ERROR: + return qApp->signer()->getLastErrorStr().toStdString(); } return libcdoc::CryptoBackend::getLastErrorStr(code); } diff --git a/client/CDocSupport.h b/client/CDocSupport.h index f7a64777b..930e3e2d1 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -54,6 +54,9 @@ struct DDConfiguration : public libcdoc::Configuration { struct DDCryptoBackend : public libcdoc::CryptoBackend { static constexpr int BACKEND_ERROR = -303; + static constexpr int PIN_CANCELED = -304; + static constexpr int PIN_INCORRECT = -305; + static constexpr int PIN_LOCKED = -306; libcdoc::result_t decryptRSA(std::vector &result, const std::vector &data, bool oaep, unsigned int idx) override final; diff --git a/client/QSigner.cpp b/client/QSigner.cpp index 99ac784f7..e6bcb3541 100644 --- a/client/QSigner.cpp +++ b/client/QSigner.cpp @@ -171,11 +171,12 @@ X509Cert QSigner::cert() const return X509Cert((const unsigned char*)der.constData(), size_t(der.size()), X509Cert::Der); } -QByteArray QSigner::decrypt(std::function &&func) +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 {}; } @@ -183,24 +184,27 @@ QByteArray QSigner::decrypt(std::function &&func) { Q_EMIT error(tr("Failed to decrypt document"), tr("Authentication certificate is not selected.")); d->lock.unlock(); + pin_status = QCryptoBackend::GeneralError; return {}; } - switch(auto status = QCryptoBackend::PinStatus(login(d->auth))) + 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(status)); + 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(status)); + 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) + if(d->backend->lastError() == QCryptoBackend::PinCanceled) { + pin_status = QCryptoBackend::PinCanceled; return {}; + } if(result.isEmpty()) Q_EMIT error(tr("Failed to decrypt document"), {}); diff --git a/client/QSigner.h b/client/QSigner.h index 77e2f5179..dfa2ec045 100644 --- a/client/QSigner.h +++ b/client/QSigner.h @@ -23,10 +23,10 @@ #include #include +#include #include -class QCryptoBackend; class QSmartCard; class QSslKey; class TokenData; @@ -41,7 +41,7 @@ class QSigner final: public QThread, public digidoc::Signer QList cache() const; digidoc::X509Cert cert() const final; - QByteArray decrypt(std::function &&func); + QByteArray decrypt(std::function &&func, QCryptoBackend::PinStatus& pin_status); QSslKey key() const; void logout() const; void selectCard(const TokenData &token); From 959e7197ba138acc63da86616eab7d1cf64c261b Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Thu, 5 Feb 2026 13:00:08 +0200 Subject: [PATCH 41/44] Adjust PasswordDialog UI Signed-off-by: Raul Metsma --- .github/workflows/build.yml | 6 +- client/CDocSupport.cpp | 4 +- client/CDocSupport.h | 43 ++-- client/CryptoDoc.cpp | 54 ++--- client/CryptoDoc.h | 2 +- client/MainWindow.cpp | 75 +----- client/MainWindow.h | 4 - client/common_enums.h | 1 + client/dialogs/AddRecipients.cpp | 2 - client/dialogs/PasswordDialog.cpp | 152 ++++-------- client/dialogs/PasswordDialog.h | 41 ++-- client/dialogs/PasswordDialog.ui | 391 +++++++++++++++++++----------- client/images/icon_info_main.svg | 3 + client/images/images.qrc | 1 + client/libcdoc | 2 +- client/translations/en.ts | 100 +++++--- client/translations/et.ts | 100 +++++--- client/translations/ru.ts | 96 +++++--- client/widgets/AddressItem.cpp | 16 +- client/widgets/ContainerPage.cpp | 36 ++- client/widgets/ContainerPage.h | 7 +- client/widgets/MainAction.cpp | 3 +- 22 files changed, 615 insertions(+), 524 deletions(-) create mode 100644 client/images/icon_info_main.svg diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 66bd6a74f..9eed1ac76 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -187,10 +187,10 @@ jobs: dotnet tool install -g wix --version 6.0.2 wix extension -g add WixToolset.UI.wixext/6.0.2 - name: Build - env: - VCPKG_MANIFEST_DIR: ${{ github.workspace }}/client/libcdoc run: | - cmake "-GNinja" -B build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo "-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DVCPKG_MANIFEST_DIR=${{ github.workspace }}/client/libcdoc + cmake "-GNinja" -B build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo ` + -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake ` + -DVCPKG_MANIFEST_DIR=${{ github.workspace }}/client/libcdoc cmake --build build --target msi cmake --build build --target msishellext cmake --build build --target appx diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 0f577399f..b2923ee91 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -1,7 +1,5 @@ -#define __CDOCSUPPORT_CPP__ - /* - * QDigiDocCrypto + * QDigiDoc4 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 930e3e2d1..1dab486f1 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -1,8 +1,5 @@ -#ifndef __CDOCSUPPORT_H__ -#define __CDOCSUPPORT_H__ - /* - * QDigiDocCrypto + * QDigiDoc4 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,6 +17,8 @@ * */ +#pragma once + #include #include @@ -52,27 +51,27 @@ struct DDConfiguration : public libcdoc::Configuration { // Bridges to qApp->signer() // -struct DDCryptoBackend : public libcdoc::CryptoBackend { +struct DDCryptoBackend final : public libcdoc::CryptoBackend { static constexpr int BACKEND_ERROR = -303; static constexpr int PIN_CANCELED = -304; static constexpr int PIN_INCORRECT = -305; static constexpr int PIN_LOCKED = -306; libcdoc::result_t decryptRSA(std::vector &result, const std::vector &data, bool oaep, - unsigned int idx) override final; + unsigned int idx) final; libcdoc::result_t 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) override final; + unsigned int idx) final; libcdoc::result_t deriveHMACExtract(std::vector &dst, const std::vector &publicKey, const std::vector &salt, - unsigned int idx) override final; + unsigned int idx) final; libcdoc::result_t getSecret(std::vector &secret, - unsigned int idx) override final; + unsigned int idx) final; std::string getLastErrorStr(libcdoc::result_t code) const final; std::vector secret; @@ -86,7 +85,7 @@ struct DDCryptoBackend : public libcdoc::CryptoBackend { // Bridges to QNetworkAccessManager // -struct DDNetworkBackend : public libcdoc::NetworkBackend, private QObject { +struct DDNetworkBackend final : public libcdoc::NetworkBackend, private QObject { static constexpr int BACKEND_ERROR = -303; std::string getLastErrorStr(libcdoc::result_t code) const final; @@ -95,17 +94,17 @@ struct DDNetworkBackend : public libcdoc::NetworkBackend, private QObject { const std::vector &rcpt_key, const std::vector &key_material, const std::string &type, - uint64_t expiry_ts) override final; + uint64_t expiry_ts) final; libcdoc::result_t fetchKey(std::vector &result, const std::string &keyserver_id, - const std::string &transaction_id) override final; + const std::string &transaction_id) final; libcdoc::result_t - getClientTLSCertificate(std::vector &dst) override final { + getClientTLSCertificate(std::vector &dst) final { return libcdoc::NOT_IMPLEMENTED; } libcdoc::result_t getPeerTLSCertificates( - std::vector> &dst) override final { + std::vector> &dst) final { return libcdoc::NOT_IMPLEMENTED; } @@ -120,7 +119,7 @@ struct DDNetworkBackend : public libcdoc::NetworkBackend, private QObject { // Bridges to Qt logging system // -class DDCDocLogger : private libcdoc::ILogger { +class DDCDocLogger final : private libcdoc::ILogger { public: static void setUpLogger(); @@ -128,7 +127,7 @@ class DDCDocLogger : private libcdoc::ILogger { DDCDocLogger() = default; ~DDCDocLogger() = default; void LogMessage(libcdoc::ILogger::LogLevel level, std::string_view file, int line, - std::string_view message) override final; + std::string_view message) final; }; class CDocSupport { @@ -147,7 +146,7 @@ struct IOEntry std::unique_ptr data; }; -struct TempListConsumer : public libcdoc::MultiDataConsumer { +struct TempListConsumer final : public libcdoc::MultiDataConsumer { static constexpr int64_t MAX_VEC_SIZE = 500L * 1024L * 1024L; explicit TempListConsumer(size_t max_memory_size = 500L * 1024L * 1024L) @@ -158,23 +157,21 @@ struct TempListConsumer : public libcdoc::MultiDataConsumer { libcdoc::result_t close() noexcept final; bool isError() noexcept final; libcdoc::result_t open(const std::string &name, - int64_t size) override final; + int64_t size) final; size_t _max_memory_size; std::vector files; }; -struct StreamListSource : public libcdoc::MultiDataSource { +struct StreamListSource final : public libcdoc::MultiDataSource { StreamListSource(const std::vector &files); libcdoc::result_t read(uint8_t *dst, size_t size) noexcept final; bool isError() noexcept final; bool isEof() noexcept final; - libcdoc::result_t getNumComponents() override final; - libcdoc::result_t next(std::string &name, int64_t &size) override final; + libcdoc::result_t getNumComponents() final; + libcdoc::result_t next(std::string &name, int64_t &size) final; const std::vector &_files; int64_t _current = -1; }; - -#endif // __CDOCSUPPORT_H__ diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 04a270179..a5eb195cd 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -55,27 +55,6 @@ auto toHex = [](const std::vector& data) -> QString { struct CryptoDoc::Private { bool isEncryptedWarning(const QString &title) const; - inline libcdoc::result_t decrypt(unsigned int lock_idx) { - TempListConsumer cons; - libcdoc::result_t result = waitFor([&]{ - std::vector fmk; - libcdoc::result_t result = reader->getFMK(fmk, lock_idx); - qCDebug(CRYPTO) << "getFMK result: " << result << " " << QString::fromStdString(reader->getLastErrorStr()); - if (result != libcdoc::OK) return result; - result = reader->decrypt(fmk, &cons); - std::fill(fmk.begin(), fmk.end(), 0); - qCDebug(CRYPTO) << "Decryption result: " << result << " " << QString::fromStdString(reader->getLastErrorStr()); - return result; - }); - if (result == libcdoc::OK) { - files = std::move(cons.files); - // Success, immediately create writer from reader - keys.clear(); - writer_last_error.clear(); - reader.reset(); - } - return result; - } inline libcdoc::result_t encrypt() { libcdoc::result_t res = waitFor([&]{ @@ -101,8 +80,7 @@ struct CryptoDoc::Private } } if (!crypto.secret.empty()) { - auto key = - libcdoc::Recipient::makeSymmetric(label.toStdString(), kdf_iter); + auto key = libcdoc::Recipient::makeSymmetric(label.toStdString(), 65536); enc_keys.push_back(key); } libcdoc::CDocWriter *writer = libcdoc::CDocWriter::createWriter( @@ -133,7 +111,6 @@ struct CryptoDoc::Private QStringList tempFiles; // Encryption data QString label; - uint32_t kdf_iter; // libcdoc handlers DDConfiguration conf; @@ -356,7 +333,7 @@ ContainerState CryptoDoc::state() const bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) { - if( d->fileName.isEmpty() ) + if(!d->reader) { WarningDialog::create() ->withTitle(QSigner::tr("Failed to decrypt document")) @@ -364,11 +341,9 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) ->open(); return false; } - if (!d->reader) - return true; int lock_idx = -1; - const std::vector locks = d->reader->getLocks(); + const std::vector &locks = d->reader->getLocks(); if (lock == nullptr) { QByteArray der = qApp->signer()->tokenauth().cert().toDer(); lock_idx = d->reader->getLockForCert( @@ -423,7 +398,20 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) d->crypto.secret.assign(secret.cbegin(), secret.cend()); - libcdoc::result_t result = d->decrypt(lock_idx); + TempListConsumer cons; + libcdoc::result_t result = waitFor([&]{ + std::vector fmk; + auto scope = qScopeGuard([&] { + std::fill(fmk.begin(), fmk.end(), 0); + }); + libcdoc::result_t result = d->reader->getFMK(fmk, lock_idx); + qCDebug(CRYPTO) << "getFMK result: " << result << " " << QString::fromStdString(d->reader->getLastErrorStr()); + if (result != libcdoc::OK) + return result; + result = d->reader->decrypt(fmk, &cons); + qCDebug(CRYPTO) << "Decryption result: " << result << " " << QString::fromStdString(d->reader->getLastErrorStr()); + return result; + }); if (result != libcdoc::OK) { const std::string &msg = d->reader->getLastErrorStr(); WarningDialog::create() @@ -434,12 +422,17 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) return false; } + d->files = std::move(cons.files); + // Success, immediately create writer from reader + d->keys.clear(); + d->writer_last_error.clear(); + d->reader.reset(); return !d->isEncrypted(); } DocumentModel *CryptoDoc::documentModel() const { return d->documents; } -bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QByteArray& secret, uint32_t kdf_iter) +bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QByteArray& secret) { if( !filename.isEmpty() ) d->fileName = filename; @@ -457,7 +450,6 @@ bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QB // Encrypt with symmetric key d->label = label; d->crypto.secret.assign(secret.cbegin(), secret.cend()); - d->kdf_iter = kdf_iter; } // Encrypt for address list else if(d->keys.empty()) diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index 3eae9b228..e7bfeed27 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -59,7 +59,7 @@ class CryptoDoc final: public QObject bool canDecrypt(const QSslCertificate &cert); void clear(const QString &file = {}); bool decrypt(const libcdoc::Lock *lock, const QByteArray& secret); - bool encrypt(const QString &filename = {}, const QString& label = {}, const QByteArray& secret = {}, uint32_t kdf_iter = 0); + bool encrypt(const QString &filename = {}, const QString& label = {}, const QByteArray& secret = {}); DocumentModel* documentModel() const; QString fileName() const; const std::vector& keys() const; diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index fca90c7c7..b66651beb 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -117,8 +117,6 @@ MainWindow::MainWindow( QWidget *parent ) ui->crypto->warningIcon(true); }); - connect(ui->cryptoContainerPage, &ContainerPage::decryptReq, this, &MainWindow::decryptClicked); - connect(ui->accordion, &Accordion::changePinClicked, this, &MainWindow::changePinClicked); connect(ui->cardInfo, &CardWidget::selected, ui->selector, &QToolButton::toggle); @@ -180,40 +178,6 @@ ContainerState MainWindow::currentState() return ContainerState::Uninitialized; } -bool MainWindow::decrypt(const libcdoc::Lock *lock) { - if (!cryptoDoc) - return false; - - QByteArray secret; - if (lock && (lock->type == libcdoc::Lock::Type::SYMMETRIC_KEY || - lock->type == libcdoc::Lock::Type::PASSWORD)) { - PasswordDialog p; - p.setLabel(QString::fromStdString(lock->label)); - if (lock->type == libcdoc::Lock::Type::PASSWORD) { - p.setMode(PasswordDialog::Mode::DECRYPT, - PasswordDialog::Type::PASSWORD); - if (!p.exec()) - return false; - secret = p.secret(); - } else { - p.setMode(PasswordDialog::Mode::DECRYPT, PasswordDialog::Type::KEY); - if (!p.exec()) - return false; - secret = p.secret(); - } - } - - WaitDialogHolder waitDialog(this, tr("Decrypting")); - - if (cryptoDoc->decrypt(lock, secret)) { - ui->cryptoContainerPage->transition(cryptoDoc.get(), - qApp->signer()->tokenauth().cert()); - FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); - return true; - } - return false; -} - void MainWindow::dragEnterEvent(QDragEnterEvent *event) { if(!event->source() && !dropEventFiles(event).isEmpty()) @@ -264,7 +228,7 @@ bool MainWindow::encrypt(bool askForKey) if(!cryptoDoc) return false; - while (!FileDialog::fileIsWritable(cryptoDoc->fileName())) + if(!FileDialog::fileIsWritable(cryptoDoc->fileName())) { auto *dlg = WarningDialog::create(this) ->withTitle(CryptoDoc::tr("Failed to encrypt document")) @@ -278,23 +242,17 @@ bool MainWindow::encrypt(bool askForKey) ui->cryptoContainerPage->setHeader(to); } - if (askForKey) { - PasswordDialog p; - p.setMode(PasswordDialog::Mode::ENCRYPT, PasswordDialog::Type::PASSWORD); - if(!p.exec()) return false; - QString label = p.label(); - QByteArray secret = p.secret(); - if (p.type == PasswordDialog::Type::PASSWORD) { - WaitDialogHolder waitDialog(this, tr("Encrypting")); - return cryptoDoc->encrypt(cryptoDoc->fileName(), label, secret, 65536); - } else { - WaitDialogHolder waitDialog(this, tr("Encrypting")); - return cryptoDoc->encrypt(cryptoDoc->fileName(), label, secret, 0); - } - } else { + if(!askForKey) { WaitDialogHolder waitDialog(this, tr("Encrypting")); return cryptoDoc->encrypt(); } + + PasswordDialog p(PasswordDialog::Mode::ENCRYPT, this); + if(!p.exec()) + return false; + + WaitDialogHolder waitDialog(this, tr("Encrypting")); + return cryptoDoc->encrypt(cryptoDoc->fileName(), p.label(), p.secret()); } void MainWindow::mouseReleaseEvent(QMouseEvent *event) @@ -450,13 +408,8 @@ void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString if(cryptoDoc && wrap(cryptoDoc->fileName(), false)) FadeInNotification::success(ui->topBar, tr("Converted to signed document!")); break; - case DecryptContainer: - case DecryptToken: - if(decrypt(nullptr)) - { - ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->signer()->tokenauth().cert()); - FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); - } + case DecryptedContainer: + FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); break; case EncryptContainer: if(encrypt()) @@ -874,9 +827,3 @@ void MainWindow::updateSelector() cardPopup->deleteLater(); }); } - -void -MainWindow::decryptClicked(const libcdoc::Lock *lock) -{ - decrypt(lock); -} diff --git a/client/MainWindow.h b/client/MainWindow.h index 0d8901bc3..ce6887c80 100644 --- a/client/MainWindow.h +++ b/client/MainWindow.h @@ -23,7 +23,6 @@ #include "common_enums.h" #include "QSmartCard.h" -#include "cdoc/Lock.h" namespace Ui { class MainWindow; @@ -59,7 +58,6 @@ class MainWindow final : public QWidget void changePinClicked(QSmartCardData::PinType type, QSmartCard::PinAction action); void convertToCDoc(); ria::qdigidoc4::ContainerState currentState(); - bool decrypt(const libcdoc::Lock *lock); bool encrypt(bool askForKey = false); void loadPicture(); void navigateToPage( ria::qdigidoc4::Pages page, const QStringList &files = QStringList(), bool create = true ); @@ -81,6 +79,4 @@ class MainWindow final : public QWidget std::unique_ptr cryptoDoc; std::unique_ptr digiDoc; Ui::MainWindow *ui; - - void decryptClicked(const libcdoc::Lock *lock); }; diff --git a/client/common_enums.h b/client/common_enums.h index 34c751910..af4f8ed6d 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -44,6 +44,7 @@ enum Actions : unsigned char { EncryptContainer, DecryptContainer, DecryptToken, + DecryptedContainer, SignatureAdd, SignatureMobile, diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index 8be03ed5a..ad9c72ab4 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -19,7 +19,6 @@ #include "AddRecipients.h" -#include "dialogs/PasswordDialog.h" #include "ui_AddRecipients.h" #include "Application.h" @@ -32,7 +31,6 @@ #include "TokenData.h" #include "dialogs/WarningDialog.h" #include "effects/Overlay.h" -#include "Crypto.h" #include "widgets/AddressItem.h" #include diff --git a/client/dialogs/PasswordDialog.cpp b/client/dialogs/PasswordDialog.cpp index 6f35fa9e0..717df9161 100644 --- a/client/dialogs/PasswordDialog.cpp +++ b/client/dialogs/PasswordDialog.cpp @@ -1,18 +1,47 @@ +/* + * QDigiDoc4 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + #include "PasswordDialog.h" #include "ui_PasswordDialog.h" -#include +#include -PasswordDialog::PasswordDialog(QWidget *parent) - : QDialog(parent), mode(Mode::DECRYPT), type(Type::PASSWORD) +PasswordDialog::PasswordDialog(Mode mode, QWidget *parent) + : QDialog(parent) , ui(new Ui::PasswordDialog) { + setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint); ui->setupUi(this); - connect(ui->generateKey, &QPushButton::clicked, this, &PasswordDialog::genKeyClicked); - connect(ui->typeSelector, &QTabWidget::currentChanged, this, &PasswordDialog::typeChanged); - connect(ui->passwordLine, &QLineEdit::textChanged, this, &PasswordDialog::lineChanged); - connect(ui->password2Line, &QLineEdit::textChanged, this, &PasswordDialog::lineChanged); - connect(ui->keyEdit, &QPlainTextEdit::textChanged, this, &PasswordDialog::editChanged); + ui->labelLine->setReadOnly(mode == Mode::DECRYPT); + ui->infoWidget->setHidden(mode == Mode::DECRYPT); + ui->passwordHint->setHidden(mode == Mode::DECRYPT); + ui->password2Label->setHidden(mode == Mode::DECRYPT); + ui->password2Line->setHidden(mode == Mode::DECRYPT); + if(mode == DECRYPT) { + ui->passwordLabel->setText(tr("Enter password to decrypt the document")); + ui->ok->setText(tr("Decrypt")); + } + connect(ui->ok, &QPushButton::clicked, this, &QDialog::accept); + connect(ui->cancel, &QPushButton::clicked, this, &QDialog::reject); + connect(ui->passwordLine, &QLineEdit::textChanged, this, &PasswordDialog::updateOK); + connect(ui->password2Line, &QLineEdit::textChanged, this, &PasswordDialog::updateOK); + updateOK(); } PasswordDialog::~PasswordDialog() @@ -20,14 +49,6 @@ PasswordDialog::~PasswordDialog() delete ui; } -void -PasswordDialog::setMode(Mode _mode, Type _type) -{ - mode = _mode; - type = _type; - updateUI(); -} - void PasswordDialog::setLabel(const QString& label) { @@ -43,101 +64,20 @@ PasswordDialog::label() QByteArray PasswordDialog::secret() const { - if (type == Type::PASSWORD) { - return ui->passwordLine->text().toUtf8(); - } else { - QString hex = ui->keyEdit->toPlainText(); - return QByteArray::fromHex(hex.toUtf8()); - } -} - -void -PasswordDialog::typeChanged(int index) -{ - Type new_type = static_cast(index); - if (new_type != type) setMode(mode, new_type); -} - -void -PasswordDialog::lineChanged(const QString& text) -{ - updateOK(); -} - -void -PasswordDialog::editChanged() -{ - updateOK(); -} - -void -PasswordDialog::genKeyClicked() -{ - QByteArray key(32, 0); - auto *g = QRandomGenerator::system(); - for (int i = 0; i < key.size(); i += 4) { - const quint32 r = g->generate(); - const int n = qMin(4, key.size() - i); - memcpy(key.data() + i, &r, n); - } - ui->keyEdit->clear(); - ui->keyEdit->appendPlainText(key.toHex()); -} - -void -PasswordDialog::updateUI() -{ - ui->typeSelector->setCurrentIndex(type); - if (mode == Mode::DECRYPT) { - ui->labelLine->setReadOnly(true); - if (type == Type::PASSWORD) { - ui->typeSelector->setTabEnabled(Type::PASSWORD, true); - ui->typeSelector->setTabEnabled(Type::KEY, false); - ui->passwordLabel->setText("Enter password to decrypt the document"); - ui->passwordLine->setEchoMode(QLineEdit::EchoMode::Password); - ui->password2Label->hide(); - ui->password2Line->hide(); - } else { - ui->typeSelector->setTabEnabled(Type::PASSWORD, false); - ui->typeSelector->setTabEnabled(Type::KEY, true); - ui->keyLabel->setText("Enter key to decrypt the document"); - ui->generateKey->hide(); - } - } else { - ui->labelLine->setReadOnly(false); - if (type == Type::PASSWORD) { - ui->typeSelector->setTabEnabled(Type::PASSWORD, true); - ui->typeSelector->setTabEnabled(Type::KEY, true); - ui->passwordLabel->setText("Enter a password to encrypt the document"); - ui->passwordLine->setEchoMode(QLineEdit::EchoMode::Password); - ui->password2Label->show(); - ui->password2Line->show(); - } else { - ui->typeSelector->setTabEnabled(Type::PASSWORD, true); - ui->typeSelector->setTabEnabled(Type::KEY, true); - ui->keyLabel->setText("Enter a key to encrypt the document"); - ui->generateKey->show(); - } - } - updateOK(); + return ui->passwordLine->text().toUtf8(); } void PasswordDialog::updateOK() { bool active = false; - if (mode == Mode::DECRYPT) { - if (type == Type::PASSWORD) { - active = !ui->passwordLine->text().isEmpty(); - } else { - active = !ui->keyEdit->toPlainText().isEmpty(); - } - } else { - if (type == Type::PASSWORD) { - active = !ui->passwordLine->text().isEmpty() && ui->passwordLine->text() == ui->password2Line->text(); - } else { - active = !ui->keyEdit->toPlainText().isEmpty(); - } + if(ui->password2Line->isVisible()) + { + static const QRegularExpression re(QStringLiteral(R"(^(?=.{20,64}$)(?=.*\d)(?=.*[A-Z])(?=.*[a-z]).*$)")); + active = re.match(ui->passwordLine->text()).hasMatch() && + ui->passwordLine->text() == ui->password2Line->text(); } - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(active); + else + active = !ui->passwordLine->text().isEmpty(); + ui->ok->setEnabled(active); } diff --git a/client/dialogs/PasswordDialog.h b/client/dialogs/PasswordDialog.h index 451144c25..53c4a657b 100644 --- a/client/dialogs/PasswordDialog.h +++ b/client/dialogs/PasswordDialog.h @@ -1,5 +1,23 @@ -#ifndef PASSWORDDIALOG_H -#define PASSWORDDIALOG_H +/* + * 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 @@ -17,31 +35,16 @@ class PasswordDialog : public QDialog DECRYPT }; - enum Type { - PASSWORD, - KEY - }; - - Mode mode; - Type type; - - explicit PasswordDialog(QWidget *parent = nullptr); + explicit PasswordDialog(Mode mode, QWidget *parent = nullptr); ~PasswordDialog(); - void setMode(Mode mode, Type type); - void setLabel(const QString& label); QString label(); QByteArray secret() const; + private: Ui::PasswordDialog *ui; - void typeChanged(int index); - void lineChanged(const QString& text); - void editChanged(); void genKeyClicked(); - void updateUI(); void updateOK(); }; - -#endif // PASSWORDDIALOG_H diff --git a/client/dialogs/PasswordDialog.ui b/client/dialogs/PasswordDialog.ui index dd8d20494..0398b152b 100644 --- a/client/dialogs/PasswordDialog.ui +++ b/client/dialogs/PasswordDialog.ui @@ -2,22 +2,106 @@ PasswordDialog + + Qt::WindowModal + 0 0 - 400 - 322 + 496 + 570 - - Dialog + + QWidget { +color: #07142A; +font-family: Roboto, Helvetica; +font-size: 14px; +} +#PasswordDialog { +background-color: #FFFFFF; +border-radius: 4px; +} +QLineEdit { +padding: 10px 14px; +border: 1px solid #C4CBD8; +border-radius: 4px; +background-color: white; +placeholder-text-color: #607496; +font-size: 16px; +} +QLineEdit::disabled { +color: #607496; +background-color: #F3F5F7; +} +QPushButton { +border: 1px solid #AD2A45; +border-radius: 4px; +padding: 12px 12px; +color: #AD2A45; +font-weight: 700; +} +QPushButton:hover, QPushButton:focus { +background-color: #F5EBED; +} +QPushButton:pressed { +background-color: #E1C1C6; +} +QPushButton:default { +background-color: #2F70B6; +border-color: #2F70B6; +color: #ffffff; +} +QPushButton:default:hover, QPushButton:default:focus { +background-color: #2B66A6; +border-color: #2B66A6; +} +QPushButton:default:pressed { +background-color: #215081; +border-color: #215081; +} +QPushButton:default:disabled { +background-color: #82A9D3; +border-color: #82A9D3; +} +#title { +font-size: 20px; +font-weight: 700; +color: #003168; +} +#infoWidget { +border-radius: 4px; +background-color: #F3F5F7; +} +#passwordHint { +color: #607496; +margin-left: 6px; +} - + + + 32 + + + QLayout::SetFixedSize + + + 24 + + + 24 + + + 24 + + + 24 + - + - Key label (recipient name or id) + Encrypt with password Qt::AlignCenter @@ -25,151 +109,170 @@ - + + + + + Key label (recipient name or id) + + + labelLine + + + + + + + + 400 + 0 + + + + + - - - 0 - - - - Password - - - - - - Password - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - QLineEdit::Password - - - - - - - Repeat password - - - - - - - QLineEdit::Password - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Symmetric key - - - - - - Symmetric key - - - - - - - - - - Generate Key - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - + + + + 10 + + + 12 + + + 12 + + + 12 + + + 12 + + + + + :/images/icon_info_main.svg + + + + + + + Be sure to save the password in a secure place +- without the password, you won’t be able to open the file again. + + + + - - - Qt::Horizontal + + + 6 - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false + + + + Enter a password to encrypt the document + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + passwordLine + + + + + + + QLineEdit::Password + + + + + + + • Length: 20–64 characters +• Contains at least one number (0–9) +• Contains at least one uppercase letter +• Contains at least one lowercase letter + + + Qt::AutoText + + + + + + + + + 6 - + + + + Repeat password + + + password2Line + + + + + + + QLineEdit::Password + + + + + + + + + + + Cancel + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Encrypt + + + true + + + + - - - - buttonBox - accepted() - PasswordDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - PasswordDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - + + + + diff --git a/client/images/icon_info_main.svg b/client/images/icon_info_main.svg new file mode 100644 index 000000000..b9b08c9cc --- /dev/null +++ b/client/images/icon_info_main.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/images/images.qrc b/client/images/images.qrc index 67af45958..20cb586aa 100644 --- a/client/images/images.qrc +++ b/client/images/images.qrc @@ -37,6 +37,7 @@ icon_IDkaart_red.svg icon_IDkaart_green.svg icon_info.svg + icon_info_main.svg icon_Krypto_hover.svg icon_Krypto_small.svg icon_Krypto.svg diff --git a/client/libcdoc b/client/libcdoc index fc41f1d31..96bbd265f 160000 --- a/client/libcdoc +++ b/client/libcdoc @@ -1 +1 @@ -Subproject commit fc41f1d31c1e242cbd0454407f9b1153effbd1b8 +Subproject commit 96bbd265facca710128d7aa5e635b5ddeb181501 diff --git a/client/translations/en.ts b/client/translations/en.ts index 13d654105..2d5da7852 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -237,13 +237,6 @@ Start downloading - - CDoc2 - - CDoc contains additional payload data that is not part of content - CDoc contains additional payload data that is not part of content - - CDocumentModel @@ -477,6 +470,10 @@ DigiDoc4 Client DigiDoc4 Client + + Decrypting + Decrypting + You are about to delete the last file in the container You are about to delete the last file in the container @@ -541,37 +538,21 @@ Container is not open - Key already exists - Key already exists + Unsupported file format + Unsupported file format - Failed to create temporary files<br />%1 - Failed to create temporary files<br />%1 + Key already exists + Key already exists You do not have the key to decrypt this document You do not have the key to decrypt this document - - Error parsing document - Error parsing document - No keys specified No recipients specified - - ID-CARD - ID-CARD - - - Digi-ID E-RESIDENT - Digi-ID E-RESIDENT - - - Digi-ID - Digi-ID - Failed to add key Failed to add key @@ -600,10 +581,6 @@ Failed to open document Failed to open document - - An error occurred while opening the document - An error occurred while opening the document - Don't show again Don't show again @@ -1233,6 +1210,10 @@ Recipient Recipient + + Lock type + Lock type + Expiry date Expiry date @@ -1326,6 +1307,10 @@ E-Seal Encrypt Encrypt + + Encrypt long-term + Encrypt long-term + Decrypt Decrypt @@ -1464,7 +1449,7 @@ ID-Card Decrypting - Decrypting + Decrypting My eID @@ -1744,6 +1729,57 @@ ID-Card The PCSC service, required for using the ID-card, is not working. Check your computer settings. + + PasswordDialog + + Encrypt with password + Encrypt with password + + + Key label (recipient name or id) + Key label (recipient name or id) + + + Be sure to save the password in a secure place +- without the password, you won’t be able to open the file again. + Be sure to save the password in a secure place +- without the password, you won’t be able to open the file again. + + + • Length: 20–64 characters +• Contains at least one number (0–9) +• Contains at least one uppercase letter +• Contains at least one lowercase letter + • Length: 20–64 characters +• Contains at least one number (0–9) +• Contains at least one uppercase letter +• Contains at least one lowercase letter + + + Repeat password + Repeat password + + + Cancel + Cancel + + + Encrypt + Encrypt + + + Enter password to decrypt the document + Enter password to decrypt the document + + + Decrypt + Decrypt + + + Enter a password to encrypt the document + Enter a password to encrypt the document + + PinPopup diff --git a/client/translations/et.ts b/client/translations/et.ts index 583614003..8a7e5f3fc 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -237,13 +237,6 @@ Alusta paigaldust - - CDoc2 - - CDoc contains additional payload data that is not part of content - CDoc sisaldab lisanduvat andmehulka, mis ei ole osa sisust - - CDocumentModel @@ -477,6 +470,10 @@ DigiDoc4 Client DigiDoc4 klient + + Decrypting + Dekrüpteerin + You are about to delete the last file in the container Oled kustutamas viimast faili ümbrikus @@ -541,37 +538,21 @@ Turvaümbrik ei ole avatud - Key already exists - Adressaat on juba lisatud + Unsupported file format + - Failed to create temporary files<br />%1 - Ajutiste failde loomine ebaõnnestus<br />%1 + Key already exists + Adressaat on juba lisatud You do not have the key to decrypt this document Sul puudub võti millega dekrüpteerida seda turvaümbrikut - - Error parsing document - Viga dokumendi parsimisel - No keys specified Ühtegi adressaati ei ole lisatud - - ID-CARD - ID-KAART - - - Digi-ID E-RESIDENT - Digi-ID E-RESIDENT - - - Digi-ID - Digi-ID - Failed to add key Võtme lisamine ebaõnnestus @@ -600,10 +581,6 @@ Failed to open document Dokumendi avamine ebaõnnestus - - An error occurred while opening the document - Ümbriku avamisel tekkis viga - Don't show again Ära rohkem näita @@ -1233,6 +1210,10 @@ Recipient Adressaat + + Lock type + Luku tüüp + Expiry date Aegumiskuupäev @@ -1326,6 +1307,10 @@ E-templiga Encrypt Krüpteeri + + Encrypt long-term + + Decrypt Dekrüpteeri @@ -1464,7 +1449,7 @@ ID-kaardiga Decrypting - Dekrüpteerin + Dekrüpteerin My eID @@ -1744,6 +1729,57 @@ ID-kaardiga ID-kaardi kasutamiseks vajalik PCSC teenus ei tööta. Kontrolli arvuti seadeid. + + PasswordDialog + + Encrypt with password + Krüpteeri parooliga + + + Key label (recipient name or id) + + + + Be sure to save the password in a secure place +- without the password, you won’t be able to open the file again. + Salvesta parool kindlasti turvalisse kohta +- ilma paroolita ei saa faili enam avada. + + + • Length: 20–64 characters +• Contains at least one number (0–9) +• Contains at least one uppercase letter +• Contains at least one lowercase letter + • Pikkus: 20 - 64 tähemärki +• Sisaldab vähemalt ühte numbrit (0-9) +• Sisaldab vähemalt ühte suurtähte +• Sisaldab vähemalt ühte väiketähte + + + Repeat password + Parool uuesti + + + Cancel + Katkesta + + + Encrypt + Krüpteeri + + + Enter password to decrypt the document + + + + Decrypt + Dekrüpteeri + + + Enter a password to encrypt the document + Loo ümbrikule parool + + PinPopup diff --git a/client/translations/ru.ts b/client/translations/ru.ts index 64805c9c3..da1babcc7 100644 --- a/client/translations/ru.ts +++ b/client/translations/ru.ts @@ -237,13 +237,6 @@ Начать установку - - CDoc2 - - CDoc contains additional payload data that is not part of content - CDoc содержит дополнительные данные, которые не являются частью содержимого - - CDocumentModel @@ -477,6 +470,10 @@ DigiDoc4 Client DigiDoc4 клиент + + Decrypting + Расшифровка + You are about to delete the last file in the container Вы собираетесь удалить последний файл в контейнере @@ -541,37 +538,21 @@ Контейнер не открыт - Key already exists - Адресат уже добавлен + Unsupported file format + - Failed to create temporary files<br />%1 - Неудачное создание временных файлов<br />%1 + Key already exists + Адресат уже добавлен You do not have the key to decrypt this document У вас отсутствует ключ для расшифровки документа - - Error parsing document - Ошибка анализа документа - No keys specified Не выбрано ни одного получателя - - ID-CARD - ID-КАРТА - - - Digi-ID E-RESIDENT - Digi-ID E-RESIDENT - - - Digi-ID - Digi-ID - Failed to add key Не удалось добавить ключ @@ -600,10 +581,6 @@ Failed to open document Не удалось открыть документ - - An error occurred while opening the document - Во время открытия конверта возникла ошибка - Don't show again Больше не показывать @@ -1233,6 +1210,10 @@ Recipient Получател + + Lock type + + Expiry date Дата окончания @@ -1326,6 +1307,10 @@ E-Seal Encrypt Зашифровать + + Encrypt long-term + + Decrypt Расшифровать @@ -1464,7 +1449,7 @@ ID-картой Decrypting - Расшифровка + Расшифровка My eID @@ -1744,6 +1729,53 @@ ID-картой Необходимая для использования ID-карты услуга PCSC не работает. Проверьте настройки компьютера. + + PasswordDialog + + Encrypt with password + + + + Key label (recipient name or id) + + + + Be sure to save the password in a secure place +- without the password, you won’t be able to open the file again. + + + + • Length: 20–64 characters +• Contains at least one number (0–9) +• Contains at least one uppercase letter +• Contains at least one lowercase letter + + + + Repeat password + + + + Cancel + Отмена + + + Encrypt + Зашифровать + + + Enter password to decrypt the document + + + + Decrypt + Расшифровать + + + Enter a password to encrypt the document + + + PinPopup diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index 597d15c29..08b0abbe6 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -75,20 +75,11 @@ AddressItem::AddressItem(const CDKey &key, Type type, QWidget *parent) if (map.contains("server_exp")) { ui->expireDate = QDateTime::fromSecsSinceEpoch(QString::fromStdString(map["server_exp"]).toLongLong()); } - if (ui->key.lock.isSymmetric()) { - ui->decrypt->show(); - connect(ui->decrypt, &QToolButton::clicked, this, [this] { - emit decrypt(&ui->key.lock); - }); - } else { - ui->decrypt->hide(); - } } else { // No rcpt, lock is invalid = unsupported lock unsupported = true; ui->code.clear(); ui->label = tr("Unsupported cryptographic algorithm or recipient type"); - ui->decrypt->hide(); } if(!unsupported) @@ -96,10 +87,14 @@ AddressItem::AddressItem(const CDKey &key, Type type, QWidget *parent) connect(ui->add, &QToolButton::clicked, this, [this]{ emit add(this);}); connect(ui->remove, &QToolButton::clicked, this, [this]{ emit remove(this);}); + connect(ui->decrypt, &QToolButton::clicked, this, [this] { + emit decrypt(&ui->key.lock); + }); setIdType(); ui->add->setVisible(type == Add); ui->remove->setVisible(type != Add); + ui->decrypt->setVisible(ui->key.lock.type == libcdoc::Lock::PASSWORD); } AddressItem::~AddressItem() @@ -126,9 +121,6 @@ const CDKey& AddressItem::getKey() const } void AddressItem::idChanged(const SslCertificate &cert) { - QByteArray qder = cert.toDer(); - std::vector sder = std::vector(qder.cbegin(), qder.cend()); - if (ui->key.lock.isValid()) { QSslKey pkey = cert.publicKey(); QByteArray der = pkey.toDer(); diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index c0a3365e0..291e9f3e3 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -29,6 +29,7 @@ #include "dialogs/AddRecipients.h" #include "dialogs/FileDialog.h" #include "dialogs/MobileDialog.h" +#include "dialogs/PasswordDialog.h" #include "dialogs/SmartIDDialog.h" #include "dialogs/WarningDialog.h" #include "widgets/AddressItem.h" @@ -53,6 +54,7 @@ ContainerPage::ContainerPage(QWidget *parent) , ui(new Ui::ContainerPage) { ui->setupUi( this ); + mainAction = new MainAction(this); ui->leftPane->init(fileName); ui->containerFile->installEventFilter(this); ui->summary->hide(); @@ -61,6 +63,7 @@ ContainerPage::ContainerPage(QWidget *parent) connect(btn, &QAbstractButton::clicked, this, [this,code] { emit action(code); }); }; + connect(mainAction, &MainAction::action, this, &ContainerPage::handleAction); connect(ui->cancel, &QPushButton::clicked, this, [this] { window()->setWindowFilePath({}); window()->setWindowTitle(tr("DigiDoc4 Client")); @@ -120,7 +123,7 @@ void ContainerPage::clear(int code) void ContainerPage::clearPopups() { - if(mainAction) mainAction->hideDropdown(); + mainAction->hideDropdown(); } void ContainerPage::elideFileName() @@ -201,6 +204,14 @@ void ContainerPage::changeEvent(QEvent* event) QWidget::changeEvent(event); } +void ContainerPage::decrypt(CryptoDoc *container, const libcdoc::Lock *lock, const QByteArray &secret) { + WaitDialogHolder waitDialog(this, tr("Decrypting")); + if (!container->decrypt(lock, secret)) + return; + transition(container, QSslCertificate{}); + emit action(DecryptedContainer, {}, {}); +} + template void ContainerPage::deleteConfirm(C *c, int index) { @@ -234,11 +245,6 @@ void ContainerPage::setHeader(const QString &file) void ContainerPage::showMainAction(const QList &actions) { - if(!mainAction) - { - mainAction = std::make_unique(this); - connect(mainAction.get(), &MainAction::action, this, &ContainerPage::handleAction); - } mainAction->showActions(actions); bool isSignCard = actions.contains(SignatureAdd) || actions.contains(SignatureToken); bool isSignMobile = !isSignCard && (actions.contains(SignatureMobile) || actions.contains(SignatureSmartID)); @@ -255,8 +261,7 @@ void ContainerPage::showSigningButton() { if (!isSupported) { - if(mainAction) - mainAction->hide(); + mainAction->hide(); ui->mainActionSpacer->changeSize(1, 20, QSizePolicy::Fixed); ui->navigationArea->layout()->invalidate(); } @@ -315,6 +320,11 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert connect(container, &CryptoDoc::destroyed, this, [this] { clear(ClearCryptoWarning); }); + disconnect(mainAction, &MainAction::action, container, nullptr); + connect(mainAction, &MainAction::action, container, [container, this](int action) { + if(action == DecryptToken || action == DecryptContainer) + decrypt(container, nullptr, {}); + }); clear(ClearCryptoWarning); isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); @@ -326,7 +336,15 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert hasUnsupported = hasUnsupported || (key.rcpt_cert.isNull() && !key.lock.isValid()); AddressItem *addr = new AddressItem(key, AddressItem::Icon, ui->rightPane); ui->rightPane->addWidget(addr); - connect(addr, &AddressItem::decrypt, this, [this, key] { emit decryptReq(&key.lock); }); + connect(addr, &AddressItem::decrypt, container, [container, key, this] { + if (key.lock.type != libcdoc::Lock::Type::PASSWORD) + return; + PasswordDialog p(PasswordDialog::Mode::DECRYPT, this); + p.setLabel(QString::fromStdString(key.lock.label)); + if (!p.exec()) + return; + decrypt(container, &key.lock, p.secret()); + }); } if (hasUnsupported) emit warning({UnsupportedCDocWarning}); diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index 4a79c7974..36c0745ee 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -23,8 +23,6 @@ #include "widgets/MainAction.h" #include "CryptoDoc.h" -#include - namespace Ui { class ContainerPage; } @@ -60,11 +58,10 @@ class ContainerPage final : public QWidget void removed(int row); void warning(const WarningText &warningText); - void decryptReq(const libcdoc::Lock *key); - private: void changeEvent(QEvent* event) final; void clear(int code); + void decrypt(CryptoDoc *container, const libcdoc::Lock *lock, const QByteArray &secret); template void deleteConfirm(C *c, int index); void elideFileName(); @@ -77,7 +74,7 @@ class ContainerPage final : public QWidget void translateLabels(); Ui::ContainerPage *ui; - std::unique_ptr mainAction; + MainAction *mainAction {}; QString idCode; QString fileName; diff --git a/client/widgets/MainAction.cpp b/client/widgets/MainAction.cpp index dc717b443..dea6c8312 100644 --- a/client/widgets/MainAction.cpp +++ b/client/widgets/MainAction.cpp @@ -167,5 +167,6 @@ void MainAction::showDropdown() void MainAction::update() { hideDropdown(); - ui->mainAction->setText(label(ui->actions[0])); + if(!ui->actions.isEmpty()) + ui->mainAction->setText(label(ui->actions[0])); } From 118065eb4cffefa4fe14e2ae87a482de35b7a3d4 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Tue, 10 Feb 2026 11:44:44 +0200 Subject: [PATCH 42/44] Fix build Signed-off-by: Raul Metsma --- .github/workflows/build.yml | 2 +- .gitmodules | 2 +- client/CMakeLists.txt | 5 ++--- client/CheckConnection.cpp | 2 +- client/dialogs/AddRecipients.cpp | 3 --- client/widgets/AddressItem.cpp | 6 ++---- client/widgets/AddressItem.h | 5 ++--- client/widgets/AddressItem.ui | 4 ++-- client/widgets/ContainerPage.cpp | 2 +- client/widgets/ContainerPage.h | 10 +++++----- 10 files changed, 17 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9eed1ac76..e6eb5e0d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -189,7 +189,7 @@ jobs: - name: Build run: | cmake "-GNinja" -B build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo ` - -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake ` + -DCMAKE_TOOLCHAIN_FILE=${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake ` -DVCPKG_MANIFEST_DIR=${{ github.workspace }}/client/libcdoc cmake --build build --target msi cmake --build build --target msishellext diff --git a/.gitmodules b/.gitmodules index a73e2acd7..98c19ef37 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = ../qt-common [submodule "client/libcdoc"] path = client/libcdoc - url = git@github.com:open-eid/libcdoc.git + url = ../libcdoc diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index a5fb3ac20..161cf446b 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -41,6 +41,8 @@ add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE main.cpp Application.cpp Application.h + CDocSupport.cpp + CDocSupport.h CheckConnection.cpp CheckConnection.h CryptoDoc.cpp @@ -84,8 +86,6 @@ add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE TokenData.cpp TokenData.h Utils.h - dialogs/PasswordDialog.h dialogs/PasswordDialog.cpp dialogs/PasswordDialog.ui - CDocSupport.cpp CDocSupport.h ) qt_add_translations(${PROJECT_NAME} TS_FILES translations/en.ts @@ -129,7 +129,6 @@ set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_ICON_FILE Icon.icns MACOSX_BUNDLE_GUI_IDENTIFIER "ee.ria.${PROJECT_NAME}" ) -target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}) target_compile_definitions(${PROJECT_NAME} PRIVATE CDOC2_GET_URL="${CDOC2_GET_URL}" diff --git a/client/CheckConnection.cpp b/client/CheckConnection.cpp index 45f26105c..689521e9a 100644 --- a/client/CheckConnection.cpp +++ b/client/CheckConnection.cpp @@ -86,7 +86,7 @@ QNetworkAccessManager* CheckConnection::setupNAM(QNetworkRequest &req, const QBy qDebug() << "SSL Error:" << error.error() << error.certificate().subjectInfo(QSslCertificate::CommonName); } } - reply->ignoreSslErrors(errors); + reply->ignoreSslErrors(ignore); }); return nam; } diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index ad9c72ab4..95584fcec 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -160,9 +160,6 @@ void AddRecipients::addRecipient(const QSslCertificate& cert, bool select) AddressItem *leftItem = itemListValue(ui->leftPane, key); if(!leftItem) { - QByteArray qder = cert.toDer(); - std::vector sder = std::vector(qder.cbegin(), qder.cend()); - leftItem = new AddressItem(key, AddressItem::Add, ui->leftPane); ui->leftPane->addWidget(leftItem); diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index 08b0abbe6..c1724a5d2 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -17,10 +17,6 @@ * */ -#include -#include -#include - #include "AddressItem.h" #include "ui_AddressItem.h" @@ -28,6 +24,8 @@ #include "SslCertificate.h" #include "dialogs/KeyDialog.h" +#include + using namespace ria::qdigidoc4; class AddressItem::Private: public Ui::AddressItem diff --git a/client/widgets/AddressItem.h b/client/widgets/AddressItem.h index fe0d949e8..8a322e279 100644 --- a/client/widgets/AddressItem.h +++ b/client/widgets/AddressItem.h @@ -19,13 +19,12 @@ #pragma once -#include - #include "widgets/Item.h" -#include "cdoc/Lock.h" struct CDKey; +namespace libcdoc { struct Lock; } + class AddressItem final : public Item { Q_OBJECT diff --git a/client/widgets/AddressItem.ui b/client/widgets/AddressItem.ui index a6aaeeef5..a67c48600 100644 --- a/client/widgets/AddressItem.ui +++ b/client/widgets/AddressItem.ui @@ -154,7 +154,7 @@ icon: url(:/images/icon_remove_clicked.svg); - + 24 @@ -168,7 +168,7 @@ icon: url(:/images/icon_remove_clicked.svg); Remove - border: none; background: none; + border: none diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 291e9f3e3..4e95ee430 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -33,6 +33,7 @@ #include "dialogs/SmartIDDialog.h" #include "dialogs/WarningDialog.h" #include "widgets/AddressItem.h" +#include "widgets/MainAction.h" #include "widgets/SignatureItem.h" #include "widgets/WarningItem.h" @@ -534,4 +535,3 @@ void ContainerPage::translateLabels() ui->cancel->setText(tr(cancelText)); ui->convert->setText(tr(convertText)); } - diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index 36c0745ee..cd4fe1d77 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -19,16 +19,16 @@ #pragma once +#include + #include "common_enums.h" -#include "widgets/MainAction.h" -#include "CryptoDoc.h" -namespace Ui { -class ContainerPage; -} +namespace libcdoc { struct Lock; } +namespace Ui { class ContainerPage; } class CryptoDoc; class DigiDoc; +class MainAction; class QSslCertificate; class SignatureItem; class SslCertificate; From 816c17dcd9b7e7f8afe567ccb1719c4a74f6cf2e Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Tue, 10 Feb 2026 15:55:26 +0200 Subject: [PATCH 43/44] Adjust encryption flow Signed-off-by: Raul Metsma --- client/CDocSupport.h | 8 +- client/CryptoDoc.cpp | 150 +++++++++++++------------------ client/MainWindow.cpp | 48 +--------- client/MainWindow.h | 2 - client/common_enums.h | 3 +- client/widgets/ContainerPage.cpp | 54 ++++++++++- client/widgets/ContainerPage.h | 1 + 7 files changed, 124 insertions(+), 142 deletions(-) diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 1dab486f1..2e6883232 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -52,10 +52,10 @@ struct DDConfiguration : public libcdoc::Configuration { // struct DDCryptoBackend final : public libcdoc::CryptoBackend { - static constexpr int BACKEND_ERROR = -303; - static constexpr int PIN_CANCELED = -304; - static constexpr int PIN_INCORRECT = -305; - static constexpr int PIN_LOCKED = -306; + static constexpr int BACKEND_ERROR = -503; + static constexpr int PIN_CANCELED = -504; + static constexpr int PIN_INCORRECT = -505; + static constexpr int PIN_LOCKED = -506; libcdoc::result_t decryptRSA(std::vector &result, const std::vector &data, bool oaep, unsigned int idx) final; diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index a5eb195cd..53a5bf500 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -48,69 +48,16 @@ using namespace ria::qdigidoc4; Q_LOGGING_CATEGORY(CRYPTO, "CRYPTO") -auto toHex = [](const std::vector& data) -> QString { - return QByteArray::fromRawData(reinterpret_cast(data.data()), data.size()).toHex(); -}; - struct CryptoDoc::Private { bool isEncryptedWarning(const QString &title) const; - inline libcdoc::result_t encrypt() { - libcdoc::result_t res = waitFor([&]{ - qCDebug(CRYPTO) << "CryptoDoc::Private::encrypt, thread id: " << QThread::currentThreadId(); - qCDebug(CRYPTO) << "Encrypt" << fileName; - libcdoc::OStreamConsumer ofs(fileName.toStdString()); - if (ofs.isError()) - return (libcdoc::result_t) libcdoc::OUTPUT_ERROR; - StreamListSource slsrc(files); - std::vector enc_keys; - std::string keyserver_id; - if (Settings::CDOC2_DEFAULT && Settings::CDOC2_USE_KEYSERVER) { - keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; - } - for (auto &key : keys) { - QByteArray ba = key.rcpt_cert.toDer(); - if (keyserver_id.empty()) { - enc_keys.push_back( - libcdoc::Recipient::makeCertificate({}, {ba.cbegin(), ba.cend()})); - } else { - enc_keys.push_back( - libcdoc::Recipient::makeServer({}, {ba.cbegin(), ba.cend()}, keyserver_id)); - } - } - if (!crypto.secret.empty()) { - auto key = libcdoc::Recipient::makeSymmetric(label.toStdString(), 65536); - enc_keys.push_back(key); - } - libcdoc::CDocWriter *writer = libcdoc::CDocWriter::createWriter( - Settings::CDOC2_DEFAULT ? 2 : 1, &ofs, false, &conf, &crypto, &network); - libcdoc::result_t result = writer->encrypt(slsrc, enc_keys); - if (result != libcdoc::OK) { - writer_last_error = writer->getLastErrorStr(); - std::filesystem::remove(std::filesystem::path(fileName.toStdString())); - } - qCDebug(CRYPTO) << "Encryption result: " << result << " " << QString::fromStdString(writer->getLastErrorStr()); - delete writer; - ofs.close(); - if (result == libcdoc::OK) { - // Encryption successful, open new reader - reader = createCDocReader(fileName.toStdString()); - } - return result; - }); - return res; - } - std::unique_ptr reader; - std::string writer_last_error; QString fileName; bool isEncrypted() const { return reader != nullptr; } CDocumentModel *documents = new CDocumentModel(this); QStringList tempFiles; - // Encryption data - QString label; // libcdoc handlers DDConfiguration conf; @@ -120,23 +67,14 @@ struct CryptoDoc::Private std::vector files; std::vector keys; - const std::vector &getFiles() { - return files; - } - std::unique_ptr createCDocReader(const std::string& filename) { - std::unique_ptr r(libcdoc::CDocReader::createReader(filename, &conf, &crypto, &network)); - if (!r) { - WarningDialog::create() - ->withTitle(CryptoDoc::tr("Failed to open document")) - ->withText(CryptoDoc::tr("Unsupported file format")) - ->open(); - return nullptr; - } + void createCDocReader(const QString &filename) { + reader = std::unique_ptr(libcdoc::CDocReader::createReader(filename.toStdString(), &conf, &crypto, &network)); + if (!reader) + return; keys.clear(); - for (auto& key : r->getLocks()) { + for (auto& key : reader->getLocks()) { keys.push_back({key, QSslCertificate()}); } - return r; } }; @@ -205,17 +143,17 @@ QString CDocumentModel::copy(int row, const QString &dst) const QString CDocumentModel::data(int row) const { - return FileDialog::normalized(QString::fromStdString(d->getFiles().at(row).name)); + return FileDialog::normalized(QString::fromStdString(d->files.at(row).name)); } quint64 CDocumentModel::fileSize(int row) const { - return d->getFiles().at(row).size; + return d->files.at(row).size; } QString CDocumentModel::mime(int row) const { - return FileDialog::normalized(QString::fromStdString(d->getFiles().at(row).mime)); + return FileDialog::normalized(QString::fromStdString(d->files.at(row).mime)); } void CDocumentModel::open(int row) @@ -255,7 +193,7 @@ bool CDocumentModel::removeRow(int row) int CDocumentModel::rowCount() const { - return int(d->getFiles().size()); + return int(d->files.size()); } QString CDocumentModel::save(int row, const QString &path) const @@ -322,7 +260,6 @@ void CryptoDoc::clear( const QString &file ) d->tempFiles.clear(); d->reader.reset(); d->fileName = file; - d->writer_last_error.clear(); d->files.clear(); } @@ -425,7 +362,6 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) d->files = std::move(cons.files); // Success, immediately create writer from reader d->keys.clear(); - d->writer_last_error.clear(); d->reader.reset(); return !d->isEncrypted(); } @@ -434,6 +370,9 @@ DocumentModel *CryptoDoc::documentModel() const { return d->documents; } bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QByteArray& secret) { + // I think the correct semantics is to fail if container is already encrypted + if(d->reader) + return false; if( !filename.isEmpty() ) d->fileName = filename; if( d->fileName.isEmpty() ) @@ -444,15 +383,7 @@ bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QB ->open(); return false; } - // I think the correct semantics is to fail if container is already encrypted - if(d->reader) return false; - if (!secret.isEmpty()) { - // Encrypt with symmetric key - d->label = label; - d->crypto.secret.assign(secret.cbegin(), secret.cend()); - } - // Encrypt for address list - else if(d->keys.empty()) + if(secret.isEmpty() && d->keys.empty()) { WarningDialog::create() ->withTitle(tr("Failed to encrypt document")) @@ -460,15 +391,54 @@ bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QB ->open(); return false; } - libcdoc::result_t result = d->encrypt(); - d->label.clear(); + QString writer_last_error; + libcdoc::result_t result = waitFor([&] -> libcdoc::result_t { + qCDebug(CRYPTO) << "Encrypt" << d->fileName; + auto writer = std::unique_ptr(libcdoc::CDocWriter::createWriter( + Settings::CDOC2_DEFAULT ? 2 : 1, d->fileName.toStdString(), &d->conf, &d->crypto, &d->network)); + if (!writer) + return libcdoc::OUTPUT_ERROR; + std::vector enc_keys; + std::string keyserver_id; + if (Settings::CDOC2_DEFAULT && Settings::CDOC2_USE_KEYSERVER) { + keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; + } + // Encrypt for address list + for (const auto &key : d->keys) { + QByteArray ba = key.rcpt_cert.toDer(); + enc_keys.push_back(keyserver_id.empty() ? + libcdoc::Recipient::makeCertificate({}, {ba.cbegin(), ba.cend()}) : + libcdoc::Recipient::makeServer({}, {ba.cbegin(), ba.cend()}, keyserver_id)); + } + // Encrypt with symmetric key + if (!secret.isEmpty()) { + // NIST recommends at least 600000 iterations for PBKDF2 with SHA-256, see https://csrc.nist.gov/publications/detail/sp/800-132/final + d->crypto.secret.assign(secret.cbegin(), secret.cend()); + enc_keys.push_back(libcdoc::Recipient::makeSymmetric(label.toStdString(), 600000)); + } + StreamListSource slsrc(d->files); + libcdoc::result_t result = writer->encrypt(slsrc, enc_keys); + writer_last_error = QString::fromStdString(writer->getLastErrorStr()); + qCDebug(CRYPTO) << "Encryption result: " << result << ' ' << writer_last_error; + if (result == libcdoc::OK) // Encryption successful, open new reader + d->createCDocReader(d->fileName); + else if(QFile::exists(d->fileName)) + QFile::remove(d->fileName); + return result; + }); d->crypto.secret.clear(); if (result != libcdoc::OK) { WarningDialog::create() ->withTitle(tr("Failed to encrypt document")) ->withText(tr("Please check your internet connection and network settings.")) - ->withDetails(QString::fromStdString(d->writer_last_error)) + ->withDetails(writer_last_error) + ->open(); + } else if(!d->reader) { + WarningDialog::create() + ->withTitle(CryptoDoc::tr("Failed to open document")) + ->withText(CryptoDoc::tr("Unsupported file format")) ->open(); + return false; } return d->isEncrypted(); @@ -492,11 +462,15 @@ bool CryptoDoc::move(const QString &to) bool CryptoDoc::open(const QString &file) { - d->writer_last_error.clear(); clear(file); - d->reader = d->createCDocReader(file.toStdString()); - if (!d->reader) + d->createCDocReader(file); + if(!d->reader) { + WarningDialog::create() + ->withTitle(CryptoDoc::tr("Failed to open document")) + ->withText(CryptoDoc::tr("Unsupported file format")) + ->open(); return false; + } std::vector files = CDocSupport::getCDocFileList(file); for (auto& f : files) { d->files.push_back({f.name, {}, f.size, {}}); diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index b66651beb..ccd7b9e44 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -223,38 +223,6 @@ QStringList MainWindow::dropEventFiles(QDropEvent *event) return files; } -bool MainWindow::encrypt(bool askForKey) -{ - if(!cryptoDoc) - return false; - - if(!FileDialog::fileIsWritable(cryptoDoc->fileName())) - { - auto *dlg = WarningDialog::create(this) - ->withTitle(CryptoDoc::tr("Failed to encrypt document")) - ->withText(tr("Cannot alter container %1. Save different location?").arg(FileDialog::normalized(cryptoDoc->fileName()))) - ->addButton(WarningDialog::YES, QMessageBox::Yes); - if(dlg->exec() != QMessageBox::Yes) - return false; - QString to = FileDialog::getSaveFileName(this, FileDialog::tr("Save file"), cryptoDoc->fileName()); - if(to.isNull() || !cryptoDoc->move(to)) - return false; - ui->cryptoContainerPage->setHeader(to); - } - - if(!askForKey) { - WaitDialogHolder waitDialog(this, tr("Encrypting")); - return cryptoDoc->encrypt(); - } - - PasswordDialog p(PasswordDialog::Mode::ENCRYPT, this); - if(!p.exec()) - return false; - - WaitDialogHolder waitDialog(this, tr("Encrypting")); - return cryptoDoc->encrypt(cryptoDoc->fileName(), p.label(), p.secret()); -} - void MainWindow::mouseReleaseEvent(QMouseEvent *event) { if(auto *cardPopup = findChild()) @@ -408,21 +376,11 @@ void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString if(cryptoDoc && wrap(cryptoDoc->fileName(), false)) FadeInNotification::success(ui->topBar, tr("Converted to signed document!")); break; - case DecryptedContainer: + case DecryptContainerSuccess: FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); break; - case EncryptContainer: - if(encrypt()) - { - ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->signer()->tokenauth().cert()); - FadeInNotification::success(ui->topBar, tr("Encryption succeeded!")); - } - break; - case EncryptLT: - if(encrypt(true)) { - ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->signer()->tokenauth().cert()); - FadeInNotification::success(ui->topBar, tr("Encryption succeeded!")); - } + case EncryptContainerSuccess: + FadeInNotification::success(ui->topBar, tr("Encryption succeeded!")); break; case ClearCryptoWarning: ui->crypto->warningIcon(false); diff --git a/client/MainWindow.h b/client/MainWindow.h index ce6887c80..9b1ab95bc 100644 --- a/client/MainWindow.h +++ b/client/MainWindow.h @@ -58,8 +58,6 @@ class MainWindow final : public QWidget void changePinClicked(QSmartCardData::PinType type, QSmartCard::PinAction action); void convertToCDoc(); ria::qdigidoc4::ContainerState currentState(); - bool encrypt(bool askForKey = false); - void loadPicture(); void navigateToPage( ria::qdigidoc4::Pages page, const QStringList &files = QStringList(), bool create = true ); void onCryptoAction(int action, const QString &id, const QString &phone); void onSignAction(int action, const QString &idCode, const QString &info2); diff --git a/client/common_enums.h b/client/common_enums.h index af4f8ed6d..b51113adf 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -42,9 +42,10 @@ enum Actions : unsigned char { ContainerEncrypt, EncryptContainer, + EncryptContainerSuccess, DecryptContainer, DecryptToken, - DecryptedContainer, + DecryptContainerSuccess, SignatureAdd, SignatureMobile, diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 4e95ee430..af7754b43 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -20,9 +20,11 @@ #include "ContainerPage.h" #include "ui_ContainerPage.h" +#include "Application.h" #include "CryptoDoc.h" #include "DigiDoc.h" #include "PrintSheet.h" +#include "QSigner.h" #include "Settings.h" #include "SslCertificate.h" #include "TokenData.h" @@ -210,7 +212,7 @@ void ContainerPage::decrypt(CryptoDoc *container, const libcdoc::Lock *lock, con if (!container->decrypt(lock, secret)) return; transition(container, QSslCertificate{}); - emit action(DecryptedContainer, {}, {}); + emit action(DecryptContainerSuccess, {}, {}); } template @@ -236,6 +238,42 @@ void ContainerPage::deleteConfirm(C *c, int index) emit action(ContainerClose); } +void ContainerPage::encrypt(CryptoDoc *container, bool longTerm) +{ + if(!FileDialog::fileIsWritable(container->fileName())) + { + auto *dlg = WarningDialog::create(this) + ->withTitle(CryptoDoc::tr("Failed to encrypt document")) + ->withText(tr("Cannot alter container %1. Save different location?").arg(FileDialog::normalized(container->fileName()))) + ->addButton(WarningDialog::YES, QMessageBox::Yes); + if(dlg->exec() != QMessageBox::Yes) + return; + QString to = FileDialog::getSaveFileName(this, FileDialog::tr("Save file"), container->fileName()); + if(to.isNull() || !container->move(to)) + return; + setHeader(to); + } + + if(!longTerm) { + WaitDialogHolder waitDialog(this, tr("Encrypting")); + if(!container->encrypt(container->fileName(), {}, {})) + return; + transition(container, qApp->signer()->tokenauth().cert()); + emit action(EncryptContainerSuccess, {}, {}); + return; + } + + PasswordDialog p(PasswordDialog::Mode::ENCRYPT, this); + if(!p.exec()) + return; + + WaitDialogHolder waitDialog(this, tr("Encrypting")); + if(!container->encrypt(container->fileName(), p.label(), p.secret())) + return; + transition(container, QSslCertificate{}); + emit action(EncryptContainerSuccess, {}, {}); +} + void ContainerPage::setHeader(const QString &file) { fileName = QDir::toNativeSeparators (file); @@ -323,8 +361,20 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert }); disconnect(mainAction, &MainAction::action, container, nullptr); connect(mainAction, &MainAction::action, container, [container, this](int action) { - if(action == DecryptToken || action == DecryptContainer) + switch (action) + { + case EncryptContainer: + encrypt(container, false); + break; + case EncryptLT: + encrypt(container, true); + break; + case DecryptToken: + case DecryptContainer: decrypt(container, nullptr, {}); + break; + default: + } }); clear(ClearCryptoWarning); diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index cd4fe1d77..d047e45e5 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -65,6 +65,7 @@ class ContainerPage final : public QWidget template void deleteConfirm(C *c, int index); void elideFileName(); + void encrypt(CryptoDoc *container, bool longTerm); bool eventFilter(QObject *o, QEvent *e) final; void showMainAction(const QList &actions); void showSigningButton(); From 91365182a4197f74e3250c440a70ef46d854847d Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Thu, 12 Feb 2026 14:13:05 +0200 Subject: [PATCH 44/44] Update libcdoc Signed-off-by: Raul Metsma --- client/dialogs/KeyDialog.cpp | 4 ++-- client/libcdoc | 2 +- client/widgets/AddressItem.cpp | 20 ++++++++------------ 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/client/dialogs/KeyDialog.cpp b/client/dialogs/KeyDialog.cpp index 945ad2a88..44a9dc174 100644 --- a/client/dialogs/KeyDialog.cpp +++ b/client/dialogs/KeyDialog.cpp @@ -63,8 +63,8 @@ KeyDialog::KeyDialog(const CDKey &k, QWidget *parent ) QSslCertificate cert; if (!k.rcpt_cert.isNull()) { cert = k.rcpt_cert; - } else if (k.lock.isCertificate()) { - std::vector certData = k.lock.getBytes(libcdoc::Lock::Params::CERT); + } else if (k.lock.isCDoc1()) { + const std::vector &certData = k.lock.getBytes(libcdoc::Lock::Params::CERT); cert = QSslCertificate(QByteArray::fromRawData(reinterpret_cast(certData.data()), certData.size()), QSsl::Der); } if (!cert.isNull()) { diff --git a/client/libcdoc b/client/libcdoc index 96bbd265f..14d60f243 160000 --- a/client/libcdoc +++ b/client/libcdoc @@ -1 +1 @@ -Subproject commit 96bbd265facca710128d7aa5e635b5ddeb181501 +Subproject commit 14d60f243afff75217b7bfb34cc99d6b252954a2 diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index c1724a5d2..aed92d262 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -119,11 +119,10 @@ const CDKey& AddressItem::getKey() const } void AddressItem::idChanged(const SslCertificate &cert) { - if (ui->key.lock.isValid()) { - QSslKey pkey = cert.publicKey(); - QByteArray der = pkey.toDer(); - ui->yourself = ui->key.lock.hasTheSameKey( - std::vector(der.cbegin(), der.cend())); + ui->yourself = false; + if (ui->key.lock.isPKI()) { + const auto &key = ui->key.lock.getBytes(libcdoc::Lock::RCPT_KEY); + ui->yourself = cert.publicKey().toDer() == QByteArray::fromRawData((const char*)key.data(), key.size()); } setName(); } @@ -189,18 +188,15 @@ void AddressItem::setIdType() { if (!ui->key.rcpt_cert.isNull()) { // Recipient certificate - SslCertificate cert(ui->key.rcpt_cert); - setIdType(cert); + setIdType(SslCertificate(ui->key.rcpt_cert)); } else if (ui->key.lock.isValid()) { // Known lock type // Needed to include translation for "ID-CARD" void(QT_TR_NOOP("ID-CARD")); auto items = libcdoc::Recipient::parseLabel(ui->key.lock.label); - if (ui->key.lock.isCertificate()) { - auto bytes = ui->key.lock.getBytes(libcdoc::Lock::CERT); - QByteArray qbytes((const char *)bytes.data(), bytes.size()); - SslCertificate cert(qbytes, QSsl::Der); - setIdType(cert); + if (ui->key.lock.isCDoc1()) { + const auto &bytes = ui->key.lock.getBytes(libcdoc::Lock::CERT); + setIdType(SslCertificate(QByteArray::fromRawData((const char *)bytes.data(), bytes.size()), QSsl::Der)); } else { ui->idType->setText(tr(items["type"].data())); }