1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 16:12:49 -05:00

Remove the u2f lib (#1820)

This commit is contained in:
Oscar Hinton
2022-01-24 12:14:04 +01:00
committed by GitHub
parent 5268f2781e
commit ac8ca46f0f
44 changed files with 3489 additions and 1247 deletions

View File

@ -50,7 +50,6 @@
<PackageReference Include="AspNetCoreRateLimit" Version="2.1.0" />
<PackageReference Include="Braintree" Version="4.18.0" />
<PackageReference Include="Stripe.net" Version="37.26.0" />
<PackageReference Include="U2F.Core" Version="1.0.4" />
<PackageReference Include="Otp.NET" Version="1.2.2" />
<PackageReference Include="YubicoDotNetClient" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />

View File

@ -1,26 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Entities
{
public class U2f : ITableObject<int>
{
public int Id { get; set; }
public Guid UserId { get; set; }
[MaxLength(200)]
public string KeyHandle { get; set; }
[MaxLength(200)]
public string Challenge { get; set; }
[MaxLength(50)]
public string AppId { get; set; }
[MaxLength(20)]
public string Version { get; set; }
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public void SetNewId()
{
// int will be auto-populated
Id = 0;
}
}
}

View File

@ -6,7 +6,7 @@
Email = 1,
Duo = 2,
YubiKey = 3,
U2f = 4,
// U2f = 4, // Deprecated
Remember = 5,
OrganizationDuo = 6,
WebAuthn = 7,

View File

@ -1,221 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using U2F.Core.Exceptions;
using U2F.Core.Models;
using U2F.Core.Utils;
using U2fLib = U2F.Core.Crypto.U2F;
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 = JsonSerializer.Serialize(oldChallenges);
var token = JsonSerializer.Serialize(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;
}
}
}

View File

@ -375,7 +375,6 @@ namespace Bit.Core.IdentityServer
case TwoFactorProviderType.Email:
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.YubiKey:
case TwoFactorProviderType.U2f:
case TwoFactorProviderType.WebAuthn:
case TwoFactorProviderType.Remember:
if (type != TwoFactorProviderType.Remember &&
@ -403,7 +402,6 @@ namespace Bit.Core.IdentityServer
switch (type)
{
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.U2f:
case TwoFactorProviderType.WebAuthn:
case TwoFactorProviderType.Email:
case TwoFactorProviderType.YubiKey:
@ -422,16 +420,6 @@ namespace Bit.Core.IdentityServer
["Signature"] = token
};
}
else if (type == TwoFactorProviderType.U2f)
{
// TODO: Remove "Challenges" in a future update. Deprecated.
var tokens = token?.Split('|');
return new Dictionary<string, object>
{
["Challenge"] = tokens != null && tokens.Length > 0 ? tokens[0] : null,
["Challenges"] = tokens != null && tokens.Length > 1 ? tokens[1] : null
};
}
else if (type == TwoFactorProviderType.WebAuthn)
{
if (token == null)

View File

@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Fido2NetLib.Objects;
using PeterO.Cbor;
using U2F.Core.Utils;
namespace Bit.Core.Models
{
@ -15,79 +11,6 @@ namespace Bit.Core.Models
public bool Enabled { get; set; }
public Dictionary<string, object> MetaData { get; set; } = new Dictionary<string, object>();
public class U2fMetaData
{
public U2fMetaData() { }
public U2fMetaData(dynamic o)
{
Name = o.Name;
KeyHandle = o.KeyHandle;
PublicKey = o.PublicKey;
Certificate = o.Certificate;
Counter = o.Counter;
Compromised = o.Compromised;
}
public string Name { get; set; }
public string KeyHandle { get; set; }
[JsonIgnore]
public byte[] KeyHandleBytes =>
string.IsNullOrWhiteSpace(KeyHandle) ? null : Utils.Base64StringToByteArray(KeyHandle);
public string PublicKey { get; set; }
[JsonIgnore]
public byte[] PublicKeyBytes =>
string.IsNullOrWhiteSpace(PublicKey) ? null : Utils.Base64StringToByteArray(PublicKey);
public string Certificate { get; set; }
[JsonIgnore]
public byte[] CertificateBytes =>
string.IsNullOrWhiteSpace(Certificate) ? null : Utils.Base64StringToByteArray(Certificate);
public uint Counter { get; set; }
public bool Compromised { get; set; }
private static CBORObject CreatePublicKeyFromU2fRegistrationData(byte[] keyHandleData, byte[] publicKeyData)
{
var x = new byte[32];
var y = new byte[32];
Buffer.BlockCopy(publicKeyData, 1, x, 0, 32);
Buffer.BlockCopy(publicKeyData, 33, y, 0, 32);
var point = new ECPoint
{
X = x,
Y = y,
};
var coseKey = CBORObject.NewMap();
coseKey.Add(COSE.KeyCommonParameter.KeyType, COSE.KeyType.EC2);
coseKey.Add(COSE.KeyCommonParameter.Alg, -7);
coseKey.Add(COSE.KeyTypeParameter.Crv, COSE.EllipticCurve.P256);
coseKey.Add(COSE.KeyTypeParameter.X, point.X);
coseKey.Add(COSE.KeyTypeParameter.Y, point.Y);
return coseKey;
}
public WebAuthnData ToWebAuthnData()
{
return new WebAuthnData
{
Name = Name,
Descriptor = new PublicKeyCredentialDescriptor
{
Id = KeyHandleBytes,
Type = PublicKeyCredentialType.PublicKey
},
PublicKey = CreatePublicKeyFromU2fRegistrationData(KeyHandleBytes, PublicKeyBytes).EncodeToBytes(),
SignatureCounter = Counter,
Migrated = true,
};
}
}
public class WebAuthnData
{
public WebAuthnData() { }
@ -130,7 +53,6 @@ namespace Bit.Core.Models
{
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.YubiKey:
case TwoFactorProviderType.U2f:
case TwoFactorProviderType.WebAuthn:
return true;
default:

View File

@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Entities;
namespace Bit.Core.Repositories
{
public interface IU2fRepository : IRepository<U2f, int>
{
Task<ICollection<U2f>> GetManyByUserIdAsync(Guid userId);
Task DeleteManyByUserIdAsync(Guid userId);
}
}

View File

@ -21,7 +21,6 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using File = System.IO.File;
using U2fLib = U2F.Core.Crypto.U2F;
namespace Bit.Core.Services
{
@ -481,25 +480,6 @@ namespace Bit.Core.Services
return false;
}
// Delete U2F token is this is a migrated WebAuthn token.
var entry = new TwoFactorProvider.WebAuthnData(provider.MetaData[keyName]);
if (entry?.Migrated ?? false)
{
var u2fProvider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
if (u2fProvider?.MetaData?.ContainsKey(keyName) ?? false)
{
u2fProvider.MetaData.Remove(keyName);
if (u2fProvider.MetaData.Count > 0)
{
providers[TwoFactorProviderType.U2f] = u2fProvider;
}
else
{
providers.Remove(TwoFactorProviderType.U2f);
}
}
}
provider.MetaData.Remove(keyName);
providers[TwoFactorProviderType.WebAuthn] = provider;
user.SetTwoFactorProviders(providers);
@ -899,13 +879,6 @@ namespace Bit.Core.Services
return;
}
// Since the user can no longer directly manipulate U2F tokens, we should
// disable them when the user disables WebAuthn.
if (type == TwoFactorProviderType.WebAuthn)
{
providers.Remove(TwoFactorProviderType.U2f);
}
providers.Remove(type);
user.SetTwoFactorProviders(providers);
await SaveUserAsync(user);