1
0
mirror of https://github.com/bitwarden/server synced 2025-12-25 04:33:26 +00:00
Files
server/src/Core/Identity/U2fTokenProvider.cs
Matt Gibson 5537470703 Use sas token for attachment downloads (#1153)
* Get limited life attachment download URL

This change limits url download to a 1min lifetime.
This requires moving to a new container to allow for non-public blob
access.

Clients will have to call GetAttachmentData api function to receive the download
URL. For backwards compatibility, attachment URLs are still present, but will not
work for attachments stored in non-public access blobs.

* Make GlobalSettings interface for testing

* Test LocalAttachmentStorageService equivalence

* Remove comment

* Add missing globalSettings using

* Simplify default attachment container

* Default to attachments containe for existing methods

A new upload method will be made for uploading to attachments-v2.
For compatibility for clients which don't use these new methods, we need
to still use the old container. The new container will be used only for
new uploads

* Remove Default MetaData fixture.

* Keep attachments container blob-level security for all instances

* Close unclosed FileStream

* Favor default value for noop services
2021-02-22 15:35:16 -06:00

222 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 Bit.Core.Settings;
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 userService.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.U2f, user);
}
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;
}
}
}