1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-06 13:38:13 -05:00

Add demo authentication and cleanup controller

This commit is contained in:
Bernd Schoolmann 2025-03-14 14:16:47 +01:00
parent 3cd3495a45
commit 0b34f09fc7
No known key found for this signature in database
7 changed files with 216 additions and 26 deletions

View File

@ -24,19 +24,36 @@ public class OpaqueKeyExchangeController : Controller
}
[HttpPost("~/opaque/start-registration")]
public async Task<OpaqueRegistrationStartResponse> StartRegistration([FromBody] OpaqueRegistrationStartRequest request)
public async Task<OpaqueRegistrationStartResponse> StartRegistrationAsync([FromBody] OpaqueRegistrationStartRequest request)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var result = await _opaqueKeyExchangeService.StartRegistration(System.Convert.FromBase64String(request.RegistrationRequest), user, request.CipherConfiguration);
return new OpaqueRegistrationStartResponse(result.Item1, System.Convert.ToBase64String(result.Item2));
var result = await _opaqueKeyExchangeService.StartRegistration(Convert.FromBase64String(request.RegistrationRequest), user, request.CipherConfiguration);
return new OpaqueRegistrationStartResponse(result.Item1, Convert.ToBase64String(result.Item2));
}
[HttpPost("~/opaque/finish-registration")]
public async Task<String> FinishRegistration([FromBody] OpaqueRegistrationFinishRequest request)
public async void FinishRegistrationAsync([FromBody] OpaqueRegistrationFinishRequest request)
{
await Task.Run(() => { });
return "";
var user = await _userService.GetUserByPrincipalAsync(User);
_opaqueKeyExchangeService.FinishRegistration(request.SessionId, Convert.FromBase64String(request.RegistrationUpload), user);
}
// TODO: Remove and move to token endpoint
[HttpPost("~/opaque/start-login")]
public async Task<OpaqueLoginStartResponse> StartLoginAsync([FromBody] OpaqueLoginStartRequest request)
{
var result = await _opaqueKeyExchangeService.StartLogin(Convert.FromBase64String(request.CredentialRequest), request.Email);
return new OpaqueLoginStartResponse(result.Item1, Convert.ToBase64String(result.Item2));
}
// TODO: Remove and move to token endpoint
[HttpPost("~/opaque/finish-login")]
public async Task<bool> FinishLoginAsync([FromBody] OpaqueLoginFinishRequest request)
{
var result = await _opaqueKeyExchangeService.FinishLogin(request.SessionId, Convert.FromBase64String(request.CredentialFinalization));
return result;
}
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Opaque;
public class OpaqueLoginFinishRequest
{
[Required]
public string CredentialFinalization { get; set; }
[Required]
public Guid SessionId { get; set; }
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Opaque;
public class OpaqueLoginStartRequest
{
[Required]
public string Email { get; set; }
[Required]
public string CredentialRequest { get; set; }
}

View File

@ -5,7 +5,7 @@ namespace Bit.Api.Auth.Models.Request.Opaque;
public class OpaqueRegistrationFinishRequest
{
[Required]
public String RegistrationUpload { get; set; }
public string RegistrationUpload { get; set; }
[Required]
public Guid SessionId { get; set; }
@ -15,9 +15,9 @@ public class OpaqueRegistrationFinishRequest
public class RotateableKeyset
{
[Required]
public String EncryptedUserKey { get; set; }
public string EncryptedUserKey { get; set; }
[Required]
public String EncryptedPublicKey { get; set; }
public string EncryptedPublicKey { get; set; }
[Required]
public String EncryptedPrivateKey { get; set; }
public string EncryptedPrivateKey { get; set; }
}

View File

@ -0,0 +1,17 @@
using Bit.Core.Models.Api;
namespace Bit.Api.Auth.Models.Response.Opaque;
public class OpaqueLoginStartResponse : ResponseModel
{
public OpaqueLoginStartResponse(Guid sessionId, string credentialResponse, string obj = "login-start-response")
: base(obj)
{
CredentialResponse = credentialResponse;
SessionId = sessionId;
}
public string CredentialResponse { get; set; }
public Guid SessionId { get; set; }
}

View File

@ -6,5 +6,9 @@ namespace Bit.Core.Auth.Services;
public interface IOpaqueKeyExchangeService
{
public Task<(Guid, byte[])> StartRegistration(byte[] request, User user, CipherConfiguration cipherConfiguration);
public Task<bool> FinishRegistration(Guid sessionId, byte[] request, User user);
public void FinishRegistration(Guid sessionId, byte[] registrationUpload, User user);
public Task<(Guid, byte[])> StartLogin(byte[] request, string email);
public Task<bool> FinishLogin(Guid sessionId, byte[] finishCredential);
public void SetActive(Guid sessionId, User user);
public void Unenroll(User user);
}

View File

@ -3,6 +3,8 @@ using Bitwarden.OPAQUE;
namespace Bit.Core.Auth.Services;
#nullable enable
public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
{
@ -17,32 +19,158 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
public async Task<(Guid, byte[])> StartRegistration(byte[] request, User user, CipherConfiguration cipherConfiguration)
{
var registrationRequest = _bitwardenOpaque.StartRegistration(cipherConfiguration, null, request, user.Id.ToString());
var message = registrationRequest.registrationResponse;
var serverSetup = registrationRequest.serverSetup;
// persist server setup
var sessionId = Guid.NewGuid();
SessionStore.RegisterSessions.Add(sessionId, new RegisterSession() { SessionId = sessionId, ServerSetup = serverSetup, cipherConfiguration = cipherConfiguration });
await Task.Run(() => { });
return (sessionId, message);
return await Task.Run(() =>
{
var registrationResponse = _bitwardenOpaque.StartRegistration(cipherConfiguration, null, request, user.Id.ToString());
var sessionId = Guid.NewGuid();
SessionStore.RegisterSessions.Add(sessionId, new RegisterSession() { sessionId = sessionId, serverSetup = registrationResponse.serverSetup, cipherConfiguration = cipherConfiguration, userId = user.Id });
return (sessionId, registrationResponse.registrationResponse);
});
}
public async Task<bool> FinishRegistration(Guid sessionId, byte[] request, User user)
public async void FinishRegistration(Guid sessionId, byte[] registrationUpload, User user)
{
await Task.Run(() => { });
return true;
await Task.Run(() =>
{
var cipherConfiguration = SessionStore.RegisterSessions[sessionId].cipherConfiguration;
try
{
var registrationFinish = _bitwardenOpaque.FinishRegistration(cipherConfiguration, registrationUpload);
SessionStore.RegisterSessions[sessionId].serverRegistration = registrationFinish.serverRegistration;
// todo move to changepassword
SetActive(sessionId, user);
}
catch (Exception e)
{
SessionStore.RegisterSessions.Remove(sessionId);
throw new Exception(e.Message);
}
});
}
public async Task<(Guid, byte[])> StartLogin(byte[] request, string email)
{
return await Task.Run(() =>
{
var credential = PersistentStore.Credentials.First(x => x.Value.email == email);
if (credential.Value == null)
{
// generate fake credential
throw new InvalidOperationException("User not found");
}
var cipherConfiguration = credential.Value.cipherConfiguration;
var serverSetup = credential.Value.serverSetup;
var serverRegistration = credential.Value.serverRegistration;
var loginResponse = _bitwardenOpaque.StartLogin(cipherConfiguration, serverSetup, serverRegistration, request, credential.Key.ToString());
var sessionId = Guid.NewGuid();
SessionStore.LoginSessions.Add(sessionId, new LoginSession() { userId = credential.Key, loginState = loginResponse.state });
return (sessionId, loginResponse.credentialResponse);
});
}
public async Task<bool> FinishLogin(Guid sessionId, byte[] credentialFinalization)
{
return await Task.Run(() =>
{
if (!SessionStore.LoginSessions.ContainsKey(sessionId))
{
throw new InvalidOperationException("Session not found");
}
var credential = PersistentStore.Credentials[SessionStore.LoginSessions[sessionId].userId];
if (credential == null)
{
throw new InvalidOperationException("User not found");
}
var loginState = SessionStore.LoginSessions[sessionId].loginState;
var cipherConfiguration = credential.cipherConfiguration;
try
{
var success = _bitwardenOpaque.FinishLogin(cipherConfiguration, loginState, credentialFinalization);
SessionStore.LoginSessions.Remove(sessionId);
return true;
}
catch (Exception e)
{
// print
Console.WriteLine(e.Message);
SessionStore.LoginSessions.Remove(sessionId);
return false;
}
});
}
public async void SetActive(Guid sessionId, User user)
{
await Task.Run(() =>
{
var session = SessionStore.RegisterSessions[sessionId];
if (session.userId != user.Id)
{
throw new InvalidOperationException("Session does not belong to user");
}
if (session.serverRegistration == null)
{
throw new InvalidOperationException("Session did not complete registration");
}
SessionStore.RegisterSessions.Remove(sessionId);
// to be copied to the persistent DB
var cipherConfiguration = session.cipherConfiguration;
var serverRegistration = session.serverRegistration;
var serverSetup = session.serverSetup;
if (PersistentStore.Credentials.ContainsKey(user.Id))
{
PersistentStore.Credentials.Remove(user.Id);
}
PersistentStore.Credentials.Add(user.Id, new Credential() { serverRegistration = serverRegistration, serverSetup = serverSetup, cipherConfiguration = cipherConfiguration, email = user.Email });
});
}
public async void Unenroll(User user)
{
await Task.Run(() =>
{
PersistentStore.Credentials.Remove(user.Id);
});
}
}
public class RegisterSession
{
public Guid SessionId { get; set; }
public byte[] ServerSetup { get; set; }
public CipherConfiguration cipherConfiguration { get; set; }
public required Guid sessionId { get; set; }
public required byte[] serverSetup { get; set; }
public required CipherConfiguration cipherConfiguration { get; set; }
public required Guid userId { get; set; }
public byte[]? serverRegistration { get; set; }
}
public class LoginSession
{
public required Guid userId { get; set; }
public required byte[] loginState { get; set; }
}
public class SessionStore()
{
public static Dictionary<Guid, RegisterSession> RegisterSessions = new Dictionary<Guid, RegisterSession>();
public static Dictionary<Guid, byte[]> LoginSessions = new Dictionary<Guid, byte[]>();
public static Dictionary<Guid, LoginSession> LoginSessions = new Dictionary<Guid, LoginSession>();
}
public class Credential
{
public required byte[] serverRegistration { get; set; }
public required byte[] serverSetup { get; set; }
public required CipherConfiguration cipherConfiguration { get; set; }
public required string email { get; set; }
}
public class PersistentStore()
{
public static Dictionary<Guid, Credential> Credentials = new Dictionary<Guid, Credential>();
}