https://bugs.kde.org/show_bug.cgi?id=515024

            Bug ID: 515024
           Summary: Add support for different secrets backends with
                    QtKeyChain
    Classification: Plasma
           Product: plasmashell
      Version First master
       Reported In:
          Platform: Other
                OS: Linux
            Status: REPORTED
          Severity: normal
          Priority: NOR
         Component: Networks widget
          Assignee: [email protected]
          Reporter: [email protected]
  Target Milestone: 1.0

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 |

-- 
You are receiving this mail because:
You are watching all bug changes.

Reply via email to