Skip to content

Commit 44a9dd9

Browse files
committed
User V2UpgradeToken for key rotation without logout
1 parent 946a032 commit 44a9dd9

File tree

31 files changed

+11891
-221
lines changed

31 files changed

+11891
-221
lines changed

src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ public async Task RotateUserAccountKeysAsync([FromBody] RotateUserAccountKeysAnd
121121
OrganizationUsers = await _organizationUserValidator.ValidateAsync(user, model.AccountUnlockData.OrganizationAccountRecoveryUnlockData),
122122
WebAuthnKeys = await _webauthnKeyValidator.ValidateAsync(user, model.AccountUnlockData.PasskeyUnlockData),
123123
DeviceKeys = await _deviceValidator.ValidateAsync(user, model.AccountUnlockData.DeviceKeyUnlockData),
124+
V2UpgradeToken = model.AccountUnlockData.V2UpgradeToken?.ToData(),
124125

125126
Ciphers = await _cipherValidator.ValidateAsync(user, model.AccountData.Ciphers),
126127
Folders = await _folderValidator.ValidateAsync(user, model.AccountData.Folders),

src/Api/KeyManagement/Models/Requests/UnlockDataRequestModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ public class UnlockDataRequestModel
1414
public required IEnumerable<ResetPasswordWithOrgIdRequestModel> OrganizationAccountRecoveryUnlockData { get; set; }
1515
public required IEnumerable<WebAuthnLoginRotateKeyRequestModel> PasskeyUnlockData { get; set; }
1616
public required IEnumerable<OtherDeviceKeysUpdateRequestModel> DeviceKeyUnlockData { get; set; }
17+
public V2UpgradeTokenRequestModel? V2UpgradeToken { get; set; }
1718
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using Bit.Core.KeyManagement.Models.Data;
3+
using Bit.Core.Utilities;
4+
5+
namespace Bit.Api.KeyManagement.Models.Requests;
6+
7+
/// <summary>
8+
/// Request model for V2 upgrade token submitted during key rotation.
9+
/// Contains wrapped user keys allowing clients to unlock after V1→V2 upgrade.
10+
/// </summary>
11+
public class V2UpgradeTokenRequestModel
12+
{
13+
/// <summary>
14+
/// User Key V2 Wrapped User Key V1.
15+
/// </summary>
16+
[Required]
17+
[EncryptedString]
18+
public required string WrappedUserKey1 { get; init; }
19+
20+
/// <summary>
21+
/// User Key V1 Wrapped User Key V2.
22+
/// </summary>
23+
[Required]
24+
[EncryptedString]
25+
public required string WrappedUserKey2 { get; init; }
26+
27+
public V2UpgradeTokenData ToData()
28+
{
29+
return new V2UpgradeTokenData
30+
{
31+
WrappedUserKey1 = WrappedUserKey1,
32+
WrappedUserKey2 = WrappedUserKey2
33+
};
34+
}
35+
}

src/Api/Vault/Models/Response/SyncResponseModel.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,14 @@ public SyncResponseModel(
8787
Salt = user.Email.ToLowerInvariant()
8888
}
8989
: null,
90-
WebAuthnPrfOptions = webAuthnPrfOptions.Length > 0 ? webAuthnPrfOptions : null
90+
WebAuthnPrfOptions = webAuthnPrfOptions.Length > 0 ? webAuthnPrfOptions : null,
91+
V2UpgradeToken = V2UpgradeTokenData.FromJson(user.V2UpgradeToken) is { } data
92+
? new V2UpgradeTokenResponseModel
93+
{
94+
WrappedUserKey1 = data.WrappedUserKey1,
95+
WrappedUserKey2 = data.WrappedUserKey2
96+
}
97+
: null
9198
};
9299
}
93100

src/Core/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ public static class FeatureFlagKeys
208208
public const string EnableAccountEncryptionV2KeyConnectorRegistration = "enable-account-encryption-v2-key-connector-registration";
209209
public const string SdkKeyRotation = "pm-30144-sdk-key-rotation";
210210
public const string UnlockViaSdk = "unlock-via-sdk";
211+
public const string NoLogoutOnKeyUpgradeRotation = "pm-31050-no-logout-key-upgrade-rotation";
211212
public const string EnableAccountEncryptionV2JitPasswordRegistration = "enable-account-encryption-v2-jit-password-registration";
212213

213214
/* Mobile Team */

src/Core/Entities/User.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
105105
public DateTime? LastKeyRotationDate { get; set; }
106106
public DateTime? LastEmailChangeDate { get; set; }
107107
public bool VerifyDevices { get; set; } = true;
108+
/// <summary>
109+
/// V2 upgrade token stored as JSON containing two wrapped user keys.
110+
/// Allows clients to unlock vault after V1 to V2 key rotation without logout.
111+
/// </summary>
112+
public string? V2UpgradeToken { get; set; }
108113
// PM-28827 Uncomment below line.
109114
// public string? MasterPasswordSalt { get; set; }
110115

src/Core/Enums/PushNotificationLogOutReason.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
public enum PushNotificationLogOutReason : byte
44
{
5-
KdfChange = 0
5+
KdfChange = 0,
6+
KeyRotation = 1
67
}

src/Core/KeyManagement/Models/Api/Response/UserDecryptionResponseModel.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ public class UserDecryptionResponseModel
1515
/// </summary>
1616
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
1717
public WebAuthnPrfDecryptionOption[]? WebAuthnPrfOptions { get; set; }
18+
19+
/// <summary>
20+
/// V2 upgrade token returned when available, allowing unlock after V1→V2 upgrade.
21+
/// </summary>
22+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
23+
public V2UpgradeTokenResponseModel? V2UpgradeToken { get; set; }
1824
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Bit.Core.KeyManagement.Models.Api.Response;
2+
3+
public class V2UpgradeTokenResponseModel
4+
{
5+
public required string WrappedUserKey1 { get; set; }
6+
public required string WrappedUserKey2 { get; set; }
7+
}

src/Core/KeyManagement/Models/Data/RotateUserAccountKeysData.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class RotateUserAccountKeysData
2020
public required IReadOnlyList<OrganizationUser> OrganizationUsers { get; set; }
2121
public required IEnumerable<WebAuthnLoginRotateKeyData> WebAuthnKeys { get; set; }
2222
public required IEnumerable<Device> DeviceKeys { get; set; }
23+
public V2UpgradeTokenData? V2UpgradeToken { get; set; }
2324

2425
// User vault data encrypted by the userkey
2526
public required IEnumerable<Cipher> Ciphers { get; set; }

0 commit comments

Comments
 (0)