Watch
1
0
Fork
You've already forked vaultwarden
0
mirror of https://github.com/dani-garcia/vaultwarden.git synced 2026-11-13 01:29:19 -05:00

Compare commits

...
Author SHA1 Message Date
Daniel García
7c82fbed0c
Implement V2 key rotation support (#7766)
* Implement V2 key rotation support

* Clear user key ids left stale by key rotations

* Refuse key ids that aren't 32 lowercase hex characters, like upstream

* Save the signature key pair before the v2 account state

* Gate v2 logins on upstream's v2 encryption check

* Document the v2 encryption feature flags

* Remove the v2 upgrade feature flags

With force-upgrade-v2-encryption on, the clients loop on any v1 account that stays v1 on a vault route: the migration check runs fullSync(false), which writes lastSync, which triggers the scheduler again. No server response stops it, so don't serve the flag until the clients fix it.

pm-30144-sdk-key-rotation and pm-31050-no-logout-key-upgrade-rotation only serve that upgrade, so they go too.

* Move the v2 upgrade token migration to its own version

Diesel identifies a migration by the date in its folder name alone, and silently drops one of two folders that share it. The Custom Roles migration uses the same date.

* Refuse rotations from a replaced user key, like upstream

Both rotation endpoints accept the optional oldUserKeyId. When it and the
stored key id are both set and differ, the rotation is refused with a 409
asking the client to sync, before the account keys are checked or anything
is written.

Also correct the comment on saving the signature key pair first: by then the
vault data is already re-encrypted, so a failure there doesn't keep the
account usable.

* Update the v2 client minimum in .env.template and the key pair comments

V2 accounts need 2026.10.1, or 2026.10.2 for the desktop app, so the per-flag versions are only enough to create one. The comments on saving the signature key pair now cover the rotation of an account that is already v2.
2026-11-12 22:30:39 +01:00
Daniel García
d28c8339fb
Implement V2 registration support (#7765)
* Implement V2 registration support

* Require the client version header on every login, like upstream

Every user login (password, user API key, SSO, including a first SSO login)
now needs the Bitwarden-Client-Version header, for V1 accounts too. V2
accounts need 2026.10.1, or 2026.10.2 for the desktop app, upstream's
current minimum.

* Escape the email in the client version log line

On a first SSO login the email comes from the identity provider and isn't validated yet, so a newline in it could add a line to the log.
2026-11-12 22:30:20 +01:00
Daniel García
5ad0c6d41a
Support blob-encrypted ciphers and match upstream's key id checks (#7764) 2026-11-12 22:30:00 +01:00
44 changed files with 2011 additions and 431 deletions

View file

@ -411,6 +411,9 @@
## - "biometrics-sdk-ipc": Enable biometric unlock over the SDK IPC framework (Desktop >= 2026.9.0, Browser >= 2026.9.0)
## - "windows-native-credential-sync": Register the desktop app as a passkey provider in Windows, so passkeys in the vault can be used by other apps and browsers.
## Requires the Microsoft Store (appx) build of the desktop app. Stable releases up to and including 2026.9.1 lack the required plugin registration, so it has no effect there yet.
## - "pm-27278-v2-password-registration": Register new accounts with v2 encryption. (Web >= 2026.6.4, Desktop/Browser/CLI >= 2026.7.0, Android >= 2026.4.1, iOS >= 2026.5.1)
## - "enable-account-encryption-v2-jit-password-registration": Give SSO users who set their master password on their first login v2 encryption. (Web >= 2026.1.1, Desktop/Browser/CLI >= 2026.1.0, Android >= 2026.4.1, iOS >= 2026.6.0)
## These two flags can't be undone: a v2 account stays v2, and it can only log in from clients that are 2026.10.1 or newer (2026.10.2 for the desktop app), so the versions above are only enough to create one.
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=
## Require new device emails. When a user logs in an email is required to be sent.

View file

@ -14,9 +14,8 @@ extend-ignore-re = [
# In SMTP it's called HELO, so ignore it
"(?i)helo_name",
"Server name sent during.+HELO",
# COSE Is short for CBOR Object Signing and Encryption, ignore these specific items
"COSEKey",
"COSEAlgorithm",
# COSE Is short for CBOR Object Signing and Encryption
"(?i)cose",
# Ignore this specific string as it's valid
"Ensure they are valid OTPs",
# This word is misspelled upstream

View file

@ -107,7 +107,7 @@ serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.154"
# A safe, extensible ORM and Query builder
diesel = { version = "2.3.14", features = ["chrono", "r2d2", "numeric"] }
diesel = { version = "2.3.14", features = ["chrono", "r2d2", "numeric", "64-column-tables"] }
diesel_migrations = "2.3.2"
derive_more = { version = "2.1.1", features = [

View file

@ -0,0 +1,3 @@
-- A blob-encrypted cipher keeps all of its content in `data`, up to 500,000 characters, so the
-- 64 KiB of TEXT is too small. Like upstream, which uses LONGTEXT.
ALTER TABLE ciphers MODIFY data LONGTEXT NOT NULL;

View file

@ -0,0 +1,5 @@
DROP TABLE IF EXISTS user_signature_key_pairs;
ALTER TABLE users DROP COLUMN signed_public_key;
ALTER TABLE users DROP COLUMN security_state;
ALTER TABLE users DROP COLUMN security_version;

View file

@ -0,0 +1,14 @@
ALTER TABLE users ADD COLUMN signed_public_key TEXT;
ALTER TABLE users ADD COLUMN security_state TEXT;
ALTER TABLE users ADD COLUMN security_version INTEGER;
CREATE TABLE user_signature_key_pairs (
uuid CHAR(36) NOT NULL PRIMARY KEY,
user_uuid CHAR(36) NOT NULL UNIQUE,
signature_algorithm INTEGER NOT NULL, -- 0 = ed25519, 1 = mldsa44
signing_key TEXT NOT NULL,
verifying_key TEXT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (user_uuid) REFERENCES users (uuid) ON DELETE CASCADE
);

View file

@ -0,0 +1,4 @@
-- Key rotations before this kept the id of the old user key, so every new
-- cipher was refused from then on. Stale ids can't be told apart, so clear
-- all of them; clients record their current key's id again when it's empty.
UPDATE users SET key_id = NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE users DROP COLUMN v2_upgrade_token;
ALTER TABLE users_organizations DROP COLUMN v2_upgrade_token;

View file

@ -0,0 +1,2 @@
ALTER TABLE users ADD COLUMN v2_upgrade_token TEXT;
ALTER TABLE users_organizations ADD COLUMN v2_upgrade_token TEXT;

View file

@ -0,0 +1,3 @@
-- TEXT has no size limit here, only MySQL needed the change.
-- PostgreSQL rejects a migration that is only comments ("Received an empty query"), hence the no-op.
SELECT 1;

View file

@ -0,0 +1,5 @@
DROP TABLE IF EXISTS user_signature_key_pairs;
ALTER TABLE users DROP COLUMN signed_public_key;
ALTER TABLE users DROP COLUMN security_state;
ALTER TABLE users DROP COLUMN security_version;

View file

@ -0,0 +1,13 @@
ALTER TABLE users ADD COLUMN signed_public_key TEXT;
ALTER TABLE users ADD COLUMN security_state TEXT;
ALTER TABLE users ADD COLUMN security_version INTEGER;
CREATE TABLE user_signature_key_pairs (
uuid CHAR(36) NOT NULL PRIMARY KEY,
user_uuid CHAR(36) NOT NULL UNIQUE REFERENCES users (uuid) ON DELETE CASCADE,
signature_algorithm INTEGER NOT NULL, -- 0 = ed25519, 1 = mldsa44
signing_key TEXT NOT NULL,
verifying_key TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);

View file

@ -0,0 +1,4 @@
-- Key rotations before this kept the id of the old user key, so every new
-- cipher was refused from then on. Stale ids can't be told apart, so clear
-- all of them; clients record their current key's id again when it's empty.
UPDATE users SET key_id = NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE users DROP COLUMN v2_upgrade_token;
ALTER TABLE users_organizations DROP COLUMN v2_upgrade_token;

View file

@ -0,0 +1,2 @@
ALTER TABLE users ADD COLUMN v2_upgrade_token TEXT;
ALTER TABLE users_organizations ADD COLUMN v2_upgrade_token TEXT;

Some files were not shown because too many files have changed in this diff Show more