mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Update FIDO library
This commit is contained in:
@@ -57,8 +57,8 @@
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.6.9" />
|
||||
<PackageReference Include="CsvHelper" Version="32.0.3" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Fido2" Version="2.0.2" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="2.0.2" />
|
||||
<PackageReference Include="Fido2" Version="3.0.1" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="3.0.1" />
|
||||
<PackageReference Include="LNURL" Version="0.0.36" />
|
||||
<PackageReference Include="MailKit" Version="3.3.0" />
|
||||
<PackageReference Include="BTCPayServer.NETCore.Plugins.Mvc" Version="1.4.4" />
|
||||
|
||||
@@ -273,7 +273,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
return new LoginWithFido2ViewModel
|
||||
{
|
||||
Data = r,
|
||||
Data = System.Text.Json.JsonSerializer.Serialize(r, r.GetType()),
|
||||
UserId = user.Id,
|
||||
RememberMe = rememberMe
|
||||
};
|
||||
@@ -385,7 +385,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
if (await _fido2Service.CompleteLogin(viewModel.UserId, JObject.Parse(viewModel.Response).ToObject<AuthenticatorAssertionRawResponse>()))
|
||||
if (await _fido2Service.CompleteLogin(viewModel.UserId, System.Text.Json.JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(viewModel.Response)))
|
||||
{
|
||||
await _signInManager.SignInAsync(user!, viewModel.RememberMe, "FIDO2");
|
||||
_logger.LogInformation("User {Email} logged in with FIDO2", user.Email);
|
||||
|
||||
@@ -33,7 +33,6 @@ using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
using Serilog.Filters;
|
||||
using PeterO.Numbers;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using Fido2NetLib.Objects;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static BTCPayServer.Fido2.Models.Fido2CredentialBlob;
|
||||
|
||||
namespace BTCPayServer.Fido2
|
||||
{
|
||||
@@ -45,7 +46,7 @@ namespace BTCPayServer.Fido2
|
||||
var existingKeys =
|
||||
user.Fido2Credentials
|
||||
.Where(credential => credential.Type == Fido2Credential.CredentialType.FIDO2)
|
||||
.Select(c => c.GetFido2Blob().Descriptor).ToList();
|
||||
.Select(c => c.GetFido2Blob().Descriptor?.ToFido2()).ToList();
|
||||
|
||||
// 3. Create options
|
||||
var authenticatorSelection = new AuthenticatorSelection
|
||||
@@ -57,14 +58,7 @@ namespace BTCPayServer.Fido2
|
||||
var exts = new AuthenticationExtensionsClientInputs()
|
||||
{
|
||||
Extensions = true,
|
||||
UserVerificationIndex = true,
|
||||
Location = true,
|
||||
UserVerificationMethod = true,
|
||||
BiometricAuthenticatorPerformanceBounds = new AuthenticatorBiometricPerfBounds
|
||||
{
|
||||
FAR = float.MaxValue,
|
||||
FRR = float.MaxValue
|
||||
},
|
||||
UserVerificationMethod = true
|
||||
};
|
||||
|
||||
var options = _fido2.RequestNewCredential(
|
||||
@@ -81,7 +75,7 @@ namespace BTCPayServer.Fido2
|
||||
try
|
||||
{
|
||||
|
||||
var attestationResponse = JObject.Parse(data).ToObject<AuthenticatorAttestationRawResponse>();
|
||||
var attestationResponse = System.Text.Json.JsonSerializer.Deserialize<AuthenticatorAttestationRawResponse>(data);
|
||||
await using var dbContext = _contextFactory.CreateContext();
|
||||
var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials)
|
||||
.FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId);
|
||||
@@ -92,14 +86,14 @@ namespace BTCPayServer.Fido2
|
||||
|
||||
// 2. Verify and make the credentials
|
||||
var success =
|
||||
await _fido2.MakeNewCredentialAsync(attestationResponse, options, args => Task.FromResult(true));
|
||||
await _fido2.MakeNewCredentialAsync(attestationResponse, options, (args, cancellation) => Task.FromResult(true));
|
||||
|
||||
// 3. Store the credentials in db
|
||||
var newCredential = new Fido2Credential() { Name = name, ApplicationUserId = userId };
|
||||
|
||||
newCredential.SetBlob(new Fido2CredentialBlob()
|
||||
{
|
||||
Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId),
|
||||
Descriptor = new DescriptorClass(success.Result.CredentialId),
|
||||
PublicKey = success.Result.PublicKey,
|
||||
UserHandle = success.Result.User.Id,
|
||||
SignatureCounter = success.Result.Counter,
|
||||
@@ -158,21 +152,13 @@ namespace BTCPayServer.Fido2
|
||||
}
|
||||
var existingCredentials = user.Fido2Credentials
|
||||
.Where(credential => credential.Type == Fido2Credential.CredentialType.FIDO2)
|
||||
.Select(c => c.GetFido2Blob().Descriptor)
|
||||
.Select(c => c.GetFido2Blob().Descriptor?.ToFido2())
|
||||
.ToList();
|
||||
var exts = new AuthenticationExtensionsClientInputs()
|
||||
{
|
||||
SimpleTransactionAuthorization = "FIDO",
|
||||
GenericTransactionAuthorization = new TxAuthGenericArg
|
||||
{
|
||||
ContentType = "text/plain",
|
||||
Content = new byte[] { 0x46, 0x49, 0x44, 0x4F }
|
||||
},
|
||||
UserVerificationIndex = true,
|
||||
Location = true,
|
||||
UserVerificationMethod = true,
|
||||
Extensions = true,
|
||||
AppID = _fido2Configuration.Origin
|
||||
AppID = _fido2Configuration.Origins.First()
|
||||
};
|
||||
|
||||
// 3. Create options
|
||||
@@ -206,7 +192,7 @@ namespace BTCPayServer.Fido2
|
||||
|
||||
// 5. Make the assertion
|
||||
var res = await _fido2.MakeAssertionAsync(response, options, credential.Item2.PublicKey,
|
||||
credential.Item2.SignatureCounter, x => Task.FromResult(true));
|
||||
credential.Item2.SignatureCounter, (x, cancellationToken) => Task.FromResult(true));
|
||||
|
||||
// 6. Store the updated counter
|
||||
credential.Item2.SignatureCounter = res.Counter;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Fido2NetLib;
|
||||
using Fido2NetLib.Objects;
|
||||
using Newtonsoft.Json;
|
||||
@@ -6,7 +7,84 @@ namespace BTCPayServer.Fido2.Models
|
||||
{
|
||||
public class Fido2CredentialBlob
|
||||
{
|
||||
public PublicKeyCredentialDescriptor Descriptor { get; set; }
|
||||
public class Base64UrlConverter : JsonConverter<byte[]>
|
||||
{
|
||||
private readonly Required _requirement = Required.DisallowNull;
|
||||
|
||||
public Base64UrlConverter()
|
||||
{
|
||||
}
|
||||
|
||||
public Base64UrlConverter(Required required = Required.DisallowNull)
|
||||
{
|
||||
_requirement = required;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, byte[] value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(Base64Url.Encode(value));
|
||||
}
|
||||
|
||||
public override byte[] ReadJson(JsonReader reader, Type objectType, byte[] existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
byte[] ret = null;
|
||||
|
||||
if (null == reader.Value && _requirement == Required.AllowNull)
|
||||
return ret;
|
||||
|
||||
if (null == reader.Value)
|
||||
throw new Fido2VerificationException("json value must not be null");
|
||||
if (Type.GetType("System.String") != reader.ValueType)
|
||||
throw new Fido2VerificationException("json valuetype must be string");
|
||||
try
|
||||
{
|
||||
ret = Base64Url.Decode((string)reader.Value);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
throw new Fido2VerificationException("json value must be valid base64 encoded string", ex);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
public class DescriptorClass
|
||||
{
|
||||
public DescriptorClass(byte[] credentialId)
|
||||
{
|
||||
Id = credentialId;
|
||||
}
|
||||
|
||||
public DescriptorClass()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This member contains the type of the public key credential the caller is referring to.
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; } = "public-key";
|
||||
|
||||
/// <summary>
|
||||
/// This member contains the credential ID of the public key credential the caller is referring to.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
[JsonProperty("id")]
|
||||
public byte[] Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This OPTIONAL member contains a hint as to how the client might communicate with the managing authenticator of the public key credential the caller is referring to.
|
||||
/// </summary>
|
||||
[JsonProperty("transports", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string[] Transports { get; set; }
|
||||
|
||||
public PublicKeyCredentialDescriptor ToFido2()
|
||||
{
|
||||
var str = JsonConvert.SerializeObject(this);
|
||||
return System.Text.Json.JsonSerializer.Deserialize<PublicKeyCredentialDescriptor>(str);
|
||||
}
|
||||
}
|
||||
public DescriptorClass Descriptor { get; set; }
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
public byte[] PublicKey { get; set; }
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Fido2NetLib;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Fido2.Models
|
||||
{
|
||||
@@ -7,7 +8,7 @@ namespace BTCPayServer.Fido2.Models
|
||||
public string UserId { get; set; }
|
||||
|
||||
public bool RememberMe { get; set; }
|
||||
public AssertionOptions Data { get; set; }
|
||||
public string Data { get; set; }
|
||||
public string Response { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
@@ -22,14 +23,15 @@ using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
|
||||
using Fido2NetLib.Cbor;
|
||||
using Fido2NetLib.Objects;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PeterO.Cbor;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using static BTCPayServer.Fido2.Models.Fido2CredentialBlob;
|
||||
using LightningAddressData = BTCPayServer.Data.LightningAddressData;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
@@ -738,9 +740,9 @@ WHERE cte.""Id""=p.""Id""
|
||||
fido2.SetBlob(new Fido2CredentialBlob()
|
||||
{
|
||||
SignatureCounter = (uint)u2FDevice.Counter,
|
||||
PublicKey = CreatePublicKeyFromU2fRegistrationData(u2FDevice.PublicKey).EncodeToBytes(),
|
||||
PublicKey = CreatePublicKeyFromU2fRegistrationData(u2FDevice.PublicKey).Encode(),
|
||||
UserHandle = u2FDevice.KeyHandle,
|
||||
Descriptor = new PublicKeyCredentialDescriptor(u2FDevice.KeyHandle),
|
||||
Descriptor = new DescriptorClass(u2FDevice.KeyHandle),
|
||||
CredType = "u2f"
|
||||
});
|
||||
|
||||
@@ -751,27 +753,29 @@ WHERE cte.""Id""=p.""Id""
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
//from https://github.com/abergs/fido2-net-lib/blob/0fa7bb4b4a1f33f46c5f7ca4ee489b47680d579b/Test/ExistingU2fRegistrationDataTests.cs#L70
|
||||
private static CBORObject CreatePublicKeyFromU2fRegistrationData(byte[] publicKeyData)
|
||||
private static CborMap 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 point = new ECPoint
|
||||
{
|
||||
X = x,
|
||||
Y = y,
|
||||
};
|
||||
|
||||
var coseKey = CBORObject.NewMap();
|
||||
var coseKey = new CborMap
|
||||
{
|
||||
{ (long)COSE.KeyCommonParameter.KeyType, (long)COSE.KeyType.EC2 },
|
||||
{ (long)COSE.KeyCommonParameter.Alg, -7L },
|
||||
|
||||
coseKey.Add(COSE.KeyCommonParameter.KeyType, COSE.KeyType.EC2);
|
||||
coseKey.Add(COSE.KeyCommonParameter.Alg, -7);
|
||||
{ (long)COSE.KeyTypeParameter.Crv, (long)COSE.EllipticCurve.P256 },
|
||||
|
||||
coseKey.Add(COSE.KeyTypeParameter.Crv, COSE.EllipticCurve.P256);
|
||||
|
||||
coseKey.Add(COSE.KeyTypeParameter.X, x);
|
||||
coseKey.Add(COSE.KeyTypeParameter.Y, y);
|
||||
{ (long)COSE.KeyTypeParameter.X, point.X },
|
||||
{ (long)COSE.KeyTypeParameter.Y, point.Y }
|
||||
};
|
||||
|
||||
return coseKey;
|
||||
}
|
||||
|
||||
@@ -122,8 +122,7 @@ namespace BTCPayServer.Hosting
|
||||
})
|
||||
.AddCachedMetadataService(config =>
|
||||
{
|
||||
//They'll be used in a "first match wins" way in the order registered
|
||||
config.AddStaticMetadataRepository();
|
||||
config.AddFidoMetadataRepository();
|
||||
});
|
||||
var descriptor = services.Single(descriptor => descriptor.ServiceType == typeof(Fido2Configuration));
|
||||
services.Remove(descriptor);
|
||||
@@ -133,7 +132,7 @@ namespace BTCPayServer.Hosting
|
||||
return new Fido2Configuration()
|
||||
{
|
||||
ServerName = "BTCPay Server",
|
||||
Origin = $"{httpContext.HttpContext.Request.Scheme}://{httpContext.HttpContext.Request.Host}",
|
||||
Origins = new[] { $"{httpContext.HttpContext.Request.Scheme}://{httpContext.HttpContext.Request.Host}" }.ToHashSet(),
|
||||
ServerDomain = httpContext.HttpContext.Request.Host.Host
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@using Newtonsoft.Json.Linq
|
||||
@model BTCPayServer.Fido2.Models.LoginWithFido2ViewModel
|
||||
|
||||
<div class="twoFaBox">
|
||||
@@ -24,7 +25,7 @@
|
||||
<script>
|
||||
document.getElementById('btn-retry').addEventListener('click', () => window.location.reload())
|
||||
// send to server for registering
|
||||
window.makeAssertionOptions = @Safe.Json(Model.Data);
|
||||
window.makeAssertionOptions = @Safe.Json(JObject.Parse(Model.Data));
|
||||
</script>
|
||||
<script src="~/js/webauthn/helpers.js" asp-append-version="true"></script>
|
||||
<script src="~/js/webauthn/login.js" asp-append-version="true"></script>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@using Newtonsoft.Json.Linq
|
||||
@model Fido2NetLib.CredentialCreateOptions
|
||||
@{
|
||||
ViewData.SetActivePage(ManageNavPages.TwoFactorAuthentication, StringLocalizer["Register your security device"]);
|
||||
@@ -42,7 +43,7 @@
|
||||
<script>
|
||||
document.getElementById('btn-retry').addEventListener('click', function () { window.location.reload() });
|
||||
// send to server for registering
|
||||
window.makeCredentialOptions = @Json.Serialize(Model);
|
||||
window.makeCredentialOptions = @Json.Serialize(JToken.Parse(Model.ToJson()));
|
||||
</script>
|
||||
<script src="~/js/webauthn/helpers.js"></script>
|
||||
<script src="~/js/webauthn/register.js"></script>
|
||||
|
||||
@@ -40,7 +40,7 @@ async function verifyAssertionWithServer(assertedCredential) {
|
||||
extensions: assertedCredential.getClientExtensionResults(),
|
||||
response: {
|
||||
authenticatorData: coerceToBase64Url(authData),
|
||||
clientDataJson: coerceToBase64Url(clientDataJSON),
|
||||
clientDataJSON: coerceToBase64Url(clientDataJSON),
|
||||
signature: coerceToBase64Url(sig)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,8 +49,8 @@ async function registerNewCredential(newCredential) {
|
||||
type: newCredential.type,
|
||||
extensions: newCredential.getClientExtensionResults(),
|
||||
response: {
|
||||
AttestationObject: coerceToBase64Url(attestationObject),
|
||||
clientDataJson: coerceToBase64Url(clientDataJSON)
|
||||
attestationObject: coerceToBase64Url(attestationObject),
|
||||
clientDataJSON: coerceToBase64Url(clientDataJSON)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user