mirror of
https://github.com/bitwarden/server.git
synced 2025-04-21 21:15:10 -05:00
initiating u2f registration
This commit is contained in:
parent
fd5e2c9466
commit
731a1e31b9
@ -116,17 +116,26 @@ namespace Bit.Api.Controllers
|
|||||||
public async Task<TwoFactorU2fResponseModel> GetU2f([FromBody]TwoFactorRequestModel model)
|
public async Task<TwoFactorU2fResponseModel> GetU2f([FromBody]TwoFactorRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckPasswordAsync(model.MasterPasswordHash);
|
var user = await CheckPasswordAsync(model.MasterPasswordHash);
|
||||||
var response = new TwoFactorU2fResponseModel(user);
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
|
||||||
|
if(!provider.Enabled || (provider?.MetaData != null && provider.MetaData.Count > 0))
|
||||||
|
{
|
||||||
|
var reg = await _userService.StartU2fRegistrationAsync(user);
|
||||||
|
var response = new TwoFactorU2fResponseModel(user, provider, reg);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var response = new TwoFactorU2fResponseModel(user, provider);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPut("u2f")]
|
[HttpPut("u2f")]
|
||||||
[HttpPost("u2f")]
|
[HttpPost("u2f")]
|
||||||
public async Task<TwoFactorU2fResponseModel> PutU2f([FromBody]TwoFactorU2fRequestModel model)
|
public async Task<TwoFactorU2fResponseModel> PutU2f([FromBody]TwoFactorU2fRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckPasswordAsync(model.MasterPasswordHash);
|
var user = await CheckPasswordAsync(model.MasterPasswordHash);
|
||||||
model.ToUser(user);
|
await _userService.CompleteU2fRegistrationAsync(user, model.DeviceResponse);
|
||||||
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.U2f);
|
|
||||||
var response = new TwoFactorU2fResponseModel(user);
|
var response = new TwoFactorU2fResponseModel(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,9 @@
|
|||||||
},
|
},
|
||||||
"duo": {
|
"duo": {
|
||||||
"aKey": "SECRET"
|
"aKey": "SECRET"
|
||||||
|
},
|
||||||
|
"u2f": {
|
||||||
|
"appId": "https://bitwarden.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"IpRateLimitOptions": {
|
"IpRateLimitOptions": {
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" />
|
<PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.AzureDocumentDB" Version="3.6.1" />
|
<PackageReference Include="Serilog.Sinks.AzureDocumentDB" Version="3.6.1" />
|
||||||
<PackageReference Include="Stripe.net" Version="7.8.0" />
|
<PackageReference Include="Stripe.net" Version="7.8.0" />
|
||||||
|
<PackageReference Include="U2F.Core" Version="1.0.1" />
|
||||||
<PackageReference Include="WindowsAzure.Storage" Version="8.1.1" />
|
<PackageReference Include="WindowsAzure.Storage" Version="8.1.1" />
|
||||||
<PackageReference Include="Otp.NET" Version="1.0.1" />
|
<PackageReference Include="Otp.NET" Version="1.0.1" />
|
||||||
<PackageReference Include="YubicoDotNetClient" Version="1.0.0" />
|
<PackageReference Include="YubicoDotNetClient" Version="1.0.0" />
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
public virtual NotificationHubSettings NotificationHub { get; set; } = new NotificationHubSettings();
|
public virtual NotificationHubSettings NotificationHub { get; set; } = new NotificationHubSettings();
|
||||||
public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings();
|
public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings();
|
||||||
public virtual DuoSettings Duo { get; set; } = new DuoSettings();
|
public virtual DuoSettings Duo { get; set; } = new DuoSettings();
|
||||||
|
public virtual U2fSettings U2f { get; set; } = new U2fSettings();
|
||||||
|
|
||||||
public class SqlServerSettings
|
public class SqlServerSettings
|
||||||
{
|
{
|
||||||
@ -91,5 +92,10 @@
|
|||||||
{
|
{
|
||||||
public string AKey { get; set; }
|
public string AKey { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class U2fSettings
|
||||||
|
{
|
||||||
|
public string AppId { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,33 +199,6 @@ namespace Bit.Core.Models.Api
|
|||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public string DeviceResponse { get; set; }
|
public string DeviceResponse { get; set; }
|
||||||
|
|
||||||
public User ToUser(User extistingUser)
|
|
||||||
{
|
|
||||||
var providers = extistingUser.GetTwoFactorProviders();
|
|
||||||
if(providers == null)
|
|
||||||
{
|
|
||||||
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
|
||||||
}
|
|
||||||
else if(providers.ContainsKey(TwoFactorProviderType.U2f))
|
|
||||||
{
|
|
||||||
providers.Remove(TwoFactorProviderType.U2f);
|
|
||||||
}
|
|
||||||
|
|
||||||
providers.Add(TwoFactorProviderType.U2f, new TwoFactorProvider
|
|
||||||
{
|
|
||||||
MetaData = new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
["Key1"] = new TwoFactorProvider.U2fMetaData
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Enabled = true
|
|
||||||
});
|
|
||||||
extistingUser.SetTwoFactorProviders(providers);
|
|
||||||
return extistingUser;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateTwoFactorEmailRequestModel : TwoFactorEmailRequestModel
|
public class UpdateTwoFactorEmailRequestModel : TwoFactorEmailRequestModel
|
||||||
|
@ -1,11 +1,27 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
|
using Bit.Core.Models.Business;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Api
|
namespace Bit.Core.Models.Api
|
||||||
{
|
{
|
||||||
public class TwoFactorU2fResponseModel : ResponseModel
|
public class TwoFactorU2fResponseModel : ResponseModel
|
||||||
{
|
{
|
||||||
|
public TwoFactorU2fResponseModel(User user, TwoFactorProvider provider, U2fRegistration registration = null)
|
||||||
|
: base("twoFactorU2f")
|
||||||
|
{
|
||||||
|
if(user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(registration != null)
|
||||||
|
{
|
||||||
|
Challenge = new ChallengeModel(user, registration);
|
||||||
|
}
|
||||||
|
Enabled = provider.Enabled;
|
||||||
|
}
|
||||||
|
|
||||||
public TwoFactorU2fResponseModel(User user)
|
public TwoFactorU2fResponseModel(User user)
|
||||||
: base("twoFactorU2f")
|
: base("twoFactorU2f")
|
||||||
{
|
{
|
||||||
@ -15,18 +31,7 @@ namespace Bit.Core.Models.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
|
||||||
if(provider?.MetaData != null && provider.MetaData.Count > 0)
|
Enabled = provider != null && provider.Enabled;
|
||||||
{
|
|
||||||
Challenge = new ChallengeModel
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
};
|
|
||||||
Enabled = provider.Enabled;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Enabled = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChallengeModel Challenge { get; set; }
|
public ChallengeModel Challenge { get; set; }
|
||||||
@ -34,6 +39,14 @@ namespace Bit.Core.Models.Api
|
|||||||
|
|
||||||
public class ChallengeModel
|
public class ChallengeModel
|
||||||
{
|
{
|
||||||
|
public ChallengeModel(User user, U2fRegistration registration)
|
||||||
|
{
|
||||||
|
UserId = user.Id.ToString();
|
||||||
|
AppId = registration.AppId;
|
||||||
|
Challenge = registration.Challenge;
|
||||||
|
Version = registration.Version;
|
||||||
|
}
|
||||||
|
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
public string AppId { get; set; }
|
public string AppId { get; set; }
|
||||||
public string Challenge { get; set; }
|
public string Challenge { get; set; }
|
||||||
|
9
src/Core/Models/Business/U2fRegistration.cs
Normal file
9
src/Core/Models/Business/U2fRegistration.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Bit.Core.Models.Business
|
||||||
|
{
|
||||||
|
public class U2fRegistration
|
||||||
|
{
|
||||||
|
public string AppId { get; set; }
|
||||||
|
public string Challenge { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ namespace Bit.Core.Models
|
|||||||
public string KeyHandle { get; set; }
|
public string KeyHandle { get; set; }
|
||||||
public string PublicKey { get; set; }
|
public string PublicKey { get; set; }
|
||||||
public string Certificate { get; set; }
|
public string Certificate { get; set; }
|
||||||
public int Counter { get; set; }
|
public uint Counter { get; set; }
|
||||||
public bool Compromised { get; set; }
|
public bool Compromised { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using Bit.Core.Models.Table;
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
|
using Bit.Core.Models.Business;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -21,6 +22,8 @@ namespace Bit.Core.Services
|
|||||||
Task SendMasterPasswordHintAsync(string email);
|
Task SendMasterPasswordHintAsync(string email);
|
||||||
Task SendTwoFactorEmailAsync(User user);
|
Task SendTwoFactorEmailAsync(User user);
|
||||||
Task<bool> VerifyTwoFactorEmailAsync(User user, string token);
|
Task<bool> VerifyTwoFactorEmailAsync(User user, string token);
|
||||||
|
Task<U2fRegistration> StartU2fRegistrationAsync(User user);
|
||||||
|
Task<bool> CompleteU2fRegistrationAsync(User user, string deviceResponse);
|
||||||
Task InitiateEmailChangeAsync(User user, string newEmail);
|
Task InitiateEmailChangeAsync(User user, string newEmail);
|
||||||
Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword,
|
Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword,
|
||||||
string token, string key);
|
string token, string key);
|
||||||
|
@ -9,9 +9,11 @@ using Bit.Core.Repositories;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using OtpNet;
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using U2fLib = U2F.Core.Crypto.U2F;
|
||||||
|
using U2F.Core.Models;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
|
using Bit.Core.Models.Business;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -20,6 +22,7 @@ namespace Bit.Core.Services
|
|||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
private readonly ICipherRepository _cipherRepository;
|
private readonly ICipherRepository _cipherRepository;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
|
private readonly IU2fRepository _u2fRepository;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
private readonly IPushNotificationService _pushService;
|
private readonly IPushNotificationService _pushService;
|
||||||
private readonly IdentityErrorDescriber _identityErrorDescriber;
|
private readonly IdentityErrorDescriber _identityErrorDescriber;
|
||||||
@ -27,11 +30,13 @@ namespace Bit.Core.Services
|
|||||||
private readonly IPasswordHasher<User> _passwordHasher;
|
private readonly IPasswordHasher<User> _passwordHasher;
|
||||||
private readonly IEnumerable<IPasswordValidator<User>> _passwordValidators;
|
private readonly IEnumerable<IPasswordValidator<User>> _passwordValidators;
|
||||||
private readonly CurrentContext _currentContext;
|
private readonly CurrentContext _currentContext;
|
||||||
|
private readonly GlobalSettings _globalSettings;
|
||||||
|
|
||||||
public UserService(
|
public UserService(
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
ICipherRepository cipherRepository,
|
ICipherRepository cipherRepository,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
|
IU2fRepository u2fRepository,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IPushNotificationService pushService,
|
IPushNotificationService pushService,
|
||||||
IUserStore<User> store,
|
IUserStore<User> store,
|
||||||
@ -43,7 +48,8 @@ namespace Bit.Core.Services
|
|||||||
IdentityErrorDescriber errors,
|
IdentityErrorDescriber errors,
|
||||||
IServiceProvider services,
|
IServiceProvider services,
|
||||||
ILogger<UserManager<User>> logger,
|
ILogger<UserManager<User>> logger,
|
||||||
CurrentContext currentContext)
|
CurrentContext currentContext,
|
||||||
|
GlobalSettings globalSettings)
|
||||||
: base(
|
: base(
|
||||||
store,
|
store,
|
||||||
optionsAccessor,
|
optionsAccessor,
|
||||||
@ -58,6 +64,7 @@ namespace Bit.Core.Services
|
|||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_cipherRepository = cipherRepository;
|
_cipherRepository = cipherRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
|
_u2fRepository = u2fRepository;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
_pushService = pushService;
|
_pushService = pushService;
|
||||||
_identityOptions = optionsAccessor?.Value ?? new IdentityOptions();
|
_identityOptions = optionsAccessor?.Value ?? new IdentityOptions();
|
||||||
@ -65,6 +72,7 @@ namespace Bit.Core.Services
|
|||||||
_passwordHasher = passwordHasher;
|
_passwordHasher = passwordHasher;
|
||||||
_passwordValidators = passwordValidators;
|
_passwordValidators = passwordValidators;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
|
_globalSettings = globalSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid? GetProperUserId(ClaimsPrincipal principal)
|
public Guid? GetProperUserId(ClaimsPrincipal principal)
|
||||||
@ -207,6 +215,79 @@ namespace Bit.Core.Services
|
|||||||
"2faEmail:" + provider.MetaData["Email"], token);
|
"2faEmail:" + provider.MetaData["Email"], token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<U2fRegistration> StartU2fRegistrationAsync(User user)
|
||||||
|
{
|
||||||
|
await _u2fRepository.DeleteManyByUserIdAsync(user.Id);
|
||||||
|
var reg = U2fLib.StartRegistration(_globalSettings.U2f.AppId);
|
||||||
|
await _u2fRepository.CreateAsync(new U2f
|
||||||
|
{
|
||||||
|
AppId = reg.AppId,
|
||||||
|
Challenge = reg.Challenge,
|
||||||
|
Version = reg.Version,
|
||||||
|
UserId = user.Id
|
||||||
|
});
|
||||||
|
|
||||||
|
return new U2fRegistration
|
||||||
|
{
|
||||||
|
AppId = reg.AppId,
|
||||||
|
Challenge = reg.Challenge,
|
||||||
|
Version = reg.Version
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CompleteU2fRegistrationAsync(User user, string deviceResponse)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(deviceResponse))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var challenges = await _u2fRepository.GetManyByUserIdAsync(user.Id);
|
||||||
|
if(!challenges?.Any() ?? true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var registerResponse = BaseModel.FromJson<RegisterResponse>(deviceResponse);
|
||||||
|
|
||||||
|
var challenge = challenges.OrderBy(i => i.Id).Last(i => i.KeyHandle == null);
|
||||||
|
var statedReg = new StartedRegistration(challenge.Challenge, challenge.AppId);
|
||||||
|
var reg = U2fLib.FinishRegistration(statedReg, registerResponse);
|
||||||
|
|
||||||
|
await _u2fRepository.DeleteManyByUserIdAsync(user.Id);
|
||||||
|
|
||||||
|
// Add device
|
||||||
|
var providers = user.GetTwoFactorProviders();
|
||||||
|
if(providers == null)
|
||||||
|
{
|
||||||
|
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||||
|
}
|
||||||
|
else if(providers.ContainsKey(TwoFactorProviderType.U2f))
|
||||||
|
{
|
||||||
|
providers.Remove(TwoFactorProviderType.U2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
providers.Add(TwoFactorProviderType.U2f, new TwoFactorProvider
|
||||||
|
{
|
||||||
|
MetaData = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["Key1"] = new TwoFactorProvider.U2fMetaData
|
||||||
|
{
|
||||||
|
KeyHandle = reg.KeyHandle == null ? null : Convert.ToBase64String(reg.KeyHandle),
|
||||||
|
PublicKey = reg.PublicKey == null ? null : Convert.ToBase64String(reg.PublicKey),
|
||||||
|
Certificate = reg.AttestationCert == null ? null : Convert.ToBase64String(reg.AttestationCert),
|
||||||
|
Compromised = false,
|
||||||
|
Counter = reg.Counter
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Enabled = true
|
||||||
|
});
|
||||||
|
user.SetTwoFactorProviders(providers);
|
||||||
|
await UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.U2f);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task InitiateEmailChangeAsync(User user, string newEmail)
|
public async Task InitiateEmailChangeAsync(User user, string newEmail)
|
||||||
{
|
{
|
||||||
var existingUser = await _userRepository.GetByEmailAsync(newEmail);
|
var existingUser = await _userRepository.GetByEmailAsync(newEmail);
|
||||||
|
@ -32,6 +32,7 @@ namespace Bit.Core.Utilities
|
|||||||
services.AddSingleton<IFolderRepository, SqlServerRepos.FolderRepository>();
|
services.AddSingleton<IFolderRepository, SqlServerRepos.FolderRepository>();
|
||||||
services.AddSingleton<ICollectionCipherRepository, SqlServerRepos.CollectionCipherRepository>();
|
services.AddSingleton<ICollectionCipherRepository, SqlServerRepos.CollectionCipherRepository>();
|
||||||
services.AddSingleton<IGroupRepository, SqlServerRepos.GroupRepository>();
|
services.AddSingleton<IGroupRepository, SqlServerRepos.GroupRepository>();
|
||||||
|
services.AddSingleton<IU2fRepository, SqlServerRepos.U2fRepository>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddBaseServices(this IServiceCollection services)
|
public static void AddBaseServices(this IServiceCollection services)
|
||||||
|
@ -41,6 +41,9 @@
|
|||||||
},
|
},
|
||||||
"duo": {
|
"duo": {
|
||||||
"aKey": "SECRET"
|
"aKey": "SECRET"
|
||||||
|
},
|
||||||
|
"u2f": {
|
||||||
|
"appId": "https://bitwarden.com"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user