mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 23:52:50 -05:00
[PM-13362] Add private key regeneration endpoint (#4929)
* Add new RegenerateUserAsymmetricKeysCommand * add new command tests * Add regen controller * Add regen controller tests * add feature flag * Add push notification to sync new asymmetric keys to other devices
This commit is contained in:
@ -0,0 +1,50 @@
|
||||
#nullable enable
|
||||
using Bit.Api.KeyManagement.Models.Requests;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Commands.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.KeyManagement.Controllers;
|
||||
|
||||
[Route("accounts/key-management")]
|
||||
[Authorize("Application")]
|
||||
public class AccountsKeyManagementController : Controller
|
||||
{
|
||||
private readonly IEmergencyAccessRepository _emergencyAccessRepository;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IRegenerateUserAsymmetricKeysCommand _regenerateUserAsymmetricKeysCommand;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public AccountsKeyManagementController(IUserService userService,
|
||||
IFeatureService featureService,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IEmergencyAccessRepository emergencyAccessRepository,
|
||||
IRegenerateUserAsymmetricKeysCommand regenerateUserAsymmetricKeysCommand)
|
||||
{
|
||||
_userService = userService;
|
||||
_featureService = featureService;
|
||||
_regenerateUserAsymmetricKeysCommand = regenerateUserAsymmetricKeysCommand;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_emergencyAccessRepository = emergencyAccessRepository;
|
||||
}
|
||||
|
||||
[HttpPost("regenerate-keys")]
|
||||
public async Task RegenerateKeysAsync([FromBody] KeyRegenerationRequestModel request)
|
||||
{
|
||||
if (!_featureService.IsEnabled(FeatureFlagKeys.PrivateKeyRegeneration))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var user = await _userService.GetUserByPrincipalAsync(User) ?? throw new UnauthorizedAccessException();
|
||||
var usersOrganizationAccounts = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
||||
var designatedEmergencyAccess = await _emergencyAccessRepository.GetManyDetailsByGranteeIdAsync(user.Id);
|
||||
await _regenerateUserAsymmetricKeysCommand.RegenerateKeysAsync(request.ToUserAsymmetricKeys(user.Id),
|
||||
usersOrganizationAccounts, designatedEmergencyAccess);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
#nullable enable
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Api.KeyManagement.Models.Requests;
|
||||
|
||||
public class KeyRegenerationRequestModel
|
||||
{
|
||||
public required string UserPublicKey { get; set; }
|
||||
|
||||
[EncryptedString]
|
||||
public required string UserKeyEncryptedUserPrivateKey { get; set; }
|
||||
|
||||
public UserAsymmetricKeys ToUserAsymmetricKeys(Guid userId)
|
||||
{
|
||||
return new UserAsymmetricKeys
|
||||
{
|
||||
UserId = userId,
|
||||
PublicKey = UserPublicKey,
|
||||
UserKeyEncryptedPrivateKey = UserKeyEncryptedUserPrivateKey,
|
||||
};
|
||||
}
|
||||
}
|
@ -160,6 +160,7 @@ public static class FeatureFlagKeys
|
||||
public const string PM12443RemovePagingLogic = "pm-12443-remove-paging-logic";
|
||||
public const string SelfHostLicenseRefactor = "pm-11516-self-host-license-refactor";
|
||||
public const string PromoteProviderServiceUserTool = "pm-15128-promote-provider-service-user-tool";
|
||||
public const string PrivateKeyRegeneration = "pm-12241-private-key-regeneration";
|
||||
|
||||
public static List<string> GetAllKeys()
|
||||
{
|
||||
|
@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
|
||||
namespace Bit.Core.KeyManagement.Commands.Interfaces;
|
||||
|
||||
public interface IRegenerateUserAsymmetricKeysCommand
|
||||
{
|
||||
Task RegenerateKeysAsync(UserAsymmetricKeys userAsymmetricKeys,
|
||||
ICollection<OrganizationUser> usersOrganizationAccounts,
|
||||
ICollection<EmergencyAccessDetails> designatedEmergencyAccess);
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Commands.Interfaces;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.KeyManagement.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.KeyManagement.Commands;
|
||||
|
||||
public class RegenerateUserAsymmetricKeysCommand : IRegenerateUserAsymmetricKeysCommand
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ILogger<RegenerateUserAsymmetricKeysCommand> _logger;
|
||||
private readonly IUserAsymmetricKeysRepository _userAsymmetricKeysRepository;
|
||||
private readonly IPushNotificationService _pushService;
|
||||
|
||||
public RegenerateUserAsymmetricKeysCommand(
|
||||
ICurrentContext currentContext,
|
||||
IUserAsymmetricKeysRepository userAsymmetricKeysRepository,
|
||||
IPushNotificationService pushService,
|
||||
ILogger<RegenerateUserAsymmetricKeysCommand> logger)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_logger = logger;
|
||||
_userAsymmetricKeysRepository = userAsymmetricKeysRepository;
|
||||
_pushService = pushService;
|
||||
}
|
||||
|
||||
public async Task RegenerateKeysAsync(UserAsymmetricKeys userAsymmetricKeys,
|
||||
ICollection<OrganizationUser> usersOrganizationAccounts,
|
||||
ICollection<EmergencyAccessDetails> designatedEmergencyAccess)
|
||||
{
|
||||
var userId = _currentContext.UserId;
|
||||
if (!userId.HasValue ||
|
||||
userAsymmetricKeys.UserId != userId.Value ||
|
||||
usersOrganizationAccounts.Any(ou => ou.UserId != userId) ||
|
||||
designatedEmergencyAccess.Any(dea => dea.GranteeId != userId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var inOrganizations = usersOrganizationAccounts.Any(ou =>
|
||||
ou.Status is OrganizationUserStatusType.Confirmed or OrganizationUserStatusType.Revoked);
|
||||
var hasDesignatedEmergencyAccess = designatedEmergencyAccess.Any(x =>
|
||||
x.Status is EmergencyAccessStatusType.Confirmed or EmergencyAccessStatusType.RecoveryApproved
|
||||
or EmergencyAccessStatusType.RecoveryInitiated);
|
||||
|
||||
_logger.LogInformation(
|
||||
"User asymmetric keys regeneration requested. UserId: {userId} OrganizationMembership: {inOrganizations} DesignatedEmergencyAccess: {hasDesignatedEmergencyAccess} DeviceType: {deviceType}",
|
||||
userAsymmetricKeys.UserId, inOrganizations, hasDesignatedEmergencyAccess, _currentContext.DeviceType);
|
||||
|
||||
// For now, don't regenerate asymmetric keys for user's with organization membership and designated emergency access.
|
||||
if (inOrganizations || hasDesignatedEmergencyAccess)
|
||||
{
|
||||
throw new BadRequestException("Key regeneration not supported for this user.");
|
||||
}
|
||||
|
||||
await _userAsymmetricKeysRepository.RegenerateUserAsymmetricKeysAsync(userAsymmetricKeys);
|
||||
_logger.LogInformation(
|
||||
"User's asymmetric keys regenerated. UserId: {userId} OrganizationMembership: {inOrganizations} DesignatedEmergencyAccess: {hasDesignatedEmergencyAccess} DeviceType: {deviceType}",
|
||||
userAsymmetricKeys.UserId, inOrganizations, hasDesignatedEmergencyAccess, _currentContext.DeviceType);
|
||||
|
||||
await _pushService.PushSyncSettingsAsync(userId.Value);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using Bit.Core.KeyManagement.Commands;
|
||||
using Bit.Core.KeyManagement.Commands.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.Core.KeyManagement;
|
||||
|
||||
public static class KeyManagementServiceCollectionExtensions
|
||||
{
|
||||
public static void AddKeyManagementServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddKeyManagementCommands();
|
||||
}
|
||||
|
||||
private static void AddKeyManagementCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IRegenerateUserAsymmetricKeysCommand, RegenerateUserAsymmetricKeysCommand>();
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.HostedServices;
|
||||
using Bit.Core.Identity;
|
||||
using Bit.Core.IdentityServer;
|
||||
using Bit.Core.KeyManagement;
|
||||
using Bit.Core.NotificationHub;
|
||||
using Bit.Core.OrganizationFeatures;
|
||||
using Bit.Core.Repositories;
|
||||
@ -120,6 +121,7 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<IOrganizationDomainService, OrganizationDomainService>();
|
||||
services.AddVaultServices();
|
||||
services.AddReportingServices();
|
||||
services.AddKeyManagementServices();
|
||||
}
|
||||
|
||||
public static void AddTokenizers(this IServiceCollection services)
|
||||
|
Reference in New Issue
Block a user