summaryrefslogtreecommitdiffhomepage
path: root/Tunables.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Tunables.hs')
-rw-r--r--Tunables.hs162
1 files changed, 162 insertions, 0 deletions
diff --git a/Tunables.hs b/Tunables.hs
new file mode 100644
index 0000000..5c28a39
--- /dev/null
+++ b/Tunables.hs
@@ -0,0 +1,162 @@
+{-# 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
+import Data.Word
+
+-- | 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
+ { shareParams :: ShareParams
+ , objectSize :: Int
+ -- ^ a StorableObject is exactly this many bytes in size
+ -- (must be a multiple of AES block size 16, and cannot be smaller
+ -- than 256 bytes)
+ , shareOverhead :: Int
+ -- ^ Share encoding overhead as a multiple of the objectSize
+ , nameGenerationTunable :: NameGenerationTunable
+ , keyEncryptionKeyTunable :: KeyEncryptionKeyTunable
+ , encryptionTunable :: EncryptionTunable
+ }
+ deriving (Show)
+
+-- | Parameters for shareing. The secret is split into
+-- N objects, such that only M are needed to reconstruct it.
+data ShareParams = ShareParams
+ { 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)
+
+-- | Tunables used by default to backup.
+defaultTunables :: Tunables
+defaultTunables = Tunables
+ { shareParams = ShareParams { totalObjects = 3, neededObjects = 2 }
+ , objectSize = 1024*32 -- 32 kb
+ , shareOverhead = 2
+ -- The nameGenerationHash was benchmarked at 600 seconds
+ -- on a 2 core Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz.
+ , nameGenerationTunable = NameGenerationTunable
+ { nameGenerationHash = argon2 10000 (coreCost 2 (Seconds 600) d)
+ }
+ , keyEncryptionKeyTunable = KeyEncryptionKeyTunable
+ { keyEncryptionKeyHash = argon2 169 (CPUCost (Seconds 12) d)
+ , 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-5200U CPU @ 2.20GHz.
+ , randomSaltBytesBruteForceCost = coreCost 2 (Seconds (50*60)) d
+ }
+ , encryptionTunable = UseAES256
+ }
+ where
+ argon2 i c = UseArgon2 c $ Argon2.HashOptions
+ { Argon2.hashIterations = i
+ , Argon2.hashMemory = 131072 -- 128 mebibtyes per thread
+ , Argon2.hashParallelism =
+ let Divisibility n = d
+ in fromIntegral n
+ , Argon2.hashVariant = Argon2.Argon2i
+ }
+ d = Divisibility 4 -- argon2 uses 4 threads
+
+-- | Dials back hash difficulty, lies about costs.
+-- Not for production use!
+testModeTunables :: Tunables
+testModeTunables = Tunables
+ { shareParams = ShareParams { totalObjects = 3, neededObjects = 2 }
+ , objectSize = 1024*32
+ , shareOverhead = 2
+ , nameGenerationTunable = NameGenerationTunable
+ { nameGenerationHash = weakargon2 (coreCost 2 (Seconds 600) d)
+ }
+ , keyEncryptionKeyTunable = KeyEncryptionKeyTunable
+ { keyEncryptionKeyHash = weakargon2 (CPUCost (Seconds 12) d)
+ , randomSaltBytes = 1
+ , randomSaltBytesBruteForceCost = coreCost 2 (Seconds (50*60)) d
+ }
+ , encryptionTunable = UseAES256
+ }
+ where
+ weakargon2 c = UseArgon2 c Argon2.defaultHashOptions
+ d = Divisibility 4
+
+knownObjectSizes :: [Int]
+knownObjectSizes = map (calc . snd) knownTunings
+ where
+ calc t = objectSize t * shareOverhead t
+
+-- Hash for client-server Proof Of Work. This is tuned to take around
+-- 4 seconds to calculate the hash 256 times on a 4 core machine, with
+-- 0 added iterations. Adding more iterations will increase that somewhat.
+--
+-- This is not included in Tunables because it doesn't affect object
+-- encryption and storage. Any change to this will break backwards
+-- compatability of the HTTP protocol!
+proofOfWorkHashTunable :: Word32 -> ExpensiveHashTunable
+proofOfWorkHashTunable addits =
+ UseArgon2 (CPUCost (Seconds nsecs) (Divisibility 4)) $
+ Argon2.HashOptions
+ { Argon2.hashIterations = nits
+ , Argon2.hashMemory = 1000
+ , Argon2.hashParallelism = 4
+ , Argon2.hashVariant = Argon2.Argon2i
+ }
+ where
+ nits = 20 + addits
+ nsecs = (4 * fromIntegral nits / 20) / 256