summaryrefslogtreecommitdiffhomepage
path: root/Tunables.hs
blob: bd1d4b015d074cd6259e343700b24b50ac154ebe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}

{- Copyright 2016 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU AGPL version 3 or higher.
 -}

module Tunables where

import Cost
import qualified Crypto.Argon2 as Argon2

-- | To determine the tunables used for a key name the expensive hash of the
-- name is calculated, using a particular configuration, and if the
-- object names it generates are available, we know the tunables.
--
-- Since this process is expensive, it's important that the most commonly
-- used tunables come first, so that the expensive hash does not have to be
-- calculated repatedly.
--
-- The reason for using this expensive method of encoding the tunables
-- is that it prevents attacks where related objects are correlated based
-- on their tunables.
knownTunings :: [(ExpensiveHashTunable, Tunables)]
knownTunings = map (\t -> (nameGenerationHash (nameGenerationTunable t), t))
	[ defaultTunables
	]

-- | keysafe stores data for a long time, and needs to be able to process
-- data from a long time ago when restoring a key. We don't want to be 
-- locked into old choices of crypto primitives etc forever. 
--
-- So, every parameter that can be tuned is configured in this data
-- structure.
data Tunables = Tunables
	{ shardParams :: [ShardParams]
	-- ^ multiple ShardParams may be supported, with the user
	-- allowed to choose between them
	, objectSize :: Int
	-- ^ a StorableObject is exactly this many bytes in size
	-- (must be a multiple of AES block size 16)
	, nameGenerationTunable :: NameGenerationTunable
	, keyEncryptionKeyTunable :: KeyEncryptionKeyTunable
	, encryptionTunable :: EncryptionTunable
	}
	deriving (Show)

-- | Parameters for sharding. The secret is split into
-- N objects, such that only M are needed to reconstruct it.
data ShardParams = ShardParams
	{ totalObjects :: Int -- ^ N
	, neededObjects :: Int -- ^ M
	}
	deriving (Show)

-- | An expensive hash, which makes brute-forcing hard.
--
-- The creation cost estimate must be manually tuned to match the
-- hash options. Use benchmarkTunables to check this.
data ExpensiveHashTunable = UseArgon2 (Cost CreationOp) Argon2.HashOptions
	deriving (Show)

data NameGenerationTunable = NameGenerationTunable
	{ nameGenerationHash :: ExpensiveHashTunable
	}
	deriving (Show)

-- | How to generate the encryption key used to encrypt the secret key.
-- This is an expensive hash of the password, but not a super expensive
-- hash, because a password brute forcing attacker needs to run the hash
-- 256 times per random salt byte.
data KeyEncryptionKeyTunable = KeyEncryptionKeyTunable
	{ keyEncryptionKeyHash :: ExpensiveHashTunable
	, randomSaltBytes :: Int
	, randomSaltBytesBruteForceCost :: Cost BruteForceOp
	}
	deriving (Show)

-- | What encryption to use.
data EncryptionTunable = UseAES256
	deriving (Show)

defaultTunables :: Tunables
defaultTunables = Tunables
	{ shardParams = [ShardParams { totalObjects = 3, neededObjects = 2 }]
	, objectSize = 1024*64 -- 64 kb
	-- The nameGenerationHash was benchmarked at 661 seconds CPU time
	-- on a 2 core Intel(R) Core(TM) i5-4210Y CPU @ 1.50GHz.
	-- Since cost is measured per core, we double that.
	, nameGenerationTunable = NameGenerationTunable
		{ nameGenerationHash = argon2 10000 (CPUCost (Seconds (2*600)))
		}
	, keyEncryptionKeyTunable = KeyEncryptionKeyTunable
		{ keyEncryptionKeyHash = argon2 110 (CPUCost (Seconds 0))
		, randomSaltBytes = 1
		-- The keyEncryptionKeyHash is run 256 times per 
		-- random salt byte to brute-force, and its parameters
		-- were chosen so the total brute forcing time is 50 minutes,
		-- on a 2 core Intel(R) Core(TM) i5-4210Y CPU @ 1.50GHz.
		-- Since cost is measured per core, we double that.
		, randomSaltBytesBruteForceCost = CPUCost (Seconds (2*50*60))
		}
	, encryptionTunable = UseAES256
	}
  where
	argon2 i c = UseArgon2 c $ Argon2.HashOptions
		{ Argon2.hashIterations = i
		, Argon2.hashMemory = 131072 -- 128 mebibtyes per thread
		, Argon2.hashParallelism = 4 -- 4 threads
		, Argon2.hashVariant = Argon2.Argon2i
		}

-- | Dials back hash difficulty, lies about costs.
-- Not for production use!
testModeTunables :: Tunables
testModeTunables = Tunables
	{ shardParams = [ShardParams { totalObjects = 3, neededObjects = 2 }]
	, objectSize = 1024*64
	, nameGenerationTunable = NameGenerationTunable
		{ nameGenerationHash = weakargon2 (CPUCost (Seconds (2*600)))
		}
	, keyEncryptionKeyTunable = KeyEncryptionKeyTunable
		{ keyEncryptionKeyHash = weakargon2 (CPUCost (Seconds 0))
		, randomSaltBytes = 1
		, randomSaltBytesBruteForceCost = CPUCost (Seconds (2*50*60))
		}
	, encryptionTunable = UseAES256
	}
  where
	weakargon2 c = UseArgon2 c Argon2.defaultHashOptions