using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Bit.Core.Models.Table; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Repositories; using Newtonsoft.Json; using System.Collections.Generic; using System.Linq; using U2fLib = U2F.Core.Crypto.U2F; using U2F.Core.Models; using U2F.Core.Exceptions; using U2F.Core.Utils; using System; using Bit.Core.Services; using Microsoft.Extensions.DependencyInjection; namespace Bit.Core.Identity { public class U2fTokenProvider : IUserTwoFactorTokenProvider { private readonly IServiceProvider _serviceProvider; private readonly IU2fRepository _u2fRepository; private readonly GlobalSettings _globalSettings; public U2fTokenProvider( IServiceProvider serviceProvider, IU2fRepository u2fRepository, GlobalSettings globalSettings) { _serviceProvider = serviceProvider; _u2fRepository = u2fRepository; _globalSettings = globalSettings; } public async Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) { var userService = _serviceProvider.GetRequiredService(); if(!(await userService.CanAccessPremium(user))) { return false; } var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f); if(!HasProperMetaData(provider)) { return false; } return await user.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.U2f, userService); } public async Task GenerateAsync(string purpose, UserManager manager, User user) { var userService = _serviceProvider.GetRequiredService(); if(!(await userService.CanAccessPremium(user))) { return null; } var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f); var keys = LoadKeys(provider); if(keys.Count == 0) { return null; } await _u2fRepository.DeleteManyByUserIdAsync(user.Id); try { var challengeBytes = U2fLib.Crypto.GenerateChallenge(); var appId = Utilities.CoreHelpers.U2fAppIdUrl(_globalSettings); var oldChallenges = new List(); var challengeKeys = new List(); foreach(var key in keys) { var registration = new DeviceRegistration(key.Item2.KeyHandleBytes, key.Item2.PublicKeyBytes, key.Item2.CertificateBytes, key.Item2.Counter); var auth = U2fLib.StartAuthentication(appId, registration, challengeBytes); // TODO: Maybe move this to a bulk create? await _u2fRepository.CreateAsync(new U2f { AppId = auth.AppId, Challenge = auth.Challenge, KeyHandle = auth.KeyHandle, Version = auth.Version, UserId = user.Id, CreationDate = DateTime.UtcNow }); challengeKeys.Add(new { keyHandle = auth.KeyHandle, version = auth.Version }); // TODO: Old challenges array is here for backwards compat. Remove in the future. oldChallenges.Add(new { appId = auth.AppId, challenge = auth.Challenge, keyHandle = auth.KeyHandle, version = auth.Version }); } var oldToken = JsonConvert.SerializeObject(oldChallenges); var token = JsonConvert.SerializeObject(new { appId = appId, challenge = challengeBytes.ByteArrayToBase64String(), keys = challengeKeys }); return $"{token}|{oldToken}"; } catch(U2fException) { return null; } } public async Task ValidateAsync(string purpose, string token, UserManager manager, User user) { var userService = _serviceProvider.GetRequiredService(); if(!(await userService.CanAccessPremium(user)) || string.IsNullOrWhiteSpace(token)) { return false; } var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f); var keys = LoadKeys(provider); if(keys.Count == 0) { return false; } var authenticateResponse = BaseModel.FromJson(token); var key = keys.FirstOrDefault(f => f.Item2.KeyHandle == authenticateResponse.KeyHandle); if(key == null) { return false; } var challenges = await _u2fRepository.GetManyByUserIdAsync(user.Id); if(challenges.Count == 0) { return false; } // User will have a authentication request for each device they have registered so get the one that matches // the device key handle var challenge = challenges.FirstOrDefault(c => c.KeyHandle == authenticateResponse.KeyHandle); if(challenge == null) { return false; } var success = true; var registration = new DeviceRegistration(key.Item2.KeyHandleBytes, key.Item2.PublicKeyBytes, key.Item2.CertificateBytes, key.Item2.Counter); try { var auth = new StartedAuthentication(challenge.Challenge, challenge.AppId, challenge.KeyHandle); U2fLib.FinishAuthentication(auth, authenticateResponse, registration); } catch(U2fException) { success = false; } // Update database await _u2fRepository.DeleteManyByUserIdAsync(user.Id); key.Item2.Counter = registration.Counter; if(key.Item2.Counter > 0) { key.Item2.Compromised = registration.IsCompromised; } var providers = user.GetTwoFactorProviders(); providers[TwoFactorProviderType.U2f].MetaData[key.Item1] = key.Item2; user.SetTwoFactorProviders(providers); await manager.UpdateAsync(user); return success; } private bool HasProperMetaData(TwoFactorProvider provider) { return (provider?.MetaData?.Count ?? 0) > 0; } private List> LoadKeys(TwoFactorProvider provider) { var keys = new List>(); if(!HasProperMetaData(provider)) { return keys; } // Support up to 5 keys for(var i = 1; i <= 5; i++) { var keyName = $"Key{i}"; if(provider.MetaData.ContainsKey(keyName)) { var key = new TwoFactorProvider.U2fMetaData((dynamic)provider.MetaData[keyName]); if(!key?.Compromised ?? false) { keys.Add(new Tuple(keyName, key)); } } } return keys; } } }