mirror of
https://github.com/bitwarden/server.git
synced 2025-05-28 23:04:50 -05:00
u2f token provider
This commit is contained in:
parent
731a1e31b9
commit
3ae96bd510
167
src/Core/Identity/U2fTokenProvider.cs
Normal file
167
src/Core/Identity/U2fTokenProvider.cs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Bit.Core.Models.Table;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using U2F.Core.Models;
|
||||||
|
using U2fLib = U2F.Core.Crypto.U2F;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using U2F.Core.Exceptions;
|
||||||
|
|
||||||
|
namespace Bit.Core.Identity
|
||||||
|
{
|
||||||
|
public class U2fTokenProvider : IUserTwoFactorTokenProvider<User>
|
||||||
|
{
|
||||||
|
private readonly IU2fRepository _u2fRepository;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
private readonly GlobalSettings _globalSettings;
|
||||||
|
|
||||||
|
public U2fTokenProvider(
|
||||||
|
IU2fRepository u2fRepository,
|
||||||
|
IUserService userService,
|
||||||
|
GlobalSettings globalSettings)
|
||||||
|
{
|
||||||
|
_u2fRepository = u2fRepository;
|
||||||
|
_userService = userService;
|
||||||
|
_globalSettings = globalSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
||||||
|
{
|
||||||
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
|
||||||
|
var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.U2f) && HasProperMetaData(provider);
|
||||||
|
return Task.FromResult(canGenerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
||||||
|
{
|
||||||
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
|
||||||
|
if(!HasProperMetaData(provider))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = new List<TwoFactorProvider.U2fMetaData>();
|
||||||
|
|
||||||
|
var key1 = provider.MetaData["Key1"] as TwoFactorProvider.U2fMetaData;
|
||||||
|
if(!key1?.Compromised ?? false)
|
||||||
|
{
|
||||||
|
keys.Add(key1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(keys.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _u2fRepository.DeleteManyByUserIdAsync(user.Id);
|
||||||
|
|
||||||
|
var challenges = new List<object>();
|
||||||
|
foreach(var key in keys)
|
||||||
|
{
|
||||||
|
var registration = new DeviceRegistration(key.KeyHandleBytes, key.PublicKeyBytes,
|
||||||
|
key.CertificateBytes, key.Counter);
|
||||||
|
var auth = U2fLib.StartAuthentication(_globalSettings.U2f.AppId, registration);
|
||||||
|
|
||||||
|
// Maybe move this to a bulk create when we support more than 1 key?
|
||||||
|
await _u2fRepository.CreateAsync(new U2f
|
||||||
|
{
|
||||||
|
AppId = auth.AppId,
|
||||||
|
Challenge = auth.Challenge,
|
||||||
|
KeyHandle = auth.KeyHandle,
|
||||||
|
Version = auth.Version,
|
||||||
|
UserId = user.Id
|
||||||
|
});
|
||||||
|
|
||||||
|
challenges.Add(new
|
||||||
|
{
|
||||||
|
appId = auth.AppId,
|
||||||
|
challenge = auth.Challenge,
|
||||||
|
keyHandle = auth.KeyHandle,
|
||||||
|
version = auth.Version
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = JsonConvert.SerializeObject(challenges);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(token))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
|
||||||
|
if(!HasProperMetaData(provider))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = new List<TwoFactorProvider.U2fMetaData>();
|
||||||
|
|
||||||
|
var key1 = provider.MetaData["Key1"] as TwoFactorProvider.U2fMetaData;
|
||||||
|
if(!key1?.Compromised ?? false)
|
||||||
|
{
|
||||||
|
keys.Add(key1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(keys.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var authenticateResponse = BaseModel.FromJson<AuthenticateResponse>(token);
|
||||||
|
var key = keys.FirstOrDefault(f => f.KeyHandle == authenticateResponse.KeyHandle);
|
||||||
|
|
||||||
|
if(key == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var challenges = await _u2fRepository.GetManyByUserIdAsync(user.Id);
|
||||||
|
if(challenges.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = true;
|
||||||
|
// 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.First(c => c.KeyHandle == authenticateResponse.KeyHandle);
|
||||||
|
var registration = new DeviceRegistration(key.KeyHandleBytes, key.PublicKeyBytes, key.CertificateBytes,
|
||||||
|
key.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.Counter = registration.Counter;
|
||||||
|
key.Compromised = registration.IsCompromised;
|
||||||
|
|
||||||
|
var providers = user.GetTwoFactorProviders();
|
||||||
|
providers[TwoFactorProviderType.U2f].MetaData["Key1"] = key;
|
||||||
|
user.SetTwoFactorProviders(providers);
|
||||||
|
await _userService.SaveUserAsync(user);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasProperMetaData(TwoFactorProvider provider)
|
||||||
|
{
|
||||||
|
return provider?.MetaData != null && provider.MetaData.ContainsKey("Key1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -193,8 +193,10 @@ namespace Bit.Core.IdentityServer
|
|||||||
}
|
}
|
||||||
else if(type == TwoFactorProviderType.U2f)
|
else if(type == TwoFactorProviderType.U2f)
|
||||||
{
|
{
|
||||||
// TODO: U2F challenge
|
return new Dictionary<string, object>
|
||||||
return new Dictionary<string, object> { };
|
{
|
||||||
|
["Challenges"] = token
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
default:
|
default:
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using U2F.Core.Utils;
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
namespace Bit.Core.Models
|
||||||
{
|
{
|
||||||
@ -10,8 +12,14 @@ namespace Bit.Core.Models
|
|||||||
public class U2fMetaData
|
public class U2fMetaData
|
||||||
{
|
{
|
||||||
public string KeyHandle { get; set; }
|
public string KeyHandle { get; set; }
|
||||||
|
public byte[] KeyHandleBytes =>
|
||||||
|
string.IsNullOrWhiteSpace(KeyHandle) ? null : Utils.Base64StringToByteArray(KeyHandle);
|
||||||
public string PublicKey { get; set; }
|
public string PublicKey { get; set; }
|
||||||
|
public byte[] PublicKeyBytes =>
|
||||||
|
string.IsNullOrWhiteSpace(PublicKey) ? null : Utils.Base64StringToByteArray(PublicKey);
|
||||||
public string Certificate { get; set; }
|
public string Certificate { get; set; }
|
||||||
|
public byte[] CertificateBytes =>
|
||||||
|
string.IsNullOrWhiteSpace(Certificate) ? null : Utils.Base64StringToByteArray(Certificate);
|
||||||
public uint Counter { get; set; }
|
public uint Counter { get; set; }
|
||||||
public bool Compromised { get; set; }
|
public bool Compromised { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using U2fLib = U2F.Core.Crypto.U2F;
|
|||||||
using U2F.Core.Models;
|
using U2F.Core.Models;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
|
using U2F.Core.Utils;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -273,9 +274,9 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
["Key1"] = new TwoFactorProvider.U2fMetaData
|
["Key1"] = new TwoFactorProvider.U2fMetaData
|
||||||
{
|
{
|
||||||
KeyHandle = reg.KeyHandle == null ? null : Convert.ToBase64String(reg.KeyHandle),
|
KeyHandle = reg.KeyHandle == null ? null : Utils.ByteArrayToBase64String(reg.KeyHandle),
|
||||||
PublicKey = reg.PublicKey == null ? null : Convert.ToBase64String(reg.PublicKey),
|
PublicKey = reg.PublicKey == null ? null : Utils.ByteArrayToBase64String(reg.PublicKey),
|
||||||
Certificate = reg.AttestationCert == null ? null : Convert.ToBase64String(reg.AttestationCert),
|
Certificate = reg.AttestationCert == null ? null : Utils.ByteArrayToBase64String(reg.AttestationCert),
|
||||||
Compromised = false,
|
Compromised = false,
|
||||||
Counter = reg.Counter
|
Counter = reg.Counter
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user