Allow other MAC algorithms in the Remote Config.
authorguilhem <guilhem@fripost.org>
Fri, 29 Mar 2013 16:06:02 +0000 (17:06 +0100)
committerJoey Hess <joey@kitenet.net>
Fri, 29 Mar 2013 22:04:52 +0000 (18:04 -0400)
Crypto.hs
Remote/Helper/Encryptable.hs
Test.hs
Types/Crypto.hs
doc/design/encryption.mdwn
doc/encryption.mdwn

index 2777a9a206b01a6dc040ccead3b4086c5347739b..14cc6efe3c8ae62cf17d8c26420b62f49d617319 100644 (file)
--- a/Crypto.hs
+++ b/Crypto.hs
@@ -26,12 +26,11 @@ module Crypto (
        GpgOpts(..),
        getGpgOpts,
 
-       prop_hmacWithCipher_sane
+       prop_HmacSha1WithCipher_sane
 ) where
 
 import qualified Data.ByteString.Lazy as L
 import Data.ByteString.Lazy.UTF8 (fromString)
-import Data.Digest.Pure.SHA
 import Control.Applicative
 
 import Common.Annex
@@ -40,16 +39,20 @@ import Utility.Gpg.Types
 import Types.Key
 import Types.Crypto
 
-{- The beginning of a Cipher is used for HMAC; the remainder
- - is used as the GPG symmetric encryption passphrase.
+{- The beginning of a Cipher is used for MAC'ing; the remainder is used
+ - as the GPG symmetric encryption passphrase. Note that the cipher
+ - itself is base-64 encoded, hence the string is longer than
+ - 'cipherSize': 683 characters, padded to 684.
  -
- - HMAC SHA1 needs only 64 bytes. The rest of the HMAC key is for expansion,
- - perhaps to HMAC SHA512, which needs 128 bytes (ideally).
- - It also provides room the Cipher to contain data in a form like base64,
- - which does not pack a full byte of entropy into a byte of data.
+ - The 256 first characters that feed the MAC represent at best 192
+ - bytes of entropy.  However that's more than enough for both the
+ - default MAC algorithm, namely HMAC-SHA1, and the "strongest"
+ - currently supported, namely HMAC-SHA512, which respectively needs
+ - (ideally) 64 and 128 bytes of entropy.
  -
- - 256 bytes is enough for gpg's symetric cipher; unlike weaker public key
- - crypto, the key does not need to be too large.
+ - The remainder characters (320 bytes of entropy) is enough for GnuPG's
+ - symetric cipher; unlike weaker public key crypto, the key does not
+ - need to be too large.
  -}
 cipherBeginning :: Int
 cipherBeginning = 256
@@ -60,8 +63,8 @@ cipherSize = 512
 cipherPassphrase :: Cipher -> String
 cipherPassphrase (Cipher c) = drop cipherBeginning c
 
-cipherHmac :: Cipher -> String
-cipherHmac (Cipher c) = take cipherBeginning c
+cipherMac :: Cipher -> String
+cipherMac (Cipher c) = take cipherBeginning c
 
 {- Creates a new Cipher, encrypted to the specified key id. -}
 genEncryptedCipher :: String -> IO StorableCipher
@@ -115,10 +118,10 @@ decryptCipher (EncryptedCipher t _) =
 {- Generates an encrypted form of a Key. The encryption does not need to be
  - reversable, nor does it need to be the same type of encryption used
  - on content. It does need to be repeatable. -}
-encryptKey :: Cipher -> Key -> Key
-encryptKey c k = Key
-       { keyName = hmacWithCipher c (key2file k)
-       , keyBackendName = "GPGHMACSHA1"
+encryptKey :: Mac -> Cipher -> Key -> Key
+encryptKey mac c k = Key
+       { keyName = macWithCipher mac c (key2file k)
+       , keyBackendName = "GPG" ++ showMac mac
        , keySize = Nothing -- size and mtime omitted
        , keyMtime = Nothing -- to avoid leaking data
        }
@@ -147,13 +150,13 @@ encrypt opts = Gpg.feedRead ( Params "--symmetric --force-mdc" : toParams opts )
 decrypt :: Cipher -> Feeder -> Reader a -> IO a
 decrypt = Gpg.feedRead [Param "--decrypt"] . cipherPassphrase
 
-hmacWithCipher :: Cipher -> String -> String
-hmacWithCipher c = hmacWithCipher' (cipherHmac c) 
-hmacWithCipher' :: String -> String -> String
-hmacWithCipher' c s = showDigest $ hmacSha1 (fromString c) (fromString s)
+macWithCipher :: Mac -> Cipher -> String -> String
+macWithCipher mac c = macWithCipher' mac (cipherMac c)
+macWithCipher' :: Mac -> String -> String -> String
+macWithCipher' mac c s = calcMac mac (fromString c) (fromString s)
 
-{- Ensure that hmacWithCipher' returns the same thing forevermore. -}
-prop_hmacWithCipher_sane :: Bool
-prop_hmacWithCipher_sane = known_good == hmacWithCipher' "foo" "bar"
+{- Ensure that macWithCipher' returns the same thing forevermore. -}
+prop_HmacSha1WithCipher_sane :: Bool
+prop_HmacSha1WithCipher_sane = known_good == macWithCipher' HmacSha1 "foo" "bar"
   where
        known_good = "46b4ec586117154dacd49d664e5d63fdc88efb51"
index 4f0404f2a6d76b7b0be4a16b76af3f861ff0eb5e..f3b6bb78790f11db58abb456ee9d64147f19940b 100644 (file)
@@ -12,6 +12,7 @@ import qualified Data.Map as M
 import Common.Annex
 import Types.Remote
 import Crypto
+import Types.Crypto
 import qualified Annex
 import Config.Cost
 import Utility.Base64
@@ -107,7 +108,8 @@ embedCreds c
 cipherKey :: RemoteConfig -> Key -> Annex (Maybe (Cipher, Key))
 cipherKey c k = maybe Nothing make <$> remoteCipher c
   where
-       make ciphertext = Just (ciphertext, encryptKey ciphertext k)
+       make ciphertext = Just (ciphertext, encryptKey mac ciphertext k)
+       mac = fromMaybe defaultMac $ M.lookup "mac" c >>= readMac
 
 {- Stores an StorableCipher in a remote's configuration. -}
 storeCipher :: RemoteConfig -> StorableCipher -> RemoteConfig
diff --git a/Test.hs b/Test.hs
index a383a0a48fa224a66126c3171a80d3419bd89d3e..08ca824ad7f068bfe98cd4c2a7528b5939cd9fd4 100644 (file)
--- a/Test.hs
+++ b/Test.hs
@@ -103,7 +103,7 @@ quickcheck =
        , check "prop_relPathDirToFile_basics" Utility.Path.prop_relPathDirToFile_basics
        , check "prop_relPathDirToFile_regressionTest" Utility.Path.prop_relPathDirToFile_regressionTest
        , check "prop_cost_sane" Config.Cost.prop_cost_sane
-       , check "prop_hmacWithCipher_sane" Crypto.prop_hmacWithCipher_sane
+       , check "prop_HmacSha1WithCipher_sane" Crypto.prop_HmacSha1WithCipher_sane
        , check "prop_TimeStamp_sane" Logs.UUIDBased.prop_TimeStamp_sane
        , check "prop_addLog_sane" Logs.UUIDBased.prop_addLog_sane
        , check "prop_verifiable_sane" Utility.Verifiable.prop_verifiable_sane
index 135522ba11d232edc8c448043fd1db6dd55add9d..e97d02ba8e1bd26208e1ada64fc7687d5f7131d3 100644 (file)
@@ -9,8 +9,16 @@ module Types.Crypto (
        Cipher(..),
        StorableCipher(..),
        KeyIds(..),
+       Mac(..),
+       readMac,
+       showMac,
+       defaultMac,
+       calcMac,
 ) where
 
+import qualified Data.ByteString.Lazy as L
+import Data.Digest.Pure.SHA
+
 import Utility.Gpg (KeyIds(..))
 
 -- XXX ideally, this would be a locked memory region
@@ -18,3 +26,44 @@ newtype Cipher = Cipher String
 
 data StorableCipher = EncryptedCipher String KeyIds | SharedCipher String
        deriving (Ord, Eq)
+
+{- File names are (client-side) MAC'ed on special remotes.
+ - The chosen MAC algorithm needs to be same for all files stored on the
+ - remote.
+ -}
+data Mac = HmacSha1 | HmacSha224 | HmacSha256 | HmacSha384 | HmacSha512
+       deriving (Eq)
+
+defaultMac :: Mac
+defaultMac = HmacSha1
+
+-- MAC algorithms are shown as follows in the file names.
+showMac :: Mac -> String
+showMac HmacSha1   = "HMACSHA1"
+showMac HmacSha224 = "HMACSHA224"
+showMac HmacSha256 = "HMACSHA256"
+showMac HmacSha384 = "HMACSHA384"
+showMac HmacSha512 = "HMACSHA512"
+
+-- Read the MAC algorithm from the remote config.
+readMac :: String -> Maybe Mac
+readMac "HMACSHA1"   = Just HmacSha1
+readMac "HMACSHA224" = Just HmacSha224
+readMac "HMACSHA256" = Just HmacSha256
+readMac "HMACSHA384" = Just HmacSha384
+readMac "HMACSHA512" = Just HmacSha512
+readMac _ = Nothing
+
+calcMac
+  :: Mac         -- ^ MAC
+  -> L.ByteString -- ^ secret key
+  -> L.ByteString -- ^ message
+  -> String      -- ^ MAC'ed message, in hexadecimals
+calcMac mac = case mac of
+       HmacSha1   -> showDigest $* hmacSha1
+       HmacSha224 -> showDigest $* hmacSha224
+       HmacSha256 -> showDigest $* hmacSha256
+       HmacSha384 -> showDigest $* hmacSha384
+       HmacSha512 -> showDigest $* hmacSha512
+  where
+  ($*) g f x y = g $ f x y
index b7acbb732adaad1cf461ee52cdbf1fa3751f0c66..45eb43cc98a5fe51861f947685c1882abbb0f91c 100644 (file)
@@ -59,10 +59,11 @@ for each file in the repository, contact the encrypted remote to check
 if it has the file. This can be done without enumeration, although it will
 mean running gpg once per file fscked, to get the encrypted filename.
 
-So, the files stored in the remote should be encrypted. But, it needs
-to be a repeatable encryption, so they cannot just be gpg encrypted,
-that would yeild a new name each time. Instead, HMAC is used. Any hash
-could be used with HMAC; currently SHA1 is used.
+So, the files stored in the remote should be encrypted. But, it needs to
+be a repeatable encryption, so they cannot just be gpg encrypted, that
+would yeild a new name each time. Instead, HMAC is used. Any hash could
+be used with HMAC. SHA-1 is the default, but [[other_hashes|/encryption]]
+can be chosen for new remotes.
 
 It was suggested that it might not be wise to use the same cipher for both
 gpg and HMAC. Being paranoid, it's best not to tie the security of one
index cc61fea6f70eb0c556b4c65e06530a5fcaefd196..5349e8c7a1c2750d90c73e6be886ab6fd63cdbb6 100644 (file)
@@ -21,6 +21,13 @@ If you want to use encryption, run `git annex initremote` with
 Typically, you will say "encryption=2512E3C7" to use a specific gpg key.
 Or, you might say "encryption=joey@kitenet.net" to search for matching keys.
 
+The default MAC algorithm to be applied on the filenames is HMACSHA1. A
+stronger one, for instance HMACSHA512, one can be chosen upon creation
+of the special remote with the option `mac=HMACSHA512`. The available
+MAC algorithms are HMACSHA1, HMACSHA224, HMACSHA256, HMACSHA384, and
+HMACSHA512. Note that it is not possible to change algorithm for a
+non-empty remote.
+
 The [[encryption_design|design/encryption]] allows additional encryption keys
 to be added on to a special remote later. Once a key is added, it is able
 to access content that has already been stored in the special remote.