Skip to content

Rotate database key when creating a fresh SQLCipher database #274

@erskingardner

Description

@erskingardner

Problem

MdkSqliteStorage::new() can reuse a stale keyring entry when the database file no longer exists.

The constructor pre-creates the database file and, when the file was created by this call, currently calls get_or_create_db_key(service_id, db_key_id). If the app data directory was removed but the platform secret store kept the old key, MDK creates a new SQLCipher database using the old database key.

This can happen after app uninstall/reinstall on platforms where Keychain/Keystore-backed values or restored credential files outlive the database file. It also shows up during downgrade testing after #270: the fixed build rewrites raw 32-byte keys as a 62-byte mdk-sqlite-key-v1:<base64> payload, and an older build then rejects that payload as an invalid 32-byte key.

The downgrade failure is expected, but fresh database creation should not reuse a key from a previous database lifecycle.

Affected path

  • MdkSqliteStorage::new(...)
  • Automatic key management through (service_id, db_key_id)
  • Host apps with per-account MLS databases, such as Whitenoise using mdk.db.key.<pubkey>

Fix

When precreate_secure_database_file() returns Created, treat that as a fresh database lifecycle:

  • delete any existing keyring entry for (service_id, db_key_id), ignoring NoEntry
  • generate and store a new 32-byte key
  • open the new database with that key

Keep the existing behavior for AlreadyExisted:

  • use get_db_key(...)
  • return the existing missing-key error if an encrypted database exists without a key
  • do not generate a replacement key for an existing encrypted database

Tests

Add coverage for:

  • stale keyring entry exists but DB file is absent: new() creates a DB with a new key
  • existing encrypted DB reopens with the existing key
  • existing encrypted DB with missing key still errors
  • legacy raw 32-byte key migration still works for existing databases

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions