mirror of
https://github.com/bitwarden/server.git
synced 2025-04-05 05:00:19 -05:00
feat : fix database script; add comments.
This commit is contained in:
parent
11fcb26778
commit
9848d53683
@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.Auth.Models.Api.Request.Opaque;
|
using Bit.Api.Auth.Models.Request.Opaque;
|
||||||
|
using Bit.Api.Auth.Models.Response.Opaque;
|
||||||
|
using Bit.Core.Auth.Models.Api.Request.Opaque;
|
||||||
using Bit.Core.Auth.Models.Api.Response.Opaque;
|
using Bit.Core.Auth.Models.Api.Response.Opaque;
|
||||||
using Bit.Core.Auth.Services;
|
using Bit.Core.Auth.Services;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -42,15 +44,15 @@ public class OpaqueKeyExchangeController : Controller
|
|||||||
|
|
||||||
// TODO: Remove and move to token endpoint
|
// TODO: Remove and move to token endpoint
|
||||||
[HttpPost("~/opaque/start-login")]
|
[HttpPost("~/opaque/start-login")]
|
||||||
public async Task<Models.Response.Opaque.OpaqueLoginStartResponse> StartLoginAsync([FromBody] Models.Request.Opaque.OpaqueLoginStartRequest request)
|
public async Task<OpaqueLoginStartResponse> StartLoginAsync([FromBody] OpaqueLoginStartRequest request)
|
||||||
{
|
{
|
||||||
var result = await _opaqueKeyExchangeService.StartLogin(Convert.FromBase64String(request.CredentialRequest), request.Email);
|
var result = await _opaqueKeyExchangeService.StartLogin(Convert.FromBase64String(request.CredentialRequest), request.Email);
|
||||||
return new Models.Response.Opaque.OpaqueLoginStartResponse(result.Item1, Convert.ToBase64String(result.Item2));
|
return new OpaqueLoginStartResponse(result.Item1, Convert.ToBase64String(result.Item2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove and move to token endpoint
|
// TODO: Remove and move to token endpoint
|
||||||
[HttpPost("~/opaque/finish-login")]
|
[HttpPost("~/opaque/finish-login")]
|
||||||
public async Task<bool> FinishLoginAsync([FromBody] Models.Request.Opaque.OpaqueLoginFinishRequest request)
|
public async Task<bool> FinishLoginAsync([FromBody] OpaqueLoginFinishRequest request)
|
||||||
{
|
{
|
||||||
var result = await _opaqueKeyExchangeService.FinishLogin(request.SessionId, Convert.FromBase64String(request.CredentialFinalization));
|
var result = await _opaqueKeyExchangeService.FinishLogin(request.SessionId, Convert.FromBase64String(request.CredentialFinalization));
|
||||||
return result;
|
return result;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Bitwarden.Opaque;
|
||||||
|
|
||||||
namespace Bit.Core.Auth.Models.Api.Request.Opaque;
|
namespace Bit.Core.Auth.Models.Api.Request.Opaque;
|
||||||
|
|
||||||
@ -8,10 +9,10 @@ public class OpaqueRegistrationStartRequest
|
|||||||
[Required]
|
[Required]
|
||||||
public string RegistrationRequest { get; set; }
|
public string RegistrationRequest { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
public CipherConfiguration CipherConfiguration { get; set; }
|
public OpaqueKeyExchangeCipherConfiguration CipherConfiguration { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CipherConfiguration
|
public class OpaqueKeyExchangeCipherConfiguration
|
||||||
{
|
{
|
||||||
static string OpaqueKe3Ristretto3DHArgonSuite = "OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF";
|
static string OpaqueKe3Ristretto3DHArgonSuite = "OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF";
|
||||||
|
|
||||||
@ -20,20 +21,20 @@ public class CipherConfiguration
|
|||||||
[Required]
|
[Required]
|
||||||
public Argon2KsfParameters Argon2Parameters { get; set; }
|
public Argon2KsfParameters Argon2Parameters { get; set; }
|
||||||
|
|
||||||
public Bitwarden.Opaque.CipherConfiguration ToNativeConfiguration()
|
public CipherConfiguration ToNativeConfiguration()
|
||||||
{
|
{
|
||||||
if (CipherSuite == OpaqueKe3Ristretto3DHArgonSuite)
|
if (CipherSuite == OpaqueKe3Ristretto3DHArgonSuite)
|
||||||
{
|
{
|
||||||
return new Bitwarden.Opaque.CipherConfiguration
|
return new CipherConfiguration
|
||||||
{
|
{
|
||||||
OpaqueVersion = 3,
|
OpaqueVersion = 3,
|
||||||
OprfCs = Bitwarden.Opaque.OprfCs.Ristretto255,
|
OprfCs = OprfCs.Ristretto255,
|
||||||
KeGroup = Bitwarden.Opaque.KeGroup.Ristretto255,
|
KeGroup = KeGroup.Ristretto255,
|
||||||
KeyExchange = Bitwarden.Opaque.KeyExchange.TripleDH,
|
KeyExchange = KeyExchange.TripleDH,
|
||||||
Ksf = new Bitwarden.Opaque.Ksf
|
Ksf = new Ksf
|
||||||
{
|
{
|
||||||
Algorithm = Bitwarden.Opaque.KsfAlgorithm.Argon2id,
|
Algorithm = KsfAlgorithm.Argon2id,
|
||||||
Parameters = new Bitwarden.Opaque.KsfParameters
|
Parameters = new KsfParameters
|
||||||
{
|
{
|
||||||
Iterations = Argon2Parameters.Iterations,
|
Iterations = Argon2Parameters.Iterations,
|
||||||
Memory = Argon2Parameters.Memory,
|
Memory = Argon2Parameters.Memory,
|
||||||
|
@ -4,12 +4,57 @@ using Bit.Core.Entities;
|
|||||||
|
|
||||||
namespace Bit.Core.Auth.Services;
|
namespace Bit.Core.Auth.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service that exposes methods enabling the use of the Opaque Key Exchange extension.
|
||||||
|
/// </summary>
|
||||||
public interface IOpaqueKeyExchangeService
|
public interface IOpaqueKeyExchangeService
|
||||||
{
|
{
|
||||||
public Task<OpaqueRegistrationStartResponse> StartRegistration(byte[] request, User user, CipherConfiguration cipherConfiguration);
|
/// <summary>
|
||||||
|
/// Begin registering a user's Opaque Key Exchange Credential. We write to the distributed cache so since there is some back and forth between the client and server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">unsure what this byte array is for.</param>
|
||||||
|
/// <param name="user">user being acted on</param>
|
||||||
|
/// <param name="cipherConfiguration">configuration shared between the client and server to ensure the proper crypto-algorithms are being utilized.</param>
|
||||||
|
/// <returns>void</returns>
|
||||||
|
public Task<OpaqueRegistrationStartResponse> StartRegistration(byte[] request, User user, OpaqueKeyExchangeCipherConfiguration cipherConfiguration);
|
||||||
|
/// <summary>
|
||||||
|
/// This doesn't actually finish registration. It updates the cache with the server setup and cipher configuration so that the clearly named "SetActive" method can finish registration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sessionId">Cache Id</param>
|
||||||
|
/// <param name="registrationUpload">Byte Array for Rust Magic</param>
|
||||||
|
/// <param name="user">User being acted on</param>
|
||||||
|
/// <param name="keyset">Key Pair that can be used for vault decryption</param>
|
||||||
|
/// <returns>void</returns>
|
||||||
public Task FinishRegistration(Guid sessionId, byte[] registrationUpload, User user, RotateableOpaqueKeyset keyset);
|
public Task FinishRegistration(Guid sessionId, byte[] registrationUpload, User user, RotateableOpaqueKeyset keyset);
|
||||||
|
/// <summary>
|
||||||
|
/// Returns server crypto material for the client to consume and reply with a login request to the identity/token endpoint.
|
||||||
|
/// To protect against account enumeration we will always return a deterministic response based on the user's email.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">client crypto material</param>
|
||||||
|
/// <param name="email">user email trying to login</param>
|
||||||
|
/// <returns>tuple(login SessionId for cache lookup, Server crypto material)</returns>
|
||||||
public Task<(Guid, byte[])> StartLogin(byte[] request, string email);
|
public Task<(Guid, byte[])> StartLogin(byte[] request, string email);
|
||||||
|
/// <summary>
|
||||||
|
/// Accepts the client's login request and validates it against the server's crypto material. If successful then the user is logged in.
|
||||||
|
/// If using a fake account we will return a standard failed login. If the account does have a legitimate credential but is still invalid
|
||||||
|
/// we will return a failed login.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sessionId"></param>
|
||||||
|
/// <param name="finishCredential"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public Task<bool> FinishLogin(Guid sessionId, byte[] finishCredential);
|
public Task<bool> FinishLogin(Guid sessionId, byte[] finishCredential);
|
||||||
|
/// <summary>
|
||||||
|
/// This is where registration really finishes. This method writes the Credential to the database. If a credential already exists then it will be removed before the new one is added.
|
||||||
|
/// A user can only have one credential.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sessionId">cache value</param>
|
||||||
|
/// <param name="user">user being acted on</param>
|
||||||
|
/// <returns>void</returns>
|
||||||
public Task SetActive(Guid sessionId, User user);
|
public Task SetActive(Guid sessionId, User user);
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the credential for the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">user being acted on</param>
|
||||||
|
/// <returns>void</returns>
|
||||||
public Task Unenroll(User user);
|
public Task Unenroll(User user);
|
||||||
}
|
}
|
||||||
|
@ -36,12 +36,12 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
|
|||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OpaqueRegistrationStartResponse> StartRegistration(byte[] request, User user, Models.Api.Request.Opaque.CipherConfiguration cipherConfiguration)
|
public async Task<OpaqueRegistrationStartResponse> StartRegistration(byte[] request, User user, OpaqueKeyExchangeCipherConfiguration cipherConfiguration)
|
||||||
{
|
{
|
||||||
var registrationRequest = _bitwardenOpaque.StartRegistration(cipherConfiguration.ToNativeConfiguration(), null, request, user.Id.ToString());
|
var registrationRequest = _bitwardenOpaque.StartRegistration(cipherConfiguration.ToNativeConfiguration(), null, request, user.Id.ToString());
|
||||||
|
|
||||||
var sessionId = Guid.NewGuid();
|
var sessionId = Guid.NewGuid();
|
||||||
var registerSession = new RegisterSession() { SessionId = sessionId, ServerSetup = registrationRequest.serverSetup, CipherConfiguration = cipherConfiguration, UserId = user.Id };
|
var registerSession = new OpaqueKeyExchangeRegisterSession() { SessionId = sessionId, ServerSetup = registrationRequest.serverSetup, CipherConfiguration = cipherConfiguration, UserId = user.Id };
|
||||||
await _distributedCache.SetAsync(string.Format(REGISTER_SESSION_KEY, sessionId), Encoding.ASCII.GetBytes(JsonSerializer.Serialize(registerSession)));
|
await _distributedCache.SetAsync(string.Format(REGISTER_SESSION_KEY, sessionId), Encoding.ASCII.GetBytes(JsonSerializer.Serialize(registerSession)));
|
||||||
|
|
||||||
return new OpaqueRegistrationStartResponse(sessionId, Convert.ToBase64String(registrationRequest.registrationResponse));
|
return new OpaqueRegistrationStartResponse(sessionId, Convert.ToBase64String(registrationRequest.registrationResponse));
|
||||||
@ -57,7 +57,7 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var registerSession = JsonSerializer.Deserialize<RegisterSession>(Encoding.ASCII.GetString(serializedRegisterSession))!;
|
var registerSession = JsonSerializer.Deserialize<OpaqueKeyExchangeRegisterSession>(Encoding.ASCII.GetString(serializedRegisterSession))!;
|
||||||
var registrationFinish = _bitwardenOpaque.FinishRegistration(registerSession.CipherConfiguration.ToNativeConfiguration(), registrationUpload);
|
var registrationFinish = _bitwardenOpaque.FinishRegistration(registerSession.CipherConfiguration.ToNativeConfiguration(), registrationUpload);
|
||||||
registerSession.PasswordFile = registrationFinish.serverRegistration;
|
registerSession.PasswordFile = registrationFinish.serverRegistration;
|
||||||
registerSession.KeySet = Encoding.ASCII.GetBytes(JsonSerializer.Serialize(keyset));
|
registerSession.KeySet = Encoding.ASCII.GetBytes(JsonSerializer.Serialize(keyset));
|
||||||
@ -86,14 +86,18 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
|
|||||||
throw new InvalidOperationException("Credential not found");
|
throw new InvalidOperationException("Credential not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
var cipherConfiguration = JsonSerializer.Deserialize<Models.Api.Request.Opaque.CipherConfiguration>(credential.CipherConfiguration)!;
|
var cipherConfiguration = JsonSerializer.Deserialize<OpaqueKeyExchangeCipherConfiguration>(credential.CipherConfiguration)!;
|
||||||
var credentialBlob = JsonSerializer.Deserialize<OpaqueKeyExchangeCredentialBlob>(credential.CredentialBlob)!;
|
var credentialBlob = JsonSerializer.Deserialize<OpaqueKeyExchangeCredentialBlob>(credential.CredentialBlob)!;
|
||||||
var serverSetup = credentialBlob.ServerSetup;
|
var serverSetup = credentialBlob.ServerSetup;
|
||||||
var serverRegistration = credentialBlob.PasswordFile;
|
var serverRegistration = credentialBlob.PasswordFile;
|
||||||
|
|
||||||
var loginResponse = _bitwardenOpaque.StartLogin(cipherConfiguration.ToNativeConfiguration(), serverSetup, serverRegistration, request, user.Id.ToString());
|
var loginResponse = _bitwardenOpaque.StartLogin(cipherConfiguration.ToNativeConfiguration(), serverSetup, serverRegistration, request, user.Id.ToString());
|
||||||
var sessionId = Guid.NewGuid();
|
var sessionId = Guid.NewGuid();
|
||||||
var loginSession = new LoginSession() { UserId = user.Id, LoginState = loginResponse.state, CipherConfiguration = cipherConfiguration };
|
var loginSession = new OpaqueKeyExchangeLoginSession() {
|
||||||
|
UserId = user.Id,
|
||||||
|
LoginState = loginResponse.state,
|
||||||
|
CipherConfiguration = cipherConfiguration
|
||||||
|
};
|
||||||
await _distributedCache.SetAsync(string.Format(LOGIN_SESSION_KEY, sessionId), Encoding.ASCII.GetBytes(JsonSerializer.Serialize(loginSession)));
|
await _distributedCache.SetAsync(string.Format(LOGIN_SESSION_KEY, sessionId), Encoding.ASCII.GetBytes(JsonSerializer.Serialize(loginSession)));
|
||||||
return (sessionId, loginResponse.credentialResponse);
|
return (sessionId, loginResponse.credentialResponse);
|
||||||
}
|
}
|
||||||
@ -105,7 +109,7 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException("Session not found");
|
throw new InvalidOperationException("Session not found");
|
||||||
}
|
}
|
||||||
var loginSession = JsonSerializer.Deserialize<LoginSession>(Encoding.ASCII.GetString(serializedLoginSession))!;
|
var loginSession = JsonSerializer.Deserialize<OpaqueKeyExchangeLoginSession>(Encoding.ASCII.GetString(serializedLoginSession))!;
|
||||||
|
|
||||||
var credential = await _opaqueKeyExchangeCredentialRepository.GetByUserIdAsync(loginSession.UserId);
|
var credential = await _opaqueKeyExchangeCredentialRepository.GetByUserIdAsync(loginSession.UserId);
|
||||||
if (credential == null)
|
if (credential == null)
|
||||||
@ -137,7 +141,7 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException("Session not found");
|
throw new InvalidOperationException("Session not found");
|
||||||
}
|
}
|
||||||
var session = JsonSerializer.Deserialize<RegisterSession>(Encoding.ASCII.GetString(serializedRegisterSession))!;
|
var session = JsonSerializer.Deserialize<OpaqueKeyExchangeRegisterSession>(Encoding.ASCII.GetString(serializedRegisterSession))!;
|
||||||
|
|
||||||
if (session.UserId != user.Id)
|
if (session.UserId != user.Id)
|
||||||
{
|
{
|
||||||
@ -184,19 +188,19 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RegisterSession
|
public class OpaqueKeyExchangeRegisterSession
|
||||||
{
|
{
|
||||||
public required Guid SessionId { get; set; }
|
public required Guid SessionId { get; set; }
|
||||||
public required byte[] ServerSetup { get; set; }
|
public required byte[] ServerSetup { get; set; }
|
||||||
public required Models.Api.Request.Opaque.CipherConfiguration CipherConfiguration { get; set; }
|
public required OpaqueKeyExchangeCipherConfiguration CipherConfiguration { get; set; }
|
||||||
public required Guid UserId { get; set; }
|
public required Guid UserId { get; set; }
|
||||||
public byte[]? PasswordFile { get; set; }
|
public byte[]? PasswordFile { get; set; }
|
||||||
public byte[]? KeySet { get; set; }
|
public byte[]? KeySet { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LoginSession
|
public class OpaqueKeyExchangeLoginSession
|
||||||
{
|
{
|
||||||
public required Guid UserId { get; set; }
|
public required Guid UserId { get; set; }
|
||||||
public required byte[] LoginState { get; set; }
|
public required byte[] LoginState { get; set; }
|
||||||
public required Models.Api.Request.Opaque.CipherConfiguration CipherConfiguration { get; set; }
|
public required OpaqueKeyExchangeCipherConfiguration CipherConfiguration { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -816,7 +816,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
user.ForcePasswordReset = true;
|
user.ForcePasswordReset = true;
|
||||||
user.Key = key;
|
user.Key = key;
|
||||||
|
|
||||||
// TODO: Add support
|
// TODO: Add Opaque-KE support
|
||||||
await _opaqueKeyExchangeService.Unenroll(user);
|
await _opaqueKeyExchangeService.Unenroll(user);
|
||||||
await _userRepository.ReplaceAsync(user);
|
await _userRepository.ReplaceAsync(user);
|
||||||
await _mailService.SendAdminResetPasswordEmailAsync(user.Email, user.Name, org.DisplayName());
|
await _mailService.SendAdminResetPasswordEmailAsync(user.Email, user.Name, org.DisplayName());
|
||||||
@ -844,7 +844,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
user.Key = key;
|
user.Key = key;
|
||||||
user.MasterPasswordHint = hint;
|
user.MasterPasswordHint = hint;
|
||||||
|
|
||||||
// TODO: Add support
|
// TODO: Add Opaque-KE support
|
||||||
await _opaqueKeyExchangeService.Unenroll(user);
|
await _opaqueKeyExchangeService.Unenroll(user);
|
||||||
await _userRepository.ReplaceAsync(user);
|
await _userRepository.ReplaceAsync(user);
|
||||||
await _mailService.SendUpdatedTempPasswordEmailAsync(user.Email, user.Name);
|
await _mailService.SendUpdatedTempPasswordEmailAsync(user.Email, user.Name);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Bit.Api.Auth.Models.Request.Opaque;
|
||||||
|
using Bit.Api.Auth.Models.Response.Opaque;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
@ -48,6 +50,7 @@ public class AccountsController : Controller
|
|||||||
private readonly IReferenceEventService _referenceEventService;
|
private readonly IReferenceEventService _referenceEventService;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory;
|
||||||
|
private readonly IOpaqueKeyExchangeService _opaqueKeyExchangeService;
|
||||||
private readonly IOpaqueKeyExchangeCredentialRepository _opaqueKeyExchangeCredentialRepository;
|
private readonly IOpaqueKeyExchangeCredentialRepository _opaqueKeyExchangeCredentialRepository;
|
||||||
|
|
||||||
private readonly byte[] _defaultKdfHmacKey = null;
|
private readonly byte[] _defaultKdfHmacKey = null;
|
||||||
@ -98,6 +101,7 @@ public class AccountsController : Controller
|
|||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> registrationEmailVerificationTokenDataFactory,
|
IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> registrationEmailVerificationTokenDataFactory,
|
||||||
IOpaqueKeyExchangeCredentialRepository opaqueKeyExchangeCredentialRepository,
|
IOpaqueKeyExchangeCredentialRepository opaqueKeyExchangeCredentialRepository,
|
||||||
|
IOpaqueKeyExchangeService opaqueKeyExchangeService,
|
||||||
GlobalSettings globalSettings
|
GlobalSettings globalSettings
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@ -112,6 +116,7 @@ public class AccountsController : Controller
|
|||||||
_referenceEventService = referenceEventService;
|
_referenceEventService = referenceEventService;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
_registrationEmailVerificationTokenDataFactory = registrationEmailVerificationTokenDataFactory;
|
_registrationEmailVerificationTokenDataFactory = registrationEmailVerificationTokenDataFactory;
|
||||||
|
_opaqueKeyExchangeService = opaqueKeyExchangeService;
|
||||||
_opaqueKeyExchangeCredentialRepository = opaqueKeyExchangeCredentialRepository;
|
_opaqueKeyExchangeCredentialRepository = opaqueKeyExchangeCredentialRepository;
|
||||||
|
|
||||||
if (CoreHelpers.SettingHasValue(globalSettings.KdfDefaultHashKey))
|
if (CoreHelpers.SettingHasValue(globalSettings.KdfDefaultHashKey))
|
||||||
@ -204,33 +209,27 @@ public class AccountsController : Controller
|
|||||||
model.EmailVerificationToken);
|
model.EmailVerificationToken);
|
||||||
|
|
||||||
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
||||||
break;
|
|
||||||
case RegisterFinishTokenType.OrganizationInvite:
|
case RegisterFinishTokenType.OrganizationInvite:
|
||||||
identityResult = await _registerUserCommand.RegisterUserViaOrganizationInviteToken(user, model.MasterPasswordHash,
|
identityResult = await _registerUserCommand.RegisterUserViaOrganizationInviteToken(user, model.MasterPasswordHash,
|
||||||
model.OrgInviteToken, model.OrganizationUserId);
|
model.OrgInviteToken, model.OrganizationUserId);
|
||||||
|
|
||||||
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
||||||
break;
|
|
||||||
case RegisterFinishTokenType.OrgSponsoredFreeFamilyPlan:
|
case RegisterFinishTokenType.OrgSponsoredFreeFamilyPlan:
|
||||||
identityResult = await _registerUserCommand.RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken(user, model.MasterPasswordHash, model.OrgSponsoredFreeFamilyPlanToken);
|
identityResult = await _registerUserCommand.RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken(user, model.MasterPasswordHash, model.OrgSponsoredFreeFamilyPlanToken);
|
||||||
|
|
||||||
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
||||||
break;
|
|
||||||
case RegisterFinishTokenType.EmergencyAccessInvite:
|
case RegisterFinishTokenType.EmergencyAccessInvite:
|
||||||
Debug.Assert(model.AcceptEmergencyAccessId.HasValue);
|
Debug.Assert(model.AcceptEmergencyAccessId.HasValue);
|
||||||
identityResult = await _registerUserCommand.RegisterUserViaAcceptEmergencyAccessInviteToken(user, model.MasterPasswordHash,
|
identityResult = await _registerUserCommand.RegisterUserViaAcceptEmergencyAccessInviteToken(user, model.MasterPasswordHash,
|
||||||
model.AcceptEmergencyAccessInviteToken, model.AcceptEmergencyAccessId.Value);
|
model.AcceptEmergencyAccessInviteToken, model.AcceptEmergencyAccessId.Value);
|
||||||
|
|
||||||
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
||||||
break;
|
|
||||||
case RegisterFinishTokenType.ProviderInvite:
|
case RegisterFinishTokenType.ProviderInvite:
|
||||||
Debug.Assert(model.ProviderUserId.HasValue);
|
Debug.Assert(model.ProviderUserId.HasValue);
|
||||||
identityResult = await _registerUserCommand.RegisterUserViaProviderInviteToken(user, model.MasterPasswordHash,
|
identityResult = await _registerUserCommand.RegisterUserViaProviderInviteToken(user, model.MasterPasswordHash,
|
||||||
model.ProviderInviteToken, model.ProviderUserId.Value);
|
model.ProviderInviteToken, model.ProviderUserId.Value);
|
||||||
|
|
||||||
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new BadRequestException("Invalid registration finish request");
|
throw new BadRequestException("Invalid registration finish request");
|
||||||
}
|
}
|
||||||
@ -270,7 +269,7 @@ public class AccountsController : Controller
|
|||||||
var credential = await _opaqueKeyExchangeCredentialRepository.GetByUserIdAsync(user.Id);
|
var credential = await _opaqueKeyExchangeCredentialRepository.GetByUserIdAsync(user.Id);
|
||||||
if (credential != null)
|
if (credential != null)
|
||||||
{
|
{
|
||||||
return new PreloginResponseModel(kdfInformation, JsonSerializer.Deserialize<CipherConfiguration>(credential.CipherConfiguration)!);
|
return new PreloginResponseModel(kdfInformation, JsonSerializer.Deserialize<OpaqueKeyExchangeCipherConfiguration>(credential.CipherConfiguration)!);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -293,6 +292,14 @@ public class AccountsController : Controller
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("opaque-ke/start-login")]
|
||||||
|
[RequireFeature(FeatureFlagKeys.OpaqueKeyExchange)]
|
||||||
|
public async Task<OpaqueLoginStartResponse> GetOpaqueKeyExchangeStartLoginMaterial([FromBody] OpaqueLoginStartRequest request)
|
||||||
|
{
|
||||||
|
var result = await _opaqueKeyExchangeService.StartLogin(Convert.FromBase64String(request.CredentialRequest), request.Email);
|
||||||
|
return new OpaqueLoginStartResponse(result.Item1, Convert.ToBase64String(result.Item2));
|
||||||
|
}
|
||||||
|
|
||||||
private UserKdfInformation GetDefaultKdf(string email)
|
private UserKdfInformation GetDefaultKdf(string email)
|
||||||
{
|
{
|
||||||
if (_defaultKdfHmacKey == null)
|
if (_defaultKdfHmacKey == null)
|
||||||
|
@ -6,7 +6,7 @@ namespace Bit.Identity.Models.Response.Accounts;
|
|||||||
|
|
||||||
public class PreloginResponseModel
|
public class PreloginResponseModel
|
||||||
{
|
{
|
||||||
public PreloginResponseModel(UserKdfInformation kdfInformation, CipherConfiguration opaqueConfiguration)
|
public PreloginResponseModel(UserKdfInformation kdfInformation, OpaqueKeyExchangeCipherConfiguration opaqueConfiguration)
|
||||||
{
|
{
|
||||||
Kdf = kdfInformation.Kdf;
|
Kdf = kdfInformation.Kdf;
|
||||||
KdfIterations = kdfInformation.KdfIterations;
|
KdfIterations = kdfInformation.KdfIterations;
|
||||||
@ -19,5 +19,5 @@ public class PreloginResponseModel
|
|||||||
public int KdfIterations { get; set; }
|
public int KdfIterations { get; set; }
|
||||||
public int? KdfMemory { get; set; }
|
public int? KdfMemory { get; set; }
|
||||||
public int? KdfParallelism { get; set; }
|
public int? KdfParallelism { get; set; }
|
||||||
public CipherConfiguration OpaqueConfiguration { get; set; }
|
public OpaqueKeyExchangeCipherConfiguration OpaqueConfiguration { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -25,15 +25,13 @@ public class OpaqueKeyExchangeCredentialRepository : Repository<OpaqueKeyExchang
|
|||||||
|
|
||||||
public async Task<OpaqueKeyExchangeCredential?> GetByUserIdAsync(Guid userId)
|
public async Task<OpaqueKeyExchangeCredential?> GetByUserIdAsync(Guid userId)
|
||||||
{
|
{
|
||||||
using (var connection = new SqlConnection(ConnectionString))
|
using var connection = new SqlConnection(ConnectionString);
|
||||||
{
|
var results = await connection.QueryAsync<OpaqueKeyExchangeCredential>(
|
||||||
var results = await connection.QueryAsync<OpaqueKeyExchangeCredential>(
|
$"[{Schema}].[{Table}_ReadByUserId]",
|
||||||
$"[{Schema}].[{Table}_ReadByUserId]",
|
new { UserId = userId },
|
||||||
new { UserId = userId },
|
commandType: CommandType.StoredProcedure);
|
||||||
commandType: CommandType.StoredProcedure);
|
|
||||||
|
|
||||||
return results.FirstOrDefault();
|
return results.FirstOrDefault();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - How do we want to handle rotation?
|
// TODO - How do we want to handle rotation?
|
||||||
|
@ -8,7 +8,7 @@ CREATE TABLE [dbo].[OpaqueKeyExchangeCredential]
|
|||||||
[EncryptedPrivateKey] VARCHAR(MAX) NOT NULL,
|
[EncryptedPrivateKey] VARCHAR(MAX) NOT NULL,
|
||||||
[EncryptedUserKey] VARCHAR(MAX) NULL,
|
[EncryptedUserKey] VARCHAR(MAX) NULL,
|
||||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||||
CONSTRAINT [PK_OpaqueKeyExchangeCredential] PRIMARY KEY CLUSTERED ([Id] ASC),
|
CONSTRAINT [PK_OpaqueKeyExchangeCredential] PRIMARY KEY CLUSTERED ([UserId]), -- using this as the primary key ensure users only have one credential
|
||||||
CONSTRAINT [FK_OpaqueKeyExchangeCredential_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])
|
CONSTRAINT [FK_OpaqueKeyExchangeCredential_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user