From e08c566a7426d04ca6549a73a25a1fe41eb0ab7a Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 14 Jul 2023 17:47:08 +0300 Subject: [PATCH] CDoc 2.0 (#1077) CDOC-1 Signed-off-by: Raul Metsma --- .github/workflows/build.yml | 12 +- CMakeLists.txt | 4 + client/Application.cpp | 22 +- client/CDoc1.cpp | 640 +++++++++++++++++ client/CDoc1.h | 68 ++ client/CDoc2.cpp | 774 +++++++++++++++++++++ client/CDoc2.h | 45 ++ client/CMakeLists.txt | 41 +- client/CheckConnection.cpp | 69 +- client/CheckConnection.h | 9 +- client/Crypto.cpp | 348 ++++++++++ client/Crypto.h | 74 ++ client/CryptoDoc.cpp | 1039 ++++------------------------ client/CryptoDoc.h | 74 +- client/Diagnostics.cpp | 14 +- client/DigiDoc.cpp | 38 +- client/DigiDoc.h | 18 +- client/DocumentModel.cpp | 2 +- client/DocumentModel.h | 4 +- client/MainWindow.cpp | 29 +- client/PrintSheet.cpp | 19 +- client/QCNG.cpp | 36 +- client/QCNG.h | 3 +- client/QCryptoBackend.h | 3 +- client/QPKCS11.cpp | 55 +- client/QPKCS11.h | 3 +- client/QSigner.cpp | 53 +- client/QSigner.h | 1 - client/Settings.cpp | 19 +- client/Settings.h | 9 + client/SslCertificate.cpp | 18 +- client/Utils.h | 17 +- client/dialogs/AddRecipients.cpp | 45 +- client/dialogs/AddRecipients.h | 2 +- client/dialogs/FileDialog.cpp | 42 +- client/dialogs/FileDialog.h | 2 +- client/dialogs/KeyDialog.cpp | 3 + client/dialogs/MobileProgress.cpp | 38 +- client/dialogs/SettingsDialog.cpp | 51 +- client/dialogs/SettingsDialog.ui | 111 ++- client/dialogs/SmartIDProgress.cpp | 38 +- client/dialogs/WarningDialog.cpp | 26 +- client/dialogs/WarningDialog.h | 11 +- client/mac/Info.plist.cmake | 20 +- client/pkcs11.h | 462 ++++++++++++- client/qdigidoc4.desktop | 2 +- client/qdigidoc4.xml | 6 + client/translations/en.ts | 115 +-- client/translations/et.ts | 115 +-- client/translations/ru.ts | 115 +-- client/widgets/AddressItem.cpp | 9 +- client/widgets/AddressItem.h | 1 + client/widgets/ContainerPage.cpp | 16 +- client/widgets/ContainerPage.h | 1 - client/widgets/FileList.cpp | 6 +- client/widgets/SignatureItem.cpp | 4 +- prepare_osx_build_environment.sh | 2 +- qdigidoc4.wxs | 13 +- schema/header.fbs | 53 ++ schema/recipients.fbs | 76 ++ 60 files changed, 3522 insertions(+), 1423 deletions(-) create mode 100644 client/CDoc1.cpp create mode 100644 client/CDoc1.h create mode 100644 client/CDoc2.cpp create mode 100644 client/CDoc2.h create mode 100644 client/Crypto.cpp create mode 100644 client/Crypto.h create mode 100644 schema/header.fbs create mode 100644 schema/recipients.fbs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 458ea62fd..59e2dd6dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: env: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: YES run: | - brew install ninja + brew install ninja flatbuffers HASH=($(shasum prepare_osx_build_environment.sh)) curl -O -L -s https://installer.id.ee/media/github/opensc_0.22.0.pkg curl -O -L -s https://installer.id.ee/media/github/${HASH}.zip @@ -63,10 +63,10 @@ jobs: steps: - name: Install dependencies if: matrix.container == 'ubuntu:20.04' - run: apt update -qq && apt install --no-install-recommends -y git lsb-release fakeroot build-essential devscripts debhelper cdbs pkg-config cmake libldap2-dev gettext libpcsclite-dev libssl-dev libqt5svg5-dev qttools5-dev-tools qttools5-dev lintian + run: apt update -qq && apt install --no-install-recommends -y git lsb-release fakeroot build-essential devscripts debhelper cdbs pkg-config cmake libldap2-dev gettext libpcsclite-dev libssl-dev libqt5svg5-dev qttools5-dev-tools qttools5-dev lintian libflatbuffers-dev zlib1g-dev - name: Install dependencies if: matrix.container != 'ubuntu:20.04' - run: apt update -qq && apt install --no-install-recommends -y git lsb-release fakeroot build-essential devscripts debhelper cdbs pkg-config cmake libldap2-dev gettext libpcsclite-dev libssl-dev libgl-dev libqt6svg6-dev qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools libqt6core5compat6-dev lintian + run: apt update -qq && apt install --no-install-recommends -y git lsb-release fakeroot build-essential devscripts debhelper cdbs pkg-config cmake libldap2-dev gettext libpcsclite-dev libssl-dev libgl-dev libqt6svg6-dev qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools libqt6core5compat6-dev lintian libflatbuffers-dev zlib1g-dev - name: Checkout uses: actions/checkout@v3 with: @@ -110,7 +110,7 @@ jobs: MAKEFLAGS: -j3 steps: - name: Install Deps - run: dnf install -y git gcc-c++ cmake rpm-build gettext openssl-devel openldap-devel pcsc-lite-devel qt6-qtsvg-devel qt6-qttools-devel qt6-qt5compat-devel + run: dnf install -y git gcc-c++ cmake rpm-build gettext openssl-devel openldap-devel pcsc-lite-devel qt6-qtsvg-devel qt6-qttools-devel qt6-qt5compat-devel flatbuffers-devel flatbuffers-compiler zlib-devel - name: Checkout uses: actions/checkout@v3 with: @@ -169,7 +169,7 @@ jobs: - name: Prepare vcpkg uses: lukka/run-vcpkg@v7 with: - vcpkgArguments: openssl + vcpkgArguments: openssl zlib flatbuffers vcpkgGitCommitId: 9b9c2758ece1d8ac0de90589730bb5ccf45c0874 vcpkgTriplet: x64-windows - name: Install Qt @@ -257,7 +257,7 @@ jobs: with: submodules: recursive - name: Install dependencies - run: sudo apt update -qq && sudo apt install --no-install-recommends -y cmake libldap2-dev gettext libpcsclite-dev libminizip-dev libxml-security-c-dev libgl-dev libqt6svg6-dev qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools libqt6core5compat6-dev + run: sudo apt update -qq && sudo apt install --no-install-recommends -y cmake libldap2-dev gettext libpcsclite-dev libminizip-dev libxml-security-c-dev libgl-dev libqt6svg6-dev qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools libqt6core5compat6-dev libflatbuffers-dev zlib1g-dev - name: Download artifact uses: dawidd6/action-download-artifact@v2 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d24fd22e..f8f327154 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,13 @@ find_package(LibDigiDocpp 3.15.0 REQUIRED) find_package( LDAP REQUIRED ) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) find_package(Qt${QT_VERSION_MAJOR} 5.12.0 REQUIRED COMPONENTS Core Widgets Network PrintSupport Svg LinguistTools) +find_package(FlatBuffers CONFIG REQUIRED NAMES FlatBuffers Flatbuffers) +find_package(ZLIB REQUIRED) set_env( TSL_URL "https://ec.europa.eu/tools/lotl/eu-lotl.xml" CACHE STRING "TSL trust list primary URL" ) set_env( TSL_INCLUDE "EE" CACHE STRING "TSL list include in binary" ) +set_env(CDOC2_GET_URL "https://cdoc2-keyserver-get" CACHE STRING "CDoc 2.0 Key Server get URL") +set_env(CDOC2_POST_URL "https://cdoc2-keyserver-post" CACHE STRING "CDoc 2.0 Key Server post URL") set_env( MOBILEID_URL "https://dd-mid.ria.ee/mid-api" CACHE STRING "URL for Mobile-ID" ) set_env( SMARTID_URL "https://dd-sid.ria.ee/v1" CACHE STRING "URL for Smart-ID" ) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION YES) diff --git a/client/Application.cpp b/client/Application.cpp index 95a5b7353..e9a5ca1c8 100644 --- a/client/Application.cpp +++ b/client/Application.cpp @@ -224,12 +224,6 @@ class DigidocConf final: public digidoc::XmlConfCurrent { if(Settings::TSA_URL == Application::confValue(Settings::TSA_URL.KEY).toString()) Settings::TSA_URL.clear(); // Cleanup user conf if it is default url - QList list; - for(const auto &cert: Application::confValue(QLatin1String("CERT-BUNDLE")).toArray()) - list.append(QSslCertificate(fromBase64(cert), QSsl::Der)); - QSslConfiguration ssl = QSslConfiguration::defaultConfiguration(); - ssl.setCaCertificates(list); - QSslConfiguration::setDefaultConfiguration(ssl); } #endif @@ -798,14 +792,14 @@ void Application::parseArgs( const QString &msg ) void Application::parseArgs(QStringList args) { - bool crypto = args.contains(QLatin1String("-crypto")); - bool sign = args.contains(QLatin1String("-sign")); - bool newWindow = args.contains(QLatin1String("-newWindow")); - args.removeAll(QStringLiteral("-sign")); - args.removeAll(QStringLiteral("-crypto")); - args.removeAll(QStringLiteral("-newWindow")); - QString suffix = args.size() == 1 ? QFileInfo(args.value(0)).suffix() : QString(); - showClient(args, crypto || (suffix.compare(QLatin1String("cdoc"), Qt::CaseInsensitive) == 0), sign, newWindow); + bool crypto = args.removeAll(QStringLiteral("-crypto")) > 0; + bool sign = args.removeAll(QStringLiteral("-sign")) > 0; + bool newWindow = args.removeAll(QStringLiteral("-newWindow")) > 0; + if(QString suffix = args.value(0); + suffix.endsWith(QLatin1String(".cdoc"), Qt::CaseInsensitive) || + suffix.endsWith(QLatin1String(".cdoc2"), Qt::CaseInsensitive)) + crypto = true; + showClient(args, crypto, sign, newWindow); } uint Application::readTSLVersion(const QString &path) diff --git a/client/CDoc1.cpp b/client/CDoc1.cpp new file mode 100644 index 000000000..bbb70418a --- /dev/null +++ b/client/CDoc1.cpp @@ -0,0 +1,640 @@ +/* + * QDigiDocClient + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "CDoc1.h" + +#include "Application.h" +#include "Crypto.h" +#include "QCryptoBackend.h" +#include "QSigner.h" +#include "Utils.h" +#include "dialogs/FileDialog.h" + +#include +#include +#include +#include +#include + +#include + +const QString CDoc1::AES128CBC_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#aes128-cbc"); +const QString CDoc1::AES192CBC_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#aes192-cbc"); +const QString CDoc1::AES256CBC_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#aes256-cbc"); +const QString CDoc1::AES128GCM_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#aes128-gcm"); +const QString CDoc1::AES192GCM_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#aes192-gcm"); +const QString CDoc1::AES256GCM_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#aes256-gcm"); +const QString CDoc1::RSA_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#rsa-1_5"); +const QString CDoc1::KWAES128_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#kw-aes128"); +const QString CDoc1::KWAES192_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#kw-aes192"); +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} +}; +const QHash CDoc1::KWAES_SIZE{{KWAES128_MTH, 16}, {KWAES192_MTH, 24}, {KWAES256_MTH, 32}}; + +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.id = xml.attributes().value(QLatin1String("Id")).toString(); + 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; + // EncryptedData/KeyInfo/KeyName + if(xml.name() == QLatin1String("KeyName")) + key.name = xml.readElementText(); + // EncryptedData/KeyInfo/EncryptedKey/EncryptionMethod + else if(xml.name() == QLatin1String("EncryptionMethod")) + key.method = xml.attributes().value(QLatin1String("Algorithm")).toString(); + // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod + else if(xml.name() == QLatin1String("AgreementMethod")) + key.agreement = xml.attributes().value(QLatin1String("Algorithm")).toString(); + // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod + else if(xml.name() == QLatin1String("KeyDerivationMethod")) + key.derive = xml.attributes().value(QLatin1String("Algorithm")).toString(); + // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod/ConcatKDFParams + else if(xml.name() == QLatin1String("ConcatKDFParams")) + { + key.AlgorithmID = QByteArray::fromHex(xml.attributes().value(QLatin1String("AlgorithmID")).toUtf8()); + if(key.AlgorithmID.front() == char(0x00)) key.AlgorithmID.remove(0, 1); + key.PartyUInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyUInfo")).toUtf8()); + if(key.PartyUInfo.front() == char(0x00)) key.PartyUInfo.remove(0, 1); + key.PartyVInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyVInfo")).toUtf8()); + if(key.PartyVInfo.front() == char(0x00)) key.PartyVInfo.remove(0, 1); + } + // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod/ConcatKDFParams/DigestMethod + else if(xml.name() == QLatin1String("DigestMethod")) + key.concatDigest = xml.attributes().value(QLatin1String("Algorithm")).toString(); + // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/OriginatorKeyInfo/KeyValue/ECKeyValue/PublicKey + else if(xml.name() == QLatin1String("PublicKey")) + { + xml.readNext(); + key.publicKey = fromBase64(xml.text()); + } + // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/X509Data/X509Certificate + else if(xml.name() == QLatin1String("X509Certificate")) + { + xml.readNext(); + key.setCert(QSslCertificate(fromBase64(xml.text()), QSsl::Der)); + } + // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/CipherData/CipherValue + else if(xml.name() == QLatin1String("CipherValue")) + { + xml.readNext(); + key.cipher = fromBase64(xml.text()); + } + } + keys.append(std::move(key)); + }); + if(!keys.isEmpty()) + setLastError({}); + + if(files.empty() && properties.contains(QStringLiteral("Filename"))) + { + files.push_back({ + properties.value(QStringLiteral("Filename")), + {}, + mime == MIME_ZLIB ? properties.value(QStringLiteral("OriginalMimeType")) : mime, + properties.value(QStringLiteral("OriginalSize")).toUInt(), + {} + }); + } +} + +bool CDoc1::decryptPayload(const QByteArray &key) +{ + if(!isOpen()) + return false; + setLastError({}); + QByteArray data; + seek(0); + readXML(this, [&data](QXmlStreamReader &xml) { + // EncryptedData/KeyInfo + if(xml.name() == QLatin1String("KeyInfo")) + xml.skipCurrentElement(); + // EncryptedData/CipherData/CipherValue + else if(xml.name() == QLatin1String("CipherValue")) + { + xml.readNext(); + data = fromBase64(xml.text()); + } + }); + if(data.isEmpty()) + return setLastError(CryptoDoc::tr("Error parsing document")); + data = Crypto::cipher(ENC_MTH[method], key, data, false); + if(data.isEmpty()) + return setLastError(CryptoDoc::tr("Failed to decrypt document")); + + // remove ANSIX923 padding + if(data.size() > 0 && method == AES128CBC_MTH) + { + QByteArray ansix923(data[data.size()-1], 0); + ansix923[ansix923.size()-1] = char(ansix923.size()); + if(data.right(ansix923.size()) == ansix923) + { + qCDebug(CRYPTO) << "Removing ANSIX923 padding size:" << ansix923.size(); + data.resize(data.size() - ansix923.size()); + } + } + + if(mime == MIME_ZLIB) + { + // Add size header for qUncompress compatibilty + unsigned origsize = std::max(properties.value(QStringLiteral("OriginalSize")).toUInt(), 1); + qCDebug(CRYPTO) << "Decompressing zlib content size" << origsize; + QByteArray size(4, 0); + size[0] = char((origsize & 0xff000000) >> 24); + size[1] = char((origsize & 0x00ff0000) >> 16); + size[2] = char((origsize & 0x0000ff00) >> 8); + size[3] = char((origsize & 0x000000ff)); + data = qUncompress(size + data); + mime = properties[QStringLiteral("OriginalMimeType")]; + } + + if(mime == MIME_DDOC || mime == MIME_DDOC_OLD) + { + qCDebug(CRYPTO) << "Contains DDoc content" << mime; + QTemporaryFile ddoc(QDir::tempPath() + "/XXXXXX"); + if(!ddoc.open()) + return setLastError(CryptoDoc::tr("Failed to create temporary files
%1").arg(ddoc.errorString())); + ddoc.write(data); + ddoc.flush(); + ddoc.reset(); + files = readDDoc(&ddoc); + return !files.empty(); + } + + auto buffer = std::make_unique(); + buffer->setData(data); + if(!buffer->open(QBuffer::ReadWrite)) + return false; + qCDebug(CRYPTO) << "Contains raw file" << mime; + if(!files.empty()) + { + files[0].size = data.size(); + files[0].data = std::move(buffer); + } + else if(properties.contains(QStringLiteral("Filename"))) + { + files.push_back({ + properties.value(QStringLiteral("Filename")), + {}, + mime, + data.size(), + std::move(buffer), + }); + } + else + return setLastError(CryptoDoc::tr("Error parsing document")); + + return !files.empty(); +} + +CKey CDoc1::canDecrypt(const QSslCertificate &cert) const +{ + for(const CKey &k: qAsConst(keys)) + { + if(!ENC_MTH.contains(method) || + k.cert != cert || + k.cipher.isEmpty()) + continue; + if(cert.publicKey().algorithm() == QSsl::Rsa && + k.method == RSA_MTH) + return k; + if(cert.publicKey().algorithm() == QSsl::Ec && + !k.publicKey.isEmpty() && + KWAES_SIZE.contains(k.method) && + k.derive == CONCATKDF_MTH && + k.agreement == AGREEMENT_MTH) + 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.id.isEmpty()) + w.writeAttribute(QStringLiteral("Id"), k.id); + 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"), [&]{ + if(!k.name.isEmpty()) + w.writeTextElement(DS, QStringLiteral("KeyName"), k.name); + 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()); + + const QString encryptionMethod = KWAES256_MTH; + 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], KWAES_SIZE[encryptionMethod], + 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"), encryptionMethod}, + }); + 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.data(), 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], + int(KWAES_SIZE[key.method]), 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, const 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, const 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 new file mode 100644 index 000000000..eed15ce76 --- /dev/null +++ b/client/CDoc1.h @@ -0,0 +1,68 @@ +/* + * 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, const std::function &f = {}); + static void writeElement(QXmlStreamWriter &x, const QString &ns, const QString &name, const QMap &attrs, const std::function &f = {}); + + QString method, mime; + QHash properties; + + static const QString + AES128CBC_MTH, AES192CBC_MTH, AES256CBC_MTH, + AES128GCM_MTH, AES192GCM_MTH, AES256GCM_MTH, + KWAES128_MTH, KWAES192_MTH, KWAES256_MTH, + SHA256_MTH, SHA384_MTH, SHA512_MTH, + RSA_MTH, CONCATKDF_MTH, AGREEMENT_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; + static const QHash KWAES_SIZE; +}; diff --git a/client/CDoc2.cpp b/client/CDoc2.cpp new file mode 100644 index 000000000..48fab449b --- /dev/null +++ b/client/CDoc2.cpp @@ -0,0 +1,774 @@ +/* + * 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 "Colors.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([] { + auto *notification = new FadeInNotification(Application::mainWindow(), + ria::qdigidoc4::colors::WHITE, ria::qdigidoc4::colors::MANTIS, 110); + notification->start(QCoreApplication::translate("MainWindow", "Check internet connection"), 750, 3000, 1200); + 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()) + { + inflateInit2(&s, MAX_WBITS); + open(QIODevice::ReadOnly); + } + if(io->isWritable()) + { + deflateInit(&s, Z_DEFAULT_COMPRESSION); + 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() + 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 size = io->read(in.data(), 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; + } + Header eof{}; + return io->write((const char*)&eof, Header::Size) == Header::Size; + } + + std::vector files(bool &warning) const + { + std::vector result; + Header h {}; + while(io->bytesAvailable() > 0) + { + if(io->read((char*)&h, Header::Size) != Header::Size) + return {}; + if(h.isNull()) + { + 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(io->read((char*)&h, Header::Size) != Header::Size || h.isNull() || !h.verify()) + return {}; + 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() { + static const Header zeroBlock {}; + return memcmp(this, &zeroBlock, 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 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 int TAR::Header::Size = int(sizeof(TAR::Header)); +} + +CDoc2::CDoc2(const QString &path) + : QFile(path) +{ + 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(!cdoc20::Header::VerifyHeaderBuffer(verifier)) + return; + const auto *header = cdoc20::Header::GetHeader(header_data.constData()); + if(!header) + return; + if(header->payload_encryption_method() != cdoc20::Header::PayloadEncryptionMethod::CHACHA20POLY1305) + return; + const auto *recipients = header->recipients(); + if(!recipients) + return; + setLastError({}); + + auto toByteArray = [](const flatbuffers::Vector *data) { + return data ? QByteArray((const char*)data->Data(), int(data->size())) : QByteArray(); + }; + auto toString = [](const flatbuffers::String *data) { + return data ? QString::fromUtf8(data->c_str(), data->size()) : QString(); + }; + for(const auto *recipient: *recipients){ + if(recipient->fmk_encryption_method() != cdoc20::Header::FMKEncryptionMethod::XOR) + { + qWarning() << "Unsupported FMK encryption method: skipping"; + continue; + } + auto fillRecipient = [&] (auto key, bool isRSA) { + CKey k(toByteArray(key->recipient_public_key()), isRSA); + k.recipient = toString(recipient->key_label()); + k.cipher = toByteArray(recipient->encrypted_fmk()); + return k; + }; + using cdoc20::Recipients::Capsule; + switch(recipient->capsule_type()) + { + case Capsule::ECCPublicKeyCapsule: + { + if(const auto *key = recipient->capsule_as_ECCPublicKeyCapsule()) + { + if(key->curve() != cdoc20::Recipients::EllipticCurve::secp384r1) + { + qWarning() << "Unsupported ECC curve: skipping"; + continue; + } + CKey k = fillRecipient(key, false); + 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: + { + const auto *server = recipient->capsule_as_KeyServerCapsule(); + if(!server) + qWarning() << "Unsupported Key Details: skipping"; + + auto fillKeyServer = [&] (auto key, bool isRSA) { + CKey k = fillRecipient(key, isRSA); + k.keyserver_id = toString(server->keyserver_id()); + k.transaction_id = toString(server->transaction_id()); + return k; + }; + switch(server->recipient_key_details_type()) + { + case cdoc20::Recipients::ServerDetailsUnion::ServerEccDetails: + { + if(const auto *eccDetails = server->recipient_key_details_as_ServerEccDetails()) + { + if(eccDetails->curve() == cdoc20::Recipients::EllipticCurve::secp384r1) + keys.append(fillKeyServer(eccDetails, false)); + } + break; + } + case cdoc20::Recipients::ServerDetailsUnion::ServerRsaDetails: + { + if(const auto *rsaDetails = server->recipient_key_details_as_ServerRsaDetails()) + keys.append(fillKeyServer(rsaDetails, true)); + break; + } + default: + qWarning() << "Unsupported Key Server Details: skipping"; + } + break; + } + default: + qWarning() << "Unsupported Key Details: skipping"; + } + } +} + +CKey CDoc2::canDecrypt(const QSslCertificate &cert) const +{ + return keys.value(keys.indexOf(CKey(cert))); +} + +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")); + 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_GET_CERT)); + 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.recipient), 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.recipient), 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.recipient), 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.recipient), 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; + } + file.write(enc.resultTAG()); + if(!enc.result()) + { + file.remove(); + return false; + } + 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_POST_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 = key_material; + else + key.publicKey = key_material; + } +#ifndef NDEBUG + qDebug() << "publicKeyDer" << key.key.toHex(); + qDebug() << "ephPublicKeyDer" << key.publicKey.toHex(); +#endif + QByteArray kek = qApp->signer()->decrypt([&key](QCryptoBackend *backend) { + if(key.isRSA) + return backend->decrypt(key.encrypted_kek, true); + QByteArray kekPm = backend->deriveHMACExtract(key.publicKey, KEKPREMASTER, KEY_LEN); +#ifndef NDEBUG + qDebug() << "kekPm" << kekPm.toHex(); +#endif + QByteArray info = KEK + cdoc20::Header::EnumNameFMKEncryptionMethod(cdoc20::Header::FMKEncryptionMethod::XOR) + key.key + key.publicKey; + return Crypto::expand(kekPm, info, KEY_LEN); + }); + if(kek.isEmpty()) + { + setLastError(QStringLiteral("Failed to derive key")); + return {}; + } + QByteArray fmk = Crypto::xor_data(key.cipher, kek); + QByteArray hhk = Crypto::expand(fmk, HMAC); +#ifndef NDEBUG + qDebug() << "kek" << kek.toHex(); + qDebug() << "xor" << key.cipher.toHex(); + qDebug() << "fmk" << fmk.toHex(); + qDebug() << "hhk" << hhk.toHex(); + qDebug() << "hmac" << headerHMAC.toHex(); +#endif + if(Crypto::sign_hmac(hhk, header_data) == headerHMAC) + return fmk; + setLastError(QStringLiteral("CDoc 2.0 hash mismatch")); + return {}; +} + +int CDoc2::version() +{ + return 2; +} diff --git a/client/CDoc2.h b/client/CDoc2.h new file mode 100644 index 000000000..54bcf58d6 --- /dev/null +++ b/client/CDoc2.h @@ -0,0 +1,45 @@ +/* + * QDigiDocClient + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include "CryptoDoc.h" + +#include + +class CDoc2 final: public CDoc, private QFile { +public: + explicit CDoc2() = default; + explicit CDoc2(const QString &path); + + CKey canDecrypt(const QSslCertificate &cert) const final; + bool decryptPayload(const QByteArray &fmk) final; + QByteArray deriveFMK(const QByteArray &priv, const CKey &key); + bool isSupported(); + bool save(const QString &path) final; + QByteArray transportKey(const CKey &key) final; + int version() final; + +private: + QByteArray header_data, headerHMAC; + qint64 noncePos = -1; + + static const QByteArray LABEL, CEK, HMAC, KEK, KEKPREMASTER, PAYLOAD, SALT; + static constexpr int KEY_LEN = 32, NONCE_LEN = 12; +}; diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 404c6992e..5cecd8110 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -12,6 +12,10 @@ add_custom_command( configure_file( translations/tr.qrc tr.qrc COPYONLY ) qt_add_translation(SOURCES translations/en.ts translations/et.ts translations/ru.ts) +set(SCHEMAS + ../schema/recipients.fbs + ../schema/header.fbs +) add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE ${PROJECT_NAME}.rc ${SOURCES} @@ -22,6 +26,9 @@ add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE main.cpp Application.cpp CheckConnection.cpp + CDoc1.cpp + CDoc2.cpp + Crypto.cpp CryptoDoc.cpp DateTime.cpp DigiDoc.cpp @@ -128,6 +135,9 @@ target_link_libraries(${PROJECT_NAME} Qt${QT_VERSION_MAJOR}::Svg ${LIBDIGIDOCPP_LIBRARY} ${LDAP_LIBRARIES} + $ + $ + ZLIB::ZLIB ) if(NOT BUILD_DATE) @@ -148,11 +158,32 @@ set_target_properties(${PROJECT_NAME} PROPERTIES ) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR} ${LIBDIGIDOCPP_INCLUDE_DIR}) target_compile_definitions(${PROJECT_NAME} PRIVATE + CDOC2_GET_URL="${CDOC2_GET_URL}" + CDOC2_POST_URL="${CDOC2_POST_URL}" MOBILEID_URL="${MOBILEID_URL}" SMARTID_URL="${SMARTID_URL}" BUILD_DATE="${BUILD_DATE}" ) +foreach(SCHEMA ${SCHEMAS}) + get_filename_component(stem ${SCHEMA} NAME_WE) + get_filename_component(name ${SCHEMA} NAME) + set(GENERATED_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/${stem}_generated.h) + add_custom_command( + OUTPUT ${GENERATED_INCLUDE} + COMMENT "Compiling flatbuffer for ${name}" + COMMAND flatbuffers::flatc + --cpp + --scoped-enums + -o ${CMAKE_CURRENT_BINARY_DIR} + -I ${CMAKE_CURRENT_SOURCE_DIR} + ${SCHEMA} + DEPENDS flatbuffers::flatc ${SCHEMA} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + target_sources(${PROJECT_NAME} PRIVATE ${SCHEMA} ${GENERATED_INCLUDE}) +endforeach() + if( APPLE ) file(GLOB_RECURSE RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/mac/Resources/*.icns @@ -310,13 +341,18 @@ else() DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${RES}x${RES}/mimetypes/ RENAME application-x-cdoc.png ) - foreach(TGT + foreach(TGT application-vnd.etsi.asic-s+zip.png application-x-ddoc.png application-vnd.lt.archyvai.adoc-2008.png) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink application-vnd.etsi.asic-e+zip.png \ \$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_DATADIR}/icons/hicolor/${RES}x${RES}/mimetypes/${TGT})") endforeach() + foreach(TGT + application-x-cdoc2.png) + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink application-x-cdoc.png \ + \$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_DATADIR}/icons/hicolor/${RES}x${RES}/mimetypes/${TGT})") + endforeach() # Workaround Ubuntu 21.10 Yaru icon theme issues install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory \ \$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_DATADIR}/icons/Yaru/${RES}x${RES}/mimetypes/)") @@ -325,7 +361,8 @@ else() application-vnd.etsi.asic-s+zip.png application-x-ddoc.png application-vnd.lt.archyvai.adoc-2008.png - application-x-cdoc.png) + application-x-cdoc.png + application-x-cdoc2.png) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink \ ${CMAKE_INSTALL_FULL_DATADIR}/icons/hicolor/${RES}x${RES}/mimetypes/${TGT} \ \$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_DATADIR}/icons/Yaru/${RES}x${RES}/mimetypes/${TGT})") diff --git a/client/CheckConnection.cpp b/client/CheckConnection.cpp index b8ad54b6a..426666f2c 100644 --- a/client/CheckConnection.cpp +++ b/client/CheckConnection.cpp @@ -19,26 +19,26 @@ #include "CheckConnection.h" +#include "Application.h" + #include #include +#include +#include #include #include -CheckConnection::CheckConnection() = default; - -bool CheckConnection::check(const QString &url) +bool CheckConnection::check(const QUrl &url) { QNetworkAccessManager nam; QEventLoop e; QNetworkReply *reply = nam.head(QNetworkRequest(url)); - QObject::connect(reply, &QNetworkReply::sslErrors, reply, [&](const QList &errors){ - reply->ignoreSslErrors(errors); - }); + QObject::connect(reply, &QNetworkReply::sslErrors, reply, + QOverload &>::of(&QNetworkReply::ignoreSslErrors)); QObject::connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); e.exec(); m_error = reply->error(); qtmessage = reply->errorString(); - reply->deleteLater(); return m_error == QNetworkReply::NoError; } @@ -60,3 +60,58 @@ QString CheckConnection::errorString() const return QCoreApplication::translate("CheckConnection", "Cannot connect to certificate status service!"); } } + +QNetworkAccessManager* CheckConnection::setupNAM(QNetworkRequest &req, const QByteArray &add) +{ + req.setSslConfiguration(sslConfiguration(add)); + req.setRawHeader("User-Agent", QStringLiteral("%1/%2 (%3)") + .arg(Application::applicationName(), Application::applicationVersion(), Common::applicationOs()).toUtf8()); + auto *nam = new QNetworkAccessManager(); + QObject::connect(nam, &QNetworkAccessManager::sslErrors, nam, [](QNetworkReply *reply, const QList &errors) { + QList ignore; + for(const QSslError &error: errors) + { + switch(error.error()) + { + case QSslError::UnableToVerifyFirstCertificate: + case QSslError::UnableToGetLocalIssuerCertificate: + case QSslError::CertificateUntrusted: + case QSslError::SelfSignedCertificateInChain: + if(reply->sslConfiguration().caCertificates().contains(reply->sslConfiguration().peerCertificate())) { + ignore.append(error); + break; + } + Q_FALLTHROUGH(); + default: + qDebug() << "SSL Error:" << error.error() << error.certificate().subjectInfo(QSslCertificate::CommonName); + } + } + reply->ignoreSslErrors(ignore); + }); + return nam; +} + +QNetworkAccessManager* CheckConnection::setupNAM(QNetworkRequest &req, + const QSslCertificate &cert, const QSslKey &key, const QByteArray &add) +{ + QNetworkAccessManager* nam = setupNAM(req, add); + QSslConfiguration ssl = req.sslConfiguration(); + ssl.setPrivateKey(key); + ssl.setLocalCertificate(cert); + req.setSslConfiguration(ssl); + return nam; +} + +QSslConfiguration CheckConnection::sslConfiguration(const QByteArray &add) +{ + QSslConfiguration ssl = QSslConfiguration::defaultConfiguration(); +#ifdef CONFIG_URL + QList trusted; + for(const auto &cert: Application::confValue(QLatin1String("CERT-BUNDLE")).toArray()) + trusted.append(QSslCertificate(QByteArray::fromBase64(cert.toString().toLatin1()), QSsl::Der)); + if(!add.isEmpty()) + trusted.append(QSslCertificate(QByteArray::fromBase64(add), QSsl::Der)); + ssl.setCaCertificates(trusted); +#endif + return ssl; +} diff --git a/client/CheckConnection.h b/client/CheckConnection.h index 7e23b7f7a..d68ffda6c 100644 --- a/client/CheckConnection.h +++ b/client/CheckConnection.h @@ -24,13 +24,16 @@ class CheckConnection { public: - CheckConnection(); - - bool check(const QString &url); + bool check(const QUrl &url = QStringLiteral("https://id.eesti.ee/config.json")); QNetworkReply::NetworkError error() const; QString errorString() const; QString errorDetails() const; + static QNetworkAccessManager* setupNAM(QNetworkRequest &req, const QByteArray &add = {}); + static QNetworkAccessManager* setupNAM(QNetworkRequest &req, + const QSslCertificate &cert, const QSslKey &key, const QByteArray &add = {}); + static QSslConfiguration sslConfiguration(const QByteArray &add = {}); + private: QNetworkReply::NetworkError m_error = QNetworkReply::NoError; QString qtmessage; diff --git a/client/Crypto.cpp b/client/Crypto.cpp new file mode 100644 index 000000000..543a2a0b4 --- /dev/null +++ b/client/Crypto.cpp @@ -0,0 +1,348 @@ +/* + * 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 + +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); + 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::resultTAG() const +{ + QByteArray result(EVP_CIPHER_CTX_block_size(ctx.get()), 0); + if(isError(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, int(result.size()), result.data()))) + result.clear(); + return result; +} + +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); + QByteArray result = c.update(data); + return c.result() ? result : QByteArray(); +} + +QByteArray Crypto::cipher(const EVP_CIPHER *cipher, const QByteArray &key, const QByteArray &data, bool encrypt) +{ + QByteArray iv(EVP_CIPHER_iv_length(cipher), 0), _data = data, tag; + if(!encrypt) + { + iv = data.left(iv.length()); + if(EVP_CIPHER_mode(cipher) == EVP_CIPH_GCM_MODE) + tag = data.right(16); + _data = data.mid(iv.size(), data.size() - iv.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 {}; + + QByteArray result(_data.size() + EVP_CIPHER_CTX_block_size(ctx.get()), Qt::Uninitialized); + int size = int(result.size()); + auto resultPointer = puchar(result.data()); //Detach only once + if(isError(EVP_CipherUpdate(ctx.get(), resultPointer, &size, pcuchar(_data.constData()), int(_data.size())))) + 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 {}; + result.resize(size + size2); + if(encrypt) + { + result.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()); + result.append(tag); + } + } + return result; +} + +QByteArray Crypto::concatKDF(QCryptographicHash::Algorithm hashAlg, quint32 keyDataLen, const QByteArray &z, const QByteArray &otherInfo) +{ + if(z.isEmpty()) + return z; + 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) +{ + ASN1_OBJECT *obj = OBJ_nid2obj(EC_GROUP_get_curve_name(EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(key)))); + QByteArray buf(50, 0); + buf.resize(int(OBJ_obj2txt(buf.data(), buf.size(), obj, 0))); + 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 new file mode 100644 index 000000000..86f742795 --- /dev/null +++ b/client/Crypto.h @@ -0,0 +1,74 @@ +/* + * 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 resultTAG() const; + }; + + static QByteArray aes_wrap(const QByteArray &key, const QByteArray &data, bool encrypt); + static QByteArray cipher(const EVP_CIPHER *cipher, const QByteArray &key, const QByteArray &data, bool encrypt); + static QByteArray curve_oid(EVP_PKEY *key); + static QByteArray concatKDF(QCryptographicHash::Algorithm digestMethod, + quint32 keyDataLen, 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 04a535cfc..b485d437e 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -20,89 +20,36 @@ #include "CryptoDoc.h" #include "Application.h" +#include "CDoc1.h" +#include "CDoc2.h" +#include "Crypto.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 #include #include -#include -#include #include -#include -#include #include #include -#include -#include #include #include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#if defined(Q_OS_WIN) -#include -#include -#endif +#include using namespace ria::qdigidoc4; -using puchar = uchar *; -using pcuchar = const uchar *; - -#define SCOPE(TYPE, DATA) std::unique_ptr(DATA, TYPE##_free) - -Q_LOGGING_CATEGORY(CRYPTO,"CRYPTO") - class CryptoDoc::Private final: public QThread { Q_OBJECT public: - struct File - { - QString name, id, mime, size; - QByteArray data; - }; - - static QByteArray AES_wrap(const QByteArray &key, const QByteArray &data, bool encrypt); - QByteArray crypto(const EVP_CIPHER *cipher, const QByteArray &data, bool encrypt); - bool isEncryptedWarning() const; -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - static QByteArray fromBase64(QStringView data); -#else - static QByteArray fromBase64(const QStringRef &data); -#endif - static bool opensslError(bool err); - QByteArray readCDoc(QIODevice *cdoc, bool data); - void readDDoc(QIODevice *ddoc); void run() final; - static void showError(const QString &err, const QString &details = {}) - { - WarningDialog::show(err, details); - } - static QString size(const QString &size) - { - bool converted = false; - quint64 result = size.toUInt(&converted); - return converted ? FileDialog::fileSize(result) : size; - } inline void waitForFinished() { QEventLoop e; @@ -110,703 +57,38 @@ class CryptoDoc::Private final: public QThread start(); e.exec(); } - static void 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()); - } - static void writeElement(QXmlStreamWriter &x, const QString &ns, const QString &name, const std::function &f = nullptr) - { - x.writeStartElement(ns, name); - if(f) - f(); - x.writeEndElement(); - } - static void writeElement(QXmlStreamWriter &x, const QString &ns, const QString &name, const QMap &attrs, const std::function &f = nullptr) - { - x.writeStartElement(ns, name); - writeAttributes(x, attrs); - if(f) - f(); - x.writeEndElement(); - } - static void writeBase64(QXmlStreamWriter &x, const QByteArray &data) - { - for(int i = 0; i < data.size(); i+=48) - x.writeCharacters(data.mid(i, 48).toBase64() + "\n"); - } - static void writeBase64Element(QXmlStreamWriter &x, const QString &ns, const QString &name, const QByteArray &data) - { - x.writeStartElement(ns, name); - writeBase64(x, data); - x.writeEndElement(); - } - void writeCDoc(QIODevice *cdoc, const QByteArray &transportKey, - const QByteArray &encryptedData, const QString &file, const QString &mime); - void writeDDoc(QIODevice *ddoc); - - static const QString MIME_XML, MIME_ZLIB, MIME_DDOC, MIME_DDOC_OLD; - static const QString DS, DENC, DSIG11, XENC11; - static const QString AES128CBC_MTH, AES192CBC_MTH, AES256CBC_MTH, AES128GCM_MTH, AES192GCM_MTH, AES256GCM_MTH, - RSA_MTH, KWAES128_MTH, KWAES192_MTH, KWAES256_MTH, CONCATKDF_MTH, AGREEMENT_MTH, SHA256_MTH, SHA384_MTH, SHA512_MTH; - static const QHash ENC_MTH; - static const QHash SHA_MTH; - static const QHash KWAES_SIZE; - QString method, mime, fileName, lastError; + std::unique_ptr cdoc; + QString fileName; QByteArray key; - QHash properties; - QList keys; - QList files; bool isEncrypted = false; CDocumentModel *documents = new CDocumentModel(this); QStringList tempFiles; }; -const QString CryptoDoc::Private::MIME_XML = QStringLiteral("text/xml"); -const QString CryptoDoc::Private::MIME_ZLIB = QStringLiteral("http://www.isi.edu/in-noes/iana/assignments/media-types/application/zip"); -const QString CryptoDoc::Private::MIME_DDOC = QStringLiteral("http://www.sk.ee/DigiDoc/v1.3.0/digidoc.xsd"); -const QString CryptoDoc::Private::MIME_DDOC_OLD = QStringLiteral("http://www.sk.ee/DigiDoc/1.3.0/digidoc.xsd"); -const QString CryptoDoc::Private::DS = QStringLiteral("http://www.w3.org/2000/09/xmldsig#"); -const QString CryptoDoc::Private::DENC = QStringLiteral("http://www.w3.org/2001/04/xmlenc#"); -const QString CryptoDoc::Private::DSIG11 = QStringLiteral("http://www.w3.org/2009/xmldsig11#"); -const QString CryptoDoc::Private::XENC11 = QStringLiteral("http://www.w3.org/2009/xmlenc11#"); - -const QString CryptoDoc::Private::AES128CBC_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#aes128-cbc"); -const QString CryptoDoc::Private::AES192CBC_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#aes192-cbc"); -const QString CryptoDoc::Private::AES256CBC_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#aes256-cbc"); -const QString CryptoDoc::Private::AES128GCM_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#aes128-gcm"); -const QString CryptoDoc::Private::AES192GCM_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#aes192-gcm"); -const QString CryptoDoc::Private::AES256GCM_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#aes256-gcm"); -const QString CryptoDoc::Private::RSA_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#rsa-1_5"); -const QString CryptoDoc::Private::KWAES128_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#kw-aes128"); -const QString CryptoDoc::Private::KWAES192_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#kw-aes192"); -const QString CryptoDoc::Private::KWAES256_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#kw-aes256"); -const QString CryptoDoc::Private::CONCATKDF_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#ConcatKDF"); -const QString CryptoDoc::Private::AGREEMENT_MTH = QStringLiteral("http://www.w3.org/2009/xmlenc11#ECDH-ES"); -const QString CryptoDoc::Private::SHA256_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha256"); -const QString CryptoDoc::Private::SHA384_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha384"); -const QString CryptoDoc::Private::SHA512_MTH = QStringLiteral("http://www.w3.org/2001/04/xmlenc#sha512"); - -const QHash CryptoDoc::Private::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 CryptoDoc::Private::SHA_MTH{ - {SHA256_MTH, QCryptographicHash::Sha256}, {SHA384_MTH, QCryptographicHash::Sha384}, {SHA512_MTH, QCryptographicHash::Sha512} -}; -const QHash CryptoDoc::Private::KWAES_SIZE{{KWAES128_MTH, 16}, {KWAES192_MTH, 24}, {KWAES256_MTH, 32}}; - -QByteArray CryptoDoc::Private::AES_wrap(const QByteArray &key, const QByteArray &data, bool encrypt) -{ - QByteArray result; - AES_KEY aes{}; - if(0 != (encrypt ? - AES_set_encrypt_key(pcuchar(key.data()), key.length() * 8, &aes) : - AES_set_decrypt_key(pcuchar(key.data()), key.length() * 8, &aes))) - return result; - result.resize(data.size() + 8); - int size = encrypt ? - AES_wrap_key(&aes, nullptr, puchar(result.data()), pcuchar(data.data()), uint(data.size())) : - AES_unwrap_key(&aes, nullptr, puchar(result.data()), pcuchar(data.data()), uint(data.size())); - if(size > 0) - result.resize(size); - else - result.clear(); - return result; -} - -QByteArray CryptoDoc::Private::crypto(const EVP_CIPHER *cipher, const QByteArray &data, bool encrypt) -{ - QByteArray iv, _data, tag; - if(encrypt) - { -#ifdef WIN32 - RAND_poll(); -#else - RAND_load_file("/dev/urandom", 1024); -#endif - _data = data; - iv.resize(EVP_CIPHER_iv_length(cipher)); - key.resize(EVP_CIPHER_key_length(cipher)); - std::array salt{}; - std::array indata{}; - RAND_bytes(salt.data(), salt.size()); - RAND_bytes(indata.data(), indata.size()); - if(opensslError(EVP_BytesToKey(cipher, EVP_sha256(), salt.data(), indata.data(), indata.size(), - 1, puchar(key.data()), puchar(iv.data())) <= 0)) - return {}; - } - else - { - iv = data.left(EVP_CIPHER_iv_length(cipher)); - if(EVP_CIPHER_mode(cipher) == EVP_CIPH_GCM_MODE) - tag = data.right(16); - _data = data.mid(iv.size(), data.size() - iv.size() - tag.size()); - } - - auto ctx = SCOPE(EVP_CIPHER_CTX, EVP_CIPHER_CTX_new()); - if(opensslError(EVP_CipherInit(ctx.get(), cipher, pcuchar(key.constData()), pcuchar(iv.constData()), encrypt) <= 0)) - return {}; - - int size = 0; - QByteArray result(_data.size() + EVP_CIPHER_CTX_block_size(ctx.get()), Qt::Uninitialized); - auto resultPointer = puchar(result.data()); //Detach only once - if(opensslError(EVP_CipherUpdate(ctx.get(), resultPointer, &size, pcuchar(_data.constData()), _data.size()) <= 0)) - return {}; - - if(!encrypt && EVP_CIPHER_mode(cipher) == EVP_CIPH_GCM_MODE) - EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), tag.data()); - - int size2 = 0; - if(opensslError(EVP_CipherFinal(ctx.get(), resultPointer + size, &size2) <= 0)) - return {}; - result.resize(size + size2); - if(encrypt) - { - result.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, tag.size(), tag.data()); - result.append(tag); - } - } - return result; -} - -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -QByteArray CryptoDoc::Private::fromBase64(QStringView data) -#else -QByteArray CryptoDoc::Private::fromBase64(const QStringRef &data) -#endif -{ - 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 = 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; -} - bool CryptoDoc::Private::isEncryptedWarning() const { if( fileName.isEmpty() ) - showError(CryptoDoc::tr("Container is not open")); + WarningDialog::show(CryptoDoc::tr("Container is not open")); if(isEncrypted) - showError(CryptoDoc::tr("Container is encrypted")); + WarningDialog::show(CryptoDoc::tr("Container is encrypted")); return fileName.isEmpty() || isEncrypted; } -bool CryptoDoc::Private::opensslError(bool err) -{ - if(err) - { - unsigned long errorCode = 0; - while((errorCode = ERR_get_error()) != 0) - qCWarning(CRYPTO) << ERR_error_string(errorCode, nullptr); - } - return err; -} - void CryptoDoc::Private::run() { - if(!isEncrypted) - { - qCDebug(CRYPTO) << "Encrypt" << fileName; - QBuffer data; - if(!data.open(QBuffer::WriteOnly)) - return; - - QString mime, name; - if(files.size() > 1) - { - qCDebug(CRYPTO) << "Creating DDoc container"; - writeDDoc(&data); - mime = MIME_DDOC; - name = QFileInfo(fileName).completeBaseName() + ".ddoc"; - } - else - { - qCDebug(CRYPTO) << "Adding raw file"; - data.write(files[0].data); - mime = files[0].mime; - name = files[0].name; - } - method = AES256GCM_MTH; - - QByteArray result = crypto(ENC_MTH[method], data.data(), true); - if(QFile cdoc(fileName); cdoc.open(QFile::WriteOnly)) - writeCDoc(&cdoc, key, result, name, mime); - } - else + if(isEncrypted) { qCDebug(CRYPTO) << "Decrypt" << fileName; - QByteArray result; - if(QFile cdoc(fileName); cdoc.open(QFile::ReadOnly)) - result = readCDoc(&cdoc, true); - result = crypto(ENC_MTH[method], result, false); - - // remove ANSIX923 padding - if(result.size() > 0 && method == AES128CBC_MTH) - { - QByteArray ansix923(result[result.size()-1], 0); - ansix923[ansix923.size()-1] = char(ansix923.size()); - if(result.right(ansix923.size()) == ansix923) - { - qCDebug(CRYPTO) << "Removing ANSIX923 padding size:" << ansix923.size(); - result.resize(result.size() - ansix923.size()); - } - } - - if(mime == MIME_ZLIB) - { - // Add size header for qUncompress compatibilty - unsigned int origsize = std::max(properties[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)); - result = qUncompress(size + result); - 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()) - { - lastError = CryptoDoc::tr("Failed to create temporary files
%1").arg(ddoc.errorString()); - return; - } - ddoc.write(result); - ddoc.flush(); - ddoc.reset(); - result.clear(); - readDDoc(&ddoc); - } - else - { - qCDebug(CRYPTO) << "Contains raw file" << mime; - if(!files.isEmpty()) - files[0].data = result; - else if(properties.contains(QStringLiteral("Filename"))) - { - File f; - f.name = properties[QStringLiteral("Filename")]; - f.mime = mime; - f.size = FileDialog::fileSize(quint64(result.size())); - f.data = result; - files.append(std::move(f)); - } - else - lastError = CryptoDoc::tr("Error parsing document"); - } - } - isEncrypted = !isEncrypted; -} - -QByteArray CryptoDoc::Private::readCDoc(QIODevice *cdoc, bool data) -{ - qCDebug(CRYPTO) << "Parsing CDOC file, reading data only" << data; - QXmlStreamReader xml(cdoc); - - if(!data) - { - files.clear(); - keys.clear(); - properties.clear(); - method.clear(); - mime.clear(); - } - bool atStart = true; - while( !xml.atEnd() ) - { - switch(xml.readNext()) - { - case QXmlStreamReader::StartElement: - atStart = false; - break; - case QXmlStreamReader::DTD: - showError(CryptoDoc::tr("An error occurred while opening the document."), QStringLiteral("XML DTD Declarations are not supported")); - return {}; - case QXmlStreamReader::EntityReference: - showError(CryptoDoc::tr("An error occurred while opening the document."), QStringLiteral("XML ENTITY References are not supported")); - return {}; - default: continue; - } - - if(data) - { - // EncryptedData/KeyInfo - if(xml.name() == QLatin1String("KeyInfo")) - xml.skipCurrentElement(); - // EncryptedData/CipherData/CipherValue - else if(xml.name() == QLatin1String("CipherValue")) - { - xml.readNext(); - return fromBase64(xml.text()); - } - continue; - } - - // 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('|'); - File file; - file.name = fileparts.value(0); - file.size = size(fileparts.value(1)); - file.mime = fileparts.value(2); - file.id = fileparts.value(3); - files.append(std::move(file)); - } - 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 - else if(xml.name() == QLatin1String("EncryptedKey")) - { - CKey key; - key.id = xml.attributes().value(QLatin1String("Id")).toString(); - 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; - // EncryptedData/KeyInfo/KeyName - if(xml.name() == QLatin1String("KeyName")) - key.name = xml.readElementText(); - // EncryptedData/KeyInfo/EncryptedKey/EncryptionMethod - else if(xml.name() == QLatin1String("EncryptionMethod")) - key.method = xml.attributes().value(QLatin1String("Algorithm")).toString(); - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod - else if(xml.name() == QLatin1String("AgreementMethod")) - key.agreement = xml.attributes().value(QLatin1String("Algorithm")).toString(); - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod - else if(xml.name() == QLatin1String("KeyDerivationMethod")) - key.derive = xml.attributes().value(QLatin1String("Algorithm")).toString(); - // 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[0] == char(0x00)) key.AlgorithmID.remove(0, 1); - key.PartyUInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyUInfo")).toUtf8()); - if(key.PartyUInfo[0] == char(0x00)) key.PartyUInfo.remove(0, 1); - key.PartyVInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyVInfo")).toUtf8()); - if(key.PartyVInfo[0] == 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.cert = 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)); - } + isEncrypted = !cdoc->decryptPayload(key); } - if(xml.hasError() && atStart) - showError(CryptoDoc::tr("Failed to open the container. " - "You need to update your ID-software in order to open CDOC2 containers. " - "Install new ID-software from www.id.ee."), xml.errorString()); - else if(xml.hasError()) - showError(CryptoDoc::tr("An error occurred while opening the document."), xml.errorString()); - return {}; -} - -void CryptoDoc::Private::writeCDoc(QIODevice *cdoc, const QByteArray &transportKey, - const QByteArray &encryptedData, const QString &file, const QString &mime) -{ -#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"), file }, - }; - QList reverse = files; - std::reverse(reverse.begin(), reverse.end()); - for(const File &f: qAsConst(reverse)) - props.insert(QStringLiteral("orig_file"), QStringLiteral("%1|%2|%3|%4").arg(f.name).arg(f.data.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.id.isEmpty()) - w.writeAttribute(QStringLiteral("Id"), k.id); - if(!k.recipient.isEmpty()) - w.writeAttribute(QStringLiteral("Recipient"), k.recipient); - QByteArray cipher; - if (k.cert.publicKey().algorithm() == QSsl::Rsa) - { - RSA *rsa = static_cast(k.cert.publicKey().handle()); - cipher.resize(RSA_size(rsa)); - if(opensslError(RSA_public_encrypt(transportKey.size(), pcuchar(transportKey.constData()), - puchar(cipher.data()), rsa, RSA_PKCS1_PADDING) <= 0)) - return; - writeElement(w, DENC, QStringLiteral("EncryptionMethod"), { - {QStringLiteral("Algorithm"), RSA_MTH}, - }); - writeElement(w, DS, QStringLiteral("KeyInfo"), [&]{ - if(!k.name.isEmpty()) - w.writeTextElement(DS, QStringLiteral("KeyName"), k.name); - writeElement(w, DS, QStringLiteral("X509Data"), [&]{ - writeBase64Element(w, DS, QStringLiteral("X509Certificate"), k.cert.toDer()); - }); - }); - } - else - { - QByteArray derCert = k.cert.toDer(); - auto pp = pcuchar(derCert.data()); - auto peerCert = SCOPE(X509, d2i_X509(nullptr, &pp, derCert.size())); - EVP_PKEY *peerPKey = X509_get0_pubkey(peerCert.get()); - - auto ctx = SCOPE(EVP_PKEY_CTX, EVP_PKEY_CTX_new(peerPKey, nullptr)); - EVP_PKEY *key = nullptr; - if(opensslError(!ctx) || - opensslError(EVP_PKEY_keygen_init(ctx.get()) < 1) || - opensslError(EVP_PKEY_keygen(ctx.get(), &key)) < 1) - return; - auto pkey = SCOPE(EVP_PKEY, key); - ctx = SCOPE(EVP_PKEY_CTX, EVP_PKEY_CTX_new(pkey.get(), nullptr)); - - size_t sharedSecretLen = 0; - if (opensslError(!ctx) || - opensslError(EVP_PKEY_derive_init(ctx.get()) <= 0) || - opensslError(EVP_PKEY_derive_set_peer(ctx.get(), peerPKey) <= 0) || - opensslError(EVP_PKEY_derive(ctx.get(), nullptr, &sharedSecretLen) <= 0)) - return; - QByteArray sharedSecret(int(sharedSecretLen), 0); - if(opensslError(EVP_PKEY_derive(ctx.get(), puchar(sharedSecret.data()), &sharedSecretLen) <= 0)) - return; - - QByteArray oid(50, 0); - oid.resize(OBJ_obj2txt(oid.data(), oid.size(), - OBJ_nid2obj(EC_GROUP_get_curve_name(EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey.get())))), 1)); - QByteArray SsDer(i2d_PublicKey(pkey.get(), nullptr), 0); - auto p = puchar(SsDer.data()); - i2d_PublicKey(pkey.get(), &p); - - const QString encryptionMethod = KWAES256_MTH; - 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 = CryptoDoc::concatKDF(SHA_MTH[concatDigest], KWAES_SIZE[encryptionMethod], - 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 = AES_wrap(encryptionKey, transportKey, true); - if(opensslError(cipher.isEmpty())) - return; - - writeElement(w, DENC, QStringLiteral("EncryptionMethod"), { - {QStringLiteral("Algorithm"), encryptionMethod}, - }); - 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"), "00" + props.value("DocumentFormat").toUtf8().toHex()}, - {QStringLiteral("PartyUInfo"), "00" + SsDer.toHex()}, - {QStringLiteral("PartyVInfo"), "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"), {{"URI", "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"), encryptedData); - }); - 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(); -} - -void CryptoDoc::Private::readDDoc(QIODevice *ddoc) -{ - qCDebug(CRYPTO) << "Parsing DDOC container"; - files.clear(); - QXmlStreamReader x(ddoc); - while(!x.atEnd()) - { - switch(x.readNext()) - { - case QXmlStreamReader::StartElement: break; - case QXmlStreamReader::DTD: - qCWarning(CRYPTO) << "XML DTD Declarations are not supported"; - return; - case QXmlStreamReader::EntityReference: - qCWarning(CRYPTO) << "XML ENTITY References are not supported"; - return; - default: continue; - } - - 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(); - file.data = fromBase64( x.text() ); - file.size = FileDialog::fileSize(quint64(file.data.size())); - files.append(std::move(file)); - } - } -} - -void CryptoDoc::Private::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)) + else { - 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.data.size())}, - }); - writeBase64(x, file.data); - x.writeEndElement(); //DataFile + qCDebug(CRYPTO) << "Encrypt" << fileName; + isEncrypted = cdoc->save(fileName); } - - x.writeEndElement(); //SignedDoc - x.writeEndDocument(); } - CDocumentModel::CDocumentModel(CryptoDoc::Private *doc) : d( doc ) {} @@ -819,36 +101,35 @@ bool CDocumentModel::addFile(const QString &file, const QString &mime) QFileInfo info(file); if(info.size() == 0) { - d->showError(DocumentModel::tr("Cannot add empty file to the container.")); + WarningDialog::show(DocumentModel::tr("Cannot add empty file to the container.")); return false; } - if(info.size() > 120*1024*1024) + if(d->cdoc->version() == 1 && info.size() > 120*1024*1024) { - d->showError(tr("Added file(s) exceeds the maximum size limit of the container (∼120MB). " + WarningDialog::show(tr("Added file(s) exceeds the maximum size limit of the container (∼120MB). " "Read more about it")); return false; } QString fileName(info.fileName()); - if(std::any_of(d->files.cbegin(), d->files.cend(), + if(std::any_of(d->cdoc->files.cbegin(), d->cdoc->files.cend(), [&fileName](const auto &containerFile) { return containerFile.name == fileName; })) { - d->showError(DocumentModel::tr("Cannot add the file to the envelope. File '%1' is already in container.") - .arg(FileDialog::normalized(fileName))); + WarningDialog::show(DocumentModel::tr("Cannot add the file to the envelope. File '%1' is already in container.") + .arg(FileDialog::normalized(fileName))); return false; } - QFile data(file); - if(!data.open(QFile::ReadOnly)) - return false; - CryptoDoc::Private::File f; - f.id = QStringLiteral("D%1").arg(d->files.size()); - f.mime = mime; - f.name = QFileInfo(file).fileName(); - f.data = data.readAll(); - f.size = FileDialog::fileSize(quint64(f.data.size())); - d->files.append(std::move(f)); - emit added(FileDialog::normalized(d->files.last().name)); + auto data = std::make_unique(file); + data->open(QFile::ReadWrite); + d->cdoc->files.push_back({ + QFileInfo(file).fileName(), + QStringLiteral("D%1").arg(d->cdoc->files.size()), + mime, + data->size(), + std::move(data), + }); + emit added(FileDialog::normalized(d->cdoc->files.back().name)); return true; } @@ -859,30 +140,29 @@ void CDocumentModel::addTempReference(const QString &file) QString CDocumentModel::copy(int row, const QString &dst) const { - const CryptoDoc::Private::File &file = d->files.at(row); + const CDoc::File &file = d->cdoc->files.at(row); if( QFile::exists( dst ) ) QFile::remove( dst ); - if(QFile f(dst); !f.open(QFile::WriteOnly) || f.write(file.data) < 0) - { - d->showError(tr("Failed to save file '%1'").arg(dst)); - return {}; - } - return dst; + file.data->seek(0); + if(QFile f(dst); f.open(QFile::WriteOnly) && copyIODevice(file.data.get(), &f) == file.size) + return dst; + WarningDialog::show(tr("Failed to save file '%1'").arg(dst)); + return {}; } QString CDocumentModel::data(int row) const { - return FileDialog::normalized(d->files.at(row).name); + return FileDialog::normalized(d->cdoc->files.at(row).name); } -QString CDocumentModel::fileSize(int /*row*/) const +quint64 CDocumentModel::fileSize(int row) const { - return {}; + return d->cdoc->files.at(row).size; } QString CDocumentModel::mime(int row) const { - return FileDialog::normalized(d->files.at(row).mime); + return FileDialog::normalized(d->cdoc->files.at(row).mime); } void CDocumentModel::open(int row) @@ -892,43 +172,35 @@ void CDocumentModel::open(int row) QString path = FileDialog::tempPath(FileDialog::safeName(data(row))); if(!verifyFile(path)) return; - QFileInfo f(copy(row, path)); - if( !f.exists() ) + if(copy(row, path).isEmpty()) return; - d->tempFiles.append(f.absoluteFilePath()); -#if defined(Q_OS_WIN) - ::SetFileAttributesW(f.absoluteFilePath().toStdWString().c_str(), FILE_ATTRIBUTE_READONLY); -#else - QFile::setPermissions(f.absoluteFilePath(), QFile::Permissions(QFile::Permission::ReadOwner)); -#endif - if(FileDialog::isSignedPDF(f.absoluteFilePath())) - qApp->showClient({ f.absoluteFilePath() }, false, false, true); + d->tempFiles.append(path); + FileDialog::setReadOnly(path); + if(FileDialog::isSignedPDF(path)) + Application::showClient({ path }, false, false, true); else - QDesktopServices::openUrl(QUrl::fromLocalFile(f.absoluteFilePath())); + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); } -bool CDocumentModel::removeRows(int row, int count) +bool CDocumentModel::removeRow(int row) { if(d->isEncryptedWarning()) return false; - if( d->files.isEmpty() || row >= d->files.size() ) + if(d->cdoc->files.empty() || row >= d->cdoc->files.size()) { - d->showError(DocumentModel::tr("Internal error")); + WarningDialog::show(DocumentModel::tr("Internal error")); return false; } - for( int i = row + count - 1; i >= row; --i ) - { - d->files.removeAt( i ); - emit removed(i); - } + d->cdoc->files.erase(d->cdoc->files.cbegin() + row); + emit removed(row); return true; } int CDocumentModel::rowCount() const { - return d->files.size(); + return int(d->cdoc->files.size()); } QString CDocumentModel::save(int row, const QString &path) const @@ -945,32 +217,41 @@ QString CDocumentModel::save(int row, const QString &path) const return fileName; } - - -void CKey::setCert( const QSslCertificate &c ) +CKey::CKey(const QSslCertificate &c) { - cert = 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()); QString o = c.subjectInfo(QSslCertificate::Organization); static const QRegularExpression rx(QStringLiteral("ESTEID \\((.*)\\)")); QRegularExpressionMatch match = rx.match(o); if(match.hasMatch()) - return QStringLiteral("%1,%2").arg(cn, match.captured(1)); + return QStringLiteral("%1 %2").arg(cn, match.captured(1)); if(o == QLatin1String("ESTEID")) - return QStringLiteral("%1,%2").arg(cn, CryptoDoc::tr("ID-CARD")); + return QStringLiteral("%1 %2").arg(cn, CryptoDoc::tr("ID-CARD")); int certType = c.type(); if(certType & SslCertificate::EResidentSubType) - return QStringLiteral("%1,%2").arg(cn, CryptoDoc::tr("Digi-ID E-RESIDENT")); + 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")); + 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 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; +} CryptoDoc::CryptoDoc( QObject *parent ) @@ -985,86 +266,37 @@ CryptoDoc::~CryptoDoc() { clear(); delete d; } bool CryptoDoc::addKey( const CKey &key ) { - if( d->isEncryptedWarning() ) + if(d->isEncryptedWarning()) return false; - if( d->keys.contains( key ) ) + if(d->cdoc->keys.contains(key)) { - d->showError(tr("Key already exists")); + WarningDialog::show(tr("Key already exists")); return false; } - d->keys.append(key); + d->cdoc->keys.append(key); return true; } bool CryptoDoc::canDecrypt(const QSslCertificate &cert) { - if(cert.isNull()) - return false; - for(const CKey &k: qAsConst(d->keys)) - { - if(!Private::ENC_MTH.contains(d->method) || k.cert != cert) - continue; - if(cert.publicKey().algorithm() == QSsl::Rsa && - !k.cipher.isEmpty() && - k.method == Private::RSA_MTH) - return true; - if(cert.publicKey().algorithm() == QSsl::Ec && - !k.publicKey.isEmpty() && - !k.cipher.isEmpty() && - Private::KWAES_SIZE.contains(k.method) && - k.derive == Private::CONCATKDF_MTH && - k.agreement == Private::AGREEMENT_MTH) - return true; - } - return false; -} - -QByteArray CryptoDoc::concatKDF(QCryptographicHash::Algorithm digestMethod, - quint32 keyDataLen, const QByteArray &z, const QByteArray &otherInfo) -{ - if(z.isEmpty()) - return z; - quint32 hashLen = 0; - switch(digestMethod) - { - case QCryptographicHash::Sha256: hashLen = 32; break; - case QCryptographicHash::Sha384: hashLen = 48; break; - case QCryptographicHash::Sha512: hashLen = 64; break; - default: return {}; - } - auto reps = quint32(std::ceil(double(keyDataLen) / double(hashLen))); - QCryptographicHash md(digestMethod); - 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)); + return !d->cdoc->canDecrypt(cert).key.isEmpty(); } void CryptoDoc::clear( const QString &file ) { for(const QString &f: qAsConst(d->tempFiles)) { -#if defined(Q_OS_WIN) //reset read-only attribute to enable delete file - ::SetFileAttributesW(f.toStdWString().c_str(), FILE_ATTRIBUTE_NORMAL); -#endif + FileDialog::setReadOnly(f, false); QFile::remove(f); } d->tempFiles.clear(); d->isEncrypted = false; d->fileName = file; - d->files.clear(); - d->keys.clear(); - d->properties.clear(); - d->method.clear(); - d->mime.clear(); + if(Settings::CDOC2_DEFAULT) + d->cdoc = std::make_unique(); + else + d->cdoc = std::make_unique(); } ContainerState CryptoDoc::state() const @@ -1076,54 +308,52 @@ bool CryptoDoc::decrypt() { if( d->fileName.isEmpty() ) { - d->showError(tr("Container is not open")); + WarningDialog::show(tr("Container is not open")); return false; } if(!d->isEncrypted) return true; - CKey key; - for(const CKey &k: qAsConst(d->keys)) + CKey key = d->cdoc->canDecrypt(qApp->signer()->tokenauth().cert()); + if(key.key.isEmpty()) { - if( qApp->signer()->tokenauth().cert() == k.cert ) + WarningDialog::show(tr("You do not have the key to decrypt this document")); + return false; + } + + if(d->cdoc->version() == 2 && Settings::CDOC2_USE_KEYSERVER && !Settings::CDOC2_NOTIFICATION.isSet()) + { + auto *dlg = new WarningDialog(tr("You must enter your PIN code twice in order to decrypt the CDOC2 container. " + "The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. " + "Second PIN entry is required to decrypt the CDOC2 container."), Application::mainWindow()); + dlg->setCancelText(WarningDialog::Cancel); + dlg->addButton(WarningDialog::OK, QMessageBox::Ok); + dlg->addButton(tr("DON'T SHOW AGAIN"), QMessageBox::Ignore); + switch (dlg->exec()) { - key = k; + case QMessageBox::Ok: break; + case QMessageBox::Ignore: + Settings::CDOC2_NOTIFICATION = true; break; + default: return false; } } - if( key.cert.isNull() ) - { - d->showError(tr("You do not have the key to decrypt this document")); - return false; - } - bool isECDH = key.cert.publicKey().algorithm() == QSsl::Ec; - QByteArray decryptedKey = qApp->signer()->decrypt([&key, &isECDH](QCryptoBackend *backend) { - if(!isECDH) - return backend->decrypt(key.cipher); - return backend->deriveConcatKDF(key.publicKey, Private::SHA_MTH[key.concatDigest], - int(Private::KWAES_SIZE[key.method]), key.AlgorithmID, key.PartyUInfo, key.PartyVInfo); - }); - if(decryptedKey.isEmpty()) - return false; - if(isECDH) - { + d->key = d->cdoc->transportKey(key); #ifndef NDEBUG - qDebug() << "DEC Ss" << key.publicKey.toHex(); - qDebug() << "DEC ConcatKDF" << decryptedKey.toHex(); + qDebug() << "Transport key" << d->key.toHex(); #endif - d->key = d->AES_wrap(decryptedKey, key.cipher, false); - d->opensslError(d->key.isEmpty()); + if(d->key.isEmpty()) + { + WarningDialog::show(tr("Failed to decrypt document. Please check your internet connection and network settings."), d->cdoc->lastError); + return false; } - else // RSA decrypts directly transport key - d->key = decryptedKey; -#ifndef NDEBUG - qDebug() << "DEC transport" << d->key.toHex(); -#endif d->waitForFinished(); - if( !d->lastError.isEmpty() ) - d->showError(d->lastError); + if(d->isEncrypted) + WarningDialog::show(tr("Error parsing document")); + if(!d->cdoc->lastError.isEmpty()) + WarningDialog::show(d->cdoc->lastError); return !d->isEncrypted; } @@ -1136,22 +366,22 @@ bool CryptoDoc::encrypt( const QString &filename ) d->fileName = filename; if( d->fileName.isEmpty() ) { - d->showError(tr("Container is not open")); + WarningDialog::show(tr("Container is not open")); return false; } if(d->isEncrypted) return true; - if( d->keys.isEmpty() ) + if(d->cdoc->keys.isEmpty()) { - d->showError(tr("No keys specified")); + WarningDialog::show(tr("No keys specified")); return false; } d->waitForFinished(); - if( !d->lastError.isEmpty() ) - d->showError(d->lastError); - open(d->fileName); - + if(d->isEncrypted) + open(d->fileName); + else + WarningDialog::show(tr("Failed to encrypt document. Please check your internet connection and network settings."), d->cdoc->lastError); return d->isEncrypted; } @@ -1159,13 +389,14 @@ QString CryptoDoc::fileName() const { return d->fileName; } QList CryptoDoc::keys() const { - return d->keys; + return d->cdoc->keys; } QList CryptoDoc::files() { QList fileList; - for(const Private::File &f: d->files) + fileList.reserve(d->cdoc->files.size()); + for(const CDoc::File &f: qAsConst(d->cdoc->files)) fileList.append(f.name); return fileList; } @@ -1184,29 +415,23 @@ bool CryptoDoc::move(const QString &to) bool CryptoDoc::open( const QString &file ) { clear(file); - if(QFile cdoc(d->fileName); cdoc.open(QFile::ReadOnly)) - d->readCDoc(&cdoc, false); - if(d->keys.isEmpty()) - return false; - - if(d->files.isEmpty() && d->properties.contains(QStringLiteral("Filename"))) + 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()) { - Private::File f; - f.name = d->properties[QStringLiteral("Filename")]; - f.mime = d->mime == Private::MIME_ZLIB ? d->properties[QStringLiteral("OriginalMimeType")] : d->mime; - f.size = d->size(d->properties[QStringLiteral("OriginalSize")]); - d->files.append(std::move(f)); + WarningDialog::show(tr("Failed to open document"), d->cdoc->lastError); + return false; } - - d->isEncrypted = true; Application::addRecent( file ); - return !d->keys.isEmpty(); + return true; } void CryptoDoc::removeKey( int id ) { - if( !d->isEncryptedWarning() ) - d->keys.removeAt(id); + if(!d->isEncryptedWarning()) + d->cdoc->keys.removeAt(id); } bool CryptoDoc::saveCopy(const QString &filename) diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index abb29eb9a..c759dde6d 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -22,24 +22,61 @@ #include "common_enums.h" #include "DocumentModel.h" -#include +#include #include +#include + +class QSslKey; + class CKey { public: CKey() = default; - CKey( const QSslCertificate &cert ) { setCert( cert ); } - void setCert( const QSslCertificate &cert ); - bool operator==( const CKey &other ) const { return other.cert == cert; } + CKey(const QByteArray &_key, bool _isRSA): key(_key), isRSA(_isRSA) {} + CKey(const QSslCertificate &cert); + bool operator==(const CKey &other) const { return other.key == key; } + + void setCert(const QSslCertificate &c); + QByteArray key, cipher, publicKey; QSslCertificate cert; - QString id, name, recipient, method, agreement, derive, concatDigest; + bool isRSA = false; + QString recipient; + // CDoc1 + QString agreement, concatDigest, derive, method, id, name; QByteArray AlgorithmID, PartyUInfo, PartyVInfo; - QByteArray cipher, publicKey; + // CDoc2 + QByteArray encrypted_kek; + QString keyserver_id, transaction_id; +}; + + + +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; }; -class CryptoDoc: public QObject +class CryptoDoc final: public QObject { Q_OBJECT public: @@ -61,9 +98,6 @@ class CryptoDoc: public QObject bool saveCopy(const QString &filename); ria::qdigidoc4::ContainerState state() const; - static QByteArray concatKDF(QCryptographicHash::Algorithm digestMethod, - quint32 keyDataLen, const QByteArray &z, const QByteArray &otherInfo); - private: class Private; Private *d; @@ -71,21 +105,21 @@ class CryptoDoc: public QObject friend class CDocumentModel; }; -class CDocumentModel: public DocumentModel +class CDocumentModel final: public DocumentModel { Q_OBJECT public: - bool addFile(const QString &file, const QString &mime = QStringLiteral("application/octet-stream")) override; - void addTempReference(const QString &file) override; - QString data(int row) const override; - QString fileSize(int row) const override; - QString mime(int row) const override; - bool removeRows(int row, int count) override; - int rowCount() const override; - QString save(int row, const QString &path) const override; + bool addFile(const QString &file, const QString &mime = QStringLiteral("application/octet-stream")) final; + void addTempReference(const QString &file) final; + QString data(int row) const final; + quint64 fileSize(int row) const final; + QString mime(int row) const final; + bool removeRow(int row) final; + int rowCount() const final; + QString save(int row, const QString &path) const final; public slots: - void open(int row) override; + void open(int row) final; private: CDocumentModel(CryptoDoc::Private *doc); diff --git a/client/Diagnostics.cpp b/client/Diagnostics.cpp index 39579c066..3e42444ca 100644 --- a/client/Diagnostics.cpp +++ b/client/Diagnostics.cpp @@ -45,6 +45,10 @@ void Diagnostics::generalInfo(QTextStream &s) << "
TSL_URL: " << Application::confValue(Application::TSLUrl).toString() << "
TSA_URL: " << Application::confValue(Application::TSAUrl).toString() << "
SIVA_URL: " << Application::confValue(Application::SiVaUrl).toString() + << "
CDOC2:" + << "
" << Settings::CDOC2_DEFAULT.KEY << ": " << (Settings::CDOC2_DEFAULT ? tr("true") : tr("false")) + << "
" << Settings::CDOC2_USE_KEYSERVER.KEY << ": " << (Settings::CDOC2_USE_KEYSERVER ? tr("true") : tr("false")) + << "
" << Settings::CDOC2_DEFAULT_KEYSERVER.KEY << ": " << Settings::CDOC2_DEFAULT_KEYSERVER << "

" << tr("TSL signing certs") << ":"; for(const QSslCertificate &cert: Application::confValue(Application::TSLCerts).value>()) s << "
" << cert.subjectInfo("CN").value(0); @@ -53,8 +57,7 @@ void Diagnostics::generalInfo(QTextStream &s) const QStringList tsllist = QDir(cache).entryList({QStringLiteral("*.xml")}); for(const QString &file: tsllist) { - uint ver = Application::readTSLVersion(cache + "/" + file); - if(ver > 0) + if(uint ver = Application::readTSLVersion(cache + "/" + file); ver > 0) s << "
" << file << " (" << ver << ")"; } s << "

"; @@ -126,7 +129,7 @@ void Diagnostics::generalInfo(QTextStream &s) if (r.SW == APDU("6A81")) s << " (Locked)"; if (r.SW == APDU("6A82")) s << " (Not found)"; s << "
"; - return r.resultOk(); + return r; }; if(printAID(QStringLiteral("AID35"), APDU("00A40400 0F D23300000045737445494420763335")) || printAID(QStringLiteral("UPDATER_AID"), APDU("00A40400 0A D2330000005550443101"))) @@ -138,9 +141,8 @@ void Diagnostics::generalInfo(QTextStream &s) row[2] = 0x07; // read card id s << "ID - " << reader.transfer(row).data << "
"; - QPCSCReader::Result data = reader.transfer(APDU("00CA0100 00")); QString appletVersion; - if(data.resultOk()) + if(QPCSCReader::Result data = reader.transfer(APDU("00CA0100 00"))) { for(int i = 0; i < data.data.size(); ++i) { @@ -166,6 +168,6 @@ void Diagnostics::generalInfo(QTextStream &s) } #ifdef Q_OS_WIN - s << "" << tr("Smart Card reader drivers") << ":
" << QPCSC::instance().drivers().join(QStringLiteral("
")); + s << "" << tr("Smart Card reader drivers") << ":
" << QPCSC::instance().drivers().join(QLatin1String("
")); #endif } diff --git a/client/DigiDoc.cpp b/client/DigiDoc.cpp index 34b57190d..fa12b8d5b 100644 --- a/client/DigiDoc.cpp +++ b/client/DigiDoc.cpp @@ -40,11 +40,6 @@ #include -#if defined(Q_OS_WIN) -#include -#include -#endif - using namespace digidoc; using namespace ria::qdigidoc4; @@ -165,7 +160,7 @@ QStringList DigiDocSignature::roles() const { QStringList list; for(const std::string &role: s->signerRoles()) - list.append(from( role ).trimmed()); + list.append(from(role).trimmed()); return list; } @@ -320,12 +315,12 @@ QString SDocumentModel::data(int row) const return from(doc->b->dataFiles().at(size_t(row))->fileName()); } -QString SDocumentModel::fileSize(int row) const +quint64 SDocumentModel::fileSize(int row) const { if(row >= rowCount()) return {}; - return FileDialog::fileSize(doc->b->dataFiles().at(size_t(row))->fileSize()); + return doc->b->dataFiles().at(size_t(row))->fileSize(); } QString SDocumentModel::mime(int row) const @@ -348,30 +343,23 @@ void SDocumentModel::open(int row) if( !f.exists() ) return; doc->m_tempFiles.append(f.absoluteFilePath()); -#if defined(Q_OS_WIN) - ::SetFileAttributesW(f.absoluteFilePath().toStdWString().c_str(), FILE_ATTRIBUTE_READONLY); -#else - QFile::setPermissions(f.absoluteFilePath(), QFile::Permissions(QFile::Permission::ReadOwner)); -#endif + FileDialog::setReadOnly(f.absoluteFilePath()); if(!doc->fileName().endsWith(QStringLiteral(".pdf"), Qt::CaseInsensitive) && FileDialog::isSignedPDF(f.absoluteFilePath())) qApp->showClient({ f.absoluteFilePath() }, false, false, true); else QDesktopServices::openUrl(QUrl::fromLocalFile(f.absoluteFilePath())); } -bool SDocumentModel::removeRows(int row, int count) +bool SDocumentModel::removeRow(int row) { if(!doc->b) return false; try { - for(int i = row + count - 1; i >= row; --i) - { - doc->b->removeDataFile(i); - doc->modified = true; - emit removed(i); - } + doc->b->removeDataFile(row); + doc->modified = true; + emit removed(row); return true; } catch( const Exception &e ) { doc->setLastError( tr("Failed remove document from container"), e ); } @@ -427,10 +415,8 @@ void DigiDoc::clear() m_fileName.clear(); for(const QString &file: m_tempFiles) { -#if defined(Q_OS_WIN) //reset read-only attribute to enable delete file - ::SetFileAttributesW(file.toStdWString().c_str(), FILE_ATTRIBUTE_NORMAL); -#endif + FileDialog::setReadOnly(file, false); QFile::remove(file); } @@ -503,8 +489,8 @@ bool DigiDoc::open( const QString &file ) auto *dlg = new WarningDialog(tr("Signed document in PDF and DDOC format will be transmitted to the Digital Signature Validation Service SiVa to verify the validity of the digital signature. " "Read more information about transmitted data to Digital Signature Validation service from here.
" "Do you want to continue?"), parent); - dlg->setCancelText(tr("CANCEL")); - dlg->addButton(tr("YES"), ContainerSave); + dlg->setCancelText(WarningDialog::Cancel); + dlg->addButton(WarningDialog::YES, ContainerSave); return dlg->exec() == ContainerSave; }; if((file.endsWith(QStringLiteral(".pdf"), Qt::CaseInsensitive) || @@ -521,7 +507,7 @@ bool DigiDoc::open( const QString &file ) { const DataFile *f = b->dataFiles().at(0); if(from(f->fileName()).endsWith(QStringLiteral(".ddoc"), Qt::CaseInsensitive) && - CheckConnection().check(QStringLiteral("https://id.eesti.ee/config.json")) && + CheckConnection().check() && dispatchToMain(serviceConfirmation)) { const QString tmppath = FileDialog::tempPath(FileDialog::safeName(from(f->fileName()))); diff --git a/client/DigiDoc.h b/client/DigiDoc.h index 884667531..ea220eb8c 100644 --- a/client/DigiDoc.h +++ b/client/DigiDoc.h @@ -88,19 +88,19 @@ class DigiDocSignature }; -class SDocumentModel: public DocumentModel +class SDocumentModel final: public DocumentModel { Q_OBJECT public: - bool addFile(const QString &file, const QString &mime = QStringLiteral("application/octet-stream")) override; - void addTempReference(const QString &file) override; - QString data(int row) const override; - QString fileSize(int row) const override; - QString mime(int row) const override; - bool removeRows(int row, int count) override; - int rowCount() const override; - QString save(int row, const QString &path) const override; + bool addFile(const QString &file, const QString &mime = QStringLiteral("application/octet-stream")) final; + void addTempReference(const QString &file) final; + QString data(int row) const final; + quint64 fileSize(int row) const final; + QString mime(int row) const final; + bool removeRow(int row) final; + int rowCount() const final; + QString save(int row, const QString &path) const final; public slots: void open(int row) override; diff --git a/client/DocumentModel.cpp b/client/DocumentModel.cpp index e3ea89972..6e74c28b3 100644 --- a/client/DocumentModel.cpp +++ b/client/DocumentModel.cpp @@ -60,7 +60,7 @@ bool DocumentModel::verifyFile(const QString &f) QJsonArray allowedExts = Application::confValue(QLatin1String("ALLOWED-EXTENSIONS")).toArray(defaultArray); if(!allowedExts.contains(QJsonValue(QFileInfo(f).suffix().toLower()))){ - WarningDialog::show(tr("A file with this extension cannot be opened in the DigiDoc4 Client. Download the file to view it."))->setCancelText(tr("OK")); + WarningDialog::show(tr("A file with this extension cannot be opened in the DigiDoc4 Client. Download the file to view it."))->setCancelText(WarningDialog::OK); return false; } diff --git a/client/DocumentModel.h b/client/DocumentModel.h index ec1459cff..1b20916ac 100644 --- a/client/DocumentModel.h +++ b/client/DocumentModel.h @@ -30,10 +30,10 @@ class DocumentModel: public QObject virtual void addTempFiles(const QStringList &files); virtual void addTempReference(const QString &file) = 0; virtual QString data(int row) const = 0; - virtual QString fileSize(int row) const = 0; + virtual quint64 fileSize(int row) const = 0; virtual QString mime(int row) const = 0; virtual void open(int row) = 0; - virtual bool removeRows(int row, int count) = 0; + virtual bool removeRow(int row) = 0; virtual int rowCount() const = 0; virtual QString save(int row, const QString &path) const = 0; virtual QStringList tempFiles() const; diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index 032d86e93..ec47cae6a 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -87,7 +87,7 @@ MainWindow::MainWindow( QWidget *parent ) connect(warnings, &WarningList::warningClicked, this, &MainWindow::warningClicked); - ui->version->setText(QStringLiteral("%1%2").arg(tr("Ver. "), qApp->applicationVersion())); + ui->version->setText(QStringLiteral("%1%2").arg(tr("Ver. "), Application::applicationVersion())); connect(ui->version, &QPushButton::clicked, this, [this] {showSettings(SettingsDialog::DiagnosticsSettings);}); ui->coatOfArms->load(QStringLiteral(":/images/Logo_small.svg")); @@ -232,7 +232,7 @@ void MainWindow::changeEvent(QEvent* event) { ui->retranslateUi(this); ui->noReaderInfoText->setText(tr(ui->noReaderInfoText->property("currenttext").toByteArray())); - ui->version->setText(QStringLiteral("%1%2").arg(tr("Ver. "), qApp->applicationVersion())); + ui->version->setText(QStringLiteral("%1%2").arg(tr("Ver. "), Application::applicationVersion())); setWindowTitle(windowFilePath().isEmpty() ? tr("DigiDoc4 Client") : FileDialog::normalized(QFileInfo(windowFilePath()).fileName())); showCardMenu(false); } @@ -326,7 +326,7 @@ bool MainWindow::encrypt() if(!FileDialog::fileIsWritable(cryptoDoc->fileName())) { auto *dlg = new WarningDialog(tr("Cannot alter container %1. Save different location?") .arg(FileDialog::normalized(cryptoDoc->fileName())), this); - dlg->addButton(tr("YES").toUpper(), QMessageBox::Yes); + dlg->addButton(WarningDialog::YES, QMessageBox::Yes); if(dlg->exec() == QMessageBox::Yes) { moveCryptoContainer(); return encrypt(); @@ -573,7 +573,7 @@ void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString if( !FileDialog::fileIsWritable(target)) { auto *dlg = new WarningDialog(tr("Cannot alter container %1. Save different location?").arg(target), this); - dlg->addButton(tr("YES").toUpper(), QMessageBox::Yes); + dlg->addButton(WarningDialog::YES, QMessageBox::Yes); if(dlg->exec() == QMessageBox::Yes) { QString file = selectFile(tr("Save file"), target, true); if(!file.isEmpty()) @@ -708,9 +708,9 @@ void MainWindow::openContainer(bool signature) QString filter = QFileDialog::tr("All Files (*)") + QStringLiteral(";;") + tr("Documents (%1)"); if(signature) filter = filter.arg(QStringLiteral("*.bdoc *.ddoc *.asice *.sce *.asics *.scs *.edoc *.adoc%1") - .arg(qApp->confValue(Application::SiVaUrl).toString().isEmpty() ? QLatin1String() : QLatin1String(" *.pdf"))); + .arg(Application::confValue(Application::SiVaUrl).toString().isEmpty() ? QLatin1String() : QLatin1String(" *.pdf"))); else - filter = filter.arg(QLatin1String("*.cdoc")); + filter = filter.arg(QLatin1String("*.cdoc *.cdoc2")); QStringList files = FileDialog::getOpenFileNames(this, tr("Select documents"), {}, filter); if(!files.isEmpty()) openFiles(files); @@ -776,7 +776,7 @@ bool MainWindow::save(bool saveAs) if(!FileDialog::fileIsWritable(target)) { auto *dlg = new WarningDialog(tr("Cannot alter container %1. Save different location?").arg(target), this); - dlg->addButton(tr("YES").toUpper(), QMessageBox::Yes); + dlg->addButton(WarningDialog::YES, QMessageBox::Yes); if(dlg->exec() == QMessageBox::Yes) { if(QString file = selectFile(tr("Save file"), target, true); !file.isEmpty()) return saveAs ? digiDoc->saveAs(file) : digiDoc->save(file); @@ -790,6 +790,7 @@ QString MainWindow::selectFile( const QString &title, const QString &filename, b static const QString adoc = tr("Documents (%1)").arg(QLatin1String("*.adoc")); static const QString bdoc = tr("Documents (%1)").arg(QLatin1String("*.bdoc")); static const QString cdoc = tr("Documents (%1)").arg(QLatin1String("*.cdoc")); + static const QString cdoc2 = tr("Documents (%1)").arg(QLatin1String("*.cdoc2")); static const QString edoc = tr("Documents (%1)").arg(QLatin1String("*.edoc")); static const QString asic = tr("Documents (%1)").arg(QLatin1String("*.asice *.sce")); const QString ext = QFileInfo( filename ).suffix().toLower(); @@ -799,6 +800,7 @@ QString MainWindow::selectFile( const QString &title, const QString &filename, b { if(ext == QLatin1String("bdoc")) exts.append(bdoc); if(ext == QLatin1String("cdoc")) exts.append(cdoc); + if(ext == QLatin1String("cdoc2")) exts.append(cdoc2); if(ext == QLatin1String("asice") || ext == QLatin1String("sce")) exts.append(asic); if(ext == QLatin1String("edoc")) exts.append(edoc); if(ext == QLatin1String("adoc")) exts.append(adoc); @@ -808,6 +810,7 @@ QString MainWindow::selectFile( const QString &title, const QString &filename, b exts = QStringList{ bdoc, asic, edoc, adoc }; if(ext == QLatin1String("bdoc")) active = bdoc; if(ext == QLatin1String("cdoc")) active = cdoc; + if(ext == QLatin1String("cdoc2")) active = cdoc2; if(ext == QLatin1String("asice") || ext == QLatin1String("sce")) active = asic; if(ext == QLatin1String("edoc")) active = edoc; if(ext == QLatin1String("adoc")) active = adoc; @@ -889,7 +892,7 @@ void MainWindow::showSettings(int page) template void MainWindow::sign(F &&sign) { - if(!CheckConnection().check(QStringLiteral("https://id.eesti.ee/config.json"))) + if(!CheckConnection().check()) { auto *notification = new FadeInNotification(this, MOJO, MARZIPAN, 110); notification->start(tr("Check internet connection"), 750, 3000, 1200); @@ -955,12 +958,12 @@ bool MainWindow::removeFile(DocumentModel *model, int index) auto count = model->rowCount(); if(count != 1) { - model->removeRows(index, 1); + model->removeRow(index); } else { auto *dlg = new WarningDialog(tr("You are about to delete the last file in the container, it is removed along with the container."), this); - dlg->setCancelText(tr("CANCEL")); + dlg->setCancelText(WarningDialog::Cancel); dlg->resetCancelStyle(); dlg->addButton(tr("REMOVE"), ContainerSave, true); if (dlg->exec() == ContainerSave) { @@ -971,7 +974,7 @@ bool MainWindow::removeFile(DocumentModel *model, int index) } for(auto i = 0; i < model->rowCount(); ++i) { - if(!model->fileSize(i).trimmed().startsWith(QLatin1String("0 "))) + if(model->fileSize(i) > 0) continue; warnings->closeWarning(EmptyFileWarning); if(digiDoc) @@ -1028,7 +1031,7 @@ bool MainWindow::validateFiles(const QString &container, const QStringList &file [containerInfo] (const QString &file) { return containerInfo == QFileInfo(file); })) return true; WarningDialog::show(this, tr("Cannot add container to same container\n%1") - .arg(FileDialog::normalized(container)))->setCancelText(tr("CANCEL")); + .arg(FileDialog::normalized(container)))->setCancelText(WarningDialog::Cancel); return false; } @@ -1072,7 +1075,7 @@ bool MainWindow::wrapContainer(bool signing) tr("Files can not be added to the signed container. The system will create a new container which shall contain the signed document and the files you wish to add.") : tr("Files can not be added to the cryptocontainer. The system will create a new container which shall contain the cypto-document and the files you wish to add."); auto *dlg = new WarningDialog(msg, this); - dlg->setCancelText(tr("CANCEL")); + dlg->setCancelText(WarningDialog::Cancel); dlg->addButton(tr("CONTINUE"), ContainerSave); return dlg->exec() == ContainerSave; } diff --git a/client/PrintSheet.cpp b/client/PrintSheet.cpp index 7d7002b62..74f2e7d19 100644 --- a/client/PrintSheet.cpp +++ b/client/PrintSheet.cpp @@ -61,6 +61,23 @@ PrintSheet::PrintSheet( DigiDoc *doc, QPrinter *printer ) text.setFamily(QStringLiteral("Arial, Liberation Sans, Helvetica, sans-serif")); text.setPixelSize( 12 ); + auto fileSize = [](quint64 bytes) + { + const quint64 kb = 1024; + const quint64 mb = 1024 * kb; + const quint64 gb = 1024 * mb; + const quint64 tb = 1024 * gb; + if(bytes >= tb) + return QStringLiteral("%1 TB").arg(qreal(bytes) / tb, 0, 'f', 3); + if(bytes >= gb) + return QStringLiteral("%1 GB").arg(qreal(bytes) / gb, 0, 'f', 2); + if(bytes >= mb) + return QStringLiteral("%1 MB").arg(qreal(bytes) / mb, 0, 'f', 1); + if(bytes >= kb) + return QStringLiteral("%1 KB").arg(bytes / kb); + return QStringLiteral("%1 B").arg(bytes); + }; + QFont head = text; QFont sHead = text; head.setPixelSize( 28 ); @@ -95,7 +112,7 @@ PrintSheet::PrintSheet( DigiDoc *doc, QPrinter *printer ) int fileHeight = drawTextRect( QRect( left, top+5, right - left - 150, 20 ), doc->documentModel()->data(i) ); drawTextRect( QRect( right-150, top+5, 150, fileHeight ), - doc->documentModel()->fileSize(i) ); + fileSize(doc->documentModel()->fileSize(i))); top += fileHeight; newPage( 50 ); } diff --git a/client/QCNG.cpp b/client/QCNG.cpp index 0477a2dfe..f9daafa4d 100644 --- a/client/QCNG.cpp +++ b/client/QCNG.cpp @@ -53,13 +53,20 @@ QCNG::~QCNG() delete d; } -QByteArray QCNG::decrypt(const QByteArray &data) const +QByteArray QCNG::decrypt(const QByteArray &data, bool oaep) const { return exec([&](NCRYPT_PROV_HANDLE prov, NCRYPT_KEY_HANDLE key, QByteArray &result) { - auto size = DWORD(data.size()); + BCRYPT_OAEP_PADDING_INFO padding {BCRYPT_SHA256_ALGORITHM, nullptr, 0}; + PVOID paddingInfo = oaep ? &padding : nullptr; + DWORD flags = oaep ? NCRYPT_PAD_OAEP_FLAG : NCRYPT_PAD_PKCS1_FLAG; + DWORD size = 0; + SECURITY_STATUS err = NCryptDecrypt(key, PBYTE(data.constData()), DWORD(data.size()), + paddingInfo, nullptr, 0, &size, flags); + if(FAILED(err)) + return err; result.resize(int(size)); - SECURITY_STATUS err = NCryptDecrypt(key, PBYTE(data.constData()), DWORD(data.size()), nullptr, - PBYTE(result.data()), DWORD(result.size()), &size, NCRYPT_PAD_PKCS1_FLAG); + err = NCryptDecrypt(key, PBYTE(data.constData()), DWORD(data.size()), + paddingInfo, PBYTE(result.data()), DWORD(result.size()), &size, flags); if(SUCCEEDED(err)) result.resize(int(size)); return err; @@ -120,6 +127,27 @@ QByteArray QCNG::deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash }); } +QByteArray QCNG::deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const +{ + return derive(publicKey, [&](NCRYPT_SECRET_HANDLE sharedSecret, QByteArray &derived) { + QVector paramValues{ + {ULONG(salt.size()), KDF_HMAC_KEY, PBYTE(salt.data())}, + {ULONG(sizeof(BCRYPT_SHA256_ALGORITHM)), KDF_HASH_ALGORITHM, PBYTE(BCRYPT_SHA256_ALGORITHM)}, + }; + BCryptBufferDesc params{ BCRYPTBUFFER_VERSION }; + params.cBuffers = ULONG(paramValues.size()); + params.pBuffers = paramValues.data(); + DWORD size = 0; + SECURITY_STATUS err = 0; + if(FAILED(err = NCryptDeriveKey(sharedSecret, BCRYPT_KDF_HMAC, ¶ms, nullptr, 0, &size, 0))) + return err; + derived.resize(int(size)); + if(SUCCEEDED(err = NCryptDeriveKey(sharedSecret, BCRYPT_KDF_HMAC, ¶ms, PBYTE(derived.data()), size, &size, 0))) + derived.resize(keySize); + return err; + }); +} + QByteArray QCNG::exec(std::function &&func) const { d->err = UnknownError; diff --git a/client/QCNG.h b/client/QCNG.h index 2c0333d2a..baf1f2632 100644 --- a/client/QCNG.h +++ b/client/QCNG.h @@ -34,9 +34,10 @@ class QCNG final: public QCryptoBackend ~QCNG() final; QList tokens() const final; - QByteArray decrypt(const QByteArray &data) const final; + QByteArray decrypt(const QByteArray &data, bool oaep) const final; QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, int keySize, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const final; + QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final; PinStatus lastError() const final; PinStatus login(const TokenData &token) final; void logout() final {} diff --git a/client/QCryptoBackend.h b/client/QCryptoBackend.h index c412030e4..9a7035366 100644 --- a/client/QCryptoBackend.h +++ b/client/QCryptoBackend.h @@ -42,9 +42,10 @@ class QCryptoBackend: public QObject using QObject::QObject; virtual QList tokens() const = 0; - virtual QByteArray decrypt(const QByteArray &data) const = 0; + virtual QByteArray decrypt(const QByteArray &data, bool oaep) const = 0; virtual QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, int keySize, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const = 0; + virtual QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const = 0; virtual PinStatus lastError() const { return PinOK; } virtual PinStatus login(const TokenData &cert) = 0; virtual void logout() = 0; diff --git a/client/QPKCS11.cpp b/client/QPKCS11.cpp index fae6b759f..8dda2bc89 100644 --- a/client/QPKCS11.cpp +++ b/client/QPKCS11.cpp @@ -20,7 +20,7 @@ #include "QPKCS11_p.h" #include "Application.h" -#include "CryptoDoc.h" +#include "Crypto.h" #include "SslCertificate.h" #include "TokenData.h" #include "dialogs/PinPopup.h" @@ -92,6 +92,29 @@ QPKCS11::~QPKCS11() delete d; } +QByteArray QPKCS11::decrypt(const QByteArray &data, bool oaep) const +{ + std::vector key = d->findObject(d->session, CKO_PRIVATE_KEY, d->id); + if(key.size() != 1) + return {}; + + CK_RSA_PKCS_OAEP_PARAMS params { CKM_SHA256, CKG_MGF1_SHA256, 0, nullptr, 0 }; + CK_MECHANISM mech = oaep ? + CK_MECHANISM{ CKM_RSA_PKCS_OAEP, ¶ms, sizeof(params) } : + CK_MECHANISM{ CKM_RSA_PKCS, nullptr, 0 }; + if(d->f->C_DecryptInit(d->session, &mech, key.front()) != CKR_OK) + return {}; + + CK_ULONG size = 0; + if(d->f->C_Decrypt(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), nullptr, &size) != CKR_OK) + return {}; + + QByteArray result(int(size), 0); + if(d->f->C_Decrypt(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), CK_BYTE_PTR(result.data()), &size) != CKR_OK) + return {}; + return result; +} + QByteArray QPKCS11::derive(const QByteArray &publicKey) const { std::vector key = d->findObject(d->session, CKO_PRIVATE_KEY, d->id); @@ -118,28 +141,12 @@ QByteArray QPKCS11::derive(const QByteArray &publicKey) const QByteArray QPKCS11::deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, int keySize, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const { - return CryptoDoc::concatKDF(digest, quint32(keySize), derive(publicKey), algorithmID + partyUInfo + partyVInfo); + return Crypto::concatKDF(digest, quint32(keySize), derive(publicKey), algorithmID + partyUInfo + partyVInfo); } -QByteArray QPKCS11::decrypt( const QByteArray &data ) const +QByteArray QPKCS11::deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const { - QByteArray result; - std::vector key = d->findObject(d->session, CKO_PRIVATE_KEY, d->id); - if(key.size() != 1) - return result; - - CK_MECHANISM mech { CKM_RSA_PKCS, nullptr, 0 }; - if(d->f->C_DecryptInit(d->session, &mech, key.front()) != CKR_OK) - return result; - - CK_ULONG size = 0; - if(d->f->C_Decrypt(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), nullptr, &size) != CKR_OK) - return result; - - result.resize(int(size)); - if(d->f->C_Decrypt(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), CK_BYTE_PTR(result.data()), &size) != CKR_OK) - result.clear(); - return result; + return Crypto::extract(derive(publicKey), salt, keySize); } bool QPKCS11::isLoaded() const @@ -387,19 +394,19 @@ QByteArray QPKCS11::sign(QCryptographicHash::Algorithm type, const QByteArray &d switch(type) { case QCryptographicHash::Sha224: - data += QByteArray::fromHex("302d300d06096086480165030402040500041c"); + data = QByteArray::fromHex("302d300d06096086480165030402040500041c"); pssParams = { CKM_SHA224, CKG_MGF1_SHA224, 24 }; break; case QCryptographicHash::Sha256: - data += QByteArray::fromHex("3031300d060960864801650304020105000420"); + data = QByteArray::fromHex("3031300d060960864801650304020105000420"); pssParams = { CKM_SHA256, CKG_MGF1_SHA256, 32 }; break; case QCryptographicHash::Sha384: - data += QByteArray::fromHex("3041300d060960864801650304020205000430"); + data = QByteArray::fromHex("3041300d060960864801650304020205000430"); pssParams = { CKM_SHA384, CKG_MGF1_SHA384, 48 }; break; case QCryptographicHash::Sha512: - data += QByteArray::fromHex("3051300d060960864801650304020305000440"); + data = QByteArray::fromHex("3051300d060960864801650304020305000440"); pssParams = { CKM_SHA512, CKG_MGF1_SHA512, 64 }; break; default: break; diff --git a/client/QPKCS11.h b/client/QPKCS11.h index 6963bcfbd..300e5299c 100644 --- a/client/QPKCS11.h +++ b/client/QPKCS11.h @@ -28,10 +28,11 @@ class QPKCS11 final: public QCryptoBackend explicit QPKCS11(QObject *parent = nullptr); ~QPKCS11() final; - QByteArray decrypt(const QByteArray &data) const final; + QByteArray decrypt(const QByteArray &data, bool oaep) const final; QByteArray derive(const QByteArray &publicKey) const; QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, int keySize, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const final; + QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final; bool isLoaded() const; bool load( const QString &driver ); void unload(); diff --git a/client/QSigner.cpp b/client/QSigner.cpp index d3b7e9e0e..ff2c8283d 100644 --- a/client/QSigner.cpp +++ b/client/QSigner.cpp @@ -39,6 +39,8 @@ #include #include +#include +#include #include @@ -54,7 +56,10 @@ class QSigner::Private final static ECDSA_SIG* ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey); + static int rsa_sign(int type, const unsigned char *m, unsigned int m_len, + unsigned char *sigret, unsigned int *siglen, const RSA *rsa); + RSA_METHOD *rsamethod = RSA_meth_dup(RSA_get_default_method()); EC_KEY_METHOD *ecmethod = EC_KEY_METHOD_new(EC_KEY_get_default_method()); }; @@ -74,6 +79,26 @@ ECDSA_SIG* QSigner::Private::ecdsa_do_sign(const unsigned char *dgst, int dgst_l return sig; } +int QSigner::Private::rsa_sign(int type, const unsigned char *m, unsigned int m_len, + unsigned char *sigret, unsigned int *siglen, const RSA *rsa) +{ + auto *backend = (QCryptoBackend*)RSA_get_ex_data(rsa, 0); + QCryptographicHash::Algorithm algo = QCryptographicHash::Sha256; + switch(type) + { + case NID_sha224: algo = QCryptographicHash::Sha224; break; + case NID_sha256: algo = QCryptographicHash::Sha256; break; + case NID_sha384: algo = QCryptographicHash::Sha384; break; + case NID_sha512: algo = QCryptographicHash::Sha512; break; + } + QByteArray result = backend->sign(algo, QByteArray::fromRawData((const char*)m, int(m_len))); + if(result.isEmpty()) + return 0; + *siglen = (unsigned int)result.size(); + memcpy(sigret, result.constData(), size_t(result.size())); + return 1; +} + using namespace digidoc; @@ -82,6 +107,8 @@ QSigner::QSigner(QObject *parent) : QThread(parent) , d(new Private) { + RSA_meth_set1_name(d->rsamethod, "QSmartCard"); + RSA_meth_set_sign(d->rsamethod, Private::rsa_sign); using EC_KEY_sign = int (*)(int type, const unsigned char *dgst, int dlen, unsigned char *sig, unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *eckey); using EC_KEY_sign_setup = int (*)(EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp); @@ -117,20 +144,13 @@ QSigner::~QSigner() requestInterruption(); wait(); delete d->smartcard; + RSA_meth_free(d->rsamethod); EC_KEY_METHOD_free(d->ecmethod); delete d; } QList QSigner::cache() const { return d->cache; } -QSet QSigner::cards() const -{ - QSet cards; - for(const TokenData &t: qAsConst(d->cache)) - cards.insert(t.card()); - return cards; -} - bool QSigner::cardsOrder(const TokenData &s1, const TokenData &s2) { static auto cardsOrderScore = [](QChar c) -> quint8 { @@ -229,7 +249,7 @@ QByteArray QSigner::decrypt(std::function &&func) QSslKey QSigner::key() const { QSslKey key = d->auth.cert().publicKey(); - if(!key.handle() || key.algorithm() != QSsl::Ec) + if(!key.handle()) return {}; if(!QCardLock::instance().exclusiveTryLock()) return {}; @@ -251,9 +271,18 @@ QSslKey QSigner::key() const } } while(status != QCryptoBackend::PinOK); - EC_KEY *ec = (EC_KEY*)key.handle(); - EC_KEY_set_method(ec, d->ecmethod); - EC_KEY_set_ex_data(ec, 0, d->backend); + if(key.algorithm() == QSsl::Ec) + { + auto *ec = (EC_KEY*)key.handle(); + EC_KEY_set_method(ec, d->ecmethod); + EC_KEY_set_ex_data(ec, 0, d->backend); + } + else + { + RSA *rsa = (RSA*)key.handle(); + RSA_set_method(rsa, d->rsamethod); + RSA_set_ex_data(rsa, 0, d->backend); + } return key; } diff --git a/client/QSigner.h b/client/QSigner.h index eb9205974..ea7b42bdc 100644 --- a/client/QSigner.h +++ b/client/QSigner.h @@ -39,7 +39,6 @@ class QSigner final: public QThread, public digidoc::Signer explicit QSigner(QObject *parent = nullptr); ~QSigner() final; - QSet cards() const; QList cache() const; digidoc::X509Cert cert() const final; QByteArray decrypt(std::function &&func); diff --git a/client/Settings.cpp b/client/Settings.cpp index 87802efed..f287a4a8c 100644 --- a/client/Settings.cpp +++ b/client/Settings.cpp @@ -26,13 +26,24 @@ template using Option = Settings::Option; +const Option Settings::CDOC2_DEFAULT { QStringLiteral("CDOC2-DEFAULT"), false }; +const Option Settings::CDOC2_NOTIFICATION { QStringLiteral("CDOC2-NOTIFICATION"), false }; +const Option Settings::CDOC2_USE_KEYSERVER { QStringLiteral("CDOC2-USE-KEYSERVER"), true }; +const Option Settings::CDOC2_DEFAULT_KEYSERVER { QStringLiteral("CDOC2-DEFAULT-KEYSERVER"), [] { + return Application::confValue(QLatin1String("CDOC2-DEFAULT-KEYSERVER")).toString(QStringLiteral("ria-test")); +}}; +const Option Settings::CDOC2_GET { QStringLiteral("CDOC2-GET"), QStringLiteral(CDOC2_GET_URL) }; +const Option Settings::CDOC2_GET_CERT { QStringLiteral("CDOC2-GET-CERT") }; +const Option Settings::CDOC2_POST { QStringLiteral("CDOC2-POST"), QStringLiteral(CDOC2_POST_URL) }; +const Option Settings::CDOC2_POST_CERT { QStringLiteral("CDOC2-POST-CERT") }; + const Option Settings::MID_UUID { QStringLiteral("MIDUUID") }; const Option Settings::MID_NAME { QStringLiteral("MIDNAME"), QStringLiteral("RIA DigiDoc") }; const Option Settings::MID_PROXY_URL { QStringLiteral("MID-PROXY-URL"), [] { - return qApp->confValue(QLatin1String("MID-PROXY-URL")).toString(QStringLiteral(MOBILEID_URL)); + return Application::confValue(QLatin1String("MID-PROXY-URL")).toString(QStringLiteral(MOBILEID_URL)); }}; const Option Settings::MID_SK_URL { QStringLiteral("MID-SK-URL"), [] { - return qApp->confValue(QLatin1String("MID-SK-URL")).toString(QStringLiteral(MOBILEID_URL)); + return Application::confValue(QLatin1String("MID-SK-URL")).toString(QStringLiteral(MOBILEID_URL)); }}; const Option Settings::MID_UUID_CUSTOM { QStringLiteral("MIDUUID-CUSTOM"), [] { return Settings::MID_UUID.isSet(); } }; @@ -44,10 +55,10 @@ const Option Settings::MOBILEID_ORDER { QStringLiteral("MIDOrder"), true } const Option Settings::SID_UUID { QStringLiteral("SIDUUID") }; const Option Settings::SID_NAME { QStringLiteral("SIDNAME"), QStringLiteral("RIA DigiDoc") }; const Option Settings::SID_PROXY_URL { QStringLiteral("SID-PROXY-URL"), []{ - return qApp->confValue(QLatin1String("SIDV2-PROXY-URL")).toString(qApp->confValue(QLatin1String("SID-PROXY-URL")).toString(QStringLiteral(SMARTID_URL))); + return Application::confValue(QLatin1String("SIDV2-PROXY-URL")).toString(Application::confValue(QLatin1String("SID-PROXY-URL")).toString(QStringLiteral(SMARTID_URL))); }}; const Option Settings::SID_SK_URL { QStringLiteral("SID-SK-URL"), []{ - return qApp->confValue(QLatin1String("SIDV2-SK-URL")).toString(qApp->confValue(QLatin1String("SID-SK-URL")).toString(QStringLiteral(SMARTID_URL))); + return Application::confValue(QLatin1String("SIDV2-SK-URL")).toString(Application::confValue(QLatin1String("SID-SK-URL")).toString(QStringLiteral(SMARTID_URL))); }}; const Option Settings::SID_UUID_CUSTOM { QStringLiteral("SIDUUID-CUSTOM"), [] { return Settings::SID_UUID.isSet(); } }; diff --git a/client/Settings.h b/client/Settings.h index 81b90285d..df869388d 100644 --- a/client/Settings.h +++ b/client/Settings.h @@ -75,6 +75,15 @@ struct Settings const D DEFAULT {}; }; + static const Option CDOC2_DEFAULT; + static const Option CDOC2_NOTIFICATION; + static const Option CDOC2_USE_KEYSERVER; + static const Option CDOC2_DEFAULT_KEYSERVER; + static const Option CDOC2_GET; + static const Option CDOC2_GET_CERT; + static const Option CDOC2_POST; + static const Option CDOC2_POST_CERT; + static const Option MID_UUID; static const Option MID_NAME; static const Option MID_PROXY_URL; diff --git a/client/SslCertificate.cpp b/client/SslCertificate.cpp index 1b6856791..f97d69918 100644 --- a/client/SslCertificate.cpp +++ b/client/SslCertificate.cpp @@ -20,6 +20,7 @@ #include "SslCertificate.h" #include "Common.h" +#include "Crypto.h" #include #include @@ -36,8 +37,7 @@ #include -#define SCOPE(TYPE, DATA) std::unique_ptr(static_cast(DATA), TYPE##_free) -#define toQByteArray(x) QByteArray((const char*)x->data, x->length) +#define toQByteArray(x) QByteArray((const char*)(x)->data, (x)->length) template static QByteArray i2dDer(Func func, Arg arg) { @@ -160,15 +160,7 @@ QString SslCertificate::keyName() const default: #ifndef OPENSSL_NO_ECDSA if(X509 *c = (X509*)handle()) - { - EVP_PKEY *key = X509_get0_pubkey(c); - const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(key); - int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); - ASN1_OBJECT *obj = OBJ_nid2obj(nid); - QByteArray buff(50, 0); - if(OBJ_obj2txt(buff.data(), buff.size(), obj, 0) > 0) - return buff; - } + return Crypto::curve_oid(X509_get0_pubkey(c)); #endif } return tr("Unknown"); @@ -278,7 +270,7 @@ QString SslCertificate::toString( const QString &format ) const QRegularExpressionMatch match; for(int pos = 0; (match = r.match(ret, pos)).hasMatch(); ) { QString cap = match.captured(); - QString si = cap == QStringLiteral("serialNumber") ? personalCode() : subjectInfo(cap.toLatin1()); + QString si = cap == QLatin1String("serialNumber") ? personalCode() : subjectInfo(cap.toLatin1()); ret.replace(match.capturedStart(), cap.size(), si); pos = match.capturedStart() + si.size(); } @@ -354,7 +346,7 @@ SslCertificate::Validity SslCertificate::validateOnline() const // Get issuer QNetworkRequest r(urls.values(SslCertificate::ad_CAIssuers).first()); r.setRawHeader("User-Agent", QStringLiteral("%1/%2 (%3)") - .arg(qApp->applicationName(), qApp->applicationVersion(), Common::applicationOs()).toUtf8()); + .arg(QApplication::applicationName(), QApplication::applicationVersion(), Common::applicationOs()).toUtf8()); QNetworkReply *repl = m.get(r); e.exec(); QSslCertificate issuer(repl->readAll(), QSsl::Der); diff --git a/client/Utils.h b/client/Utils.h index 5799a871c..37fb82ee6 100644 --- a/client/Utils.h +++ b/client/Utils.h @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include #include @@ -49,7 +50,7 @@ namespace { inline auto dispatchToMain(F&& function, Args&& ...args) { std::invoke_result_t result{}; QEventLoop l; - QTimer* timer = new QTimer(); + QTimer *timer = new QTimer(); timer->moveToThread(qApp->thread()); timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, timer, [&, function = std::forward(function)] { @@ -57,7 +58,7 @@ namespace { l.exit(); timer->deleteLater(); }); - QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); + QMetaObject::invokeMethod(timer, [timer] { timer->start(0); }, Qt::QueuedConnection); l.exec(); return result; } @@ -76,4 +77,16 @@ namespace { } return escaped; } + + inline qint64 copyIODevice(QIODevice *from, QIODevice *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->write(buf.data(), i) != i) + return -1; + } + return i < 0 ? i : size; + } } diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index a053301e4..cc9bb1a91 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -22,28 +22,29 @@ #include "ui_AddRecipients.h" #include "Application.h" +#include "CheckConnection.h" #include "common_enums.h" #include "FileDialog.h" #include "IKValidator.h" #include "LdapSearch.h" #include "QSigner.h" +#include "Settings.h" #include "Styles.h" #include "TokenData.h" #include "dialogs/WarningDialog.h" #include "effects/Overlay.h" -#include - -#include -#include -#include -#include +#include +#include +#include #include +#include #include #include -#include -#include +#include #include +#include +#include AddRecipients::AddRecipients(ItemList* itemList, QWidget *parent) : QDialog(parent) @@ -131,7 +132,7 @@ void AddRecipients::addAllRecipientToRightPane() QList history; for(AddressItem *value: leftList) { - if(!rightList.contains(value->getKey().cert)) + if(!rightList.contains(value->getKey())) { addRecipientToRightPane(value); history.append(toHistory(value->getKey().cert)); @@ -216,7 +217,7 @@ AddressItem * AddRecipients::addRecipientToLeftPane(const QSslCertificate& cert) bool AddRecipients::addRecipientToRightPane(const CKey &key, bool update) { - if (rightList.contains(key.cert)) + if (rightList.contains(key)) return false; if(update) @@ -224,28 +225,36 @@ bool AddRecipients::addRecipientToRightPane(const CKey &key, bool update) auto expiryDate = key.cert.expiryDate(); if(expiryDate <= QDateTime::currentDateTime()) { + if(Settings::CDOC2_DEFAULT && Settings::CDOC2_USE_KEYSERVER) + { + WarningDialog::show(this, tr("Failed to add certificate. An expired certificate cannot be used for encryption.")); + return false; + } auto *dlg = new WarningDialog(tr("Are you sure that you want use certificate for encrypting, which expired on %1?
" "When decrypter has updated certificates then decrypting is impossible.") .arg(expiryDate.toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"))), this); - dlg->setCancelText(tr("NO")); - dlg->addButton(tr("YES"), QMessageBox::Yes); + dlg->setCancelText(WarningDialog::NO); + dlg->addButton(WarningDialog::YES, QMessageBox::Yes); if(dlg->exec() != QMessageBox::Yes) return false; } + QSslConfiguration backup = QSslConfiguration::defaultConfiguration(); + QSslConfiguration::setDefaultConfiguration(CheckConnection::sslConfiguration()); QList errors = QSslCertificate::verify({ key.cert }); + QSslConfiguration::setDefaultConfiguration(backup); errors.removeAll(QSslError(QSslError::CertificateExpired, key.cert)); if(!errors.isEmpty()) { auto *dlg = new WarningDialog(tr("Recipient’s certification chain contains certificates that are not trusted. Continue with encryption?"), this); - dlg->setCancelText(tr("NO")); - dlg->addButton(tr("YES"), QMessageBox::Yes); + dlg->setCancelText(WarningDialog::NO); + dlg->addButton(WarningDialog::YES, QMessageBox::Yes); if(dlg->exec() != QMessageBox::Yes) return false; } } updated = update; - rightList.append(key.cert); + rightList.append(key); auto *rightItem = new AddressItem(key, ui->rightPane); connect(rightItem, &AddressItem::remove, this, &AddRecipients::removeRecipientFromRightPane); @@ -278,7 +287,7 @@ void AddRecipients::addSelectedCerts(const QList& selectedCertD QString AddRecipients::defaultUrl(QLatin1String key, const QString &defaultValue) { - return qApp->confValue(key).toString(defaultValue); + return Application::confValue(key).toString(defaultValue); } void AddRecipients::enableRecipientFromCard() @@ -305,7 +314,7 @@ QList AddRecipients::keys() QString AddRecipients::path() { #ifdef Q_OS_WIN - QSettings s( QSettings::IniFormat, QSettings::UserScope, qApp->organizationName(), "qdigidoccrypto" ); + QSettings s( QSettings::IniFormat, QSettings::UserScope, Application::organizationName(), "qdigidoccrypto" ); QFileInfo f( s.fileName() ); return f.absolutePath() + "/" + f.baseName() + "/certhistory.xml"; #else @@ -335,7 +344,7 @@ void AddRecipients::removeRecipientFromRightPane(Item *toRemove) it.value()->setDisabled(false); it.value()->showButton(AddressItem::Add); } - rightList.removeAll(rightItem->getKey().cert); + rightList.removeAll(rightItem->getKey()); updated = true; ui->confirm->setDisabled(rightList.isEmpty()); } diff --git a/client/dialogs/AddRecipients.h b/client/dialogs/AddRecipients.h index 99edabece..fbe907bf3 100644 --- a/client/dialogs/AddRecipients.h +++ b/client/dialogs/AddRecipients.h @@ -73,7 +73,7 @@ class AddRecipients final : public QDialog Ui::AddRecipients *ui; QHash leftList; - QList rightList; + QList rightList; LdapSearch *ldap_person, *ldap_corp; bool updated = false; diff --git a/client/dialogs/FileDialog.cpp b/client/dialogs/FileDialog.cpp index 92176f569..ff52cb828 100644 --- a/client/dialogs/FileDialog.cpp +++ b/client/dialogs/FileDialog.cpp @@ -52,7 +52,8 @@ class CPtr QString FileDialog::createNewFileName(const QString &file, bool signature, QWidget *parent) { - const QString extension = signature ? QStringLiteral(".asice") : QStringLiteral(".cdoc"); + const QString extension = signature ? QStringLiteral(".asice") : + Settings::CDOC2_DEFAULT ? QStringLiteral(".cdoc2") : QStringLiteral(".cdoc"); const QString type = signature ? tr("signature container") : tr("crypto container"); QString capitalized = type[0].toUpper() + type.mid(1); const QString defaultDir = Settings::DEFAULT_DIR; @@ -77,10 +78,18 @@ FileDialog::FileType FileDialog::detect(const QString &filename) const QFileInfo f(filename); if(!f.isFile()) return Other; - static const QStringList exts {"bdoc", "ddoc", "asice", "sce", "asics", "scs", "edoc", "adoc"}; + static const QStringList exts { + QStringLiteral("bdoc"), QStringLiteral("ddoc"), + QStringLiteral("asice"), QStringLiteral("sce"), + QStringLiteral("asics"), QStringLiteral("scs"), + QStringLiteral("edoc"), QStringLiteral("adoc"), + }; if(exts.contains(f.suffix(), Qt::CaseInsensitive)) return SignatureDocument; - if(!f.suffix().compare(QStringLiteral("cdoc"), Qt::CaseInsensitive)) + static const QStringList cexts { + QStringLiteral("cdoc"), QStringLiteral("cdoc2"), + }; + if(cexts.contains(f.suffix(), Qt::CaseInsensitive)) return CryptoDocument; if(isSignedPDF(filename)) return SignatureDocument; @@ -97,23 +106,6 @@ bool FileDialog::fileIsWritable( const QString &filename ) return result; } -QString FileDialog::fileSize( quint64 bytes ) -{ - const quint64 kb = 1024; - const quint64 mb = 1024 * kb; - const quint64 gb = 1024 * mb; - const quint64 tb = 1024 * gb; - if( bytes >= tb ) - return QStringLiteral("%1 TB").arg(qreal(bytes) / tb, 0, 'f', 3); - if( bytes >= gb ) - return QStringLiteral("%1 GB").arg(qreal(bytes) / gb, 0, 'f', 2); - if( bytes >= mb ) - return QStringLiteral("%1 MB").arg(qreal(bytes) / mb, 0, 'f', 1); - if( bytes >= kb ) - return QStringLiteral("%1 KB").arg(bytes / kb); - return QStringLiteral("%1 B").arg(bytes); -} - int FileDialog::fileZone(const QString &path) { #ifdef Q_OS_WIN @@ -164,6 +156,16 @@ void FileDialog::setFileZone(const QString &path, int zone) #endif } +void FileDialog::setReadOnly(const QString &path, bool readonly) +{ +#if defined(Q_OS_WIN) + ::SetFileAttributesW(LPCWSTR(path.utf16()), readonly ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL); +#else + QFile::setPermissions(path, QFile::Permissions(QFile::Permission::ReadOwner) + .setFlag(QFile::Permission::WriteOwner, !readonly)); +#endif +} + QString FileDialog::getDir( const QString &dir ) { #ifdef Q_OS_OSX diff --git a/client/dialogs/FileDialog.h b/client/dialogs/FileDialog.h index f6da2a86b..c08dfb793 100644 --- a/client/dialogs/FileDialog.h +++ b/client/dialogs/FileDialog.h @@ -36,10 +36,10 @@ class FileDialog : public QFileDialog static QString createNewFileName(const QString &file, bool signature, QWidget *parent); static FileType detect(const QString &filename); static bool fileIsWritable( const QString &filename ); - static QString fileSize( quint64 bytes ); static int fileZone(const QString &path); static bool isSignedPDF(const QString &path); static void setFileZone(const QString &path, int zone); + static void setReadOnly(const QString &path, bool readonly = true); static QString normalized(const QString &file); static QString safeName(const QString &file); static QString tempPath(const QString &file); diff --git a/client/dialogs/KeyDialog.cpp b/client/dialogs/KeyDialog.cpp index d011b81c8..41f93d0ff 100644 --- a/client/dialogs/KeyDialog.cpp +++ b/client/dialogs/KeyDialog.cpp @@ -52,6 +52,7 @@ KeyDialog::KeyDialog( const CKey &k, QWidget *parent ) connect(d->showCert, &QPushButton::clicked, this, [this, cert=k.cert] { CertificateDetails::showCertificate(cert, this); }); + d->showCert->setHidden(k.cert.isNull()); auto addItem = [&](const QString ¶meter, const QString &value) { if(value.isEmpty()) @@ -67,6 +68,8 @@ KeyDialog::KeyDialog( const CKey &k, QWidget *parent ) addItem(tr("Agreement method"), k.agreement); addItem(tr("Key derivation method"), k.derive); 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"), SslCertificate(k.cert).issuerInfo(QSslCertificate::CommonName)); d->view->resizeColumnToContents( 0 ); diff --git a/client/dialogs/MobileProgress.cpp b/client/dialogs/MobileProgress.cpp index 7fd2a915c..7e6ccc4be 100644 --- a/client/dialogs/MobileProgress.cpp +++ b/client/dialogs/MobileProgress.cpp @@ -21,13 +21,12 @@ #include "ui_MobileProgress.h" #include "Application.h" +#include "CheckConnection.h" #include "Settings.h" #include "Styles.h" #include "Utils.h" #include "dialogs/WarningDialog.h" -#include - #include #include @@ -52,7 +51,7 @@ class MobileProgress::Private final: public QDialog, public Ui::MobileProgress using QDialog::QDialog; void reject() final { l.exit(QDialog::Rejected); } QTimeLine *statusTimer{}; - QNetworkAccessManager *manager = new QNetworkAccessManager(this); + QNetworkAccessManager *manager {}; QNetworkRequest req; QString ssid, cell, sessionID; std::vector signature; @@ -102,38 +101,9 @@ background-color: #007aff; QObject::connect(d->statusTimer, &QTimeLine::frameChanged, d->signProgressBar, &QProgressBar::setValue); QObject::connect(d->statusTimer, &QTimeLine::finished, d, &QDialog::reject); -#ifdef CONFIG_URL - QList trusted; - for(const auto &cert: Application::confValue(QLatin1String("CERT-BUNDLE")).toArray()) - trusted.append(QSslCertificate(QByteArray::fromBase64(cert.toString().toLatin1()), QSsl::Der)); - QSslConfiguration ssl = QSslConfiguration::defaultConfiguration(); - ssl.setCaCertificates(trusted); - d->req.setSslConfiguration(ssl); -#endif d->req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - d->req.setRawHeader("User-Agent", QStringLiteral("%1/%2 (%3)") - .arg(QApplication::applicationName(), QApplication::applicationVersion(), Common::applicationOs()).toUtf8()); - QObject::connect(d->manager, &QNetworkAccessManager::sslErrors, d->manager, [](QNetworkReply *reply, const QList &err) { - QList ignore; - for(const QSslError &e: err) - { - switch(e.error()) - { - case QSslError::UnableToGetLocalIssuerCertificate: - case QSslError::CertificateUntrusted: - case QSslError::SelfSignedCertificateInChain: - if(reply->sslConfiguration().caCertificates().contains(reply->sslConfiguration().peerCertificate())) { - ignore << e; - break; - } - Q_FALLTHROUGH(); - default: - qCWarning(MIDLog) << "SSL Error:" << e.error() << e.certificate().subjectInfo(QSslCertificate::CommonName); - break; - } - } - reply->ignoreSslErrors(ignore); - }); + d->manager = CheckConnection::setupNAM(d->req); + d->manager->setParent(d); QObject::connect(d->manager, &QNetworkAccessManager::finished, d, [=](QNetworkReply *reply) { QScopedPointer scope(reply); auto returnError = [=](const QString &err, const QString &details = {}) { diff --git a/client/dialogs/SettingsDialog.cpp b/client/dialogs/SettingsDialog.cpp index da8149b4b..78f60596b 100644 --- a/client/dialogs/SettingsDialog.cpp +++ b/client/dialogs/SettingsDialog.cpp @@ -105,6 +105,18 @@ SettingsDialog::SettingsDialog(int page, QWidget *parent) ui->chkRoleAddressInfo->setFont(regularFont); ui->chkLibdigidocppDebug->setFont(regularFont); + ui->lblGeneralCDoc2->setFont(headerFont); + + ui->chkCdoc2KeyServer->setFont(regularFont); + ui->lblCdoc2Name->setFont(regularFont); + ui->lblCdoc2UUID->setFont(regularFont); + ui->lblCdoc2Fetch->setFont(regularFont); + ui->lblCdoc2Post->setFont(regularFont); + ui->cmbCdoc2Name->setFont(regularFont); + ui->txtCdoc2UUID->setFont(regularFont); + ui->txtCdoc2Fetch->setFont(regularFont); + ui->txtCdoc2Post->setFont(regularFont); + // pageServices ui->lblTimeStamp->setFont(headerFont); ui->rdTimeStampDefault->setFont(regularFont); @@ -286,7 +298,7 @@ void SettingsDialog::checkConnection() { QApplication::setOverrideCursor( Qt::WaitCursor ); saveProxy(); - if(CheckConnection connection; !connection.check(QStringLiteral("https://id.eesti.ee/config.json"))) + if(CheckConnection connection; !connection.check()) { Application::restoreOverrideCursor(); auto *notification = new FadeInNotification(this, @@ -363,6 +375,41 @@ void SettingsDialog::initFunctionality() connect(ui->txtGeneralDirectory, &QLineEdit::textChanged, this, [](const QString &text) { Settings::DEFAULT_DIR = text; }); +#endif + ui->wgtCDoc2->hide(); +#if 0 + ui->chkCdoc2KeyServer->setChecked(Settings::CDOC2_USE_KEYSERVER); + ui->cmbCdoc2Name->setEnabled(ui->chkCdoc2KeyServer->isChecked()); + connect(ui->chkCdoc2KeyServer, &QCheckBox::toggled, this, [this](bool checked) { + Settings::CDOC2_USE_KEYSERVER = checked; + ui->cmbCdoc2Name->setEnabled(checked); + }); +#ifdef CONFIG_URL + QJsonObject list = Application::confValue(QLatin1String("CDOC2-CONF")).toObject(); + auto setCDoc2Values = [this, list](const QString &key) { + ui->txtCdoc2UUID->setText(key); + QJsonObject data = list.value(key).toObject(); + ui->txtCdoc2Fetch->setText(data.value(QLatin1String("FETCH")).toString(Settings::CDOC2_GET)); + ui->txtCdoc2Post->setText(data.value(QLatin1String("POST")).toString(Settings::CDOC2_POST)); + }; + for(QJsonObject::const_iterator i = list.constBegin(); i != list.constEnd(); ++i) + ui->cmbCdoc2Name->addItem(i.value().toObject().value(QLatin1String("NAME")).toString(), i.key()); + if(Settings::CDOC2_GET.isSet() || Settings::CDOC2_POST.isSet()) + ui->cmbCdoc2Name->addItem(QStringLiteral("Custom"), QStringLiteral("custom")); + connect(ui->cmbCdoc2Name, qOverload(&QComboBox::currentIndexChanged), this, [this, setCDoc2Values] (int index) { + QString key = ui->cmbCdoc2Name->itemData(index).toString(); + Settings::CDOC2_DEFAULT_KEYSERVER = key; + setCDoc2Values(key); + }); + QString cdoc2Service = Settings::CDOC2_DEFAULT_KEYSERVER; + ui->cmbCdoc2Name->setCurrentIndex(ui->cmbCdoc2Name->findData(cdoc2Service)); + setCDoc2Values(cdoc2Service); +#else + ui->cmbCdoc2Name->addItem(QStringLiteral("Default")); + ui->txtCdoc2UUID->setText(QStringLiteral("default")); + ui->txtCdoc2Fetch->setText(QStringLiteral(CDOC2_GET_URL)); + ui->txtCdoc2Post->setText(QStringLiteral(CDOC2_POST_URL)); +#endif #endif // pageProxy @@ -480,7 +527,7 @@ void SettingsDialog::initFunctionality() auto *dlg = WarningDialog::show(this, tr("Restart DigiDoc4 Client to activate logging. Read more " "here. Restart now?")); dlg->setCancelText(tr("NO")); - dlg->addButton(tr("YES"), 1); + dlg->addButton(WarningDialog::YES, 1); connect(dlg, &WarningDialog::finished, qApp, [](int result) { if(result == 1) { qApp->setProperty("restart", true); diff --git a/client/dialogs/SettingsDialog.ui b/client/dialogs/SettingsDialog.ui index 0735a4030..042b20326 100644 --- a/client/dialogs/SettingsDialog.ui +++ b/client/dialogs/SettingsDialog.ui @@ -313,7 +313,7 @@ QRadioButton::indicator::checked { - + @@ -356,19 +356,6 @@ QRadioButton::indicator::checked { - - - - Qt::Horizontal - - - - 495 - 20 - - - - @@ -453,6 +440,99 @@ QRadioButton::indicator::checked { + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 5 + + + + + Qt::TabFocus + + + color: #041E42; + + + CDoc 2.0 + + + + + + + Use key server + + + + + + + Name + + + + + + + + + + UUID + + + + + + + true + + + + + + + Fetch URL + + + + + + + true + + + + + + + Post URL + + + + + + + true + + + + + + @@ -480,7 +560,7 @@ QRadioButton::indicator::checked { 0 0 - 789 + 378 527 @@ -1542,5 +1622,6 @@ QPushButton:disabled { + diff --git a/client/dialogs/SmartIDProgress.cpp b/client/dialogs/SmartIDProgress.cpp index 42bb0f18e..3dc3e897a 100644 --- a/client/dialogs/SmartIDProgress.cpp +++ b/client/dialogs/SmartIDProgress.cpp @@ -21,13 +21,12 @@ #include "ui_MobileProgress.h" #include "Application.h" +#include "CheckConnection.h" #include "Settings.h" #include "Styles.h" #include "Utils.h" #include "dialogs/WarningDialog.h" -#include - #include #include @@ -61,7 +60,7 @@ class SmartIDProgress::Private final: public QDialog, public Ui::MobileProgress } QTimer *timer{}; QTimeLine *statusTimer{}; - QNetworkAccessManager *manager = new QNetworkAccessManager(this); + QNetworkAccessManager *manager {}; QNetworkRequest req; QString documentNumber, sessionID, fileName; X509Cert cert; @@ -114,38 +113,9 @@ background-color: #007aff; d->statusTimer->setFrameRange(d->signProgressBar->minimum(), d->signProgressBar->maximum()); QObject::connect(d->statusTimer, &QTimeLine::frameChanged, d->signProgressBar, &QProgressBar::setValue); -#ifdef CONFIG_URL - QList trusted; - for(const auto &cert: Application::confValue(QLatin1String("CERT-BUNDLE")).toArray()) - trusted.append(QSslCertificate(QByteArray::fromBase64(cert.toString().toLatin1()), QSsl::Der)); - QSslConfiguration ssl = QSslConfiguration::defaultConfiguration(); - ssl.setCaCertificates(trusted); - d->req.setSslConfiguration(ssl); -#endif d->req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - d->req.setRawHeader("User-Agent", QStringLiteral("%1/%2 (%3)") - .arg(QApplication::applicationName(), QApplication::applicationVersion(), Common::applicationOs()).toUtf8()); - QObject::connect(d->manager, &QNetworkAccessManager::sslErrors, d->manager, [](QNetworkReply *reply, const QList &err) { - QList ignore; - for(const QSslError &e: err) - { - switch(e.error()) - { - case QSslError::UnableToGetLocalIssuerCertificate: - case QSslError::CertificateUntrusted: - case QSslError::SelfSignedCertificateInChain: - if(reply->sslConfiguration().caCertificates().contains(reply->sslConfiguration().peerCertificate())) { - ignore << e; - break; - } - Q_FALLTHROUGH(); - default: - qCWarning(SIDLog) << "SSL Error:" << e.error() << e.certificate().subjectInfo(QSslCertificate::CommonName); - break; - } - } - reply->ignoreSslErrors(ignore); - }); + d->manager = CheckConnection::setupNAM(d->req); + d->manager->setParent(d); QNetworkAccessManager::connect(d->manager, &QNetworkAccessManager::finished, d, [&](QNetworkReply *reply){ QScopedPointer scope(reply); auto returnError = [=](const QString &err, const QString &details = {}) { diff --git a/client/dialogs/WarningDialog.cpp b/client/dialogs/WarningDialog.cpp index ca27bee20..f8bad60bb 100644 --- a/client/dialogs/WarningDialog.cpp +++ b/client/dialogs/WarningDialog.cpp @@ -65,6 +65,11 @@ WarningDialog::~WarningDialog() delete ui; } +void WarningDialog::addButton(ButtonText label, int ret, bool red) +{ + addButton(buttonLabel(label), ret, red); +} + void WarningDialog::addButton(const QString& label, int ret, bool red) { auto *button = new QPushButton(label, this); @@ -90,6 +95,16 @@ void WarningDialog::addButton(const QString& label, int ret, bool red) ui->buttonBarLayout->insertWidget(ui->buttonBarLayout->findChildren().size() + 1, button); } +QString WarningDialog::buttonLabel(ButtonText label) +{ + switch (label) { + case NO: return tr("NO"); + case OK: return QStringLiteral("OK"); + case Cancel: return tr("CANCEL"); + case YES: return tr("YES"); + } +} + void WarningDialog::resetCancelStyle() { style()->unpolish(ui->cancel); @@ -104,16 +119,15 @@ void WarningDialog::setButtonSize(int width, int margin) ui->cancel->setMaximumSize(width, ui->cancel->minimumHeight()); } -void WarningDialog::setCancelText(const QString& label) +void WarningDialog::setCancelText(ButtonText label) { - ui->cancel->setText(label); - ui->cancel->setAccessibleName(label.toLower()); + setCancelText(buttonLabel(label)); } -void WarningDialog::setText(const QString& text) +void WarningDialog::setCancelText(const QString& label) { - ui->text->setText(text); - adjustSize(); + ui->cancel->setText(label); + ui->cancel->setAccessibleName(label.toLower()); } WarningDialog* WarningDialog::show(const QString &text, const QString &details) diff --git a/client/dialogs/WarningDialog.h b/client/dialogs/WarningDialog.h index bc0526621..733b78484 100644 --- a/client/dialogs/WarningDialog.h +++ b/client/dialogs/WarningDialog.h @@ -32,19 +32,28 @@ class WarningDialog final: public QDialog Q_OBJECT public: + enum ButtonText { + Cancel, + OK, + NO, + YES, + }; explicit WarningDialog(const QString &text, const QString &details, QWidget *parent = nullptr); explicit WarningDialog(const QString &text, QWidget *parent = nullptr); ~WarningDialog() final; + void addButton(ButtonText label, int ret, bool red = false); void addButton(const QString& label, int ret, bool red = false); void setButtonSize(int width, int margin); + void setCancelText(ButtonText label); void setCancelText(const QString& label); void resetCancelStyle(); - void setText(const QString& text); static WarningDialog *show(const QString &text, const QString &details = {}); static WarningDialog *show(QWidget *parent, const QString &text, const QString &details = {}); private: + static QString buttonLabel(ButtonText label); + Ui::WarningDialog *ui; WaitDialogHider hider; }; diff --git a/client/mac/Info.plist.cmake b/client/mac/Info.plist.cmake index 1336c56c0..dc9c0267c 100644 --- a/client/mac/Info.plist.cmake +++ b/client/mac/Info.plist.cmake @@ -107,12 +107,14 @@ CFBundleTypeExtensions cdoc + cdoc2 CFBundleTypeIconFile cdoc.icns CFBundleTypeMIMETypes application/x-cdoc + application/x-cdoc2 CFBundleTypeName DigiDoc encrypted container @@ -220,8 +222,6 @@ ee.ria.bdoc UTTypeTagSpecification - com.apple.ostype - BDOC public.filename-extension asice @@ -251,8 +251,6 @@ ee.ria.ddoc UTTypeTagSpecification - com.apple.ostype - DDOC public.filename-extension ddoc public.mime-type @@ -273,12 +271,16 @@ ee.ria.cdoc UTTypeTagSpecification - com.apple.ostype - CDOC public.filename-extension - cdoc + + cdoc + cdoc2 + public.mime-type - application/x-cdoc + + application/x-cdoc + application/x-cdoc2 + @@ -295,8 +297,6 @@ ee.ria.asics UTTypeTagSpecification - com.apple.ostype - ASICS public.filename-extension asics diff --git a/client/pkcs11.h b/client/pkcs11.h index d24597b26..1ea46d3c7 100644 --- a/client/pkcs11.h +++ b/client/pkcs11.h @@ -63,9 +63,9 @@ extern "C" { version of this file, please consider deleting the revision macro (you may use a macro with a different name to keep track of your versions). */ -#define CRYPTOKI_VERSION_MAJOR 2 -#define CRYPTOKI_VERSION_MINOR 20 -#define CRYPTOKI_VERSION_REVISION 6 +#define CRYPTOKI_VERSION_MAJOR 3 +#define CRYPTOKI_VERSION_MINOR 0 +#define CRYPTOKI_VERSION_REVISION 0 /* Compatibility interface is default, unless CRYPTOKI_GNU is @@ -166,7 +166,10 @@ extern "C" { #define ck_rv_t CK_RV #define ck_notify_t CK_NOTIFY +#define ck_interface CK_INTERFACE + #define ck_function_list _CK_FUNCTION_LIST +#define ck_function_list_3_0 _CK_FUNCTION_LIST_3_0 #define ck_createmutex_t CK_CREATEMUTEX #define ck_destroymutex_t CK_DESTROYMUTEX @@ -318,8 +321,16 @@ typedef unsigned long ck_object_class_t; #define CKO_HW_FEATURE (5UL) #define CKO_DOMAIN_PARAMETERS (6UL) #define CKO_MECHANISM (7UL) +#define CKO_OTP_KEY (8UL) +#define CKO_PROFILE (9UL) #define CKO_VENDOR_DEFINED (1UL << 31) +#define CKP_INVALID_ID (0UL) +#define CKP_BASELINE_PROVIDER (1UL) +#define CKP_EXTENDED_PROVIDER (2UL) +#define CKP_AUTHENTICATION_TOKEN (3UL) +#define CKP_PUBLIC_CERTIFICATES_TOKEN (4UL) +#define CKP_VENDOR_DEFINED (1UL << 31) typedef unsigned long ck_hw_feature_type_t; @@ -359,8 +370,18 @@ typedef unsigned long ck_key_type_t; #define CKK_GOSTR3410 (0x30UL) #define CKK_GOSTR3411 (0x31UL) #define CKK_GOST28147 (0x32UL) +#define CKK_EC_EDWARDS (0x40UL) +#define CKK_EC_MONTGOMERY (0x41UL) #define CKK_VENDOR_DEFINED (1UL << 31) +/* + * A mask for new GOST algorithms. + * For details visit https://tc26.ru/standarts/perevody/guidelines-the-pkcs-11-extensions-for-implementing-the-gost-r-34-10-2012-and-gost-r-34-11-2012-russian-standards-.html + */ +#define NSSCK_VENDOR_PKCS11_RU_TEAM (CKK_VENDOR_DEFINED | 0x54321000) +#define CK_VENDOR_PKCS11_RU_TEAM_TK26 NSSCK_VENDOR_PKCS11_RU_TEAM + +#define CKK_GOSTR3410_512 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x003) typedef unsigned long ck_certificate_type_t; @@ -458,7 +479,22 @@ typedef unsigned long ck_attribute_type_t; #define CKA_SUPPORTED_CMS_ATTRIBUTES (0x503UL) #define CKA_WRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x211UL) #define CKA_UNWRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x212UL) +#define CKA_OTP_FORMAT (0x220UL) +#define CKA_OTP_LENGTH (0x221UL) +#define CKA_OTP_TIME_INTERVAL (0x222UL) +#define CKA_OTP_USER_FRIENDLY_MODE (0x223UL) +#define CKA_OTP_CHALLENGE_REQUIREMENT (0x224UL) +#define CKA_OTP_TIME_REQUIREMENT (0x225UL) +#define CKA_OTP_COUNTER_REQUIREMENT (0x226UL) +#define CKA_OTP_PIN_REQUIREMENT (0x227UL) +#define CKA_OTP_USER_IDENTIFIER (0x22AUL) +#define CKA_OTP_SERVICE_IDENTIFIER (0x22BUL) +#define CKA_OTP_SERVICE_LOGO (0x22CUL) +#define CKA_OTP_SERVICE_LOGO_TYPE (0x22DUL) +#define CKA_OTP_COUNTER (0x22EUL) +#define CKA_OTP_TIME (0x22FUL) #define CKA_ALLOWED_MECHANISMS (CKF_ARRAY_ATTRIBUTE | 0x600UL) +#define CKA_PROFILE_ID (0x601UL) #define CKA_VENDOR_DEFINED (1UL << 31) @@ -480,8 +516,6 @@ struct ck_date typedef unsigned long ck_mechanism_type_t; -typedef unsigned long int ck_rsa_pkcs_mgf_type_t; - #define CKM_RSA_PKCS_KEY_PAIR_GEN (0UL) #define CKM_RSA_PKCS (1UL) #define CKM_RSA_9796 (2UL) @@ -500,6 +534,10 @@ typedef unsigned long int ck_rsa_pkcs_mgf_type_t; #define CKM_DSA_KEY_PAIR_GEN (0x10UL) #define CKM_DSA (0x11UL) #define CKM_DSA_SHA1 (0x12UL) +#define CKM_DSA_SHA224 (0x13UL) +#define CKM_DSA_SHA256 (0x14UL) +#define CKM_DSA_SHA384 (0x15UL) +#define CKM_DSA_SHA512 (0x16UL) #define CKM_DH_PKCS_KEY_PAIR_GEN (0x20UL) #define CKM_DH_PKCS_DERIVE (0x21UL) #define CKM_X9_42_DH_KEY_PAIR_GEN (0x30UL) @@ -514,6 +552,14 @@ typedef unsigned long int ck_rsa_pkcs_mgf_type_t; #define CKM_SHA512_RSA_PKCS_PSS (0x45UL) #define CKM_SHA224_RSA_PKCS (0x46UL) #define CKM_SHA224_RSA_PKCS_PSS (0x47UL) +#define CKM_SHA3_256_RSA_PKCS (0x60UL) +#define CKM_SHA3_384_RSA_PKCS (0x61UL) +#define CKM_SHA3_512_RSA_PKCS (0x62UL) +#define CKM_SHA3_256_RSA_PKCS_PSS (0x63UL) +#define CKM_SHA3_384_RSA_PKCS_PSS (0x64UL) +#define CKM_SHA3_512_RSA_PKCS_PSS (0x65UL) +#define CKM_SHA3_224_RSA_PKCS (0x66UL) +#define CKM_SHA3_224_RSA_PKCS_PSS (0x67UL) #define CKM_RC2_KEY_GEN (0x100UL) #define CKM_RC2_ECB (0x101UL) #define CKM_RC2_CBC (0x102UL) @@ -535,6 +581,8 @@ typedef unsigned long int ck_rsa_pkcs_mgf_type_t; #define CKM_DES3_MAC (0x134UL) #define CKM_DES3_MAC_GENERAL (0x135UL) #define CKM_DES3_CBC_PAD (0x136UL) +#define CKM_DES3_CMAC_GENERAL (0x137UL) +#define CKM_DES3_CMAC (0x138UL) #define CKM_CDMF_KEY_GEN (0x140UL) #define CKM_CDMF_ECB (0x141UL) #define CKM_CDMF_CBC (0x142UL) @@ -568,6 +616,22 @@ typedef unsigned long int ck_rsa_pkcs_mgf_type_t; #define CKM_SHA512 (0x270UL) #define CKM_SHA512_HMAC (0x271UL) #define CKM_SHA512_HMAC_GENERAL (0x272UL) +#define CKM_SHA3_256 (0x2B0UL) +#define CKM_SHA3_256_HMAC (0x2B1UL) +#define CKM_SHA3_256_HMAC_GENERAL (0x2B2UL) +#define CKM_SHA3_256_KEY_GEN (0x2B3UL) +#define CKM_SHA3_224 (0x2B5UL) +#define CKM_SHA3_224_HMAC (0x2B6UL) +#define CKM_SHA3_224_HMAC_GENERAL (0x2B7UL) +#define CKM_SHA3_224_KEY_GEN (0x2B8UL) +#define CKM_SHA3_384 (0x2C0UL) +#define CKM_SHA3_384_HMAC (0x2C1UL) +#define CKM_SHA3_384_HMAC_GENERAL (0x2C2UL) +#define CKM_SHA3_384_KEY_GEN (0x2C3UL) +#define CKM_SHA3_512 (0x2D0UL) +#define CKM_SHA3_512_HMAC (0x2D1UL) +#define CKM_SHA3_512_HMAC_GENERAL (0x2D2UL) +#define CKM_SHA3_512_KEY_GEN (0x2D3UL) #define CKM_CAST_KEY_GEN (0x300UL) #define CKM_CAST_ECB (0x301UL) #define CKM_CAST_CBC (0x302UL) @@ -670,9 +734,16 @@ typedef unsigned long int ck_rsa_pkcs_mgf_type_t; #define CKM_ECDSA_SHA256 (0x1044UL) #define CKM_ECDSA_SHA384 (0x1045UL) #define CKM_ECDSA_SHA512 (0x1046UL) +#define CKM_ECDSA_SHA3_224 (0x1047UL) +#define CKM_ECDSA_SHA3_256 (0x1048UL) +#define CKM_ECDSA_SHA3_384 (0x1049UL) +#define CKM_ECDSA_SHA3_512 (0x104AUL) #define CKM_ECDH1_DERIVE (0x1050UL) #define CKM_ECDH1_COFACTOR_DERIVE (0x1051UL) #define CKM_ECMQV_DERIVE (0x1052UL) +#define CKM_EC_EDWARDS_KEY_PAIR_GEN (0x1055UL) +#define CKM_EC_MONTGOMERY_KEY_PAIR_GEN (0x1056UL) +#define CKM_EDDSA (0x1057UL) #define CKM_JUNIPER_KEY_GEN (0x1060UL) #define CKM_JUNIPER_ECB128 (0x1061UL) #define CKM_JUNIPER_CBC128 (0x1062UL) @@ -690,17 +761,37 @@ typedef unsigned long int ck_rsa_pkcs_mgf_type_t; #define CKM_AES_GCM (0x1087UL) #define CKM_AES_CCM (0x1088UL) #define CKM_AES_CTS (0x1089UL) +#define CKM_AES_CMAC (0x108AUL) +#define CKM_AES_CMAC_GENERAL (0x108BUL) +#define CKM_AES_XCBC_MAC (0x108CUL) +#define CKM_AES_XCBC_MAC_96 (0x108DUL) +#define CKM_AES_GMAC (0x108EUL) #define CKM_BLOWFISH_KEY_GEN (0x1090UL) #define CKM_BLOWFISH_CBC (0x1091UL) #define CKM_TWOFISH_KEY_GEN (0x1092UL) #define CKM_TWOFISH_CBC (0x1093UL) +#define CKM_DES_ECB_ENCRYPT_DATA (0x1100UL) +#define CKM_DES_CBC_ENCRYPT_DATA (0x1101UL) +#define CKM_DES3_ECB_ENCRYPT_DATA (0x1102UL) +#define CKM_DES3_CBC_ENCRYPT_DATA (0x1103UL) +#define CKM_AES_ECB_ENCRYPT_DATA (0x1104UL) +#define CKM_AES_CBC_ENCRYPT_DATA (0x1105UL) #define CKM_GOSTR3410_KEY_PAIR_GEN (0x1200UL) #define CKM_GOSTR3410 (0x1201UL) #define CKM_GOSTR3410_WITH_GOSTR3411 (0x1202UL) #define CKM_GOSTR3410_KEY_WRAP (0x1203UL) #define CKM_GOSTR3410_DERIVE (0x1204UL) +#define CKM_GOSTR3410_512_KEY_PAIR_GEN (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x005) +#define CKM_GOSTR3410_512 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x006) +#define CKM_GOSTR3410_12_DERIVE (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x007) +#define CKM_GOSTR3410_WITH_GOSTR3411_12_256 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x008) +#define CKM_GOSTR3410_WITH_GOSTR3411_12_512 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x009) #define CKM_GOSTR3411 (0x1210UL) #define CKM_GOSTR3411_HMAC (0x1211UL) +#define CKM_GOSTR3411_12_256 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x012) +#define CKM_GOSTR3411_12_512 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x013) +#define CKM_GOSTR3411_12_256_HMAC (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x014) +#define CKM_GOSTR3411_12_512_HMAC (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x015) #define CKM_GOST28147_KEY_GEN (0x1220UL) #define CKM_GOST28147_ECB (0x1221UL) #define CKM_GOST28147 (0x1222UL) @@ -710,8 +801,16 @@ typedef unsigned long int ck_rsa_pkcs_mgf_type_t; #define CKM_DSA_PARAMETER_GEN (0x2000UL) #define CKM_DH_PKCS_PARAMETER_GEN (0x2001UL) #define CKM_X9_42_DH_PARAMETER_GEN (0x2002UL) -#define CKM_VENDOR_DEFINED (1UL << 31) +#define CKM_AES_OFB (0x2104UL) +#define CKM_AES_CFB64 (0x2105UL) +#define CKM_AES_CFB8 (0x2106UL) +#define CKM_AES_CFB128 (0x2107UL) +#define CKM_AES_KEY_WRAP (0x2109UL) +#define CKM_AES_KEY_WRAP_PAD (0x210AUL) +#define CKM_XEDDSA (0x4029UL) + +#define CKM_VENDOR_DEFINED (1UL << 31) struct ck_mechanism { @@ -729,6 +828,14 @@ struct ck_mechanism_info }; #define CKF_HW (1UL << 0) + +#define CKF_MESSAGE_ENCRYPT (1UL << 1) +#define CKF_MESSAGE_DECRYPT (1UL << 2) +#define CKF_MESSAGE_SIGN (1UL << 3) +#define CKF_MESSAGE_VERIFY (1UL << 4) +#define CKF_MULTI_MESSAGE (1UL << 5) +#define CKF_FIND_OBJECTS (1UL << 6) + #define CKF_ENCRYPT (1UL << 8) #define CKF_DECRYPT (1UL << 9) #define CKF_DIGEST (1UL << 10) @@ -746,15 +853,22 @@ struct ck_mechanism_info #define CKF_EC_F_P (1UL << 20) #define CKF_EC_F_2M (1UL << 21) #define CKF_EC_ECPARAMETERS (1UL << 22) -#define CKF_EC_NAMEDCURVE (1UL << 23) +#define CKF_EC_OID (1UL << 23) +#define CKF_EC_NAMEDCURVE CKF_EC_OID #define CKF_EC_UNCOMPRESS (1UL << 24) #define CKF_EC_COMPRESS (1UL << 25) +#define CKF_EC_CURVENAME (1UL << 26) /* Flags for C_WaitForSlotEvent. */ #define CKF_DONT_BLOCK (1UL) /* Flags for Key derivation */ -#define CKD_NULL (1UL << 0) +#define CKD_NULL (0x1UL) +#define CKD_SHA1_KDF (0x2UL) +#define CKD_SHA224_KDF (0x5UL) +#define CKD_SHA256_KDF (0x6UL) +#define CKD_SHA384_KDF (0x7UL) +#define CKD_SHA512_KDF (0x8UL) typedef struct CK_ECDH1_DERIVE_PARAMS { unsigned long kdf; @@ -764,10 +878,34 @@ typedef struct CK_ECDH1_DERIVE_PARAMS { unsigned char * pPublicData; } CK_ECDH1_DERIVE_PARAMS; +typedef struct CK_ECMQV_DERIVE_PARAMS { + unsigned long kdf; + unsigned long ulSharedDataLen; + unsigned char * pSharedData; + unsigned long ulPublicDataLen; + unsigned char * pPublicData; + unsigned long ulPrivateDataLen; + CK_OBJECT_HANDLE hPrivateData; + unsigned long ulPublicDataLen2; + unsigned char * pPublicData2; + CK_OBJECT_HANDLE publicKey; +} CK_ECMQV_DERIVE_PARAMS; + +typedef unsigned long ck_rsa_pkcs_mgf_type_t; +typedef unsigned long CK_RSA_PKCS_OAEP_SOURCE_TYPE; + +typedef struct CK_RSA_PKCS_OAEP_PARAMS { + CK_MECHANISM_TYPE hashAlg; + CK_RSA_PKCS_MGF_TYPE mgf; + CK_RSA_PKCS_OAEP_SOURCE_TYPE source; + void *pSourceData; + unsigned long ulSourceDataLen; +} CK_RSA_PKCS_OAEP_PARAMS; + typedef struct CK_RSA_PKCS_PSS_PARAMS { - ck_mechanism_type_t hashAlg; - unsigned long mgf; - unsigned long sLen; + ck_mechanism_type_t hashAlg; + CK_RSA_PKCS_MGF_TYPE mgf; + unsigned long sLen; } CK_RSA_PKCS_PSS_PARAMS; #define CKG_MGF1_SHA1 (0x00000001UL) @@ -775,6 +913,44 @@ typedef struct CK_RSA_PKCS_PSS_PARAMS { #define CKG_MGF1_SHA256 (0x00000002UL) #define CKG_MGF1_SHA384 (0x00000003UL) #define CKG_MGF1_SHA512 (0x00000004UL) +#define CKG_MGF1_SHA3_224 (0x00000006UL) +#define CKG_MGF1_SHA3_256 (0x00000007UL) +#define CKG_MGF1_SHA3_384 (0x00000008UL) +#define CKG_MGF1_SHA3_512 (0x00000009UL) + +#define CKZ_DATA_SPECIFIED (0x00000001UL) + +typedef struct CK_GCM_PARAMS { + void * pIv; + unsigned long ulIvLen; + unsigned long ulIvBits; + void * pAAD; + unsigned long ulAADLen; + unsigned long ulTagBits; +} CK_GCM_PARAMS; + +/* EDDSA */ +typedef struct CK_EDDSA_PARAMS { + unsigned char phFlag; + unsigned long ulContextDataLen; + unsigned char *pContextData; +} CK_EDDSA_PARAMS; + +typedef CK_EDDSA_PARAMS *CK_EDDSA_PARAMS_PTR; + +/* XEDDSA */ +typedef struct CK_XEDDSA_PARAMS { + unsigned long hash; +} CK_XEDDSA_PARAMS; + +typedef CK_XEDDSA_PARAMS *CK_XEDDSA_PARAMS_PTR; + +typedef struct CK_AES_CTR_PARAMS { + unsigned long ulCounterBits; + unsigned char cb[16]; +} CK_AES_CTR_PARAMS; + +typedef CK_AES_CTR_PARAMS *CK_AES_CTR_PARAMS_PTR; typedef unsigned long ck_rv_t; @@ -782,8 +958,17 @@ typedef unsigned long ck_rv_t; typedef ck_rv_t (*ck_notify_t) (ck_session_handle_t session, ck_notification_t event, void *application); +struct ck_interface { + char * pInterfaceName; + void * pFunctionList; + ck_flags_t flags; +}; + +#define CKF_INTERFACE_FORK_SAFE (0x00000001UL) + /* Forward reference. */ struct ck_function_list; +struct ck_function_list_3_0; #define _CK_DECLARE_FUNCTION(name, args) \ typedef ck_rv_t (*CK_ ## name) args; \ @@ -840,7 +1025,7 @@ _CK_DECLARE_FUNCTION (C_SetOperationState, unsigned char *operation_state, unsigned long operation_state_len, ck_object_handle_t encryption_key, - ck_object_handle_t authentiation_key)); + ck_object_handle_t authentication_key)); _CK_DECLARE_FUNCTION (C_Login, (ck_session_handle_t session, ck_user_type_t user_type, unsigned char *pin, unsigned long pin_len)); @@ -1065,6 +1250,147 @@ _CK_DECLARE_FUNCTION (C_GenerateRandom, _CK_DECLARE_FUNCTION (C_GetFunctionStatus, (ck_session_handle_t session)); _CK_DECLARE_FUNCTION (C_CancelFunction, (ck_session_handle_t session)); +_CK_DECLARE_FUNCTION (C_GetInterfaceList, + (struct ck_interface *interfaces_list, + unsigned long *count)); +_CK_DECLARE_FUNCTION (C_GetInterface, + (unsigned char *interface_name, + struct ck_version *version, + struct ck_interface **interface_ptr, + ck_flags_t flags)); + +_CK_DECLARE_FUNCTION (C_LoginUser, + (ck_session_handle_t session, + ck_user_type_t user_type, + unsigned char *pin, + unsigned long pin_len, + unsigned char *username, + unsigned long username_len)); + +_CK_DECLARE_FUNCTION (C_SessionCancel, + (ck_session_handle_t session, + ck_flags_t flags)); + +_CK_DECLARE_FUNCTION (C_MessageEncryptInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_EncryptMessage, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *associated_data, + unsigned long associated_data_len, + unsigned char *plaintext, + unsigned long plaintext_len, + unsigned char *ciphertext, + unsigned long *ciphertext_len)); +_CK_DECLARE_FUNCTION (C_EncryptMessageBegin, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *associated_data, + unsigned long associated_data_len)); +_CK_DECLARE_FUNCTION (C_EncryptMessageNext, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *plaintext_part, + unsigned long plaintext_part_len, + unsigned char *ciphertext_part, + unsigned long *ciphertext_part_len, + ck_flags_t flags)); +_CK_DECLARE_FUNCTION (C_MessageEncryptFinal, + (ck_session_handle_t session)); + +_CK_DECLARE_FUNCTION (C_MessageDecryptInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_DecryptMessage, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *associated_data, + unsigned long associated_data_len, + unsigned char *ciphertext, + unsigned long ciphertext_len, + unsigned char *plaintext, + unsigned long *plaintext_len)); +_CK_DECLARE_FUNCTION (C_DecryptMessageBegin, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *associated_data, + unsigned long associated_data_len)); +_CK_DECLARE_FUNCTION (C_DecryptMessageNext, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *ciphertext_part, + unsigned long ciphertext_part_len, + unsigned char *plaintext_part, + unsigned long *plaintext_part_len, + ck_flags_t flags)); +_CK_DECLARE_FUNCTION (C_MessageDecryptFinal, + (ck_session_handle_t session)); + +_CK_DECLARE_FUNCTION (C_MessageSignInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_SignMessage, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *data, + unsigned long data_len, + unsigned char *signature, + unsigned long *signature_len)); +_CK_DECLARE_FUNCTION (C_SignMessageBegin, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len)); +_CK_DECLARE_FUNCTION (C_SignMessageNext, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *data, + unsigned long data_len, + unsigned char *signature, + unsigned long *signature_len)); +_CK_DECLARE_FUNCTION (C_MessageSignFinal, + (ck_session_handle_t session)); + +_CK_DECLARE_FUNCTION (C_MessageVerifyInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_VerifyMessage, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *data, + unsigned long data_len, + unsigned char *signature, + unsigned long signature_len)); +_CK_DECLARE_FUNCTION (C_VerifyMessageBegin, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len)); +_CK_DECLARE_FUNCTION (C_VerifyMessageNext, + (ck_session_handle_t session, + void *parameter, + unsigned long parameter_len, + unsigned char *data, + unsigned long data_len, + unsigned char *signature, + unsigned long signature_len)); +_CK_DECLARE_FUNCTION (C_MessageVerifyFinal, + (ck_session_handle_t session)); + +/* Flags in Message-based encryption/decryption API */ +#define CKF_END_OF_MESSAGE (0x00000001UL) struct ck_function_list { @@ -1139,6 +1465,105 @@ struct ck_function_list CK_C_WaitForSlotEvent C_WaitForSlotEvent; }; +struct ck_function_list_3_0 +{ + struct ck_version version; + CK_C_Initialize C_Initialize; + CK_C_Finalize C_Finalize; + CK_C_GetInfo C_GetInfo; + CK_C_GetFunctionList C_GetFunctionList; + CK_C_GetSlotList C_GetSlotList; + CK_C_GetSlotInfo C_GetSlotInfo; + CK_C_GetTokenInfo C_GetTokenInfo; + CK_C_GetMechanismList C_GetMechanismList; + CK_C_GetMechanismInfo C_GetMechanismInfo; + CK_C_InitToken C_InitToken; + CK_C_InitPIN C_InitPIN; + CK_C_SetPIN C_SetPIN; + CK_C_OpenSession C_OpenSession; + CK_C_CloseSession C_CloseSession; + CK_C_CloseAllSessions C_CloseAllSessions; + CK_C_GetSessionInfo C_GetSessionInfo; + CK_C_GetOperationState C_GetOperationState; + CK_C_SetOperationState C_SetOperationState; + CK_C_Login C_Login; + CK_C_Logout C_Logout; + CK_C_CreateObject C_CreateObject; + CK_C_CopyObject C_CopyObject; + CK_C_DestroyObject C_DestroyObject; + CK_C_GetObjectSize C_GetObjectSize; + CK_C_GetAttributeValue C_GetAttributeValue; + CK_C_SetAttributeValue C_SetAttributeValue; + CK_C_FindObjectsInit C_FindObjectsInit; + CK_C_FindObjects C_FindObjects; + CK_C_FindObjectsFinal C_FindObjectsFinal; + CK_C_EncryptInit C_EncryptInit; + CK_C_Encrypt C_Encrypt; + CK_C_EncryptUpdate C_EncryptUpdate; + CK_C_EncryptFinal C_EncryptFinal; + CK_C_DecryptInit C_DecryptInit; + CK_C_Decrypt C_Decrypt; + CK_C_DecryptUpdate C_DecryptUpdate; + CK_C_DecryptFinal C_DecryptFinal; + CK_C_DigestInit C_DigestInit; + CK_C_Digest C_Digest; + CK_C_DigestUpdate C_DigestUpdate; + CK_C_DigestKey C_DigestKey; + CK_C_DigestFinal C_DigestFinal; + CK_C_SignInit C_SignInit; + CK_C_Sign C_Sign; + CK_C_SignUpdate C_SignUpdate; + CK_C_SignFinal C_SignFinal; + CK_C_SignRecoverInit C_SignRecoverInit; + CK_C_SignRecover C_SignRecover; + CK_C_VerifyInit C_VerifyInit; + CK_C_Verify C_Verify; + CK_C_VerifyUpdate C_VerifyUpdate; + CK_C_VerifyFinal C_VerifyFinal; + CK_C_VerifyRecoverInit C_VerifyRecoverInit; + CK_C_VerifyRecover C_VerifyRecover; + CK_C_DigestEncryptUpdate C_DigestEncryptUpdate; + CK_C_DecryptDigestUpdate C_DecryptDigestUpdate; + CK_C_SignEncryptUpdate C_SignEncryptUpdate; + CK_C_DecryptVerifyUpdate C_DecryptVerifyUpdate; + CK_C_GenerateKey C_GenerateKey; + CK_C_GenerateKeyPair C_GenerateKeyPair; + CK_C_WrapKey C_WrapKey; + CK_C_UnwrapKey C_UnwrapKey; + CK_C_DeriveKey C_DeriveKey; + CK_C_SeedRandom C_SeedRandom; + CK_C_GenerateRandom C_GenerateRandom; + CK_C_GetFunctionStatus C_GetFunctionStatus; + CK_C_CancelFunction C_CancelFunction; + CK_C_WaitForSlotEvent C_WaitForSlotEvent; + /* PKCS #11 3.0 functions */ + CK_C_GetInterfaceList C_GetInterfaceList; + CK_C_GetInterface C_GetInterface; + CK_C_LoginUser C_LoginUser; + CK_C_SessionCancel C_SessionCancel; + CK_C_MessageEncryptInit C_MessageEncryptInit; + CK_C_EncryptMessage C_EncryptMessage; + CK_C_EncryptMessageBegin C_EncryptMessageBegin; + CK_C_EncryptMessageNext C_EncryptMessageNext; + CK_C_MessageEncryptFinal C_MessageEncryptFinal; + CK_C_MessageDecryptInit C_MessageDecryptInit; + CK_C_DecryptMessage C_DecryptMessage; + CK_C_DecryptMessageBegin C_DecryptMessageBegin; + CK_C_DecryptMessageNext C_DecryptMessageNext; + CK_C_MessageDecryptFinal C_MessageDecryptFinal; + CK_C_MessageSignInit C_MessageSignInit; + CK_C_SignMessage C_SignMessage; + CK_C_SignMessageBegin C_SignMessageBegin; + CK_C_SignMessageNext C_SignMessageNext; + CK_C_MessageSignFinal C_MessageSignFinal; + CK_C_MessageVerifyInit C_MessageVerifyInit; + CK_C_VerifyMessage C_VerifyMessage; + CK_C_VerifyMessageBegin C_VerifyMessageBegin; + CK_C_VerifyMessageNext C_VerifyMessageNext; + CK_C_MessageVerifyFinal C_MessageVerifyFinal; +}; + + typedef ck_rv_t (*ck_createmutex_t) (void **mutex); typedef ck_rv_t (*ck_destroymutex_t) (void *mutex); @@ -1174,6 +1599,7 @@ struct ck_c_initialize_args #define CKR_ATTRIBUTE_SENSITIVE (0x11UL) #define CKR_ATTRIBUTE_TYPE_INVALID (0x12UL) #define CKR_ATTRIBUTE_VALUE_INVALID (0x13UL) +#define CKR_ACTION_PROHIBITED (0x1BUL) #define CKR_DATA_INVALID (0x20UL) #define CKR_DATA_LEN_RANGE (0x21UL) #define CKR_DEVICE_ERROR (0x30UL) @@ -1236,6 +1662,7 @@ struct ck_c_initialize_args #define CKR_RANDOM_SEED_NOT_SUPPORTED (0x120UL) #define CKR_RANDOM_NO_RNG (0x121UL) #define CKR_DOMAIN_PARAMS_INVALID (0x130UL) +#define CKR_CURVE_NOT_SUPPORTED (0x140UL) #define CKR_BUFFER_TOO_SMALL (0x150UL) #define CKR_SAVED_STATE_INVALID (0x160UL) #define CKR_INFORMATION_SENSITIVE (0x170UL) @@ -1320,10 +1747,18 @@ typedef struct ck_mechanism *CK_MECHANISM_PTR; typedef struct ck_mechanism_info CK_MECHANISM_INFO; typedef struct ck_mechanism_info *CK_MECHANISM_INFO_PTR; +typedef struct ck_interface CK_INTERFACE; +typedef struct ck_interface *CK_INTERFACE_PTR; +typedef struct ck_interface **CK_INTERFACE_PTR_PTR; + typedef struct ck_function_list CK_FUNCTION_LIST; typedef struct ck_function_list *CK_FUNCTION_LIST_PTR; typedef struct ck_function_list **CK_FUNCTION_LIST_PTR_PTR; +typedef struct ck_function_list_3_0 CK_FUNCTION_LIST_3_0; +typedef struct ck_function_list_3_0 *CK_FUNCTION_LIST_3_0_PTR; +typedef struct ck_function_list_3_0 **CK_FUNCTION_LIST_3_0_PTR_PTR; + typedef struct ck_c_initialize_args CK_C_INITIALIZE_ARGS; typedef struct ck_c_initialize_args *CK_C_INITIALIZE_ARGS_PTR; @@ -1397,7 +1832,10 @@ typedef struct ck_c_initialize_args *CK_C_INITIALIZE_ARGS_PTR; #undef ck_rv_t #undef ck_notify_t +#undef ck_interface + #undef ck_function_list +#undef ck_function_list_3_0 #undef ck_createmutex_t #undef ck_destroymutex_t diff --git a/client/qdigidoc4.desktop b/client/qdigidoc4.desktop index d1ccc3160..5b9b7a434 100644 --- a/client/qdigidoc4.desktop +++ b/client/qdigidoc4.desktop @@ -13,7 +13,7 @@ Keywords=ID-card;Utility; Keywords[et_EE]=ID-kaart;Haldusvahend; Categories=Qt;Office; -MimeType=application/vnd.etsi.asic-e+zip;application/vnd.etsi.asic-s+zip;application/x-ddoc;application/vnd.lt.archyvai.adoc-2008;application/x-cdoc; +MimeType=application/vnd.etsi.asic-e+zip;application/vnd.etsi.asic-s+zip;application/x-ddoc;application/vnd.lt.archyvai.adoc-2008;application/x-cdoc;application/x-cdoc2; Actions=new-window diff --git a/client/qdigidoc4.xml b/client/qdigidoc4.xml index abc2c2d7c..703cd28a0 100644 --- a/client/qdigidoc4.xml +++ b/client/qdigidoc4.xml @@ -34,4 +34,10 @@ Зашифрованный DigiDoc контейнер + + DigiDoc encrypted container + DigiDoc turvaümbrik + Зашифрованный DigiDoc контейнер + + diff --git a/client/translations/en.ts b/client/translations/en.ts index 69f62ed02..d3b3fe0f7 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -59,14 +59,6 @@ Are you sure that you want use certificate for encrypting, which expired on %1?<br />When decrypter has updated certificates then decrypting is impossible. Are you sure that you want use certificate for encrypting, which expired on %1?<br /><br />When decrypter has updated certificates then decrypting is impossible. - - YES - YES - - - NO - NO - Recipient’s certification chain contains certificates that are not trusted. Continue with encryption? Recipient’s certification chain contains certificates that are not trusted. Continue with encryption? @@ -100,6 +92,10 @@ The name you were looking for gave too many results, please refine your search. The name you were looking for gave too many results, please refine your search. + + Failed to add certificate. An expired certificate cannot be used for encryption. + Failed to add certificate. An expired certificate cannot be used for encryption. + AddressItem @@ -233,6 +229,13 @@ An ID-software update has been found. To download the update, go to the <a href="https://www.id.ee/en/article/install-id-software/">id.ee</a> website. macOS users can download the update from the <a href="https://itunes.apple.com/ee/developer/ria/id556524921?mt=12">Mac App Store</a>. + + CDoc2 + + CDoc contains additional payload data that is not part of content + CDoc contains additional payload data that is not part of content + + CDocumentModel @@ -614,13 +617,33 @@ Digi-ID - Failed to open the container. You need to update your ID-software in order to open CDOC2 containers. Install new ID-software from <a href='https://www.id.ee/en/article/install-id-software/'>www.id.ee</a>. - Failed to open the container. You need to update your ID-software in order to open CDOC2 containers. Install new ID-software from <a href='https://www.id.ee/en/article/install-id-software/'>www.id.ee</a>. + Failed to decrypt document + Failed to decrypt document + + + Failed to open document + Failed to open document An error occurred while opening the document. An error occurred while opening the document. + + You must enter your PIN code twice in order to decrypt the CDOC2 container. The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. Second PIN entry is required to decrypt the CDOC2 container. + You must enter your PIN code twice in order to decrypt the CDOC2 container. The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. Second PIN entry is required to decrypt the CDOC2 container. + + + Failed to encrypt document. Please check your internet connection and network settings. + Failed to encrypt document. Please check your internet connection and network settings. + + + DON'T SHOW AGAIN + DON'T SHOW AGAIN + + + Failed to decrypt document. Please check your internet connection and network settings. + Failed to decrypt document. Please check your internet connection and network settings. + Diagnostics @@ -740,6 +763,14 @@ Applet version Applet version + + true + + + + false + + DigiDoc @@ -807,14 +838,6 @@ The limit for digital signatures per month has been reached for this IP address. <a href="https://www.id.ee/en/article/for-organisations-that-sign-large-quantities-of-documents-using-digidoc4-client/">Additional information</a> The limit for digital signatures per month has been reached for this IP address. <a href="https://www.id.ee/en/article/for-organisations-that-sign-large-quantities-of-documents-using-digidoc4-client/">Additional information</a> - - CANCEL - CANCEL - - - YES - YES - Please check your computer time. <a href='https://www.id.ee/en/article/digidoc4-client-error-please-check-your-computer-time-2/'>Additional information</a> Please check your computer time. <a href='https://www.id.ee/en/article/digidoc4-client-error-please-check-your-computer-time-2/'>Additional information</a> @@ -866,10 +889,6 @@ Internal error Internal error - - OK - OK - Cannot add file with name 'mimetype' to the envelope. Cannot add file with name 'mimetype' to the envelope. @@ -925,14 +944,6 @@ Add files Add files - - NO - NO - - - YES - YES - SAVE WITH OTHER NAME SAVE WITH OTHER NAME @@ -949,10 +960,6 @@ %1 already exists.<br />Do you want replace it? %1 already exists.<br />Do you want replace it? - - CANCEL - CANCEL - FirstRun @@ -1326,6 +1333,14 @@ Expiry date Expiry date + + Key server ID + Key server ID + + + Transaction ID + Transaction ID + LdapSearch @@ -1414,10 +1429,6 @@ ID-CARD MainWindow - - YES - YES - Help Help @@ -1518,10 +1529,6 @@ ID-CARD You are about to delete the last file in the container, it is removed along with the container. You are about to delete the last file in the container, it is removed along with the container. - - CANCEL - CANCEL - Removing signature Removing signature @@ -2397,10 +2404,6 @@ and enter Smart-ID PIN2-code. Language Language - - Online TSL digest check - Online TSL digest check - Container default location Container default location @@ -2593,10 +2596,6 @@ Additional licenses and components %1 version %2, released %3 %1 version %2, released %3 - - YES - YES - NO NO @@ -2919,10 +2918,6 @@ Additional licenses and components Remove signature %1? Remove signature %1? - - CANCEL - CANCEL - Remove signature accessible @@ -3194,6 +3189,18 @@ Additional licenses and components accessible Close + + NO + NO + + + CANCEL + CANCEL + + + YES + YES + WarningItem diff --git a/client/translations/et.ts b/client/translations/et.ts index 357bdbf30..e4040a2ba 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -59,14 +59,6 @@ Are you sure that you want use certificate for encrypting, which expired on %1?<br />When decrypter has updated certificates then decrypting is impossible. Kas oled kindel, et soovid kasutada krüpteerimiseks sertifikaati, mis aegus %1?<br /><br />Juhul, kui dekrüpteerija on oma sertifikaate uuendanud, ei ole dekrüpteerimine võimalik. - - YES - JAH - - - NO - EI - Recipient’s certification chain contains certificates that are not trusted. Continue with encryption? Adressaadi sertifitseerimisahelas on mitteusaldatud sertifikaate. Jätkame krüpteerimist? @@ -100,6 +92,10 @@ The name you were looking for gave too many results, please refine your search. Sinu otsitud nimi andis liiga palju vastuseid, palun täpsusta otsingut. + + Failed to add certificate. An expired certificate cannot be used for encryption. + Sertifikaadi lisamine ebaõnnestus. Aegunud sertifikaati ei saa kasutada krüpteerimiseks. + AddressItem @@ -233,6 +229,13 @@ Saadaval on ID-tarkvara uuendus, mille saad paigaldada veebilehelt <a href="https://www.id.ee/artikkel/paigalda-id-tarkvara/">id.ee</a>, macOS kasutajad saavad uuenduse alla laadida <a href="https://itunes.apple.com/ee/developer/ria/id556524921?mt=12">Mac App Store</a>'ist. + + CDoc2 + + CDoc contains additional payload data that is not part of content + + + CDocumentModel @@ -614,13 +617,33 @@ Digi-ID - Failed to open the container. You need to update your ID-software in order to open CDOC2 containers. Install new ID-software from <a href='https://www.id.ee/en/article/install-id-software/'>www.id.ee</a>. - Ümbriku avamine ebaõnnestus. CDOC2 ümbriku avamiseks pead ID-tarkvara uuendama. Paigalda uus ID-tarkvara veebilehelt <a href='https://www.id.ee/artikkel/paigalda-id-tarkvara/'>www.id.ee</a>. + Failed to decrypt document + Dokumendi dekrüpteerimine ebaõnnestus + + + Failed to open document + Dokumendi avamine ebaõnnestus An error occurred while opening the document. Ümbriku avamisel tekkis viga. + + You must enter your PIN code twice in order to decrypt the CDOC2 container. The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. Second PIN entry is required to decrypt the CDOC2 container. + CDOC2 ümbriku dekrüpteerimiseks tuleb sisestada PIN-koodi kaks korda. Esimene PIN-koodi sisestamine on vajalik autentimiseks CDOC2 ümbrikus viidatud võtmeedastusserverisse. Teine PIN-koodi sisestamine on vajalik CDOC2 ümbriku dekrüpteerimiseks. + + + Failed to encrypt document. Please check your internet connection and network settings. + Dokumendi krüpteerimine ebaõnnestus. Palun kontrolli internetiühendust ja võrgu sätteid. + + + DON'T SHOW AGAIN + ÄRA ROHKEM NÄITA + + + Failed to decrypt document. Please check your internet connection and network settings. + Dokumendi dekrüpteerimine ebaõnnestus. Palun kontrolli internetiühendust ja võrgu sätteid. + Diagnostics @@ -740,6 +763,14 @@ Applet version Apleti versioon + + true + + + + false + + DigiDoc @@ -807,14 +838,6 @@ The limit for digital signatures per month has been reached for this IP address. <a href="https://www.id.ee/en/article/for-organisations-that-sign-large-quantities-of-documents-using-digidoc4-client/">Additional information</a> Sinu IP-aadressi tasuta allkirjade kuulimiit on ületatud. <a href="https://www.id.ee/artikkel/asutustele-kus-allkirjastatakse-digidoc4-kliendi-kaudu-suuremates-kogustes-dokumente/">Loe täpsemalt siit</a> - - CANCEL - KATKESTA - - - YES - JAH - Please check your computer time. <a href='https://www.id.ee/en/article/digidoc4-client-error-please-check-your-computer-time-2/'>Additional information</a> Palun kontrolli oma arvuti kellaaega. <a href='https://www.id.ee/artikkel/digidoc4-klient-viga-palun-kontrollige-oma-arvuti-kellaaega-2/'>Loe täpsemalt siit</a> @@ -866,10 +889,6 @@ Internal error Sisemine viga - - OK - OK - Cannot add file with name 'mimetype' to the envelope. Faili nimega "mimetype" ei saa lisada ümbrikusse. @@ -925,14 +944,6 @@ Add files Lisa faile - - NO - EI - - - YES - JAH - SAVE WITH OTHER NAME SALVESTA TEISE NIMEGA @@ -949,10 +960,6 @@ %1 already exists.<br />Do you want replace it? %1 on juba olemas.<br />Soovid asendada? - - CANCEL - KATKESTA - FirstRun @@ -1326,6 +1333,14 @@ Expiry date Aegumiskuupäev + + Key server ID + Serveri identifikaator + + + Transaction ID + Transaktsiooni identifikaator + LdapSearch @@ -1414,10 +1429,6 @@ ID-KAARDIGA MainWindow - - YES - JAH - Help Abi @@ -1518,10 +1529,6 @@ ID-KAARDIGA You are about to delete the last file in the container, it is removed along with the container. Oled kustutamas viimast faili ümbrikus, koos sellega eemaldatakse ka ümbrik. - - CANCEL - KATKESTA - Removing signature Allkirja eemaldamine @@ -2401,10 +2408,6 @@ ja sisesta nutiseadmes Smart-ID PIN2-kood. Language Keel - - Online TSL digest check - Kontrolli TSL värskendust - Container default location Ümbriku loomise vaikekataloog @@ -2593,10 +2596,6 @@ Täiendavad litsentsid ja komponendid %1 version %2, released %3 %1 versioon %2, avalikustatud %3 - - YES - JAH - NO EI @@ -2919,10 +2918,6 @@ Täiendavad litsentsid ja komponendid Remove signature %1? Kas eemaldada allkiri %1? - - CANCEL - KATKESTA - Remove signature accessible @@ -3194,6 +3189,18 @@ Täiendavad litsentsid ja komponendid accessible Sulge + + NO + EI + + + CANCEL + KATKESTA + + + YES + JAH + WarningItem diff --git a/client/translations/ru.ts b/client/translations/ru.ts index 50c197eca..bbdd61529 100644 --- a/client/translations/ru.ts +++ b/client/translations/ru.ts @@ -59,14 +59,6 @@ Are you sure that you want use certificate for encrypting, which expired on %1?<br />When decrypter has updated certificates then decrypting is impossible. Вы уверены, что хотите использовать для зашифровки сертификат, который просрочился %1?<br /><br />В случае, когда расшифровщик обновит сертификат, расшифровка будет невозможна. - - YES - ДА - - - NO - НЕТ - Recipient’s certification chain contains certificates that are not trusted. Continue with encryption? Цепочка сертификатов получателя содержит ненадежные сертификаты. Продолжить шифрование? @@ -100,6 +92,10 @@ The name you were looking for gave too many results, please refine your search. Согласно Вашему запросу выдается много ответов, пожалуйста уточните запрос. + + Failed to add certificate. An expired certificate cannot be used for encryption. + Не удалось добавить сертификат. Сертификат с истекшим сроком действия нельзя использовать для шифрования. + AddressItem @@ -233,6 +229,13 @@ Выпущено обновление для программного обеспечения ID-карты. Чтобы скачать обновление, перейдите на сайт <a href="https://www.id.ee/ru/artikkel/ustanovite-id-programmu/">id.ee</a>. Пользователи macOS могут скачать обновление в магазине <a href="https://itunes.apple.com/ee/developer/ria/id556524921?mt=12">Mac App Store</a>. + + CDoc2 + + CDoc contains additional payload data that is not part of content + + + CDocumentModel @@ -614,13 +617,33 @@ Digi-ID - Failed to open the container. You need to update your ID-software in order to open CDOC2 containers. Install new ID-software from <a href='https://www.id.ee/en/article/install-id-software/'>www.id.ee</a>. - Не удалось открыть конверт. Для того чтобы открыть конверт CDOC2, необходимо обновить программное обеспечение ID. Установите новое программное обеспечение ID с веб-сайта <a href='https://www.id.ee/ru/artikkel/ustanovite-id-programmu/'>www.id.ee</a>. + Failed to decrypt document + Не удалось расшифровать документ + + + Failed to open document + Не удалось открыть документ An error occurred while opening the document. Во время открытия конверта возникла ошибка. + + You must enter your PIN code twice in order to decrypt the CDOC2 container. The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. Second PIN entry is required to decrypt the CDOC2 container. + PIN-код необходимо ввести дважды, чтобы расшифровать конверт CDOC2. Первый ввод PIN-кода требуется для аутентификации на сервере передачи ключей, указанном в конверте CDOC2. Для расшифровки конверта CDOC2 требуется второй ввод PIN-кода. + + + Failed to encrypt document. Please check your internet connection and network settings. + Не удалось зашифровать документ. Пожалуйста, проверьте подключение к Интернету и настройки сети. + + + DON'T SHOW AGAIN + БОЛЬШЕ НЕ ПОКАЗЫВАТЬ + + + Failed to decrypt document. Please check your internet connection and network settings. + Не удалось расшифровать документ. Пожалуйста, проверьте подключение к Интернету и настройки сети. + Diagnostics @@ -740,6 +763,14 @@ Applet version Версия апплета + + true + + + + false + + DigiDoc @@ -807,14 +838,6 @@ The limit for digital signatures per month has been reached for this IP address. <a href="https://www.id.ee/en/article/for-organisations-that-sign-large-quantities-of-documents-using-digidoc4-client/">Additional information</a> Предел для цифровых подписей в месяц был достигнут для этого IP-адреса. <a href="https://www.id.ee/ru/artikkel/dlya-uchrezhdenij-v-kotoryh-v-bolshom-obeme-podpisyvayutsya-dokumenty-s-pomoshhyu-digidoc4-klienta/">Дополнительная информация</a> - - CANCEL - ОТМЕНА - - - YES - ДА - Please check your computer time. <a href='https://www.id.ee/en/article/digidoc4-client-error-please-check-your-computer-time-2/'>Additional information</a> Пожалуйста проверьте время Вашего компьютера. <a href='https://www.id.ee/ru/artikkel/oshibka-klienta-digidoc4-pozhalujsta-proverte-chasy-na-vashem-kompyutere-2/'>Дополнительная информация</a> @@ -866,10 +889,6 @@ Internal error Внутренняя ошибка - - OK - OK - Cannot add file with name 'mimetype' to the envelope. Файл с названием "mimetype" нельзя добавить в контейнер. @@ -925,14 +944,6 @@ Add files Добавить файлы - - NO - НЕТ - - - YES - ДА - SAVE WITH OTHER NAME СОХРАНИТЬ ПОД ДРУГИМ ИМЕНЕМ @@ -949,10 +960,6 @@ %1 already exists.<br />Do you want replace it? %1 уже существует.<br />Заменить? - - CANCEL - ОТМЕНА - FirstRun @@ -1326,6 +1333,14 @@ Expiry date Дата окончания + + Key server ID + Идентификатор сервера + + + Transaction ID + Идентификатор транзакции + LdapSearch @@ -1414,10 +1429,6 @@ ID-КАРТОЙ MainWindow - - YES - ДА - Help Помощь @@ -1518,10 +1529,6 @@ ID-КАРТОЙ You are about to delete the last file in the container, it is removed along with the container. Вы собираетесь удалить последний файл в контейнере, он удаляется вместе с контейнером. - - CANCEL - ОТМЕНА - Removing signature Удаление подписи @@ -2402,10 +2409,6 @@ and enter Smart-ID PIN2-code. Language Язык - - Online TSL digest check - Проверить обновления TSL - Container default location Расположение контейнера по умолчанию @@ -2594,10 +2597,6 @@ Additional licenses and components %1 version %2, released %3 %1 версия %2, выпущенный %3 - - YES - ДА - NO НЕТ @@ -2920,10 +2919,6 @@ Additional licenses and components Remove signature %1? Удалить подпись %1? - - CANCEL - ОТМЕНА - Remove signature accessible @@ -3195,6 +3190,18 @@ Additional licenses and components accessible Закрыть + + NO + НЕТ + + + CANCEL + ОТМЕНА + + + YES + ДА + WarningItem diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index a6ebccf65..08d231d3e 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -101,12 +101,17 @@ const CKey& AddressItem::getKey() const return ui->key; } -void AddressItem::idChanged(const SslCertificate &cert) +void AddressItem::idChanged(const CKey &key) { - ui->yourself = !cert.isNull() && ui->key.cert == cert; + ui->yourself = !key.key.isNull() && ui->key == key; setName(); } +void AddressItem::idChanged(const SslCertificate &cert) +{ + idChanged(CKey(cert)); +} + void AddressItem::initTabOrder(QWidget *item) { setTabOrder(item, ui->name); diff --git a/client/widgets/AddressItem.h b/client/widgets/AddressItem.h index a7792abb9..54d3788db 100644 --- a/client/widgets/AddressItem.h +++ b/client/widgets/AddressItem.h @@ -40,6 +40,7 @@ class AddressItem final : public Item ~AddressItem() final; const CKey& getKey() const; + void idChanged(const CKey &cert); void idChanged(const SslCertificate &cert) final; void initTabOrder(QWidget *item) final; QWidget* lastTabWidget() final; diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index fb233f16c..a01ef63c8 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -154,8 +154,8 @@ void ContainerPage::clearPopups() void ContainerPage::elideFileName() { - ui->containerFile->setText("" + - ui->containerFile->fontMetrics().elidedText(FileDialog::normalized(fileName).toHtmlEscaped(), Qt::ElideMiddle, ui->containerFile->width()) + ""); + ui->containerFile->setText(QStringLiteral("%1") + .arg(ui->containerFile->fontMetrics().elidedText(FileDialog::normalized(fileName).toHtmlEscaped(), Qt::ElideMiddle, ui->containerFile->width()))); } bool ContainerPage::eventFilter(QObject *o, QEvent *e) @@ -218,15 +218,6 @@ void ContainerPage::forward(int code) } } -void ContainerPage::initContainer( const QString &file, const QString &suffix ) -{ - const QFileInfo f( file ); - if( !f.isFile() ) return; - - fileName = (f.dir().path() + QDir::separator() + f.completeBaseName() + suffix); - ui->containerFile->setText(fileName.toHtmlEscaped()); -} - void ContainerPage::hideRightPane() { ui->rightPane->hide(); @@ -345,8 +336,7 @@ void ContainerPage::transition(DigiDoc* container) hasEmptyFile = false; for (auto i = 0; i < container->documentModel()->rowCount(); i++) { - const auto fileSize = container->documentModel()->fileSize(i).trimmed(); - if (fileSize.startsWith(QStringLiteral("0 "), Qt::CaseInsensitive)) + if(container->documentModel()->fileSize(i) == 0) { emit warning(EmptyFileWarning); hasEmptyFile = true; diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index 54224549f..f035d2b69 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -71,7 +71,6 @@ class ContainerPage final : public QWidget bool eventFilter(QObject *o, QEvent *e) final; void forward(int code); void hideRightPane(); - void initContainer( const QString &file, const QString &suffix ); void showMainAction(const QList &actions); void showRightPane(ria::qdigidoc4::ItemType itemType, const char *header); void showSigningButton(); diff --git a/client/widgets/FileList.cpp b/client/widgets/FileList.cpp index 468853345..a1cc5c7aa 100644 --- a/client/widgets/FileList.cpp +++ b/client/widgets/FileList.cpp @@ -168,11 +168,11 @@ void FileList::saveAll() } auto *dlg = new WarningDialog(tr("%1 already exists.
Do you want replace it?").arg( dest ), this); dlg->setButtonSize(60, 5); - dlg->setCancelText(tr("NO")); - dlg->addButton(tr("YES"), QMessageBox::Yes); + dlg->setCancelText(WarningDialog::NO); + dlg->addButton(WarningDialog::YES, QMessageBox::Yes); dlg->addButton(tr("SAVE WITH OTHER NAME"), QMessageBox::No); dlg->addButton(tr("REPLACE ALL"), QMessageBox::YesToAll); - dlg->addButton(tr("CANCEL"), QMessageBox::NoToAll); + dlg->addButton(WarningDialog::Cancel, QMessageBox::NoToAll); b = dlg->exec(); if(b == QDialog::Rejected) diff --git a/client/widgets/SignatureItem.cpp b/client/widgets/SignatureItem.cpp index fa72b29cd..1efe064ec 100644 --- a/client/widgets/SignatureItem.cpp +++ b/client/widgets/SignatureItem.cpp @@ -201,9 +201,9 @@ void SignatureItem::removeSignature() .arg(c.toString(c.showCN() ? QStringLiteral("CN serialNumber") : QStringLiteral("GN SN serialNumber"))); auto *dlg = WarningDialog::show(this, msg); - dlg->setCancelText(tr("CANCEL")); + dlg->setCancelText(WarningDialog::Cancel); dlg->resetCancelStyle(); - dlg->addButton(QStringLiteral("OK"), SignatureRemove, true); + dlg->addButton(WarningDialog::OK, SignatureRemove, true); connect(dlg, &WarningDialog::finished, this, [this](int result) { if(result == SignatureRemove) emit remove(this); diff --git a/prepare_osx_build_environment.sh b/prepare_osx_build_environment.sh index c8b823611..a3ca098eb 100755 --- a/prepare_osx_build_environment.sh +++ b/prepare_osx_build_environment.sh @@ -78,7 +78,7 @@ if [[ ! -d ${OPENSSL_PATH} ]] ; then tar xf openssl-${OPENSSL_VER}.tar.gz cd openssl-${OPENSSL_VER} for ARCH in x86_64 arm64; do - ./Configure darwin64-${ARCH} --prefix=${OPENSSL_PATH} shared no-autoload-config no-module no-engine no-tests enable-ec_nistp_64_gcc_128 + ./Configure darwin64-${ARCH} --prefix=${OPENSSL_PATH} shared no-autoload-config no-module no-tests enable-ec_nistp_64_gcc_128 make -s > /dev/null make install_sw mv ${OPENSSL_PATH} ${OPENSSL_PATH}.${ARCH} diff --git a/qdigidoc4.wxs b/qdigidoc4.wxs index 5f2970aff..9f83b04b3 100644 --- a/qdigidoc4.wxs +++ b/qdigidoc4.wxs @@ -4,9 +4,9 @@ Depends package: libdigidocpp-3.13.8.1379.msi msiexec /a libdigidocpp-3.13.8.1378.msi /qn TARGETDIR=C:\target -"%WIX%\bin\candle.exe" qdigidoc4.wxs -dMSI_VERSION=4.2.1 - -dlibs_path="C:\target\libdigidocpp\x86" - -dqt_path=C:\Qt\5.12\msvc2017 +"%WIX%\bin\candle.exe" qdigidoc4.wxs -dMSI_VERSION=4.4.0 + -dlibs_path="C:\target\libdigidocpp\x64" + -dqt_path=C:\Qt\6.5.0\msvc2019 -dclient_path=client\qdigidoc4.exe -dico_path=client\images\digidoc.ico @@ -14,7 +14,7 @@ msiexec /a libdigidocpp-3.13.8.1378.msi /qn TARGETDIR=C:\target --> - + @@ -116,6 +116,11 @@ msiexec /a libdigidocpp-3.13.8.1378.msi /qn TARGETDIR=C:\target + + + + + diff --git a/schema/header.fbs b/schema/header.fbs new file mode 100644 index 000000000..3c242f20e --- /dev/null +++ b/schema/header.fbs @@ -0,0 +1,53 @@ +/* + * 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 "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 new file mode 100644 index 000000000..99bfbe509 --- /dev/null +++ b/schema/recipients.fbs @@ -0,0 +1,76 @@ +/* + * 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 + * + */ + +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); +}