Skip to content

[PM-31052] User V2UpgradeToken for key rotation without logout#6995

Open
mzieniukbw wants to merge 6 commits intomainfrom
km/pm-31052-upgrade-token-support
Open

[PM-31052] User V2UpgradeToken for key rotation without logout#6995
mzieniukbw wants to merge 6 commits intomainfrom
km/pm-31052-upgrade-token-support

Conversation

@mzieniukbw
Copy link
Contributor

@mzieniukbw mzieniukbw commented Feb 12, 2026

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-31052

📔 Objective

The V2 upgrade token provides a way for upgrade rotations to retain access to old encrypted data, and both the old and new user-key for a temporary period.
Regular key rotations will still logout - v2 upgrade token expected to be null.
Exposes the V2 upgrade token in sync's user decryption options response, so other clients will be able to unlock with old and new user key.

Also fixes a bug in EF UserRepo, where User's V2 fields were not set (security state, version and signed public key)

📸 Screenshots

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

Logo
Checkmarx One – Scan Summary & Detailsddbc8d66-cc50-4347-a5c6-059f2210d76e

New Issues (1)

Checkmarx found the following issues in this Pull Request

# Severity Issue Source File / Package Checkmarx Insight
1 MEDIUM CSRF /src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs: 105
detailsMethod at line 105 of /src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs gets a parameter from a user request from model. Thi...
Attack Vector

@codecov
Copy link

codecov bot commented Feb 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.33%. Comparing base (946a032) to head (14bcba3).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6995      +/-   ##
==========================================
+ Coverage   56.26%   60.33%   +4.07%     
==========================================
  Files        1983     1989       +6     
  Lines       87651    87723      +72     
  Branches     7815     7820       +5     
==========================================
+ Hits        49318    52932    +3614     
+ Misses      36503    32873    -3630     
- Partials     1830     1918      +88     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mzieniukbw mzieniukbw marked this pull request as ready for review February 12, 2026 16:37
@mzieniukbw mzieniukbw requested review from a team as code owners February 12, 2026 16:37
@sonarqubecloud
Copy link

SetV1ModelUser(model);

var originalSecurityStamp = user.SecurityStamp = Guid.NewGuid().ToString();
var wrappedKey1 = "2.key1==|data1==|hmac1==";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The V1 key that's wrapped would be "7.XXXXXXXXX" since the upgrade migration always is to a COSE key.

{
var data = new V2UpgradeTokenData
{
WrappedUserKey1 = "2.key1==|data1==|hmac1==",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: 7.xxxxxxxxxx


public class V2UpgradeTokenRequestModelTests
{
private const string _validWrappedKey1 = "2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: 7.xxxxxxxxx

data.AccountKeys.SignatureKeyPair = null;
data.AccountUnlockData.V2UpgradeToken = new V2UpgradeTokenRequestModel
{
WrappedUserKey1 = "2.key1==|data1==|hmac1==",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: 7.xxxxxxxxx

userEntity.AccountRevisionDate = user.AccountRevisionDate;
userEntity.RevisionDate = user.RevisionDate;

userEntity.SignedPublicKey = user.SignedPublicKey;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof. Makes me wonder if we should QA V2 rotations on EF separately?

}

[Theory, BitAutoData]
public async Task RotateUserAccountKeysAsync_WithExistingToken_WithNewToken_UpdatesToken(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, thinking through this scenario, I think we only ever expect one upgrade rotation, when going from V1 encryption to V2 encryption. I'm not sure when we would do a second rotation / if we would do this, then I do believe some devices would break.

If a device is on UK1, then goes to UK2, the token T12 allows devices with local method unlocking UK1 to get to UK2.

But if we now update from UK2 to UK3, then the token is T23. If a device still has unlock methods unlocking UK1, then the token would not function / break.

Should we instead only allow the token to be present if:

  • The user is a v1 user
  • AND The key-rotation contains a V2 cryptographic state
  • AND There is no upgrade token present on the user table
    OTHERWISE -> This is a regular logout key-rotation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants