summaryrefslogtreecommitdiffhomepage
path: root/Types.hs
blob: 41c3a0075397ec3658dc74c6184f6812a5e092c1 (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
131
132
{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, MultiParamTypeClasses, FlexibleInstances #-}

module Types where

import Types.Cost
import Entropy
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Crypto.Argon2 as Argon2
import Data.String

-- | keysafe stores secret keys.
newtype SecretKey = SecretKey B.ByteString

-- | Objects stored on a keysafe server are (probably) a shard of an
-- encrypted secret key.
newtype StorableObject = StorableObject { fromStorableObject :: BL.ByteString }

-- | 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
	}

-- | 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. Carefully chosen parts of this are exposed at various points
-- in the data stored for a key, to allow future versions of keysafe to
-- make the right decisions when processing it.
data Tunables = Tunables
	{ shardParams :: ShardParams
	, objectSize :: Int
	-- ^ a StorableObject is exactly this many bytes in size
	, expensiveHashTunable :: ExpensiveHashTunable
	, encryptionTunable :: EncryptionTunable
	, decryptionPuzzleTunable :: DecryptionPuzzleTunable
	}

-- | An expensive hash, used to make it hard to crack an encrypted secret key.
data ExpensiveHashTunable = UseArgon2 Argon2.HashOptions (Cost CreationOp)
	deriving (Show)

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

-- | An additional puzzle that makes decryption more expensive.
data DecryptionPuzzleTunable = KeyBlindingLeftSide (Cost DecryptionOp)
	deriving (Show)

defaultTunables :: Tunables
defaultTunables = Tunables
	{ shardParams = ShardParams { totalObjects = 3, neededObjects = 2 }
	, objectSize = 1024*64 -- 64 kb
	, expensiveHashTunable = UseArgon2 argonoptions argoncost
	, encryptionTunable = UseAES256
	-- AES can be calculated more efficiently by a GPU, so the
	-- cost is a GPU cost.
	-- This is set to only 1 minute because GPUs are quite a lot
	-- faster than CPUs at AES, and so setting it higher would make
	-- clients too slow at key recovery.
	, decryptionPuzzleTunable = KeyBlindingLeftSide (GPUCost (Seconds 60))
	}
  where
	argonoptions = Argon2.HashOptions
		{ Argon2.hashIterations = 10000
		, Argon2.hashMemory = 131072 -- 128 mebibtyes per thread
		, Argon2.hashParallelism = 4 -- 4 threads
		, Argon2.hashVariant = Argon2.Argon2i
		}
	-- argon2 is GPU and ASIC resistent, so it uses CPU time.
	-- The above HashOptions were 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.
	argoncost = CPUCost (Seconds (2*600))

-- | Dials back cryptographic difficulty, not for production use.
testModeTunables :: Tunables
testModeTunables = Tunables
	{ shardParams = ShardParams { totalObjects = 3, neededObjects = 2 }
	, objectSize = 1024*64
	, expensiveHashTunable = UseArgon2 Argon2.defaultHashOptions (CPUCost (Seconds (2*600)))
	, encryptionTunable = UseAES256
	, decryptionPuzzleTunable = KeyBlindingLeftSide (GPUCost (Seconds 60))
	}

-- | The secret key, encrypted with a password.
data EncryptedSecretKey = EncryptedSecretKey B.ByteString (CostCalc BruteForceOp UnknownPassword)

instance Bruteforceable EncryptedSecretKey UnknownPassword where
	getBruteCostCalc (EncryptedSecretKey _ cc) = cc

-- | A password used to encrypt a key stored in keysafe.
newtype Password = Password B.ByteString
	deriving (IsString)

-- | Naive calculation of the entropy of a password.
-- Does not take common passwords and password generation patterns into
-- account, so this is an overestimation of how hard a password
-- is to crack.
passwordEntropy :: Password -> Entropy UnknownPassword
passwordEntropy (Password p) = Entropy $ floor $ totalEntropy p

-- | A name associated with a key stored in keysafe.
newtype Name = Name B.ByteString
	deriving (Show)

-- | The type of the key that is stored in keysafe.
newtype KeyType = KeyType B.ByteString
	deriving (Show)

gpgKey :: KeyType
gpgKey = KeyType "gpg"

-- | Enough information to uniquely identify a key stored in keysafe.
data KeyIdent = KeyIdent KeyType Name
	deriving (Show)

newtype ShardNum = ShardNum Int
	deriving (Show)

-- | Enough information to uniquely identify an object stored on a keysafe
-- server for a key.
data ObjectIdent = ObjectIdent ShardNum KeyIdent
	deriving (Show)

data Benchmark t = Benchmark { expectedBenchmark :: t, actualBenchmark :: t }
	deriving (Show)