Skip to content

Commit

Permalink
Changing password should update encrypted master key and nonce
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack-Edwards committed Oct 13, 2024
1 parent 80167b7 commit 8070b57
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ public class PasswordChangeRequest
{
public List<VersionedPassword> OldVersionedPasswords { get; set; }
public VersionedPassword NewVersionedPassword { get; set; }
public byte[] EncryptedMasterKey { get; set; }
public byte[] Nonce { get; set; }

[JsonConstructor]
public PasswordChangeRequest(List<VersionedPassword> oldVersionedPasswords, VersionedPassword newVersionedPassword)
public PasswordChangeRequest(List<VersionedPassword> oldVersionedPasswords, VersionedPassword newVersionedPassword, byte[] encryptedMasterKey, byte[] nonce)
{
OldVersionedPasswords = oldVersionedPasswords;
NewVersionedPassword = newVersionedPassword;
EncryptedMasterKey = encryptedMasterKey;
Nonce = nonce;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,7 @@ internal class ChangeUserPasswordCommandHandler
private readonly ServerPasswordSettings _serverPasswordSettings;


public ChangeUserPasswordCommandHandler(
DataContext dataContext,
IPasswordHashService passwordHashService,
IOptions<ServerPasswordSettings> serverPasswordSettings)
public ChangeUserPasswordCommandHandler(DataContext dataContext, IPasswordHashService passwordHashService, IOptions<ServerPasswordSettings> serverPasswordSettings)
{
_dataContext = dataContext;
_passwordHashService = passwordHashService;
Expand All @@ -70,7 +67,9 @@ public async Task<Either<PasswordChangeError, Unit>> Handle(ChangeUserPasswordCo
return await ValidatePasswordChangeRequest(request.Request)
.BindAsync(async validPasswordChangeRequest => await (
from foundUser in GetUserAsync(request.UserId)
from passwordVerificationSuccess in VerifyAndChangePasswordAsync(validPasswordChangeRequest, foundUser)
from passwordVerificationSuccess in VerifyAndChangePasswordAsync(validPasswordChangeRequest, foundUser).AsTask()
from updateMasterKeyResult in Either<PasswordChangeError, Unit>.From(UpdateUserMasterKey(foundUser, request.Request.EncryptedMasterKey, request.Request.Nonce)).AsTask()
from _ in Either<PasswordChangeError, Unit>.FromRightAsync(SaveChangesAsync())
select Unit.Default)
);
}
Expand All @@ -95,6 +94,7 @@ private Either<PasswordChangeError, ValidPasswordChangeRequest> ValidatePassword
private async Task<Either<PasswordChangeError, UserEntity>> GetUserAsync(Guid userId)
{
UserEntity? foundUser = await _dataContext.Users
.Include(x => x.MasterKey)
.Where(x => x.Id == userId)
.FirstOrDefaultAsync();

Expand All @@ -106,7 +106,7 @@ private async Task<Either<PasswordChangeError, UserEntity>> GetUserAsync(Guid us
return foundUser;
}

private async Task<Either<PasswordChangeError, Unit>> VerifyAndChangePasswordAsync(ValidPasswordChangeRequest validChangePasswordRequest, UserEntity userEntity)
private Either<PasswordChangeError, Unit> VerifyAndChangePasswordAsync(ValidPasswordChangeRequest validChangePasswordRequest, UserEntity userEntity)
{
bool requestContainsUserClientPasswordVersion = validChangePasswordRequest.OldVersionedPasswords.ContainsKey(userEntity.ClientPasswordVersion);
if (!requestContainsUserClientPasswordVersion)
Expand Down Expand Up @@ -138,8 +138,6 @@ private async Task<Either<PasswordChangeError, Unit>> VerifyAndChangePasswordAsy
userEntity.PasswordSalt = hashOutput.Salt;
userEntity.ServerPasswordVersion = _passwordHashService.LatestServerPasswordVersion;
userEntity.ClientPasswordVersion = _serverPasswordSettings.ClientVersion;

await _dataContext.SaveChangesAsync();

return Unit.Default;
}
Expand All @@ -160,4 +158,18 @@ private Either<PasswordChangeError, IDictionary<short, byte[]>> GetValidClientPa

return clientPasswords.ToDictionary(x => x.Version, x => x.Password);
}

private Unit UpdateUserMasterKey(UserEntity userEntity, byte[] encryptedMasterKey, byte[] nonce)
{
userEntity.MasterKey!.EncryptedKey = encryptedMasterKey;
userEntity.MasterKey!.Nonce = nonce;
userEntity.MasterKey!.Updated = DateTime.UtcNow;
return Unit.Default;
}

private async Task<Unit> SaveChangesAsync()
{
await _dataContext.SaveChangesAsync();
return Unit.Default;
}
}
23 changes: 16 additions & 7 deletions Crypter.Test/Integration_Tests/TestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,24 +146,33 @@ internal static VersionedPassword GetVersionedPassword(string password, short ve
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
return new VersionedPassword(passwordBytes, version);
}
internal static (byte[] masterKey, InsertMasterKeyRequest request) GetInsertMasterKeyRequest(string password)

internal static (byte[] masterKey, byte[] nonce) GetRandomMasterKey()
{
Random random = new Random();

byte[] passwordBytes = Encoding.UTF8.GetBytes(password);


byte[] randomBytesMasterKey = new byte[32];
random.NextBytes(randomBytesMasterKey);

byte[] randomBytesNonce = new byte[32];
random.NextBytes(randomBytesNonce);

return (randomBytesMasterKey, randomBytesNonce);
}

internal static (byte[] masterKey, InsertMasterKeyRequest request) GetInsertMasterKeyRequest(string password)
{
Random random = new Random();

byte[] passwordBytes = Encoding.UTF8.GetBytes(password);

(byte[] masterKey, byte[] nonce) = GetRandomMasterKey();

byte[] randomBytesRecoveryProof = new byte[32];
random.NextBytes(randomBytesRecoveryProof);

InsertMasterKeyRequest request = new InsertMasterKeyRequest(passwordBytes, randomBytesMasterKey, randomBytesNonce, randomBytesRecoveryProof);
return (randomBytesMasterKey, request);
InsertMasterKeyRequest request = new InsertMasterKeyRequest(passwordBytes, masterKey, nonce, randomBytesRecoveryProof);
return (masterKey, request);
}

internal static InsertKeyPairRequest GetInsertKeyPairRequest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
using System.Threading.Tasks;
using Crypter.Common.Client.Interfaces.HttpClients;
using Crypter.Common.Client.Interfaces.Repositories;
using Crypter.Common.Contracts.Features.Keys;
using Crypter.Common.Contracts.Features.UserAuthentication;
using Crypter.Common.Contracts.Features.UserAuthentication.PasswordChange;
using Crypter.Common.Enums;
Expand Down Expand Up @@ -90,17 +91,30 @@ await loginResult.DoRightAsync(async loginResponse =>
await _clientTokenRepository!.StoreRefreshTokenAsync(loginResponse.RefreshToken, TokenType.Session);
});

(_, InsertMasterKeyRequest masterKeyRequest) = TestData.GetInsertMasterKeyRequest(TestData.DefaultPassword);
Either<InsertMasterKeyError, Unit> masterKeyResult = await _client!.UserKey.InsertMasterKeyAsync(masterKeyRequest);

List<VersionedPassword> oldPasswords = [TestData.GetVersionedPassword(TestData.DefaultPassword, 1)];
VersionedPassword newPassword = TestData.GetVersionedPassword(updatedPassword, 1);
PasswordChangeRequest request = new PasswordChangeRequest(oldPasswords, newPassword);
(byte[] masterKey, byte[] nonce) = TestData.GetRandomMasterKey();
PasswordChangeRequest request = new PasswordChangeRequest(oldPasswords, newPassword, masterKey, nonce);
Either<PasswordChangeError, Unit> result = await _client!.UserAuthentication.ChangePasswordAsync(request);

UserMasterKeyEntity masterKeyEntity = await _dataContext!.UserMasterKeys
.Where(x => x.User!.Username == TestData.DefaultUsername)
.FirstAsync();

LoginRequest newLoginRequest = TestData.GetLoginRequest(TestData.DefaultUsername, updatedPassword);
Either<LoginError, LoginResponse> newLoginResult = await _client!.UserAuthentication.LoginAsync(newLoginRequest);

Assert.That(registrationResult.IsRight, Is.True);
Assert.That(loginResult.IsRight, Is.True);
Assert.That(masterKeyResult.IsRight, Is.True);
Assert.That(result.IsRight, Is.True);
Assert.That(masterKeyEntity.EncryptedKey, Is.Not.EqualTo(masterKeyRequest.EncryptedKey));
Assert.That(masterKeyEntity.Nonce, Is.Not.EqualTo(masterKeyRequest.Nonce));
Assert.That(masterKeyEntity.EncryptedKey, Is.EqualTo(masterKey));
Assert.That(masterKeyEntity.Nonce, Is.EqualTo(nonce));
Assert.That(newLoginResult.IsRight, Is.True);
}

Expand All @@ -121,6 +135,9 @@ await loginResult.DoRightAsync(async loginResponse =>
await _clientTokenRepository!.StoreRefreshTokenAsync(loginResponse.RefreshToken, TokenType.Session);
});

(_, InsertMasterKeyRequest masterKeyRequest) = TestData.GetInsertMasterKeyRequest(TestData.DefaultPassword);
Either<InsertMasterKeyError, Unit> masterKeyResult = await _client!.UserKey.InsertMasterKeyAsync(masterKeyRequest);

UserEntity userEntity = await _dataContext!.Users
.AsTracking()
.Where(x => x.Username == TestData.DefaultUsername)
Expand All @@ -131,7 +148,8 @@ await loginResult.DoRightAsync(async loginResponse =>

List<VersionedPassword> oldPasswords = [TestData.GetVersionedPassword(TestData.DefaultPassword, 0)];
VersionedPassword newPassword = TestData.GetVersionedPassword(updatedPassword, 1);
PasswordChangeRequest request = new PasswordChangeRequest(oldPasswords, newPassword);
(byte[] masterKey, byte[] nonce) = TestData.GetRandomMasterKey();
PasswordChangeRequest request = new PasswordChangeRequest(oldPasswords, newPassword, masterKey, nonce);
await _client!.UserAuthentication.ChangePasswordAsync(request);

IServiceScope newScope = _factory!.Services.CreateScope();
Expand Down Expand Up @@ -159,14 +177,24 @@ await loginResult.DoRightAsync(async loginResponse =>
await _clientTokenRepository!.StoreRefreshTokenAsync(loginResponse.RefreshToken, TokenType.Session);
});

(_, InsertMasterKeyRequest masterKeyRequest) = TestData.GetInsertMasterKeyRequest(TestData.DefaultPassword);
Either<InsertMasterKeyError, Unit> masterKeyResult = await _client!.UserKey.InsertMasterKeyAsync(masterKeyRequest);

List<VersionedPassword> oldPasswords = [TestData.GetVersionedPassword("not the right password", 1)];
VersionedPassword newPassword = TestData.GetVersionedPassword("new password", 1);
PasswordChangeRequest request = new PasswordChangeRequest(oldPasswords, newPassword);
(byte[] masterKey, byte[] nonce) = TestData.GetRandomMasterKey();
PasswordChangeRequest request = new PasswordChangeRequest(oldPasswords, newPassword, masterKey, nonce);
Either<PasswordChangeError, Unit> result = await _client!.UserAuthentication.ChangePasswordAsync(request);

UserMasterKeyEntity masterKeyEntity = await _dataContext!.UserMasterKeys
.Where(x => x.User!.Username == TestData.DefaultUsername)
.FirstAsync();

Assert.That(registrationResult.IsRight, Is.True);
Assert.That(loginResult.IsRight, Is.True);
Assert.That(result.IsLeft, Is.True);
Assert.That(masterKeyEntity.EncryptedKey, Is.EqualTo(masterKeyRequest.EncryptedKey));
Assert.That(masterKeyEntity.Nonce, Is.EqualTo(masterKeyRequest.Nonce));
}

[Test]
Expand All @@ -184,13 +212,23 @@ await loginResult.DoRightAsync(async loginResponse =>
await _clientTokenRepository!.StoreRefreshTokenAsync(loginResponse.RefreshToken, TokenType.Session);
});

(_, InsertMasterKeyRequest masterKeyRequest) = TestData.GetInsertMasterKeyRequest(TestData.DefaultPassword);
Either<InsertMasterKeyError, Unit> masterKeyResult = await _client!.UserKey.InsertMasterKeyAsync(masterKeyRequest);

List<VersionedPassword> oldPasswords = [TestData.GetVersionedPassword(TestData.DefaultPassword, 0)];
VersionedPassword newPassword = TestData.GetVersionedPassword("new password", 1);
PasswordChangeRequest request = new PasswordChangeRequest(oldPasswords, newPassword);
(byte[] masterKey, byte[] nonce) = TestData.GetRandomMasterKey();
PasswordChangeRequest request = new PasswordChangeRequest(oldPasswords, newPassword, masterKey, nonce);
Either<PasswordChangeError, Unit> result = await _client!.UserAuthentication.ChangePasswordAsync(request);

UserMasterKeyEntity masterKeyEntity = await _dataContext!.UserMasterKeys
.Where(x => x.User!.Username == TestData.DefaultUsername)
.FirstAsync();

Assert.That(registrationResult.IsRight, Is.True);
Assert.That(loginResult.IsRight, Is.True);
Assert.That(result.IsLeft, Is.True);
Assert.That(masterKeyEntity.EncryptedKey, Is.EqualTo(masterKeyRequest.EncryptedKey));
Assert.That(masterKeyEntity.Nonce, Is.EqualTo(masterKeyRequest.Nonce));
}
}

0 comments on commit 8070b57

Please sign in to comment.