mirror of
https://github.com/bitwarden/server.git
synced 2024-11-26 12:55:17 +01:00
initiating u2f registration
This commit is contained in:
parent
fd5e2c9466
commit
731a1e31b9
@ -116,8 +116,18 @@ namespace Bit.Api.Controllers
|
||||
public async Task<TwoFactorU2fResponseModel> GetU2f([FromBody]TwoFactorRequestModel model)
|
||||
{
|
||||
var user = await CheckPasswordAsync(model.MasterPasswordHash);
|
||||
var response = new TwoFactorU2fResponseModel(user);
|
||||
return response;
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = new TwoFactorU2fResponseModel(user, provider);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("u2f")]
|
||||
@ -125,8 +135,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<TwoFactorU2fResponseModel> PutU2f([FromBody]TwoFactorU2fRequestModel model)
|
||||
{
|
||||
var user = await CheckPasswordAsync(model.MasterPasswordHash);
|
||||
model.ToUser(user);
|
||||
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.U2f);
|
||||
await _userService.CompleteU2fRegistrationAsync(user, model.DeviceResponse);
|
||||
var response = new TwoFactorU2fResponseModel(user);
|
||||
return response;
|
||||
}
|
||||
|
@ -45,6 +45,9 @@
|
||||
},
|
||||
"duo": {
|
||||
"aKey": "SECRET"
|
||||
},
|
||||
"u2f": {
|
||||
"appId": "https://bitwarden.com"
|
||||
}
|
||||
},
|
||||
"IpRateLimitOptions": {
|
||||
|
@ -55,6 +55,7 @@
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.AzureDocumentDB" Version="3.6.1" />
|
||||
<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="Otp.NET" Version="1.0.1" />
|
||||
<PackageReference Include="YubicoDotNetClient" Version="1.0.0" />
|
||||
|
@ -17,6 +17,7 @@
|
||||
public virtual NotificationHubSettings NotificationHub { get; set; } = new NotificationHubSettings();
|
||||
public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings();
|
||||
public virtual DuoSettings Duo { get; set; } = new DuoSettings();
|
||||
public virtual U2fSettings U2f { get; set; } = new U2fSettings();
|
||||
|
||||
public class SqlServerSettings
|
||||
{
|
||||
@ -91,5 +92,10 @@
|
||||
{
|
||||
public string AKey { get; set; }
|
||||
}
|
||||
|
||||
public class U2fSettings
|
||||
{
|
||||
public string AppId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,33 +199,6 @@ namespace Bit.Core.Models.Api
|
||||
{
|
||||
[Required]
|
||||
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
|
||||
|
@ -1,11 +1,27 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
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)
|
||||
: base("twoFactorU2f")
|
||||
{
|
||||
@ -15,18 +31,7 @@ namespace Bit.Core.Models.Api
|
||||
}
|
||||
|
||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
|
||||
if(provider?.MetaData != null && provider.MetaData.Count > 0)
|
||||
{
|
||||
Challenge = new ChallengeModel
|
||||
{
|
||||
// TODO
|
||||
};
|
||||
Enabled = provider.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
Enabled = false;
|
||||
}
|
||||
Enabled = provider != null && provider.Enabled;
|
||||
}
|
||||
|
||||
public ChallengeModel Challenge { get; set; }
|
||||
@ -34,6 +39,14 @@ namespace Bit.Core.Models.Api
|
||||
|
||||
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 AppId { 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 PublicKey { get; set; }
|
||||
public string Certificate { get; set; }
|
||||
public int Counter { get; set; }
|
||||
public uint Counter { get; set; }
|
||||
public bool Compromised { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using Bit.Core.Models.Table;
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@ -21,6 +22,8 @@ namespace Bit.Core.Services
|
||||
Task SendMasterPasswordHintAsync(string email);
|
||||
Task SendTwoFactorEmailAsync(User user);
|
||||
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<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword,
|
||||
string token, string key);
|
||||
|
@ -9,9 +9,11 @@ using Bit.Core.Repositories;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Bit.Core.Enums;
|
||||
using OtpNet;
|
||||
using System.Security.Claims;
|
||||
using U2fLib = U2F.Core.Crypto.U2F;
|
||||
using U2F.Core.Models;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@ -20,6 +22,7 @@ namespace Bit.Core.Services
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IU2fRepository _u2fRepository;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IPushNotificationService _pushService;
|
||||
private readonly IdentityErrorDescriber _identityErrorDescriber;
|
||||
@ -27,11 +30,13 @@ namespace Bit.Core.Services
|
||||
private readonly IPasswordHasher<User> _passwordHasher;
|
||||
private readonly IEnumerable<IPasswordValidator<User>> _passwordValidators;
|
||||
private readonly CurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public UserService(
|
||||
IUserRepository userRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IU2fRepository u2fRepository,
|
||||
IMailService mailService,
|
||||
IPushNotificationService pushService,
|
||||
IUserStore<User> store,
|
||||
@ -43,7 +48,8 @@ namespace Bit.Core.Services
|
||||
IdentityErrorDescriber errors,
|
||||
IServiceProvider services,
|
||||
ILogger<UserManager<User>> logger,
|
||||
CurrentContext currentContext)
|
||||
CurrentContext currentContext,
|
||||
GlobalSettings globalSettings)
|
||||
: base(
|
||||
store,
|
||||
optionsAccessor,
|
||||
@ -58,6 +64,7 @@ namespace Bit.Core.Services
|
||||
_userRepository = userRepository;
|
||||
_cipherRepository = cipherRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_u2fRepository = u2fRepository;
|
||||
_mailService = mailService;
|
||||
_pushService = pushService;
|
||||
_identityOptions = optionsAccessor?.Value ?? new IdentityOptions();
|
||||
@ -65,6 +72,7 @@ namespace Bit.Core.Services
|
||||
_passwordHasher = passwordHasher;
|
||||
_passwordValidators = passwordValidators;
|
||||
_currentContext = currentContext;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
public Guid? GetProperUserId(ClaimsPrincipal principal)
|
||||
@ -207,6 +215,79 @@ namespace Bit.Core.Services
|
||||
"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)
|
||||
{
|
||||
var existingUser = await _userRepository.GetByEmailAsync(newEmail);
|
||||
|
@ -32,6 +32,7 @@ namespace Bit.Core.Utilities
|
||||
services.AddSingleton<IFolderRepository, SqlServerRepos.FolderRepository>();
|
||||
services.AddSingleton<ICollectionCipherRepository, SqlServerRepos.CollectionCipherRepository>();
|
||||
services.AddSingleton<IGroupRepository, SqlServerRepos.GroupRepository>();
|
||||
services.AddSingleton<IU2fRepository, SqlServerRepos.U2fRepository>();
|
||||
}
|
||||
|
||||
public static void AddBaseServices(this IServiceCollection services)
|
||||
|
@ -41,6 +41,9 @@
|
||||
},
|
||||
"duo": {
|
||||
"aKey": "SECRET"
|
||||
},
|
||||
"u2f": {
|
||||
"appId": "https://bitwarden.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user