KWallet

kwalletfreedesktopservice.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2021 Slava Aseev <nullptrnine@basealt.ru>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7#include "kwalletfreedesktopservice.h"
8
9#include "ksecretd.h"
10#include "ksecretd_debug.h"
11#include "kwalletfreedesktopcollection.h"
12#include "kwalletfreedesktopitem.h"
13#include "kwalletfreedesktopprompt.h"
14#include "kwalletfreedesktopserviceadaptor.h"
15#include "kwalletfreedesktopsession.h"
16#include <KConfigGroup>
17#include <QWidget>
18#include <string.h>
19
20#ifdef Q_OS_WIN
21#include <windows.h>
22#endif
23
24[[maybe_unused]] int DBUS_SECRET_SERVICE_META_TYPE_REGISTER = []() {
25 qDBusRegisterMetaType<StrStrMap>();
26 qDBusRegisterMetaType<QMap<QString, QString>>();
27 qDBusRegisterMetaType<FreedesktopSecret>();
28 qDBusRegisterMetaType<FreedesktopSecretMap>();
29 qDBusRegisterMetaType<PropertiesMap>();
30 qDBusRegisterMetaType<QCA::SecureArray>();
31
32 return 0;
33}();
34
35namespace
36{
37QString mangleInvalidObjectPathChars(const QString &str)
38{
39 const auto utf8Str = str.toUtf8();
40 static constexpr char hex[] = "0123456789abcdef";
41 static_assert(sizeof(hex) == 17);
42
43 QString mangled;
44 mangled.reserve(utf8Str.size());
45
46 for (const auto &c : utf8Str) {
47 if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_') {
48 const auto cp = static_cast<quint8>(c);
49 mangled.push_back(QChar::fromLatin1('_'));
50 mangled.push_back(QChar::fromLatin1(hex[cp >> 4]));
51 mangled.push_back(QChar::fromLatin1(hex[cp & 0x0f]));
52 } else {
53 mangled.push_back(QChar::fromLatin1(c));
54 }
55 }
56
57 return mangled;
58}
59}
60
61#define LABEL_NUMBER_PREFIX "__"
62#define LABEL_NUMBER_POSTFIX "_"
63#define LABEL_NUMBER_REGEX "(^.*)" LABEL_NUMBER_PREFIX "(\\d+)" LABEL_NUMBER_POSTFIX "$"
64
65EntryLocation EntryLocation::fromUniqueLabel(const FdoUniqueLabel &uniqLabel)
66{
67 QString dir;
68 QString name = uniqLabel.label;
69
70 const int slashPos = uniqLabel.label.indexOf(QChar::fromLatin1('/'));
71 if (slashPos == -1 || slashPos == uniqLabel.label.size() - 1) {
72 dir = QStringLiteral(FDO_SECRETS_DEFAULT_DIR);
73 } else {
74 dir = uniqLabel.label.left(slashPos);
75 name = uniqLabel.label.right((uniqLabel.label.size() - dir.size()) - 1);
76 }
77
78 return EntryLocation{dir, FdoUniqueLabel::makeName(name, uniqLabel.copyId)};
79}
80
81FdoUniqueLabel EntryLocation::toUniqueLabel() const
82{
83 return FdoUniqueLabel::fromEntryLocation(*this);
84}
85
86FdoUniqueLabel FdoUniqueLabel::fromEntryLocation(const EntryLocation &entryLocation)
87{
88 const auto uniqLabel = FdoUniqueLabel::fromName(entryLocation.key);
89
90 if (entryLocation.folder == QStringLiteral(FDO_SECRETS_DEFAULT_DIR)) {
91 return uniqLabel;
92 } else {
93 return {entryLocation.folder + QChar::fromLatin1('/') + uniqLabel.label, uniqLabel.copyId};
94 }
95}
96
97FdoUniqueLabel FdoUniqueLabel::fromName(const QString &name)
98{
99 static QRegularExpression regexp(QStringLiteral(LABEL_NUMBER_REGEX));
100
101 const auto match = regexp.match(name);
102 if (match.hasMatch()) {
103 const QString strNum = match.captured(2);
104 bool ok = false;
105 const int n = strNum.toInt(&ok);
106 if (ok) {
107 return FdoUniqueLabel{match.captured(1), n};
108 }
109 }
110 return FdoUniqueLabel{name};
111}
112
113QString FdoUniqueLabel::makeName(const QString &label, int n)
114{
115 if (n == -1) {
116 return label;
117 } else {
118 return label + QStringLiteral(LABEL_NUMBER_PREFIX) + QString::number(n) + QStringLiteral(LABEL_NUMBER_POSTFIX);
119 }
120}
121
122QString FdoUniqueLabel::toName() const
123{
124 return makeName(label, copyId);
125}
126
127EntryLocation FdoUniqueLabel::toEntryLocation() const
128{
129 return EntryLocation::fromUniqueLabel(*this);
130}
131
132QString KWalletFreedesktopService::wrapToCollectionPath(const QString &itemPath)
133{
134 /* Take only /org/freedesktop/secrets/collection/collection_name */
135 return itemPath.section(QChar::fromLatin1('/'), 0, 5);
136}
137
138KWalletFreedesktopService::KWalletFreedesktopService(KSecretD *parent)
139 : QObject(nullptr)
140 , m_parent(parent)
141 , m_kwalletrc(QStringLiteral("kwalletrc"))
142{
143 (void)new KWalletFreedesktopServiceAdaptor(this);
144
145 /* register */
146 QDBusConnection::sessionBus().registerObject(QStringLiteral(FDO_SECRETS_SERVICE_OBJECT), this);
147
148 KConfig kwalletrc(QStringLiteral("kwalletrc"));
149 KConfigGroup cfgSecrets(&kwalletrc, "org.freedesktop.secrets");
150
151 if (cfgSecrets.readEntry<bool>("apiEnabled", true)) {
152 QDBusConnection::sessionBus().registerService(QStringLiteral("org.freedesktop.secrets"));
153 }
154
155 QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.secretservicecompat"));
156
157 if (!parent || !parent->isEnabled()) {
158 return;
159 }
160
161 connect(m_parent, static_cast<void (KSecretD::*)(const QString &)>(&KSecretD::walletClosed), this, &KWalletFreedesktopService::lockCollection);
162 connect(m_parent, &KSecretD::entryUpdated, this, &KWalletFreedesktopService::entryUpdated);
163 connect(m_parent, &KSecretD::entryDeleted, this, &KWalletFreedesktopService::entryDeleted);
164 connect(m_parent, &KSecretD::entryRenamed, this, &KWalletFreedesktopService::entryRenamed);
165 connect(m_parent, &KSecretD::walletDeleted, this, &KWalletFreedesktopService::walletDeleted);
166 connect(m_parent, &KSecretD::walletCreated, this, &KWalletFreedesktopService::walletCreated);
167
168 const auto walletNames = backend()->wallets();
169
170 /* Build collections */
171 for (const QString &walletName : walletNames) {
172 const auto objectPath = makeUniqueObjectPath(walletName);
173 auto collection = std::make_unique<KWalletFreedesktopCollection>(this, -1, walletName, objectPath);
174
175 m_collections.emplace(objectPath.path(), std::move(collection));
176 }
177}
178
179KWalletFreedesktopService::~KWalletFreedesktopService() = default;
180
181QList<QDBusObjectPath> KWalletFreedesktopService::collections() const
182{
183 QList<QDBusObjectPath> result;
184 result.reserve(m_collections.size());
185
186 for (const auto &collectionPair : m_collections) {
187 result.push_back(QDBusObjectPath(collectionPair.first));
188 }
189
190 return result;
191}
192
193QDBusObjectPath KWalletFreedesktopService::CreateCollection(const QVariantMap &properties, const QString &alias, QDBusObjectPath &prompt)
194{
195 prompt.setPath(QStringLiteral("/"));
196
197 const auto labelIter = properties.find(QStringLiteral("org.freedesktop.Secret.Collection.Label"));
198 if (labelIter == properties.end()) {
199 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Collection.Label property is missing"));
200 return QDBusObjectPath("/");
201 }
202 if (!labelIter->canConvert<QString>()) {
203 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Type of Collection.Label property is invalid"));
204 return QDBusObjectPath("/");
205 }
206
207 prompt = nextPromptPath();
208 auto fdoPromptPtr = std::make_unique<KWalletFreedesktopPrompt>(this, prompt, PromptType::Create, message().service());
209 auto &fdoPrompt = *m_prompts.emplace(prompt.path(), std::move(fdoPromptPtr)).first->second;
210
211 fdoPrompt.appendProperties(labelIter->toString(), QDBusObjectPath("/"), alias);
212 fdoPrompt.subscribeForWalletAsyncOpened();
213
214 return QDBusObjectPath("/");
215}
216
217FreedesktopSecretMap KWalletFreedesktopService::GetSecrets(const QList<QDBusObjectPath> &items, const QDBusObjectPath &session)
218{
219 FreedesktopSecretMap result;
220
221 for (const QDBusObjectPath &itemPath : items) {
222 const auto item = getItemByObjectPath(itemPath);
223
224 if (item) {
225 result.insert(itemPath, item->getSecret(connection(), message(), session));
226 } else {
227 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Can't find item at path ") + itemPath.path());
228 break;
229 }
230 }
231
232 return result;
233}
234
235QList<QDBusObjectPath> KWalletFreedesktopService::Lock(const QList<QDBusObjectPath> &objects, QDBusObjectPath &prompt)
236{
237 prompt = QDBusObjectPath("/");
238 QList<QDBusObjectPath> result;
239
240 /* Try find in active collections */
241 for (const QDBusObjectPath &object : objects) {
242 const QString collectionPath = wrapToCollectionPath(resolveIfAlias(object.path()));
243
244 const auto foundCollection = m_collections.find(collectionPath);
245 if (foundCollection != m_collections.end()) {
246 const int walletHandle = foundCollection->second->walletHandle();
247 const int rc = m_parent->close(walletHandle, true, FDO_APPID, message());
248
249 if (rc == 0) {
250 result.push_back(QDBusObjectPath(collectionPath));
251 } else {
252 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Can't lock object at path ") + collectionPath);
253 }
254 } else {
255 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Collection at path ") + collectionPath + QStringLiteral(" does not exist"));
256 }
257 }
258
259 return result;
260}
261
262QDBusVariant KWalletFreedesktopService::OpenSession(const QString &algorithm, const QDBusVariant &input, QDBusObjectPath &result)
263{
264 std::unique_ptr<KWalletFreedesktopSessionAlgorithm> sessionAlgorithm;
265 if (algorithm == QStringLiteral("plain")) {
266 sessionAlgorithm = createSessionAlgorithmPlain();
267 } else if (algorithm == QStringLiteral("dh-ietf1024-sha256-aes128-cbc-pkcs7")) {
268 if (!input.variant().canConvert<QByteArray>()) {
269 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Second input argument must be a byte array."));
270 return {};
271 }
272
273 sessionAlgorithm = createSessionAlgorithmDhAes(input.variant().toByteArray());
274 } else {
275 sendErrorReply(QDBusError::ErrorType::NotSupported,
276 QStringLiteral("Algorithm ") + algorithm
277 + QStringLiteral(" is not supported. (only plain and dh-ietf1024-sha256-aes128-cbc-pkcs7 are supported)"));
278 return {};
279 }
280
281 if (!sessionAlgorithm) {
282 // Creation of session algorithm failed, createSessionAlgorithm...() method should have sent an error reply.
283 return {};
284 }
285
286 const QString sessionPath = createSession(std::move(sessionAlgorithm));
287 result.setPath(sessionPath);
288 return QDBusVariant(QVariant(m_sessions[sessionPath]->negotiationOutput()));
289}
290
291QDBusObjectPath KWalletFreedesktopService::ReadAlias(const QString &name)
292{
293 QString walletName;
294
295 m_kwalletrc.reparseConfiguration();
296 if (name == QStringLiteral("default")) {
297 KConfigGroup cfg(&m_kwalletrc, "Wallet");
298 walletName = defaultWalletName(cfg);
299
300 } else {
301 KConfigGroup cfg(&m_kwalletrc, "org.freedesktop.secrets.aliases");
302 walletName = cfg.readEntry(name, QString());
303 }
304
305 if (!walletName.isEmpty()) {
306 const auto *collection = getCollectionByWalletName(walletName);
307 if (collection) {
308 return collection->fdoObjectPath();
309 }
310 }
311
312 return QDBusObjectPath("/");
313}
314
315QList<QDBusObjectPath> KWalletFreedesktopService::SearchItems(const StrStrMap &attributes, QList<QDBusObjectPath> &locked)
316{
317 QList<QDBusObjectPath> unlocked;
318
319 for (const auto &collectionPair : m_collections) {
320 auto &collection = *collectionPair.second;
321
322 if (collection.locked()) {
323 locked += collection.SearchItems(attributes);
324 } else {
325 unlocked += collection.SearchItems(attributes);
326 }
327 }
328
329 return unlocked;
330}
331
332void KWalletFreedesktopService::SetAlias(const QString &name, const QDBusObjectPath &collectionPath)
333{
334 const auto foundCollection = m_collections.find(collectionPath.path());
335 if (foundCollection == m_collections.end()) {
336 return;
337 }
338
339 auto *collection = foundCollection->second.get();
340 createCollectionAlias(name, collection);
341}
342
343QString KWalletFreedesktopService::resolveIfAlias(QString alias)
344{
345 if (alias.startsWith(QStringLiteral(FDO_ALIAS_PATH))) {
346 const auto path = ReadAlias(alias.remove(0, QStringLiteral(FDO_ALIAS_PATH).size())).path();
347 if (path != QStringLiteral("/")) {
348 alias = path;
349 } else {
350 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Alias ") + alias + QStringLiteral(" does not exist"));
351 return {};
352 }
353 }
354
355 if (!alias.startsWith(QStringLiteral(FDO_SECRETS_COLLECTION_PATH))) {
356 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Collection object path is invalid"));
357 return {};
358 }
359
360 return alias;
361}
362
363struct UnlockedObject {
364 QString walletName;
365 QDBusObjectPath objectPath;
366};
367
368QList<QDBusObjectPath> KWalletFreedesktopService::Unlock(const QList<QDBusObjectPath> &objects, QDBusObjectPath &prompt)
369{
370 prompt = QDBusObjectPath("/");
371
372 QList<QDBusObjectPath> result;
373 QList<UnlockedObject> needUnlock;
374
375 /* Try find in active collections */
376 for (const QDBusObjectPath &object : objects) {
377 const QString strPath = object.path();
378 const QString collectionPath = wrapToCollectionPath(resolveIfAlias(strPath));
379
380 const auto foundCollection = m_collections.find(collectionPath);
381 if (foundCollection != m_collections.end()) {
382 if (foundCollection->second->locked()) {
383 needUnlock.push_back({foundCollection->second->walletName(), QDBusObjectPath(strPath)});
384 } else {
385 result.push_back(QDBusObjectPath(strPath));
386 }
387 } else {
388 sendErrorReply(QDBusError::ErrorType::InvalidObjectPath, QStringLiteral("Object ") + strPath + QStringLiteral(" does not exist"));
389 return {};
390 }
391 }
392
393 if (!needUnlock.empty()) {
394 const auto promptPath = nextPromptPath();
395 auto fdoPromptPtr = std::make_unique<KWalletFreedesktopPrompt>(this, promptPath, PromptType::Open, message().service());
396 auto &fdoPrompt = *m_prompts.emplace(promptPath.path(), std::move(fdoPromptPtr)).first->second;
397
398 prompt = QDBusObjectPath(promptPath);
399
400 for (const auto &[walletName, objectPath] : std::as_const(needUnlock)) {
401 fdoPrompt.appendProperties(walletName, objectPath);
402 }
403
404 fdoPrompt.subscribeForWalletAsyncOpened();
405 }
406 return result;
407}
408
409std::unique_ptr<KWalletFreedesktopSessionAlgorithm> KWalletFreedesktopService::createSessionAlgorithmPlain() const
410{
411 return std::make_unique<KWalletFreedesktopSessionAlgorithmPlain>();
412}
413
414std::unique_ptr<KWalletFreedesktopSessionAlgorithm> KWalletFreedesktopService::createSessionAlgorithmDhAes(const QByteArray &clientKey) const
415{
416 if (clientKey.size() < FDO_DH_PUBLIC_KEY_SIZE) {
417 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Client public key size is invalid"));
418 return nullptr;
419 }
420
421 QCA::KeyGenerator keygen;
422 const auto dlGroup = QCA::DLGroup(keygen.createDLGroup(QCA::IETF_1024));
423 if (dlGroup.isNull()) {
424 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("createDLGroup failed: maybe libqca-ossl is missing"));
425 return nullptr;
426 }
427
428 auto privateKey = QCA::PrivateKey(keygen.createDH(dlGroup));
429 const auto publicKey = QCA::PublicKey(privateKey);
430 const auto clientPublicKey = QCA::DHPublicKey(dlGroup, QCA::BigInteger(QCA::SecureArray(clientKey)));
431 const auto commonSecret = privateKey.deriveKey(clientPublicKey);
432 const auto symmetricKey = QCA::HKDF().makeKey(commonSecret, {}, {}, FDO_SECRETS_CIPHER_KEY_SIZE);
433
434 return std::make_unique<KWalletFreedesktopSessionAlgorithmDhAes>(publicKey, symmetricKey);
435}
436
437QString KWalletFreedesktopService::createSession(std::unique_ptr<KWalletFreedesktopSessionAlgorithm> algorithm)
438{
439 const QString sessionPath = QStringLiteral(FDO_SECRETS_SESSION_PATH) + QString::number(++m_session_counter);
440 auto session = std::make_unique<KWalletFreedesktopSession>(this, std::move(algorithm), sessionPath, connection(), message());
441 m_sessions[sessionPath] = std::move(session);
442 return sessionPath;
443}
444
445QString KWalletFreedesktopService::defaultWalletName(KConfigGroup &cfg)
446{
447 auto walletName = cfg.readEntry("Default Wallet", "kdewallet");
448 if (walletName.isEmpty()) {
449 walletName = QStringLiteral("kdewallet");
450 }
451 return walletName;
452}
453
454QDBusObjectPath KWalletFreedesktopService::promptUnlockCollection(const QString &walletName, int handle)
455{
456 auto *collection = getCollectionByWalletName(walletName);
457 QString objectPath;
458
459 if (collection) {
460 collection->onWalletChangeState(handle);
461 onCollectionChanged(collection->fdoObjectPath());
462 objectPath = collection->fdoObjectPath().path();
463 } else {
464 const auto path = makeUniqueObjectPath(walletName);
465 objectPath = path.path();
466 auto newCollection = std::make_unique<KWalletFreedesktopCollection>(this, handle, walletName, path);
467 m_collections[objectPath] = std::move(newCollection);
468 onCollectionCreated(path);
469 }
470
471 return QDBusObjectPath(objectPath);
472}
473
474/* Triggered after KSecretD::walletClosed signal */
475void KWalletFreedesktopService::lockCollection(const QString &name)
476{
477 auto *collection = getCollectionByWalletName(name);
478 if (collection) {
479 collection->onWalletChangeState(-1);
480 onCollectionChanged(collection->fdoObjectPath());
481 }
482}
483
484/* Triggered after KSecretD::entryUpdated signal */
485void KWalletFreedesktopService::entryUpdated(const QString &walletName, const QString &folder, const QString &entryName)
486{
487 auto *collection = getCollectionByWalletName(walletName);
488 if (!collection) {
489 return;
490 }
491
492 const EntryLocation entryLocation{folder, entryName};
493 const auto *item = collection->findItemByEntryLocation(entryLocation);
494 if (item) {
495 collection->onItemChanged(item->fdoObjectPath());
496 } else {
497 auto objectPath = collection->nextItemPath();
498 collection->pushNewItem(entryLocation.toUniqueLabel(), objectPath);
499 collection->onItemCreated(objectPath);
500 }
501}
502
503/* Triggered after KSecretD::entryDeleted signal */
504void KWalletFreedesktopService::entryDeleted(const QString &walletName, const QString &folder, const QString &entryName)
505{
506 auto *collection = getCollectionByWalletName(walletName);
507 if (!collection) {
508 return;
509 }
510
511 const auto *item = collection->findItemByEntryLocation({folder, entryName});
512 if (item) {
513 collection->onItemDeleted(item->fdoObjectPath());
514 }
515}
516
517/* Triggered after KSecretD::entryRenamed signal */
518void KWalletFreedesktopService::entryRenamed(const QString &walletName, const QString &folder, const QString &oldName, const QString &newName)
519{
520 auto *collection = getCollectionByWalletName(walletName);
521 if (!collection) {
522 return;
523 }
524
525 const EntryLocation oldLocation{folder, oldName};
526 const EntryLocation newLocation{folder, newName};
527
528 auto *item = collection->findItemByEntryLocation(oldLocation);
529 if (!item) {
530 /* Warn if label not found and not yet renamed */
531 if (!collection->findItemByEntryLocation(newLocation)) {
532 qCWarning(KSECRETD_LOG) << "Cannot rename secret service label:" << FdoUniqueLabel::fromEntryLocation(oldLocation).label;
533 }
534 return;
535 }
536
537 if (item) {
538 collection->itemAttributes().renameLabel(oldLocation, newLocation);
539 item->uniqueLabel(newLocation.toUniqueLabel());
540 collection->onItemChanged(item->fdoObjectPath());
541 }
542}
543
544/* Triggered after KSecretD::walletDeleted signal */
545void KWalletFreedesktopService::walletDeleted(const QString &walletName)
546{
547 auto *collection = getCollectionByWalletName(walletName);
548 if (collection) {
549 collection->Delete();
550 }
551}
552
553/* Triggered after KSecretD::walletCreated signal */
554void KWalletFreedesktopService::walletCreated(const QString &walletName)
555{
556 const auto objectPath = makeUniqueObjectPath(walletName);
557 auto collection = std::make_unique<KWalletFreedesktopCollection>(this, -1, walletName, objectPath);
558 m_collections.emplace(objectPath.path(), std::move(collection));
559 onCollectionCreated(objectPath);
560}
561
562bool KWalletFreedesktopService::desecret(const QDBusMessage &message, FreedesktopSecret &secret)
563{
564 const auto foundSession = m_sessions.find(secret.session.path());
565
566 if (foundSession != m_sessions.end()) {
567 const KWalletFreedesktopSession &session = *foundSession->second;
568 return session.decrypt(message, secret);
569 }
570
571 return false;
572}
573
574bool KWalletFreedesktopService::ensecret(const QDBusMessage &message, FreedesktopSecret &secret)
575{
576 const auto foundSession = m_sessions.find(secret.session.path());
577
578 if (foundSession != m_sessions.end()) {
579 const KWalletFreedesktopSession &session = *foundSession->second;
580 return session.encrypt(message, secret);
581 }
582
583 return false;
584}
585
586QDBusObjectPath KWalletFreedesktopService::nextPromptPath()
587{
588 static uint64_t id = 0;
589 return QDBusObjectPath(QStringLiteral(FDO_SECRET_SERVICE_PROMPT_PATH) + QStringLiteral("p") + QString::number(id++));
590}
591
592QDBusArgument &operator<<(QDBusArgument &arg, const FreedesktopSecret &secret)
593{
594 arg.beginStructure();
595 arg << secret.session;
596 arg << secret.parameters;
597 arg << secret.value;
598 arg << secret.mimeType;
599 arg.endStructure();
600 return arg;
601}
602
603const QDBusArgument &operator>>(const QDBusArgument &arg, FreedesktopSecret &secret)
604{
605 arg.beginStructure();
606 arg >> secret.session;
607 arg >> secret.parameters;
608 arg >> secret.value;
609 arg >> secret.mimeType;
610 arg.endStructure();
611 return arg;
612}
613
614QDataStream &operator<<(QDataStream &stream, const QCA::SecureArray &value)
615{
616 QByteArray bytes = value.toByteArray();
617 stream << bytes;
618 explicit_zero_mem(bytes.data(), bytes.size());
619 return stream;
620}
621
622QDataStream &operator>>(QDataStream &stream, QCA::SecureArray &value)
623{
624 QByteArray bytes;
625 stream >> bytes;
626 value = QCA::SecureArray(bytes);
627 explicit_zero_mem(bytes.data(), bytes.size());
628 return stream;
629}
630
631QDBusArgument &operator<<(QDBusArgument &arg, const QCA::SecureArray &value)
632{
633 QByteArray bytes = value.toByteArray();
634 arg << bytes;
635 explicit_zero_mem(bytes.data(), bytes.size());
636 return arg;
637}
638
639const QDBusArgument &operator>>(const QDBusArgument &arg, QCA::SecureArray &buf)
640{
641 QByteArray byteArray;
642 arg >> byteArray;
643 buf = QCA::SecureArray(byteArray);
644 explicit_zero_mem(byteArray.data(), byteArray.size());
645 return arg;
646}
647
648KSecretD *KWalletFreedesktopService::backend() const
649{
650 return m_parent;
651}
652
653QDBusObjectPath KWalletFreedesktopService::fdoObjectPath() const
654{
655 return QDBusObjectPath(FDO_SECRETS_SERVICE_OBJECT);
656}
657
658KWalletFreedesktopItem *KWalletFreedesktopService::getItemByObjectPath(const QDBusObjectPath &path) const
659{
660 const auto str = path.path();
661 if (!str.startsWith(QStringLiteral(FDO_SECRETS_COLLECTION_PATH))) {
662 return nullptr;
663 }
664
665 const QString collectionPath = wrapToCollectionPath(str);
666 const auto collectionPos = m_collections.find(collectionPath);
667 if (collectionPos == m_collections.end()) {
668 return nullptr;
669 }
670
671 const auto &collection = collectionPos->second;
672 return collection->getItemByObjectPath(str);
673}
674
675KWalletFreedesktopPrompt *KWalletFreedesktopService::getPromptByObjectPath(const QDBusObjectPath &path) const
676{
677 const auto foundPrompt = m_prompts.find(path.path());
678 if (foundPrompt != m_prompts.end()) {
679 return foundPrompt->second.get();
680 } else {
681 return nullptr;
682 }
683}
684
685FdoUniqueLabel KWalletFreedesktopService::makeUniqueCollectionLabel(const QString &label)
686{
687 int n = -1;
688 auto walletName = label;
689 const QStringList wallets = backend()->wallets();
690
691 while (wallets.contains(walletName)) {
692 walletName = FdoUniqueLabel::makeName(label, ++n);
693 }
694
695 return {label, n};
696}
697
698QString KWalletFreedesktopService::makeUniqueWalletName(const QString &labelPrefix)
699{
700 return makeUniqueCollectionLabel(labelPrefix).toName();
701}
702
703QDBusObjectPath KWalletFreedesktopService::makeUniqueObjectPath(const QString &walletName) const
704{
705 auto mangled = mangleInvalidObjectPathChars(walletName);
706 mangled.insert(0, QStringLiteral(FDO_SECRETS_COLLECTION_PATH));
707
708 QString result = mangled;
709 int postfix = 0;
710 while (m_collections.count(result)) {
711 result = mangled + QString::number(postfix++);
712 }
713
714 return QDBusObjectPath(result);
715}
716
717QStringList KWalletFreedesktopService::readAliasesFor(const QString &walletName)
718{
719 m_kwalletrc.reparseConfiguration();
720 KConfigGroup cfg(&m_kwalletrc, "org.freedesktop.secrets.aliases");
721 const auto map = cfg.entryMap();
722 QStringList aliases;
723
724 for (auto i = map.begin(); i != map.end(); ++i) {
725 if (i.value() == walletName) {
726 aliases.push_back(i.key());
727 }
728 }
729
730 KConfigGroup cfgWallet(&m_kwalletrc, "Wallet");
731 if (defaultWalletName(cfgWallet) == walletName) {
732 aliases.push_back(QStringLiteral("default"));
733 }
734
735 return aliases;
736}
737
738void KWalletFreedesktopService::updateCollectionAlias(const QString &alias, const QString &walletName)
739{
740 QString sectName = QStringLiteral("org.freedesktop.secrets.aliases");
741 QString sectKey = alias;
742
743 if (alias == QStringLiteral("default")) {
744 sectName = QStringLiteral("Wallet");
745 sectKey = QStringLiteral("Default Wallet");
746 }
747
748 KConfigGroup cfg(&m_kwalletrc, sectName);
749 cfg.writeEntry(sectKey, walletName);
750 m_kwalletrc.sync();
751}
752
753void KWalletFreedesktopService::createCollectionAlias(const QString &alias, const QString &walletName)
754{
755 QString sectName = QStringLiteral("org.freedesktop.secrets.aliases");
756 QString sectKey = alias;
757
758 if (alias == QStringLiteral("default")) {
759 sectName = QStringLiteral("Wallet");
760 sectKey = QStringLiteral("Default Wallet");
761 }
762
763 m_kwalletrc.reparseConfiguration();
764 KConfigGroup cfg(&m_kwalletrc, sectName);
765
766 const QString prevWalletName = cfg.readEntry(sectKey, QString());
767 if (!prevWalletName.isEmpty()) {
768 const auto *prevCollection = getCollectionByWalletName(prevWalletName);
769 if (prevCollection) {
770 QDBusConnection::sessionBus().unregisterObject(QStringLiteral(FDO_ALIAS_PATH) + alias);
771 }
772 }
773
774 cfg.writeEntry(sectKey, walletName);
775 m_kwalletrc.sync();
776
777 auto *collection = getCollectionByWalletName(walletName);
778 if (collection) {
779 QDBusConnection::sessionBus().registerObject(QStringLiteral(FDO_ALIAS_PATH) + alias, collection);
780 }
781}
782
783void KWalletFreedesktopService::createCollectionAlias(const QString &alias, KWalletFreedesktopCollection *collection)
784{
785 QString sectName = QStringLiteral("org.freedesktop.secrets.aliases");
786 QString sectKey = alias;
787
788 if (alias == QStringLiteral("default")) {
789 sectName = QStringLiteral("Wallet");
790 sectKey = QStringLiteral("Default Wallet");
791 }
792
793 m_kwalletrc.reparseConfiguration();
794 KConfigGroup cfg(&m_kwalletrc, sectName);
795
796 const QString prevWalletName = cfg.readEntry(sectKey, "");
797 if (!prevWalletName.isEmpty()) {
798 const auto *prevCollection = getCollectionByWalletName(prevWalletName);
799 if (prevCollection) {
800 QDBusConnection::sessionBus().unregisterObject(QStringLiteral(FDO_ALIAS_PATH) + alias);
801 }
802 }
803
804 cfg.writeEntry(sectKey, collection->walletName());
805 m_kwalletrc.sync();
806 QDBusConnection::sessionBus().registerObject(QStringLiteral(FDO_ALIAS_PATH) + alias, collection);
807}
808
809void KWalletFreedesktopService::removeAlias(const QString &alias)
810{
811 if (alias == QStringLiteral("default")) {
812 return;
813 }
814
815 KConfigGroup cfg(&m_kwalletrc, "org.freedesktop.secrets.aliases");
816 cfg.deleteEntry(alias);
817 m_kwalletrc.sync();
818 QDBusConnection::sessionBus().unregisterObject(QStringLiteral(FDO_ALIAS_PATH) + alias);
819}
820
821KWalletFreedesktopCollection *KWalletFreedesktopService::getCollectionByWalletName(const QString &walletName) const
822{
823 for (const auto &collectionKeyValue : m_collections) {
824 const auto collection = collectionKeyValue.second.get();
825 if (collection->walletName() == walletName) {
826 return collection;
827 }
828 }
829
830 return nullptr;
831}
832
833void KWalletFreedesktopService::deletePrompt(const QString &objectPath)
834{
835 const auto foundPrompt = m_prompts.find(objectPath);
836 if (foundPrompt == m_prompts.end()) {
837 return;
838 }
839
840 /* This can be called in the context of the prompt that is currently being
841 * deleted. Therefore, we should schedule deletion on the next event loop iteration
842 */
843 foundPrompt->second->deleteLater();
844 foundPrompt->second.release();
845 m_prompts.erase(foundPrompt);
846}
847
848void KWalletFreedesktopService::deleteSession(const QString &objectPath)
849{
850 const auto foundSession = m_sessions.find(objectPath);
851 if (foundSession == m_sessions.end()) {
852 return;
853 }
854
855 /* This can be called in the context of the session that is currently being
856 * deleted. Therefore, we should schedule deletion on the next event loop iteration
857 */
858 foundSession->second->deleteLater();
859 foundSession->second.release();
860 m_sessions.erase(foundSession);
861}
862
863void KWalletFreedesktopService::onCollectionCreated(const QDBusObjectPath &path)
864{
865 Q_EMIT CollectionCreated(path);
866
867 QVariantMap props;
868 props.insert(QStringLiteral("Collections"), QVariant::fromValue(collections()));
869 onPropertiesChanged(props);
870}
871
872void KWalletFreedesktopService::onCollectionChanged(const QDBusObjectPath &path)
873{
874 Q_EMIT CollectionChanged(path);
875}
876
877void KWalletFreedesktopService::onCollectionDeleted(const QDBusObjectPath &path)
878{
879 const auto collectionMapPos = m_collections.find(path.path());
880 if (collectionMapPos == m_collections.end()) {
881 return;
882 }
883 auto &collectionPair = *collectionMapPos;
884 collectionPair.second->itemAttributes().deleteFile();
885
886 /* This can be called in the context of the collection that is currently being
887 * deleted. Therefore, we should schedule deletion on the next event loop iteration
888 */
889 collectionPair.second->deleteLater();
890 collectionPair.second.release();
891 m_collections.erase(collectionMapPos);
892
893 Q_EMIT CollectionDeleted(path);
894
895 QVariantMap props;
896 props[QStringLiteral("Collections")] = QVariant::fromValue(collections());
897 onPropertiesChanged(props);
898}
899
900void KWalletFreedesktopService::onPropertiesChanged(const QVariantMap &properties)
901{
902 auto msg = QDBusMessage::createSignal(fdoObjectPath().path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"));
903 auto args = QVariantList();
904 args << QStringLiteral("org.freedesktop.Secret.Service") << properties << QStringList();
905 msg.setArguments(args);
907}
908
909QDataStream &operator<<(QDataStream &stream, const QDBusObjectPath &value)
910{
911 return stream << value.path();
912}
913
914QDataStream &operator>>(QDataStream &stream, QDBusObjectPath &value)
915{
916 QString str;
917 stream >> str;
918 value = QDBusObjectPath(str);
919 return stream;
920}
921
922const QDBusArgument &operator>>(const QDBusArgument &arg, PropertiesMap &value)
923{
924 arg.beginMap();
925 value.map.clear();
926
927 while (!arg.atEnd()) {
928 arg.beginMapEntry();
929 QString key;
930 QVariant val;
931 arg >> key >> val;
932
933 /* For org.freedesktop.Secret.Item.Attributes */
934 if (val.canConvert<QDBusArgument>()) {
935 auto metaArg = val.value<QDBusArgument>();
936 StrStrMap metaMap;
937 metaArg >> metaMap;
938 val = QVariant::fromValue(metaMap);
939 }
940 value.map.insert(key, val);
941
942 arg.endMapEntry();
943 }
944 arg.endMap();
945
946 return arg;
947}
948
949QDBusArgument &operator<<(QDBusArgument &arg, const PropertiesMap &value)
950{
951 arg << value.map;
952 return arg;
953}
954
955void explicit_zero_mem(void *data, size_t size)
956{
957#if defined(KSECRETD_HAVE_EXPLICIT_BZERO)
958 explicit_bzero(data, size);
959#elif defined(KSECRETD_HAVE_RTLSECUREZEROMEMORY)
960 RtlSecureZeroMemory(data, size);
961#else
962 auto p = reinterpret_cast<volatile char *>(data);
963 for (size_t i = 0; i < size; ++i) {
964 p[i] = 0;
965 }
966#endif
967}
968
969#include "moc_kwalletfreedesktopservice.cpp"
void deleteEntry(const char *key, WriteConfigFlags pFlags=Normal)
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
QMap< QString, QString > entryMap() const
PrivateKey createDH(const DLGroup &domain, const QString &provider=QString())
DLGroup createDLGroup(QCA::DLGroupSet set, const QString &provider=QString())
QByteArray toByteArray() const
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
KGuiItem properties()
QString label(StandardShortcut id)
char * data()
qsizetype size() const const
QChar fromLatin1(char c)
bool atEnd() const const
void beginMap(QMetaType keyMetaType, QMetaType valueMetaType)
void beginMapEntry()
void beginStructure()
void endMapEntry()
void endStructure()
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
bool registerService(const QString &serviceName)
bool send(const QDBusMessage &message) const const
QDBusConnection sessionBus()
void unregisterObject(const QString &path, UnregisterMode mode)
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
QString path() const const
void setPath(const QString &path)
QVariant variant() const const
bool empty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
iterator insert(const Key &key, const T &value)
Q_EMITQ_EMIT
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
QString left(qsizetype n) const const
QString number(double n, char format, int precision)
void push_back(QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
QString right(qsizetype n) const const
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QTextStream & hex(QTextStream &stream)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool canConvert() const const
QVariant fromValue(T &&value)
QByteArray toByteArray() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:53:00 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.