├── .gitattributes ├── .gitignore ├── Binomial Ladder Sketch Parameter Calculator ├── App.config ├── Binomial Ladder Filter Parameter Calculator.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── License.txt ├── PostSimulationAnalysis ├── PostSimulationAnalysis.xproj ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ └── launchSettings.json └── project.json ├── PostSimulationAnalysisOldRuntime ├── App.config ├── BlockingCollectionExtensions.cs ├── Condition.cs ├── PostSimulationAnalysisOldRuntime.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── ROCPoint.cs └── Trial.cs ├── README.md ├── Simulator ├── ConcurrentStreamWriter.cs ├── DebugLogger.cs ├── Distributions.cs ├── ExperimentalConfiguration.cs ├── IPAddressDebugInfo.cs ├── IPPool.cs ├── ParameterSweeper.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── SimIpHistory.cs ├── SimulatedAccounts.cs ├── SimulatedLoginAttempt.cs ├── SimulatedLoginAttemptGenerator.cs ├── SimulatedPasswords.cs ├── SimulatedUserAccount.cs ├── SimulatedUserAccountController.cs ├── Simulator.cs ├── Simulator.xproj ├── WeightedSelector.cs └── project.json ├── StopGuessing.sln ├── StopGuessing ├── AccountStorage │ ├── Memory │ │ ├── MemoryOnlyStableStore.cs │ │ ├── MemoryUserAccount.cs │ │ └── MemoryUserAccountController.cs │ └── Sql │ │ ├── DbUserAccount.cs │ │ ├── DbUserAccountContext.cs │ │ ├── DbUserAccountController.cs │ │ ├── DbUserAccountCreditBalance.cs │ │ ├── DbUserAccountRepository.cs │ │ ├── DbUserAccountRepositoryFactory.cs │ │ ├── IncorrectPhaseTwoHashEntity.cs │ │ ├── SuccessfulLoginCookieEntitty.cs │ │ └── TableKeyEncoding.cs ├── Clients │ ├── DistributedBinomialLadderFilterClient.cs │ └── LoginAttemptClient.cs ├── Controllers │ ├── DistributedBinomialLadderController.cs │ ├── IUserAccountController.cs │ ├── LoginAttemptController.cs │ ├── UserAccountController.cs │ └── ValuesController.cs ├── DataStructures │ ├── AgingSketch.cs │ ├── ArrayOfUnsignedNumericOfNonstandardSize.cs │ ├── BinomialDistribution.cs │ ├── BinomialLadderFilter.cs │ ├── Cache.cs │ ├── CapacityConstrainedSet.cs │ ├── ConsistentHashRing.cs │ ├── DecayingDouble.cs │ ├── DictionaryThatTracksAccessRecency.cs │ ├── DistributedResponsibilitySet.cs │ ├── EditDistance.cs │ ├── FilterArray.cs │ ├── FixedSizeLRUCache.cs │ ├── FrequencyTracker.cs │ ├── MaxWeightHashing.cs │ ├── MemoryUsageLimiter.cs │ ├── MultiperiodFrequencyTracker.cs │ ├── Proportion.cs │ ├── RestClient.cs │ ├── SelfLoadingCache.cs │ ├── Sequence.cs │ ├── SequenceWithFastLookup.cs │ ├── Sketch.cs │ ├── SmallCapacityConstrainedSet.cs │ ├── StaticUtilities.cs │ └── TaskParalllel.cs ├── EncryptionPrimitives │ ├── ECEncryptedMessage_AES_CBC_HMACSHA256.cs │ ├── EncryptedValueField.cs │ ├── Encryption.cs │ ├── ExpensiveHashFunction.cs │ ├── ManagedSHA256.cs │ ├── StrongRandomNumberGenerator.cs │ └── UniversalHash.cs ├── Interfaces │ ├── IBinomialLadderFilter.cs │ ├── IDistributedResponsibilitySet.cs │ ├── IFactory.cs │ ├── IStableStore.cs │ ├── IUserAccount.cs │ └── IUserAccountFactory.cs ├── Migrations │ ├── 20160429174517_DbContextFirstMigration.Designer.cs │ ├── 20160429174517_DbContextFirstMigration.cs │ └── DbUserAccountContextModelSnapshot.cs ├── Models │ ├── AuthenticationOutcome.cs │ ├── BlockingAlgorithmOptions.cs │ ├── IpHistory.cs │ ├── LoginAttempt.cs │ ├── LoginAttemptSummaryForTypoAnalysis.cs │ └── RemoteHost.cs ├── Program.cs ├── Project_Readme.html ├── Properties │ └── launchSettings.json ├── Startup.cs ├── StopGuessing.xproj ├── Utilities │ ├── ReaderWriterLockSlimExtension.cs │ └── TaskHelper.cs ├── appsettings.json ├── project.json └── web.config ├── StopGuessingOld ├── AccountStorage │ ├── Memory │ │ ├── MemoryOnlyStableStore.cs │ │ ├── MemoryUserAccount.cs │ │ └── MemoryUserAccountController.cs │ └── Sql │ │ ├── DbUserAccount.cs │ │ ├── DbUserAccountContext.cs │ │ ├── DbUserAccountController.cs │ │ ├── DbUserAccountCreditBalance.cs │ │ ├── DbUserAccountRepository.cs │ │ ├── DbUserAccountRepositoryFactory.cs │ │ ├── IncorrectPhaseTwoHashEntity.cs │ │ ├── SuccessfulLoginCookieEntitty.cs │ │ └── TableKeyEncoding.cs ├── Clients │ ├── DistributedBinomialLadderFilterClient.cs │ └── LoginAttemptClient.cs ├── Controllers │ ├── DistributedBinomialLadderController.cs │ ├── IUserAccountController.cs │ ├── LoginAttemptController.cs │ └── UserAccountController.cs ├── DataStructures │ ├── AgingSketch.cs │ ├── ArrayOfUnsignedNumericOfNonstandardSize.cs │ ├── BinomialDistribution.cs │ ├── BinomialLadderFilter.cs │ ├── Cache.cs │ ├── CapacityConstrainedSet.cs │ ├── ConsistentHashRing.cs │ ├── DecayingDouble.cs │ ├── DictionaryThatTracksAccessRecency.cs │ ├── DistributedResponsibilitySet.cs │ ├── DoubleThatDecaysWithTime.cs │ ├── EditDistance.cs │ ├── FilterArray.cs │ ├── FixedSizeLRUCache.cs │ ├── FrequencyTracker.cs │ ├── MaxWeightHashing.cs │ ├── MemoryUsageLimiter.cs │ ├── MultiperiodFrequencyTracker.cs │ ├── Proportion.cs │ ├── RestClient.cs │ ├── SelfLoadingCache.cs │ ├── Sequence.cs │ ├── SequenceWithFastLookup.cs │ ├── Sketch.cs │ ├── SmallCapacityConstrainedSet.cs │ ├── StaticUtilities.cs │ └── TaskParalllel.cs ├── EncryptionPrimitives │ ├── ECEncryptedMessage_AES_CBC_HMACSHA256.cs │ ├── EncryptedValueField.cs │ ├── Encryption.cs │ ├── ExpensiveHashFunction.cs │ ├── ManagedSHA256.cs │ ├── StrongRandomNumberGenerator.cs │ └── UniversalHash.cs ├── Interfaces │ ├── IBinomialLadderFilter.cs │ ├── IDistributedResponsibilitySet.cs │ ├── IFactory.cs │ ├── IStableStore.cs │ ├── IUserAccount.cs │ └── IUserAccountFactory.cs ├── Migrations │ ├── 20160429174517_DbContextFirstMigration.Designer.cs │ ├── 20160429174517_DbContextFirstMigration.cs │ └── DbUserAccountContextModelSnapshot.cs ├── Models │ ├── AuthenticationOutcome.cs │ ├── BlockingAlgorithmOptions.cs │ ├── IpHistory.cs │ ├── LoginAttempt.cs │ ├── LoginAttemptSummaryForTypoAnalysis.cs │ └── RemoteHost.cs ├── Project_Readme.html ├── Properties │ ├── AssemblyInfo.cs │ ├── PublishProfiles │ │ ├── stopguessing-publish.ps1 │ │ └── stopguessing.pubxml │ └── launchSettings.json ├── Service References │ └── Application Insights │ │ └── ConnectedService.json ├── Startup.cs ├── StopGuessingOld.xproj ├── Utilities │ └── TaskHelper.cs ├── appsettings.json ├── config.json ├── project.json └── wwwroot │ ├── index.html │ └── web.config ├── StopGuessingTests ├── BinomialLadderFilterTest.cs ├── CacheTest.cs ├── CapacityConstrainedSetTest.cs ├── ConsistentHashRingTest.cs ├── DbUserAccountTests.cs ├── DecayingDoubleTests.cs ├── EditDistanceTest.cs ├── EncryptionUnitTest.cs ├── FunctionalTests.cs ├── MaxWeightHashingTest.cs ├── MemoryLimitingTest.cs ├── Properties │ └── AssemblyInfo.cs ├── Pseudorandom.cs ├── SequenceTest.cs ├── StopGuessingTests.xproj ├── TableKeyEncodingTests.cs ├── UniversalHashTest.cs └── project.json └── global.json /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Binomial Ladder Sketch Parameter Calculator/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Binomial Ladder Sketch Parameter Calculator/Binomial Ladder Filter Parameter Calculator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A5CE9C56-5AD2-486C-87DD-782D6178A799} 8 | Exe 9 | Properties 10 | Binomial_Ladder_Sketch_Parameter_Calculator 11 | Binomial Ladder Sketch Parameter Calculator 12 | v4.6 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | -------------------------------------------------------------------------------- /Binomial Ladder Sketch Parameter Calculator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Binomial Ladder Sketch Parameter Calculator")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Binomial Ladder Sketch Parameter Calculator")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a5ce9c56-5ad2-486c-87dd-782d6178a799")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | StopGuessing 2 | 3 | Copyright (c) Microsoft Corporation 4 | All rights reserved. 5 | MIT License 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /PostSimulationAnalysis/PostSimulationAnalysis.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 4cd3a6e1-2e18-4be8-9027-e5a2fddde02d 11 | PostSimulationAnalysis 12 | ..\artifacts\obj\$(MSBuildProjectName) 13 | ..\artifacts\bin\$(MSBuildProjectName)\ 14 | 15 | 16 | 17 | 2.0 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /PostSimulationAnalysis/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PostSimulationAnalysis")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PostSimulationAnalysis")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4cd3a6e1-2e18-4be8-9027-e5a2fddde02d")] 24 | -------------------------------------------------------------------------------- /PostSimulationAnalysis/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "PostSimulationAnalysis": { 4 | "commandName": "PostSimulationAnalysis", 5 | "sdkVersion": "dnx-clr-win-x64.1.0.0-rc1-update1" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /PostSimulationAnalysis/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "description": "PostSimulationAnalysis Console Application", 4 | "authors": [ "stus" ], 5 | "tags": [ "" ], 6 | "projectUrl": "", 7 | "licenseUrl": "", 8 | 9 | "dependencies": { 10 | }, 11 | 12 | "commands": { 13 | "PostSimulationAnalysis": "PostSimulationAnalysis" 14 | }, 15 | 16 | "frameworks": { 17 | "dnx451": { } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PostSimulationAnalysisOldRuntime/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PostSimulationAnalysisOldRuntime/BlockingCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | // From: http://blogs.msdn.com/b/pfxteam/archive/2010/04/06/9990420.aspx 9 | 10 | namespace PostSimulationAnalysisOldRuntime 11 | { 12 | public static class BlockingCollectionExtensions 13 | { 14 | public static Partitioner GetConsumingPartitioner( 15 | this BlockingCollection collection) 16 | { 17 | return new BlockingCollectionPartitioner(collection); 18 | } 19 | 20 | private class BlockingCollectionPartitioner : Partitioner 21 | { 22 | private BlockingCollection _collection; 23 | 24 | internal BlockingCollectionPartitioner( 25 | BlockingCollection collection) 26 | { 27 | if (collection == null) 28 | throw new ArgumentNullException("collection"); 29 | _collection = collection; 30 | } 31 | 32 | public override bool SupportsDynamicPartitions 33 | { 34 | get { return true; } 35 | } 36 | 37 | public override IList> GetPartitions( 38 | int partitionCount) 39 | { 40 | if (partitionCount < 1) 41 | throw new ArgumentOutOfRangeException("partitionCount"); 42 | var dynamicPartitioner = GetDynamicPartitions(); 43 | return Enumerable.Range(0, partitionCount).Select(_ => 44 | dynamicPartitioner.GetEnumerator()).ToArray(); 45 | } 46 | 47 | public override IEnumerable GetDynamicPartitions() 48 | { 49 | return _collection.GetConsumingEnumerable(); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /PostSimulationAnalysisOldRuntime/PostSimulationAnalysisOldRuntime.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0E1898E6-417D-463D-A0C7-7D4A53452538} 8 | Exe 9 | Properties 10 | PostSimulationAnalysisOldRuntime 11 | PostSimulationAnalysisOldRuntime 12 | v4.6 13 | 512 14 | true 15 | 16 | 17 | x64 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /PostSimulationAnalysisOldRuntime/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PostSimulationAnalysisOldRuntime")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PostSimulationAnalysisOldRuntime")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0e1898e6-417d-463d-a0c7-7d4a53452538")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PostSimulationAnalysisOldRuntime/ROCPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PostSimulationAnalysisOldRuntime 8 | { 9 | public class ROCPoint 10 | { 11 | public int FalsePositives; 12 | public int FalsePositiveUniqueUsers; 13 | public int FalsePositiveUniqueIPs; 14 | public int TruePositives; 15 | public int FalseNegatives; 16 | public int TrueNegatives; 17 | public double BlockingThreshold; 18 | public int FalsePositivesWithProxy; 19 | public int FalsePositivesWithAttackerIp; 20 | public int FalsePositivesWithProxyAndAttackerIp; 21 | public long FalseNegativesWithProxy; 22 | public long FalseNegativesWithBenignIp; 23 | public long FalseNegativesWithBenignProxyIp; 24 | 25 | public ROCPoint(int falsePositives, int falsePositiveUniqueUsers, int falsePositiveUniqueIPs, 26 | int truePositives, int falseNegatives, int trueNegatives, 27 | int falsePositivesWithAttackerIp, 28 | int falsePositivesWithProxy, 29 | int falsePositivesWithProxyAndAttackerIp, 30 | long falseNegativesWithBenignIp, 31 | long falseNegativesWithProxy, 32 | long falseNegativesWithBenignProxyIp, 33 | double blockingThreshold) 34 | { 35 | FalsePositives = falsePositives; 36 | FalsePositiveUniqueUsers = falsePositiveUniqueUsers; 37 | FalsePositiveUniqueIPs = falsePositiveUniqueIPs; 38 | TruePositives = truePositives; 39 | FalseNegatives = falseNegatives; 40 | TrueNegatives = trueNegatives; 41 | FalsePositivesWithProxy = falsePositivesWithProxy; 42 | FalsePositivesWithAttackerIp = falsePositivesWithAttackerIp; 43 | FalsePositivesWithProxyAndAttackerIp = falsePositivesWithProxyAndAttackerIp; 44 | FalseNegativesWithProxy = falseNegativesWithProxy; 45 | FalseNegativesWithBenignIp = falseNegativesWithBenignIp; 46 | FalseNegativesWithBenignProxyIp = falseNegativesWithBenignProxyIp; 47 | BlockingThreshold = blockingThreshold; 48 | } 49 | 50 | public static double fractionOrZeroIfNaN(int numerator, int denominator) 51 | { 52 | if (denominator == 0) 53 | return 0; 54 | return ((double)numerator / (double)denominator); 55 | } 56 | 57 | 58 | public double FalsePositiveRate => fractionOrZeroIfNaN(FalsePositives, FalsePositives + TrueNegatives); 59 | public double TruePositiveRate => fractionOrZeroIfNaN(TruePositives, TruePositives + FalseNegatives); 60 | public double Precision => fractionOrZeroIfNaN(TruePositives, TruePositives + FalsePositives); 61 | public double Recall => fractionOrZeroIfNaN(TruePositives, TruePositives + FalseNegatives); 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StopGuessing 2 | A system for protecting password-based authentication systems from online-guessing attacks. 3 | 4 | ## Purpose 5 | Services that employ passwords to authenticate users are subject to online-guessing attacks. 6 | Attackers can pick a common password and try to login to the user's account with that password. 7 | If services don't do anything to stop this attack, attackers can issue millions of guesses and compromise many accounts. 8 | Some services block user accounts after a few failed guesses, but if attackers are trying to login to all user accounts 9 | this will cause all users to be locked out. 10 | Thus, more advanced systems to prevent online-guessing attacks block IP addresses engaged in guessing, rather than 11 | the accounts targeted by guessers. 12 | 13 | StopGuessing is a reference implementation of an IP reputation framework. 14 | It provides two unique features not present in previous system. 15 | First, StopGuessing identifies frequently-occuring passwords in failed login attempts to identify which passwords are being frequently guessed by attackers. 16 | It can provide stronger protection to users whose passwords are among those being guessed frequently, and provide faster blocking to IP addresses that guess these passwords. 17 | To detect frequently-occuring incorrect passwords, it uses a new data structure called a binomial ladder filter. 18 | Second, StopGuessing is able to identify which login attempts have failed due to typos of the users' password, 19 | and be less quick to conclude that an IP that submitted the typo is guessing than for a failure that is not caused by a typo. 20 | 21 | For more information about the motivation for this approach, the underlying algorithms, and for simulations that measure the 22 | efficacy of StopGuessing against different attacks, see the following papers: 23 | 24 | The Binomial Ladder Filter: https://research.microsoft.com/... 25 | StopGuessing: https://research.microsoft.com/... 26 | 27 | ## Project Structure 28 | 29 | 30 | 31 | ## Contributing 32 | There are many opportunities to contribute to the StopGuessing project. 33 | You might want to help the system use additional IP reputation information, or information about the geographic location or other features of IPs. 34 | You might want to make it easier to use StopGuessing on other platforms. 35 | You might want to port part or all of the code to be native to other languages. 36 | You might want to build support for the binomial ladder filter into memory databases. 37 | If you'd like to contribute, the best way to get started is to reach out to us at stopguessing@microsoft.com. 38 | 39 | -------------------------------------------------------------------------------- /Simulator/ConcurrentStreamWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Threading; 8 | 9 | namespace Simulator 10 | { 11 | public class ConcurrentStreamWriter : IDisposable 12 | { 13 | private readonly TextWriter _textWriter; 14 | private readonly BlockingCollection _writeQueue; 15 | private Task _backgroundWriterTask; 16 | 17 | public ConcurrentStreamWriter(TextWriter textWriter) 18 | { 19 | _textWriter = textWriter; 20 | _writeQueue = new BlockingCollection(); 21 | _backgroundWriterTask = Task.Run(() => BackgroundWritingLoop()); 22 | } 23 | 24 | public ConcurrentStreamWriter(string path, FileMode fileMode = FileMode.CreateNew, 25 | FileAccess fileAccess = FileAccess.Write) 26 | : this(new StreamWriter(new FileStream(path, fileMode, fileAccess))) 27 | { 28 | } 29 | 30 | public void Write(string stringToWrite) 31 | { 32 | _writeQueue.Add(stringToWrite); 33 | } 34 | 35 | public void WriteLine(string stringToWrite) 36 | { 37 | _writeQueue.Add(stringToWrite + "\r\n"); 38 | } 39 | 40 | private bool _closed = false; 41 | public void Close() 42 | { 43 | bool close = false; 44 | lock (this) 45 | { 46 | if (!_closed) 47 | { 48 | _closed = close = true; 49 | } 50 | } 51 | 52 | if (close) 53 | { 54 | _writeQueue.CompleteAdding(); 55 | _backgroundWriterTask.Wait(); 56 | _textWriter.Flush(); 57 | _textWriter.Dispose(); 58 | } 59 | } 60 | public void Dispose() 61 | { 62 | Close(); 63 | } 64 | 65 | 66 | private void BackgroundWritingLoop() 67 | { 68 | try 69 | { 70 | foreach (string item in _writeQueue.GetConsumingEnumerable()) 71 | { 72 | _textWriter.Write(item); 73 | } 74 | } 75 | catch (System.OperationCanceledException) 76 | {} 77 | } 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Simulator/DebugLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Simulator 8 | { 9 | public class DebugLogger 10 | { 11 | private TextWriter writer; 12 | private readonly DateTime whenStarted; 13 | private long _lastMemory = 0; 14 | private DateTime _lastEventTime; 15 | 16 | public DebugLogger(TextWriter writer) 17 | { 18 | this.writer = writer; 19 | whenStarted = _lastEventTime = DateTime.UtcNow; 20 | } 21 | 22 | public void WriteStatus(string status, params Object[] args) 23 | { 24 | DateTime now = DateTime.UtcNow; 25 | TimeSpan eventTime = now - whenStarted; 26 | TimeSpan sinceLastEvent = now - _lastEventTime; 27 | long eventMemory = GC.GetTotalMemory(false); 28 | long memDiff = eventMemory - _lastMemory; 29 | long eventMemoryMB = eventMemory / (1024L * 1024L); 30 | long memDiffMB = memDiff / (1024L * 1024L); 31 | _lastMemory = eventMemory; 32 | _lastEventTime = now; 33 | Console.Out.WriteLine("Time: {0:00}:{1:00}:{2:00}.{3:000} seconds ({4:0.000}), Memory: {5}MB (increased by {6}MB)", 34 | eventTime.Hours, 35 | eventTime.Minutes, 36 | eventTime.Seconds, 37 | eventTime.Milliseconds, 38 | sinceLastEvent.TotalSeconds, 39 | eventMemoryMB, memDiffMB); 40 | Console.Out.WriteLine(status, args); 41 | lock (writer) 42 | { 43 | writer.WriteLine( 44 | "Time: {0:00}:{1:00}:{2:00}.{3:000} seconds ({4:0.000}), Memory: {5}MB (increased by {6}MB)", 45 | eventTime.Hours, 46 | eventTime.Minutes, 47 | eventTime.Seconds, 48 | eventTime.Milliseconds, 49 | sinceLastEvent.TotalSeconds, 50 | eventMemoryMB, memDiffMB); 51 | writer.WriteLine(status, args); 52 | writer.Flush(); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Simulator/Distributions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using StopGuessing.EncryptionPrimitives; 6 | 7 | namespace Simulator 8 | { 9 | public static class Distributions 10 | { 11 | /// For creating benign accounts 12 | public static double GetLogNormal(double mu, double sigma) 13 | { 14 | double x = Math.Exp(mu + sigma * GetNormal()); 15 | return x; 16 | } 17 | 18 | public static double GetNormal() 19 | { 20 | double v1; 21 | double s; 22 | do 23 | { 24 | v1 = 2.0 * StrongRandomNumberGenerator.GetFraction() - 1.0; 25 | double v2 = 2.0 * StrongRandomNumberGenerator.GetFraction() - 1.0; 26 | s = v1 * v1 + v2 * v2; 27 | } while (s >= 1d || s <= 0d); 28 | 29 | s = Math.Sqrt((-2.0 * Math.Log(s)) / s); 30 | return v1 * s; 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Simulator/ExperimentalConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using StopGuessing.Models; 6 | 7 | namespace Simulator 8 | { 9 | public class ExperimentalConfiguration 10 | { 11 | private const int DaysPerYear = 365; 12 | private const int WeeksPerYear = 52; 13 | private const int MonthsPerYear = 12; 14 | private const ulong Thousand = 1000; 15 | private const ulong Million = Thousand*Thousand; 16 | private const ulong Billion = Thousand*Million; 17 | 18 | public class BenignUserAccountGroup 19 | { 20 | public ulong GroupSize; 21 | public ulong LoginsPerYear; 22 | } 23 | 24 | public BlockingAlgorithmOptions BlockingOptions = new BlockingAlgorithmOptions(); 25 | 26 | public TimeSpan TestTimeSpan = new TimeSpan(1, 0, 0, 0); // 1 day 27 | 28 | public enum AttackStrategy 29 | { 30 | BreadthFirst, 31 | Weighted, 32 | UseUntilLikelyPopular 33 | }; 34 | public AttackStrategy AttackersStrategy = AttackStrategy.BreadthFirst; 35 | 36 | public string OutputPath = @"e:\"; 37 | public string OutputDirectoryName = @"Experiment"; 38 | public string PasswordFrequencyFile = @"..\..\rockyou-withcount.txt"; 39 | public string PreviouslyKnownPopularPasswordFile = @"..\..\phpbb.txt"; 40 | 41 | public ulong TotalLoginAttemptsToIssue = 10*Thousand; 42 | 43 | public double ChanceOfCoookieReUse = 0.90d; 44 | public int MaxCookiesPerUserAccount = 10; 45 | 46 | public double ChanceOfIpReUse = 0.85d; 47 | public int MaxIpPerUserAccount = 5; 48 | 49 | public int PopularPasswordsToRemoveFromDistribution = 0; 50 | 51 | public double ChanceOfLongRepeatOfStalePassword = 0.0004; // 1 in 2,500 52 | public double MinutesBetweenLongRepeatOfOldPassword = 5; // an attempt every 5 minutes 53 | public uint LengthOfLongRepeatOfOldPassword = (uint) ( (60 * 24) / 5 ); // 24 hours / an attempt every 5 minutes 54 | public double ChanceOfBenignPasswordTypo = 0.02d; 55 | public double ChanceOfRepeatTypo = 2d/3d; // two thirds 56 | public double ChanceOfRepeatUseOfPasswordFromAnotherAccount = 1d / 3d; // one thirds 57 | public double ChanceOfRepeatWrongAccountName = .2d; // 20% 58 | public double DelayBetweenRepeatBenignErrorsInSeconds = 7d; 59 | public double ChanceOfBenignAccountNameTypoResultingInAValidUserName = 0.02d; 60 | public double ChanceOfAccidentallyUsingAnotherAccountPassword = 0.02d; 61 | 62 | public double FractionOfLoginAttemptsFromAttacker = 0.5d; 63 | 64 | public ulong NumberOfAttackerControlledAccounts = 1*Thousand; 65 | 66 | public uint NumberOfIpAddressesControlledByAttacker = 100;// * (uint)Thousand; 67 | public double FractionOfMaliciousIPsToOverlapWithBenign = 0.01; 68 | 69 | public ulong MaxAttackerGuessesPerPassword = 25; 70 | 71 | public uint ProxySizeInUniqueClientIPs = 1000; 72 | public double FractionOfBenignIPsBehindProxies = 0.20d; 73 | 74 | public double ProbabilityThatAttackerChoosesAnInvalidAccount = 0.10d; 75 | 76 | public uint NumberOfPopularPasswordsForAttackerToExploit = 1*(uint)Thousand; 77 | 78 | public uint NumberOfBenignAccounts = 10*(uint)Thousand; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Simulator/IPAddressDebugInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Simulator 7 | { 8 | /// 9 | /// Information that the simulator maintains about each IP address for reporting purposes 10 | /// 11 | public class IpAddressDebugInfo 12 | { 13 | /// 14 | /// Set if one or more benign users use this IP address to login 15 | /// 16 | public bool UsedByBenignUsers; 17 | 18 | /// 19 | /// Set if one or more attackers use this IP address either to guess legitimate user's passwords 20 | /// or to login to accounts the attackers control (e.g., to provide traffic that makes the IP look 21 | /// like it's in use by legitimate users.) 22 | /// 23 | public bool UsedByAttackers; 24 | 25 | /// 26 | /// Set if the IP is a proxy shared by many users 27 | /// 28 | public bool IsPartOfProxy; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Simulator/ParameterSweeper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Simulator 7 | { 8 | public delegate void ParameterSettingFunction(ExperimentalConfiguration config, T iterationParameter); 9 | 10 | public interface IParameterSweeper 11 | { 12 | int GetParameterCount(); 13 | void SetParameter(ExperimentalConfiguration config, int parameterIndex); 14 | string GetParameterString(int parameterIndex); 15 | } 16 | 17 | 18 | public class ParameterSweeper : IParameterSweeper 19 | { 20 | public string Name; 21 | public T[] Parameters; 22 | public ParameterSettingFunction ParameterSetter; 23 | 24 | public int GetParameterCount() 25 | { 26 | return Parameters.Length; 27 | } 28 | 29 | public void SetParameter(ExperimentalConfiguration config, int parameterIndex) 30 | { 31 | ParameterSetter(config, Parameters[parameterIndex]); 32 | } 33 | 34 | public string GetParameterString(int parameterIndex) 35 | { 36 | return Parameters[parameterIndex].ToString(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Simulator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("Simulator")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("4968aaa1-c21a-42f2-ac43-a5a11feb1b66")] 20 | -------------------------------------------------------------------------------- /Simulator/SimulatedUserAccount.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Net; 3 | using StopGuessing.AccountStorage.Memory; 4 | using StopGuessing.DataStructures; 5 | 6 | namespace Simulator 7 | { 8 | public class SimulatedUserAccount : MemoryUserAccount 9 | { 10 | public string Password; 11 | 12 | public ConcurrentBag Cookies = new ConcurrentBag(); 13 | public ConcurrentBag ClientAddresses = new ConcurrentBag(); 14 | 15 | public DecayingDouble ConsecutiveIncorrectAttempts = new DecayingDouble(0); 16 | public DecayingDouble MaxConsecutiveIncorrectAttempts = new DecayingDouble(0); 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Simulator/Simulator.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 4968aaa1-c21a-42f2-ac43-a5a11feb1b66 11 | Simulator 12 | .\obj 13 | .\bin\ 14 | v4.6 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Simulator/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "Microsoft.NETCore.App": { 9 | "type": "platform", 10 | "version": "1.0.0" 11 | }, 12 | "StopGuessing": "1.0.0-*" 13 | }, 14 | 15 | "frameworks": { 16 | "netcoreapp1.0": { 17 | "imports": [ 18 | "dotnet5.6", 19 | "dnxcore50", 20 | "portable-net451+win8" 21 | ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /StopGuessing.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{22C611CE-29B3-4F1E-A741-13B8B5CC0DCC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSimulationAnalysisOldRuntime", "PostSimulationAnalysisOldRuntime\PostSimulationAnalysisOldRuntime.csproj", "{0E1898E6-417D-463D-A0C7-7D4A53452538}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Binomial Ladder Sketch Parameter Calculator", "Binomial Ladder Sketch Parameter Calculator\Binomial Ladder Sketch Parameter Calculator.csproj", "{A5CE9C56-5AD2-486C-87DD-782D6178A799}" 11 | EndProject 12 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "StopGuessing", "StopGuessing\StopGuessing.xproj", "{39C5479B-744F-4F2B-AD22-3BB4BBAE3B3A}" 13 | EndProject 14 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "StopGuessingTests", "StopGuessingTests\StopGuessingTests.xproj", "{D0F070FB-CF09-41DE-99DF-9193705FB1AB}" 15 | EndProject 16 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Simulator", "Simulator\Simulator.xproj", "{4968AAA1-C21A-42F2-AC43-A5A11FEB1B66}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {0E1898E6-417D-463D-A0C7-7D4A53452538}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {0E1898E6-417D-463D-A0C7-7D4A53452538}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {0E1898E6-417D-463D-A0C7-7D4A53452538}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {0E1898E6-417D-463D-A0C7-7D4A53452538}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {A5CE9C56-5AD2-486C-87DD-782D6178A799}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {A5CE9C56-5AD2-486C-87DD-782D6178A799}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {A5CE9C56-5AD2-486C-87DD-782D6178A799}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {A5CE9C56-5AD2-486C-87DD-782D6178A799}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {39C5479B-744F-4F2B-AD22-3BB4BBAE3B3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {39C5479B-744F-4F2B-AD22-3BB4BBAE3B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {39C5479B-744F-4F2B-AD22-3BB4BBAE3B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {39C5479B-744F-4F2B-AD22-3BB4BBAE3B3A}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {D0F070FB-CF09-41DE-99DF-9193705FB1AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {D0F070FB-CF09-41DE-99DF-9193705FB1AB}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {D0F070FB-CF09-41DE-99DF-9193705FB1AB}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {D0F070FB-CF09-41DE-99DF-9193705FB1AB}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {4968AAA1-C21A-42F2-AC43-A5A11FEB1B66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {4968AAA1-C21A-42F2-AC43-A5A11FEB1B66}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {4968AAA1-C21A-42F2-AC43-A5A11FEB1B66}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {4968AAA1-C21A-42F2-AC43-A5A11FEB1B66}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /StopGuessing/AccountStorage/Memory/MemoryOnlyStableStore.cs: -------------------------------------------------------------------------------- 1 | namespace StopGuessing.AccountStorage.Memory 2 | { 3 | //public class MemoryOnlyStableStore : IStableStore 4 | //{ 5 | // public ConcurrentDictionary Accounts = new ConcurrentDictionary(); 6 | // public ConcurrentDictionary LoginAttempts = new ConcurrentDictionary(); 7 | 8 | 9 | // public async Task IsIpAddressAlwaysPermittedAsync(System.Net.IPAddress clientIpAddress, CancellationToken cancelToken = default(CancellationToken)) 10 | // { 11 | // return await Task.RunInBackground( () => false, cancelToken); 12 | // } 13 | 14 | // public async Task ReadAccountAsync(string usernameOrAccountId, CancellationToken cancelToken) 15 | // { 16 | // if (Accounts == null) 17 | // return null; 18 | // return await Task.RunInBackground( () => 19 | // { 20 | // UserAccount account; 21 | // Accounts.TryGetValue(usernameOrAccountId, out account); 22 | // return account; 23 | // }, cancelToken); 24 | // } 25 | 26 | // public async Task ReadLoginAttemptAsync(string key, CancellationToken cancelToken) 27 | // { 28 | // if (LoginAttempts == null) 29 | // return null; 30 | // return await Task.RunInBackground(() => 31 | // { 32 | // LoginAttempt attempt; 33 | // LoginAttempts.TryGetValue(key, out attempt); 34 | // return attempt; 35 | // }, cancelToken); 36 | // } 37 | 38 | // public async Task> ReadMostRecentLoginAttemptsAsync(System.Net.IPAddress clientIpAddress, int numberToRead, 39 | // bool includeSuccesses = true, bool includeFailures = true, CancellationToken cancelToken = default(CancellationToken)) 40 | // { 41 | // // fail on purpose 42 | // return await Task.RunInBackground(() => new List(), cancelToken); 43 | // } 44 | 45 | // public async Task> ReadMostRecentLoginAttemptsAsync(string usernameOrAccountId, int numberToRead, 46 | // bool includeSuccesses = true, bool includeFailures = true, CancellationToken cancelToken = default(CancellationToken)) 47 | // { 48 | // // fail on purpose 49 | // return await Task.RunInBackground(() => new List(), cancelToken); 50 | // } 51 | 52 | // public async Task WriteAccountAsync(UserAccount account, CancellationToken cancelToken) 53 | // { 54 | // if (Accounts == null) 55 | // return; 56 | 57 | // // REMOVE FOR PRODUCTION 58 | // // For testing the mipact of Task.RunInBackground() on performance 59 | // //if (true) 60 | // //{ 61 | // // Accounts[account.UsernameOrAccountId] = account; 62 | // // return; 63 | // //} 64 | 65 | // await Task.RunInBackground(() => 66 | // { 67 | // Accounts[account.UsernameOrAccountId] = account; 68 | // }, cancelToken); 69 | // } 70 | 71 | // public async Task WriteLoginAttemptAsync(LoginAttempt attempt, CancellationToken cancelToken) 72 | // { 73 | // if (LoginAttempts == null) 74 | // return; 75 | // await Task.RunInBackground(() => 76 | // { 77 | // LoginAttempts[attempt.UniqueKey] = attempt; 78 | // }, cancelToken); 79 | // } 80 | //} 81 | } 82 | -------------------------------------------------------------------------------- /StopGuessing/AccountStorage/Sql/DbUserAccount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using StopGuessing.Controllers; 4 | using StopGuessing.EncryptionPrimitives; 5 | using StopGuessing.Interfaces; 6 | 7 | namespace StopGuessing.AccountStorage.Sql 8 | { 9 | /// 10 | /// An implementation of IUserAccount that stores account information in an SQL database. 11 | /// 12 | public class DbUserAccount : IUserAccount 13 | { 14 | public string DbUserAccountId { get; set; } 15 | 16 | [NotMapped] 17 | public string UsernameOrAccountId => DbUserAccountId; 18 | 19 | /// 20 | /// The salt is a random unique sequence of bytes that is included when the password is hashed (phase 1 of hashing) 21 | /// to ensure that attackers who might obtain the set of account hashes cannot hash a password once and then compare 22 | /// the hash against every account. 23 | /// 24 | public byte[] SaltUniqueToThisAccount { get; set; } = 25 | StrongRandomNumberGenerator.GetBytes(UserAccountController.DefaultSaltLength); 26 | 27 | /// 28 | /// The name of the (hopefully) expensive hash function used for the first phase of password hashing. 29 | /// 30 | public string PasswordHashPhase1FunctionName { get; set; } = 31 | ExpensiveHashFunctionFactory.DefaultFunctionName; 32 | 33 | /// 34 | /// The number of iterations to use for the phase 1 hash to make it more expensive. 35 | /// 36 | public int NumberOfIterationsToUseForPhase1Hash { get; set; } = 37 | UserAccountController.DefaultIterationsForPasswordHash; 38 | 39 | /// 40 | /// An EC public encryption symmetricKey used to store log about password failures, which can can only be decrypted when the user 41 | /// enters her correct password, the expensive (phase1) hash of which is used to symmetrically encrypt the matching EC private symmetricKey. 42 | /// 43 | public byte[] EcPublicAccountLogKey { get; set; } 44 | 45 | /// 46 | /// The EC private symmetricKey encrypted with phase 1 (expensive) hash of the password 47 | /// 48 | public byte[] EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1 { get; set; } 49 | 50 | /// 51 | /// The Phase2 password hash is the result of hasing the password (and salt) first with the expensive hash function to create a Phase1 hash, 52 | /// then hasing that Phase1 hash (this time without the salt) using SHA256 so as to make it unnecessary to store the 53 | /// phase1 hash in this record. Doing so allows the Phase1 hash to be used as a symmetric encryption symmetricKey for the log. 54 | /// 55 | public string PasswordHashPhase2 { get; set; } 56 | 57 | /// 58 | /// The account's credit limit for offsetting penalties for IP addresses from which 59 | /// the account has logged in successfully. 60 | /// 61 | public double CreditLimit { get; set; } = 62 | UserAccountController.DefaultCreditLimit; 63 | 64 | /// 65 | /// The half life with which used credits are removed from the system freeing up new credit 66 | /// 67 | public TimeSpan CreditHalfLife { get; set; } = 68 | new TimeSpan(TimeSpan.TicksPerHour * UserAccountController.DefaultCreditHalfLifeInHours); 69 | 70 | 71 | public DbUserAccount() 72 | { 73 | } 74 | 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /StopGuessing/AccountStorage/Sql/DbUserAccountContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace StopGuessing.AccountStorage.Sql 4 | { 5 | public class DbUserAccountContext : DbContext 6 | { 7 | public DbSet DbUserAccounts { get; set; } 8 | public DbSet DbUserAccountCreditBalances { get; set; } 9 | 10 | public DbUserAccountContext() : base() 11 | { 12 | } 13 | 14 | public DbUserAccountContext(DbContextOptions options) : base(options) 15 | { 16 | } 17 | 18 | protected override void OnModelCreating(ModelBuilder modelBuilder) 19 | { 20 | modelBuilder.Entity().HasIndex(e => e.DbUserAccountId).IsUnique(true); 21 | modelBuilder.Entity().HasKey(e => e.DbUserAccountId); 22 | modelBuilder.Entity() 23 | .Property(e => e.DbUserAccountId) 24 | .IsRequired(); 25 | modelBuilder.Entity().HasIndex(e => e.DbUserAccountId).IsUnique(true); 26 | modelBuilder.Entity().HasKey(e => e.DbUserAccountId); 27 | modelBuilder.Entity() 28 | .Property(e => e.DbUserAccountId) 29 | .IsRequired(); 30 | modelBuilder.Entity() 31 | .Property(e => e.ConsumedCreditsLastValue).IsConcurrencyToken(true); 32 | modelBuilder.Entity() 33 | .Property(e => e.ConsumedCreditsLastUpdatedUtc).IsConcurrencyToken(true); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /StopGuessing/AccountStorage/Sql/DbUserAccountCreditBalance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StopGuessing.AccountStorage.Sql 4 | { 5 | public class DbUserAccountCreditBalance 6 | { 7 | public string DbUserAccountId { get; set; } 8 | 9 | /// 10 | /// A decaying double with the amount of credits consumed against the credit limit 11 | /// used to offset IP blocking penalties. 12 | /// 13 | public double ConsumedCreditsLastValue { get; set; } 14 | 15 | public DateTime? ConsumedCreditsLastUpdatedUtc { get; set; } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /StopGuessing/AccountStorage/Sql/DbUserAccountRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.EntityFrameworkCore; 5 | using StopGuessing.Interfaces; 6 | 7 | namespace StopGuessing.AccountStorage.Sql 8 | { 9 | /// 10 | /// An implementation of a repository for DbUserAccounts to allow these accounts 11 | /// to be read and stored into a database. 12 | /// 13 | public class DbUserAccountRepository : IRepository 14 | { 15 | private readonly DbUserAccountContext _context; 16 | 17 | 18 | public DbUserAccountRepository() 19 | { 20 | _context = new DbUserAccountContext(); 21 | } 22 | 23 | public DbUserAccountRepository(DbContextOptions options) 24 | { 25 | _context = new DbUserAccountContext(options); 26 | } 27 | 28 | 29 | public async Task LoadAsync(string usernameOrAccountId, CancellationToken cancellationToken = default(CancellationToken)) 30 | { 31 | return await _context.DbUserAccounts.Where(u => u.UsernameOrAccountId == usernameOrAccountId).FirstOrDefaultAsync(cancellationToken: cancellationToken); 32 | } 33 | 34 | public async Task AddAsync(DbUserAccount itemToAdd, CancellationToken cancellationToken = default(CancellationToken)) 35 | { 36 | _context.DbUserAccounts.Add(itemToAdd); 37 | await _context.SaveChangesAsync(cancellationToken); 38 | } 39 | 40 | public async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) 41 | { 42 | await _context.SaveChangesAsync(cancellationToken); 43 | } 44 | 45 | public void Dispose() 46 | { 47 | _context?.Dispose(); 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /StopGuessing/AccountStorage/Sql/DbUserAccountRepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using StopGuessing.Interfaces; 4 | 5 | namespace StopGuessing.AccountStorage.Sql 6 | { 7 | 8 | /// 9 | /// A factory for generating repositories that can read and write user accounts to an Azure database. 10 | /// 11 | public class DbUserAccountRepositoryFactory : IUserAccountRepositoryFactory 12 | { 13 | private readonly DbContextOptions _options; 14 | 15 | public DbUserAccountRepositoryFactory() 16 | { 17 | _options = null; 18 | } 19 | 20 | public DbUserAccountRepositoryFactory(DbContextOptions options) 21 | { 22 | _options = options; 23 | } 24 | 25 | public DbUserAccountRepositoryFactory(Action> optionsAction) 26 | { 27 | DbContextOptionsBuilder optionsBuilder = new DbContextOptionsBuilder(); 28 | optionsAction.Invoke(optionsBuilder); 29 | _options = optionsBuilder.Options; 30 | } 31 | 32 | public DbUserAccountRepository CreateDbUserAccountRepository() 33 | { 34 | return _options != null ? new DbUserAccountRepository(_options) : new DbUserAccountRepository(); 35 | } 36 | 37 | public IRepository Create() 38 | { 39 | return CreateDbUserAccountRepository(); 40 | } 41 | 42 | public void Dispose() 43 | { 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /StopGuessing/AccountStorage/Sql/IncorrectPhaseTwoHashEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.WindowsAzure.Storage.Table; 3 | 4 | namespace StopGuessing.AccountStorage.Sql 5 | { 6 | /// 7 | /// Used for storing into an azure table a record that maps a account identifier to sets of incorrect 8 | /// passwords that have been submitted for that account. 9 | /// 10 | public class IncorrectPhaseTwoHashEntity : TableEntity 11 | { 12 | public string UsernameOrAccountId => TableKeyEncoding.Decode(RowKey); 13 | public string HashValue => TableKeyEncoding.Decode(PartitionKey); 14 | public DateTime LastSeenUtc { get; set; } 15 | 16 | public IncorrectPhaseTwoHashEntity() 17 | { 18 | } 19 | 20 | public IncorrectPhaseTwoHashEntity(string usernameOrAccountId, string hashValue, DateTime? lastSeenUtc = null) 21 | { 22 | PartitionKey = TableKeyEncoding.Encode(hashValue); 23 | RowKey = TableKeyEncoding.Encode(usernameOrAccountId); 24 | LastSeenUtc = lastSeenUtc ?? DateTime.UtcNow; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /StopGuessing/AccountStorage/Sql/SuccessfulLoginCookieEntitty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.WindowsAzure.Storage.Table; 3 | 4 | namespace StopGuessing.AccountStorage.Sql 5 | { 6 | 7 | public class SuccessfulLoginCookieEntity : TableEntity 8 | { 9 | public string UsernameOrAccountId => TableKeyEncoding.Decode(RowKey); 10 | public string HashedValue => TableKeyEncoding.Decode(PartitionKey); 11 | public DateTime LastSeenUtc { get; set; } 12 | 13 | public SuccessfulLoginCookieEntity() 14 | { 15 | } 16 | 17 | public SuccessfulLoginCookieEntity(string usernameOrAccountId, string hashOfCookie, DateTime? lastSeenUtc = null) 18 | { 19 | PartitionKey = TableKeyEncoding.Encode(hashOfCookie); 20 | RowKey = TableKeyEncoding.Encode(usernameOrAccountId); 21 | LastSeenUtc = lastSeenUtc ?? DateTime.UtcNow; 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /StopGuessing/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace StopGuessing.Controllers 5 | { 6 | [Route("api/[controller]")] 7 | public class ValuesController : Controller 8 | { 9 | // GET api/values 10 | [HttpGet] 11 | public IEnumerable Get() 12 | { 13 | return new string[] { "value1", "value2" }; 14 | } 15 | 16 | // GET api/values/5 17 | [HttpGet("{id}")] 18 | public string Get(int id) 19 | { 20 | return "value"; 21 | } 22 | 23 | // POST api/values 24 | [HttpPost] 25 | public void Post([FromBody]string value) 26 | { 27 | } 28 | 29 | // PUT api/values/5 30 | [HttpPut("{id}")] 31 | public void Put(int id, [FromBody]string value) 32 | { 33 | } 34 | 35 | // DELETE api/values/5 36 | [HttpDelete("{id}")] 37 | public void Delete(int id) 38 | { 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /StopGuessing/DataStructures/Cache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace StopGuessing.DataStructures 8 | { 9 | /// 10 | /// An interface for classes that have an async method to call to start 11 | /// freeing the resources they are using. This can be handy when a class 12 | /// needs to store data to a disk or over a network before it releases 13 | /// itself from memory. 14 | /// 15 | /// If implementing IAsyncDisposable and IDisposable, users should call _either_ 16 | /// Dispose or DisposeAsync, not both. This allows implementers to make the 17 | /// Dispose method simply call await DisposeAsync(); 18 | /// 19 | public interface IAsyncDisposable 20 | { 21 | Task DisposeAsync(); 22 | } 23 | 24 | 25 | //public interface IHasUniqueIdentityKeyString 26 | //{ 27 | // string UniqueIdentityKeyString { Get; } 28 | //} 29 | 30 | 31 | /// 32 | /// Implements a cache with a LRU (least-recently used)-like replacement policy. 33 | /// (Since the cache knows when objects were accessed from the cache -- when it wrote 34 | /// an item or retrieved it -- it treats these as uses. It does not actually 35 | /// know when an object is used after the access happens.) 36 | /// 37 | /// This class implements IDicionary via inheritance. 38 | /// 39 | /// The type of key used to access items in the cache. 40 | /// The value type of values stored in the cache. 41 | public class Cache : DictionaryThatTracksAccessRecency 42 | { 43 | public virtual TValue ConstructDefaultValueForMissingCacheEntry(TKey key) 44 | { 45 | return default(TValue); 46 | } 47 | 48 | 49 | /// 50 | /// Requests the removal of items from the cache to free up space, removing the 51 | /// least-recently accessed items first. 52 | /// 53 | /// The number of items to remove. 54 | public void RecoverSpace(int numberOfItemsToRemove) 55 | { 56 | // Remove the least-recently-accessed values from the cache 57 | KeyValuePair[] entriesToRemove = RemoveAndGetLeastRecentlyAccessed(numberOfItemsToRemove).ToArray(); 58 | 59 | ConcurrentBag asyncDisposalTasks = new ConcurrentBag(); 60 | 61 | // Call the disposal method on each class. 62 | Parallel.For(0, entriesToRemove.Length, (counter, loop) => 63 | { 64 | KeyValuePair entryToRemove = entriesToRemove[counter]; 65 | TValue valueToRemove = entryToRemove.Value; 66 | 67 | // ReSharper disable once CanBeReplacedWithTryCastAndCheckForNull 68 | if (valueToRemove is IAsyncDisposable) 69 | { 70 | asyncDisposalTasks.Add(((IAsyncDisposable)valueToRemove).DisposeAsync()); 71 | } 72 | else if (valueToRemove is IDisposable) 73 | { 74 | ((IDisposable)valueToRemove).Dispose(); 75 | } 76 | 77 | // Remove the last reference to the value so that it can be garbage collected immediately. 78 | entriesToRemove[counter] = default(KeyValuePair); 79 | }); 80 | 81 | Task.WaitAll(asyncDisposalTasks.ToArray()); 82 | } 83 | 84 | /// 85 | /// Requests the removal of items from the cache to free up space, removing the 86 | /// least-recently accessed items first. 87 | /// 88 | /// The fraction of items to remove. 89 | public void RecoverSpace(double fractionOfItemsToRemove) 90 | { 91 | // Calculate the number ot items to remove as a fraction of the total number of items in the cache 92 | int numberOfItemsToRemove = (int)(Count * fractionOfItemsToRemove); 93 | // Remove that many 94 | RecoverSpace(numberOfItemsToRemove); 95 | } 96 | } 97 | 98 | 99 | } -------------------------------------------------------------------------------- /StopGuessing/DataStructures/FixedSizeLRUCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StopGuessing.DataStructures 4 | { 5 | public class FixedSizeLruCache : DictionaryThatTracksAccessRecency 6 | { 7 | public int Capacity { get; } 8 | 9 | public FixedSizeLruCache(int capacity) 10 | { 11 | Capacity = capacity; 12 | } 13 | 14 | 15 | public override void Add(TKey key, TValue value) 16 | { 17 | lock (KeysOrderedFromMostToLeastRecentlyUsed) 18 | { 19 | // Remove any nodes with the same key 20 | LinkedListNode> nodeWithSameKey; 21 | if (KeyToLinkedListNode.TryGetValue(key, out nodeWithSameKey)) 22 | { 23 | KeysOrderedFromMostToLeastRecentlyUsed.Remove(nodeWithSameKey); 24 | KeyToLinkedListNode.Remove(key); 25 | } 26 | // Remove the oldest node 27 | if (KeyToLinkedListNode.Count == Capacity) 28 | { 29 | LinkedListNode> oldestNode = KeysOrderedFromMostToLeastRecentlyUsed.Last; 30 | KeysOrderedFromMostToLeastRecentlyUsed.Remove(oldestNode); 31 | KeyToLinkedListNode.Remove(oldestNode.Value.Key); 32 | } 33 | // Add 34 | // Creeate the node 35 | LinkedListNode> newNode = 36 | new LinkedListNode>( 37 | new KeyValuePair(key, value)); 38 | // Put it at the front of the recency-order linked list 39 | KeysOrderedFromMostToLeastRecentlyUsed.AddFirst(newNode); 40 | // Add it to the dictionary for fast lookup. 41 | KeyToLinkedListNode[key] = newNode; 42 | } 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /StopGuessing/DataStructures/Proportion.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace StopGuessing.DataStructures 5 | { 6 | /// 7 | /// A class representing a proportion, or fraction, of numerator over denominator. 8 | /// This comes in handy when one might care about the actual magnitude of the numerator/denominator, 9 | /// which is list in a representation that divides the numerator by the denominator. 10 | /// 11 | public struct Proportion 12 | { 13 | public ulong Numerator { get; } 14 | public ulong Denominator { get; } 15 | 16 | public double AsDouble { get; } 17 | 18 | 19 | public Proportion(ulong numerator, ulong denominator) 20 | { 21 | Numerator = numerator; 22 | Denominator = denominator; 23 | AsDouble = Denominator == 0 ? 0 : Numerator / ((double)Denominator); 24 | } 25 | 26 | 27 | /// 28 | /// Return this proportion modified to have a denominator at least as large as a value specified 29 | /// in the parameter. 30 | /// 31 | /// The denominator to use if the proportions denominator is less than this value. 32 | /// If the minDenominator is less than this proportion's Denominator, the return valuee will be this proportion. 33 | /// Otherwise, it will be a new proportion with the same Numerator but with the Denominator set to . 34 | public Proportion MinDenominator(ulong minDenominator) 35 | { 36 | return Denominator >= minDenominator ? this : new Proportion(Numerator, minDenominator); 37 | } 38 | 39 | public static Proportion GetLargest(IEnumerable proportions) 40 | { 41 | Proportion max = new Proportion(0,ulong.MaxValue); 42 | foreach (Proportion p in proportions) 43 | { 44 | if (p.AsDouble > max.AsDouble) 45 | max = p; 46 | } 47 | return max; 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /StopGuessing/DataStructures/SequenceWithFastLookup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StopGuessing.DataStructures 4 | { 5 | public class SequenceWithFastLookup : Sequence 6 | { 7 | protected HashSet FastLookupSet; 8 | 9 | public SequenceWithFastLookup(int capacity) : base(capacity) 10 | { 11 | FastLookupSet = new HashSet(); 12 | } 13 | 14 | public override void RemoveAt(int index) 15 | { 16 | lock (SequenceArray) 17 | { 18 | FastLookupSet.Remove(this[index]); 19 | base.RemoveAt(index); 20 | } 21 | } 22 | 23 | public override void Add(T value) 24 | { 25 | lock (SequenceArray) 26 | { 27 | FastLookupSet.Add(value); 28 | base.Add(value); 29 | } 30 | } 31 | 32 | public override bool Contains(T value) 33 | { 34 | lock (SequenceArray) 35 | { 36 | return FastLookupSet.Contains(value); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /StopGuessing/DataStructures/StaticUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace StopGuessing.DataStructures 6 | { 7 | internal class StaticUtilities 8 | { 9 | //// From: http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059 10 | //public static bool IsAssignableToGenericType(Type givenType, Type genericType) 11 | //{ 12 | // if (givenType == null || genericType == null) 13 | // return false; 14 | 15 | // // Check all interfaces (which will include any ancestor interfaces) to see if they match the genericType 16 | // if (givenType.GetInterfaces().Any(it => it.IsConstructedGenericType && it.GetGenericTypeDefinition() == genericType)) 17 | // return true; 18 | 19 | // // Walk up the ancestry chain to all types this type inherits from looking for the type match 20 | // for (; givenType != null; givenType = givenType.BaseType) 21 | // { 22 | // if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) 23 | // return true; 24 | // } 25 | 26 | // return false; 27 | //} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /StopGuessing/EncryptionPrimitives/EncryptedValueField.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using Newtonsoft.Json; 6 | 7 | namespace StopGuessing.EncryptionPrimitives 8 | { 9 | 10 | 11 | public class EncryptedByteField 12 | { 13 | [DataMember] 14 | public string Ciphertext { get; set; } 15 | 16 | /// 17 | /// 18 | /// The encryption format is a JSON-encoded EcEncryptedMessageAesCbcHmacSha256. 19 | /// 20 | /// The value to store encrypted. 21 | /// The public key used to encrypt the value. 22 | public void Write(byte[] value, byte[] publicKeyAsByteArray) 23 | { 24 | using (Encryption.IPublicKey publicKey = Encryption.GetPublicKeyFromByteArray(publicKeyAsByteArray)) 25 | { 26 | Write(value, publicKey); 27 | } 28 | } 29 | 30 | public void Write(byte[] value, Encryption.IPublicKey publicKey) 31 | { 32 | Ciphertext = JsonConvert.SerializeObject( 33 | new EcEncryptedMessageAesCbcHmacSha256(value, publicKey)); 34 | } 35 | 36 | /// 37 | /// 38 | /// The private key that can be used to decrypt the value. 39 | /// The decrypted value. 40 | public byte[] Read(Encryption.IPrivateKey privateKey) 41 | { 42 | if (string.IsNullOrEmpty(Ciphertext)) 43 | throw new MemberAccessException("Cannot decrypt a value that has not been written."); 44 | 45 | EcEncryptedMessageAesCbcHmacSha256 messageDeserializedFromJson = 46 | JsonConvert.DeserializeObject(Ciphertext); 47 | return messageDeserializedFromJson.Decrypt(privateKey); 48 | } 49 | 50 | public bool HasValue => !string.IsNullOrEmpty(Ciphertext); 51 | } 52 | 53 | public class EncryptedStringField : EncryptedByteField 54 | { 55 | public void Write(string value, byte[] ecPublicKeyAsByteArray) => 56 | Write(Encoding.UTF8.GetBytes(value), ecPublicKeyAsByteArray); 57 | 58 | public void Write(string value, Encryption.IPublicKey publicKey) => 59 | Write(Encoding.UTF8.GetBytes(value), publicKey); 60 | 61 | public new string Read(Encryption.IPrivateKey privateKey) => Encoding.UTF8.GetString(base.Read(privateKey)); 62 | } 63 | 64 | public class EncryptedValueField : EncryptedStringField 65 | { 66 | public void Write(T value, byte[] ecPublicKeyAsByteArray) => 67 | Write(JsonConvert.SerializeObject(value), ecPublicKeyAsByteArray); 68 | 69 | public void Write(T value, Encryption.IPublicKey publicKey) => 70 | Write(JsonConvert.SerializeObject(value), publicKey); 71 | 72 | public new T Read(Encryption.IPrivateKey privateKey) => JsonConvert.DeserializeObject(base.Read(privateKey)); 73 | } 74 | 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /StopGuessing/EncryptionPrimitives/ExpensiveHashFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StopGuessing.EncryptionPrimitives 4 | { 5 | 6 | public delegate byte[] ExpensiveHashFunction(string password, byte[] saltBytes, int iterations); 7 | 8 | public static class ExpensiveHashFunctionFactory 9 | { 10 | 11 | public const string DefaultFunctionName = "Rfc2898"; 12 | public const int DefaultNumberOfIterations = 10000; 13 | 14 | private static readonly Dictionary ExpensiveHashFunctions = new Dictionary 15 | { 16 | {DefaultFunctionName, (password, salt, iterations) => 17 | new System.Security.Cryptography.Rfc2898DeriveBytes(password, salt, iterations).GetBytes(16)} 18 | }; 19 | 20 | 21 | public static ExpensiveHashFunction Get(string hashFunctionName) 22 | { 23 | return ExpensiveHashFunctions[hashFunctionName]; 24 | } 25 | 26 | public static void Add(string name, ExpensiveHashFunction function) 27 | { 28 | ExpensiveHashFunctions[name] = function; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /StopGuessing/EncryptionPrimitives/ManagedSHA256.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Threading.Tasks; 6 | 7 | namespace StopGuessing.EncryptionPrimitives 8 | { 9 | public static class ManagedSHA256 10 | { 11 | [ThreadStatic] static SHA256 Sha256; 12 | 13 | 14 | public static byte[] Hash(byte[] buffer) 15 | { 16 | if (Sha256 == null) 17 | { 18 | Sha256 = SHA256.Create(); 19 | } 20 | //using (SHA256 hash = SHA256.Create()) 21 | //{ 22 | return Sha256.ComputeHash(buffer); 23 | //} 24 | 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /StopGuessing/EncryptionPrimitives/StrongRandomNumberGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace StopGuessing.EncryptionPrimitives 5 | { 6 | /// 7 | /// A utility interface to .NET's cryptographically-strong random number generator 8 | /// (the interface we which .NET provided) 9 | /// 10 | public static class StrongRandomNumberGenerator 11 | { 12 | // Pre-allocate a thread-safe random number generator 13 | private static readonly System.Security.Cryptography.RandomNumberGenerator LocalRandomNumberGenerator = System.Security.Cryptography.RandomNumberGenerator.Create(); 14 | public static void GetBytes(byte[] bytes) 15 | { 16 | LocalRandomNumberGenerator.GetBytes(bytes); 17 | } 18 | 19 | public static byte[] GetBytes(int count) 20 | { 21 | byte[] bytes = new byte[count]; 22 | LocalRandomNumberGenerator.GetBytes(bytes); 23 | return bytes; 24 | } 25 | 26 | public static ulong Get64Bits(ulong? mod = null) 27 | { 28 | byte[] randBytes = new byte[8]; 29 | GetBytes(randBytes); 30 | 31 | ulong result = BitConverter.ToUInt64(randBytes, 0); 32 | if (mod.HasValue) 33 | result = result % mod.Value; 34 | return result; 35 | } 36 | 37 | public static ulong Get64Bits(long mod) 38 | { 39 | return Get64Bits((ulong)mod); 40 | } 41 | 42 | public static uint Get32Bits(uint? mod = null) 43 | { 44 | // We'll need the randomness to determine which bit to set and which to clear 45 | byte[] randBytes = new byte[4]; 46 | GetBytes(randBytes); 47 | 48 | uint result = BitConverter.ToUInt32(randBytes, 0); 49 | if (mod.HasValue) 50 | result = result % mod.Value; 51 | return result; 52 | } 53 | 54 | public static uint Get32Bits(int mod) 55 | { 56 | return Get32Bits((uint)mod); 57 | } 58 | 59 | public static double GetFraction() 60 | { 61 | return (double)Get64Bits() / (double)ulong.MaxValue; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /StopGuessing/Interfaces/IBinomialLadderFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace StopGuessing.Interfaces 8 | { 9 | public interface IBinomialLadderFilter 10 | { 11 | Task StepAsync(string key, int? heightOfLadderInRungs = null, TimeSpan? timeout = null, 12 | CancellationToken cancellationToken = new CancellationToken()); 13 | 14 | Task GetHeightAsync(string element, int? heightOfLadderInRungs = null, TimeSpan? timeout = null, 15 | CancellationToken cancellationToken = new CancellationToken()); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /StopGuessing/Interfaces/IDistributedResponsibilitySet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StopGuessing.Interfaces 4 | { 5 | 6 | /// 7 | /// Keep track of a set of members such that each will be responsible for some fraction 8 | /// of keys. 9 | /// 10 | /// Generalizes the concept of Highest Random Weight (HRW, a.k.a. Rendezvous) hashing and 11 | /// consistent hashing (rings) so that one can utilize whichever is more appropriate for evenness 12 | /// of distribution and efficiency for small sets (where HRW is superior) or scalability for 13 | /// large sets (where consistent hashing superior). 14 | /// Consistent hashing becomes more efficient somewhere between 20 members (for a low-optimized .NET 15 | /// implementation of HRW) and 500 members (for an HRW implementation that takes advantage of processor 16 | /// parallelism to perform lots of fast universal hashes.) 17 | /// 18 | /// 19 | public interface IDistributedResponsibilitySet 20 | { 21 | int Count { get; } 22 | 23 | bool ContainsKey(string key); 24 | 25 | void Add(string uniqueKeyIdentifiyingMember, TMember member); 26 | 27 | void AddRange(IEnumerable> newKeyMemberPairs); 28 | 29 | void Remove(string uniqueKeyIdentifiyingMember); 30 | 31 | void RemoveRange(IEnumerable uniqueKeysIdentifiyingMember); 32 | 33 | TMember FindMemberResponsible(string key); 34 | 35 | List FindMembersResponsible(string key, int numberOfUniqueMembersToFind); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /StopGuessing/Interfaces/IFactory.cs: -------------------------------------------------------------------------------- 1 | namespace StopGuessing.Interfaces 2 | { 3 | 4 | public interface IFactory 5 | { 6 | T Create(); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /StopGuessing/Interfaces/IStableStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using StopGuessing.AccountStorage.Memory; 5 | 6 | namespace StopGuessing.Interfaces 7 | { 8 | 9 | 10 | //public interface IUserAccountContextFactory : IStableStoreFactory 11 | //{ } 12 | 13 | public class MemoryOnlyUserAccountRepository : IRepository // IStableStoreContext 14 | { 15 | private ConcurrentDictionary _store; 16 | 17 | public MemoryOnlyUserAccountRepository(ConcurrentDictionary store) 18 | { 19 | _store = store; 20 | } 21 | 22 | #pragma warning disable 1998 23 | public async Task LoadAsync(string usernameOrAccountId, CancellationToken cancellationToken = default(CancellationToken)) 24 | #pragma warning restore 1998 25 | { 26 | cancellationToken.ThrowIfCancellationRequested(); 27 | MemoryUserAccount result; 28 | _store.TryGetValue(usernameOrAccountId, out result); 29 | return result; 30 | } 31 | 32 | #pragma warning disable 1998 33 | public async Task AddAsync(MemoryUserAccount itemToAdd, CancellationToken cancellationToken = default(CancellationToken)) 34 | #pragma warning restore 1998 35 | { 36 | _store.TryAdd(itemToAdd.UsernameOrAccountId, itemToAdd); 37 | } 38 | 39 | #pragma warning disable 1998 40 | public async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) 41 | #pragma warning disable 1998 42 | { 43 | cancellationToken.ThrowIfCancellationRequested(); 44 | return; 45 | } 46 | 47 | public void Dispose() 48 | { 49 | 50 | } 51 | 52 | } 53 | 54 | public class MemoryOnlyUserAccountFactory : IUserAccountRepositoryFactory 55 | { 56 | private readonly ConcurrentDictionary _store = new ConcurrentDictionary(); 57 | 58 | public IRepository Create() 59 | { 60 | return new MemoryOnlyUserAccountRepository(_store); 61 | } 62 | 63 | public void Add(MemoryUserAccount account) 64 | { 65 | _store[account.UsernameOrAccountId] = account; 66 | } 67 | 68 | public void Dispose() 69 | { 70 | } 71 | 72 | } 73 | 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /StopGuessing/Interfaces/IUserAccount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StopGuessing.Interfaces 4 | { 5 | /// 6 | /// This interface specifies the records that must be implemented by a UserAccount record. 7 | /// 8 | public interface IUserAccount 9 | { 10 | /// 11 | /// A string that uniquely identifies the account. 12 | /// 13 | string UsernameOrAccountId { get; } 14 | 15 | /// 16 | /// The salt is a random unique sequence of bytes that is included when the password is hashed (phase 1 of hashing) 17 | /// to ensure that attackers who might obtain the set of account hashes cannot hash a password once and then compare 18 | /// the hash against every account. 19 | /// 20 | byte[] SaltUniqueToThisAccount { get; } 21 | 22 | /// 23 | /// The name of the (hopefully) expensive hash function used for the first phase of password hashing. 24 | /// 25 | string PasswordHashPhase1FunctionName { get; set; } 26 | 27 | /// 28 | /// The number of iterations to use for the phase 1 hash to make it more expensive. 29 | /// 30 | int NumberOfIterationsToUseForPhase1Hash { get; set; } 31 | 32 | /// 33 | /// An EC public encryption symmetricKey used to store log about password failures, which can can only be decrypted when the user 34 | /// enters her correct password, the expensive (phase1) hash of which is used to symmetrically encrypt the matching EC private symmetricKey. 35 | /// 36 | byte[] EcPublicAccountLogKey { get; set; } 37 | 38 | /// 39 | /// The EC private symmetricKey encrypted with phase 1 (expensive) hash of the password 40 | /// 41 | byte[] EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1 { get; set; } 42 | 43 | /// 44 | /// The Phase2 password hash is the result of hasing the password (and salt) first with the expensive hash function to create a Phase1 hash, 45 | /// then hasing that Phase1 hash (this time without the salt) using SHA256 so as to make it unnecessary to store the 46 | /// phase1 hash in this record. Doing so allows the Phase1 hash to be used as a symmetric encryption symmetricKey for the log. 47 | /// 48 | string PasswordHashPhase2 { get; set; } 49 | 50 | /// 51 | /// The account's credit limit for offsetting penalties for IP addresses from which 52 | /// the account has logged in successfully. 53 | /// 54 | double CreditLimit { get; set; } 55 | 56 | /// 57 | /// The half life with which used credits are removed from the system freeing up new credit 58 | /// 59 | TimeSpan CreditHalfLife { get; set; } 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /StopGuessing/Interfaces/IUserAccountFactory.cs: -------------------------------------------------------------------------------- 1 | //#define Simulation 2 | // FIXME remove 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace StopGuessing.Interfaces 9 | { 10 | public interface IRepository : IDisposable 11 | { 12 | Task LoadAsync(TKey key, CancellationToken cancellationToken = default(CancellationToken)); 13 | Task AddAsync(T itemToAdd, CancellationToken cancellationToken = default(CancellationToken)); 14 | Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); 15 | 16 | } 17 | 18 | public interface IUserAccountRepositoryFactory : IFactory> where TUserAccount : IUserAccount 19 | { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /StopGuessing/Migrations/20160429174517_DbContextFirstMigration.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata; 4 | using Microsoft.EntityFrameworkCore.Migrations; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using StopGuessing.AccountStorage.Sql; 7 | 8 | namespace StopGuessing.Migrations 9 | { 10 | [DbContext(typeof(DbUserAccountContext))] 11 | [Migration("20160429174517_DbContextFirstMigration")] 12 | partial class DbContextFirstMigration 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "7.0.0-rc1-16348") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("StopGuessing.Azure.DbUserAccount", b => 21 | { 22 | b.Property("DbUserAccountId"); 23 | 24 | b.Property("CreditHalfLife"); 25 | 26 | b.Property("CreditLimit"); 27 | 28 | b.Property("EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1"); 29 | 30 | b.Property("EcPublicAccountLogKey"); 31 | 32 | b.Property("NumberOfIterationsToUseForPhase1Hash"); 33 | 34 | b.Property("PasswordHashPhase1FunctionName"); 35 | 36 | b.Property("PasswordHashPhase2"); 37 | 38 | b.Property("SaltUniqueToThisAccount"); 39 | 40 | b.HasKey("DbUserAccountId"); 41 | 42 | b.HasIndex("DbUserAccountId") 43 | .IsUnique(); 44 | }); 45 | 46 | modelBuilder.Entity("StopGuessing.Azure.DbUserAccountCreditBalance", b => 47 | { 48 | b.Property("DbUserAccountId"); 49 | 50 | b.Property("ConsumedCreditsLastUpdatedUtc") 51 | .IsConcurrencyToken(); 52 | 53 | b.Property("ConsumedCreditsLastValue") 54 | .IsConcurrencyToken(); 55 | 56 | b.HasKey("DbUserAccountId"); 57 | 58 | b.HasIndex("DbUserAccountId") 59 | .IsUnique(); 60 | }); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /StopGuessing/Migrations/20160429174517_DbContextFirstMigration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace StopGuessing.Migrations 5 | { 6 | public partial class DbContextFirstMigration : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "DbUserAccount", 12 | columns: table => new 13 | { 14 | DbUserAccountId = table.Column(nullable: false), 15 | CreditHalfLife = table.Column(nullable: false), 16 | CreditLimit = table.Column(nullable: false), 17 | EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1 = table.Column(nullable: true), 18 | EcPublicAccountLogKey = table.Column(nullable: true), 19 | NumberOfIterationsToUseForPhase1Hash = table.Column(nullable: false), 20 | PasswordHashPhase1FunctionName = table.Column(nullable: true), 21 | PasswordHashPhase2 = table.Column(nullable: true), 22 | SaltUniqueToThisAccount = table.Column(nullable: true) 23 | }, 24 | constraints: table => 25 | { 26 | table.PrimaryKey("PK_DbUserAccount", x => x.DbUserAccountId); 27 | }); 28 | migrationBuilder.CreateTable( 29 | name: "DbUserAccountCreditBalance", 30 | columns: table => new 31 | { 32 | DbUserAccountId = table.Column(nullable: false), 33 | ConsumedCreditsLastUpdatedUtc = table.Column(nullable: true), 34 | ConsumedCreditsLastValue = table.Column(nullable: false) 35 | }, 36 | constraints: table => 37 | { 38 | table.PrimaryKey("PK_DbUserAccountCreditBalance", x => x.DbUserAccountId); 39 | }); 40 | migrationBuilder.CreateIndex( 41 | name: "IX_DbUserAccount_DbUserAccountId", 42 | table: "DbUserAccount", 43 | column: "DbUserAccountId", 44 | unique: true); 45 | migrationBuilder.CreateIndex( 46 | name: "IX_DbUserAccountCreditBalance_DbUserAccountId", 47 | table: "DbUserAccountCreditBalance", 48 | column: "DbUserAccountId", 49 | unique: true); 50 | } 51 | 52 | protected override void Down(MigrationBuilder migrationBuilder) 53 | { 54 | migrationBuilder.DropTable("DbUserAccount"); 55 | migrationBuilder.DropTable("DbUserAccountCreditBalance"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /StopGuessing/Migrations/DbUserAccountContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using StopGuessing.AccountStorage.Sql; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | 7 | namespace StopGuessing.Migrations 8 | { 9 | [DbContext(typeof(DbUserAccountContext))] 10 | partial class DbUserAccountContextModelSnapshot : ModelSnapshot 11 | { 12 | protected override void BuildModel(ModelBuilder modelBuilder) 13 | { 14 | modelBuilder 15 | .HasAnnotation("ProductVersion", "7.0.0-rc1-16348") 16 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 17 | 18 | modelBuilder.Entity("StopGuessing.Azure.DbUserAccount", b => 19 | { 20 | b.Property("DbUserAccountId"); 21 | 22 | b.Property("CreditHalfLife"); 23 | 24 | b.Property("CreditLimit"); 25 | 26 | b.Property("EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1"); 27 | 28 | b.Property("EcPublicAccountLogKey"); 29 | 30 | b.Property("NumberOfIterationsToUseForPhase1Hash"); 31 | 32 | b.Property("PasswordHashPhase1FunctionName"); 33 | 34 | b.Property("PasswordHashPhase2"); 35 | 36 | b.Property("SaltUniqueToThisAccount"); 37 | 38 | b.HasKey("DbUserAccountId"); 39 | 40 | b.HasIndex("DbUserAccountId") 41 | .IsUnique(); 42 | }); 43 | 44 | modelBuilder.Entity("StopGuessing.Azure.DbUserAccountCreditBalance", b => 45 | { 46 | b.Property("DbUserAccountId"); 47 | 48 | b.Property("ConsumedCreditsLastUpdatedUtc") 49 | .IsConcurrencyToken(); 50 | 51 | b.Property("ConsumedCreditsLastValue") 52 | .IsConcurrencyToken(); 53 | 54 | b.HasKey("DbUserAccountId"); 55 | 56 | b.HasIndex("DbUserAccountId") 57 | .IsUnique(); 58 | }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /StopGuessing/Models/AuthenticationOutcome.cs: -------------------------------------------------------------------------------- 1 | namespace StopGuessing.Models 2 | { 3 | /// 4 | /// Possible outcomes of an authentication attempt. The only information ever 5 | /// revealed to an untrusted client is whether the outcome is CredentialsValid, 6 | /// in which case the client should be logged in, or some other value, in which case 7 | /// the client should only be told the credentials were invalid (but not given any more 8 | /// specific information). 9 | /// 10 | public enum AuthenticationOutcome { 11 | /// 12 | /// Default value before the authentication has occurred. 13 | /// 14 | Undetermined = 0, 15 | /// 16 | /// The credentials were valid and the user should be allowed to login. 17 | /// 18 | CredentialsValid = 1, 19 | /// 20 | /// The credentials were valid but the client has been guessing so much that 21 | /// the authentication system should act as if the credentials were invalid. 22 | /// 23 | CredentialsValidButBlocked = -1, 24 | /// 25 | /// The username or account ID provided does not map to a valid account. 26 | /// 27 | CredentialsInvalidNoSuchAccount = -2, 28 | /// 29 | /// The username or account ID provided does not map to a valid account, 30 | /// and we've seen this same mistake made recently. 31 | /// 32 | CredentialsInvalidRepeatedNoSuchAccount = -3, 33 | /// 34 | /// The password provided is not the correct password for this account, 35 | /// and we've seen the same account name/password pair fail recently. 36 | /// 37 | CredentialsInvalidRepeatedIncorrectPassword = -4, 38 | /// 39 | /// The password provided is not the correct password for this account, 40 | /// 41 | CredentialsInvalidIncorrectPassword = -5, 42 | /// 43 | /// The password provided is not the correct password for this account, 44 | /// and its not even close (by measure of edit distance). 45 | /// 46 | CredentialsInvalidIncorrectPasswordTypoUnlikely = -6, 47 | /// 48 | /// The password provided is not the correct password for this account, 49 | /// but it is close enough (by measure of edit distance) that it was 50 | /// more likely a typo than a guess. 51 | /// 52 | CredentialsInvalidIncorrectPasswordTypoLikely = -7 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /StopGuessing/Models/IpHistory.cs: -------------------------------------------------------------------------------- 1 | //#define Simulation 2 | // FIXME 3 | using System; 4 | using System.Net; 5 | using StopGuessing.DataStructures; 6 | 7 | namespace StopGuessing.Models 8 | { 9 | /// 10 | /// This class keeps track of recent login successes and failures for a given client IP so that 11 | /// we can try to determine if this client should be blocked due to likely-password-guessing 12 | /// behaviors. 13 | /// 14 | public class IpHistory 15 | { 16 | /// 17 | /// The IP address being tracked. 18 | /// 19 | public readonly IPAddress Address; 20 | 21 | /// 22 | /// A set of recent login attempts that have failed due to incorrect passwords that are kept around so that, 23 | /// when the correct password is provided, we can see if those passwords were typos and adjust the block 24 | /// score to reduce past penalities given to failed logins that were typos. 25 | /// 26 | /// This is implemented as a capacity constrained set, similar to a cache, where newer values push out old values. 27 | /// 28 | public SmallCapacityConstrainedSet RecentPotentialTypos; 29 | 30 | /// 31 | /// The current block score for this IP, in the form of a number that decays with time. 32 | /// 33 | public DecayingDouble CurrentBlockScore; 34 | 35 | public IpHistory( 36 | IPAddress address, 37 | BlockingAlgorithmOptions options) 38 | { 39 | Address = address; 40 | CurrentBlockScore = new DecayingDouble(); 41 | RecentPotentialTypos = 42 | new SmallCapacityConstrainedSet(options.NumberOfFailuresToTrackForGoingBackInTimeToIdentifyTypos); 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /StopGuessing/Models/LoginAttemptSummaryForTypoAnalysis.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Threading.Tasks; 6 | using StopGuessing.DataStructures; 7 | using StopGuessing.EncryptionPrimitives; 8 | 9 | namespace StopGuessing.Models 10 | { 11 | /// 12 | /// A record of a recent failed login attempt with just enough information to determine whether 13 | /// that attempt failed due to an incorrectly typed password. 14 | /// 15 | public struct LoginAttemptSummaryForTypoAnalysis 16 | { 17 | /// 18 | /// The unique identifier of the account that was being logged into. 19 | /// 20 | public string UsernameOrAccountId { get; set; } 21 | 22 | /// 23 | /// The penalty applied to the blockign score when this login attempt registered as having 24 | /// an invalid password 25 | /// 26 | public DecayingDouble Penalty { get; set; } 27 | 28 | /// 29 | /// When a login attempt is sent with an incorrect password, that incorrect password is encrypted 30 | /// with the UserAccount's EcPublicAccountLogKey. That private key to decrypt is encrypted 31 | /// wiith the phase1 hash of the user's correct password. If the correct password is provided in the future, 32 | /// we can go back and audit the incorrect password to see if it was within a short edit distance 33 | /// of the correct password--which would indicate it was likely a (benign) typo and not a random guess. 34 | /// 35 | public EncryptedStringField EncryptedIncorrectPassword { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /StopGuessing/Models/RemoteHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StopGuessing.Models 4 | { 5 | /// 6 | /// This class identifies remote hosts to be used as part of a distributed system for 7 | /// balancing the load of login requests (and storing data associated with past requests) 8 | /// across systems. 9 | /// 10 | public class RemoteHost 11 | { 12 | public Uri Uri { get; set; } 13 | 14 | // FIXME -- remove this and find another way to test 15 | // public bool IsLocalHost { get; set; } 16 | 17 | public override string ToString() 18 | { 19 | return Uri.ToString(); 20 | } 21 | } 22 | 23 | public class TestRemoveHost : RemoteHost 24 | { 25 | public string KeyPrefix { get; set; } 26 | 27 | public new string ToString() 28 | { 29 | return KeyPrefix + base.ToString(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /StopGuessing/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Builder; 8 | using StopGuessing; 9 | 10 | namespace StopGuessing 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | var host = new WebHostBuilder() 17 | .UseKestrel() 18 | .UseContentRoot(Directory.GetCurrentDirectory()) 19 | //.UseIISIntegration() 20 | .UseStartup() 21 | .Build(); 22 | 23 | host.Run(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /StopGuessing/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:3265/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "api/values", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "StopGuessing_Core": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "http://localhost:5000/api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /StopGuessing/StopGuessing.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 39c5479b-744f-4f2b-ad22-3bb4bbae3b3a 10 | StopGuessing 11 | .\obj 12 | .\bin\ 13 | v4.6 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /StopGuessing/Utilities/ReaderWriterLockSlimExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace StopGuessing.Utilities 5 | { 6 | // Based on http://stackoverflow.com/questions/170028/how-would-you-simplify-entering-and-exiting-a-readerwriterlock/170040#170040 7 | public static class ReaderWriterLockSlimExtension 8 | { 9 | 10 | private sealed class ReadLockToken : IDisposable 11 | { 12 | private ReaderWriterLockSlim _lock; 13 | 14 | public ReadLockToken(ReaderWriterLockSlim @lock) 15 | { 16 | this._lock = @lock; 17 | @lock.EnterReadLock(); 18 | } 19 | 20 | public void Dispose() 21 | { 22 | if (_lock != null) 23 | { 24 | _lock.ExitReadLock(); 25 | _lock = null; 26 | } 27 | } 28 | } 29 | 30 | private sealed class WriteLockTocken : IDisposable 31 | { 32 | private ReaderWriterLockSlim _lock; 33 | 34 | public WriteLockTocken(ReaderWriterLockSlim @lock) 35 | { 36 | this._lock = @lock; 37 | @lock.EnterWriteLock(); 38 | } 39 | 40 | public void Dispose() 41 | { 42 | if (_lock != null) 43 | { 44 | _lock.EnterWriteLock(); 45 | _lock = null; 46 | } 47 | } 48 | } 49 | 50 | public static IDisposable Read(this ReaderWriterLockSlim obj) 51 | { 52 | return new ReadLockToken(obj); 53 | } 54 | public static IDisposable Write(this ReaderWriterLockSlim obj) 55 | { 56 | return new WriteLockTocken(obj); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /StopGuessing/Utilities/TaskHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace StopGuessing.Utilities 7 | { 8 | public static class TaskHelper 9 | { 10 | public static void RunInBackground(Task task) { } 11 | public static void RunInBackground(Task task) { } 12 | } 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /StopGuessing/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | }, 10 | "data": { 11 | "ConnectionString": "Server=tcp:stopguessingserver.database.windows.net,1433;Database=stopguessing_db;User ID=stopguessing@stopguessingserver;Password=1VatIsDisStope?;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;", 12 | "StorageConnectionString": "DefaultEndpointsProtocol=https;AccountName=stopguessingstorage;AccountKey=ibvA6IeSDIwivra8eFGHEgZVrDweizOG76/TshyFVp0Y0xLjGH/i5xY8+rz0TdltBFpu4FjjlNIfDU7hGRivzw==", 13 | "UniqueConfigurationSecretPhrase": "TakeThatAlgorithmicComplexityAttacks!" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /StopGuessing/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0-rc2-3002702", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final", 8 | "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final", 9 | "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final", 10 | "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final", 11 | "Microsoft.Extensions.Logging": "1.0.0-rc2-final", 12 | "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final", 13 | "Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final", 14 | "Microsoft.EntityFrameworkCore": "1.0.0-rc2-final", 15 | "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0-rc2-final", 16 | "Microsoft.EntityFrameworkCore.Relational": "1.0.0-rc2-final", 17 | "Microsoft.EntityFrameworkCore.InMemory": "1.0.0-rc2-final", 18 | "WindowsAzure.Storage": "7.0.2-preview", 19 | "Microsoft.AspNetCore.Mvc.Core": "1.0.0-rc2-final", 20 | "Microsoft.AspNetCore.Mvc.Abstractions": "1.0.0-rc2-final", 21 | "Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final" 22 | }, 23 | 24 | "tools": { 25 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": { 26 | "version": "1.0.0-preview1-final", 27 | "imports": "portable-net45+win8+dnxcore50" 28 | }, 29 | "Microsoft.EntityFrameworkCore.Tools": { 30 | "version": "1.0.0-preview1-final", 31 | "imports": [ 32 | "portable-net45+win8+dnxcore50", 33 | "portable-net45+win8" 34 | ] 35 | } 36 | }, 37 | 38 | "frameworks": { 39 | "netcoreapp1.0": { 40 | "imports": [ 41 | "dotnet5.6", 42 | "dnxcore50", 43 | "portable-net451+win8" 44 | ] 45 | } 46 | }, 47 | 48 | "buildOptions": { 49 | "emitEntryPoint": true, 50 | "preserveCompilationContext": true 51 | }, 52 | 53 | "runtimeOptions": { 54 | "gcServer": true 55 | }, 56 | 57 | "publishOptions": { 58 | "include": [ 59 | "wwwroot", 60 | "Views", 61 | "appsettings.json", 62 | "web.config" 63 | ] 64 | }, 65 | 66 | "scripts": { 67 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /StopGuessing/web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /StopGuessingOld/AccountStorage/Memory/MemoryOnlyStableStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using StopGuessing.Models; 6 | 7 | namespace StopGuessing 8 | { 9 | //public class MemoryOnlyStableStore : IStableStore 10 | //{ 11 | // public ConcurrentDictionary Accounts = new ConcurrentDictionary(); 12 | // public ConcurrentDictionary LoginAttempts = new ConcurrentDictionary(); 13 | 14 | 15 | // public async Task IsIpAddressAlwaysPermittedAsync(System.Net.IPAddress clientIpAddress, CancellationToken cancelToken = default(CancellationToken)) 16 | // { 17 | // return await Task.RunInBackground( () => false, cancelToken); 18 | // } 19 | 20 | // public async Task ReadAccountAsync(string usernameOrAccountId, CancellationToken cancelToken) 21 | // { 22 | // if (Accounts == null) 23 | // return null; 24 | // return await Task.RunInBackground( () => 25 | // { 26 | // UserAccount account; 27 | // Accounts.TryGetValue(usernameOrAccountId, out account); 28 | // return account; 29 | // }, cancelToken); 30 | // } 31 | 32 | // public async Task ReadLoginAttemptAsync(string key, CancellationToken cancelToken) 33 | // { 34 | // if (LoginAttempts == null) 35 | // return null; 36 | // return await Task.RunInBackground(() => 37 | // { 38 | // LoginAttempt attempt; 39 | // LoginAttempts.TryGetValue(key, out attempt); 40 | // return attempt; 41 | // }, cancelToken); 42 | // } 43 | 44 | // public async Task> ReadMostRecentLoginAttemptsAsync(System.Net.IPAddress clientIpAddress, int numberToRead, 45 | // bool includeSuccesses = true, bool includeFailures = true, CancellationToken cancelToken = default(CancellationToken)) 46 | // { 47 | // // fail on purpose 48 | // return await Task.RunInBackground(() => new List(), cancelToken); 49 | // } 50 | 51 | // public async Task> ReadMostRecentLoginAttemptsAsync(string usernameOrAccountId, int numberToRead, 52 | // bool includeSuccesses = true, bool includeFailures = true, CancellationToken cancelToken = default(CancellationToken)) 53 | // { 54 | // // fail on purpose 55 | // return await Task.RunInBackground(() => new List(), cancelToken); 56 | // } 57 | 58 | // public async Task WriteAccountAsync(UserAccount account, CancellationToken cancelToken) 59 | // { 60 | // if (Accounts == null) 61 | // return; 62 | 63 | // // REMOVE FOR PRODUCTION 64 | // // For testing the mipact of Task.RunInBackground() on performance 65 | // //if (true) 66 | // //{ 67 | // // Accounts[account.UsernameOrAccountId] = account; 68 | // // return; 69 | // //} 70 | 71 | // await Task.RunInBackground(() => 72 | // { 73 | // Accounts[account.UsernameOrAccountId] = account; 74 | // }, cancelToken); 75 | // } 76 | 77 | // public async Task WriteLoginAttemptAsync(LoginAttempt attempt, CancellationToken cancelToken) 78 | // { 79 | // if (LoginAttempts == null) 80 | // return; 81 | // await Task.RunInBackground(() => 82 | // { 83 | // LoginAttempts[attempt.UniqueKey] = attempt; 84 | // }, cancelToken); 85 | // } 86 | //} 87 | } 88 | -------------------------------------------------------------------------------- /StopGuessingOld/AccountStorage/Memory/MemoryUserAccountController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using StopGuessing.Controllers; 5 | using StopGuessing.DataStructures; 6 | using StopGuessing.Utilities; 7 | 8 | namespace StopGuessing.AccountStorage.Memory 9 | { 10 | public class MemoryUserAccountControllerFactory : IUserAccountControllerFactory 11 | { 12 | 13 | public IUserAccountController Create() 14 | { 15 | return new MemoryUserAccountController(); 16 | } 17 | } 18 | 19 | /// 20 | /// An in-memory implementation of a user-account store that can be used for testing purposes only. 21 | /// 22 | public class MemoryUserAccountController : UserAccountController 23 | { 24 | public MemoryUserAccountController() 25 | { 26 | } 27 | 28 | public MemoryUserAccount Create( 29 | string usernameOrAccountId, 30 | string password = null, 31 | int numberOfIterationsToUseForHash = 0, 32 | string passwordHashFunctionName = null, 33 | int? maxNumberOfCookiesToTrack = null, 34 | int? maxFailedPhase2HashesToTrack = null, 35 | DateTime? currentDateTimeUtc = null) 36 | { 37 | MemoryUserAccount account = new MemoryUserAccount {UsernameOrAccountId = usernameOrAccountId}; 38 | 39 | Initialize(account, password, numberOfIterationsToUseForHash, passwordHashFunctionName); 40 | 41 | account.HashesOfCookiesOfClientsThatHaveSuccessfullyLoggedIntoThisAccount = 42 | new SmallCapacityConstrainedSet(maxNumberOfCookiesToTrack ?? DefaultMaxNumberOfCookiesToTrack); 43 | account.RecentIncorrectPhase2Hashes = new SmallCapacityConstrainedSet(maxFailedPhase2HashesToTrack ?? DefaultMaxFailedPhase2HashesToTrack); 44 | account.ConsumedCredits = new DecayingDouble(0, currentDateTimeUtc); 45 | 46 | return account; 47 | } 48 | 49 | public override Task AddIncorrectPhaseTwoHashAsync(MemoryUserAccount userAccount, string phase2Hash, DateTime? whenSeenUtc = null, 50 | CancellationToken cancellationToken = default(CancellationToken)) => 51 | TaskHelper.PretendToBeAsync(userAccount.RecentIncorrectPhase2Hashes.Add(phase2Hash)); 52 | 53 | public override Task HasClientWithThisHashedCookieSuccessfullyLoggedInBeforeAsync( 54 | MemoryUserAccount userAccount, 55 | string hashOfCookie, 56 | CancellationToken cancellationToken = default(CancellationToken)) => 57 | TaskHelper.PretendToBeAsync(userAccount.HashesOfCookiesOfClientsThatHaveSuccessfullyLoggedIntoThisAccount.Contains(hashOfCookie)); 58 | 59 | #pragma warning disable 1998 60 | public override async Task RecordHashOfDeviceCookieUsedDuringSuccessfulLoginAsync(MemoryUserAccount account, string hashOfCookie, 61 | #pragma warning restore 1998 62 | DateTime? whenSeenUtc = null, CancellationToken cancellationToken = new CancellationToken()) 63 | { 64 | account.HashesOfCookiesOfClientsThatHaveSuccessfullyLoggedIntoThisAccount.Add(hashOfCookie); 65 | } 66 | 67 | 68 | #pragma warning disable 1998 69 | public override async Task TryGetCreditAsync(MemoryUserAccount userAccount, 70 | double amountRequested, 71 | DateTime? timeOfRequestUtc = null, 72 | CancellationToken cancellationToken = default(CancellationToken)) 73 | #pragma warning restore 1998 74 | { 75 | DateTime timeOfRequestOrNowUtc = timeOfRequestUtc ?? DateTime.UtcNow; 76 | double amountAvailable = Math.Max(0, userAccount.CreditLimit - userAccount.ConsumedCredits.GetValue(userAccount.CreditHalfLife, timeOfRequestOrNowUtc)); 77 | double amountConsumed = Math.Min(amountRequested, amountAvailable); 78 | userAccount.ConsumedCredits.SubtractInPlace(userAccount.CreditHalfLife, amountConsumed, timeOfRequestOrNowUtc); 79 | return amountConsumed; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /StopGuessingOld/AccountStorage/Sql/DbUserAccount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using StopGuessing.Controllers; 4 | using StopGuessing.EncryptionPrimitives; 5 | using StopGuessing.Interfaces; 6 | using StopGuessing.Models; 7 | 8 | namespace StopGuessing.AccountStorage.Sql 9 | { 10 | /// 11 | /// An implementation of IUserAccount that stores account information in an SQL database. 12 | /// 13 | public class DbUserAccount : IUserAccount 14 | { 15 | public string DbUserAccountId { get; set; } 16 | 17 | [NotMapped] 18 | public string UsernameOrAccountId => DbUserAccountId; 19 | 20 | /// 21 | /// The salt is a random unique sequence of bytes that is included when the password is hashed (phase 1 of hashing) 22 | /// to ensure that attackers who might obtain the set of account hashes cannot hash a password once and then compare 23 | /// the hash against every account. 24 | /// 25 | public byte[] SaltUniqueToThisAccount { get; set; } = 26 | StrongRandomNumberGenerator.GetBytes(UserAccountController.DefaultSaltLength); 27 | 28 | /// 29 | /// The name of the (hopefully) expensive hash function used for the first phase of password hashing. 30 | /// 31 | public string PasswordHashPhase1FunctionName { get; set; } = 32 | ExpensiveHashFunctionFactory.DefaultFunctionName; 33 | 34 | /// 35 | /// The number of iterations to use for the phase 1 hash to make it more expensive. 36 | /// 37 | public int NumberOfIterationsToUseForPhase1Hash { get; set; } = 38 | UserAccountController.DefaultIterationsForPasswordHash; 39 | 40 | /// 41 | /// An EC public encryption symmetricKey used to store log about password failures, which can can only be decrypted when the user 42 | /// enters her correct password, the expensive (phase1) hash of which is used to symmetrically encrypt the matching EC private symmetricKey. 43 | /// 44 | public byte[] EcPublicAccountLogKey { get; set; } 45 | 46 | /// 47 | /// The EC private symmetricKey encrypted with phase 1 (expensive) hash of the password 48 | /// 49 | public byte[] EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1 { get; set; } 50 | 51 | /// 52 | /// The Phase2 password hash is the result of hasing the password (and salt) first with the expensive hash function to create a Phase1 hash, 53 | /// then hasing that Phase1 hash (this time without the salt) using SHA256 so as to make it unnecessary to store the 54 | /// phase1 hash in this record. Doing so allows the Phase1 hash to be used as a symmetric encryption symmetricKey for the log. 55 | /// 56 | public string PasswordHashPhase2 { get; set; } 57 | 58 | /// 59 | /// The account's credit limit for offsetting penalties for IP addresses from which 60 | /// the account has logged in successfully. 61 | /// 62 | public double CreditLimit { get; set; } = 63 | UserAccountController.DefaultCreditLimit; 64 | 65 | /// 66 | /// The half life with which used credits are removed from the system freeing up new credit 67 | /// 68 | public TimeSpan CreditHalfLife { get; set; } = 69 | new TimeSpan(TimeSpan.TicksPerHour * UserAccountController.DefaultCreditHalfLifeInHours); 70 | 71 | 72 | public DbUserAccount() 73 | { 74 | } 75 | 76 | 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /StopGuessingOld/AccountStorage/Sql/DbUserAccountContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.Entity; 2 | using Microsoft.Data.Entity.Infrastructure; 3 | 4 | namespace StopGuessing.AccountStorage.Sql 5 | { 6 | public class DbUserAccountContext : DbContext 7 | { 8 | public DbSet DbUserAccounts { get; set; } 9 | public DbSet DbUserAccountCreditBalances { get; set; } 10 | 11 | public DbUserAccountContext() : base() 12 | { 13 | } 14 | 15 | public DbUserAccountContext(DbContextOptions options) : base(options) 16 | { 17 | } 18 | 19 | protected override void OnModelCreating(ModelBuilder modelBuilder) 20 | { 21 | modelBuilder.Entity().HasIndex(e => e.DbUserAccountId).IsUnique(true); 22 | modelBuilder.Entity().HasKey(e => e.DbUserAccountId); 23 | modelBuilder.Entity() 24 | .Property(e => e.DbUserAccountId) 25 | .IsRequired(); 26 | modelBuilder.Entity().HasIndex(e => e.DbUserAccountId).IsUnique(true); 27 | modelBuilder.Entity().HasKey(e => e.DbUserAccountId); 28 | modelBuilder.Entity() 29 | .Property(e => e.DbUserAccountId) 30 | .IsRequired(); 31 | modelBuilder.Entity() 32 | .Property(e => e.ConsumedCreditsLastValue).IsConcurrencyToken(true); 33 | modelBuilder.Entity() 34 | .Property(e => e.ConsumedCreditsLastUpdatedUtc).IsConcurrencyToken(true); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /StopGuessingOld/AccountStorage/Sql/DbUserAccountCreditBalance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StopGuessing.AccountStorage.Sql 4 | { 5 | public class DbUserAccountCreditBalance 6 | { 7 | public string DbUserAccountId { get; set; } 8 | 9 | /// 10 | /// A decaying double with the amount of credits consumed against the credit limit 11 | /// used to offset IP blocking penalties. 12 | /// 13 | public double ConsumedCreditsLastValue { get; set; } 14 | 15 | public DateTime? ConsumedCreditsLastUpdatedUtc { get; set; } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /StopGuessingOld/AccountStorage/Sql/DbUserAccountRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Data.Entity; 5 | using Microsoft.Data.Entity.Infrastructure; 6 | using StopGuessing.Interfaces; 7 | using StopGuessing.Models; 8 | 9 | namespace StopGuessing.AccountStorage.Sql 10 | { 11 | /// 12 | /// An implementation of a repository for DbUserAccounts to allow these accounts 13 | /// to be read and stored into a database. 14 | /// 15 | public class DbUserAccountRepository : IRepository 16 | { 17 | private readonly DbUserAccountContext _context; 18 | 19 | 20 | public DbUserAccountRepository() 21 | { 22 | _context = new DbUserAccountContext(); 23 | } 24 | 25 | public DbUserAccountRepository(DbContextOptions options) 26 | { 27 | _context = new DbUserAccountContext(options); 28 | } 29 | 30 | 31 | public async Task LoadAsync(string usernameOrAccountId, CancellationToken cancellationToken = default(CancellationToken)) 32 | { 33 | return await _context.DbUserAccounts.Where(u => u.UsernameOrAccountId == usernameOrAccountId).FirstOrDefaultAsync(cancellationToken: cancellationToken); 34 | } 35 | 36 | public async Task AddAsync(DbUserAccount itemToAdd, CancellationToken cancellationToken = default(CancellationToken)) 37 | { 38 | _context.DbUserAccounts.Add(itemToAdd); 39 | await _context.SaveChangesAsync(cancellationToken); 40 | } 41 | 42 | public async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) 43 | { 44 | await _context.SaveChangesAsync(cancellationToken); 45 | } 46 | 47 | public void Dispose() 48 | { 49 | _context?.Dispose(); 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /StopGuessingOld/AccountStorage/Sql/DbUserAccountRepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.Entity; 3 | using Microsoft.Data.Entity.Infrastructure; 4 | using StopGuessing.Interfaces; 5 | using StopGuessing.Models; 6 | 7 | namespace StopGuessing.AccountStorage.Sql 8 | { 9 | 10 | /// 11 | /// A factory for generating repositories that can read and write user accounts to an Azure database. 12 | /// 13 | public class DbUserAccountRepositoryFactory : IUserAccountRepositoryFactory 14 | { 15 | private readonly DbContextOptions _options; 16 | 17 | public DbUserAccountRepositoryFactory() 18 | { 19 | _options = null; 20 | } 21 | 22 | public DbUserAccountRepositoryFactory(DbContextOptions options) 23 | { 24 | _options = options; 25 | } 26 | 27 | public DbUserAccountRepositoryFactory(Action> optionsAction) 28 | { 29 | DbContextOptionsBuilder optionsBuilder = new DbContextOptionsBuilder(); 30 | optionsAction.Invoke(optionsBuilder); 31 | _options = optionsBuilder.Options; 32 | } 33 | 34 | public DbUserAccountRepository CreateDbUserAccountRepository() 35 | { 36 | return _options != null ? new DbUserAccountRepository(_options) : new DbUserAccountRepository(); 37 | } 38 | 39 | public IRepository Create() 40 | { 41 | return CreateDbUserAccountRepository(); 42 | } 43 | 44 | public void Dispose() 45 | { 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /StopGuessingOld/AccountStorage/Sql/IncorrectPhaseTwoHashEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.WindowsAzure.Storage.Table; 3 | 4 | namespace StopGuessing.AccountStorage.Sql 5 | { 6 | /// 7 | /// Used for storing into an azure table a record that maps a account identifier to sets of incorrect 8 | /// passwords that have been submitted for that account. 9 | /// 10 | public class IncorrectPhaseTwoHashEntity : TableEntity 11 | { 12 | public string UsernameOrAccountId => TableKeyEncoding.Decode(RowKey); 13 | public string HashValue => TableKeyEncoding.Decode(PartitionKey); 14 | public DateTime LastSeenUtc { get; set; } 15 | 16 | public IncorrectPhaseTwoHashEntity() 17 | { 18 | } 19 | 20 | public IncorrectPhaseTwoHashEntity(string usernameOrAccountId, string hashValue, DateTime? lastSeenUtc = null) 21 | { 22 | PartitionKey = TableKeyEncoding.Encode(hashValue); 23 | RowKey = TableKeyEncoding.Encode(usernameOrAccountId); 24 | LastSeenUtc = lastSeenUtc ?? DateTime.UtcNow; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /StopGuessingOld/AccountStorage/Sql/SuccessfulLoginCookieEntitty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.WindowsAzure.Storage.Table; 3 | 4 | namespace StopGuessing.AccountStorage.Sql 5 | { 6 | 7 | public class SuccessfulLoginCookieEntity : TableEntity 8 | { 9 | public string UsernameOrAccountId => TableKeyEncoding.Decode(RowKey); 10 | public string HashedValue => TableKeyEncoding.Decode(PartitionKey); 11 | public DateTime LastSeenUtc { get; set; } 12 | 13 | public SuccessfulLoginCookieEntity() 14 | { 15 | } 16 | 17 | public SuccessfulLoginCookieEntity(string usernameOrAccountId, string hashOfCookie, DateTime? lastSeenUtc = null) 18 | { 19 | PartitionKey = TableKeyEncoding.Encode(hashOfCookie); 20 | RowKey = TableKeyEncoding.Encode(usernameOrAccountId); 21 | LastSeenUtc = lastSeenUtc ?? DateTime.UtcNow; 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /StopGuessingOld/DataStructures/Cache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace StopGuessing.DataStructures 8 | { 9 | /// 10 | /// An interface for classes that have an async method to call to start 11 | /// freeing the resources they are using. This can be handy when a class 12 | /// needs to store data to a disk or over a network before it releases 13 | /// itself from memory. 14 | /// 15 | /// If implementing IAsyncDisposable and IDisposable, users should call _either_ 16 | /// Dispose or DisposeAsync, not both. This allows implementers to make the 17 | /// Dispose method simply call await DisposeAsync(); 18 | /// 19 | public interface IAsyncDisposable 20 | { 21 | Task DisposeAsync(); 22 | } 23 | 24 | 25 | //public interface IHasUniqueIdentityKeyString 26 | //{ 27 | // string UniqueIdentityKeyString { Get; } 28 | //} 29 | 30 | 31 | /// 32 | /// Implements a cache with a LRU (least-recently used)-like replacement policy. 33 | /// (Since the cache knows when objects were accessed from the cache -- when it wrote 34 | /// an item or retrieved it -- it treats these as uses. It does not actually 35 | /// know when an object is used after the access happens.) 36 | /// 37 | /// This class implements IDicionary via inheritance. 38 | /// 39 | /// The type of key used to access items in the cache. 40 | /// The value type of values stored in the cache. 41 | public class Cache : DictionaryThatTracksAccessRecency 42 | { 43 | public virtual TValue ConstructDefaultValueForMissingCacheEntry(TKey key) 44 | { 45 | return default(TValue); 46 | } 47 | 48 | 49 | /// 50 | /// Requests the removal of items from the cache to free up space, removing the 51 | /// least-recently accessed items first. 52 | /// 53 | /// The number of items to remove. 54 | public void RecoverSpace(int numberOfItemsToRemove) 55 | { 56 | // Remove the least-recently-accessed values from the cache 57 | KeyValuePair[] entriesToRemove = RemoveAndGetLeastRecentlyAccessed(numberOfItemsToRemove).ToArray(); 58 | 59 | ConcurrentBag asyncDisposalTasks = new ConcurrentBag(); 60 | 61 | // Call the disposal method on each class. 62 | Parallel.For(0, entriesToRemove.Length, (counter, loop) => 63 | { 64 | KeyValuePair entryToRemove = entriesToRemove[counter]; 65 | TValue valueToRemove = entryToRemove.Value; 66 | 67 | // ReSharper disable once CanBeReplacedWithTryCastAndCheckForNull 68 | if (valueToRemove is IAsyncDisposable) 69 | { 70 | asyncDisposalTasks.Add(((IAsyncDisposable)valueToRemove).DisposeAsync()); 71 | } 72 | else if (valueToRemove is IDisposable) 73 | { 74 | ((IDisposable)valueToRemove).Dispose(); 75 | } 76 | 77 | // Remove the last reference to the value so that it can be garbage collected immediately. 78 | entriesToRemove[counter] = default(KeyValuePair); 79 | }); 80 | 81 | Task.WaitAll(asyncDisposalTasks.ToArray()); 82 | } 83 | 84 | /// 85 | /// Requests the removal of items from the cache to free up space, removing the 86 | /// least-recently accessed items first. 87 | /// 88 | /// The fraction of items to remove. 89 | public void RecoverSpace(double fractionOfItemsToRemove) 90 | { 91 | // Calculate the number ot items to remove as a fraction of the total number of items in the cache 92 | int numberOfItemsToRemove = (int)(Count * fractionOfItemsToRemove); 93 | // Remove that many 94 | RecoverSpace(numberOfItemsToRemove); 95 | } 96 | } 97 | 98 | 99 | } -------------------------------------------------------------------------------- /StopGuessingOld/DataStructures/FixedSizeLRUCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StopGuessing.DataStructures 4 | { 5 | public class FixedSizeLruCache : DictionaryThatTracksAccessRecency 6 | { 7 | public int Capacity { get; } 8 | 9 | public FixedSizeLruCache(int capacity) 10 | { 11 | Capacity = capacity; 12 | } 13 | 14 | 15 | public override void Add(TKey key, TValue value) 16 | { 17 | lock (KeysOrderedFromMostToLeastRecentlyUsed) 18 | { 19 | // Remove any nodes with the same key 20 | LinkedListNode> nodeWithSameKey; 21 | if (KeyToLinkedListNode.TryGetValue(key, out nodeWithSameKey)) 22 | { 23 | KeysOrderedFromMostToLeastRecentlyUsed.Remove(nodeWithSameKey); 24 | KeyToLinkedListNode.Remove(key); 25 | } 26 | // Remove the oldest node 27 | if (KeyToLinkedListNode.Count == Capacity) 28 | { 29 | LinkedListNode> oldestNode = KeysOrderedFromMostToLeastRecentlyUsed.Last; 30 | KeysOrderedFromMostToLeastRecentlyUsed.Remove(oldestNode); 31 | KeyToLinkedListNode.Remove(oldestNode.Value.Key); 32 | } 33 | // Add 34 | // Creeate the node 35 | LinkedListNode> newNode = 36 | new LinkedListNode>( 37 | new KeyValuePair(key, value)); 38 | // Put it at the front of the recency-order linked list 39 | KeysOrderedFromMostToLeastRecentlyUsed.AddFirst(newNode); 40 | // Add it to the dictionary for fast lookup. 41 | KeyToLinkedListNode[key] = newNode; 42 | } 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /StopGuessingOld/DataStructures/Proportion.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace StopGuessing.DataStructures 5 | { 6 | /// 7 | /// A class representing a proportion, or fraction, of numerator over denominator. 8 | /// This comes in handy when one might care about the actual magnitude of the numerator/denominator, 9 | /// which is list in a representation that divides the numerator by the denominator. 10 | /// 11 | public struct Proportion 12 | { 13 | public ulong Numerator { get; } 14 | public ulong Denominator { get; } 15 | 16 | public double AsDouble { get; } 17 | 18 | 19 | public Proportion(ulong numerator, ulong denominator) 20 | { 21 | Numerator = numerator; 22 | Denominator = denominator; 23 | AsDouble = Denominator == 0 ? 0 : Numerator / ((double)Denominator); 24 | } 25 | 26 | 27 | /// 28 | /// Return this proportion modified to have a denominator at least as large as a value specified 29 | /// in the parameter. 30 | /// 31 | /// The denominator to use if the proportions denominator is less than this value. 32 | /// If the minDenominator is less than this proportion's Denominator, the return valuee will be this proportion. 33 | /// Otherwise, it will be a new proportion with the same Numerator but with the Denominator set to . 34 | public Proportion MinDenominator(ulong minDenominator) 35 | { 36 | return Denominator >= minDenominator ? this : new Proportion(Numerator, minDenominator); 37 | } 38 | 39 | public static Proportion GetLargest(IEnumerable proportions) 40 | { 41 | Proportion max = new Proportion(0,ulong.MaxValue); 42 | foreach (Proportion p in proportions) 43 | { 44 | if (p.AsDouble > max.AsDouble) 45 | max = p; 46 | } 47 | return max; 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /StopGuessingOld/DataStructures/SequenceWithFastLookup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StopGuessing.DataStructures 4 | { 5 | public class SequenceWithFastLookup : Sequence 6 | { 7 | protected HashSet FastLookupSet; 8 | 9 | public SequenceWithFastLookup(int capacity) : base(capacity) 10 | { 11 | FastLookupSet = new HashSet(); 12 | } 13 | 14 | public override void RemoveAt(int index) 15 | { 16 | lock (SequenceArray) 17 | { 18 | FastLookupSet.Remove(this[index]); 19 | base.RemoveAt(index); 20 | } 21 | } 22 | 23 | public override void Add(T value) 24 | { 25 | lock (SequenceArray) 26 | { 27 | FastLookupSet.Add(value); 28 | base.Add(value); 29 | } 30 | } 31 | 32 | public override bool Contains(T value) 33 | { 34 | lock (SequenceArray) 35 | { 36 | return FastLookupSet.Contains(value); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /StopGuessingOld/DataStructures/StaticUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace StopGuessing.DataStructures 5 | { 6 | internal class StaticUtilities 7 | { 8 | // From: http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059 9 | public static bool IsAssignableToGenericType(Type givenType, Type genericType) 10 | { 11 | if (givenType == null || genericType == null) 12 | return false; 13 | 14 | // Check all interfaces (which will include any ancestor interfaces) to see if they match the genericType 15 | if (givenType.GetInterfaces().Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType)) 16 | return true; 17 | 18 | // Walk up the ancestry chain to all types this type inherits from looking for the type match 19 | for (; givenType != null; givenType = givenType.BaseType) 20 | { 21 | if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) 22 | return true; 23 | } 24 | 25 | return false; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /StopGuessingOld/EncryptionPrimitives/ECEncryptedMessage_AES_CBC_HMACSHA256.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Security.Cryptography; 4 | 5 | namespace StopGuessing.EncryptionPrimitives 6 | { 7 | /// 8 | /// A message encrypted with a message recipient's public EC Key by 9 | /// (1) generating a one-time EC key, 10 | /// (2) deriving a session key from the EC public key and the private portion of the one-time EC key 11 | /// (3) encryption the message with the session key using AES CBC and a SHA256 MAC (and zero'd out IV) 12 | /// (4) storing the public portion of the one-time EC key and the message 13 | /// 14 | /// The message can be decrypted by providing the private portion of the recipient's EC key, 15 | /// deriving the session key from that recipeint's private key and the public one-time EC key. 16 | /// 17 | [DataContract] 18 | public class EcEncryptedMessageAesCbcHmacSha256 19 | { 20 | /// 21 | /// The public portion of the one-time EC key used to generate a session (encryption) key. 22 | /// 23 | [DataMember] 24 | public byte[] PublicOneTimeEcKey { get; set; } 25 | 26 | /// 27 | /// The messsage encrypted with AES CBC and a SHA256 MAC at the end of the plaintext. 28 | /// The encryption key is derived from the private portion of the one-time EC key an the 29 | /// recipient's public EC key. 30 | /// 31 | [DataMember] 32 | public byte[] EncryptedMessage { get; set; } 33 | 34 | 35 | public EcEncryptedMessageAesCbcHmacSha256() 36 | {} 37 | 38 | /// 39 | /// Creates an encrypted message 40 | /// 41 | /// The public portion of the message recpient's EC key. 42 | /// The message to encrypt. 43 | public EcEncryptedMessageAesCbcHmacSha256(ECDiffieHellmanPublicKey recipientsEcPublicKey, 44 | byte[] plaintextMessageAsByteArray) 45 | { 46 | byte[] sessionKey; 47 | using (CngKey oneTimeEcCngKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256)) 48 | { 49 | using (ECDiffieHellmanCng oneTimeEcKey = new ECDiffieHellmanCng(oneTimeEcCngKey)) 50 | { 51 | PublicOneTimeEcKey = oneTimeEcKey.PublicKey.ToByteArray(); 52 | sessionKey = oneTimeEcKey.DeriveKeyMaterial(recipientsEcPublicKey); 53 | } 54 | } 55 | EncryptedMessage = Encryption.EncryptAesCbc(plaintextMessageAsByteArray, sessionKey, addHmac: true); 56 | } 57 | 58 | 59 | /// 60 | /// Decrypt the message by providing the recipient's private EC key. 61 | /// 62 | /// The private EC key matching the public key provided for encryption. 63 | /// The decrypted message as a byte array 64 | public byte[] Decrypt(ECDiffieHellmanCng recipientsPrivateEcKey) 65 | { 66 | byte[] sessionKey; 67 | 68 | try 69 | { 70 | using (CngKey otherPartiesPublicKey = CngKey.Import(PublicOneTimeEcKey, CngKeyBlobFormat.EccPublicBlob)) 71 | { 72 | sessionKey = recipientsPrivateEcKey.DeriveKeyMaterial(otherPartiesPublicKey); 73 | } 74 | } 75 | catch (CryptographicException e) 76 | { 77 | throw new Exception("Failed to Decrypt log entry", e); 78 | } 79 | 80 | return Encryption.DecryptAesCbc(EncryptedMessage, sessionKey, checkAndRemoveHmac: true); 81 | } 82 | 83 | 84 | public byte[] Decrypt(byte[] ecPrivateKey) 85 | { 86 | using (CngKey privateCngKey = CngKey.Import(ecPrivateKey, CngKeyBlobFormat.EccPrivateBlob)) 87 | { 88 | using (ECDiffieHellmanCng privateKey = new ECDiffieHellmanCng(privateCngKey)) 89 | { 90 | return Decrypt(privateKey); 91 | } 92 | } 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /StopGuessingOld/EncryptionPrimitives/EncryptedValueField.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using Newtonsoft.Json; 6 | 7 | namespace StopGuessing.EncryptionPrimitives 8 | { 9 | 10 | 11 | public class EncryptedByteField 12 | { 13 | [DataMember] 14 | public string Ciphertext { get; set; } 15 | 16 | /// 17 | /// 18 | /// The encryption format is a JSON-encoded EcEncryptedMessageAesCbcHmacSha256. 19 | /// 20 | /// The value to store encrypted. 21 | /// The public key used to encrypt the value. 22 | public void Write(byte[] value, byte[] ecPublicKeyAsByteArray) 23 | { 24 | using (ECDiffieHellmanPublicKey ecPublicKey = ECDiffieHellmanCngPublicKey.FromByteArray(ecPublicKeyAsByteArray, CngKeyBlobFormat.EccPublicBlob)) 25 | { 26 | Write(value, ecPublicKey); 27 | } 28 | } 29 | 30 | public void Write(byte[] value, ECDiffieHellmanPublicKey ecPublicKey) 31 | { 32 | Ciphertext = JsonConvert.SerializeObject( 33 | new EcEncryptedMessageAesCbcHmacSha256(ecPublicKey, value)); 34 | } 35 | 36 | /// 37 | /// 38 | /// The private key that can be used to decrypt the value. 39 | /// The decrypted value. 40 | public byte[] Read(ECDiffieHellmanCng ecPrivateKey) 41 | { 42 | if (string.IsNullOrEmpty(Ciphertext)) 43 | throw new MemberAccessException("Cannot decrypt a value that has not been written."); 44 | 45 | EcEncryptedMessageAesCbcHmacSha256 messageDeserializedFromJson = 46 | JsonConvert.DeserializeObject(Ciphertext); 47 | return messageDeserializedFromJson.Decrypt(ecPrivateKey); 48 | } 49 | 50 | public bool HasValue => !string.IsNullOrEmpty(Ciphertext); 51 | } 52 | 53 | public class EncryptedStringField : EncryptedByteField 54 | { 55 | public void Write(string value, byte[] ecPublicKeyAsByteArray) => 56 | Write(Encoding.UTF8.GetBytes(value), ecPublicKeyAsByteArray); 57 | 58 | public void Write(string value, ECDiffieHellmanPublicKey ecPublicKey) => 59 | Write(Encoding.UTF8.GetBytes(value), ecPublicKey); 60 | 61 | public new string Read(ECDiffieHellmanCng ecPrivateKey) => Encoding.UTF8.GetString(base.Read(ecPrivateKey)); 62 | } 63 | 64 | public class EncryptedValueField : EncryptedStringField 65 | { 66 | public void Write(T value, byte[] ecPublicKeyAsByteArray) => 67 | Write(JsonConvert.SerializeObject(value), ecPublicKeyAsByteArray); 68 | 69 | public void Write(T value, ECDiffieHellmanPublicKey ecPublicKey) => 70 | Write(JsonConvert.SerializeObject(value), ecPublicKey); 71 | 72 | public new T Read(ECDiffieHellmanCng ecPrivateKey) => JsonConvert.DeserializeObject(base.Read(ecPrivateKey)); 73 | } 74 | 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /StopGuessingOld/EncryptionPrimitives/ExpensiveHashFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNet.Cryptography.KeyDerivation; 3 | 4 | namespace StopGuessing.EncryptionPrimitives 5 | { 6 | 7 | public delegate byte[] ExpensiveHashFunction(string password, byte[] saltBytes, int iterations); 8 | 9 | public static class ExpensiveHashFunctionFactory 10 | { 11 | 12 | public const string DefaultFunctionName = "PBKDF2_SHA256"; 13 | public const int DefaultNumberOfIterations = 10000; 14 | 15 | private static readonly Dictionary ExpensiveHashFunctions = new Dictionary 16 | { 17 | {DefaultFunctionName, (password, salt, iterations) => 18 | KeyDerivation.Pbkdf2( 19 | password: password, 20 | salt: salt, 21 | prf: KeyDerivationPrf.HMACSHA256, 22 | iterationCount: iterations, 23 | numBytesRequested: 16)} 24 | }; 25 | 26 | 27 | public static ExpensiveHashFunction Get(string hashFunctionName) 28 | { 29 | return ExpensiveHashFunctions[hashFunctionName]; 30 | } 31 | 32 | public static void Add(string name, ExpensiveHashFunction function) 33 | { 34 | ExpensiveHashFunctions[name] = function; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /StopGuessingOld/EncryptionPrimitives/ManagedSHA256.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Threading.Tasks; 6 | 7 | namespace StopGuessing.EncryptionPrimitives 8 | { 9 | public static class ManagedSHA256 10 | { 11 | public static byte[] Hash(byte[] buffer) 12 | { 13 | using (SHA256Managed hash = new SHA256Managed()) 14 | { 15 | return hash.ComputeHash(buffer); 16 | } 17 | 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /StopGuessingOld/EncryptionPrimitives/StrongRandomNumberGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace StopGuessing.EncryptionPrimitives 5 | { 6 | /// 7 | /// A utility interface to .NET's cryptographically-strong random number generator 8 | /// (the interface we which .NET provided) 9 | /// 10 | public static class StrongRandomNumberGenerator 11 | { 12 | // Pre-allocate a thread-safe random number generator 13 | private static readonly RNGCryptoServiceProvider LocalRandomNumberGenerator = new RNGCryptoServiceProvider(); 14 | public static void GetBytes(byte[] bytes) 15 | { 16 | LocalRandomNumberGenerator.GetBytes(bytes); 17 | } 18 | 19 | public static byte[] GetBytes(int count) 20 | { 21 | byte[] bytes = new byte[count]; 22 | LocalRandomNumberGenerator.GetBytes(bytes); 23 | return bytes; 24 | } 25 | 26 | public static ulong Get64Bits(ulong? mod = null) 27 | { 28 | byte[] randBytes = new byte[8]; 29 | GetBytes(randBytes); 30 | 31 | ulong result = BitConverter.ToUInt64(randBytes, 0); 32 | if (mod.HasValue) 33 | result = result % mod.Value; 34 | return result; 35 | } 36 | 37 | public static ulong Get64Bits(long mod) 38 | { 39 | return Get64Bits((ulong)mod); 40 | } 41 | 42 | public static uint Get32Bits(uint? mod = null) 43 | { 44 | // We'll need the randomness to determine which bit to set and which to clear 45 | byte[] randBytes = new byte[4]; 46 | GetBytes(randBytes); 47 | 48 | uint result = BitConverter.ToUInt32(randBytes, 0); 49 | if (mod.HasValue) 50 | result = result % mod.Value; 51 | return result; 52 | } 53 | 54 | public static uint Get32Bits(int mod) 55 | { 56 | return Get32Bits((uint)mod); 57 | } 58 | 59 | public static double GetFraction() 60 | { 61 | return (double)Get64Bits() / (double)ulong.MaxValue; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /StopGuessingOld/Interfaces/IBinomialLadderFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace StopGuessing.Interfaces 8 | { 9 | public interface IBinomialLadderFilter 10 | { 11 | Task StepAsync(string key, int? heightOfLadderInRungs = null, TimeSpan? timeout = null, 12 | CancellationToken cancellationToken = new CancellationToken()); 13 | 14 | Task GetHeightAsync(string element, int? heightOfLadderInRungs = null, TimeSpan? timeout = null, 15 | CancellationToken cancellationToken = new CancellationToken()); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /StopGuessingOld/Interfaces/IDistributedResponsibilitySet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StopGuessing.Interfaces 4 | { 5 | 6 | /// 7 | /// Keep track of a set of members such that each will be responsible for some fraction 8 | /// of keys. 9 | /// 10 | /// Generalizes the concept of Highest Random Weight (HRW, a.k.a. Rendezvous) hashing and 11 | /// consistent hashing (rings) so that one can utilize whichever is more appropriate for evenness 12 | /// of distribution and efficiency for small sets (where HRW is superior) or scalability for 13 | /// large sets (where consistent hashing superior). 14 | /// Consistent hashing becomes more efficient somewhere between 20 members (for a low-optimized .NET 15 | /// implementation of HRW) and 500 members (for an HRW implementation that takes advantage of processor 16 | /// parallelism to perform lots of fast universal hashes.) 17 | /// 18 | /// 19 | public interface IDistributedResponsibilitySet 20 | { 21 | int Count { get; } 22 | 23 | bool ContainsKey(string key); 24 | 25 | void Add(string uniqueKeyIdentifiyingMember, TMember member); 26 | 27 | void AddRange(IEnumerable> newKeyMemberPairs); 28 | 29 | void Remove(string uniqueKeyIdentifiyingMember); 30 | 31 | void RemoveRange(IEnumerable uniqueKeysIdentifiyingMember); 32 | 33 | TMember FindMemberResponsible(string key); 34 | 35 | List FindMembersResponsible(string key, int numberOfUniqueMembersToFind); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /StopGuessingOld/Interfaces/IFactory.cs: -------------------------------------------------------------------------------- 1 | namespace StopGuessing.Interfaces 2 | { 3 | 4 | public interface IFactory 5 | { 6 | T Create(); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /StopGuessingOld/Interfaces/IStableStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using StopGuessing.AccountStorage.Memory; 5 | 6 | namespace StopGuessing.Interfaces 7 | { 8 | 9 | 10 | //public interface IUserAccountContextFactory : IStableStoreFactory 11 | //{ } 12 | 13 | public class MemoryOnlyUserAccountRepository : IRepository // IStableStoreContext 14 | { 15 | private ConcurrentDictionary _store; 16 | 17 | public MemoryOnlyUserAccountRepository(ConcurrentDictionary store) 18 | { 19 | _store = store; 20 | } 21 | 22 | #pragma warning disable 1998 23 | public async Task LoadAsync(string usernameOrAccountId, CancellationToken cancellationToken = default(CancellationToken)) 24 | #pragma warning restore 1998 25 | { 26 | cancellationToken.ThrowIfCancellationRequested(); 27 | MemoryUserAccount result; 28 | _store.TryGetValue(usernameOrAccountId, out result); 29 | return result; 30 | } 31 | 32 | #pragma warning disable 1998 33 | public async Task AddAsync(MemoryUserAccount itemToAdd, CancellationToken cancellationToken = default(CancellationToken)) 34 | #pragma warning restore 1998 35 | { 36 | _store.TryAdd(itemToAdd.UsernameOrAccountId, itemToAdd); 37 | } 38 | 39 | #pragma warning disable 1998 40 | public async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) 41 | #pragma warning disable 1998 42 | { 43 | cancellationToken.ThrowIfCancellationRequested(); 44 | return; 45 | } 46 | 47 | public void Dispose() 48 | { 49 | 50 | } 51 | 52 | } 53 | 54 | public class MemoryOnlyUserAccountFactory : IUserAccountRepositoryFactory 55 | { 56 | private readonly ConcurrentDictionary _store = new ConcurrentDictionary(); 57 | 58 | public IRepository Create() 59 | { 60 | return new MemoryOnlyUserAccountRepository(_store); 61 | } 62 | 63 | public void Add(MemoryUserAccount account) 64 | { 65 | _store[account.UsernameOrAccountId] = account; 66 | } 67 | 68 | public void Dispose() 69 | { 70 | } 71 | 72 | } 73 | 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /StopGuessingOld/Interfaces/IUserAccount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StopGuessing.Interfaces 4 | { 5 | /// 6 | /// This interface specifies the records that must be implemented by a UserAccount record. 7 | /// 8 | public interface IUserAccount 9 | { 10 | /// 11 | /// A string that uniquely identifies the account. 12 | /// 13 | string UsernameOrAccountId { get; } 14 | 15 | /// 16 | /// The salt is a random unique sequence of bytes that is included when the password is hashed (phase 1 of hashing) 17 | /// to ensure that attackers who might obtain the set of account hashes cannot hash a password once and then compare 18 | /// the hash against every account. 19 | /// 20 | byte[] SaltUniqueToThisAccount { get; } 21 | 22 | /// 23 | /// The name of the (hopefully) expensive hash function used for the first phase of password hashing. 24 | /// 25 | string PasswordHashPhase1FunctionName { get; set; } 26 | 27 | /// 28 | /// The number of iterations to use for the phase 1 hash to make it more expensive. 29 | /// 30 | int NumberOfIterationsToUseForPhase1Hash { get; set; } 31 | 32 | /// 33 | /// An EC public encryption symmetricKey used to store log about password failures, which can can only be decrypted when the user 34 | /// enters her correct password, the expensive (phase1) hash of which is used to symmetrically encrypt the matching EC private symmetricKey. 35 | /// 36 | byte[] EcPublicAccountLogKey { get; set; } 37 | 38 | /// 39 | /// The EC private symmetricKey encrypted with phase 1 (expensive) hash of the password 40 | /// 41 | byte[] EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1 { get; set; } 42 | 43 | /// 44 | /// The Phase2 password hash is the result of hasing the password (and salt) first with the expensive hash function to create a Phase1 hash, 45 | /// then hasing that Phase1 hash (this time without the salt) using SHA256 so as to make it unnecessary to store the 46 | /// phase1 hash in this record. Doing so allows the Phase1 hash to be used as a symmetric encryption symmetricKey for the log. 47 | /// 48 | string PasswordHashPhase2 { get; set; } 49 | 50 | /// 51 | /// The account's credit limit for offsetting penalties for IP addresses from which 52 | /// the account has logged in successfully. 53 | /// 54 | double CreditLimit { get; set; } 55 | 56 | /// 57 | /// The half life with which used credits are removed from the system freeing up new credit 58 | /// 59 | TimeSpan CreditHalfLife { get; set; } 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /StopGuessingOld/Interfaces/IUserAccountFactory.cs: -------------------------------------------------------------------------------- 1 | //#define Simulation 2 | // FIXME remove 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace StopGuessing.Interfaces 9 | { 10 | public interface IRepository : IDisposable 11 | { 12 | Task LoadAsync(TKey key, CancellationToken cancellationToken = default(CancellationToken)); 13 | Task AddAsync(T itemToAdd, CancellationToken cancellationToken = default(CancellationToken)); 14 | Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); 15 | 16 | } 17 | 18 | public interface IUserAccountRepositoryFactory : IFactory> where TUserAccount : IUserAccount 19 | { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /StopGuessingOld/Migrations/20160429174517_DbContextFirstMigration.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.Entity; 3 | using Microsoft.Data.Entity.Infrastructure; 4 | using Microsoft.Data.Entity.Metadata; 5 | using Microsoft.Data.Entity.Migrations; 6 | using StopGuessing.AccountStorage.Sql; 7 | 8 | namespace StopGuessing.Migrations 9 | { 10 | [DbContext(typeof(DbUserAccountContext))] 11 | [Migration("20160429174517_DbContextFirstMigration")] 12 | partial class DbContextFirstMigration 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "7.0.0-rc1-16348") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("StopGuessing.Azure.DbUserAccount", b => 21 | { 22 | b.Property("DbUserAccountId"); 23 | 24 | b.Property("CreditHalfLife"); 25 | 26 | b.Property("CreditLimit"); 27 | 28 | b.Property("EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1"); 29 | 30 | b.Property("EcPublicAccountLogKey"); 31 | 32 | b.Property("NumberOfIterationsToUseForPhase1Hash"); 33 | 34 | b.Property("PasswordHashPhase1FunctionName"); 35 | 36 | b.Property("PasswordHashPhase2"); 37 | 38 | b.Property("SaltUniqueToThisAccount"); 39 | 40 | b.HasKey("DbUserAccountId"); 41 | 42 | b.HasIndex("DbUserAccountId") 43 | .IsUnique(); 44 | }); 45 | 46 | modelBuilder.Entity("StopGuessing.Azure.DbUserAccountCreditBalance", b => 47 | { 48 | b.Property("DbUserAccountId"); 49 | 50 | b.Property("ConsumedCreditsLastUpdatedUtc") 51 | .IsConcurrencyToken(); 52 | 53 | b.Property("ConsumedCreditsLastValue") 54 | .IsConcurrencyToken(); 55 | 56 | b.HasKey("DbUserAccountId"); 57 | 58 | b.HasIndex("DbUserAccountId") 59 | .IsUnique(); 60 | }); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /StopGuessingOld/Migrations/20160429174517_DbContextFirstMigration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Data.Entity.Migrations; 4 | 5 | namespace StopGuessing.Migrations 6 | { 7 | public partial class DbContextFirstMigration : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "DbUserAccount", 13 | columns: table => new 14 | { 15 | DbUserAccountId = table.Column(nullable: false), 16 | CreditHalfLife = table.Column(nullable: false), 17 | CreditLimit = table.Column(nullable: false), 18 | EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1 = table.Column(nullable: true), 19 | EcPublicAccountLogKey = table.Column(nullable: true), 20 | NumberOfIterationsToUseForPhase1Hash = table.Column(nullable: false), 21 | PasswordHashPhase1FunctionName = table.Column(nullable: true), 22 | PasswordHashPhase2 = table.Column(nullable: true), 23 | SaltUniqueToThisAccount = table.Column(nullable: true) 24 | }, 25 | constraints: table => 26 | { 27 | table.PrimaryKey("PK_DbUserAccount", x => x.DbUserAccountId); 28 | }); 29 | migrationBuilder.CreateTable( 30 | name: "DbUserAccountCreditBalance", 31 | columns: table => new 32 | { 33 | DbUserAccountId = table.Column(nullable: false), 34 | ConsumedCreditsLastUpdatedUtc = table.Column(nullable: true), 35 | ConsumedCreditsLastValue = table.Column(nullable: false) 36 | }, 37 | constraints: table => 38 | { 39 | table.PrimaryKey("PK_DbUserAccountCreditBalance", x => x.DbUserAccountId); 40 | }); 41 | migrationBuilder.CreateIndex( 42 | name: "IX_DbUserAccount_DbUserAccountId", 43 | table: "DbUserAccount", 44 | column: "DbUserAccountId", 45 | unique: true); 46 | migrationBuilder.CreateIndex( 47 | name: "IX_DbUserAccountCreditBalance_DbUserAccountId", 48 | table: "DbUserAccountCreditBalance", 49 | column: "DbUserAccountId", 50 | unique: true); 51 | } 52 | 53 | protected override void Down(MigrationBuilder migrationBuilder) 54 | { 55 | migrationBuilder.DropTable("DbUserAccount"); 56 | migrationBuilder.DropTable("DbUserAccountCreditBalance"); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /StopGuessingOld/Migrations/DbUserAccountContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.Entity; 3 | using Microsoft.Data.Entity.Infrastructure; 4 | using Microsoft.Data.Entity.Metadata; 5 | using Microsoft.Data.Entity.Migrations; 6 | using StopGuessing.AccountStorage.Sql; 7 | 8 | namespace StopGuessing.Migrations 9 | { 10 | [DbContext(typeof(DbUserAccountContext))] 11 | partial class DbUserAccountContextModelSnapshot : ModelSnapshot 12 | { 13 | protected override void BuildModel(ModelBuilder modelBuilder) 14 | { 15 | modelBuilder 16 | .HasAnnotation("ProductVersion", "7.0.0-rc1-16348") 17 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 18 | 19 | modelBuilder.Entity("StopGuessing.Azure.DbUserAccount", b => 20 | { 21 | b.Property("DbUserAccountId"); 22 | 23 | b.Property("CreditHalfLife"); 24 | 25 | b.Property("CreditLimit"); 26 | 27 | b.Property("EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1"); 28 | 29 | b.Property("EcPublicAccountLogKey"); 30 | 31 | b.Property("NumberOfIterationsToUseForPhase1Hash"); 32 | 33 | b.Property("PasswordHashPhase1FunctionName"); 34 | 35 | b.Property("PasswordHashPhase2"); 36 | 37 | b.Property("SaltUniqueToThisAccount"); 38 | 39 | b.HasKey("DbUserAccountId"); 40 | 41 | b.HasIndex("DbUserAccountId") 42 | .IsUnique(); 43 | }); 44 | 45 | modelBuilder.Entity("StopGuessing.Azure.DbUserAccountCreditBalance", b => 46 | { 47 | b.Property("DbUserAccountId"); 48 | 49 | b.Property("ConsumedCreditsLastUpdatedUtc") 50 | .IsConcurrencyToken(); 51 | 52 | b.Property("ConsumedCreditsLastValue") 53 | .IsConcurrencyToken(); 54 | 55 | b.HasKey("DbUserAccountId"); 56 | 57 | b.HasIndex("DbUserAccountId") 58 | .IsUnique(); 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /StopGuessingOld/Models/AuthenticationOutcome.cs: -------------------------------------------------------------------------------- 1 | namespace StopGuessing.Models 2 | { 3 | /// 4 | /// Possible outcomes of an authentication attempt. The only information ever 5 | /// revealed to an untrusted client is whether the outcome is CredentialsValid, 6 | /// in which case the client should be logged in, or some other value, in which case 7 | /// the client should only be told the credentials were invalid (but not given any more 8 | /// specific information). 9 | /// 10 | public enum AuthenticationOutcome { 11 | /// 12 | /// Default value before the authentication has occurred. 13 | /// 14 | Undetermined = 0, 15 | /// 16 | /// The credentials were valid and the user should be allowed to login. 17 | /// 18 | CredentialsValid = 1, 19 | /// 20 | /// The credentials were valid but the client has been guessing so much that 21 | /// the authentication system should act as if the credentials were invalid. 22 | /// 23 | CredentialsValidButBlocked = -1, 24 | /// 25 | /// The username or account ID provided does not map to a valid account. 26 | /// 27 | CredentialsInvalidNoSuchAccount = -2, 28 | /// 29 | /// The username or account ID provided does not map to a valid account, 30 | /// and we've seen this same mistake made recently. 31 | /// 32 | CredentialsInvalidRepeatedNoSuchAccount = -3, 33 | /// 34 | /// The password provided is not the correct password for this account, 35 | /// and we've seen the same account name/password pair fail recently. 36 | /// 37 | CredentialsInvalidRepeatedIncorrectPassword = -4, 38 | /// 39 | /// The password provided is not the correct password for this account, 40 | /// 41 | CredentialsInvalidIncorrectPassword = -5, 42 | /// 43 | /// The password provided is not the correct password for this account, 44 | /// and its not even close (by measure of edit distance). 45 | /// 46 | CredentialsInvalidIncorrectPasswordTypoUnlikely = -6, 47 | /// 48 | /// The password provided is not the correct password for this account, 49 | /// but it is close enough (by measure of edit distance) that it was 50 | /// more likely a typo than a guess. 51 | /// 52 | CredentialsInvalidIncorrectPasswordTypoLikely = -7 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /StopGuessingOld/Models/IpHistory.cs: -------------------------------------------------------------------------------- 1 | //#define Simulation 2 | // FIXME 3 | using System; 4 | using System.Net; 5 | using StopGuessing.DataStructures; 6 | 7 | namespace StopGuessing.Models 8 | { 9 | /// 10 | /// This class keeps track of recent login successes and failures for a given client IP so that 11 | /// we can try to determine if this client should be blocked due to likely-password-guessing 12 | /// behaviors. 13 | /// 14 | public class IpHistory 15 | { 16 | /// 17 | /// The IP address being tracked. 18 | /// 19 | public readonly IPAddress Address; 20 | 21 | /// 22 | /// A set of recent login attempts that have failed due to incorrect passwords that are kept around so that, 23 | /// when the correct password is provided, we can see if those passwords were typos and adjust the block 24 | /// score to reduce past penalities given to failed logins that were typos. 25 | /// 26 | /// This is implemented as a capacity constrained set, similar to a cache, where newer values push out old values. 27 | /// 28 | public SmallCapacityConstrainedSet RecentPotentialTypos; 29 | 30 | /// 31 | /// The current block score for this IP, in the form of a number that decays with time. 32 | /// 33 | public DecayingDouble CurrentBlockScore; 34 | 35 | public IpHistory( 36 | IPAddress address, 37 | BlockingAlgorithmOptions options) 38 | { 39 | Address = address; 40 | CurrentBlockScore = new DecayingDouble(); 41 | RecentPotentialTypos = 42 | new SmallCapacityConstrainedSet(options.NumberOfFailuresToTrackForGoingBackInTimeToIdentifyTypos); 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /StopGuessingOld/Models/LoginAttemptSummaryForTypoAnalysis.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Threading.Tasks; 6 | using StopGuessing.DataStructures; 7 | using StopGuessing.EncryptionPrimitives; 8 | 9 | namespace StopGuessing.Models 10 | { 11 | /// 12 | /// A record of a recent failed login attempt with just enough information to determine whether 13 | /// that attempt failed due to an incorrectly typed password. 14 | /// 15 | public struct LoginAttemptSummaryForTypoAnalysis 16 | { 17 | /// 18 | /// The unique identifier of the account that was being logged into. 19 | /// 20 | public string UsernameOrAccountId { get; set; } 21 | 22 | /// 23 | /// The penalty applied to the blockign score when this login attempt registered as having 24 | /// an invalid password 25 | /// 26 | public DecayingDouble Penalty { get; set; } 27 | 28 | /// 29 | /// When a login attempt is sent with an incorrect password, that incorrect password is encrypted 30 | /// with the UserAccount's EcPublicAccountLogKey. That private key to decrypt is encrypted 31 | /// wiith the phase1 hash of the user's correct password. If the correct password is provided in the future, 32 | /// we can go back and audit the incorrect password to see if it was within a short edit distance 33 | /// of the correct password--which would indicate it was likely a (benign) typo and not a random guess. 34 | /// 35 | public EncryptedStringField EncryptedIncorrectPassword { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /StopGuessingOld/Models/RemoteHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Configuration; 3 | 4 | namespace StopGuessing.Models 5 | { 6 | /// 7 | /// This class identifies remote hosts to be used as part of a distributed system for 8 | /// balancing the load of login requests (and storing data associated with past requests) 9 | /// across systems. 10 | /// 11 | public class RemoteHost 12 | { 13 | public Uri Uri { get; set; } 14 | 15 | // FIXME -- remove this and find another way to test 16 | // public bool IsLocalHost { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | return Uri.ToString(); 21 | } 22 | } 23 | 24 | public class TestRemoveHost : RemoteHost 25 | { 26 | public string KeyPrefix { get; set; } 27 | 28 | public new string ToString() 29 | { 30 | return KeyPrefix + base.ToString(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /StopGuessingOld/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TestStopGuessing")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TestStopGuessing")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("ba64f492-2be9-422c-b899-9cfea0d1a048")] 24 | -------------------------------------------------------------------------------- /StopGuessingOld/Properties/PublishProfiles/stopguessing-publish.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding(SupportsShouldProcess=$true)] 2 | param($publishProperties, $packOutput, $nugetUrl) 3 | 4 | # to learn more about this file visit http://go.microsoft.com/fwlink/?LinkId=524327 5 | $publishModuleVersion = '1.0.1' 6 | function Get-VisualStudio2015InstallPath{ 7 | [cmdletbinding()] 8 | param() 9 | process{ 10 | $keysToCheck = @('hklm:\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0', 11 | 'hklm:\SOFTWARE\Microsoft\VisualStudio\14.0', 12 | 'hklm:\SOFTWARE\Wow6432Node\Microsoft\VWDExpress\14.0', 13 | 'hklm:\SOFTWARE\Microsoft\VWDExpress\14.0' 14 | ) 15 | [string]$vsInstallPath=$null 16 | 17 | foreach($keyToCheck in $keysToCheck){ 18 | if(Test-Path $keyToCheck){ 19 | $vsInstallPath = (Get-itemproperty $keyToCheck -Name InstallDir -ErrorAction SilentlyContinue | select -ExpandProperty InstallDir -ErrorAction SilentlyContinue) 20 | } 21 | 22 | if($vsInstallPath){ 23 | break; 24 | } 25 | } 26 | 27 | $vsInstallPath 28 | } 29 | } 30 | 31 | $vsInstallPath = Get-VisualStudio2015InstallPath 32 | $publishModulePath = "{0}Extensions\Microsoft\Web Tools\Publish\Scripts\{1}\" -f $vsInstallPath, $publishModuleVersion 33 | 34 | if(!(Test-Path $publishModulePath)){ 35 | $publishModulePath = "{0}VWDExpressExtensions\Microsoft\Web Tools\Publish\Scripts\{1}\" -f $vsInstallPath, $publishModuleVersion 36 | } 37 | 38 | $defaultPublishSettings = New-Object psobject -Property @{ 39 | LocalInstallDir = $publishModulePath 40 | } 41 | 42 | function Enable-PackageDownloader{ 43 | [cmdletbinding()] 44 | param( 45 | $toolsDir = "$env:LOCALAPPDATA\Microsoft\Web Tools\Publish\package-downloader-$publishModuleVersion\", 46 | $pkgDownloaderDownloadUrl = 'http://go.microsoft.com/fwlink/?LinkId=524325') # package-downloader.psm1 47 | process{ 48 | if(get-module package-downloader){ 49 | remove-module package-downloader | Out-Null 50 | } 51 | 52 | if(!(get-module package-downloader)){ 53 | if(!(Test-Path $toolsDir)){ New-Item -Path $toolsDir -ItemType Directory -WhatIf:$false } 54 | 55 | $expectedPath = (Join-Path ($toolsDir) 'package-downloader.psm1') 56 | if(!(Test-Path $expectedPath)){ 57 | 'Downloading [{0}] to [{1}]' -f $pkgDownloaderDownloadUrl,$expectedPath | Write-Verbose 58 | (New-Object System.Net.WebClient).DownloadFile($pkgDownloaderDownloadUrl, $expectedPath) 59 | } 60 | 61 | if(!$expectedPath){throw ('Unable to download package-downloader.psm1')} 62 | 63 | 'importing module [{0}]' -f $expectedPath | Write-Output 64 | Import-Module $expectedPath -DisableNameChecking -Force 65 | } 66 | } 67 | } 68 | 69 | function Enable-PublishModule{ 70 | [cmdletbinding()] 71 | param() 72 | process{ 73 | if(get-module publish-module){ 74 | remove-module publish-module | Out-Null 75 | } 76 | 77 | if(!(get-module publish-module)){ 78 | $localpublishmodulepath = Join-Path $defaultPublishSettings.LocalInstallDir 'publish-module.psm1' 79 | if(Test-Path $localpublishmodulepath){ 80 | 'importing module [publish-module="{0}"] from local install dir' -f $localpublishmodulepath | Write-Verbose 81 | Import-Module $localpublishmodulepath -DisableNameChecking -Force 82 | $true 83 | } 84 | } 85 | } 86 | } 87 | 88 | try{ 89 | 90 | if (!(Enable-PublishModule)){ 91 | Enable-PackageDownloader 92 | Enable-NuGetModule -name 'publish-module' -version $publishModuleVersion -nugetUrl $nugetUrl 93 | } 94 | 95 | 'Calling Publish-AspNet' | Write-Verbose 96 | # call Publish-AspNet to perform the publish operation 97 | Publish-AspNet -publishProperties $publishProperties -packOutput $packOutput 98 | } 99 | catch{ 100 | "An error occurred during publish.`n{0}" -f $_.Exception.Message | Write-Error 101 | } -------------------------------------------------------------------------------- /StopGuessingOld/Properties/PublishProfiles/stopguessing.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | MSDeploy 9 | Debug 10 | Any CPU 11 | http://stopguessing.azurewebsites.net 12 | True 13 | False 14 | False 15 | <_DefaultDNXVersion>dnx-clr-win-x86.1.0.0-beta8 16 | True 17 | web 18 | wwwroot 19 | wwwroot 20 | True 21 | False 22 | False 23 | stopguessing.scm.azurewebsites.net:443 24 | stopguessing 25 | 26 | True 27 | WMSVC 28 | True 29 | $stopguessing 30 | <_SavePWD>True 31 | <_DestinationType>AzureWebSite 32 | False 33 | 34 | -------------------------------------------------------------------------------- /StopGuessingOld/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:42175/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNET_ENV": "Development" 16 | } 17 | }, 18 | "web": { 19 | "commandName": "web", 20 | "environmentVariables": { 21 | "Hosting:Environment": "Development" 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /StopGuessingOld/Service References/Application Insights/ConnectedService.json: -------------------------------------------------------------------------------- 1 | { 2 | "ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider", 3 | "Version": "3.3.1.0", 4 | "GettingStartedDocument": { 5 | "Uri": "http://go.microsoft.com/fwlink/?LinkID=613414" 6 | } 7 | } -------------------------------------------------------------------------------- /StopGuessingOld/StopGuessingOld.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | ba64f492-2be9-422c-b899-9cfea0d1a048 10 | StopGuessing 11 | ..\artifacts\obj\$(MSBuildProjectName) 12 | .\bin\ 13 | 14 | 15 | 2.0 16 | 63517 17 | /subscriptions/713c63c4-888c-4756-8593-e7db2c9c31c5/resourcegroups/Default-ApplicationInsights-CentralUS/providers/microsoft.insights/components/TestStopGuessing 18 | 19 | 20 | -------------------------------------------------------------------------------- /StopGuessingOld/Utilities/TaskHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace StopGuessing.Utilities 7 | { 8 | public static class TaskHelper 9 | { 10 | public static void RunInBackground(Task task) { } 11 | public static void RunInBackground(Task task) { } 12 | 13 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 14 | public static async Task PretendToBeAsync(T t) 15 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 16 | { 17 | return t; 18 | } 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /StopGuessingOld/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApplicationInsights": { 3 | "InstrumentationKey": "" 4 | }, 5 | "data": { 6 | "ConnectionString": "Server=tcp:stopguessingserver.database.windows.net,1433;Database=stopguessing_db;User ID=stopguessing@stopguessingserver;Password=1VatIsDisStope?;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;", 7 | "StorageConnectionString": "DefaultEndpointsProtocol=https;AccountName=stopguessingstorage;AccountKey=ibvA6IeSDIwivra8eFGHEgZVrDweizOG76/TshyFVp0Y0xLjGH/i5xY8+rz0TdltBFpu4FjjlNIfDU7hGRivzw==", 8 | "UniqueConfigurationSecretPhrase": "TakeThatAlgorithmicComplexityAttacks!" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /StopGuessingOld/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApplicationInsights": { 3 | "InstrumentationKey": "46d6aa92-9d4d-453d-882e-a24fc79e03de" 4 | } 5 | } -------------------------------------------------------------------------------- /StopGuessingOld/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final", 4 | "Microsoft.Framework.Configuration.Json": "1.0.0-beta8", 5 | "Microsoft.Framework.Logging": "1.0.0-beta8", 6 | "Microsoft.Framework.Logging.Console": "1.0.0-beta8", 7 | "Microsoft.Framework.Logging.Debug": "1.0.0-beta8", 8 | "Microsoft.Framework.Configuration": "1.0.0-*", 9 | "Microsoft.Framework.WebEncoders.Core": "1.0.0-*", 10 | "Microsoft.AspNet.Cryptography.KeyDerivation": "1.0.0-rc1-final", 11 | "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final", 12 | "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", 13 | "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final", 14 | "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final", 15 | "EntityFramework.Commands": "7.0.0-rc1-final", 16 | "EntityFramework.Core": "7.0.0-rc1-final", 17 | "Microsoft.WindowsAzure.ConfigurationManager": "3.2.1", 18 | "WindowsAzure.Storage": "7.0.2-preview" 19 | }, 20 | "frameworks": { 21 | "net451": { 22 | "dependencies": { 23 | }, 24 | "frameworkAssemblies": { 25 | "System.Linq": "4.0.0", 26 | "System.Net.Http": "4.0.0.0", 27 | "System.Net.Http.WebRequest": "4.0.0.0", 28 | "System.Runtime.Serialization": "4.0.0.0", 29 | "System.Web": "4.0.0.0" 30 | } 31 | } 32 | }, 33 | "publishOptions": { 34 | "excludeFiles": [ 35 | "node_modules", 36 | "bower_components" 37 | ] 38 | }, 39 | "version": "1.0.0-*", 40 | "webroot": "wwwroot" 41 | } 42 | -------------------------------------------------------------------------------- /StopGuessingOld/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | StopGuessing 6 | 7 | 8 | The stop guessing server. 9 | 10 | -------------------------------------------------------------------------------- /StopGuessingOld/wwwroot/web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /StopGuessingTests/BinomialLadderFilterTest.cs: -------------------------------------------------------------------------------- 1 | using StopGuessing.DataStructures; 2 | using Xunit; 3 | 4 | namespace StopGuessingTests 5 | { 6 | public class BinomialLadderFilterTest 7 | { 8 | [Fact] 9 | public void TwentyObservations() 10 | { 11 | BinomialLadderFilter freqFilter = new BinomialLadderFilter(1024*1024*1024, 64); 12 | string somethingToObserve = "Gosh. It's a nice day out, isn't it?"; 13 | 14 | int observationCount = freqFilter.GetHeight(somethingToObserve); 15 | 16 | for (int i = 0; i < 25; i++) 17 | { 18 | int lastCount = freqFilter.Step(somethingToObserve); 19 | Assert.Equal(observationCount, lastCount); 20 | observationCount++; 21 | } 22 | 23 | Assert.Equal(observationCount, freqFilter.GetHeight(somethingToObserve)); 24 | } 25 | 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /StopGuessingTests/CacheTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using StopGuessing.DataStructures; 5 | using Xunit; 6 | 7 | namespace StopGuessingTests 8 | { 9 | 10 | public class CacheTests 11 | { 12 | private Cache CreateCacheContainingFirstThousandCountingNumbers() 13 | { 14 | Cache c = new Cache(); 15 | foreach (KeyValuePair entry in Enumerable.Range(1, 1000).Select(i => new KeyValuePair(i, i.ToString()))) 16 | c.Add(entry); 17 | return c; 18 | } 19 | 20 | [Fact] 21 | public void CacheTestBasicFetchAndLru() 22 | { 23 | Cache c = CreateCacheContainingFirstThousandCountingNumbers(); 24 | Assert.False(c.ContainsKey(1001)); 25 | Assert.False(c.ContainsKey(0)); 26 | Assert.False(c.ContainsKey(8675309)); 27 | Assert.Equal("1", c.LeastRecentlyAccessed.Value); 28 | Assert.Equal(1, c.LeastRecentlyAccessed.Key); 29 | Assert.Equal("1000", c.MostRecentlyAccessed.Value); 30 | Assert.Equal(1000, c.MostRecentlyAccessed.Key); 31 | string y = c[1]; 32 | Assert.Equal("1", y); 33 | string z = c[3]; 34 | Assert.Equal("3", z); 35 | Assert.Equal("3", c.MostRecentlyAccessed.Value); 36 | Assert.Equal("2", c.LeastRecentlyAccessed.Value); 37 | } 38 | 39 | [Fact] 40 | public void CacheTestRecoverSpace() 41 | { 42 | Cache c = CreateCacheContainingFirstThousandCountingNumbers(); 43 | c.RecoverSpace(0.25d); 44 | Assert.Equal(750, c.Count); 45 | Assert.Equal("251", c.LeastRecentlyAccessed.Value); 46 | Assert.Equal("1000", c.MostRecentlyAccessed.Value); 47 | 48 | // Make elements 251--750 touched more recently than 751-1000 49 | for (int i = 251; i <= 750; i++) 50 | { 51 | Assert.Equal(i.ToString(), c[i]); 52 | } 53 | // Remove another 250 elements 54 | c.RecoverSpace(250); 55 | Assert.Equal(500, c.Count); 56 | Assert.Equal("750", c.MostRecentlyAccessed.Value); 57 | Assert.Equal("251", c.LeastRecentlyAccessed.Value); 58 | 59 | List> lra = c.GetLeastRecentlyAccessed(500); 60 | for (int i = 251; i <= 750; i++) 61 | { 62 | Assert.Equal(i, lra[i - 251].Key); 63 | } 64 | } 65 | 66 | [Fact] 67 | public void CacheSelfLoadingAsync() 68 | { 69 | // Not to be confused with a self-loathing cache. 70 | 71 | Cache c = new SelfLoadingCache( 72 | // Map key to value 73 | (key, cancelToken) => { return Task.Run(() => key.ToString(), cancelToken); } 74 | ); 75 | Assert.Equal(0, c.Count); 76 | for (int i = 1; i <= 1000; i++) 77 | { 78 | Assert.Equal(i.ToString(), c[i]); 79 | } 80 | Assert.Equal(1000, c.Count); 81 | Assert.Equal("1000", c.MostRecentlyAccessed.Value); 82 | Assert.Equal("1", c.LeastRecentlyAccessed.Value); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /StopGuessingTests/CapacityConstrainedSetTest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using StopGuessing.DataStructures; 3 | using Xunit; 4 | 5 | namespace StopGuessingTests 6 | { 7 | // This project can output the Class library as a NuGet Package. 8 | // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build". 9 | public class CapacityConstrainedSetTest 10 | { 11 | 12 | [Fact] 13 | public void CapacityConstrainedSetTestSerialize() 14 | { 15 | CapacityConstrainedSet before = new CapacityConstrainedSet(3); 16 | before.UnionWith( new[] {1,2} ); 17 | before.Add(3); 18 | before.Add(4); 19 | 20 | Assert.False(before.Contains(0)); 21 | Assert.False(before.Contains(1)); 22 | Assert.True(before.Contains(2)); 23 | Assert.True(before.Contains(3)); 24 | Assert.True(before.Contains(4)); 25 | Assert.Equal(before.Capacity, 3); 26 | 27 | string serialized = JsonConvert.SerializeObject(before); 28 | CapacityConstrainedSet after = JsonConvert.DeserializeObject>(serialized); 29 | 30 | Assert.False(after.Contains(0)); 31 | Assert.False(after.Contains(1)); 32 | Assert.True(after.Contains(2)); 33 | Assert.True(after.Contains(3)); 34 | Assert.True(after.Contains(4)); 35 | 36 | Assert.Equal(before.InOrderAdded, after.InOrderAdded); 37 | 38 | Assert.Equal(before, after); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /StopGuessingTests/ConsistentHashRingTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using StopGuessing.DataStructures; 5 | using Xunit; 6 | 7 | namespace StopGuessingTests 8 | { 9 | 10 | public class ConsistentHashRingTests 11 | { 12 | static string[] _oneToTenAsWords = new string[] { 13 | "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"}; 14 | 15 | public ConsistentHashRing CreateConsistentHashRingForTenValues() 16 | { 17 | ConsistentHashRing ring = new ConsistentHashRing( 18 | // For testing purposes only, the private key we will use to initialize 19 | // the universal hash function for the hash ring will be the key master from Ghostbusters 20 | "Louis Tully as played by Rick Moranis", 21 | // The ring will contain the words one to ten, capitalized. 22 | _oneToTenAsWords.Select( word => new KeyValuePair(word, word) ), 23 | 1024); 24 | return ring; 25 | } 26 | 27 | [Fact] 28 | public void HashRingEvenDistributionTest() 29 | { 30 | Pseudorandom pseudo = new Pseudorandom(); 31 | int trials = 10000000; 32 | ConsistentHashRing ring = CreateConsistentHashRingForTenValues(); 33 | Dictionary ringCoverage = ring.FractionalCoverage; 34 | 35 | double expectedFraction = 1d / 10d; 36 | 37 | foreach (string word in _oneToTenAsWords) 38 | { 39 | double share = ringCoverage[word]; 40 | double bias = (expectedFraction - share) / expectedFraction; 41 | double absBias = Math.Abs(bias); 42 | Assert.True(bias < .15d); 43 | Assert.True(bias < .15d); 44 | } 45 | 46 | 47 | Dictionary counts = new Dictionary(); 48 | Dictionary frequencies = new Dictionary(); 49 | foreach (string number in _oneToTenAsWords) 50 | counts[number] = 0; 51 | 52 | for (int i = 0; i < trials; i++) 53 | { 54 | string rand = pseudo.GetString(13); 55 | string word = ring.FindMemberResponsible(rand); 56 | counts[word]++; 57 | } 58 | foreach (string word in _oneToTenAsWords) 59 | { 60 | double freq = frequencies[word] = ((double)counts[word]) / (double)trials; 61 | double error = freq - ringCoverage[word]; 62 | Assert.True(Math.Abs(error) < 0.01); 63 | } 64 | 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /StopGuessingTests/DecayingDoubleTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using StopGuessing.DataStructures; 3 | using Xunit; 4 | 5 | namespace StopGuessingTests 6 | { 7 | public class DecayingDoubleTests 8 | { 9 | readonly static TimeSpan OneDay = new TimeSpan(1,0,0,0); 10 | readonly static DateTime FirstDayOfCentury = new DateTime(2000, 1, 1, 0,0,0, DateTimeKind.Utc); 11 | readonly static DateTime OneDayLater = FirstDayOfCentury + OneDay; 12 | readonly static DateTime TwoDaysLater = FirstDayOfCentury + new TimeSpan(OneDay.Ticks * 2L); 13 | readonly static DateTime FourDaysLater = FirstDayOfCentury + new TimeSpan(OneDay.Ticks * 4L); 14 | 15 | [Fact] 16 | public void DecayOverTwoHalfLives() 17 | { 18 | double shouldBeVeryCloseTo1 = DecayingDouble.Decay(4, OneDay, FirstDayOfCentury, TwoDaysLater); 19 | Assert.InRange(shouldBeVeryCloseTo1, .99999,1.000001); 20 | } 21 | 22 | [Fact] 23 | public void AddInPlace() 24 | { 25 | DecayingDouble d = new DecayingDouble(4, FirstDayOfCentury); 26 | // Two days later the double should be 1, so adding 1 should yield 4 27 | d.AddInPlace(OneDay, 3, TwoDaysLater); 28 | Assert.InRange(d.ValueAtTimeOfLastUpdate, 3.99999, 4.000001); 29 | double shouldBeVeryCloseTo1 = d.GetValue(OneDay, FourDaysLater); 30 | Assert.InRange(shouldBeVeryCloseTo1, .99999, 1.000001); 31 | } 32 | [Fact] 33 | public void SubtractInPlace() 34 | { 35 | DecayingDouble d = new DecayingDouble(4, FirstDayOfCentury); 36 | // One day later the double should be 2, so subtracting 1 should yield 1 37 | d.SubtractInPlace(OneDay, 1, OneDayLater); 38 | Assert.InRange(d.ValueAtTimeOfLastUpdate, .99999, 1.000001); 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /StopGuessingTests/EditDistanceTest.cs: -------------------------------------------------------------------------------- 1 | using StopGuessing.DataStructures; 2 | using Xunit; 3 | 4 | namespace StopGuessingTests 5 | { 6 | public class EditDistanceTests 7 | { 8 | [Fact] 9 | public void EditDistanceTranspose() 10 | { 11 | Assert.Equal(0.8f, EditDistance.Calculate("Transposition", "Transopsition", new EditDistance.Costs(transpose: .8f))); 12 | } 13 | 14 | [Fact] 15 | public void EditDistanceSubstitute() 16 | { 17 | Assert.Equal(0.75f, EditDistance.Calculate("Substitution", "Sabstitution!", new EditDistance.Costs(substitute: .25f, add: .5f))); 18 | } 19 | 20 | [Fact] 21 | public void EditDistanceDelete() 22 | { 23 | Assert.Equal(0.4f, EditDistance.Calculate("Deletion", "eletion", new EditDistance.Costs(delete: 0.4f))); 24 | } 25 | 26 | [Fact] 27 | public void EditDistanceAdd() 28 | { 29 | Assert.Equal(0.4f, EditDistance.Calculate("dd", "Add", new EditDistance.Costs(add: 0.4f))); 30 | Assert.Equal(0.4f, EditDistance.Calculate("Add", "Add!", new EditDistance.Costs(add: 0.4f))); 31 | } 32 | 33 | [Fact] 34 | public void EditDistanceChangeCase() 35 | { 36 | Assert.Equal(0.8f, EditDistance.Calculate("CaseSensitive", "casesensitive", new EditDistance.Costs(caseChange: 0.4f))); 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /StopGuessingTests/MaxWeightHashingTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using StopGuessing.DataStructures; 5 | using Xunit; 6 | 7 | namespace StopGuessingTests 8 | { 9 | public class MaxWeightHashingTests 10 | { 11 | static string[] _nodeNames = new string[] { 12 | "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };//, 13 | // "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen", "Twenty"}; 14 | 15 | private MaxWeightHashing CreateMaxWeightHasher() 16 | { 17 | MaxWeightHashing hasher = new MaxWeightHashing( 18 | // For testing purposes only, the private key we will use to initialize 19 | // the universal hash function for the hash ring will be the key master from Ghostbusters 20 | "Louis Tully as played by Rick Moranis", 21 | // The ring will contain the words one to ten, capitalized. 22 | _nodeNames.Select(word => new KeyValuePair(word, word))); 23 | return hasher; 24 | } 25 | 26 | [Fact] 27 | public void MaxWeightHasherEvenDistributionTest() 28 | { 29 | Pseudorandom pseudo = new Pseudorandom(); 30 | int trials = 10000000; 31 | MaxWeightHashing hasher = CreateMaxWeightHasher(); 32 | 33 | double expectedFraction = 1d / ((double) _nodeNames.Length); 34 | 35 | 36 | Dictionary counts = new Dictionary(); 37 | foreach (string number in _nodeNames) 38 | counts[number] = 0; 39 | 40 | for (int i = 0; i < trials; i++) 41 | { 42 | string rand = pseudo.GetString(13); 43 | string word = hasher.FindMemberResponsible(rand); 44 | counts[word]++; 45 | } 46 | 47 | double greatestAdjustedError = 0; 48 | foreach (string word in _nodeNames) 49 | { 50 | double freq = ((double)counts[word]) / (double)trials; 51 | double error = freq - expectedFraction; 52 | double adjustedError = error / expectedFraction; 53 | if (adjustedError > greatestAdjustedError) 54 | greatestAdjustedError = adjustedError; 55 | Assert.True(Math.Abs(adjustedError) < 0.005); 56 | } 57 | 58 | Console.Out.WriteLine("Highest adjusted error: {0}", greatestAdjustedError); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /StopGuessingTests/MemoryLimitingTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace StopGuessingTests 7 | { 8 | public class MemoryLimitingTest 9 | { 10 | [Fact] 11 | public void BigFreakinAllocationTest() 12 | { 13 | Console.Error.WriteLine("Starting test"); 14 | Console.Out.WriteLine("Out starting test."); 15 | TestConfiguration config = FunctionalTests.InitTest(); 16 | //config.StableStore.Accounts = null; 17 | //config.StableStore.LoginAttempts = null; 18 | 19 | uint levelOfParallelism = 8; 20 | List tasks = new List(); 21 | // Create so many accounts that we have to flush them from cache. 22 | for (uint thread = 1; thread <= levelOfParallelism; thread++)// 23 | { 24 | uint myThread = thread; 25 | tasks.Add( 26 | Task.Run(() => BigFreakinAllocationTestLoop(config, myThread, levelOfParallelism))); 27 | } 28 | Task.WaitAll(tasks.ToArray()); 29 | } 30 | 31 | static readonly string BigString = new string('*', 10*1024); 32 | public void BigFreakinAllocationTestLoop(TestConfiguration config, uint threadIndex, uint levelOfParallelism) 33 | { 34 | for (uint i = threadIndex; i < 512 * 1024; i += levelOfParallelism) 35 | { 36 | string username = "User" + i + BigString; 37 | System.Threading.Thread.Sleep(10); 38 | FunctionalTests.CreateTestAccount(config, username, "passwordfor" + i); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /StopGuessingTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("StopGuessingTests")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("d0f070fb-cf09-41de-99df-9193705fb1ab")] 20 | -------------------------------------------------------------------------------- /StopGuessingTests/Pseudorandom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace StopGuessingTests 5 | { 6 | class Pseudorandom 7 | { 8 | private Random _random; 9 | 10 | public Pseudorandom(int seed = 42) 11 | { 12 | _random = new Random(seed); 13 | } 14 | 15 | public void Seed(int seed) 16 | { 17 | _random = new Random(seed); 18 | } 19 | 20 | static readonly char[] Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_+=".ToCharArray(); 21 | 22 | public string GetString(int length = 12) 23 | { 24 | //System.Security.Cryptography.RandomNumberGenerator rng = System.Security.Cryptography.RandomNumberGenerator.Create(); 25 | 26 | StringBuilder s = new StringBuilder(length); 27 | for (int i = 0; i < length; i++) 28 | { 29 | s.Append(Alphabet[_random.Next(Alphabet.Length)]); 30 | //byte[] r = new byte[16]; 31 | //rng.GetBytes(r); 32 | //s.Append(Convert.ToBase64String(r)); 33 | } 34 | 35 | return s.ToString(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /StopGuessingTests/SequenceTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Newtonsoft.Json; 4 | using StopGuessing.DataStructures; 5 | using Xunit; 6 | 7 | namespace StopGuessingTests 8 | { 9 | // This project can output the Class library as a NuGet Package. 10 | // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build". 11 | public class SequenceTest 12 | { 13 | 14 | [Fact] 15 | public void SequenceTestSerialize() 16 | { 17 | Sequence before = new Sequence(3); 18 | before.AddRange( new[] {1,2}); 19 | before.Add(3); 20 | before.Add(4); 21 | 22 | Assert.Equal(4, before[0]); 23 | Assert.Equal(3, before[1]); 24 | Assert.Equal(2, before[2]); 25 | Assert.Equal(3, before.Capacity); 26 | Assert.Equal(4, before.CountOfAllObservedItems); 27 | 28 | //JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); 29 | //serializerSettings.Converters.Add(new SequenceConverter()); 30 | 31 | string serialized = JsonConvert.SerializeObject(before); 32 | Sequence after = JsonConvert.DeserializeObject>(serialized); 33 | 34 | Assert.Equal(4, after[0]); 35 | Assert.Equal(3, after[1]); 36 | Assert.Equal(2,after[2]); 37 | Assert.Equal(3, after.Capacity); 38 | Assert.Equal(4, after.CountOfAllObservedItems); 39 | Assert.Equal(before, after); 40 | } 41 | 42 | [Fact] 43 | public void SequenceTestToList() 44 | { 45 | Sequence s = new Sequence(5); 46 | s.AddRange(new[] { 1, 2, 3 }); 47 | List asList = s.ToList(); 48 | Assert.Equal(1, asList[0]); 49 | Assert.Equal(2, asList[1]); 50 | Assert.Equal(3, asList[2]); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /StopGuessingTests/StopGuessingTests.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | d0f070fb-cf09-41de-99df-9193705fb1ab 10 | StopGuessingTests 11 | .\obj 12 | .\bin\ 13 | v4.6 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /StopGuessingTests/TableKeyEncodingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using StopGuessing.AccountStorage.Sql; 5 | using Xunit; 6 | 7 | namespace StopGuessingTests 8 | { 9 | public class TableKeyEncodingTests 10 | { 11 | const char Unicode0X1A = (char) 0x1a; 12 | 13 | 14 | public void RoundTripTest(string unencoded, string encoded) 15 | { 16 | Assert.Equal(encoded, TableKeyEncoding.Encode(unencoded)); 17 | Assert.Equal(unencoded, TableKeyEncoding.Decode(encoded)); 18 | } 19 | 20 | [Fact] 21 | public void RoundTrips() 22 | { 23 | RoundTripTest("!\n", "!!!n"); 24 | RoundTripTest("left" + Unicode0X1A + "right", "left!x1aright"); 25 | } 26 | 27 | 28 | // The following characters are not allowed in values for the PartitionKey and RowKey properties: 29 | // The forward slash(/) character 30 | // The backslash(\) character 31 | // The number sign(#) character 32 | // The question mark (?) character 33 | // Control characters from U+0000 to U+001F, including: 34 | // The horizontal tab(\t) character 35 | // The linefeed(\n) character 36 | // The carriage return (\r) character 37 | // Control characters from U+007F to U+009F 38 | [Fact] 39 | void EncodesAllForbiddenCharacters() 40 | { 41 | List forbiddenCharacters = "\\/#?\t\n\r".ToCharArray().ToList(); 42 | forbiddenCharacters.AddRange(Enumerable.Range(0x00, 1+(0x1f-0x00)).Select(i => (char)i)); 43 | forbiddenCharacters.AddRange(Enumerable.Range(0x7f, 1+(0x9f-0x7f)).Select(i => (char)i)); 44 | string allForbiddenCharacters = String.Join("", forbiddenCharacters); 45 | string allForbiddenCharactersEncoded = TableKeyEncoding.Encode(allForbiddenCharacters); 46 | 47 | // Make sure decoding is same as encoding 48 | Assert.Equal(allForbiddenCharacters, TableKeyEncoding.Decode(allForbiddenCharactersEncoded)); 49 | 50 | // Ensure encoding does not contain any forbidden characters 51 | Assert.Equal(0, allForbiddenCharacters.Count( c => allForbiddenCharactersEncoded.Contains(c) )); 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /StopGuessingTests/UniversalHashTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using StopGuessing.EncryptionPrimitives; 4 | using Xunit; 5 | 6 | namespace StopGuessingTests 7 | { 8 | public class UniversalHashTest 9 | { 10 | [Fact] 11 | public void UniversalHashTestBias() 12 | { 13 | Pseudorandom pseudo = new Pseudorandom(); 14 | UniversalHashFunction f = new UniversalHashFunction("Louis Tully as played by Rick Moranis!"); 15 | ulong trials = 100000000; 16 | 17 | ulong[] bitCounts = new ulong[64]; 18 | 19 | for (ulong trial = 0; trial < trials; trial++) 20 | { 21 | string randomString = pseudo.GetString(8); 22 | UInt64 supposedlyUnbiasedBits = f.Hash(randomString, UniversalHashFunction.MaximumNumberOfResultBitsAllowing32BiasedBits); 23 | for (int bit=0; bit < bitCounts.Length; bit++) 24 | { 25 | if ((supposedlyUnbiasedBits & (0x8000000000000000ul >> bit)) != 0ul) 26 | bitCounts[bit]++; 27 | } 28 | } 29 | 30 | double[] biases = bitCounts.Select(count => ( (0.5d - (((double)count) / ((double)trials)))) / 0.5d ).ToArray(); 31 | 32 | /// The first 32 bits should be unbiased 33 | for (int bit = 0; bit < 32; bit++) 34 | { 35 | double bias = biases[bit]; 36 | double biasAbs = Math.Abs(bias); 37 | Assert.True(biasAbs < 0.0005d); 38 | } 39 | 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /StopGuessingTests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "testRunner": "xunit", 4 | "dependencies": { 5 | "xunit": "2.1.0", 6 | "StopGuessing": "1.0.0-*", 7 | "dotnet-test-xunit": "1.0.0-rc2-build10025" 8 | }, 9 | "frameworks": { 10 | "netcoreapp1.0": { 11 | "dependencies": { 12 | "Microsoft.NETCore.App": { 13 | "type": "platform", 14 | "version": "1.0.0-rc2-3002702" 15 | } 16 | }, 17 | "imports": [ 18 | "dnxcore50", 19 | "portable-net45+win8" 20 | ] 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ 3 | "wrap" 4 | ], 5 | "sdk": { 6 | "runtime": "coreclr", 7 | "architecture": "x86" 8 | } 9 | } --------------------------------------------------------------------------------