Allow other MAC algorithms in the Remote Config.

This commit is contained in:
guilhem 2013-03-29 17:06:02 +01:00 committed by Joey Hess
parent cad52c9d9d
commit 55f0f858ee
6 changed files with 91 additions and 29 deletions

View file

@ -26,12 +26,11 @@ module Crypto (
GpgOpts(..), GpgOpts(..),
getGpgOpts, getGpgOpts,
prop_hmacWithCipher_sane prop_HmacSha1WithCipher_sane
) where ) where
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import Data.ByteString.Lazy.UTF8 (fromString) import Data.ByteString.Lazy.UTF8 (fromString)
import Data.Digest.Pure.SHA
import Control.Applicative import Control.Applicative
import Common.Annex import Common.Annex
@ -40,16 +39,20 @@ import Utility.Gpg.Types
import Types.Key import Types.Key
import Types.Crypto import Types.Crypto
{- The beginning of a Cipher is used for HMAC; the remainder {- The beginning of a Cipher is used for MAC'ing; the remainder is used
- is used as the GPG symmetric encryption passphrase. - 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, - The 256 first characters that feed the MAC represent at best 192
- perhaps to HMAC SHA512, which needs 128 bytes (ideally). - bytes of entropy. However that's more than enough for both the
- It also provides room the Cipher to contain data in a form like base64, - default MAC algorithm, namely HMAC-SHA1, and the "strongest"
- which does not pack a full byte of entropy into a byte of data. - 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 - The remainder characters (320 bytes of entropy) is enough for GnuPG's
- crypto, the key does not need to be too large. - symetric cipher; unlike weaker public key crypto, the key does not
- need to be too large.
-} -}
cipherBeginning :: Int cipherBeginning :: Int
cipherBeginning = 256 cipherBeginning = 256
@ -60,8 +63,8 @@ cipherSize = 512
cipherPassphrase :: Cipher -> String cipherPassphrase :: Cipher -> String
cipherPassphrase (Cipher c) = drop cipherBeginning c cipherPassphrase (Cipher c) = drop cipherBeginning c
cipherHmac :: Cipher -> String cipherMac :: Cipher -> String
cipherHmac (Cipher c) = take cipherBeginning c cipherMac (Cipher c) = take cipherBeginning c
{- Creates a new Cipher, encrypted to the specified key id. -} {- Creates a new Cipher, encrypted to the specified key id. -}
genEncryptedCipher :: String -> IO StorableCipher 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 {- 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 - reversable, nor does it need to be the same type of encryption used
- on content. It does need to be repeatable. -} - on content. It does need to be repeatable. -}
encryptKey :: Cipher -> Key -> Key encryptKey :: Mac -> Cipher -> Key -> Key
encryptKey c k = Key encryptKey mac c k = Key
{ keyName = hmacWithCipher c (key2file k) { keyName = macWithCipher mac c (key2file k)
, keyBackendName = "GPGHMACSHA1" , keyBackendName = "GPG" ++ showMac mac
, keySize = Nothing -- size and mtime omitted , keySize = Nothing -- size and mtime omitted
, keyMtime = Nothing -- to avoid leaking data , 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 :: Cipher -> Feeder -> Reader a -> IO a
decrypt = Gpg.feedRead [Param "--decrypt"] . cipherPassphrase decrypt = Gpg.feedRead [Param "--decrypt"] . cipherPassphrase
hmacWithCipher :: Cipher -> String -> String macWithCipher :: Mac -> Cipher -> String -> String
hmacWithCipher c = hmacWithCipher' (cipherHmac c) macWithCipher mac c = macWithCipher' mac (cipherMac c)
hmacWithCipher' :: String -> String -> String macWithCipher' :: Mac -> String -> String -> String
hmacWithCipher' c s = showDigest $ hmacSha1 (fromString c) (fromString s) macWithCipher' mac c s = calcMac mac (fromString c) (fromString s)
{- Ensure that hmacWithCipher' returns the same thing forevermore. -} {- Ensure that macWithCipher' returns the same thing forevermore. -}
prop_hmacWithCipher_sane :: Bool prop_HmacSha1WithCipher_sane :: Bool
prop_hmacWithCipher_sane = known_good == hmacWithCipher' "foo" "bar" prop_HmacSha1WithCipher_sane = known_good == macWithCipher' HmacSha1 "foo" "bar"
where where
known_good = "46b4ec586117154dacd49d664e5d63fdc88efb51" known_good = "46b4ec586117154dacd49d664e5d63fdc88efb51"

View file

@ -12,6 +12,7 @@ import qualified Data.Map as M
import Common.Annex import Common.Annex
import Types.Remote import Types.Remote
import Crypto import Crypto
import Types.Crypto
import qualified Annex import qualified Annex
import Config.Cost import Config.Cost
import Utility.Base64 import Utility.Base64
@ -107,7 +108,8 @@ embedCreds c
cipherKey :: RemoteConfig -> Key -> Annex (Maybe (Cipher, Key)) cipherKey :: RemoteConfig -> Key -> Annex (Maybe (Cipher, Key))
cipherKey c k = maybe Nothing make <$> remoteCipher c cipherKey c k = maybe Nothing make <$> remoteCipher c
where 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. -} {- Stores an StorableCipher in a remote's configuration. -}
storeCipher :: RemoteConfig -> StorableCipher -> RemoteConfig storeCipher :: RemoteConfig -> StorableCipher -> RemoteConfig

View file

@ -103,7 +103,7 @@ quickcheck =
, check "prop_relPathDirToFile_basics" Utility.Path.prop_relPathDirToFile_basics , check "prop_relPathDirToFile_basics" Utility.Path.prop_relPathDirToFile_basics
, check "prop_relPathDirToFile_regressionTest" Utility.Path.prop_relPathDirToFile_regressionTest , check "prop_relPathDirToFile_regressionTest" Utility.Path.prop_relPathDirToFile_regressionTest
, check "prop_cost_sane" Config.Cost.prop_cost_sane , 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_TimeStamp_sane" Logs.UUIDBased.prop_TimeStamp_sane
, check "prop_addLog_sane" Logs.UUIDBased.prop_addLog_sane , check "prop_addLog_sane" Logs.UUIDBased.prop_addLog_sane
, check "prop_verifiable_sane" Utility.Verifiable.prop_verifiable_sane , check "prop_verifiable_sane" Utility.Verifiable.prop_verifiable_sane

View file

@ -9,8 +9,16 @@ module Types.Crypto (
Cipher(..), Cipher(..),
StorableCipher(..), StorableCipher(..),
KeyIds(..), KeyIds(..),
Mac(..),
readMac,
showMac,
defaultMac,
calcMac,
) where ) where
import qualified Data.ByteString.Lazy as L
import Data.Digest.Pure.SHA
import Utility.Gpg (KeyIds(..)) import Utility.Gpg (KeyIds(..))
-- XXX ideally, this would be a locked memory region -- XXX ideally, this would be a locked memory region
@ -18,3 +26,44 @@ newtype Cipher = Cipher String
data StorableCipher = EncryptedCipher String KeyIds | SharedCipher String data StorableCipher = EncryptedCipher String KeyIds | SharedCipher String
deriving (Ord, Eq) 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

View 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 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. 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 So, the files stored in the remote should be encrypted. But, it needs to
to be a repeatable encryption, so they cannot just be gpg encrypted, be a repeatable encryption, so they cannot just be gpg encrypted, that
that would yeild a new name each time. Instead, HMAC is used. Any hash would yeild a new name each time. Instead, HMAC is used. Any hash could
could be used with HMAC; currently SHA1 is used. 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 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 gpg and HMAC. Being paranoid, it's best not to tie the security of one

View 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. 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. 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 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 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. to access content that has already been stored in the special remote.