public async Task <CacheItem> ExecuteAsync(string context)
        {
            var encryptionToken = Guid.NewGuid().ToString("N");

            using var rng = new RNGCryptoServiceProvider();
            var vector = new byte[16];

            rng.GetBytes(vector);
            var vectorValue = Convert.ToBase64String(vector);

            return(await _cacheItemRepository.UpdateAsync(context, item =>
            {
                item.EncKey = encryptionToken;
                item.EncVector = vectorValue;

                if (_coreConfiguration.TFAEnabled &&
                    _coreConfiguration.Fido2FallbackBehavior == Fido2FallbackBehavior.Passcode)
                {
                    item.AuthCookieType = CookieType.Passcode;
                }
                else
                {
                    item.AuthCookieType = CookieType.Basic;
                }
            }));
        }
        public async Task ExecuteAsync(UserIdentitiesData input, CacheItem relatedItem)
        {
            var recoveryToken = !string.IsNullOrEmpty(input.RecoveryData) ? Guid.NewGuid().ToString("N") : null;

            // Credentials created at web app
            if (input.ActualChallengeType.HasValue && input.ActualChallengeType != relatedItem.ChallengeType)
            {
                relatedItem.ChallengeType = input.ActualChallengeType.Value;
            }
            // check if users exists or not. If not - then this is LinkOnLogin
            else if (relatedItem.ChallengeType == ChallengeType.Login)
            {
                var isUserExists = await _userHandlerAdapter.IsUserExistsAsync(input.PublicKey);

                if (!isUserExists)
                {
                    relatedItem.ChallengeType = ChallengeType.LinkOnLogin;
                }
            }

            await _cacheItemRepository.UpdateAsync(relatedItem.Context, item =>
            {
                item.ChallengeType = relatedItem.ChallengeType;
                item.RecoveryToken = recoveryToken;
                item.RecoveryData  = input.RecoveryData;
                item.FinishFlow(input.DID, input.PublicKey);
            });
        }
Example #3
0
        public async Task <string> ExecuteAsync(Input input)
        {
            var respToken = _jwtService.GetJwtHash(input.ResponseJwt).EncodeBase64String();

            await _cacheItemRepository.UpdateAsync(input.Context, cacheItem =>
            {
                cacheItem.RequestToken  = input.RequestToken;
                cacheItem.ResponseToken = respToken;
                cacheItem.RecoveryToken = _cookieService.GetVersionedCookieValues(input.RecoveryTokenCookieValue).Values
                                          ?.First();

                // ignore fido2 credential for recover because it will be recreated
                var authCookie = _cookieService.GetVersionedCookieValues(!string.IsNullOrEmpty(input.CredIdCookieValue)
                    ? input.CredIdCookieValue
                    : input.EncryptionTokenCookieValue);

                if (authCookie.Type.HasValue)
                {
                    cacheItem.AuthCookieType = authCookie.Type.Value;
                }

                if (authCookie.Type == CookieType.Fido2 && cacheItem.ChallengeType != ChallengeType.Recover)
                {
                    cacheItem.Fido2CredentialId = authCookie.Values?.First();
                }
                else if ((authCookie.Type == CookieType.Basic || authCookie.Type == CookieType.Passcode) &&
                         authCookie.Values?.Length == 2)
                {
                    cacheItem.EncKey    = authCookie.Values[0];
                    cacheItem.EncVector = authCookie.Values[1];
                }
            });

            return(respToken);
        }
        public async Task ExecuteAsync(ApproveActionRequest request)
        {
            if (string.IsNullOrEmpty(request.Context) || string.IsNullOrEmpty(request.Nonce))
            {
                throw new CommandValidationException("Context and nonce are required");
            }


            await _cacheItemRepository.UpdateAsync(request.Context, item =>
            {
                if (item.Nonce != request.Nonce)
                {
                    throw new ArgumentException(
                        $"Can not find any item with context '{request.Context}' and nonce '{request.Nonce}'");
                }

                if (item.Status != CacheItemStatus.WaitingForApproval)
                {
                    throw new ArgumentException(
                        $"Incorrect status={item.Status.ToString()} for approval '{request.Context}'");
                }

                item.Status = request.IsApproved ? CacheItemStatus.Approved : CacheItemStatus.Declined;
            });
        }
        public async Task <CacheItem> ExecuteAsync(JwtContainer jwtContainer, CacheItem relatedItem)
        {
            var userData = _jwtService.GetDataFromJwt <UserProfileData>(jwtContainer.Jwt).Data;

            await _recoveryHandler.RemoveConnectionsAsync(userData.PublicKey);

            // TODO: remove? classic flow
            if (!_coreConfiguration.OverwriteFields)
            {
                userData.Profile = null;
            }

            // TODO: code duplication
            var recoveryToken = !string.IsNullOrEmpty(userData.RecoveryData) ? Guid.NewGuid().ToString("N") : null;

            await _recoveryHandler.OnRecoverAsync(userData.DID, new OwnIdConnection
            {
                PublicKey     = userData.PublicKey,
                RecoveryToken = recoveryToken,
                RecoveryData  = userData.RecoveryData,
                AuthType      = relatedItem.AuthCookieType == CookieType.Passcode
                    ? ConnectionAuthType.Passcode
                    : ConnectionAuthType.Basic
            });

            return(await _cacheItemRepository.UpdateAsync(relatedItem.Context, item =>
            {
                item.RecoveryToken = recoveryToken;
                item.RecoveryData = userData.RecoveryData;
                item.FinishFlow(relatedItem.DID, userData.PublicKey);
            }));
        }
Example #6
0
        public async Task ExecuteAsync(CacheItem cacheItem)
        {
            if (string.IsNullOrEmpty(cacheItem.Fido2CredentialId))
            {
                return;
            }

            var userExists = await _checkUserExistenceCommand.ExecuteAsync(new UserIdentification
            {
                AuthenticatorType = ExtAuthenticatorType.Fido2,
                UserIdentifier    = cacheItem.Fido2CredentialId
            });

            if (userExists && cacheItem.ChallengeType != ChallengeType.Login)
            {
                throw new OwnIdException(ErrorType.UserAlreadyExists);
            }

            if (!userExists)
            {
                await _cacheItemRepository.UpdateAsync(cacheItem.Context, item =>
                {
                    item.Fido2CredentialId = null;
                });
            }
        }
Example #7
0
 public async Task ExecuteAsync(string context, string errorMessage)
 {
     await _cacheItemRepository.UpdateAsync(context, cacheItem =>
     {
         cacheItem.Status = CacheItemStatus.Finished;
         cacheItem.Error  = errorMessage;
     });
 }
        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);
            }));
        }
 protected virtual async Task <CacheItem> ProcessFido2RegisterResponseAsync(CacheItem relatedItem,
                                                                            string publicKey,
                                                                            uint signatureCounter, string credentialId)
 {
     return(await CacheItemRepository.UpdateAsync(relatedItem.Context, item =>
     {
         item.FinishFlow(relatedItem.DID, publicKey);
         item.Fido2SignatureCounter = signatureCounter;
         item.Fido2CredentialId = credentialId;
     }));
 }
Example #10
0
        public async Task <CacheItem> ExecuteAsync(string context)
        {
            var random = new Random();
            var pin    = random.Next(0, 9999).ToString("D4");

            return(await _cacheItemRepository.UpdateAsync(context, item =>
            {
                item.ConcurrentId = Guid.NewGuid().ToString();
                item.SecurityCode = pin;
                item.Status = CacheItemStatus.WaitingForApproval;
            }));
        }
Example #11
0
        public async Task <CacheItem> ExecuteAsync(CacheItem relatedItem)
        {
            if (_accountRecoveryHandler == null)
            {
                throw new InternalLogicException("Missing Recovery feature");
            }

            // Recover access
            var recoverResult = await _accountRecoveryHandler.RecoverAsync(relatedItem.Payload);

            return(await _cacheItemRepository.UpdateAsync(relatedItem.Context,
                                                          item => { item.DID = recoverResult.DID; }));
        }
        public async Task <CacheItem> ExecuteAsync(JwtContainer input, CacheItem relatedItem)
        {
            var userData = _jwtService.GetDataFromJwt <UserIdentitiesData>(input.Jwt).Data;

            if (relatedItem.ChallengeType == ChallengeType.Link && relatedItem.DID != userData.DID)
            {
                throw new CommandValidationException($"Wrong user for linking {userData.DID}");
            }


            var userExists = await _userHandlerAdapter.IsUserExistsAsync(userData.PublicKey);

            if (userExists)
            {
                throw new OwnIdException(ErrorType.UserAlreadyExists);
            }

            // preventing data substitution
            userData.DID = relatedItem.DID;

            // TODO: code duplication
            var recoveryToken = !string.IsNullOrEmpty(userData.RecoveryData) ? Guid.NewGuid().ToString("N") : null;

            var connection = new OwnIdConnection
            {
                PublicKey     = userData.PublicKey,
                RecoveryToken = recoveryToken,
                RecoveryData  = userData.RecoveryData,
                AuthType      = relatedItem.AuthCookieType switch
                {
                    CookieType.Fido2 => ConnectionAuthType.Fido2,
                    CookieType.Passcode => ConnectionAuthType.Passcode,
                    _ => ConnectionAuthType.Basic
                }
            };

            await _linkHandler.OnLinkAsync(userData.DID, connection);

            return(await _cacheItemRepository.UpdateAsync(relatedItem.Context, item =>
            {
                item.RecoveryToken = recoveryToken;
                item.RecoveryData = userData.RecoveryData;
                item.FinishFlow(userData.DID, userData.PublicKey);
            }));
        }
Example #13
0
        private async Task <ITransitionResult> SwitchConnectionAuthTypeAsync(CacheItem relatedItem,
                                                                             TransitionInput <JwtContainer> input, bool supportsFido2, string publicKey)
        {
            relatedItem.NewAuthType
                = supportsFido2 && _configuration.Fido2.IsEnabled
                    ? ConnectionAuthType.Fido2
                    : ConnectionAuthType.Passcode;

            var composeInfo = new BaseJwtComposeInfo(input)
            {
                EncKey    = relatedItem.EncKey,
                EncVector = relatedItem.EncVector
            };

            switch (relatedItem.NewAuthType)
            {
            case ConnectionAuthType.Passcode:
                composeInfo.Behavior = new FrontendBehavior(StepType.EnterPasscode, relatedItem.ChallengeType,
                                                            GetNextBehaviorFunc(input, relatedItem))
                {
                    AlternativeBehavior = new FrontendBehavior(StepType.ResetPasscode, relatedItem.ChallengeType,
                                                               new CallAction(UrlProvider.GetResetPasscodeUrl(relatedItem.Context),
                                                                              HttpMethod.Delete.ToString()))
                };
                break;

            case ConnectionAuthType.Fido2:
            {
                await _cacheItemRepository.UpdateAsync(relatedItem.Context, item => item.OldPublicKey = publicKey);

                var fido2Url = UrlProvider.GetFido2Url(relatedItem.Context, relatedItem.RequestToken,
                                                       input.CultureInfo?.Name);
                composeInfo.Behavior = FrontendBehavior.CreateRedirect(fido2Url);
                break;
            }

            default:
                throw new ArgumentOutOfRangeException();
            }

            var jwt = JwtComposer.GenerateBaseStepJwt(composeInfo);

            return(new StateResult(jwt, _cookieService.CreateAuthCookies(relatedItem)));
        }
        private async Task <GetStatusResponse> GetContextStatus(GetStatusRequest requestItem)
        {
            if (string.IsNullOrEmpty(requestItem.Context) || string.IsNullOrEmpty(requestItem.Nonce))
            {
                return(null);
            }

            var cacheItem = await _cacheItemRepository.GetAsync(requestItem.Context, false);

            if (cacheItem == null || cacheItem.Nonce != requestItem.Nonce || cacheItem.Status == CacheItemStatus.Popped)
            {
                return(null);
            }

            var result = new GetStatusResponse
            {
                Status  = cacheItem.Status,
                Context = requestItem.Context
            };

            if (cacheItem.SecurityCode != null && cacheItem.Status == CacheItemStatus.WaitingForApproval)
            {
                // TODO: refactor to entity
                result.Payload = new
                {
                    data = new
                    {
                        pin = cacheItem.SecurityCode
                    }
                }
            }
            ;

            if (cacheItem.Status != CacheItemStatus.Finished)
            {
                return(result);
            }

            await _cacheItemRepository.UpdateAsync(cacheItem.Context,
                                                   item => { item.Status = CacheItemStatus.Popped; });

            if (!string.IsNullOrEmpty(cacheItem.Error))
            {
                result.Payload = new AuthResult <object>(_localizationService.GetLocalizedString(cacheItem.Error));
                return(result);
            }

            var action = cacheItem.ChallengeType.ToString();

            if (cacheItem.FlowType == FlowType.Authorize)
            {
                result.Payload = await _userHandlerAdapter.OnSuccessLoginAsync(cacheItem.DID, cacheItem.PublicKey);
            }
            else
            {
                switch (cacheItem.ChallengeType)
                {
                case ChallengeType.Login
                    when cacheItem.FlowType == FlowType.Fido2Login &&
                    string.IsNullOrWhiteSpace(cacheItem.Fido2CredentialId):
                {
                    var errorMessage = _localizationService.GetLocalizedString("Error_UserNotFound");
                    result.Payload = new AuthResult <object>(errorMessage);
                    break;
                }

                case ChallengeType.Login
                    when !string.IsNullOrWhiteSpace(cacheItem.Fido2CredentialId) &&
                    cacheItem.Fido2SignatureCounter.HasValue:
                {
                    result.Payload = await _userHandlerAdapter.OnSuccessLoginByFido2Async(
                        cacheItem.Fido2CredentialId,
                        cacheItem.Fido2SignatureCounter.Value);

                    break;
                }

                case ChallengeType.Login:
                    result.Payload = await _userHandlerAdapter.OnSuccessLoginByPublicKeyAsync(cacheItem.PublicKey);

                    break;

                case ChallengeType.LinkOnLogin:
                    action         = ChallengeType.Link.ToString();
                    result.Payload = SetPartialRegisterResult(cacheItem);
                    break;

                case ChallengeType.Register
                    when await _userHandlerAdapter.IsUserExistsAsync(cacheItem.PublicKey):
                {
                    var errorMessage = _localizationService.GetLocalizedString("Error_PhoneAlreadyConnected");
                    result.Payload = new AuthResult <object>(errorMessage);
                    return(result);
                }

                case ChallengeType.Register
                    when string.IsNullOrWhiteSpace(cacheItem.Fido2CredentialId):
                {
                    result.Payload = SetPartialRegisterResult(cacheItem);
                    break;
                }

                case ChallengeType.Register:
                    result.Payload = new
                    {
                        data = new
                        {
                            pubKey = cacheItem.PublicKey,
                            fido2SignatureCounter = cacheItem.Fido2SignatureCounter.ToString(),
                            fido2CredentialId     = cacheItem.Fido2CredentialId
                        }
                    };
                    if (cacheItem.InitialChallengeType == ChallengeType.Login &&
                        cacheItem.ChallengeType == ChallengeType.Register)
                    {
                        action = ChallengeType.Link.ToString();
                    }
                    break;

                //
                // TODO: fix needed at web-ui-sdk to avoid error in console if data is undefined
                //
                case ChallengeType.Recover:
                case ChallengeType.Link:
                    result.Payload = new
                    {
                        data = new { }
                    };
                    break;
                }
            }

            result.Metadata = _jwtService.GenerateDataJwt(new Dictionary <string, object>
            {
                {
                    "data", new
                    {
                        action,
                        authType = cacheItem.GetAuthType()
                    }
                }
            });

            return(result);
        }
        public async Task <FrontendBehavior> ExecuteAsync(CacheItem cacheItem, string routingPayload)
        {
            if (!_coreConfiguration.Fido2.IsEnabled)
            {
                return(null);
            }

            // If this is second attempt to call same start endpoint
            if (cacheItem.FlowType == FlowType.Fido2Login ||
                cacheItem.FlowType == FlowType.Fido2Register ||
                cacheItem.FlowType == FlowType.Fido2Link ||
                cacheItem.FlowType == FlowType.Fido2LinkWithPin ||
                cacheItem.FlowType == FlowType.Fido2Recover ||
                cacheItem.FlowType == FlowType.Fido2RecoverWithPin)
            {
                return(null);
            }

            // Not supported for Fido2 flows
            if (cacheItem.FlowType != FlowType.PartialAuthorize &&
                cacheItem.FlowType != FlowType.Link &&
                cacheItem.FlowType != FlowType.Recover &&
                cacheItem.FlowType != FlowType.LinkWithPin &&
                cacheItem.FlowType != FlowType.RecoverWithPin)
            {
                return(null);
            }

            if (string.IsNullOrEmpty(routingPayload))
            {
                return(null);
            }

            var routing = OwnIdSerializer.Deserialize <ExtAuthenticationRouting>(routingPayload);

            if (routing.Authenticator != ExtAuthenticatorType.Fido2)
            {
                return(null);
            }

            if (!string.IsNullOrEmpty(routing.Error))
            {
                _logger.LogError($"Found error in FIDO2 ExtAuthenticationRouting object: {routing.Error}");
                return(null);
            }

            var initialFlowType      = cacheItem.FlowType;
            var initialChallengeType = cacheItem.ChallengeType;

            switch (routing.Type)
            {
            case "l":
            {
                if (cacheItem.FlowType == FlowType.PartialAuthorize)
                {
                    cacheItem.FlowType = FlowType.Fido2Login;
                }
                break;
            }

            case "r":
                cacheItem.FlowType = cacheItem.FlowType switch
                {
                    FlowType.PartialAuthorize => FlowType.Fido2Register,
                    FlowType.Link => FlowType.Fido2Link,
                    FlowType.LinkWithPin => FlowType.Fido2LinkWithPin,
                    FlowType.Recover => FlowType.Fido2Recover,
                    FlowType.RecoverWithPin => FlowType.Fido2RecoverWithPin,
                    _ => cacheItem.FlowType
                };

                if (cacheItem.FlowType == FlowType.Fido2Register)
                {
                    cacheItem.ChallengeType = ChallengeType.Register;
                }
                break;

            default:
                throw new InternalLogicException($"Incorrect fido2 request: '{routing.Type}'");
            }

            if (initialFlowType == cacheItem.FlowType)
            {
                return(null);
            }

            await _cacheItemRepository.UpdateAsync(cacheItem.Context, item =>
            {
                item.FlowType      = cacheItem.FlowType;
                item.ChallengeType = cacheItem.ChallengeType;
            });

            if (_eventsMetricsService != null && initialChallengeType != cacheItem.ChallengeType)
            {
                _eventsMetricsService?.LogSwitchAsync(initialChallengeType.ToEventType());
                _eventsMetricsService?.LogStartAsync(cacheItem.ChallengeType.ToEventType());
            }

            // TODO: rework to exclude explicit url creation
            return(new FrontendBehavior(StepType.Fido2Authorize, cacheItem.ChallengeType,
                                        new CallAction(_urlProvider.GetChallengeUrl(cacheItem.Context, ChallengeType.Register, "/fido2"))));
        }