From 55f0f858ee772c0d5c67cdab76099c7358e60b95 Mon Sep 17 00:00:00 2001 From: guilhem Date: Fri, 29 Mar 2013 17:06:02 +0100 Subject: [PATCH] Allow other MAC algorithms in the Remote Config. --- Crypto.hs | 49 +++++++++++++++++++----------------- Remote/Helper/Encryptable.hs | 4 ++- Test.hs | 2 +- Types/Crypto.hs | 49 ++++++++++++++++++++++++++++++++++++ doc/design/encryption.mdwn | 9 ++++--- doc/encryption.mdwn | 7 ++++++ 6 files changed, 91 insertions(+), 29 deletions(-) diff --git a/Crypto.hs b/Crypto.hs index 2777a9a206..14cc6efe3c 100644 --- 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" diff --git a/Remote/Helper/Encryptable.hs b/Remote/Helper/Encryptable.hs index 4f0404f2a6..f3b6bb7879 100644 --- a/Remote/Helper/Encryptable.hs +++ b/Remote/Helper/Encryptable.hs @@ -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 a383a0a48f..08ca824ad7 100644 --- 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 diff --git a/Types/Crypto.hs b/Types/Crypto.hs index 135522ba11..e97d02ba8e 100644 --- a/Types/Crypto.hs +++ b/Types/Crypto.hs @@ -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 diff --git a/doc/design/encryption.mdwn b/doc/design/encryption.mdwn index b7acbb732a..45eb43cc98 100644 --- a/doc/design/encryption.mdwn +++ b/doc/design/encryption.mdwn @@ -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 diff --git a/doc/encryption.mdwn b/doc/encryption.mdwn index cc61fea6f7..5349e8c7a1 100644 --- a/doc/encryption.mdwn +++ b/doc/encryption.mdwn @@ -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.