summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJoey Hess <joeyh@joeyh.name>2016-08-16 13:40:52 -0400
committerJoey Hess <joeyh@joeyh.name>2016-08-16 13:40:52 -0400
commitc9c476ae7216b80932b80870a2cd06f9339306aa (patch)
treead2255c1d331bb2f286d7786e65151ba987a8247
parent3229b02f0aa6bb23e351d00ade1263851a2f1826 (diff)
downloadkeysafe-c9c476ae7216b80932b80870a2cd06f9339306aa.tar.gz
improve options to select secret key to backup/restore
-rw-r--r--CmdLine.hs14
-rw-r--r--ExpensiveHash.hs2
-rw-r--r--Serialization.hs25
-rw-r--r--Shard.hs8
-rw-r--r--Tunables.hs2
-rw-r--r--Types.hs12
-rw-r--r--keysafe.cabal20
-rw-r--r--keysafe.hs48
8 files changed, 91 insertions, 40 deletions
diff --git a/CmdLine.hs b/CmdLine.hs
index 8e3040a..ca574bb 100644
--- a/CmdLine.hs
+++ b/CmdLine.hs
@@ -11,7 +11,7 @@ import qualified Data.ByteString.UTF8 as BU8
data CmdLine = CmdLine
{ mode :: Mode
- , keytype :: KeyType
+ , secretkeysource :: Maybe SecretKeySource
, testMode :: Bool
, gui :: Bool
}
@@ -23,7 +23,7 @@ data Mode = Backup | Restore | Benchmark
parse :: Parser CmdLine
parse = CmdLine
<$> (backup <|> restore <|> benchmark)
- <*> keytypeopt
+ <*> optional (gpgswitch <|> fileswitch)
<*> testmodeswitch
<*> guiswitch
where
@@ -39,9 +39,13 @@ parse = CmdLine
( long "benchmark"
<> help "Benchmark speed of keysafe's cryptographic primitives."
)
- keytypeopt = KeyType . BU8.fromString <$> strOption
- ( long "type"
- <> help "Type of key (eg, \"gpg\")."
+ gpgswitch = GpgKey . KeyId . BU8.fromString <$> strOption
+ ( long "gpgkeyid"
+ <> help "Specify keyid of gpg key to back up or restore."
+ )
+ fileswitch = KeyFile <$> strOption
+ ( long "keyfile"
+ <> help "Specify secret key file to back up or restore. (The same filename must be used to restore a key as was used to back it up.)"
)
testmodeswitch = switch
( long "testmode"
diff --git a/ExpensiveHash.hs b/ExpensiveHash.hs
index 0e33604..9cf647f 100644
--- a/ExpensiveHash.hs
+++ b/ExpensiveHash.hs
@@ -47,7 +47,7 @@ benchmarkExpensiveHash rounds tunables expected = do
start <- getCurrentTime
forM_ [1..rounds] $ \_ -> do
let ExpensiveHash _ t = expensiveHash tunables
- (Salt (KeyId gpgKey ("benchmark" :: B.ByteString)))
+ (Salt (GpgKey (KeyId ("dummy" :: B.ByteString))))
("himom" :: B.ByteString)
t `deepseq` return ()
end <- getCurrentTime
diff --git a/Serialization.hs b/Serialization.hs
index 4d6a671..8177821 100644
--- a/Serialization.hs
+++ b/Serialization.hs
@@ -11,19 +11,26 @@ module Serialization where
import Types
import Raaz.Core.Encode
import qualified Data.ByteString as B
+import qualified Data.ByteString.UTF8 as BU8
import Data.Monoid
import Data.Word
--- | A KeyId is serialized in the form "keytype value".
--- For example "gpg C910D9222512E3C7"
-instance Encodable KeyId where
- toByteString (KeyId (KeyType t) i) =
- t <> B.singleton sepChar <> i
+-- | A SecretKeySource is serialized in the form "keytype value".
+-- For example "gpg C910D9222512E3C7", or "file path".
+instance Encodable SecretKeySource where
+ toByteString (GpgKey (KeyId b)) =
+ "gpg" <> B.singleton sepChar <> b
+ toByteString (KeyFile f) =
+ "file" <> B.singleton sepChar <> BU8.fromString f
fromByteString b = case B.break (== sepChar) b of
- (t, n)
- | B.null n -> Nothing
- | otherwise -> Just $
- KeyId (KeyType t) (B.drop 1 n)
+ (t, rest)
+ | B.null rest -> Nothing
+ | otherwise ->
+ let i = B.drop 1 rest
+ in case t of
+ "gpg" -> Just $ GpgKey (KeyId i)
+ "file" -> Just $ KeyFile (BU8.toString i)
+ _ -> Nothing
instance Encodable Name where
toByteString (Name n) = n
diff --git a/Shard.hs b/Shard.hs
index ec3332e..ad30fbe 100644
--- a/Shard.hs
+++ b/Shard.hs
@@ -33,10 +33,10 @@ instance Bruteforceable ShardIdents UnknownName where
--
-- This is an expensive operation, to make it difficult for an attacker
-- to brute force known/guessed names and find matching shards.
--- The keyid is used as a salt, both to avoid collisions when the same
--- name is chosen for multiple keys, and to prevent the attacker
--- from using a rainbow table from names to expensivehashes.
-shardIdents :: Tunables -> Name -> KeyId -> ShardIdents
+-- The keyid or filename is used as a salt, both to avoid collisions
+-- when the same name is chosen for multiple keys, and to prevent the
+-- attacker from using a rainbow table from names to expensivehashes.
+shardIdents :: Tunables -> Name -> SecretKeySource -> ShardIdents
shardIdents tunables (Name name) keyid =
ShardIdents idents creationcost bruteforcecalc
where
diff --git a/Tunables.hs b/Tunables.hs
index bd1d4b0..f4f74a4 100644
--- a/Tunables.hs
+++ b/Tunables.hs
@@ -91,7 +91,7 @@ defaultTunables = Tunables
{ nameGenerationHash = argon2 10000 (CPUCost (Seconds (2*600)))
}
, keyEncryptionKeyTunable = KeyEncryptionKeyTunable
- { keyEncryptionKeyHash = argon2 110 (CPUCost (Seconds 0))
+ { keyEncryptionKeyHash = argon2 115 (CPUCost (Seconds 0))
, randomSaltBytes = 1
-- The keyEncryptionKeyHash is run 256 times per
-- random salt byte to brute-force, and its parameters
diff --git a/Types.hs b/Types.hs
index 799e76e..d3eeccb 100644
--- a/Types.hs
+++ b/Types.hs
@@ -45,18 +45,16 @@ newtype Password = Password B.ByteString
newtype Name = Name B.ByteString
deriving (Show)
--- | The type of the key that is stored in keysafe.
-newtype KeyType = KeyType B.ByteString
+-- | Source of the secret key stored in keysafe.
+data SecretKeySource = GpgKey KeyId | KeyFile FilePath
deriving (Show)
-gpgKey :: KeyType
-gpgKey = KeyType "gpg"
-
-- | The keyid is any value that is unique to a private key, and can be
-- looked up somehow without knowing the private key.
--
--- A gpg keyid is the obvious example.
-data KeyId = KeyId KeyType B.ByteString
+-- A gpg keyid is the obvious example. But, if a gpg key is not
+-- stored on the key servers, keysafe will instead use "".
+data KeyId = KeyId B.ByteString
deriving (Show)
data BenchmarkResult t = BenchmarkResult { expectedBenchmark :: t, actualBenchmark :: t }
diff --git a/keysafe.cabal b/keysafe.cabal
index 3eee7c6..175bb9f 100644
--- a/keysafe.cabal
+++ b/keysafe.cabal
@@ -51,8 +51,26 @@ Executable keysafe
Other-Modules:
Crypto.Argon2.FFI
Crypto.Argon2
- ExpensiveHash
+ CmdLine
+ Cost
+ Crypto.SecretSharing.Internal
Encryption
+ Entropy
+ ExpensiveHash
+ Serialization
+ Shard
+ Storage
+ Storage.LocalFiles
+ Tunables
+ Types
+ Types.Cost
+ Types.UI
+ UI
+ UI.Readline
+ UI.Zenity
+ Utility.HumanTime
+ Utility.PartialPrelude
+ Utility.QuickCheck
source-repository head
type: git
diff --git a/keysafe.hs b/keysafe.hs
index 7de3079..7f89004 100644
--- a/keysafe.hs
+++ b/keysafe.hs
@@ -27,20 +27,44 @@ main :: IO ()
main = do
cmdline <- CmdLine.get
ui <- selectUI (CmdLine.gui cmdline)
- let keytype = CmdLine.keytype cmdline
-- TODO determine gpg key id by examining secret key,
-- or retrieving public key from keyserver and examining it.
- let keyid = KeyId keytype "dummy key id"
let tunables = if CmdLine.testMode cmdline
then testModeTunables
else defaultTunables
- case CmdLine.mode cmdline of
- CmdLine.Backup -> storedemo ui keyid tunables
- CmdLine.Restore -> retrievedemo ui keyid
- CmdLine.Benchmark -> benchmarkTunables tunables
+ case (CmdLine.mode cmdline, CmdLine.secretkeysource cmdline) of
+ (CmdLine.Backup, Just secretkeysource) ->
+ backup ui tunables =<< normalize secretkeysource
+ (CmdLine.Backup, Nothing) -> do
+ backup ui tunables =<< normalize =<< pickGpgKey CmdLine.Backup ui
+ (CmdLine.Restore, Just secretkeydest) ->
+ restore ui =<< normalize secretkeydest
+ (CmdLine.Restore, Nothing) -> do
+ restore ui =<< normalize =<< pickGpgKey CmdLine.Backup ui
+ (CmdLine.Benchmark, _) -> benchmarkTunables tunables
-storedemo :: UI -> KeyId -> Tunables -> IO ()
-storedemo ui keyid tunables = do
+-- | Normalize gpg keyids, by querying the gpg keyserver for the key.
+-- If the keyserver knows of the key, the long keyid is used.
+-- But, if the keyserver does not know of the key, a null keyid is used.
+normalize :: SecretKeySource -> IO SecretKeySource
+normalize = return -- TODO
+
+-- | Pick gpg secret key to back up or restore.
+--
+-- When backing up, if there is only one secret
+-- key, the choice is obvious. Otherwise prompt the user with a list.
+--
+-- When restoring, prompt the user for the name of the key,
+-- query the keyserver, and let the user pick from a list.
+-- The "other" option uses a null keyid, to handle the case where a key is
+-- not stored in the keyserver.
+pickGpgKey :: CmdLine.Mode -> UI -> IO SecretKeySource
+pickGpgKey CmdLine.Backup ui = error "TODO"
+pickGpgKey CmdLine.Restore ui = error "TODO"
+pickGpgKey _ ui = error "internal error in pickGpgKey"
+
+backup :: UI -> Tunables -> SecretKeySource -> IO ()
+backup ui tunables secretkeysource = do
username <- userName
name <- fromMaybe (error "Aborting on no name")
<$> promptName ui "Enter a name"
@@ -50,7 +74,7 @@ storedemo ui keyid tunables = do
print $ estimateAttack spotAWS $ estimateBruteforceOf kek
(passwordEntropy password [])
let esk = encrypt tunables kek secretkey
- let sis = shardIdents tunables name keyid
+ let sis = shardIdents tunables name secretkeysource
shards <- genShards esk tunables
print =<< mapM (uncurry (storeShard localFiles)) (zip (getIdents sis) shards)
print =<< obscureShards localFiles
@@ -67,13 +91,13 @@ storedemo ui keyid tunables = do
, "(Your own full name is a pretty good choice for the name to enter here.)"
]
-retrievedemo :: UI -> KeyId -> IO ()
-retrievedemo ui keyid = do
+restore :: UI -> SecretKeySource -> IO ()
+restore ui secretkeydest = do
username <- userName
name <- fromMaybe (error "Aborting on no name")
<$> promptName ui "Enter the name of the key to restore"
namedesc username validateName
- let sis = shardIdents tunables name keyid
+ let sis = shardIdents tunables name secretkeydest
-- we drop 1 to simulate not getting all shards from the servers
let l = drop 1 $ zip [1..] (getIdents sis)
shards <- map (\(RetrieveSuccess s) -> s)