1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -05:00

Improve tests

This commit is contained in:
Bernd Schoolmann
2025-06-09 14:14:09 +02:00
parent cc68b91188
commit 8bbdd7fe30
4 changed files with 277 additions and 14 deletions

View File

@ -12,6 +12,9 @@ using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.KeyManagement.Entities;
using Bit.Core.KeyManagement.Enums;
using Bit.Core.KeyManagement.Repositories;
using Bit.Core.Repositories;
using Bit.Core.Vault.Enums;
using Bit.Test.Common.AutoFixture.Attributes;
@ -24,6 +27,7 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
{
private static readonly string _mockEncryptedString =
"2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=";
private static readonly string _mockEncryptedType7String = "7.AOs41Hd8OQiCPXjyJKCiDA==";
private readonly HttpClient _client;
private readonly IEmergencyAccessRepository _emergencyAccessRepository;
@ -34,6 +38,7 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
private readonly IDeviceRepository _deviceRepository;
private readonly IPasswordHasher<User> _passwordHasher;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUserSignatureKeyPairRepository _userSignatureKeyPairRepository;
private string _ownerEmail = null!;
public AccountsKeyManagementControllerTests(ApiApplicationFactory factory)
@ -49,6 +54,7 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
_organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
_passwordHasher = _factory.GetService<IPasswordHasher<User>>();
_organizationRepository = _factory.GetService<IOrganizationRepository>();
_userSignatureKeyPairRepository = _factory.GetService<IUserSignatureKeyPairRepository>();
}
public async Task InitializeAsync()
@ -200,6 +206,7 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
var password = _passwordHasher.HashPassword(user, "newMasterPassword");
user.MasterPassword = password;
user.PublicKey = "publicKey";
user.PrivateKey = _mockEncryptedString;
await _userRepository.ReplaceAsync(user);
request.AccountUnlockData.MasterPasswordUnlockData.KdfType = user.Kdf;
@ -209,6 +216,8 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
request.AccountUnlockData.MasterPasswordUnlockData.Email = user.Email;
request.AccountKeys.AccountPublicKey = "publicKey";
request.AccountKeys.UserKeyEncryptedAccountPrivateKey = _mockEncryptedString;
request.AccountKeys.PublicKeyEncryptionKeyPair = null;
request.AccountKeys.SignatureKeyPair = null;
request.OldMasterKeyAuthenticationHash = "newMasterPassword";
@ -354,4 +363,191 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
Assert.Equal(DateTime.UtcNow, user.RevisionDate, TimeSpan.FromMinutes(1));
Assert.Equal(DateTime.UtcNow, user.AccountRevisionDate, TimeSpan.FromMinutes(1));
}
[Theory]
[BitAutoData]
public async Task RotateV2UserAccountKeysAsync_Success(RotateUserAccountKeysAndDataRequestModel request)
{
await _loginHelper.LoginAsync(_ownerEmail);
var user = await _userRepository.GetByEmailAsync(_ownerEmail);
if (user == null)
{
throw new InvalidOperationException("User not found.");
}
var password = _passwordHasher.HashPassword(user, "newMasterPassword");
user.MasterPassword = password;
user.PublicKey = "publicKey";
user.PrivateKey = _mockEncryptedType7String;
await _userRepository.ReplaceAsync(user);
await _userSignatureKeyPairRepository.CreateAsync(new UserSignatureKeyPair
{
UserId = user.Id,
SignatureAlgorithm = SignatureAlgorithm.Ed25519,
SigningKey = _mockEncryptedType7String,
VerifyingKey = "verifyingKey",
});
request.AccountUnlockData.MasterPasswordUnlockData.KdfType = user.Kdf;
request.AccountUnlockData.MasterPasswordUnlockData.KdfIterations = user.KdfIterations;
request.AccountUnlockData.MasterPasswordUnlockData.KdfMemory = user.KdfMemory;
request.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism = user.KdfParallelism;
request.AccountUnlockData.MasterPasswordUnlockData.Email = user.Email;
request.AccountKeys.AccountPublicKey = "publicKey";
request.AccountKeys.UserKeyEncryptedAccountPrivateKey = _mockEncryptedType7String;
request.AccountKeys.PublicKeyEncryptionKeyPair = new PublicKeyEncryptionKeyPairRequestModel
{
PublicKey = "publicKey",
WrappedPrivateKey = _mockEncryptedType7String,
SignedPublicKey = "signedPublicKey",
};
request.AccountKeys.SignatureKeyPair = new SignatureKeyPairRequestModel
{
SignatureAlgorithm = SignatureAlgorithm.Ed25519,
WrappedSigningKey = _mockEncryptedType7String,
VerifyingKey = "verifyingKey",
};
request.OldMasterKeyAuthenticationHash = "newMasterPassword";
request.AccountData.Ciphers =
[
new CipherWithIdRequestModel
{
Id = Guid.NewGuid(),
Type = CipherType.Login,
Name = _mockEncryptedString,
Login = new CipherLoginModel
{
Username = _mockEncryptedString,
Password = _mockEncryptedString,
},
},
];
request.AccountData.Folders = [
new FolderWithIdRequestModel
{
Id = Guid.NewGuid(),
Name = _mockEncryptedString,
},
];
request.AccountData.Sends = [
new SendWithIdRequestModel
{
Id = Guid.NewGuid(),
Name = _mockEncryptedString,
Key = _mockEncryptedString,
Disabled = false,
DeletionDate = DateTime.UtcNow.AddDays(1),
},
];
request.AccountUnlockData.MasterPasswordUnlockData.MasterKeyEncryptedUserKey = _mockEncryptedString;
request.AccountUnlockData.PasskeyUnlockData = [];
request.AccountUnlockData.DeviceKeyUnlockData = [];
request.AccountUnlockData.EmergencyAccessUnlockData = [];
request.AccountUnlockData.OrganizationAccountRecoveryUnlockData = [];
var response = await _client.PostAsJsonAsync("/accounts/key-management/rotate-user-account-keys", request);
var responseMessage = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
var userNewState = await _userRepository.GetByEmailAsync(_ownerEmail);
Assert.NotNull(userNewState);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.Email, userNewState.Email);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfType, userNewState.Kdf);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfIterations, userNewState.KdfIterations);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfMemory, userNewState.KdfMemory);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism, userNewState.KdfParallelism);
}
[Theory]
[BitAutoData]
public async Task RotateUpgradeToV2UserAccountKeysAsync_Success(RotateUserAccountKeysAndDataRequestModel request)
{
await _loginHelper.LoginAsync(_ownerEmail);
var user = await _userRepository.GetByEmailAsync(_ownerEmail);
if (user == null)
{
throw new InvalidOperationException("User not found.");
}
var password = _passwordHasher.HashPassword(user, "newMasterPassword");
user.MasterPassword = password;
user.PublicKey = "publicKey";
user.PrivateKey = _mockEncryptedString;
await _userRepository.ReplaceAsync(user);
request.AccountUnlockData.MasterPasswordUnlockData.KdfType = user.Kdf;
request.AccountUnlockData.MasterPasswordUnlockData.KdfIterations = user.KdfIterations;
request.AccountUnlockData.MasterPasswordUnlockData.KdfMemory = user.KdfMemory;
request.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism = user.KdfParallelism;
request.AccountUnlockData.MasterPasswordUnlockData.Email = user.Email;
request.AccountKeys.AccountPublicKey = "publicKey";
request.AccountKeys.UserKeyEncryptedAccountPrivateKey = _mockEncryptedType7String;
request.AccountKeys.PublicKeyEncryptionKeyPair = new PublicKeyEncryptionKeyPairRequestModel
{
PublicKey = "publicKey",
WrappedPrivateKey = _mockEncryptedType7String,
SignedPublicKey = "signedPublicKey",
};
request.AccountKeys.SignatureKeyPair = new SignatureKeyPairRequestModel
{
SignatureAlgorithm = SignatureAlgorithm.Ed25519,
WrappedSigningKey = _mockEncryptedType7String,
VerifyingKey = "verifyingKey",
};
request.OldMasterKeyAuthenticationHash = "newMasterPassword";
request.AccountData.Ciphers =
[
new CipherWithIdRequestModel
{
Id = Guid.NewGuid(),
Type = CipherType.Login,
Name = _mockEncryptedString,
Login = new CipherLoginModel
{
Username = _mockEncryptedString,
Password = _mockEncryptedString,
},
},
];
request.AccountData.Folders = [
new FolderWithIdRequestModel
{
Id = Guid.NewGuid(),
Name = _mockEncryptedString,
},
];
request.AccountData.Sends = [
new SendWithIdRequestModel
{
Id = Guid.NewGuid(),
Name = _mockEncryptedString,
Key = _mockEncryptedString,
Disabled = false,
DeletionDate = DateTime.UtcNow.AddDays(1),
},
];
request.AccountUnlockData.MasterPasswordUnlockData.MasterKeyEncryptedUserKey = _mockEncryptedString;
request.AccountUnlockData.PasskeyUnlockData = [];
request.AccountUnlockData.DeviceKeyUnlockData = [];
request.AccountUnlockData.EmergencyAccessUnlockData = [];
request.AccountUnlockData.OrganizationAccountRecoveryUnlockData = [];
var response = await _client.PostAsJsonAsync("/accounts/key-management/rotate-user-account-keys", request);
var responseMessage = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
var userNewState = await _userRepository.GetByEmailAsync(_ownerEmail);
Assert.NotNull(userNewState);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.Email, userNewState.Email);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfType, userNewState.Kdf);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfIterations, userNewState.KdfIterations);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfMemory, userNewState.KdfMemory);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism, userNewState.KdfParallelism);
}
}

View File

@ -1,4 +1,5 @@
using Bit.Core.Entities;
using Bit.Core.KeyManagement.Enums;
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.KeyManagement.Repositories;
using Bit.Core.KeyManagement.UserKey.Implementations;
@ -7,6 +8,7 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Identity;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using Xunit;
namespace Bit.Core.Test.KeyManagement.UserKey;
@ -194,4 +196,75 @@ public class RotateUserAccountKeysCommandTests
Assert.Equal(IdentityResult.Success, result);
}
[Theory, BitAutoData]
public async Task UpdateAccountKeys_ThrowsIfPublicKeyChangesAsync(SutProvider<RotateUserAccountKeysCommand> sutProvider, User user, RotateUserAccountKeysData model)
{
user.PrivateKey = "2.xxx";
sutProvider.GetDependency<IUserSignatureKeyPairRepository>()
.GetByUserIdAsync(user.Id)
.ReturnsNull();
user.PublicKey = "old-public";
model.AccountKeys.PublicKeyEncryptionKeyPairData.PublicKey = "new-public";
var saveEncryptedDataActions = new List<Core.KeyManagement.UserKey.UpdateEncryptedDataForKeyRotation>();
await Assert.ThrowsAsync<InvalidOperationException>(async () => await sutProvider.Sut.UpdateAccountKeys(model, user, saveEncryptedDataActions));
}
[Theory, BitAutoData]
public async Task UpdateAccountKeys_ThrowsIfPrivateKeyWrappedWithNotXchacha20ForV2UserAsync(SutProvider<RotateUserAccountKeysCommand> sutProvider, User user, RotateUserAccountKeysData model)
{
user.PrivateKey = "7.xxx";
sutProvider.GetDependency<IUserSignatureKeyPairRepository>()
.GetByUserIdAsync(user.Id)
.Returns(new SignatureKeyPairData(SignatureAlgorithm.Ed25519, "7.xxx", "public"));
user.PublicKey = "public";
model.UserKeyEncryptedAccountPrivateKey = "2.xxx";
model.AccountPublicKey = user.PublicKey;
model.AccountKeys.PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData("2.xxx", "public", null);
model.AccountKeys.SignatureKeyPairData = null;
var saveEncryptedDataActions = new List<Core.KeyManagement.UserKey.UpdateEncryptedDataForKeyRotation>();
await Assert.ThrowsAsync<InvalidOperationException>(async () => await sutProvider.Sut.UpdateAccountKeys(model, user, saveEncryptedDataActions));
}
[Theory, BitAutoData]
public async Task UpdateAccountKeys_ThrowsIfIsV1UserAndPrivateKeyIsWrappedWithNotAesCbcHmacAsync(SutProvider<RotateUserAccountKeysCommand> sutProvider, User user, RotateUserAccountKeysData model)
{
user.PrivateKey = "2.xxx";
sutProvider.GetDependency<IUserSignatureKeyPairRepository>()
.GetByUserIdAsync(user.Id)
.ReturnsNull();
user.PublicKey = "public";
model.UserKeyEncryptedAccountPrivateKey = "7.xxx";
model.AccountPublicKey = "public";
model.AccountKeys.PublicKeyEncryptionKeyPairData = null;
model.AccountKeys.SignatureKeyPairData = null;
var saveEncryptedDataActions = new List<Core.KeyManagement.UserKey.UpdateEncryptedDataForKeyRotation>();
await Assert.ThrowsAsync<InvalidOperationException>(async () => await sutProvider.Sut.UpdateAccountKeys(model, user, saveEncryptedDataActions));
}
[Theory, BitAutoData]
public async Task UpdateAccountKeys_SuccessAsync(SutProvider<RotateUserAccountKeysCommand> sutProvider, User user, RotateUserAccountKeysData model)
{
user.PrivateKey = "2.xxx";
sutProvider.GetDependency<IUserSignatureKeyPairRepository>()
.GetByUserIdAsync(user.Id)
.Returns(new SignatureKeyPairData(SignatureAlgorithm.Ed25519, "7.xxx", "public"));
user.PublicKey = "public";
model.AccountKeys.PublicKeyEncryptionKeyPairData.PublicKey = "public";
model.UserKeyEncryptedAccountPrivateKey = "2.xxx";
var saveEncryptedDataActions = new List<Core.KeyManagement.UserKey.UpdateEncryptedDataForKeyRotation>();
await sutProvider.Sut.UpdateAccountKeys(model, user, saveEncryptedDataActions);
Assert.NotEmpty(saveEncryptedDataActions);
}
[Theory, BitAutoData]
public void ValidateRotationModelSignatureKeyPairForv1UserAndUpgradeToV2_NoSignedPublicKeyThrows(SutProvider<RotateUserAccountKeysCommand> sutProvider, User user, RotateUserAccountKeysData model)
{
model.AccountKeys.SignatureKeyPairData = new SignatureKeyPairData(SignatureAlgorithm.Ed25519, "signingKey", "verifyingKey");
model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey = null;
var encryptedDataActions = new List<Core.KeyManagement.UserKey.UpdateEncryptedDataForKeyRotation>();
var excepction = Assert.Throws<InvalidOperationException>(() => sutProvider.Sut.ValidateRotationModelSignatureKeyPairForV1UserAndUpgradeToV2(model, user, encryptedDataActions));
Assert.Equal("The provided public key encryption key pair data does not contain a valid signed public key.", excepction.Message);
}
}