diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d690d1c84..707729634 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -30,7 +30,7 @@ jobs:
- name: Install dependencies
run: |
brew install flatbuffers
- curl -O -L -s https://installer.id.ee/media/github/opensc_0.26.1.pkg
+ curl -O -L -s https://installer.id.ee/media/github/opensc_0.27.1-3.pkg
sudo installer -verboseR -pkg libdigidocpp-pkg/build/macos/libdigidocpp*.pkg -target /
sudo installer -verboseR -pkg opensc_*.pkg -target /
rm -rf libdigidocpp-pkg
@@ -41,12 +41,12 @@ jobs:
key: vcpkg-macOS-${{ hashFiles('prepare_osx_build_environment.sh') }}
- name: Build libraries
run: |
- mkdir -p ${LIBS_PATH}/Qt-6.10.3-OpenSSL
+ mkdir -p ${LIBS_PATH}/Qt-6.11.2-OpenSSL
./prepare_osx_build_environment.sh -p ${LIBS_PATH}
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
- version: 6.10.3
+ version: 6.11.2
arch: clang_64
cache: true
- name: Build
@@ -169,22 +169,27 @@ jobs:
Rename-Item "libdigidocpp*.msi" libdigidocpp.msi
$r = Start-Process msiexec -ArgumentList '/a', 'libdigidocpp.msi', '/qn', "TARGETDIR=$env:GITHUB_WORKSPACE\libs", '/l*v', 'install.log' -Wait -PassThru
if ($r.ExitCode -ne 0) { Get-Content install.log; exit $r.ExitCode }
- - name: Cache vcpkg
+ - name: Cache vcpkg and WiX
uses: actions/cache@v5
with:
- path: ${{ github.workspace }}/vcpkg_cache
- key: vcpkg-${{ matrix.vcver }}-${{ matrix.platform }}-${{ hashFiles('client/libcdoc/vcpkg.json') }}
+ path: |
+ ${{ github.workspace }}/vcpkg_cache
+ ~/.dotnet/tools
+ ~/.wix
+ key: vcpkg-${{ matrix.vcver }}-${{ matrix.platform }}-${{ hashFiles('client/libcdoc/vcpkg.json') }}-wix-7.0.0
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
- version: 6.11.1
+ version: 6.11.2
arch: ${{ matrix.platform == 'arm64' && 'win64_msvc2022_arm64_cross_compiled' || 'win64_msvc2022_64' }}
cache: true
aqtsource: git+https://github.com/miurahr/aqtinstall.git
- name: Install WiX
run: |
- dotnet tool install -g wix --version 6.0.2
- wix extension -g add WixToolset.UI.wixext/6.0.2
+ dotnet tool install -g wix --version 7.0.0
+ wix eula accept wix7
+ wix extension -g add WixToolset.UI.wixext/7.0.0
+ wix extension -g add WixToolset.Util.wixext/7.0.0
- name: Build
shell: cmd
env:
@@ -207,6 +212,7 @@ jobs:
path: |
build/*.msi
build/*.appx
+ build/*.cer
coverity:
name: Run Coverity tests
if: github.repository == 'open-eid/DigiDoc4-Client' && contains(github.ref, 'coverity_scan')
diff --git a/AppxManifest-ShellExt.xml.cmake b/AppxManifest-ShellExt.xml.cmake
index 72ab42401..df7810187 100644
--- a/AppxManifest-ShellExt.xml.cmake
+++ b/AppxManifest-ShellExt.xml.cmake
@@ -8,7 +8,7 @@
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
IgnorableNamespaces="com desktop4 desktop5 rescap uap uap10">
+ Publisher="${APPX_PUBLISHER}" />
DigiDoc4 Shell Extension
Riigi Infosüsteemi Amet
diff --git a/AppxManifest.xml.cmake b/AppxManifest.xml.cmake
index a9c07df02..5669719a9 100644
--- a/AppxManifest.xml.cmake
+++ b/AppxManifest.xml.cmake
@@ -9,7 +9,7 @@
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
IgnorableNamespaces="com desktop desktop4 desktop5 rescap uap uap3">
+ Publisher="${APPX_PUBLISHER}" />
DigiDoc4 Client
Riigi Infosüsteemi Amet
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d5da89210..f2c6e8aae 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.22)
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/common/CMakeLists.txt)
message(FATAL_ERROR "common submodule directory empty, did you 'git clone --recursive'?")
endif()
-project(qdigidoc4 VERSION 4.11.1
+project(qdigidoc4 VERSION 4.12.0
DESCRIPTION "DigiDoc4 application for digital signing and encryption"
HOMEPAGE_URL https://github.com/open-eid/DigiDoc4-Client
)
@@ -54,6 +54,7 @@ find_package(Qt6 6.2.0 REQUIRED COMPONENTS Core Widgets Network PrintSupport Svg
if(APPLE)
add_subdirectory(extensions/DigiDocQL)
elseif(WIN32)
+ set(APPX_PUBLISHER "CN=8BBBE4D8-620A-4884-A12A-72F1A2030D8B" CACHE STRING "Publisher for test Appx and MSIX packages")
add_subdirectory(extensions/windows)
elseif(UNIX)
option(ENABLE_KDE "Install KDE service menu (default: TRUE)" TRUE)
diff --git a/README.md b/README.md
index f7fd729de..ca030141d 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@
3. Configure
cmake -B build -S . \
- -DCMAKE_PREFIX_PATH=~/cmake_builds/Qt-6.11.1-OpenSSL \
+ -DCMAKE_PREFIX_PATH=~/cmake_builds/Qt-6.11.2-OpenSSL \
-DOPENSSL_ROOT_DIR=~/cmake_build/OpenSSL \
-DLDAP_ROOT=~/cmake_build/OpenLDAP \
-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
@@ -95,7 +95,7 @@
3. Configure
cmake -G"NMAKE Makefiles" -B build -S . `
- -DCMAKE_PREFIX_PATH=C:\Qt\6.11.1\msvc2022_64 `
+ -DCMAKE_PREFIX_PATH=C:\Qt\6.11.2\msvc2022_64 `
-DLibDigiDocpp_ROOT="C:\Program Files (x86)\libdigidocpp" `
-DVCPKG_MANIFEST_DIR=client/libcdoc
diff --git a/SignAppx.ps1 b/SignAppx.ps1
new file mode 100644
index 000000000..2bd16b647
--- /dev/null
+++ b/SignAppx.ps1
@@ -0,0 +1,50 @@
+# SPDX-FileCopyrightText: Estonian Information System Authority
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+param(
+ [Parameter(Mandatory = $true)]
+ [string] $PackagePath,
+
+ [Parameter(Mandatory = $true)]
+ [string] $ManifestPath,
+
+ [Parameter(Mandatory = $true)]
+ [string] $CertificatePath
+)
+
+$ErrorActionPreference = 'Stop'
+$certificate = $null
+
+try {
+ [xml] $manifest = Get-Content -LiteralPath $ManifestPath -Raw
+ $publisher = $manifest.Package.Identity.Publisher
+ if ([string]::IsNullOrWhiteSpace($publisher)) {
+ throw "The Appx manifest does not define a publisher."
+ }
+
+ $certificate = New-SelfSignedCertificate `
+ -Type Custom `
+ -Subject $publisher `
+ -FriendlyName 'DigiDoc4 ephemeral Appx test certificate' `
+ -CertStoreLocation 'Cert:\CurrentUser\My' `
+ -Provider 'Microsoft Software Key Storage Provider' `
+ -KeyAlgorithm RSA `
+ -KeyLength 2048 `
+ -HashAlgorithm SHA256 `
+ -KeyExportPolicy NonExportable `
+ -KeyUsage DigitalSignature `
+ -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13', '2.5.29.19={text}') `
+ -NotAfter (Get-Date).AddDays(30)
+
+ Export-Certificate -Cert $certificate -FilePath $CertificatePath -Type CERT -Force | Out-Null
+
+ & signtool.exe sign /v /s My /sha1 $certificate.Thumbprint /fd SHA256 $PackagePath
+ if ($LASTEXITCODE -ne 0) {
+ throw "SignTool failed with exit code $LASTEXITCODE."
+ }
+}
+finally {
+ if ($null -ne $certificate) {
+ Remove-Item -LiteralPath "Cert:\CurrentUser\My\$($certificate.Thumbprint)" -DeleteKey
+ }
+}
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 475f57341..b460b3fdc 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -271,12 +271,13 @@ elseif(WIN32)
--skip-plugin-types generic,networkinformation,iconengines --exclude-plugins qjpeg,qico,qgif,qcertonlybackend,qschannelbackend appx
COMMAND ${CMAKE_COMMAND} -E copy ${LIBS_PATH}/digidoc-tool.exe appx
COMMAND makeappx.exe pack -d appx -p ${MSI_FILE}.appx
- # https://msdn.microsoft.com/en-us/library/windows/desktop/jj835832(v=vs.85).aspx
- #Popups GUI
- #COMMAND MakeCert.exe /n "CN=8BBBE4D8-620A-4884-A12A-72F1A2030D8B" /r /h 0 /eku "1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13" /m 120 /a sha256 /sv qdigidoc4.pvk qdigidoc4.cer
- #COMMAND Pvk2Pfx.exe /pvk qdigidoc4.pvk /spc qdigidoc4.cer /pfx qdigidoc4.pfx /f
- COMMAND signtool.exe sign -f ${CMAKE_SOURCE_DIR}/qdigidoc4.pfx -fd SHA256 -v ${MSI_FILE}.appx
+ COMMAND powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass
+ -File ${CMAKE_SOURCE_DIR}/SignAppx.ps1
+ -PackagePath ${MSI_FILE}.appx
+ -ManifestPath AppxManifest.xml
+ -CertificatePath ${MSI_FILE}.cer
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ VERBATIM
)
else()
find_package( Threads REQUIRED )
diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp
index a83b10d91..2ec479718 100644
--- a/client/CryptoDoc.cpp
+++ b/client/CryptoDoc.cpp
@@ -171,11 +171,6 @@ quint64 CDocumentModel::fileSize(int row) const
return d->files.at(row).size;
}
-QString CDocumentModel::mime(int row) const
-{
- return FileDialog::normalized(QString::fromStdString(d->files.at(row).mime));
-}
-
void CDocumentModel::open(int row)
{
if(!d->isEncrypted())
@@ -414,6 +409,7 @@ bool CryptoDoc::encrypt(const QString &filename, const QString& label, const QBy
return false;
}
QString writer_last_error;
+ auto keepAlive = FileDialog::keepAccessAlive(d->fileName);
libcdoc::result_t result = waitFor([&] -> libcdoc::result_t {
qCDebug(CRYPTO) << "Encrypt" << d->fileName;
auto writer = std::unique_ptr(libcdoc::CDocWriter::createWriter(d->version, d->fileName.toStdString(), &d->conf, &d->crypto, &d->network));
@@ -524,6 +520,7 @@ bool CryptoDoc::saveCopy(const QString &filename)
QFileInfo dst(filename);
if(src == dst)
return true;
+ auto keepAlive = FileDialog::keepAccessAlive(filename);
std::error_code ec;
std::filesystem::copy_file(src.filesystemFilePath(), dst.filesystemFilePath(),
std::filesystem::copy_options::overwrite_existing, ec);
diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h
index 3a0c2da71..be9ca121a 100644
--- a/client/CryptoDoc.h
+++ b/client/CryptoDoc.h
@@ -89,7 +89,6 @@ class CDocumentModel final: public DocumentModel
bool addFile(const QString &file, const QString &mime = QStringLiteral("application/octet-stream")) final;
QString data(int row) const final;
quint64 fileSize(int row) const final;
- QString mime(int row) const final;
void open(int row) final;
bool removeRow(int row) final;
int rowCount() const final;
diff --git a/client/DigiDoc.cpp b/client/DigiDoc.cpp
index 8409b55b4..329894dc8 100644
--- a/client/DigiDoc.cpp
+++ b/client/DigiDoc.cpp
@@ -333,14 +333,6 @@ quint64 SDocumentModel::fileSize(int row) const
return doc->b->dataFiles().at(size_t(row))->fileSize();
}
-QString SDocumentModel::mime(int row) const
-{
- if(row >= rowCount())
- return {};
-
- return from(doc->b->dataFiles().at(size_t(row))->mediaType());
-}
-
bool SDocumentModel::removeRow(int row)
{
if(!doc->b)
@@ -368,6 +360,7 @@ QString SDocumentModel::save(int row, const QString &path) const
return {};
if(QFileInfo::exists(path))
return path;
+ auto keepAlive = FileDialog::keepAccessAlive(path);
doc->b->dataFiles().at(size_t(row))->saveAs(path.toStdString());
if(!QFileInfo::exists(path))
return {};
@@ -710,6 +703,7 @@ bool DigiDoc::saveAs(const QString &filename)
{
try
{
+ auto keepAlive = FileDialog::keepAccessAlive(filename);
return waitFor([&] {
parentContainer ? parentContainer->save(to(filename)) : b->save(to(filename));
return true;
diff --git a/client/DigiDoc.h b/client/DigiDoc.h
index 739deede2..0988e01da 100644
--- a/client/DigiDoc.h
+++ b/client/DigiDoc.h
@@ -95,7 +95,6 @@ class SDocumentModel final: public DocumentModel
bool addFile(const QString &file, const QString &mime = QStringLiteral("application/octet-stream")) 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;
diff --git a/client/DocumentModel.h b/client/DocumentModel.h
index 3dc2ae2ce..f17fef7c4 100644
--- a/client/DocumentModel.h
+++ b/client/DocumentModel.h
@@ -35,7 +35,6 @@ class DocumentModel: public QObject
virtual void copyModel(DocumentModel *model);
virtual QString data(int row) const = 0;
virtual quint64 fileSize(int row) const = 0;
- virtual QString mime(int row) const = 0;
virtual void open(int row);
virtual bool removeRow(int row) = 0;
virtual int rowCount() const = 0;
diff --git a/client/dialogs/FileDialog.cpp b/client/dialogs/FileDialog.cpp
index f47151c36..5f4d11a3c 100644
--- a/client/dialogs/FileDialog.cpp
+++ b/client/dialogs/FileDialog.cpp
@@ -104,6 +104,25 @@ bool FileDialog::fileIsWritable( const QString &filename )
return result;
}
+std::unique_ptr FileDialog::keepAccessAlive(const QString &path)
+{
+#ifdef Q_OS_MACOS
+ // Qt 6.11's macOS sandbox file engine only keeps a user-selected path's
+ // security-scoped access active while some QFile instance for that exact
+ // path exists (not merely open). Code that writes to disk without going
+ // through QFile (libdigidocpp, libcdoc, std::filesystem) bypasses that
+ // entirely, so callers doing raw I/O to a save-panel-selected path must
+ // keep the object this returns alive for the duration of that write.
+ auto file = std::make_unique(path);
+ bool opened = file->open(QFile::WriteOnly|QFile::Append);
+ Q_UNUSED(opened)
+ return file;
+#else
+ Q_UNUSED(path)
+ return {};
+#endif
+}
+
bool FileDialog::isSignedPDF(const QString &path)
{
if(!path.endsWith(QLatin1String("pdf"), Qt::CaseInsensitive))
diff --git a/client/dialogs/FileDialog.h b/client/dialogs/FileDialog.h
index cb87355ff..28e0dcf25 100644
--- a/client/dialogs/FileDialog.h
+++ b/client/dialogs/FileDialog.h
@@ -19,8 +19,11 @@
#pragma once
+#include
#include
+#include
+
class FileDialog : public QFileDialog
{
Q_OBJECT
@@ -36,6 +39,7 @@ 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 std::unique_ptr keepAccessAlive(const QString &path);
static bool isSignedPDF(const QString &path);
static void setFileZone(const QString &target, const QString &source);
static void setReadOnly(const QString &path, bool readonly = true);
diff --git a/client/libcdoc b/client/libcdoc
index 83408f9fc..4f28aa8d9 160000
--- a/client/libcdoc
+++ b/client/libcdoc
@@ -1 +1 @@
-Subproject commit 83408f9fcf4af25e81d891c34f9cc437d23f021a
+Subproject commit 4f28aa8d9f20a3dcbf7c86093496840fd34f5a33
diff --git a/extensions/windows/CMakeLists.txt b/extensions/windows/CMakeLists.txt
index bc70c082a..f4d9ba656 100644
--- a/extensions/windows/CMakeLists.txt
+++ b/extensions/windows/CMakeLists.txt
@@ -64,7 +64,7 @@ if(SIGNCERT)
execute_process(
COMMAND powershell -NoProfile -NonInteractive -Command
"(Get-ChildItem Cert:\\CurrentUser\\My | Where-Object { $_.Subject -like '*${SIGNCERT}*' } | Select-Object -First 1).Subject"
- OUTPUT_VARIABLE SIGNCERT_SUBJECT
+ OUTPUT_VARIABLE APPX_PUBLISHER
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
@@ -109,4 +109,13 @@ if(SIGNCERT)
/tr http://timestamp.digicert.com /td SHA256
"${CMAKE_BINARY_DIR}/${SHELLEXT_MSIX_FILE}"
)
+else()
+ add_custom_command(TARGET msixshellext POST_BUILD
+ COMMAND powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass
+ -File ${CMAKE_SOURCE_DIR}/SignAppx.ps1
+ -PackagePath ${CMAKE_BINARY_DIR}/${SHELLEXT_MSIX_FILE}
+ -ManifestPath ${CMAKE_BINARY_DIR}/AppxManifest-ShellExt.xml
+ -CertificatePath ${CMAKE_BINARY_DIR}/${SHELLEXT_MSIX_FILE}.cer
+ VERBATIM
+ )
endif()
diff --git a/prepare_osx_build_environment.sh b/prepare_osx_build_environment.sh
index 26c014e3e..b3d2f15cd 100755
--- a/prepare_osx_build_environment.sh
+++ b/prepare_osx_build_environment.sh
@@ -4,9 +4,9 @@
set -e
######### Versions of libraries/frameworks to be compiled
-QT_VER="6.10.3"
+QT_VER="6.11.2"
OPENSSL_VER="3.5.7"
-OPENLDAP_VER="2.6.13"
+OPENLDAP_VER="2.6.14"
REBUILD=false
BUILD_PATH=~/cmake_builds
: ${MACOSX_DEPLOYMENT_TARGET:="14.0"}
diff --git a/qdigidoc4.pfx b/qdigidoc4.pfx
deleted file mode 100644
index 6c4e0fe2a..000000000
Binary files a/qdigidoc4.pfx and /dev/null differ
diff --git a/qdigidoc4.wxs b/qdigidoc4.wxs
index abedfe22f..4e7249379 100644
--- a/qdigidoc4.wxs
+++ b/qdigidoc4.wxs
@@ -17,8 +17,7 @@
+ xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">