mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 14:22:40 +01:00
Migrate existing U2F to Fido2 (#2484)
* Migrate existing U2F to Fido2 This seamlessly switches all u2f registrations over to the new FIDO2 support. Please note that I have not yet added a way to drop the u2f DB and its UI so that we can test the migration works properly for all. * add testing logic * fix u2f tests * remove duplicate status message * fix test and namespaces * fix test
This commit is contained in:
parent
c878f63f99
commit
02bf5afe0b
14 changed files with 149 additions and 59 deletions
|
@ -16,8 +16,7 @@ namespace BTCPayServer.Data
|
||||||
public CredentialType Type { get; set; }
|
public CredentialType Type { get; set; }
|
||||||
public enum CredentialType
|
public enum CredentialType
|
||||||
{
|
{
|
||||||
FIDO2,
|
FIDO2
|
||||||
U2F
|
|
||||||
}
|
}
|
||||||
public static void OnModelCreating(ModelBuilder builder)
|
public static void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,6 +9,7 @@ using BTCPayServer.Tests.Logging;
|
||||||
using BTCPayServer.U2F;
|
using BTCPayServer.U2F;
|
||||||
using BTCPayServer.U2F.Models;
|
using BTCPayServer.U2F.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NBitcoin;
|
||||||
using U2F.Core.Models;
|
using U2F.Core.Models;
|
||||||
using U2F.Core.Utils;
|
using U2F.Core.Utils;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
@ -62,7 +63,7 @@ namespace BTCPayServer.Tests
|
||||||
Assert.NotEmpty(addDeviceVM.Version);
|
Assert.NotEmpty(addDeviceVM.Version);
|
||||||
Assert.Null(addDeviceVM.DeviceResponse);
|
Assert.Null(addDeviceVM.DeviceResponse);
|
||||||
|
|
||||||
var devReg = new DeviceRegistration(Guid.NewGuid().ToByteArray(), Guid.NewGuid().ToByteArray(),
|
var devReg = new DeviceRegistration(Guid.NewGuid().ToByteArray(), RandomUtils.GetBytes(65),
|
||||||
Guid.NewGuid().ToByteArray(), 1);
|
Guid.NewGuid().ToByteArray(), 1);
|
||||||
|
|
||||||
mock.GetDevReg = () => devReg;
|
mock.GetDevReg = () => devReg;
|
||||||
|
|
|
@ -19,6 +19,8 @@ using BTCPayServer.Configuration;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
|
using BTCPayServer.Fido2;
|
||||||
|
using BTCPayServer.Fido2.Models;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
using BTCPayServer.Hosting;
|
using BTCPayServer.Hosting;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
|
@ -42,9 +44,9 @@ using BTCPayServer.Services.Labels;
|
||||||
using BTCPayServer.Services.Mails;
|
using BTCPayServer.Services.Mails;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
using BTCPayServer.U2F.Models;
|
|
||||||
using BTCPayServer.Validation;
|
using BTCPayServer.Validation;
|
||||||
using ExchangeSharp;
|
using ExchangeSharp;
|
||||||
|
using Fido2NetLib;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
@ -3324,7 +3326,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
var accountController = tester.PayTester.GetController<AccountController>();
|
var accountController = tester.PayTester.GetController<AccountController>();
|
||||||
|
|
||||||
//no 2fa or u2f enabled, login should work
|
//no 2fa or fido2 enabled, login should work
|
||||||
Assert.Equal(nameof(HomeController.Index),
|
Assert.Equal(nameof(HomeController.Index),
|
||||||
Assert.IsType<RedirectToActionResult>(await accountController.Login(new LoginViewModel()
|
Assert.IsType<RedirectToActionResult>(await accountController.Login(new LoginViewModel()
|
||||||
{
|
{
|
||||||
|
@ -3332,48 +3334,46 @@ namespace BTCPayServer.Tests
|
||||||
Password = user.RegisterDetails.Password
|
Password = user.RegisterDetails.Password
|
||||||
})).ActionName);
|
})).ActionName);
|
||||||
|
|
||||||
var manageController = user.GetController<ManageController>();
|
var manageController = user.GetController<Fido2Controller>();
|
||||||
|
|
||||||
//by default no u2f devices available
|
//by default no fido2 devices available
|
||||||
Assert.Empty(Assert
|
Assert.Empty(Assert
|
||||||
.IsType<U2FAuthenticationViewModel>(Assert
|
.IsType<Fido2AuthenticationViewModel>(Assert
|
||||||
.IsType<ViewResult>(await manageController.U2FAuthentication()).Model).Devices);
|
.IsType<ViewResult>(await manageController.List()).Model).Credentials);
|
||||||
var addRequest =
|
Assert.IsType<CredentialCreateOptions>(Assert
|
||||||
Assert.IsType<AddU2FDeviceViewModel>(Assert
|
.IsType<ViewResult>(await manageController.Create(new AddFido2CredentialViewModel()
|
||||||
.IsType<ViewResult>(manageController.AddU2FDevice("label")).Model);
|
{
|
||||||
//name should match the one provided in beginning
|
Name = "label"
|
||||||
Assert.Equal("label", addRequest.Name);
|
})).Model);
|
||||||
|
|
||||||
//sending an invalid response model back to server, should error out
|
//sending an invalid response model back to server, should error out
|
||||||
Assert.IsType<RedirectToActionResult>(await manageController.AddU2FDevice(addRequest));
|
Assert.IsType<RedirectToActionResult>(await manageController.CreateResponse("sdsdsa", "sds"));
|
||||||
var statusModel = manageController.TempData.GetStatusMessageModel();
|
var statusModel = manageController.TempData.GetStatusMessageModel();
|
||||||
Assert.Equal(StatusMessageModel.StatusSeverity.Error, statusModel.Severity);
|
Assert.Equal(StatusMessageModel.StatusSeverity.Error, statusModel.Severity);
|
||||||
|
|
||||||
var contextFactory = tester.PayTester.GetService<ApplicationDbContextFactory>();
|
var contextFactory = tester.PayTester.GetService<ApplicationDbContextFactory>();
|
||||||
|
|
||||||
//add a fake u2f device in db directly since emulating a u2f device is hard and annoying
|
//add a fake fido2 device in db directly since emulating a fido2 device is hard and annoying
|
||||||
using (var context = contextFactory.CreateContext())
|
using (var context = contextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var newDevice = new U2FDevice()
|
var newDevice = new Fido2Credential()
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid().ToString(),
|
Id = Guid.NewGuid().ToString(),
|
||||||
Name = "fake",
|
Name = "fake",
|
||||||
Counter = 0,
|
Type = Fido2Credential.CredentialType.FIDO2,
|
||||||
KeyHandle = UTF8Encoding.UTF8.GetBytes("fake"),
|
|
||||||
PublicKey = UTF8Encoding.UTF8.GetBytes("fake"),
|
|
||||||
AttestationCert = UTF8Encoding.UTF8.GetBytes("fake"),
|
|
||||||
ApplicationUserId = user.UserId
|
ApplicationUserId = user.UserId
|
||||||
};
|
};
|
||||||
await context.U2FDevices.AddAsync(newDevice);
|
newDevice.SetBlob(new Fido2CredentialBlob() { });
|
||||||
|
await context.Fido2Credentials.AddAsync(newDevice);
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
Assert.NotNull(newDevice.Id);
|
Assert.NotNull(newDevice.Id);
|
||||||
Assert.NotEmpty(Assert
|
Assert.NotEmpty(Assert
|
||||||
.IsType<U2FAuthenticationViewModel>(Assert
|
.IsType<Fido2AuthenticationViewModel>(Assert
|
||||||
.IsType<ViewResult>(await manageController.U2FAuthentication()).Model).Devices);
|
.IsType<ViewResult>(await manageController.List()).Model).Credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
//check if we are showing the u2f login screen now
|
//check if we are showing the fido2 login screen now
|
||||||
var secondLoginResult = Assert.IsType<ViewResult>(await accountController.Login(new LoginViewModel()
|
var secondLoginResult = Assert.IsType<ViewResult>(await accountController.Login(new LoginViewModel()
|
||||||
{
|
{
|
||||||
Email = user.RegisterDetails.Email,
|
Email = user.RegisterDetails.Email,
|
||||||
|
@ -3384,7 +3384,7 @@ namespace BTCPayServer.Tests
|
||||||
var vm = Assert.IsType<SecondaryLoginViewModel>(secondLoginResult.Model);
|
var vm = Assert.IsType<SecondaryLoginViewModel>(secondLoginResult.Model);
|
||||||
//2fa was never enabled for user so this should be empty
|
//2fa was never enabled for user so this should be empty
|
||||||
Assert.Null(vm.LoginWith2FaViewModel);
|
Assert.Null(vm.LoginWith2FaViewModel);
|
||||||
Assert.NotNull(vm.LoginWithU2FViewModel);
|
Assert.NotNull(vm.LoginWithFido2ViewModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,8 @@
|
||||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||||
<PackageReference Include="Fido2" Version="2.0.0-preview2" />
|
<PackageReference Include="Fido2" Version="2.0.1" />
|
||||||
<PackageReference Include="Fido2.AspNet" Version="2.0.0-preview2" />
|
<PackageReference Include="Fido2.AspNet" Version="2.0.1" />
|
||||||
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
|
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
|
||||||
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.1" />
|
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||||
|
|
|
@ -2,7 +2,7 @@ using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Abstractions.Models;
|
using BTCPayServer.Abstractions.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Fido2;
|
using BTCPayServer.Fido2.Models;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using Fido2NetLib;
|
using Fido2NetLib;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.U2F.Models
|
namespace BTCPayServer.Fido2
|
||||||
{
|
{
|
||||||
|
|
||||||
[Route("fido2")]
|
[Route("fido2")]
|
||||||
|
@ -78,8 +78,7 @@ namespace BTCPayServer.U2F.Models
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<IActionResult> CreateResponse([FromForm] string data, [FromForm] string name)
|
public async Task<IActionResult> CreateResponse([FromForm] string data, [FromForm] string name)
|
||||||
{
|
{
|
||||||
var attestationResponse = JObject.Parse(data).ToObject<AuthenticatorAttestationRawResponse>();
|
if (await _fido2Service.CompleteCreation(_userManager.GetUserId(User), name, data))
|
||||||
if (await _fido2Service.CompleteCreation(_userManager.GetUserId(User), name, attestationResponse))
|
|
||||||
{
|
{
|
||||||
|
|
||||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||||
|
|
|
@ -4,11 +4,13 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Fido2.Models;
|
||||||
using ExchangeSharp;
|
using ExchangeSharp;
|
||||||
using Fido2NetLib;
|
using Fido2NetLib;
|
||||||
using Fido2NetLib.Objects;
|
using Fido2NetLib.Objects;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Fido2
|
namespace BTCPayServer.Fido2
|
||||||
{
|
{
|
||||||
|
@ -20,11 +22,13 @@ namespace BTCPayServer.Fido2
|
||||||
new ConcurrentDictionary<string, AssertionOptions>();
|
new ConcurrentDictionary<string, AssertionOptions>();
|
||||||
private readonly ApplicationDbContextFactory _contextFactory;
|
private readonly ApplicationDbContextFactory _contextFactory;
|
||||||
private readonly IFido2 _fido2;
|
private readonly IFido2 _fido2;
|
||||||
|
private readonly Fido2Configuration _fido2Configuration;
|
||||||
|
|
||||||
public Fido2Service(ApplicationDbContextFactory contextFactory, IFido2 fido2)
|
public Fido2Service(ApplicationDbContextFactory contextFactory, IFido2 fido2, Fido2Configuration fido2Configuration)
|
||||||
{
|
{
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_fido2 = fido2;
|
_fido2 = fido2;
|
||||||
|
_fido2Configuration = fido2Configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CredentialCreateOptions> RequestCreation(string userId)
|
public async Task<CredentialCreateOptions> RequestCreation(string userId)
|
||||||
|
@ -70,25 +74,26 @@ namespace BTCPayServer.Fido2
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CompleteCreation(string userId, string name, AuthenticatorAttestationRawResponse attestationResponse)
|
public async Task<bool> CompleteCreation(string userId, string name, string data)
|
||||||
{
|
{
|
||||||
await using var dbContext = _contextFactory.CreateContext();
|
try
|
||||||
var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials)
|
{
|
||||||
.FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId);
|
|
||||||
if (user == null || !CreationStore.TryGetValue(userId, out var options))
|
var attestationResponse = JObject.Parse(data).ToObject<AuthenticatorAttestationRawResponse>();
|
||||||
{
|
await using var dbContext = _contextFactory.CreateContext();
|
||||||
|
var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials)
|
||||||
|
.FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId);
|
||||||
|
if (user == null || !CreationStore.TryGetValue(userId, out var options))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Verify and make the credentials
|
// 2. Verify and make the credentials
|
||||||
var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, args => Task.FromResult(true));
|
var success =
|
||||||
|
await _fido2.MakeNewCredentialAsync(attestationResponse, options, args => Task.FromResult(true));
|
||||||
|
|
||||||
// 3. Store the credentials in db
|
// 3. Store the credentials in db
|
||||||
var newCredential = new Fido2Credential()
|
var newCredential = new Fido2Credential() {Name = name, ApplicationUserId = userId};
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
ApplicationUserId = userId
|
|
||||||
};
|
|
||||||
|
|
||||||
newCredential.SetBlob(new Fido2CredentialBlob()
|
newCredential.SetBlob(new Fido2CredentialBlob()
|
||||||
{
|
{
|
||||||
|
@ -106,6 +111,11 @@ namespace BTCPayServer.Fido2
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Fido2Credential>> GetCredentials(string userId)
|
public async Task<List<Fido2Credential>> GetCredentials(string userId)
|
||||||
|
@ -158,7 +168,9 @@ namespace BTCPayServer.Fido2
|
||||||
},
|
},
|
||||||
UserVerificationIndex = true,
|
UserVerificationIndex = true,
|
||||||
Location = true,
|
Location = true,
|
||||||
UserVerificationMethod = true
|
UserVerificationMethod = true ,
|
||||||
|
Extensions = true,
|
||||||
|
AppID = _fido2Configuration.Origin
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. Create options
|
// 3. Create options
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
using System;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Fido2.Models;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Fido2
|
||||||
{
|
{
|
||||||
public static class Fido2Extensions
|
public static class Fido2Extensions
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using Fido2NetLib.Objects;
|
using Fido2NetLib.Objects;
|
||||||
|
|
||||||
namespace BTCPayServer.U2F.Models
|
namespace BTCPayServer.Fido2.Models
|
||||||
{
|
{
|
||||||
public class AddFido2CredentialViewModel
|
public class AddFido2CredentialViewModel
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
|
||||||
namespace BTCPayServer.U2F.Models
|
namespace BTCPayServer.Fido2.Models
|
||||||
{
|
{
|
||||||
public class Fido2AuthenticationViewModel
|
public class Fido2AuthenticationViewModel
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@ using Fido2NetLib;
|
||||||
using Fido2NetLib.Objects;
|
using Fido2NetLib.Objects;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Fido2.Models
|
||||||
{
|
{
|
||||||
public class Fido2CredentialBlob
|
public class Fido2CredentialBlob
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -9,11 +10,15 @@ using BTCPayServer.Abstractions.Contracts;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Configuration;
|
using BTCPayServer.Configuration;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Fido2;
|
||||||
|
using BTCPayServer.Fido2.Models;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
|
using Fido2NetLib;
|
||||||
|
using Fido2NetLib.Objects;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -22,6 +27,8 @@ using NBitcoin.DataEncoders;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
|
using Org.BouncyCastle.Math.EC;
|
||||||
|
using PeterO.Cbor;
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
|
@ -121,6 +128,13 @@ namespace BTCPayServer.Hosting
|
||||||
settings.TransitionInternalNodeConnectionString = true;
|
settings.TransitionInternalNodeConnectionString = true;
|
||||||
await _Settings.UpdateSetting(settings);
|
await _Settings.UpdateSetting(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (true || !settings.MigrateU2FToFIDO2)
|
||||||
|
{
|
||||||
|
await MigrateU2FToFIDO2();
|
||||||
|
settings.MigrateU2FToFIDO2 = true;
|
||||||
|
await _Settings.UpdateSetting(settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -129,6 +143,61 @@ namespace BTCPayServer.Hosting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task MigrateU2FToFIDO2()
|
||||||
|
{
|
||||||
|
await using var ctx = _DBContextFactory.CreateContext();
|
||||||
|
ctx.RemoveRange(ctx.Fido2Credentials.ToList());
|
||||||
|
var u2fDevices = await ctx.U2FDevices.ToListAsync();
|
||||||
|
foreach (U2FDevice u2FDevice in u2fDevices)
|
||||||
|
{
|
||||||
|
var fido2 = new Fido2Credential()
|
||||||
|
{
|
||||||
|
ApplicationUserId = u2FDevice.ApplicationUserId,
|
||||||
|
Name = u2FDevice.Name,
|
||||||
|
Type = Fido2Credential.CredentialType.FIDO2
|
||||||
|
};
|
||||||
|
fido2.SetBlob(new Fido2CredentialBlob()
|
||||||
|
{
|
||||||
|
SignatureCounter = (uint)u2FDevice.Counter,
|
||||||
|
PublicKey = CreatePublicKeyFromU2fRegistrationData( u2FDevice.PublicKey).EncodeToBytes() ,
|
||||||
|
UserHandle = u2FDevice.KeyHandle,
|
||||||
|
Descriptor = new PublicKeyCredentialDescriptor(u2FDevice.KeyHandle),
|
||||||
|
CredType = "u2f"
|
||||||
|
});
|
||||||
|
|
||||||
|
await ctx.AddAsync(fido2);
|
||||||
|
|
||||||
|
ctx.Remove(u2FDevice);
|
||||||
|
|
||||||
|
}
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
//from https://github.com/abergs/fido2-net-lib/blob/0fa7bb4b4a1f33f46c5f7ca4ee489b47680d579b/Test/ExistingU2fRegistrationDataTests.cs#L70
|
||||||
|
private static CBORObject CreatePublicKeyFromU2fRegistrationData(byte[] publicKeyData)
|
||||||
|
{
|
||||||
|
if (publicKeyData.Length != 65)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("u2f public key must be 65 bytes", nameof(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 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, x);
|
||||||
|
coseKey.Add(COSE.KeyTypeParameter.Y, y);
|
||||||
|
|
||||||
|
return coseKey;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task TransitionInternalNodeConnectionString()
|
private async Task TransitionInternalNodeConnectionString()
|
||||||
{
|
{
|
||||||
var nodes = LightningOptions.Value.InternalLightningByCryptoCode.Values.Select(c => c.ToString()).ToHashSet();
|
var nodes = LightningOptions.Value.InternalLightningByCryptoCode.Values.Select(c => c.ToString()).ToHashSet();
|
||||||
|
|
|
@ -9,6 +9,7 @@ using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
@ -61,7 +62,16 @@ namespace BTCPayServer.Security.GreenField
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
return AuthenticateResult.Fail(result.ToString());
|
return AuthenticateResult.Fail(result.ToString());
|
||||||
|
|
||||||
var user = await _userManager.FindByNameAsync(username);
|
var user = await _userManager.Users
|
||||||
|
.Include(applicationUser => applicationUser.U2FDevices)
|
||||||
|
.Include(applicationUser => applicationUser.Fido2Credentials)
|
||||||
|
.FirstOrDefaultAsync(applicationUser =>
|
||||||
|
applicationUser.NormalizedUserName == _userManager.NormalizeName(username));
|
||||||
|
|
||||||
|
if (user.U2FDevices.Any() || user.Fido2Credentials.Any())
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("Cannot use Basic authentication with multi-factor is enabled.");
|
||||||
|
}
|
||||||
var claims = new List<Claim>()
|
var claims = new List<Claim>()
|
||||||
{
|
{
|
||||||
new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, user.Id),
|
new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, user.Id),
|
||||||
|
|
|
@ -2,6 +2,7 @@ namespace BTCPayServer.Services
|
||||||
{
|
{
|
||||||
public class MigrationSettings
|
public class MigrationSettings
|
||||||
{
|
{
|
||||||
|
public bool MigrateU2FToFIDO2{ get; set; }
|
||||||
public bool UnreachableStoreCheck { get; set; }
|
public bool UnreachableStoreCheck { get; set; }
|
||||||
public bool DeprecatedLightningConnectionStringCheck { get; set; }
|
public bool DeprecatedLightningConnectionStringCheck { get; set; }
|
||||||
public bool ConvertMultiplierToSpread { get; set; }
|
public bool ConvertMultiplierToSpread { get; set; }
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
@model BTCPayServer.U2F.Models.Fido2AuthenticationViewModel
|
@model BTCPayServer.Fido2.Models.Fido2AuthenticationViewModel
|
||||||
@{
|
@{
|
||||||
ViewData.SetActivePageAndTitle(ManageNavPages.Fido2, "Registered FIDO2 Credentials");
|
ViewData.SetActivePageAndTitle(ManageNavPages.Fido2, "Registered FIDO2 Credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
<partial name="_StatusMessage"/>
|
|
||||||
|
|
||||||
<table class="table table-lg mb-4">
|
<table class="table table-lg mb-4">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
Loading…
Add table
Reference in a new issue