1
0
mirror of https://github.com/bitwarden/server synced 2025-12-26 21:23:39 +00:00
Files
server/src/Core/Identity/U2fTokenProvider.cs
2018-10-10 17:51:38 -04:00

221 lines
7.8 KiB
C#

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<User>
{
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<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
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<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
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<object>();
var challengeKeys = new List<object>();
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<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
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<AuthenticateResponse>(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<Tuple<string, TwoFactorProvider.U2fMetaData>> LoadKeys(TwoFactorProvider provider)
{
var keys = new List<Tuple<string, TwoFactorProvider.U2fMetaData>>();
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<string, TwoFactorProvider.U2fMetaData>(keyName, key));
}
}
}
return keys;
}
}
}