diff --git a/src/Api/Auth/Controllers/AccountsController.cs b/src/Api/Auth/Controllers/AccountsController.cs index 6c19049c49..0a3b31a74a 100644 --- a/src/Api/Auth/Controllers/AccountsController.cs +++ b/src/Api/Auth/Controllers/AccountsController.cs @@ -15,6 +15,7 @@ using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Models.Api.Request.Accounts; using Bit.Core.Auth.Models.Data; +using Bit.Core.Auth.Services; using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces; using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; using Bit.Core.Entities; @@ -57,6 +58,7 @@ public class AccountsController : Controller _organizationUserValidator; private readonly IRotationValidator, IEnumerable> _webauthnKeyValidator; + private readonly IOpaqueKeyExchangeService _opaqueKeyExchangeService; public AccountsController( @@ -76,7 +78,8 @@ public class AccountsController : Controller emergencyAccessValidator, IRotationValidator, IReadOnlyList> organizationUserValidator, - IRotationValidator, IEnumerable> webAuthnKeyValidator + IRotationValidator, IEnumerable> webAuthnKeyValidator, + IOpaqueKeyExchangeService opaqueKeyExchangeService ) { _organizationService = organizationService; @@ -94,6 +97,7 @@ public class AccountsController : Controller _emergencyAccessValidator = emergencyAccessValidator; _organizationUserValidator = organizationUserValidator; _webauthnKeyValidator = webAuthnKeyValidator; + _opaqueKeyExchangeService = opaqueKeyExchangeService; } @@ -209,8 +213,14 @@ public class AccountsController : Controller throw new UnauthorizedAccessException(); } + Guid? sessionId = null; + if (model.OpaqueSessionId != null) + { + sessionId = Guid.Parse(model.OpaqueSessionId); + } + var result = await _userService.ChangePasswordAsync(user, model.MasterPasswordHash, - model.NewMasterPasswordHash, model.MasterPasswordHint, model.Key); + model.NewMasterPasswordHash, model.MasterPasswordHint, model.Key, sessionId); if (result.Succeeded) { return; diff --git a/src/Api/Auth/Controllers/OpaqueKeyExchangeController.cs b/src/Api/Auth/Controllers/OpaqueKeyExchangeController.cs index cb0768f385..dd59d2d973 100644 --- a/src/Api/Auth/Controllers/OpaqueKeyExchangeController.cs +++ b/src/Api/Auth/Controllers/OpaqueKeyExchangeController.cs @@ -27,7 +27,7 @@ public class OpaqueKeyExchangeController : Controller public async Task StartRegistrationAsync([FromBody] OpaqueRegistrationStartRequest request) { var user = await _userService.GetUserByPrincipalAsync(User); - var result = await _opaqueKeyExchangeService.StartRegistration(Convert.FromBase64String(request.RegistrationRequest), user, request.CipherConfiguration); + var result = await _opaqueKeyExchangeService.StartRegistration(Convert.FromBase64String(request.RegistrationRequest), user, request.CipherConfiguration.ToNativeConfiguration()); return new OpaqueRegistrationStartResponse(result.Item1, Convert.ToBase64String(result.Item2)); } diff --git a/src/Api/Auth/Models/Request/Accounts/PasswordRequestModel.cs b/src/Api/Auth/Models/Request/Accounts/PasswordRequestModel.cs index ce197c4aad..93ccdaadc5 100644 --- a/src/Api/Auth/Models/Request/Accounts/PasswordRequestModel.cs +++ b/src/Api/Auth/Models/Request/Accounts/PasswordRequestModel.cs @@ -11,4 +11,5 @@ public class PasswordRequestModel : SecretVerificationRequestModel public string MasterPasswordHint { get; set; } [Required] public string Key { get; set; } + public string OpaqueSessionId { get; set; } } diff --git a/src/Api/Auth/Models/Request/Opaque/OpaqueRegistrationStartRequest.cs b/src/Api/Auth/Models/Request/Opaque/OpaqueRegistrationStartRequest.cs index b6995090c7..d0e2fb9843 100644 --- a/src/Api/Auth/Models/Request/Opaque/OpaqueRegistrationStartRequest.cs +++ b/src/Api/Auth/Models/Request/Opaque/OpaqueRegistrationStartRequest.cs @@ -1,12 +1,50 @@ using System.ComponentModel.DataAnnotations; -using Bitwarden.OPAQUE; namespace Bit.Api.Auth.Models.Request.Opaque; + public class OpaqueRegistrationStartRequest { [Required] - public String RegistrationRequest { get; set; } + public string RegistrationRequest { get; set; } [Required] public CipherConfiguration CipherConfiguration { get; set; } } + +public class CipherConfiguration +{ + static string OpaqueKe3Ristretto3DHArgonSuite = "OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF"; + + [Required] + public string CipherSuite { get; set; } + public Argon2KsfParameters Argon2Parameters { get; set; } + + public Bitwarden.OPAQUE.CipherConfiguration ToNativeConfiguration() + { + if (CipherSuite == OpaqueKe3Ristretto3DHArgonSuite) + { + return new Bitwarden.OPAQUE.CipherConfiguration + { + OprfCS = Bitwarden.OPAQUE.OprfCS.Ristretto255, + KeGroup = Bitwarden.OPAQUE.KeGroup.Ristretto255, + KeyExchange = Bitwarden.OPAQUE.KeyExchange.TripleDH, + KSF = new Bitwarden.OPAQUE.Argon2id(Argon2Parameters.iterations, Argon2Parameters.memory, Argon2Parameters.parallelism) + }; + } + else + { + throw new Exception("Unsupported cipher suite"); + } + } +} + +public class Argon2KsfParameters +{ + // Memory in KiB + [Required] + public int memory; + [Required] + public int iterations; + [Required] + public int parallelism; +} diff --git a/src/Core/Auth/Services/Implementations/OpaqueKeyExchangeService.cs b/src/Core/Auth/Services/Implementations/OpaqueKeyExchangeService.cs index edce12169d..8e14d35c5a 100644 --- a/src/Core/Auth/Services/Implementations/OpaqueKeyExchangeService.cs +++ b/src/Core/Auth/Services/Implementations/OpaqueKeyExchangeService.cs @@ -37,9 +37,6 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService { var registrationFinish = _bitwardenOpaque.FinishRegistration(cipherConfiguration, registrationUpload); SessionStore.RegisterSessions[sessionId].serverRegistration = registrationFinish.serverRegistration; - - // todo move to changepassword - SetActive(sessionId, user); } catch (Exception e) { diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index b6a1d1f05b..b5ad9dd79b 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -44,7 +44,7 @@ public interface IUserService Task InitiateEmailChangeAsync(User user, string newEmail); Task ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, string key); - Task ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string passwordHint, string key); + Task ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string passwordHint, string key, Guid? opaqueSessionId); Task SetKeyConnectorKeyAsync(User user, string key, string orgIdentifier); Task ConvertToKeyConnectorAsync(User user); Task AdminResetPasswordAsync(OrganizationUserType type, Guid orgId, Guid id, string newMasterPassword, string key); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 5076c8282e..36f227035c 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -11,6 +11,7 @@ using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; using Bit.Core.Auth.Models.Business.Tokenables; +using Bit.Core.Auth.Services; using Bit.Core.Billing.Models; using Bit.Core.Billing.Models.Sales; using Bit.Core.Billing.Services; @@ -78,6 +79,7 @@ public class UserService : UserManager, IUserService, IDisposable private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand; private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand; private readonly IDistributedCache _distributedCache; + private readonly IOpaqueKeyExchangeService _opaqueKeyExchangeService; public UserService( IUserRepository userRepository, @@ -115,7 +117,8 @@ public class UserService : UserManager, IUserService, IDisposable IPremiumUserBillingService premiumUserBillingService, IRemoveOrganizationUserCommand removeOrganizationUserCommand, IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand, - IDistributedCache distributedCache) + IDistributedCache distributedCache, + IOpaqueKeyExchangeService opaqueKeyExchangeService) : base( store, optionsAccessor, @@ -159,6 +162,7 @@ public class UserService : UserManager, IUserService, IDisposable _removeOrganizationUserCommand = removeOrganizationUserCommand; _revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand; _distributedCache = distributedCache; + _opaqueKeyExchangeService = opaqueKeyExchangeService; } public Guid? GetProperUserId(ClaimsPrincipal principal) @@ -643,7 +647,7 @@ public class UserService : UserManager, IUserService, IDisposable } public async Task ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string passwordHint, - string key) + string key, Guid? opaqueSessionId) { if (user == null) { @@ -664,6 +668,14 @@ public class UserService : UserManager, IUserService, IDisposable user.Key = key; user.MasterPasswordHint = passwordHint; + if (opaqueSessionId != null) + { + _opaqueKeyExchangeService.SetActive((Guid)opaqueSessionId, user); + } + else + { + _opaqueKeyExchangeService.Unenroll(user); + } await _userRepository.ReplaceAsync(user); await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword); await _pushService.PushLogOutAsync(user.Id, true); @@ -804,6 +816,8 @@ public class UserService : UserManager, IUserService, IDisposable user.ForcePasswordReset = true; user.Key = key; + // TODO: Add support + _opaqueKeyExchangeService.Unenroll(user); await _userRepository.ReplaceAsync(user); await _mailService.SendAdminResetPasswordEmailAsync(user.Email, user.Name, org.DisplayName()); await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_AdminResetPassword); @@ -830,6 +844,8 @@ public class UserService : UserManager, IUserService, IDisposable user.Key = key; user.MasterPasswordHint = hint; + // TODO: Add support + _opaqueKeyExchangeService.Unenroll(user); await _userRepository.ReplaceAsync(user); await _mailService.SendUpdatedTempPasswordEmailAsync(user.Email, user.Name); await _eventService.LogUserEventAsync(user.Id, EventType.User_UpdatedTempPassword);