//public DecayingDouble AllFailures(TimeSpan halfLife) => AccountFailures.Add(halfLife, PasswordFailures); //public DecayingDouble AccountFailuresSubsetWithInfrequentPassword(TimeSpan halfLife) => AccountFailures.Subtract(halfLife, AccountFailuresSubsetWithFrequentPassword); //public DecayingDouble PasswordFailuresSubsetWithInfrequentPassword(TimeSpan halfLife) => PasswordFailures.Subtract(halfLife, PasswordFailuresSubsetWithFrequentPassword); //public DecayingDouble PasswordFailuresSubsetWithoutTypo(TimeSpan halfLife) => PasswordFailures.Subtract(halfLife, PasswordFailuresSubsetWithTypo); //public DecayingDouble PasswordFailuresSubsetWithoutEitherFrequentPasswordOrTypo(TimeSpan halfLife) => PasswordFailures.Subtract(halfLife, PasswordFailuresSubsetWithTypoAndFrequentPassword); /// <summary> /// This analysis will examine the client IP's previous failed attempts to login to this account /// to determine if any failed attempts were due to typos. /// </summary> /// <param name="account">The account that the client is currently trying to login to.</param> /// <param name="whenUtc"></param> /// <param name="correctPassword">The correct password for this account. (We can only know it because /// the client must have provided the correct one this loginAttempt.)</param> /// <returns></returns> public void AdjustBlockingScoreForPastTyposTreatedAsFullFailures( Simulator simulator, SimulatedUserAccount account, DateTime whenUtc, string correctPassword) { SimLoginAttemptSummaryForTypoAnalysis[] recentPotentialTypos = RecentPotentialTypos.MostRecentFirst.ToArray(); foreach (SimLoginAttemptSummaryForTypoAnalysis potentialTypo in recentPotentialTypos) { if (account == null || potentialTypo.UsernameOrAccountId != account.UsernameOrAccountId) { continue; } // Use an edit distance calculation to determine if it was a likely typo bool likelyTypo = EditDistance.Calculate(potentialTypo.Password, correctPassword) <= simulator._experimentalConfiguration.BlockingOptions.MaxEditDistanceConsideredATypo; TimeSpan halfLife = simulator._experimentalConfiguration.BlockingOptions.BlockScoreHalfLife; DecayingDouble value = new DecayingDouble(1d, potentialTypo.WhenUtc); // Add this to the list of changed attempts if (potentialTypo.WasPasswordFrequent) { PasswordFailuresNoTypoFrequentPassword.SubtractInPlace(halfLife, value); PasswordFailuresTypoFrequentPassword.AddInPlace(halfLife, value); } RecentPotentialTypos.Remove(potentialTypo); } }
protected void AdjustBlockingScoreForPastTyposTreatedAsFullFailures( IpHistory clientsIpHistory, IUserAccountController <TUserAccount> accountController, TUserAccount account, DateTime whenUtc, string correctPassword, byte[] phase1HashOfCorrectPassword) { double credit = 0d; if (clientsIpHistory == null) { return; } LoginAttemptSummaryForTypoAnalysis[] recentPotentialTypos = clientsIpHistory.RecentPotentialTypos.MostRecentFirst.ToArray(); Encryption.IPrivateKey privateAccountLogKey = null; try { foreach (LoginAttemptSummaryForTypoAnalysis potentialTypo in recentPotentialTypos) { bool usernameCorrect = potentialTypo.UsernameOrAccountId == account.UsernameOrAccountId; if (usernameCorrect) { string passwordSubmittedInFailedAttempt = null; if (account.GetType().Name == "SimulatedUserAccount") { passwordSubmittedInFailedAttempt = potentialTypo.EncryptedIncorrectPassword.Ciphertext; } else { if (privateAccountLogKey == null) { try { privateAccountLogKey = accountController.DecryptPrivateAccountLogKey(account, phase1HashOfCorrectPassword); } catch (Exception) { return; } } try { passwordSubmittedInFailedAttempt = potentialTypo.EncryptedIncorrectPassword.Read(privateAccountLogKey); } catch (Exception) { } } if (passwordSubmittedInFailedAttempt != null) { bool passwordHadTypo = EditDistance.Calculate(passwordSubmittedInFailedAttempt, correctPassword) <= _options.MaxEditDistanceConsideredATypo; if (passwordHadTypo) { credit += potentialTypo.Penalty.GetValue(_options.AccountCreditLimitHalfLife, whenUtc) * (1d - _options.PenaltyMulitiplierForTypo); } } } clientsIpHistory.RecentPotentialTypos.Remove(potentialTypo); } clientsIpHistory.CurrentBlockScore.SubtractInPlace(account.CreditHalfLife, credit, whenUtc); } finally { privateAccountLogKey?.Dispose(); } }
/// <summary> /// This analysis will examine the client IP's previous failed attempts to login to this account /// to determine if any failed attempts were due to typos. /// </summary> /// <param name="clientsIpHistory">Records of this client's previous attempts to examine.</param> /// <param name="accountController"></param> /// <param name="account">The account that the client is currently trying to login to.</param> /// <param name="whenUtc"></param> /// <param name="correctPassword">The correct password for this account. (We can only know it because /// the client must have provided the correct one this loginAttempt.)</param> /// <param name="phase1HashOfCorrectPassword">The phase1 hash of that correct password (which we could /// recalculate from the information in the previous parameters, but doing so would be expensive.)</param> /// <returns></returns> protected void AdjustBlockingScoreForPastTyposTreatedAsFullFailures( IpHistory clientsIpHistory, IUserAccountController <TUserAccount> accountController, TUserAccount account, DateTime whenUtc, string correctPassword, byte[] phase1HashOfCorrectPassword) { double credit = 0d; if (clientsIpHistory == null) { return; } LoginAttemptSummaryForTypoAnalysis[] recentPotentialTypos = clientsIpHistory.RecentPotentialTypos.MostRecentFirst.ToArray(); Encryption.IPrivateKey privateAccountLogKey = null; try { foreach (LoginAttemptSummaryForTypoAnalysis potentialTypo in recentPotentialTypos) { bool usernameCorrect = potentialTypo.UsernameOrAccountId == account.UsernameOrAccountId; //bool usernameHadTypo = !usernameCorrect && // EditDistance.Calculate(potentialTypo.UsernameOrAccountId, account.UsernameOrAccountId) <= //_options.MaxEditDistanceConsideredATypo; if (usernameCorrect) // || usernameHadTypo { // Get the plaintext password from the previous login attempt string passwordSubmittedInFailedAttempt = null; if (account.GetType().Name == "SimulatedUserAccount") { passwordSubmittedInFailedAttempt = potentialTypo.EncryptedIncorrectPassword.Ciphertext; } else { if (privateAccountLogKey == null) { // Get the EC decryption key, which is stored encrypted with the Phase1 password hash try { privateAccountLogKey = accountController.DecryptPrivateAccountLogKey(account, phase1HashOfCorrectPassword); } catch (Exception) { // There's a problem with the key that prevents us from decrypting it. We won't be able to do this analysis. return; } } // Now try to decrypt the incorrect password from the previous attempt and perform the typo analysis try { // Attempt to decrypt the password. passwordSubmittedInFailedAttempt = potentialTypo.EncryptedIncorrectPassword.Read(privateAccountLogKey); } catch (Exception) { // An exception is likely due to an incorrect key (perhaps outdated). // Since we simply can't do anything with a record we can't Decrypt, we carry on // as if nothing ever happened. No. Really. Nothing to see here. } } if (passwordSubmittedInFailedAttempt != null) { //bool passwordCorrect = passwordSubmittedInFailedAttempt == correctPassword; bool passwordHadTypo = // !passwordCorrect && EditDistance.Calculate(passwordSubmittedInFailedAttempt, correctPassword) <= _options.MaxEditDistanceConsideredATypo; // Get credit for this nearly-correct attempt to counter penalty if (passwordHadTypo) { credit += potentialTypo.Penalty.GetValue(_options.AccountCreditLimitHalfLife, whenUtc) * (1d - _options.PenaltyMulitiplierForTypo); } } } // Now that we know whether this past event was a typo or not, we no longer need to keep track // of it (and we should remove it so we don't double credit it). clientsIpHistory.RecentPotentialTypos.Remove(potentialTypo); } // Remove the amount to be credited from the block score due to the discovery of typos clientsIpHistory.CurrentBlockScore.SubtractInPlace(account.CreditHalfLife, credit, whenUtc); } finally { privateAccountLogKey?.Dispose(); } }
public void EditDistanceTranspose() { Assert.Equal(0.8f, EditDistance.Calculate("Transposition", "Transopsition", new EditDistance.Costs(transpose: .8f))); }
public void EditDistanceChangeCase() { Assert.Equal(0.8f, EditDistance.Calculate("CaseSensitive", "casesensitive", new EditDistance.Costs(caseChange: 0.4f))); }
public void EditDistanceAdd() { Assert.Equal(0.4f, EditDistance.Calculate("dd", "Add", new EditDistance.Costs(add: 0.4f))); Assert.Equal(0.4f, EditDistance.Calculate("Add", "Add!", new EditDistance.Costs(add: 0.4f))); }
public void EditDistanceDelete() { Assert.Equal(0.4f, EditDistance.Calculate("Deletion", "eletion", new EditDistance.Costs(delete: 0.4f))); }
public void EditDistanceSubstitute() { Assert.Equal(0.75f, EditDistance.Calculate("Substitution", "Sabstitution!", new EditDistance.Costs(substitute: .25f, add: .5f))); }