public async Task <bool> CompleteLogin(string userId, AuthenticatorAssertionRawResponse response)
        {
            await using var dbContext = _contextFactory.CreateContext();
            var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials)
                       .FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId);

            if (user == null || !LoginStore.TryGetValue(userId, out var options))
            {
                return(false);
            }

            var credential = user.Fido2Credentials
                             .Where(fido2Credential => fido2Credential.Type is Fido2Credential.CredentialType.FIDO2)
                             .Select(fido2Credential => (fido2Credential, fido2Credential.GetBlob()))
                             .FirstOrDefault(fido2Credential => fido2Credential.Item2.Descriptor.Id.SequenceEqual(response.Id));

            if (credential.Item2 is null)
            {
                return(false);
            }

            // 5. Make the assertion
            var res = await _fido2.MakeAssertionAsync(response, options, credential.Item2.PublicKey,
                                                      credential.Item2.SignatureCounter, x => Task.FromResult(true));

            // 6. Store the updated counter
            credential.Item2.SignatureCounter = res.Counter;
            credential.fido2Credential.SetBlob(credential.Item2);
            await dbContext.SaveChangesAsync();

            LoginStore.Remove(userId, out _);

            // 7. return OK to client
            return(true);
        }
        public async Task <JsonResult> SignIn([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                var user = await GetUserByCredentials(clientResponse.Id);

                var credential = JsonConvert.DeserializeObject <StoredCredential>(user.Profile["PasswordlessPublicKey"].ToString());

                var result = await fido2.MakeAssertionAsync(clientResponse, options, credential.PublicKey, credential.SignatureCounter,
                                                            args => Task.FromResult(credential.UserHandle.SequenceEqual(args.UserHandle)));

                await UpdateCounter(user, credential, result.Counter);

                var claims = new List <Claim>
                {
                    new Claim(ClaimTypes.Name, user.Profile.Email)
                };

                var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);

                await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));

                return(Json(result));
            }
            catch (Exception e)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = e.Message
                }));
            }
        }
Beispiel #3
0
        public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database

                var signInUser = await _signInManager.GetTwoFactorAuthenticationUserAsync();

                var applicationUser = await _userManager.FindByNameAsync(signInUser.UserName);

                var creds = _fido2CredentialService.GetCredentialByUser(applicationUser);



                if (creds == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await _fido2CredentialService.GetCredentialsByUserHandleAsync(args.UserHandle);

                    return(storedCreds.Exists(c => new PublicKeyCredentialDescriptor(c.Descriptor).Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _fido2.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);

                // 6. Store the updated counter
                var code = await _fido2CredentialService.UpdateCounter(res.CredentialId, res.Counter);

                // TODO Fix counter
                // TODO Allow more than one key to work

                // var result = await _signInManager.TwoFactorSignInAsync("Fido2", string.Empty, false, false);
                await _signInManager.SignInAsync(applicationUser, false);

                // 7. return OK to client
                return(Json(res));
            }
            catch (Exception e)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
        public async Task <CacheItem> ExecuteAsync(string fido2Payload, CacheItem relatedItem)
        {
            var request = OwnIdSerializer.Deserialize <Fido2LoginRequest>(fido2Payload);

            var storedFido2Info = await _userHandlerAdapter.FindFido2InfoAsync(request.CredentialId);

            if (storedFido2Info == null)
            {
                throw new OwnIdException(ErrorType.UserNotFound);
            }

            var options = new AssertionOptions
            {
                Challenge = Encoding.ASCII.GetBytes(relatedItem.Context),
                RpId      = _configuration.Fido2.RelyingPartyId
            };

            var fidoResponse = new AuthenticatorAssertionRawResponse
            {
                Extensions = new AuthenticationExtensionsClientOutputs(),
                Id         = Base64Url.Decode(request.CredentialId),
                RawId      = Base64Url.Decode(request.CredentialId),
                Response   = new AuthenticatorAssertionRawResponse.AssertionResponse
                {
                    AuthenticatorData = Base64Url.Decode(request.AuthenticatorData),
                    ClientDataJson    = Base64Url.Decode(request.ClientDataJson),
                    Signature         = Base64Url.Decode(request.Signature),
                    UserHandle        = Base64Url.Decode(storedFido2Info.UserId)
                },
                Type = PublicKeyCredentialType.PublicKey
            };

            var result = await _fido2.MakeAssertionAsync(
                fidoResponse,
                options,
                Base64Url.Decode(storedFido2Info.PublicKey),
                storedFido2Info.SignatureCounter,
                args =>
            {
                var storedCredentialId     = Base64Url.Decode(storedFido2Info.CredentialId);
                var storedCredDescriptor   = new PublicKeyCredentialDescriptor(storedCredentialId);
                var credIdValidationResult = storedCredDescriptor.Id.SequenceEqual(args.CredentialId);

                return(Task.FromResult(credIdValidationResult));
            });

            return(await _cacheItemRepository.UpdateAsync(relatedItem.Context, item =>
            {
                item.PublicKey = storedFido2Info.PublicKey;
                item.Fido2SignatureCounter = result.Counter;
                item.Fido2CredentialId = request.CredentialId;
                item.FinishFlow(storedFido2Info.UserId, storedFido2Info.PublicKey);
            }));
        }
Beispiel #5
0
        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.WebAuthn);
            var keys     = LoadKeys(provider);

            if (!provider.MetaData.ContainsKey("login"))
            {
                return(false);
            }

            var clientResponse = JsonSerializer.Deserialize <AuthenticatorAssertionRawResponse>(token,
                                                                                                new JsonSerializerOptions {
                PropertyNameCaseInsensitive = true
            });

            var jsonOptions = provider.MetaData["login"].ToString();
            var options     = AssertionOptions.FromJson(jsonOptions);

            var webAuthCred = keys.Find(k => k.Item2.Descriptor.Id.SequenceEqual(clientResponse.Id));

            if (webAuthCred == null)
            {
                return(false);
            }

            IsUserHandleOwnerOfCredentialIdAsync callback = (args) => Task.FromResult(true);

            var res = await _fido2.MakeAssertionAsync(clientResponse, options, webAuthCred.Item2.PublicKey, webAuthCred.Item2.SignatureCounter, callback);

            provider.MetaData.Remove("login");

            // Update SignatureCounter
            webAuthCred.Item2.SignatureCounter = res.Counter;

            var providers = user.GetTwoFactorProviders();

            providers[TwoFactorProviderType.WebAuthn].MetaData[webAuthCred.Item1] = webAuthCred.Item2;
            user.SetTwoFactorProviders(providers);
            await userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn, logEvent : false);

            return(res.Status == "ok");
        }
Beispiel #6
0
        public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var creds = PasswordlessStore.GetCredentialById(clientResponse.Id);

                if (creds == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await PasswordlessStore.GetCredentialsByUserHandleAsync(args.UserHandle);

                    return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _fido2.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter,
                                                          callback);

                // 6. Store the updated counter
                PasswordlessStore.UpdateCounter(res.CredentialId, res.Counter);

                if (res.Status == "ok")
                {
                    var username = System.Text.Encoding.UTF8.GetString(creds.UserId);
                    await SignInOidc(username);
                }
                // 7. return OK to client
                return(Json(res));
            }
            catch (Exception e)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
        MakeAssertionAsync(AnonymousAuthenticatorAssertionRawResponse clientResponse, string deviceName)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                if (string.IsNullOrWhiteSpace(clientResponse.RequestId))
                {
                    _logger.LogError("Attempted to make assertion without request id");
                    return(Result.Failure <(User, string, string), (bool, string)>((false, "No user id or request id provided")));
                }
                var cacheKey = $"AssertionOptions:{clientResponse.RequestId}";

                if (!_cache.TryGetValue(cacheKey, out AssertionOptions options))
                {
                    _logger.LogError($"Failed to find assertion options for key '{cacheKey}'");
                    return(Result.Failure <(User, string, string), (bool, string)>((true, "No assertion found")));
                }
                _cache.Remove(cacheKey);

                // 2. Get registered credential from database
                var authorization = await _db.DeviceAuthorizations
                                    .Include(x => x.User)
                                    //todo In .net 5, ef-core 5 will support using SequenceEqual, for now we'll use normal equality and just remember that it will be properly translated into sql for real databases, see https://github.com/dotnet/efcore/issues/10582
                                    //.FirstOrDefaultAsync(x => x.CredentialId.SequenceEqual(clientResponse.Id));
                                    .FirstOrDefaultAsync(x => x.CredentialId == clientResponse.Id);

                if (authorization is null)
                {
                    _logger.LogError("Failed to find credentials");
                    return(Result.Failure <(User, string, string), (bool, string)>((true, "No credentials found")));
                }

                // 3. Make the assertion
                var avr = await _fido2.MakeAssertionAsync(clientResponse, options, authorization.PublicKey, authorization.SignatureCounter, x => Task.FromResult(true));

                // 4. Store the updated counter
                authorization.SignatureCounter = avr.Counter;

                var login = _userService.GenerateAndSaveLogin(authorization.User, deviceName, authorization.Id);
                return(Result.Success <(User user, string accessToken, string refreshToken), (bool unauthorized, string message)>(login));
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Failed to make assertion for user");
                return(Result.Failure <(User, string, string), (bool, string)>((false, "Error making assertion")));
            }
        }
        public async Task <IActionResult> MakeAssertion(AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var credentialIdString = Base64Url.Encode(clientResponse.Id);
                var cred = await context.StoredCredentials.Where(x => x.DescriptorJson.Contains(credentialIdString)).FirstOrDefaultAsync();

                if (cred == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = cred.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await context.StoredCredentials.Where(x => x.UserHandle == args.UserHandle).ToListAsync();

                    return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _fido2.MakeAssertionAsync(clientResponse, options, cred.PublicKey, storedCounter, callback);

                // 6. Store the updated counter
                cred.SignatureCounter = res.Counter;
                await context.SaveChangesAsync();

                // 7. return OK to client
                return(Ok(res));
            }
            catch (Exception e)
            {
                return(BadRequest(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
Beispiel #9
0
        /// <summary>
        /// 断言:验证断言响应
        /// <para>当客户端返回响应时,我们对其进行验证并接受登录。</para>
        /// </summary>
        public async Task <AssertionVerificationResult> MakeAssertionAsync(User user, AuthenticatorAssertionRawResponse clientRespons)
        {
            if (user == null)
            {
                return(new AssertionVerificationResult()
                {
                    Status = "bad"
                });
            }
            // 1. Get the assertion options we sent the client
            var jsonOptions = distributedCache.GetString(user.UserId.ToString() + "assertionOptions");

            if (string.IsNullOrEmpty(jsonOptions))
            {
                return(new AssertionVerificationResult()
                {
                    Status = "fail",
                    ErrorMessage = "time out"
                });
            }
            var options = AssertionOptions.FromJson(jsonOptions);
            // 2. Get registered credential from database
            var storedCredential = this.GetFIDO2ItemByUserId(user.UserId);
            // 3. Get credential counter from database

            var creds = user.FIDO2Items.Where(b => b.CredentialId.SequenceEqual(clientRespons.Id)).FirstOrDefault();

            var storedCounter = creds.SignatureCounter;
            // 4. Create callback to check if userhandle owns the credentialId

            IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
            {
                return(storedCredential.Exists(c => c.CredentialId.SequenceEqual(args.CredentialId)));
            };
            // 5. Make the assertion
            var res = await _fido2.MakeAssertionAsync(clientRespons, options, creds.PublicKey, storedCounter, callback);

            // 6. Store the updated counter
            creds.SignatureCounter = res.Counter;
            dataContext.SaveChanges();
            return(res);
        }