mirror of
https://github.com/bitwarden/server.git
synced 2025-12-10 15:55:10 -06:00
Implement TDE v2 registration
This commit is contained in:
parent
9726f4994f
commit
6210b74d10
@ -18,6 +18,7 @@ using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Kdf;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.KeyManagement.Queries.Interfaces;
|
||||
using Bit.Core.Models.Api.Response;
|
||||
using Bit.Core.Repositories;
|
||||
@ -44,6 +45,7 @@ public class AccountsController : Controller
|
||||
private readonly IUserAccountKeysQuery _userAccountKeysQuery;
|
||||
private readonly ITwoFactorEmailService _twoFactorEmailService;
|
||||
private readonly IChangeKdfCommand _changeKdfCommand;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public AccountsController(
|
||||
IOrganizationService organizationService,
|
||||
@ -57,7 +59,8 @@ public class AccountsController : Controller
|
||||
IFeatureService featureService,
|
||||
IUserAccountKeysQuery userAccountKeysQuery,
|
||||
ITwoFactorEmailService twoFactorEmailService,
|
||||
IChangeKdfCommand changeKdfCommand
|
||||
IChangeKdfCommand changeKdfCommand,
|
||||
IUserRepository userRepository
|
||||
)
|
||||
{
|
||||
_organizationService = organizationService;
|
||||
@ -72,6 +75,7 @@ public class AccountsController : Controller
|
||||
_userAccountKeysQuery = userAccountKeysQuery;
|
||||
_twoFactorEmailService = twoFactorEmailService;
|
||||
_changeKdfCommand = changeKdfCommand;
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
|
||||
@ -440,8 +444,39 @@ public class AccountsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
await _userService.SaveUserAsync(model.ToUser(user));
|
||||
return new KeysResponseModel(user);
|
||||
if (model.AccountKeys != null)
|
||||
{
|
||||
if (model.AccountKeys.ToAccountKeysData().IsV2Encryption())
|
||||
{
|
||||
await _userRepository.SetV2AccountCryptographicStateAsync(user.Id, model.AccountKeys.ToAccountKeysData());
|
||||
return new KeysResponseModel(model.AccountKeys?.ToAccountKeysData(), user.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Todo: Drop this after a transition period
|
||||
await _userService.SaveUserAsync(model.ToUser(user));
|
||||
return new KeysResponseModel(new UserAccountKeysData
|
||||
{
|
||||
PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData(
|
||||
user.PrivateKey,
|
||||
user.PublicKey
|
||||
)
|
||||
}, user.Key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Todo: Drop this after a transition period
|
||||
await _userService.SaveUserAsync(model.ToUser(user));
|
||||
return new KeysResponseModel(new UserAccountKeysData
|
||||
{
|
||||
PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData(
|
||||
user.PrivateKey,
|
||||
user.PublicKey
|
||||
)
|
||||
}, user.Key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("keys")]
|
||||
@ -453,7 +488,8 @@ public class AccountsController : Controller
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
return new KeysResponseModel(user);
|
||||
var accountKeys = await _userAccountKeysQuery.Run(user);
|
||||
return new KeysResponseModel(accountKeys, user.Key);
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
|
||||
@ -1,27 +1,32 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.KeyManagement.Models.Api.Response;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.Models.Api;
|
||||
|
||||
namespace Bit.Api.Models.Response;
|
||||
|
||||
public class KeysResponseModel : ResponseModel
|
||||
{
|
||||
public KeysResponseModel(User user)
|
||||
public KeysResponseModel(UserAccountKeysData accountKeys, string? masterKeyWrappedUserKey)
|
||||
: base("keys")
|
||||
{
|
||||
if (user == null)
|
||||
if (masterKeyWrappedUserKey != null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
Key = masterKeyWrappedUserKey;
|
||||
}
|
||||
|
||||
Key = user.Key;
|
||||
PublicKey = user.PublicKey;
|
||||
PrivateKey = user.PrivateKey;
|
||||
PublicKey = accountKeys.PublicKeyEncryptionKeyPairData.PublicKey;
|
||||
PrivateKey = accountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey;
|
||||
AccountKeys = new PrivateKeysResponseModel(accountKeys);
|
||||
}
|
||||
|
||||
public string Key { get; set; }
|
||||
/// <summary>
|
||||
/// The master key wrapped user key. The master key can either be a master-password master key or a
|
||||
/// key-connector master key.
|
||||
/// </summary>
|
||||
public string? Key { get; set; }
|
||||
[Obsolete("Use AccountKeys.PublicKeyEncryptionKeyPair.PublicKey instead")]
|
||||
public string PublicKey { get; set; }
|
||||
[Obsolete("Use AccountKeys.PublicKeyEncryptionKeyPair.WrappedPrivateKey instead")]
|
||||
public string PrivateKey { get; set; }
|
||||
public PrivateKeysResponseModel AccountKeys { get; set; }
|
||||
}
|
||||
|
||||
@ -3,17 +3,22 @@
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.KeyManagement.Models.Api.Request;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
|
||||
public class KeysRequestModel
|
||||
{
|
||||
[Obsolete("Use AccountKeys.AccountPublicKey instead")]
|
||||
[Required]
|
||||
public string PublicKey { get; set; }
|
||||
[Obsolete("Use AccountKeys.UserKeyEncryptedAccountPrivateKey instead")]
|
||||
[Required]
|
||||
public string EncryptedPrivateKey { get; set; }
|
||||
public AccountKeysRequestModel AccountKeys { get; set; }
|
||||
|
||||
[Obsolete("Use SetAccountKeysForUserCommand instead")]
|
||||
public User ToUser(User existingUser)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(PublicKey) || string.IsNullOrWhiteSpace(EncryptedPrivateKey))
|
||||
|
||||
@ -11,6 +11,7 @@ using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Kdf;
|
||||
using Bit.Core.KeyManagement.Models.Api.Request;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.KeyManagement.Queries.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
@ -38,6 +39,7 @@ public class AccountsControllerTests : IDisposable
|
||||
private readonly IUserAccountKeysQuery _userAccountKeysQuery;
|
||||
private readonly ITwoFactorEmailService _twoFactorEmailService;
|
||||
private readonly IChangeKdfCommand _changeKdfCommand;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public AccountsControllerTests()
|
||||
{
|
||||
@ -53,6 +55,7 @@ public class AccountsControllerTests : IDisposable
|
||||
_userAccountKeysQuery = Substitute.For<IUserAccountKeysQuery>();
|
||||
_twoFactorEmailService = Substitute.For<ITwoFactorEmailService>();
|
||||
_changeKdfCommand = Substitute.For<IChangeKdfCommand>();
|
||||
_userRepository = Substitute.For<IUserRepository>();
|
||||
|
||||
_sut = new AccountsController(
|
||||
_organizationService,
|
||||
@ -66,7 +69,8 @@ public class AccountsControllerTests : IDisposable
|
||||
_featureService,
|
||||
_userAccountKeysQuery,
|
||||
_twoFactorEmailService,
|
||||
_changeKdfCommand
|
||||
_changeKdfCommand,
|
||||
_userRepository
|
||||
);
|
||||
}
|
||||
|
||||
@ -738,5 +742,62 @@ public class AccountsControllerTests : IDisposable
|
||||
_userService.GetUserByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(Task.FromResult((User)null));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostKeys_WithAccountKeys_CallsSetV2AccountCryptographicState(
|
||||
User user,
|
||||
KeysRequestModel model)
|
||||
{
|
||||
// Arrange
|
||||
user.PublicKey = null;
|
||||
user.PrivateKey = null;
|
||||
model.AccountKeys = new AccountKeysRequestModel
|
||||
{
|
||||
UserKeyEncryptedAccountPrivateKey = "wrapped-private-key",
|
||||
AccountPublicKey = "public-key"
|
||||
};
|
||||
|
||||
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
||||
_featureService.IsEnabled(Bit.Core.FeatureFlagKeys.ReturnErrorOnExistingKeypair).Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await _sut.PostKeys(model);
|
||||
|
||||
// Assert
|
||||
await _userRepository.Received(1).SetV2AccountCryptographicStateAsync(
|
||||
user.Id,
|
||||
Arg.Any<UserAccountKeysData>());
|
||||
await _userService.DidNotReceiveWithAnyArgs().SaveUserAsync(Arg.Any<User>());
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("keys", result.Object);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostKeys_WithoutAccountKeys_CallsSaveUser(
|
||||
User user,
|
||||
KeysRequestModel model)
|
||||
{
|
||||
// Arrange
|
||||
user.PublicKey = null;
|
||||
user.PrivateKey = null;
|
||||
model.AccountKeys = null;
|
||||
model.PublicKey = "public-key";
|
||||
model.EncryptedPrivateKey = "encrypted-private-key";
|
||||
|
||||
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
||||
_featureService.IsEnabled(Bit.Core.FeatureFlagKeys.ReturnErrorOnExistingKeypair).Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await _sut.PostKeys(model);
|
||||
|
||||
// Assert
|
||||
await _userService.Received(1).SaveUserAsync(Arg.Is<User>(u =>
|
||||
u.PublicKey == model.PublicKey &&
|
||||
u.PrivateKey == model.EncryptedPrivateKey));
|
||||
await _userRepository.DidNotReceiveWithAnyArgs()
|
||||
.SetV2AccountCryptographicStateAsync(Arg.Any<Guid>(), Arg.Any<UserAccountKeysData>());
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("keys", result.Object);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user