mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[PM-2199] Implement userkey rotation for all TDE devices (#5446)
* Implement userkey rotation v2 * Update request models * Cleanup * Update tests * Improve test * Add tests * Fix formatting * Fix test * Remove whitespace * Fix namespace * Enable nullable on models * Fix build * Add tests and enable nullable on masterpasswordunlockdatamodel * Fix test * Remove rollback * Add tests * Make masterpassword hint optional * Update user query * Add EF test * Improve test * Cleanup * Set masterpassword hint * Remove connection close * Add tests for invalid kdf types * Update test/Core.Test/KeyManagement/UserKey/RotateUserAccountKeysCommandTests.cs Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Fix formatting * Update src/Api/KeyManagement/Models/Requests/RotateAccountKeysAndDataRequestModel.cs Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update src/Api/Auth/Models/Request/Accounts/MasterPasswordUnlockDataModel.cs Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update src/Api/Auth/Models/Request/Accounts/MasterPasswordUnlockDataModel.cs Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update src/Api/KeyManagement/Models/Requests/AccountKeysRequestModel.cs Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Fix imports * Fix tests * Add poc for tde rotation * Improve rotation transaction safety * Add validator tests * Clean up validator * Add newline * Add devicekey unlock data to integration test * Fix tests * Fix tests * Remove null check * Remove null check * Fix IsTrusted returning wrong result * Add rollback * Cleanup * Address feedback * Further renames --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Api.Auth.Models.Request;
|
||||
using Bit.Api.Auth.Models.Request.Accounts;
|
||||
using Bit.Api.Models.Request;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core.Auth.Models.Api.Request;
|
||||
@ -125,7 +124,7 @@ public class DevicesController : Controller
|
||||
}
|
||||
|
||||
[HttpPost("{identifier}/retrieve-keys")]
|
||||
public async Task<ProtectedDeviceResponseModel> GetDeviceKeys(string identifier, [FromBody] SecretVerificationRequestModel model)
|
||||
public async Task<ProtectedDeviceResponseModel> GetDeviceKeys(string identifier)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
|
||||
@ -134,14 +133,7 @@ public class DevicesController : Controller
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
if (!await _userService.VerifySecretAsync(user, model.Secret))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException(string.Empty, "User verification failed.");
|
||||
}
|
||||
|
||||
var device = await _deviceRepository.GetByIdentifierAsync(identifier, user.Id);
|
||||
|
||||
if (device == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
|
@ -8,6 +8,7 @@ using Bit.Api.Tools.Models.Request;
|
||||
using Bit.Api.Vault.Models.Request;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Models.Api.Request;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -43,6 +44,7 @@ public class AccountsKeyManagementController : Controller
|
||||
_organizationUserValidator;
|
||||
private readonly IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>>
|
||||
_webauthnKeyValidator;
|
||||
private readonly IRotationValidator<IEnumerable<OtherDeviceKeysUpdateRequestModel>, IEnumerable<Device>> _deviceValidator;
|
||||
|
||||
public AccountsKeyManagementController(IUserService userService,
|
||||
IFeatureService featureService,
|
||||
@ -57,7 +59,8 @@ public class AccountsKeyManagementController : Controller
|
||||
emergencyAccessValidator,
|
||||
IRotationValidator<IEnumerable<ResetPasswordWithOrgIdRequestModel>, IReadOnlyList<OrganizationUser>>
|
||||
organizationUserValidator,
|
||||
IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>> webAuthnKeyValidator)
|
||||
IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>> webAuthnKeyValidator,
|
||||
IRotationValidator<IEnumerable<OtherDeviceKeysUpdateRequestModel>, IEnumerable<Device>> deviceValidator)
|
||||
{
|
||||
_userService = userService;
|
||||
_featureService = featureService;
|
||||
@ -71,6 +74,7 @@ public class AccountsKeyManagementController : Controller
|
||||
_emergencyAccessValidator = emergencyAccessValidator;
|
||||
_organizationUserValidator = organizationUserValidator;
|
||||
_webauthnKeyValidator = webAuthnKeyValidator;
|
||||
_deviceValidator = deviceValidator;
|
||||
}
|
||||
|
||||
[HttpPost("regenerate-keys")]
|
||||
@ -109,6 +113,7 @@ public class AccountsKeyManagementController : Controller
|
||||
EmergencyAccesses = await _emergencyAccessValidator.ValidateAsync(user, model.AccountUnlockData.EmergencyAccessUnlockData),
|
||||
OrganizationUsers = await _organizationUserValidator.ValidateAsync(user, model.AccountUnlockData.OrganizationAccountRecoveryUnlockData),
|
||||
WebAuthnKeys = await _webauthnKeyValidator.ValidateAsync(user, model.AccountUnlockData.PasskeyUnlockData),
|
||||
DeviceKeys = await _deviceValidator.ValidateAsync(user, model.AccountUnlockData.DeviceKeyUnlockData),
|
||||
|
||||
Ciphers = await _cipherValidator.ValidateAsync(user, model.AccountData.Ciphers),
|
||||
Folders = await _folderValidator.ValidateAsync(user, model.AccountData.Folders),
|
||||
|
@ -3,6 +3,7 @@ using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Api.Auth.Models.Request;
|
||||
using Bit.Api.Auth.Models.Request.Accounts;
|
||||
using Bit.Api.Auth.Models.Request.WebAuthn;
|
||||
using Bit.Core.Auth.Models.Api.Request;
|
||||
|
||||
namespace Bit.Api.KeyManagement.Models.Requests;
|
||||
|
||||
@ -13,4 +14,5 @@ public class UnlockDataRequestModel
|
||||
public required IEnumerable<EmergencyAccessWithIdRequestModel> EmergencyAccessUnlockData { get; set; }
|
||||
public required IEnumerable<ResetPasswordWithOrgIdRequestModel> OrganizationAccountRecoveryUnlockData { get; set; }
|
||||
public required IEnumerable<WebAuthnLoginRotateKeyRequestModel> PasskeyUnlockData { get; set; }
|
||||
public required IEnumerable<OtherDeviceKeysUpdateRequestModel> DeviceKeyUnlockData { get; set; }
|
||||
}
|
||||
|
53
src/Api/KeyManagement/Validators/DeviceRotationValidator.cs
Normal file
53
src/Api/KeyManagement/Validators/DeviceRotationValidator.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using Bit.Core.Auth.Models.Api.Request;
|
||||
using Bit.Core.Auth.Utilities;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Api.KeyManagement.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Device implementation for <see cref="IRotationValidator{T,R}"/>
|
||||
/// </summary>
|
||||
public class DeviceRotationValidator : IRotationValidator<IEnumerable<OtherDeviceKeysUpdateRequestModel>, IEnumerable<Device>>
|
||||
{
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="DeviceRotationValidator"/>
|
||||
/// </summary>
|
||||
/// <param name="deviceRepository">Retrieves all user <see cref="Device"/>s</param>
|
||||
public DeviceRotationValidator(IDeviceRepository deviceRepository)
|
||||
{
|
||||
_deviceRepository = deviceRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Device>> ValidateAsync(User user, IEnumerable<OtherDeviceKeysUpdateRequestModel> devices)
|
||||
{
|
||||
var result = new List<Device>();
|
||||
|
||||
var existingTrustedDevices = (await _deviceRepository.GetManyByUserIdAsync(user.Id)).Where(d => d.IsTrusted()).ToList();
|
||||
if (existingTrustedDevices.Count == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var existing in existingTrustedDevices)
|
||||
{
|
||||
var device = devices.FirstOrDefault(c => c.DeviceId == existing.Id);
|
||||
if (device == null)
|
||||
{
|
||||
throw new BadRequestException("All existing trusted devices must be included in the rotation.");
|
||||
}
|
||||
|
||||
if (device.EncryptedUserKey == null || device.EncryptedPublicKey == null)
|
||||
{
|
||||
throw new BadRequestException("Rotated encryption keys must be provided for all devices that are trusted.");
|
||||
}
|
||||
|
||||
result.Add(device.ToDevice(existing));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -31,7 +31,7 @@ using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Tools.ImportFeatures;
|
||||
using Bit.Core.Tools.ReportFeatures;
|
||||
|
||||
using Bit.Core.Auth.Models.Api.Request;
|
||||
|
||||
#if !OSS
|
||||
using Bit.Commercial.Core.SecretsManager;
|
||||
@ -168,6 +168,9 @@ public class Startup
|
||||
services
|
||||
.AddScoped<IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>>,
|
||||
WebAuthnLoginKeyRotationValidator>();
|
||||
services
|
||||
.AddScoped<IRotationValidator<IEnumerable<OtherDeviceKeysUpdateRequestModel>, IEnumerable<Device>>,
|
||||
DeviceRotationValidator>();
|
||||
|
||||
// Services
|
||||
services.AddBaseServices(globalSettings);
|
||||
|
Reference in New Issue
Block a user