1
0
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:
Kyle Spearrin 2017-06-21 22:33:45 -04:00
parent fd5e2c9466
commit 731a1e31b9
12 changed files with 149 additions and 47 deletions

View File

@ -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;
}

View File

@ -45,6 +45,9 @@
},
"duo": {
"aKey": "SECRET"
},
"u2f": {
"appId": "https://bitwarden.com"
}
},
"IpRateLimitOptions": {

View File

@ -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" />

View File

@ -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; }
}
}
}

View File

@ -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

View File

@ -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; }

View 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; }
}
}

View File

@ -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; }
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -41,6 +41,9 @@
},
"duo": {
"aKey": "SECRET"
},
"u2f": {
"appId": "https://bitwarden.com"
}
}
}