Bug 515024

Summary: Migrate plasma-nm to QtKeyChain
Product: [Plasma] plasmashell Reporter: Andreas Schneider <asn>
Component: Networks widgetAssignee: Plasma Bugs List <plasma-bugs-null>
Status: REPORTED ---    
Severity: normal CC: mk.mateng
Priority: NOR    
Version First Reported In: master   
Target Milestone: 1.0   
Platform: Other   
OS: Linux   
See Also: https://bugs.kde.org/show_bug.cgi?id=504312
Latest Commit: Version Fixed/Implemented In:
Sentry Crash Report:

Description Andreas Schneider 2026-01-24 18:52:07 UTC
Currently KWallet can be run as a proxy service to a secret storage backend or it can provide a secret service API and act as a secret storage backend.

See e.g. https://planet.kde.org/marco-martin-2025-04-14-towards-a-transition-from-kwallet-to-secret-service/

However this doesn't always reliable work and it would be nicer to use the Secret API directly, especially if you don't use KWallet and e.g. keepassxc instead.

I'm a C developer and not C++ so I just looked and the codebase and check how we could move from KWallet API to QtKeychain. I recently discovered `systemd-id128` and started to use it for a project. The same idea is here to create entries which are machine-specifc. See the code as pseudo-code. The text was improved with AI in case you wonder.

# Migrating plasma-nm from KWallet to QtKeychain

## References

- [QtKeychain](https://github.com/frankosterfeld/qtkeychain)
- [KDE Secret Service Transition](https://planet.kde.org/marco-martin-2025-04-14-towards-a-transition-from-kwallet-to-secret-service/)

## Current Architecture

### Files Using KWallet

| File | Purpose |
|------|---------|
| `kded/secretagent.cpp` | Core secret agent - reads/writes/deletes secrets |
| `libs/editor/uiutils.cpp` | Checks `KWallet::Wallet::isEnabled()` for defaults |
| `libs/editor/widgets/passwordfield.cpp` | Checks wallet availability for UI |
| `vpn/openconnect/openconnectwidget.cpp` | Direct KWallet access for OpenConnect |

### Current Storage Format (KWallet via Secret Service)

```
Collection: "kdewallet"
Item label: "Network Management/{uuid};802-11-wireless-security"
Secret: JSON map, e.g., {"psk": "mypassword", "key-mgmt": "wpa-psk"}
```

## New Architecture

### QtKeychain Storage Format

```
Service: "plasma-nm"
Key: "{machine-id}:{uuid};{setting};{secret-key}"
Value: Single secret value
```

Example:
```
Key: "a1b2c3d4e5f6g7h8:{uuid};802-11-wireless-security;psk"
Value: "mypassword"
```

### Machine-Specific Keys

The secret storage database may be used on different computers (e.g., synced
home directories, shared storage). To avoid key collisions between machines,
prefix keys with a machine identifier using `sd_id128_get_machine_app_specific()`.

See `man sd_id128_get_machine_app_specific`.

### Key Building Function

The `buildSecretKey()` function constructs the storage key from its components:

```cpp
QString buildSecretKey(const QString &uuid,
                       const QString &settingName,
                       const QString &secretName)
{
    // Format: "{machine-id}:{uuid};{setting};{secret}"
    return QStringLiteral("%1:%2;%3;%4")
        .arg(machineId(), uuid, settingName, secretName);
}
```

**Components:**

| Component | Example | Description |
|-----------|---------|-------------|
| `machine-id` | `a1b2c3d4e5f6g7h8` | App-specific ID from `sd_id128_get_machine_app_specific()` |
| `uuid` | `{550e8400-e29b-41d4-a716-446655440000}` | NetworkManager connection UUID |
| `settingName` | `802-11-wireless-security` | NM setting type containing secrets |
| `secretName` | `psk` | Individual secret key name |

**Resulting key:**
```
a1b2c3d4e5f6g7h8:{550e8400-e29b-41d4-a716-446655440000};802-11-wireless-security;psk
```

**Machine ID generation (Linux):**

```cpp
#include <systemd/sd-id128.h>

// Generate with: systemd-id128 new
static constexpr sd_id128_t PLASMA_NM_APP_ID = SD_ID128_MAKE(
    /* insert generated UUID bytes here */
);

QString machineId()
{
    static QString cachedId;
    if (!cachedId.isEmpty()) {
        return cachedId;
    }

    sd_id128_t appSpecificId;
    if (sd_id128_get_machine_app_specific(PLASMA_NM_APP_ID, &appSpecificId) >= 0) {
        char str[SD_ID128_STRING_MAX];
        sd_id128_to_string(appSpecificId, str);
        cachedId = QString::fromLatin1(str, 16);  // First 16 chars
    } else {
        cachedId = QSysInfo::machineHostName();   // Fallback
    }
    return cachedId;
}
```

### Manifest Keys

Since QtKeychain stores one secret per key (not maps), store a manifest
listing all secret keys for each connection/setting:

```
Key: "{machine-id}:{uuid};{setting};_keys"
Value: "psk,key-mgmt,leap-password"
```

Use the same `buildSecretKey()` with `_keys` as the secret name.

## Migration Plan

### Phase 1: Add Dependencies

- Qt6Keychain (cross-platform)
- libsystemd (Linux only, for machine ID)

### Phase 2: Create SecretStorage Abstraction

Create a `SecretStorage` class wrapping QtKeychain:
- `readSecrets(uuid, settingName)` → async callback with map
- `writeSecrets(uuid, settingName, secretsMap)` → async callback
- `deleteSecrets(uuid, settingName)` → async callback
- `isAvailable()` → static check

### Phase 3: Migrate SecretAgent

Replace `KWallet::Wallet` usage in `SecretAgent` with `SecretStorage`.

### Phase 4: Migrate Existing Secrets (Linux only)

On first run, migrate old KWallet entries to QtKeychain via D-Bus.

**Migration strategy (try in order):**

1. **Try Secret Service D-Bus** (`org.freedesktop.secrets`)
   - For new KWallet with Secret Service backend
   - Query items labeled `Network Management/*`
   - Secrets are JSON maps

2. **Fall back to KWallet D-Bus** (`org.kde.kwalletd6`)
   - For old KWallet without Secret Service
   - Open wallet, read from "Network Management" folder
   - Use `readMap()` to get secrets

**KWallet D-Bus interface:**
```
Service: org.kde.kwalletd6 (or org.kde.kwalletd5)
Path: /modules/kwalletd6
Interface: org.kde.KWallet

Methods:
- open(wallet, wId, appid) → handle
- folderList(handle, appid) → folders
- entryList(handle, folder, appid) → entries
- readMap(handle, folder, key, appid) → QByteArray (serialized map)
- close(handle, force, appid)
```

**Migration steps:**

1. Check if already migrated (settings flag)
2. Try Secret Service D-Bus, else try KWallet D-Bus
3. Read entries from "Network Management" folder
4. Parse secrets (JSON from Secret Service, or QDataStream from KWallet)
5. Write each secret to QtKeychain with new key format
6. Store manifest of keys
7. Mark migration complete

**No KF6::Wallet dependency needed** - use D-Bus directly to read old entries.

**Windows/macOS**: No migration

### Phase 5: Update UI Components

Replace `KWallet::Wallet::isEnabled()` with `QKeychain::isAvailable()` in:
- `passwordfield.cpp`
- `uiutils.cpp`

### Phase 6: Remove KWallet Dependency

Remove `KF6::Wallet` from all CMakeLists.txt files.

## File Changes

| File | Change |
|------|--------|
| `CMakeLists.txt` | Add Qt6Keychain, libsystemd; remove KF6::Wallet |
| `kded/secretstorage.{h,cpp}` | New: QtKeychain wrapper |
| `kded/secretmigration.{h,cpp}` | New: D-Bus migration (Linux) |
| `kded/secretagent.{h,cpp}` | Use SecretStorage |
| `libs/editor/uiutils.cpp` | Use `QKeychain::isAvailable()` |
| `libs/editor/widgets/passwordfield.cpp` | Use `QKeychain::isAvailable()` |
| `vpn/openconnect/openconnectwidget.cpp` | Use SecretStorage |

## Platform Behavior

| Platform | Migration | Secret Storage Backend |
|----------|-----------|------------------------|
| Linux | D-Bus query → QtKeychain | Secret Service (libsecret) |
| Windows | None | Windows Credential Store |
| macOS | None | macOS Keychain |
Comment 1 Andreas Schneider 2026-01-24 18:52:44 UTC
I forgot to mention that sshkeypass already uses QtKeyChain.
Comment 2 michaelk83 2026-01-25 09:14:21 UTC
Plasma-nm not directly supporting Secret Service (via QtKeyChain) is really the main issue in bug 504312. But 504312 focuses more on the connection problems than the migration to QtKeyChain, so I'll leave this one to focus on the migration.

A few notes on your analysis:
- I wouldn't recommend sharing the Secret Service DB between machines. It's designed for local use, and most client apps don't include a machine identifier in their secret keys. So there's not much use including it in the plasma-nm keys.
- If plasma-nm needs to store a JSON map, it can just store the JSON string as the secret value. There's no need to change that part.
- It can't use "psk" as a part of the key, since that's a selectable option that's directly related to the password. It tells plasma-nm how that password is used. So it needs to be part of the value (or a separate value in a related key).
- There's no need for a manifest. Plasma-nm knows exactly which keys it needs for each connection.
- The QtKeyChain API is already quite simple. It doesn't need an extra wrapper layer.
- There's no need to migrate data. This is taken care of by KWallet. If you're using KWallet as the Secret Service provider, it already has the data internally, and exposes it through the Secret Service API using the QtKeyChain schema (and also via the old KWallet API). If you're using it as a proxy to a different provider, then it would have already migrated the data to there (with the same schema). You just need to make sure you're reading (and writing) the correct keys.
- The fallback from Secret Service to KWallet API is handled automatically by QtKeyChain. You don't need to mess with that.
Comment 3 Andreas Schneider 2026-01-25 16:33:12 UTC
Hi michaelk83,

thanks for your comments. I'm not a KDE developer and don't know the APIs like other do. I just hoped I could start some discussion and idea collection. Thanks for chiming in.

> - I wouldn't recommend sharing the Secret Service DB between machines. It's designed for local use, and most client apps don't include a
> machine identifier in their secret keys. So there's not much use including it in the plasma-nm keys.

I'm sure this is something people do and want, especially if they use a password manager which use the cloud for password storage. Probably the best would be to implement this in a kde framework to have it available in for every app. You don't want to copy and paste your password from your data base from your desktop to your notebook and vice versa. I think it is worth thinking about it :-)
Comment 4 michaelk83 2026-01-25 16:52:07 UTC
It's really not up to KDE Frameworks. The Secret Service API was designed (a long time ago) on XDG, and how the client apps use it is entirely up to each individual app.

Technically, I suppose you _could_ share the Secret Service DB, but even then you typically _don't_ want machine-specific keys. If your apps are connecting to the same accounts on all the machines where you share the DB, then they'd need the same passwords, stored in the same keys. And if they're connecting to different accounts, they should create separate keys for those accounts (but again - that's up to each app).

In NetworkManager's case, that'd just be a separate entry for each connection, which AFAIK it already does.