|
|
@@ -1,8 +1,14 @@
|
|
|
-using System.Text;
|
|
|
+using System.Security.Cryptography;
|
|
|
+using System.Security.Cryptography.X509Certificates;
|
|
|
+using System.Text;
|
|
|
using System.Text.Json;
|
|
|
using System.Text.Json.Nodes;
|
|
|
using Fido2NetLib;
|
|
|
using Fido2NetLib.Objects;
|
|
|
+using JWT.Algorithms;
|
|
|
+using JWT.Builder;
|
|
|
+using Microsoft.Extensions.Options;
|
|
|
+using Microsoft.IdentityModel.Tokens;
|
|
|
|
|
|
namespace Passwordless;
|
|
|
|
|
|
@@ -11,11 +17,15 @@ public class CredentialManager
|
|
|
private readonly IFido2 _fido2;
|
|
|
private readonly OptionsCache _optionsCache;
|
|
|
private readonly JsonSerializerOptions _jsonOptions;
|
|
|
+ private readonly JwtConfig _jwtConfig;
|
|
|
+ private readonly RSA _rsa;
|
|
|
|
|
|
- public CredentialManager(IFido2 fido2, OptionsCache optionsCache)
|
|
|
+ public CredentialManager(IFido2 fido2, OptionsCache optionsCache, IOptions<JwtConfig> jwtOptions)
|
|
|
{
|
|
|
_fido2 = fido2;
|
|
|
_optionsCache = optionsCache;
|
|
|
+ _jwtConfig = jwtOptions.Value;
|
|
|
+ _rsa = ToRsa(_jwtConfig.Key!);
|
|
|
|
|
|
_jsonOptions = new JsonSerializerOptions()
|
|
|
{
|
|
|
@@ -116,7 +126,7 @@ public class CredentialManager
|
|
|
return options;
|
|
|
}
|
|
|
|
|
|
- public async Task<AssertionVerificationResult> VerifyCredential(AuthenticatorAssertionRawResponse assertionResponse)
|
|
|
+ public async Task<string> VerifyCredential(AuthenticatorAssertionRawResponse assertionResponse)
|
|
|
{
|
|
|
// 1. Get the assertion options we sent the client
|
|
|
var optionsWithName = _optionsCache.Get<OptionsWithName<AssertionOptions>>(Convert.ToBase64String(assertionResponse.Id));
|
|
|
@@ -124,9 +134,9 @@ public class CredentialManager
|
|
|
|
|
|
if (File.Exists($"./data/{optionsWithName.Name}.json"))
|
|
|
{
|
|
|
- await using var fileStream = File.OpenRead($"./data/{optionsWithName.Name}.json");
|
|
|
+ await using var fileStream1 = File.OpenRead($"./data/{optionsWithName.Name}.json");
|
|
|
assertionVerification =
|
|
|
- (AttestationVerificationSuccess)(await JsonSerializer.DeserializeAsync(fileStream,
|
|
|
+ (AttestationVerificationSuccess)(await JsonSerializer.DeserializeAsync(fileStream1,
|
|
|
typeof(AttestationVerificationSuccess), _jsonOptions))!;
|
|
|
}
|
|
|
else
|
|
|
@@ -147,18 +157,63 @@ public class CredentialManager
|
|
|
// 5. Make the assertion
|
|
|
var result = await _fido2.MakeAssertionAsync(assertionResponse, optionsWithName.Options, assertionVerification.PublicKey, storedCounter, Callback);
|
|
|
|
|
|
- if (result.Status == "ok")
|
|
|
+ if (result.Status != "ok")
|
|
|
{
|
|
|
- assertionVerification.Counter = result.Counter;
|
|
|
- await using var fileStream = File.OpenWrite($"./data/{optionsWithName.Name}.json");
|
|
|
- await JsonSerializer.SerializeAsync(fileStream, assertionVerification, _jsonOptions);
|
|
|
+ throw new Exception($"Verification failed: {result.ErrorMessage}");
|
|
|
}
|
|
|
+
|
|
|
+ assertionVerification.Counter = result.Counter;
|
|
|
+ await using var fileStream2 = File.OpenWrite($"./data/{optionsWithName.Name}.json");
|
|
|
+ await JsonSerializer.SerializeAsync(fileStream2, assertionVerification, _jsonOptions);
|
|
|
|
|
|
+ // var store = new X509Store(StoreName.My);
|
|
|
+ // store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
|
|
|
+ // var collection = store.Certificates.Find(X509FindType.FindByThumbprint, "CE63FA2B6BCD8460150E7FBF9B138E9ED9852E3A", false);
|
|
|
+ // var certificate = collection[0];
|
|
|
+
|
|
|
+ var token = JwtBuilder.Create()
|
|
|
+ .WithAlgorithm(new RS256Algorithm(_rsa, _rsa))
|
|
|
+ .Issuer("http://localhost:5172")
|
|
|
+ .Audience("http://localhost:5172")
|
|
|
+ .IssuedAt(DateTime.UtcNow)
|
|
|
+ .ExpirationTime(DateTime.UtcNow.AddHours(1))
|
|
|
+ .Subject(optionsWithName.Name)
|
|
|
+ .AddClaim("permissions", new string[] { "MagicClaim", "Foo", "Test" })
|
|
|
+ .AddClaim("scope", "passkey")
|
|
|
+ .Encode();
|
|
|
+
|
|
|
// TODO: ???
|
|
|
// if (result.DevicePublicKey is not null)
|
|
|
// creds.DevicePublicKeys.Add(result.DevicePublicKey);
|
|
|
|
|
|
- // 7. return OK to client
|
|
|
- return result;
|
|
|
+ return token;
|
|
|
+ }
|
|
|
+
|
|
|
+ private RSA ToRsa(JsonWebKey key)
|
|
|
+ {
|
|
|
+ var rsaParameters = new RSAParameters
|
|
|
+ {
|
|
|
+ // PUBLIC KEY PARAMETERS
|
|
|
+ // n parameter - public modulus
|
|
|
+ Modulus = Base64UrlEncoder.DecodeBytes(key.N),
|
|
|
+ // e parameter - public exponent
|
|
|
+ Exponent = Base64UrlEncoder.DecodeBytes(key.E),
|
|
|
+
|
|
|
+ // PRIVATE KEY PARAMETERS (optional)
|
|
|
+ // d parameter - the private exponent value for the RSA key
|
|
|
+ D = Base64UrlEncoder.DecodeBytes(key.D),
|
|
|
+ // dp parameter - CRT exponent of the first factor
|
|
|
+ DP = Base64UrlEncoder.DecodeBytes(key.DP),
|
|
|
+ // dq parameter - CRT exponent of the second factor
|
|
|
+ DQ = Base64UrlEncoder.DecodeBytes(key.DQ),
|
|
|
+ // p parameter - first prime factor
|
|
|
+ P = Base64UrlEncoder.DecodeBytes(key.P),
|
|
|
+ // q parameter - second prime factor
|
|
|
+ Q = Base64UrlEncoder.DecodeBytes(key.Q),
|
|
|
+ // qi parameter - CRT coefficient of the second factor
|
|
|
+ InverseQ = Base64UrlEncoder.DecodeBytes(key.QI)
|
|
|
+ };
|
|
|
+
|
|
|
+ return RSA.Create(rsaParameters);
|
|
|
}
|
|
|
}
|