Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/e2ee use hardware token secure storage #5877

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -231,20 +231,25 @@ if(BUILD_CLIENT)
find_package(Sphinx)
find_package(PdfLatex)
find_package(OpenSSL 1.1 REQUIRED )
find_package(PkgConfig REQUIRED)
pkg_check_modules(OPENSC-LIBP11 libp11 REQUIRED IMPORTED_TARGET)

find_package(ZLIB REQUIRED)
find_package(SQLite3 3.9.0 REQUIRED)
set(ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH "" CACHE PATH "Path to the driver for end-to-end encryption token")
option(CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN "Enforce use of an hardware token for end-to-end encryption" false)

if(NOT WIN32 AND NOT APPLE)
find_package(PkgConfig REQUIRED)
pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET)
find_package(ZLIB REQUIRED)
find_package(SQLite3 3.9.0 REQUIRED)

if(CLOUDPROVIDERS_FOUND)
pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET)
pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET)
pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET)
endif()
endif()
if(NOT WIN32 AND NOT APPLE)
find_package(PkgConfig REQUIRED)
pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET)

if(CLOUDPROVIDERS_FOUND)
pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET)
pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET)
pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET)
endif()
endif()
endif()

option(BUILD_WITH_WEBENGINE "BUILD_WITH_WEBENGINE" ON)
Expand Down
1 change: 0 additions & 1 deletion NEXTCLOUD.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR ${NEXTCLOUD_BACKGROUND_COLOR} CA
set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE STRING "Hex color of the text in the wizard header")
option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.(png|svg)' else the default application icon is used" ON )


#
## Windows Shell Extensions & MSI - IMPORTANT: Generate new GUIDs for custom builds with "guidgen" or "uuidgen"
#
Expand Down
4 changes: 4 additions & 0 deletions config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,8 @@

#cmakedefine WITH_WEBENGINE

#cmakedefine01 CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN

#cmakedefine ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH "@ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH@"

#endif
2 changes: 2 additions & 0 deletions resources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<file>src/gui/PredefinedStatusButton.qml</file>
<file>src/gui/BasicComboBox.qml</file>
<file>src/gui/ErrorBox.qml</file>
<file>src/gui/EncryptionTokenSelectionWindow.qml</file>
<file>src/gui/filedetails/FileActivityView.qml</file>
<file>src/gui/filedetails/FileDetailsPage.qml</file>
<file>src/gui/filedetails/FileDetailsView.qml</file>
Expand Down Expand Up @@ -46,6 +47,7 @@
<file>src/gui/tray/TalkReplyTextField.qml</file>
<file>src/gui/tray/CallNotificationDialog.qml</file>
<file>src/gui/tray/EditFileLocallyLoadingDialog.qml</file>
<file>src/gui/tray/EncryptionTokenDiscoveryDialog.qml</file>
<file>src/gui/tray/NCBusyIndicator.qml</file>
<file>src/gui/tray/NCIconWithBackgroundImage.qml</file>
<file>src/gui/tray/NCToolTip.qml</file>
Expand Down
66 changes: 35 additions & 31 deletions src/common/syncjournaldb.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*

Check notice on line 1 in src/common/syncjournaldb.cpp

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/common/syncjournaldb.cpp

File src/common/syncjournaldb.cpp does not conform to Custom style guidelines. (lines 51, 52, 53, 1001, 1003)
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This library is free software; you can redistribute it and/or
Expand All @@ -16,7 +16,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <QCryptographicHash>

Check failure on line 19 in src/common/syncjournaldb.cpp

View workflow job for this annotation

GitHub Actions / build

src/common/syncjournaldb.cpp:19:10 [clang-diagnostic-error]

'QCryptographicHash' file not found
#include <QFile>
#include <QLoggingCategory>
#include <QStringList>
Expand Down Expand Up @@ -48,8 +48,9 @@

#define GET_FILE_RECORD_QUERY \
"SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \
" ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, " \
" lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, sharedByMe, isLivePhoto, livePhotoFile" \
" ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, e2eCertificateFingerprint, " \
" lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, " \
" sharedByMe, isLivePhoto, livePhotoFile" \
" FROM metadata" \
" LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id"

Expand All @@ -67,19 +68,20 @@
rec._checksumHeader = query.baValue(9);
rec._e2eMangledName = query.baValue(10);
rec._e2eEncryptionStatus = static_cast<SyncJournalFileRecord::EncryptionStatus>(query.intValue(11));
rec._lockstate._locked = query.intValue(12) > 0;
rec._lockstate._lockOwnerDisplayName = query.stringValue(13);
rec._lockstate._lockOwnerId = query.stringValue(14);
rec._lockstate._lockOwnerType = query.int64Value(15);
rec._lockstate._lockEditorApp = query.stringValue(16);
rec._lockstate._lockTime = query.int64Value(17);
rec._lockstate._lockTimeout = query.int64Value(18);
rec._lockstate._lockToken = query.stringValue(19);
rec._isShared = query.intValue(20) > 0;
rec._lastShareStateFetchedTimestamp = query.int64Value(21);
rec._sharedByMe = query.intValue(22) > 0;
rec._isLivePhoto = query.intValue(23) > 0;
rec._livePhotoFile = query.stringValue(24);
rec._e2eCertificateFingerprint = query.baValue(12);
rec._lockstate._locked = query.intValue(13) > 0;
rec._lockstate._lockOwnerDisplayName = query.stringValue(14);
rec._lockstate._lockOwnerId = query.stringValue(15);
rec._lockstate._lockOwnerType = query.int64Value(16);
rec._lockstate._lockEditorApp = query.stringValue(17);
rec._lockstate._lockTime = query.int64Value(18);
rec._lockstate._lockTimeout = query.int64Value(19);
rec._lockstate._lockToken = query.stringValue(20);
rec._isShared = query.intValue(21) > 0;
rec._lastShareStateFetchedTimestamp = query.int64Value(22);
rec._sharedByMe = query.intValue(23) > 0;
rec._isLivePhoto = query.intValue(24) > 0;
rec._livePhotoFile = query.stringValue(25);
}

static QByteArray defaultJournalMode(const QString &dbPath)
Expand Down Expand Up @@ -783,6 +785,7 @@
addColumn(QStringLiteral("contentChecksumTypeId"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("e2eMangledName"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("isE2eEncrypted"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("e2eCertificateFingerprint"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("isShared"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("lastShareStateFetchedTimestmap"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("sharedByMe"), QStringLiteral("INTEGER"));
Expand Down Expand Up @@ -995,9 +998,9 @@

const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata "
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, "
"contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, lock, lockType, lockOwnerDisplayName, lockOwnerId, "
"contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, e2eCertificateFingerprint, lock, lockType, lockOwnerDisplayName, lockOwnerId, "
"lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, sharedByMe, isLivePhoto, livePhotoFile) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30, ?31);"),
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30, ?31, ?32);"),
_db);
if (!query) {
qCDebug(lcDb) << "database error:" << query->error();
Expand All @@ -1022,19 +1025,20 @@
query->bindValue(16, contentChecksumTypeId);
query->bindValue(17, record._e2eMangledName);
query->bindValue(18, static_cast<int>(record._e2eEncryptionStatus));
query->bindValue(19, record._lockstate._locked ? 1 : 0);
query->bindValue(20, record._lockstate._lockOwnerType);
query->bindValue(21, record._lockstate._lockOwnerDisplayName);
query->bindValue(22, record._lockstate._lockOwnerId);
query->bindValue(23, record._lockstate._lockEditorApp);
query->bindValue(24, record._lockstate._lockTime);
query->bindValue(25, record._lockstate._lockTimeout);
query->bindValue(26, record._lockstate._lockToken);
query->bindValue(27, record._isShared);
query->bindValue(28, record._lastShareStateFetchedTimestamp);
query->bindValue(29, record._sharedByMe);
query->bindValue(30, record._isLivePhoto);
query->bindValue(31, record._livePhotoFile);
query->bindValue(19, record._e2eCertificateFingerprint);
query->bindValue(20, record._lockstate._locked ? 1 : 0);
query->bindValue(21, record._lockstate._lockOwnerType);
query->bindValue(22, record._lockstate._lockOwnerDisplayName);
query->bindValue(23, record._lockstate._lockOwnerId);
query->bindValue(24, record._lockstate._lockEditorApp);
query->bindValue(25, record._lockstate._lockTime);
query->bindValue(26, record._lockstate._lockTimeout);
query->bindValue(27, record._lockstate._lockToken);
query->bindValue(28, record._isShared);
query->bindValue(29, record._lastShareStateFetchedTimestamp);
query->bindValue(30, record._sharedByMe);
query->bindValue(31, record._isLivePhoto);
query->bindValue(32, record._livePhotoFile);

if (!query->exec()) {
qCDebug(lcDb) << "database error:" << query->error();
Expand Down Expand Up @@ -3035,7 +3039,7 @@

SyncJournalDb::PinStateInterface SyncJournalDb::internalPinStates()
{
return {this};
return PinStateInterface{this};
}

void SyncJournalDb::commit(const QString &context, bool startTrans)
Expand Down
5 changes: 5 additions & 0 deletions src/common/syncjournaldb.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#ifndef SYNCJOURNALDB_H
#define SYNCJOURNALDB_H

#include <QObject>

Check failure on line 22 in src/common/syncjournaldb.h

View workflow job for this annotation

GitHub Actions / build

src/common/syncjournaldb.h:22:10 [clang-diagnostic-error]

'QObject' file not found
#include <QDateTime>
#include <QHash>
#include <QMutex>
Expand Down Expand Up @@ -304,6 +304,11 @@
*/
struct OCSYNC_EXPORT PinStateInterface
{
explicit PinStateInterface(SyncJournalDb *db)
: _db(db)
{
}

PinStateInterface(const PinStateInterface &) = delete;
PinStateInterface(PinStateInterface &&) = delete;

Expand Down
1 change: 1 addition & 0 deletions src/common/syncjournalfilerecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#ifndef SYNCJOURNALFILERECORD_H
#define SYNCJOURNALFILERECORD_H

#include <QString>

Check failure on line 22 in src/common/syncjournalfilerecord.h

View workflow job for this annotation

GitHub Actions / build

src/common/syncjournalfilerecord.h:22:10 [clang-diagnostic-error]

'QString' file not found
#include <QDateTime>

#include "csync.h"
Expand Down Expand Up @@ -84,6 +84,7 @@
QByteArray _checksumHeader;
QByteArray _e2eMangledName;
EncryptionStatus _e2eEncryptionStatus = EncryptionStatus::NotEncrypted;
QByteArray _e2eCertificateFingerprint;
SyncJournalFileLockInfo _lockstate;
bool _isShared = false;
qint64 _lastShareStateFetchedTimestamp = 0;
Expand Down
37 changes: 19 additions & 18 deletions src/csync/csync.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*

Check notice on line 1 in src/csync/csync.h

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/csync/csync.h

File src/csync/csync.h does not conform to Custom style guidelines. (lines 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 157, 159, 160)
* libcsync -- a library to sync a directory with another
*
* Copyright (c) 2008-2013 by Andreas Schneider <asn@cryptomilk.org>
Expand Down Expand Up @@ -32,7 +32,7 @@
#ifndef _CSYNC_H
#define _CSYNC_H

#include <QByteArray>

Check failure on line 35 in src/csync/csync.h

View workflow job for this annotation

GitHub Actions / build

src/csync/csync.h:35:10 [clang-diagnostic-error]

'QByteArray' file not found
#include <QVariant>
#include <QLoggingCategory>

Expand Down Expand Up @@ -140,24 +140,25 @@
* the csync state of a file.
*/
enum SyncInstructions {
CSYNC_INSTRUCTION_NONE = 0, /* Nothing to do (UPDATE|RECONCILE) */
CSYNC_INSTRUCTION_EVAL = 1 << 0, /* There was changed compared to the DB (UPDATE) */
CSYNC_INSTRUCTION_REMOVE = 1 << 1, /* The file need to be removed (RECONCILE) */
CSYNC_INSTRUCTION_RENAME = 1 << 2, /* The file need to be renamed (RECONCILE) */
CSYNC_INSTRUCTION_EVAL_RENAME = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */
CSYNC_INSTRUCTION_NEW = 1 << 3, /* The file is new compared to the db (UPDATE) */
CSYNC_INSTRUCTION_CONFLICT = 1 << 4, /* The file need to be downloaded because it is a conflict (RECONCILE) */
CSYNC_INSTRUCTION_IGNORE = 1 << 5, /* The file is ignored (UPDATE|RECONCILE) */
CSYNC_INSTRUCTION_SYNC = 1 << 6, /* The file need to be pushed to the other remote (RECONCILE) */
CSYNC_INSTRUCTION_STAT_ERROR = 1 << 7,
CSYNC_INSTRUCTION_ERROR = 1 << 8,
CSYNC_INSTRUCTION_TYPE_CHANGE = 1 << 9, /* Like NEW, but deletes the old entity first (RECONCILE)
Used when the type of something changes from directory to file
or back. */
CSYNC_INSTRUCTION_UPDATE_METADATA = 1 << 10, /* If the etag has been updated and need to be writen to the db,
but without any propagation (UPDATE|RECONCILE) */
CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */
CSYNC_INSTRUCTION_UPDATE_VFS_METADATA = 1 << 13, /* vfs item metadata are out of sync and we need to tell operating system about it */
CSYNC_INSTRUCTION_NONE = 0, /* Nothing to do (UPDATE|RECONCILE) */
CSYNC_INSTRUCTION_EVAL = 1 << 0, /* There was changed compared to the DB (UPDATE) */
CSYNC_INSTRUCTION_REMOVE = 1 << 1, /* The file need to be removed (RECONCILE) */
CSYNC_INSTRUCTION_RENAME = 1 << 2, /* The file need to be renamed (RECONCILE) */
CSYNC_INSTRUCTION_EVAL_RENAME = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */
CSYNC_INSTRUCTION_NEW = 1 << 3, /* The file is new compared to the db (UPDATE) */
CSYNC_INSTRUCTION_CONFLICT = 1 << 4, /* The file need to be downloaded because it is a conflict (RECONCILE) */
CSYNC_INSTRUCTION_IGNORE = 1 << 5, /* The file is ignored (UPDATE|RECONCILE) */
CSYNC_INSTRUCTION_SYNC = 1 << 6, /* The file need to be pushed to the other remote (RECONCILE) */
CSYNC_INSTRUCTION_STAT_ERROR = 1 << 7,
CSYNC_INSTRUCTION_ERROR = 1 << 8,
CSYNC_INSTRUCTION_TYPE_CHANGE = 1 << 9, /* Like NEW, but deletes the old entity first (RECONCILE)
Used when the type of something changes from directory to file
or back. */
CSYNC_INSTRUCTION_UPDATE_METADATA = 1 << 10, /* If the etag has been updated and need to be writen to the db,
but without any propagation (UPDATE|RECONCILE) */
CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */
CSYNC_INSTRUCTION_UPDATE_VFS_METADATA = 1 << 13, /* vfs item metadata are out of sync and we need to tell operating system about it */
CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA = 1 << 14, /* encryption metadata needs update after certificate was migrated */
};

Q_ENUM_NS(SyncInstructions)
Expand Down
1 change: 1 addition & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ set(client_SRCS
syncrunfilelog.cpp
systray.h
systray.cpp
EncryptionTokenSelectionWindow.qml
thumbnailjob.h
thumbnailjob.cpp
userinfo.h
Expand Down
146 changes: 146 additions & 0 deletions src/gui/EncryptionTokenSelectionWindow.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright (C) 2023 by Matthieu Gallien <matthieu.gallien@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License
* for more details.
*/

import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import QtQml.Models 2.15

import com.nextcloud.desktopclient 1.0
import Style 1.0

import "./tray"

ApplicationWindow {
id: encryptionKeyChooserDialog

required property var certificatesInfo
required property ClientSideEncryptionTokenSelector certificateSelector
property string selectedSerialNumber: ''

flags: Qt.Window | Qt.Dialog
visible: true
modality: Qt.ApplicationModal

width: 400
height: 600
minimumWidth: 400
minimumHeight: 600

title: qsTr('Token Encryption Key Chooser')

// TODO: Rather than setting all these palette colours manually,
// create a custom style and do it for all components globally
palette {
text: Style.ncTextColor
windowText: Style.ncTextColor
buttonText: Style.ncTextColor
brightText: Style.ncTextBrightColor
highlight: Style.lightHover
highlightedText: Style.ncTextColor
light: Style.lightHover
midlight: Style.ncSecondaryTextColor
mid: Style.darkerHover
dark: Style.menuBorder
button: Style.buttonBackgroundColor
window: Style.backgroundColor
base: Style.backgroundColor
toolTipBase: Style.backgroundColor
toolTipText: Style.ncTextColor
}

onClosing: function(close) {
Systray.destroyDialog(self);
close.accepted = true
}

ColumnLayout {
anchors.fill: parent
anchors.leftMargin: 20
anchors.rightMargin: 20
anchors.bottomMargin: 20
anchors.topMargin: 20
spacing: 15
z: 2

EnforcedPlainTextLabel {
text: qsTr("Available Keys for end-to-end Encryption:")
font.bold: true
font.pixelSize: Style.bigFontPixelSizeResolveConflictsDialog
Layout.fillWidth: true
}

ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true

clip: true

ScrollBar.horizontal.policy: ScrollBar.AlwaysOff

ListView {
id: tokensListView

currentIndex: -1

model: DelegateModel {
model: certificatesInfo

delegate: ItemDelegate {
width: tokensListView.contentItem.width

text: modelData.subject

highlighted: tokensListView.currentIndex === index

onClicked: function()
{
tokensListView.currentIndex = index
selectedSerialNumber = modelData.serialNumber
}
}
}
}
}

DialogButtonBox {
Layout.fillWidth: true

Button {
text: qsTr("Choose")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
Button {
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
}

onAccepted: function() {
Systray.destroyDialog(encryptionKeyChooserDialog)
certificateSelector.serialNumber = selectedSerialNumber
}

onRejected: function() {
Systray.destroyDialog(encryptionKeyChooserDialog)
certificateSelector.serialNumber = ''
}
}
}

Rectangle {
color: Style.backgroundColor
anchors.fill: parent
z: 1
}
}
Loading
Loading