diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e46a83b58..f5553e0e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,13 +5,13 @@ 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 runs-on: macos-latest env: - MACOSX_DEPLOYMENT_TARGET: 13.0 + MACOSX_DEPLOYMENT_TARGET: 13.3 LIBS_PATH: ${{ github.workspace }}/cache steps: - &Checkout @@ -186,7 +186,8 @@ jobs: VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkg_cache,readwrite run: | cmake "-GNinja" -B build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo ` - -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake + -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/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..98c19ef37 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "common"] path = common url = ../qt-common +[submodule "client/libcdoc"] + path = client/libcdoc + url = ../libcdoc diff --git a/CMakeLists.txt b/CMakeLists.txt index de663fc88..b7c425aff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,8 +47,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/README.md b/README.md index bd8785729..46468db21 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ 3. Configure cmake -B build -S . \ - -DCMAKE_PREFIX_PATH=~/cmake_builds/Qt-6.6.3-OpenSSL + -DCMAKE_PREFIX_PATH=~/cmake_builds/Qt-6.10.2-OpenSSL -DOPENSSL_ROOT_DIR=~/cmake_build/OpenSSL \ -DLDAP_ROOT=~/cmake_build/OpenLDAP \ -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" @@ -81,10 +81,11 @@ ### Windows 1. Install dependencies from - * [Visual Studio Community 2019](https://www.visualstudio.com/downloads/) + * [Visual Studio Community 2022](https://www.visualstudio.com/downloads/) * [http://www.cmake.org](http://www.cmake.org) * [http://qt-project.org](http://qt-project.org) * [libdigidocpp-*.msi](https://github.com/open-eid/libdigidocpp/releases) + * [vcpkg](https://vcpkg.io/) 2. Fetch the source @@ -93,7 +94,10 @@ 3. Configure - cmake -G"NMAKE Makefiles" -DCMAKE_PREFIX_PATH=C:\Qt\6.6.3\msvc2019_x64 -DLibDigiDocpp_ROOT="C:\Program Files (x86)\libdigidocpp" -B build -S . + cmake -G"NMAKE Makefiles" -B build -S . ` + -DCMAKE_PREFIX_PATH=C:\Qt\6.10.2\msvc2022_x64 ` + -DLibDigiDocpp_ROOT="C:\Program Files (x86)\libdigidocpp" ` + -DVCPKG_MANIFEST_DIR=client/libcdoc 4. Build diff --git a/client/Application.cpp b/client/Application.cpp index 553f2dd74..4b956e928 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" @@ -471,6 +472,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 202aeac8a..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 -#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(QCoreApplication::translate("QSigner", "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/CDocSupport.cpp b/client/CDocSupport.cpp new file mode 100644 index 000000000..b2923ee91 --- /dev/null +++ b/client/CDocSupport.cpp @@ -0,0 +1,434 @@ +/* + * 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 +#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" + +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) +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 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) +{ + std::vector files; + int version = libcdoc::CDocReader::getCDocFileVersion(filename.toStdString()); + if (version != 1) return files; + QFile ifs(filename); + if(!ifs.open(QIODevice::ReadOnly)) + return files; + 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; +} + +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 getDecryptStatus(result, pin_status); +} + +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} +}; + +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 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 getDecryptStatus(dst, pin_status); +} + +libcdoc::result_t +DDCryptoBackend::getSecret(std::vector& _secret, unsigned int idx) +{ + _secret = secret; + return libcdoc::OK; +} + +std::string +DDCryptoBackend::getLastErrorStr(libcdoc::result_t code) const +{ + 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); +} + +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(QLatin1String(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(QLatin1String(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, 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; + } + QScopedPointer nam(CheckConnection::setupNAM(req, Settings::CDOC2_POST_CERT)); + 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()); + 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; + } + 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; + } + dst.transaction_id = tr_id.toStdString(); + + 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; +}; + +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), QLatin1String(transaction_id.c_str()))); + 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: + Q_FATAL(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) noexcept { + 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() noexcept { + 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() noexcept +{ + if (files.empty()) return false; + const 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) +{ +} + +libcdoc::result_t +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); +} + +bool +StreamListSource::isError() noexcept +{ + if ((_current < 0) || (_current >= _files.size())) return 0; + return _files[_current].data->isReadable(); +} + +bool +StreamListSource::isEof() noexcept +{ + 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..2e6883232 --- /dev/null +++ b/client/CDocSupport.h @@ -0,0 +1,177 @@ +/* + * QDigiDoc4 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include +#include + +#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 final : public libcdoc::CryptoBackend { + 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; + 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) final; + libcdoc::result_t deriveHMACExtract(std::vector &dst, + const std::vector &publicKey, + const std::vector &salt, + unsigned int idx) final; + libcdoc::result_t getSecret(std::vector &secret, + unsigned int idx) final; + std::string getLastErrorStr(libcdoc::result_t code) const final; + + std::vector secret; + + explicit DDCryptoBackend() = default; +}; + +// +// NetworkBackend +// +// Bridges to QNetworkAccessManager +// + +struct DDNetworkBackend final : 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, + uint64_t expiry_ts) final; + libcdoc::result_t + fetchKey(std::vector &result, const std::string &keyserver_id, + const std::string &transaction_id) final; + + libcdoc::result_t + getClientTLSCertificate(std::vector &dst) final { + return libcdoc::NOT_IMPLEMENTED; + } + libcdoc::result_t getPeerTLSCertificates( + std::vector> &dst) final { + return libcdoc::NOT_IMPLEMENTED; + } + + explicit DDNetworkBackend() = default; + + std::string last_error; +}; + +// +// ILogger +// +// Bridges to Qt logging system +// + +class DDCDocLogger final : 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) 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 final : 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) noexcept final; + libcdoc::result_t close() noexcept final; + bool isError() noexcept final; + libcdoc::result_t open(const std::string &name, + int64_t size) final; + + size_t _max_memory_size; + std::vector files; +}; + +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() final; + libcdoc::result_t next(std::string &name, int64_t &size) final; + + const std::vector &_files; + int64_t _current = -1; +}; diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 2f4722a77..315d95ee4 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -15,6 +15,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" @@ -33,14 +36,10 @@ add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE main.cpp Application.cpp Application.h - CDoc1.cpp - CDoc1.h - CDoc2.cpp - CDoc2.h + CDocSupport.cpp + CDocSupport.h CheckConnection.cpp CheckConnection.h - Crypto.cpp - Crypto.h CryptoDoc.cpp CryptoDoc.h DateTime.cpp @@ -104,8 +103,7 @@ target_link_libraries(${PROJECT_NAME} Qt6::SvgWidgets digidocpp::digidocpp ${LDAP_LIBRARIES} - $ - ZLIB::ZLIB + cdoc ) if(NOT BUILD_DATE) @@ -123,6 +121,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_ICON_FILE Icon.icns MACOSX_BUNDLE_GUI_IDENTIFIER "ee.ria.${PROJECT_NAME}" ) + target_compile_definitions(${PROJECT_NAME} PRIVATE CDOC2_GET_URL="${CDOC2_GET_URL}" CDOC2_POST_URL="${CDOC2_POST_URL}" @@ -133,25 +132,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 RESOURCE_FILES mac/*.icns mac/*.lproj) target_sources(${PROJECT_NAME} PRIVATE ${RESOURCE_FILES} Application_mac.mm MacMenuBar.cpp MacMenuBar.h dialogs/CertificateDetails_mac.mm) diff --git a/client/Crypto.cpp b/client/Crypto.cpp deleted file mode 100644 index e41983f1e..000000000 --- a/client/Crypto.cpp +++ /dev/null @@ -1,368 +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 -#include -#include -#include - -#include -#include - -Q_LOGGING_CATEGORY(CRYPTO,"CRYPTO") - -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, bool encrypt) -{ - Cipher c(key.size() == 32 ? EVP_aes_256_wrap() : EVP_aes_128_wrap(), key, {}, encrypt); - 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; -} - -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::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)); - 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::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); - if(isError(RAND_bytes(puchar(out.data()), int(out.size())))) - 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; -} diff --git a/client/Crypto.h b/client/Crypto.h deleted file mode 100644 index 7ed6d00ca..000000000 --- a/client/Crypto.h +++ /dev/null @@ -1,75 +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 - -#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, bool encrypt); - 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); - -private: - static bool isError(int err); -}; diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 3fbc8e1e6..53a5bf500 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -20,19 +20,15 @@ #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" #include "Settings.h" -#include "SslCertificate.h" #include "Utils.h" #include "dialogs/FileDialog.h" #include "dialogs/WarningDialog.h" -#include #include #include #include @@ -43,56 +39,55 @@ #include #include +#include +#include +#include +#include + using namespace ria::qdigidoc4; -class CryptoDoc::Private final: public QThread +Q_LOGGING_CATEGORY(CRYPTO, "CRYPTO") + +struct CryptoDoc::Private { - Q_OBJECT -public: bool isEncryptedWarning(const QString &title) const; - void run() final; - inline void waitForFinished() - { - QEventLoop e; - connect(this, &Private::finished, &e, &QEventLoop::quit); - start(); - e.exec(); - } - std::unique_ptr cdoc; + std::unique_ptr reader; + QString fileName; - QByteArray key; - bool isEncrypted = false; + bool isEncrypted() const { return reader != nullptr; } CDocumentModel *documents = new CDocumentModel(this); QStringList tempFiles; + + // libcdoc handlers + DDConfiguration conf; + DDCryptoBackend crypto; + DDNetworkBackend network; + + std::vector files; + std::vector keys; + + 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 : reader->getLocks()) { + keys.push_back({key, QSslCertificate()}); + } + } }; bool CryptoDoc::Private::isEncryptedWarning(const QString &title) const { - if( fileName.isEmpty() ) + if(fileName.isEmpty() ) WarningDialog::create()->withTitle(title)->withText(CryptoDoc::tr("Container is not open"))->open(); - if(isEncrypted) + if(isEncrypted()) WarningDialog::create()->withTitle(title)->withText(CryptoDoc::tr("Container is encrypted"))->open(); - return fileName.isEmpty() || isEncrypted; -} - -void CryptoDoc::Private::run() -{ - if(isEncrypted) - { - qCDebug(CRYPTO) << "Decrypt" << fileName; - isEncrypted = !cdoc->decryptPayload(key); - } - else - { - qCDebug(CRYPTO) << "Encrypt" << fileName; - isEncrypted = cdoc->save(fileName); - } + return fileName.isEmpty() || isEncrypted(); } -CDocumentModel::CDocumentModel(CryptoDoc::Private *doc) -: d( doc ) -{} +CDocumentModel::CDocumentModel(CryptoDoc::Private *doc) : d(doc) {} bool CDocumentModel::addFile(const QString &file, const QString &mime) { @@ -103,7 +98,7 @@ bool CDocumentModel::addFile(const QString &file, const QString &mime) if(!addFileCheck(d->fileName, info)) return false; - if(d->cdoc->version() == 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")) @@ -116,14 +111,13 @@ bool CDocumentModel::addFile(const QString &file, const QString &mime) auto data = std::make_unique(file); if(!data->open(QFile::ReadOnly)) return false; - 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; } @@ -134,7 +128,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); @@ -149,22 +143,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->files.at(row).name)); } quint64 CDocumentModel::fileSize(int row) const { - return d->cdoc->files.at(row).size; + return d->files.at(row).size; } QString CDocumentModel::mime(int row) const { - return FileDialog::normalized(d->cdoc->files.at(row).mime); + return FileDialog::normalized(QString::fromStdString(d->files.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)) @@ -184,7 +178,7 @@ bool CDocumentModel::removeRow(int row) if(d->isEncryptedWarning(DocumentModel::tr("Failed remove document from container"))) return false; - if(d->cdoc->files.empty() || row >= d->cdoc->files.size()) + if(d->files.empty() || row >= d->files.size()) { WarningDialog::create() ->withTitle(DocumentModel::tr("Failed remove document from container")) @@ -193,18 +187,18 @@ bool CDocumentModel::removeRow(int row) return false; } - d->cdoc->files.erase(d->cdoc->files.cbegin() + row); + d->files.erase(d->files.cbegin() + row); return true; } int CDocumentModel::rowCount() const { - return int(d->cdoc->files.size()); + return int(d->files.size()); } QString CDocumentModel::save(int row, const QString &path) const { - if(d->isEncrypted) + if(d->isEncrypted()) return {}; QString fileName = copy(row, path); @@ -215,95 +209,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) @@ -314,25 +219,34 @@ CryptoDoc::CryptoDoc( QObject *parent ) CryptoDoc::~CryptoDoc() { clear(); delete d; } -bool CryptoDoc::addKey( const CKey &key ) +bool +CryptoDoc::supportsSymmetricKeys() const { + return !d->reader && Settings::CDOC2_DEFAULT; +} + +bool CryptoDoc::addEncryptionKey(const QSslCertificate &cert) { if(d->isEncryptedWarning(tr("Failed to add key"))) return false; - if(d->cdoc->keys.contains(key)) - { - WarningDialog::create() - ->withTitle(tr("Failed to add key")) - ->withText(tr("Key already exists")) - ->open(); - return false; + for (auto &k : d->keys) { + if (k.rcpt_cert == cert) { + WarningDialog::create() + ->withTitle(tr("Failed to add key")) + ->withText(tr("Key already exists")) + ->open(); + return false; + } } - d->cdoc->keys.append(key); + 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(); + return d->reader->getLockForCert( + std::vector(der.cbegin(), der.cend())) >= 0; } void CryptoDoc::clear( const QString &file ) @@ -344,22 +258,19 @@ 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->files.clear(); } ContainerState CryptoDoc::state() const { - return d->isEncrypted ? EncryptedContainer : UnencryptedContainer; + return d->isEncrypted() ? EncryptedContainer : UnencryptedContainer; } -bool CryptoDoc::decrypt() +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")) @@ -367,12 +278,32 @@ bool CryptoDoc::decrypt() ->open(); return false; } - if(!d->isEncrypted) - return true; - CKey key = d->cdoc->canDecrypt(qApp->signer()->tokenauth().cert()); - if(key.key.isEmpty()) - { + 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::create() + ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withText(tr("You do not have the key to decrypt this document")) + ->open(); + 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::create() ->withTitle(QSigner::tr("Failed to decrypt document")) ->withText(tr("You do not have the key to decrypt this document")) @@ -380,8 +311,9 @@ bool CryptoDoc::decrypt() return false; } - if(d->cdoc->version() == 2 && !key.transaction_id.isEmpty() && !Settings::CDOC2_NOTIFICATION.isSet()) - { + if (d->reader->version == 2 && + (lock->type == libcdoc::Lock::Type::SERVER) && + !Settings::CDOC2_NOTIFICATION.isSet()) { auto *dlg = WarningDialog::create() ->withTitle(tr("You must enter your PIN code twice in order to decrypt the CDOC2 container")) ->withText(tr( @@ -396,43 +328,51 @@ 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()) - { + d->crypto.secret.assign(secret.cbegin(), secret.cend()); + + 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() ->withTitle(QSigner::tr("Failed to decrypt document")) ->withText(tr("Please check your internet connection and network settings.")) - ->withDetails(d->cdoc->lastError) + ->withDetails(QString::fromStdString(msg)) ->open(); return false; } - d->waitForFinished(); - if(d->isEncrypted) - WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) - ->withText(tr("Error parsing document")) - ->open(); - if(!d->cdoc->lastError.isEmpty()) - WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) - ->withDetails(d->cdoc->lastError) - ->open(); - - return !d->isEncrypted; + d->files = std::move(cons.files); + // Success, immediately create writer from reader + d->keys.clear(); + d->reader.reset(); + 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) { + // 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() ) @@ -443,9 +383,7 @@ bool CryptoDoc::encrypt( const QString &filename ) ->open(); return false; } - if(d->isEncrypted) - return true; - if(d->cdoc->keys.isEmpty()) + if(secret.isEmpty() && d->keys.empty()) { WarningDialog::create() ->withTitle(tr("Failed to encrypt document")) @@ -453,57 +391,104 @@ bool CryptoDoc::encrypt( const QString &filename ) ->open(); return false; } - - d->waitForFinished(); - if(d->isEncrypted) - open(d->fileName); - else + 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(d->cdoc->lastError) + ->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 d->isEncrypted; + return false; + } + + 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()) return false; d->fileName = to; return true; } -bool CryptoDoc::open( const QString &file ) +bool CryptoDoc::open(const QString &file) { 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()) - { + d->createCDocReader(file); + if(!d->reader) { WarningDialog::create() - ->withTitle(tr("Failed to open document")) - ->withDetails(d->cdoc->lastError) + ->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, {}}); + } Application::addRecent( file ); return true; } -void CryptoDoc::removeKey( int id ) +void CryptoDoc::removeKey(unsigned int id) { if(!d->isEncryptedWarning(tr("Failed to remove key"))) - d->cdoc->keys.removeAt(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) @@ -514,5 +499,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 cdf1683cf..e7bfeed27 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -23,64 +23,28 @@ #include "DocumentModel.h" #include +#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; -}; +Q_DECLARE_LOGGING_CATEGORY(CRYPTO) +// +// 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,22 +54,24 @@ 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 = {}); 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; private: - class Private; + struct Private; Private *d; friend class CDocumentModel; diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index 50c691af3..ccd7b9e44 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -32,6 +32,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" @@ -222,30 +223,6 @@ QStringList MainWindow::dropEventFiles(QDropEvent *event) return files; } -bool MainWindow::encrypt() -{ - if(!cryptoDoc) - return false; - - while (!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); - } - - WaitDialogHolder waitDialog(this, tr("Encrypting")); - - return cryptoDoc->encrypt(); -} - void MainWindow::mouseReleaseEvent(QMouseEvent *event) { if(auto *cardPopup = findChild()) @@ -366,15 +343,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()); + } cryptoDoc = std::move(cryptoContainer); digiDoc.reset(); @@ -397,25 +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 DecryptContainer: - case DecryptToken: - { - if(!cryptoDoc) - break; - WaitDialogHolder waitDialog(this, tr("Decrypting")); - if(cryptoDoc->decrypt()) - { - ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->signer()->tokenauth().cert()); - FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); - } + 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!")); - } + 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 3fc981369..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(); - 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/QPKCS11.cpp b/client/QPKCS11.cpp index 1afb68488..39621b48b 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" @@ -28,6 +28,13 @@ #include "dialogs/PinPopup.h" #include +#include + +#include + +#include +#include +#include #include @@ -143,12 +150,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 @@ -211,31 +259,28 @@ QPKCS11::PinStatus QPKCS11::login(const TokenData &t) if(!d->isFinDriver && !(token.flags & CKF_LOGIN_REQUIRED)) return PinOK; - CK_RV err = CKR_OK; SslCertificate cert(t.cert()); bool isSign = cert.keyUsage().keys().contains(SslCertificate::NonRepudiation); PinPopup::TokenFlags f; if(token.flags & CKF_USER_PIN_LOCKED) return PinLocked; if(token.flags & CKF_USER_PIN_COUNT_LOW) f = PinPopup::PinCountLow; if(token.flags & CKF_USER_PIN_FINAL_TRY) f = PinPopup::PinFinalTry; - if(token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) - { - f |= PinPopup::PinpadFlag; - PinPopup p(isSign ? QSmartCardData::Pin2Type : QSmartCardData::Pin1Type, f, cert, Application::mainWindow()); - p.open(); - p.startTimer(); - err = waitFor(d->f->C_Login, d->session, CKU_USER, nullptr, 0); - p.accept(); - } - else - { - PinPopup p(isSign ? QSmartCardData::Pin2Type : QSmartCardData::Pin1Type, f, cert, Application::mainWindow()); - p.setPinLen(token.ulMinPinLen, token.ulMaxPinLen < 12 ? 12 : token.ulMaxPinLen); - if( !p.exec() ) - return PinCanceled; - QByteArray pin = p.pin().toUtf8(); - err = d->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pin.constData()), CK_ULONG(pin.size())); - } + CK_RV err = dispatchToMain([&]{ + if(token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) { + f |= PinPopup::PinpadFlag; + PinPopup p(isSign ? QSmartCardData::Pin2Type : QSmartCardData::Pin1Type, f, cert, Application::mainWindow()); + p.open(); + p.startTimer(); + return waitFor(d->f->C_Login, d->session, CKU_USER, nullptr, 0); + } else { + PinPopup p(isSign ? QSmartCardData::Pin2Type : QSmartCardData::Pin1Type, f, cert, Application::mainWindow()); + p.setPinLen(token.ulMinPinLen, token.ulMaxPinLen < 12 ? 12 : token.ulMaxPinLen); + if(!p.exec()) + return CKR_FUNCTION_CANCELED; + QByteArray pin = p.pin().toUtf8(); + return d->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pin.constData()), CK_ULONG(pin.size())); + } + }); switch( err ) { diff --git a/client/QSigner.cpp b/client/QSigner.cpp index a329d1fbe..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"), {}); @@ -419,3 +423,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 5468b99bf..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); @@ -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(); diff --git a/client/SslCertificate.cpp b/client/SslCertificate.cpp index f262aa76e..48471098d 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)); + 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/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 4c48b0978..b51113adf 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -42,8 +42,10 @@ enum Actions : unsigned char { ContainerEncrypt, EncryptContainer, + EncryptContainerSuccess, DecryptContainer, DecryptToken, + DecryptContainerSuccess, SignatureAdd, SignatureMobile, @@ -51,6 +53,7 @@ enum Actions : unsigned char { SignatureToken, ClearSignatureWarning, ClearCryptoWarning, + EncryptLT }; enum Pages : unsigned char { diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index 9cbf33fd4..95584fcec 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -32,12 +32,12 @@ #include "dialogs/WarningDialog.h" #include "effects/Overlay.h" #include "widgets/AddressItem.h" -#include "widgets/ItemList.h" #include #include #include #include +#include #include AddRecipients::AddRecipients(ItemList* itemList, QWidget *parent) @@ -105,7 +105,6 @@ AddRecipients::~AddRecipients() delete ui; } - void AddRecipients::addRecipientFromFile() { QString file = FileDialog::getOpenFileName(this, windowTitle(), {}, @@ -157,12 +156,22 @@ 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); + leftItem = new AddressItem(key, AddressItem::Add, ui->leftPane); ui->leftPane->addWidget(leftItem); - leftItem->setDisabled(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"))) add->setVisible(true); @@ -174,13 +183,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) { @@ -201,9 +213,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 = WarningDialog::create(this) @@ -217,17 +229,17 @@ 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().cert)) + if(auto *leftItem = itemListValue(ui->leftPane, rightItem->getKey())) leftItem->setDisabled(false); rightList.removeAll(rightItem->getKey()); ui->confirm->setDisabled(rightList.isEmpty()); }); 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); } @@ -237,19 +249,19 @@ bool AddRecipients::isUpdated() const return ui->confirm->isEnabled(); } -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() const +QList AddRecipients::keys() const { - 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 4998f5174..56b616442 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: @@ -50,14 +50,13 @@ 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 CDKey &key); void search(const QString &term, bool select = false, const QString &type = {}); void showError(const QString &title, 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 rightList; QList ldap_person; LdapSearch *ldap_corp; int multiSearch = 0; diff --git a/client/dialogs/KeyDialog.cpp b/client/dialogs/KeyDialog.cpp index 69f43f9ce..44a9dc174 100644 --- a/client/dialogs/KeyDialog.cpp +++ b/client/dialogs/KeyDialog.cpp @@ -24,11 +24,12 @@ #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; d.setupUi(this); + d.showCert->hide(); #if defined (Q_OS_WIN) d.buttonLayout->setDirection(QBoxLayout::RightToLeft); #endif @@ -37,10 +38,6 @@ 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()); auto addItem = [view = d.view](const QString ¶meter, const QString &value) { if(value.isEmpty()) @@ -51,12 +48,41 @@ 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)); + 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; + } 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()) { + 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"), 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))); + 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..717df9161 --- /dev/null +++ b/client/dialogs/PasswordDialog.cpp @@ -0,0 +1,83 @@ +/* + * 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 + +PasswordDialog::PasswordDialog(Mode mode, QWidget *parent) + : QDialog(parent) + , ui(new Ui::PasswordDialog) +{ + setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint); + ui->setupUi(this); + 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() +{ + delete ui; +} + +void +PasswordDialog::setLabel(const QString& label) +{ + ui->labelLine->setText(label); +} + +QString +PasswordDialog::label() +{ + return ui->labelLine->text(); +} + +QByteArray +PasswordDialog::secret() const +{ + return ui->passwordLine->text().toUtf8(); +} + +void +PasswordDialog::updateOK() +{ + bool active = false; + 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(); + } + else + active = !ui->passwordLine->text().isEmpty(); + ui->ok->setEnabled(active); +} diff --git a/client/CDoc2.h b/client/dialogs/PasswordDialog.h similarity index 54% rename from client/CDoc2.h rename to client/dialogs/PasswordDialog.h index 54bcf58d6..53c4a657b 100644 --- a/client/CDoc2.h +++ b/client/dialogs/PasswordDialog.h @@ -1,5 +1,5 @@ /* - * QDigiDocClient + * QDigiDoc4 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,27 +19,32 @@ #pragma once -#include "CryptoDoc.h" +#include -#include +namespace Ui { +class PasswordDialog; +} + +class PasswordDialog : public QDialog +{ + Q_OBJECT -class CDoc2 final: public CDoc, private QFile { public: - explicit CDoc2() = default; - explicit CDoc2(const QString &path); + enum Mode { + ENCRYPT, + DECRYPT + }; + + explicit PasswordDialog(Mode mode, QWidget *parent = nullptr); + ~PasswordDialog(); - 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; + void setLabel(const QString& label); + QString label(); + QByteArray secret() const; private: - QByteArray header_data, headerHMAC; - qint64 noncePos = -1; + Ui::PasswordDialog *ui; - static const QByteArray LABEL, CEK, HMAC, KEK, KEKPREMASTER, PAYLOAD, SALT; - static constexpr int KEY_LEN = 32, NONCE_LEN = 12; + void genKeyClicked(); + void updateOK(); }; diff --git a/client/dialogs/PasswordDialog.ui b/client/dialogs/PasswordDialog.ui new file mode 100644 index 000000000..0398b152b --- /dev/null +++ b/client/dialogs/PasswordDialog.ui @@ -0,0 +1,278 @@ + + + PasswordDialog + + + Qt::WindowModal + + + + 0 + 0 + 496 + 570 + + + + 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 + + + + + Encrypt with password + + + Qt::AlignCenter + + + + + + + + + Key label (recipient name or id) + + + labelLine + + + + + + + + 400 + 0 + + + + + + + + + + + 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. + + + + + + + + + + 6 + + + + + 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 + + + + + + + + + + + + diff --git a/client/dialogs/RoleAddressDialog.cpp b/client/dialogs/RoleAddressDialog.cpp index 362e31a2b..1c12e3a33 100644 --- a/client/dialogs/RoleAddressDialog.cpp +++ b/client/dialogs/RoleAddressDialog.cpp @@ -28,6 +28,19 @@ class RoleAddressDialog::Private: public Ui::RoleAddressDialog {}; +static QString cleanUp(const QString& src) { + 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.append(*s); + } + dst.resize(dlen); + return dst; +} + RoleAddressDialog::RoleAddressDialog(QWidget *parent) : QDialog(parent) , d(new Private) @@ -57,8 +70,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 +95,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; } 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 new file mode 160000 index 000000000..14d60f243 --- /dev/null +++ b/client/libcdoc @@ -0,0 +1 @@ +Subproject commit 14d60f243afff75217b7bfb34cc99d6b252954a2 diff --git a/client/translations/en.ts b/client/translations/en.ts index 2f50459c4..2a14a3682 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -101,6 +101,10 @@ Added Added + + Decrypt + Decrypt + Add Add @@ -233,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 @@ -484,6 +481,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 @@ -548,37 +549,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 @@ -607,10 +592,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 @@ -1240,6 +1221,10 @@ Recipient Recipient + + Lock type + Lock type + Expiry date Expiry date @@ -1333,6 +1318,10 @@ E-Seal Encrypt Encrypt + + Encrypt long-term + Encrypt long-term + Decrypt Decrypt @@ -1471,7 +1460,7 @@ ID-Card Decrypting - Decrypting + Decrypting My eID @@ -1751,6 +1740,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 10d7d5548..3cf2d11fb 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -101,6 +101,10 @@ Added Lisatud + + Decrypt + Dekrüpteeri + Add Lisa @@ -233,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 @@ -484,6 +481,10 @@ DigiDoc4 Client DigiDoc4 klient + + Decrypting + Dekrüpteerin + You are about to delete the last file in the container Oled kustutamas viimast faili ümbrikus @@ -548,37 +549,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 @@ -607,10 +592,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 @@ -1240,6 +1221,10 @@ Recipient Adressaat + + Lock type + Luku tüüp + Expiry date Aegumiskuupäev @@ -1333,6 +1318,10 @@ E-templiga Encrypt Krüpteeri + + Encrypt long-term + + Decrypt Dekrüpteeri @@ -1471,7 +1460,7 @@ ID-kaardiga Decrypting - Dekrüpteerin + Dekrüpteerin My eID @@ -1751,6 +1740,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 cfe3bdede..428c0c593 100644 --- a/client/translations/ru.ts +++ b/client/translations/ru.ts @@ -101,6 +101,10 @@ Added Добавлен + + Decrypt + Расшифровать + Add Добавить @@ -233,13 +237,6 @@ Начать установку - - CDoc2 - - CDoc contains additional payload data that is not part of content - CDoc содержит дополнительные данные, которые не являются частью содержимого - - CDocumentModel @@ -484,6 +481,10 @@ DigiDoc4 Client DigiDoc4 клиент + + Decrypting + Расшифровка + You are about to delete the last file in the container Вы собираетесь удалить последний файл в контейнере @@ -548,37 +549,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 Не удалось добавить ключ @@ -607,10 +592,6 @@ Failed to open document Не удалось открыть документ - - An error occurred while opening the document - Во время открытия конверта возникла ошибка - Don't show again Больше не показывать @@ -1240,6 +1221,10 @@ Recipient Получател + + Lock type + + Expiry date Дата окончания @@ -1333,6 +1318,10 @@ E-Seal Encrypt Зашифровать + + Encrypt long-term + + Decrypt Расшифровать @@ -1471,7 +1460,7 @@ ID-картой Decrypting - Расшифровка + Расшифровка My eID @@ -1751,6 +1740,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 678a9ccf5..aed92d262 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -24,22 +24,25 @@ #include "SslCertificate.h" #include "dialogs/KeyDialog.h" +#include + using namespace ria::qdigidoc4; class AddressItem::Private: public Ui::AddressItem { public: QString code; - CKey key; + CDKey key; QString label; + QDateTime expireDate; 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,21 +50,49 @@ 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 (map.contains("server_exp")) { + ui->expireDate = QDateTime::fromSecsSinceEpoch(QString::fromStdString(map["server_exp"]).toLongLong()); + } + } else { + // No rcpt, lock is invalid = unsupported lock + unsupported = true; + ui->code.clear(); + ui->label = tr("Unsupported cryptographic algorithm or recipient type"); + } + + 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);}); + connect(ui->decrypt, &QToolButton::clicked, this, [this] { + emit decrypt(&ui->key.lock); + }); - 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); + ui->decrypt->setVisible(ui->key.lock.type == libcdoc::Lock::PASSWORD); } AddressItem::~AddressItem() @@ -82,15 +113,17 @@ 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) { + 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(); } @@ -108,10 +141,8 @@ 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*/) { + (new KeyDialog(ui->key, this))->open(); } void AddressItem::setName() @@ -128,17 +159,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 +176,62 @@ 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 + 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")); - 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.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())); } + 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 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 + 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 53e1ad15b..8a322e279 100644 --- a/client/widgets/AddressItem.h +++ b/client/widgets/AddressItem.h @@ -21,7 +21,9 @@ #include "widgets/Item.h" -class CKey; +struct CDKey; + +namespace libcdoc { struct Lock; } class AddressItem final : public Item { @@ -35,20 +37,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 266b19dde..a67c48600 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:focus { +#add:hover, #add:focus, +#decrypt:hover, #decrypt:focus { background-color: #EAF1F8; } -#add:pressed { +#add:pressed, #decrypt:pressed { background-color: #BFD3E8; } #remove:hover, #remove:focus { @@ -191,6 +192,16 @@ icon: url(:/images/icon_remove_clicked.svg); + + + + PointingHandCursor + + + Decrypt + + + diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 00d117ebe..0d07ac6af 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -20,18 +20,22 @@ #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" #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" +#include "widgets/MainAction.h" #include "widgets/SignatureItem.h" #include "widgets/WarningItem.h" @@ -53,6 +57,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 +66,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 +126,7 @@ void ContainerPage::clear(int code) void ContainerPage::clearPopups() { - if(mainAction) mainAction->hideDropdown(); + mainAction->hideDropdown(); } void ContainerPage::elideFileName() @@ -201,6 +207,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(DecryptContainerSuccess, {}, {}); +} + template void ContainerPage::deleteConfirm(C *c, int index) { @@ -224,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); @@ -234,18 +284,14 @@ 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)); 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 && - (isEncrypt || isDecrypt || isSignMobile || (isSignCard && !isBlocked && !isExpired))); + (isEncrypt || isEncryptLT || isDecrypt || isSignMobile || (isSignCard && !isBlocked && !isExpired))); ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); ui->navigationArea->layout()->invalidate(); } @@ -254,8 +300,7 @@ void ContainerPage::showSigningButton() { if (!isSupported) { - if(mainAction) - mainAction->hide(); + mainAction->hide(); ui->mainActionSpacer->changeSize(1, 20, QSizePolicy::Fixed); ui->navigationArea->layout()->invalidate(); } @@ -278,13 +323,12 @@ 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(auto &key: dlg.keys()) { - container->addKey(key); - ui->rightPane->addWidget(new AddressItem(std::move(key), AddressItem::Icon, ui->rightPane)); + container->addEncryptionKey(key.rcpt_cert); + ui->rightPane->addWidget(new AddressItem(key, AddressItem::Icon, ui->rightPane)); } showMainAction({ EncryptContainer }); }); @@ -315,6 +359,24 @@ 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) { + switch (action) + { + case EncryptContainer: + encrypt(container, false); + break; + case EncryptLT: + encrypt(container, true); + break; + case DecryptToken: + case DecryptContainer: + decrypt(container, nullptr, {}); + break; + default: + break; + } + }); clear(ClearCryptoWarning); isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); @@ -322,15 +384,24 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert ui->leftPane->init(fileName, QT_TRANSLATE_NOOP("ItemList", "Encrypted files")); ui->rightPane->init(ItemList::ItemAddress, QT_TRANSLATE_NOOP("ItemList", "Recipients")); bool hasUnsupported = false; - for(CKey &key: container->keys()) - { - hasUnsupported = hasUnsupported || key.unsupported; - ui->rightPane->addWidget(new AddressItem(std::move(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, 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) + if (hasUnsupported) emit warning({UnsupportedCDocWarning}); ui->leftPane->setModel(container->documentModel()); - updatePanes(container->state()); + updatePanes(container->state(), container); } void ContainerPage::transition(DigiDoc* container) @@ -373,7 +444,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] { @@ -435,7 +506,7 @@ void ContainerPage::transition(DigiDoc* container) showSigningButton(); ui->leftPane->setModel(container->documentModel()); - updatePanes(container->state()); + updatePanes(container->state(), nullptr); } void ContainerPage::updateDecryptionButton() @@ -443,7 +514,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); @@ -453,7 +524,7 @@ void ContainerPage::updatePanes(ContainerState state) for(QWidget *button: buttons) button->setVisible(visible); }; - switch( state ) + switch(state) { case UnsignedContainer: cancelText = QT_TR_NOOP("Cancel"); @@ -482,7 +553,11 @@ void ContainerPage::updatePanes(ContainerState state) case UnencryptedContainer: cancelText = QT_TR_NOOP("Start"); convertText = QT_TR_NOOP("Sign"); - showMainAction({ EncryptContainer }); + if (crypto_container && crypto_container->supportsSymmetricKeys()) { + showMainAction({ EncryptContainer, EncryptLT }); + } else { + showMainAction({ EncryptContainer }); + } setButtonsVisible({ ui->changeLocation, ui->convert }, true); setButtonsVisible({ ui->saveAs, ui->email }, false); break; diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index 66e171ab5..d047e45e5 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -19,18 +19,16 @@ #pragma once -#include "common_enums.h" -#include "widgets/MainAction.h" +#include -#include +#include "common_enums.h" -namespace Ui { -class ContainerPage; -} +namespace libcdoc { struct Lock; } +namespace Ui { class ContainerPage; } -class CKey; class CryptoDoc; class DigiDoc; +class MainAction; class QSslCertificate; class SignatureItem; class SslCertificate; @@ -63,19 +61,21 @@ class ContainerPage final : public QWidget 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(); + void encrypt(CryptoDoc *container, bool longTerm); bool eventFilter(QObject *o, QEvent *e) final; void showMainAction(const QList &actions); void showSigningButton(); void handleAction(int type); void updateDecryptionButton(); - void updatePanes(ria::qdigidoc4::ContainerState state); + void updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container); 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 403a788bd..dea6c8312 100644 --- a/client/widgets/MainAction.cpp +++ b/client/widgets/MainAction.cpp @@ -107,6 +107,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"); @@ -166,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])); } 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); -} diff --git a/vcpkg.json b/vcpkg.json deleted file mode 100644 index ba81ebe9d..000000000 --- a/vcpkg.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "qdigidoc4", - "dependencies": ["openssl", "flatbuffers", "zlib"], - "builtin-baseline": "bc38a15b0bee8bc48a49ea267cc32fbb49aedfc4", - "vcpkg-configuration": { - "registries": [ - { - "kind": "git", - "repository": "https://github.com/open-eid/vcpkg-ports", - "reference": "vcpkg-registry", - "baseline": "316f4d642f489b7d23d97891ed73431e7394d749", - "packages": ["openssl"] - } - ] - } -}