├── .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