From 1c16f616df9a8469d24cefb6007333df3a35a449 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 29 Apr 2012 14:02:18 -0400 Subject: [PATCH] Added shared cipher mode to encryptable special remotes. This option avoids gpg key distribution, at the expense of flexability, and with the requirement that all clones of the git repository be equally trusted. --- Annex.hs | 2 +- Crypto.hs | 75 +++++++++++++++--------------------- Remote/Helper/Encryptable.hs | 25 +++++++----- Types/Crypto.hs | 6 +-- Utility/Gpg.hs | 13 ++++++- debian/changelog | 3 ++ doc/encryption.mdwn | 13 +++++++ 7 files changed, 79 insertions(+), 58 deletions(-) diff --git a/Annex.hs b/Annex.hs index 441e460beb..f2fb1f4019 100644 --- a/Annex.hs +++ b/Annex.hs @@ -90,7 +90,7 @@ data AnnexState = AnnexState , shared :: Maybe SharedRepository , forcetrust :: TrustMap , trustmap :: Maybe TrustMap - , ciphers :: M.Map EncryptedCipher Cipher + , ciphers :: M.Map StorableCipher Cipher , lockpool :: M.Map FilePath Fd , flags :: M.Map String Bool , fields :: M.Map String String diff --git a/Crypto.hs b/Crypto.hs index cb1ca40d14..e530bd0e65 100644 --- a/Crypto.hs +++ b/Crypto.hs @@ -3,16 +3,17 @@ - Currently using gpg; could later be modified to support different - crypto backends if neccessary. - - - Copyright 2011 Joey Hess + - Copyright 2011-2012 Joey Hess - - Licensed under the GNU GPL version 3 or higher. -} module Crypto ( Cipher, - EncryptedCipher, - genCipher, - updateCipher, + StorableCipher(..), + genEncryptedCipher, + genSharedCipher, + updateEncryptedCipher, describeCipher, storeCipher, extractCipher, @@ -60,59 +61,55 @@ cipherPassphrase (Cipher c) = drop cipherHalf c cipherHmac :: Cipher -> String cipherHmac (Cipher c) = take cipherHalf c -{- Creates a new Cipher, encrypted as specified in the remote's configuration -} -genCipher :: RemoteConfig -> IO EncryptedCipher -genCipher c = do - ks <- configKeyIds c - random <- genrandom +{- Creates a new Cipher, encrypted to the specificed key id. -} +genEncryptedCipher :: String -> IO StorableCipher +genEncryptedCipher keyid = do + ks <- Gpg.findPubKeys keyid + random <- Gpg.genRandom cipherSize encryptCipher (Cipher random) ks - where - genrandom = Gpg.readStrict - -- Armor the random data, to avoid newlines, - -- since gpg only reads ciphers up to the first - -- newline. - [ Params "--gen-random --armor" - , Param $ show randomquality - , Param $ show cipherSize - ] - -- 1 is /dev/urandom; 2 is /dev/random - randomquality = 1 :: Int -{- Updates an existing Cipher, re-encrypting it to add KeyIds specified in - - the remote's configuration. -} -updateCipher :: RemoteConfig -> EncryptedCipher -> IO EncryptedCipher -updateCipher c encipher@(EncryptedCipher _ ks) = do - ks' <- configKeyIds c - cipher <- decryptCipher c encipher +{- Creates a new, shared Cipher. -} +genSharedCipher :: IO StorableCipher +genSharedCipher = SharedCipher <$> Gpg.genRandom cipherSize + +{- Updates an existing Cipher, re-encrypting it to add a keyid. -} +updateEncryptedCipher :: String -> StorableCipher -> IO StorableCipher +updateEncryptedCipher _ (SharedCipher _) = undefined +updateEncryptedCipher keyid encipher@(EncryptedCipher _ ks) = do + ks' <- Gpg.findPubKeys keyid + cipher <- decryptCipher encipher encryptCipher cipher (merge ks ks') where merge (KeyIds a) (KeyIds b) = KeyIds $ a ++ b -describeCipher :: EncryptedCipher -> String +describeCipher :: StorableCipher -> String +describeCipher (SharedCipher _) = "shared cipher" describeCipher (EncryptedCipher _ (KeyIds ks)) = "with gpg " ++ keys ks ++ " " ++ unwords ks where keys [_] = "key" keys _ = "keys" -{- Stores an EncryptedCipher in a remote's configuration. -} -storeCipher :: RemoteConfig -> EncryptedCipher -> RemoteConfig +{- Stores an StorableCipher in a remote's configuration. -} +storeCipher :: RemoteConfig -> StorableCipher -> RemoteConfig +storeCipher c (SharedCipher t) = M.insert "cipher" (toB64 t) c storeCipher c (EncryptedCipher t ks) = M.insert "cipher" (toB64 t) $ M.insert "cipherkeys" (showkeys ks) c where showkeys (KeyIds l) = join "," l -{- Extracts an EncryptedCipher from a remote's configuration. -} -extractCipher :: RemoteConfig -> Maybe EncryptedCipher +{- Extracts an StorableCipher from a remote's configuration. -} +extractCipher :: RemoteConfig -> Maybe StorableCipher extractCipher c = case (M.lookup "cipher" c, M.lookup "cipherkeys" c) of (Just t, Just ks) -> Just $ EncryptedCipher (fromB64 t) (readkeys ks) + (Just t, Nothing) -> Just $ SharedCipher (fromB64 t) _ -> Nothing where readkeys = KeyIds . split "," {- Encrypts a Cipher to the specified KeyIds. -} -encryptCipher :: Cipher -> KeyIds -> IO EncryptedCipher +encryptCipher :: Cipher -> KeyIds -> IO StorableCipher encryptCipher (Cipher c) (KeyIds ks) = do let ks' = nub $ sort ks -- gpg complains about duplicate recipient keyids encipher <- Gpg.pipeStrict (encrypt++recipients ks') c @@ -126,9 +123,9 @@ encryptCipher (Cipher c) (KeyIds ks) = do force_recipients = Params "--no-encrypt-to --no-default-recipient" {- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -} -decryptCipher :: RemoteConfig -> EncryptedCipher -> IO Cipher -decryptCipher _ (EncryptedCipher encipher _) = - Cipher <$> Gpg.pipeStrict decrypt encipher +decryptCipher :: StorableCipher -> IO Cipher +decryptCipher (SharedCipher t) = return $ Cipher t +decryptCipher (EncryptedCipher t _) = Cipher <$> Gpg.pipeStrict decrypt t where decrypt = [ Param "--decrypt" ] @@ -165,14 +162,6 @@ pass :: (Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a) -> Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a pass to c i a = to c i $ \h -> a =<< L.hGetContents h -configKeyIds :: RemoteConfig -> IO KeyIds -configKeyIds c = Gpg.findPubKeys $ configGet c "encryption" - -configGet :: RemoteConfig -> String -> String -configGet c key = fromMaybe missing $ M.lookup key c - where - missing = error $ "missing " ++ key ++ " in remote config" - hmacWithCipher :: Cipher -> String -> String hmacWithCipher c = hmacWithCipher' (cipherHmac c) hmacWithCipher' :: String -> String -> String diff --git a/Remote/Helper/Encryptable.hs b/Remote/Helper/Encryptable.hs index bcecb30cc4..a44e6e4539 100644 --- a/Remote/Helper/Encryptable.hs +++ b/Remote/Helper/Encryptable.hs @@ -17,17 +17,22 @@ import Config {- Encryption setup for a remote. The user must specify whether to use - an encryption key, or not encrypt. An encrypted cipher is created, or is - - updated to be accessible to an additional encryption key. -} + - updated to be accessible to an additional encryption key. Or the user + - could opt to use a shared cipher, which is stored unencrypted. -} encryptionSetup :: RemoteConfig -> Annex RemoteConfig -encryptionSetup c = - case (M.lookup "encryption" c, extractCipher c) of - (Nothing, Nothing) -> error "Specify encryption=key or encryption=none" - (Just "none", Nothing) -> return c - (Just "none", Just _) -> error "Cannot change encryption type of existing remote." - (Nothing, Just _) -> return c - (Just _, Nothing) -> use "encryption setup" $ genCipher c - (Just _, Just v) -> use "encryption updated" $ updateCipher c v +encryptionSetup c = case (M.lookup "encryption" c, extractCipher c) of + (Nothing, Nothing) -> error "Specify encryption=key or encryption=none or encryption=shared" + (Just "none", Nothing) -> return c + (Nothing, Just _) -> return c + (Just "shared", Just (SharedCipher _)) -> return c + (Just "none", Just _) -> cannotchange + (Just "shared", Just (EncryptedCipher _ _)) -> cannotchange + (Just _, Just (SharedCipher _)) -> cannotchange + (Just "shared", Nothing) -> use "encryption setup" $ genSharedCipher + (Just keyid, Nothing) -> use "encryption setup" $ genEncryptedCipher keyid + (Just keyid, Just v) -> use "encryption updated" $ updateEncryptedCipher keyid v where + cannotchange = error "Cannot change encryption type of existing remote." use m a = do cipher <- liftIO a showNote $ m ++ " " ++ describeCipher cipher @@ -78,7 +83,7 @@ remoteCipher c = go $ extractCipher c Nothing -> decrypt encipher cache decrypt encipher cache = do showNote "gpg" - cipher <- liftIO $ decryptCipher c encipher + cipher <- liftIO $ decryptCipher encipher Annex.changeState (\s -> s { Annex.ciphers = M.insert encipher cipher cache }) return $ Just cipher diff --git a/Types/Crypto.hs b/Types/Crypto.hs index 686bf5c1a6..135522ba11 100644 --- a/Types/Crypto.hs +++ b/Types/Crypto.hs @@ -1,13 +1,13 @@ {- git-annex crypto types - - - Copyright 2011 Joey Hess + - Copyright 2011-2012 Joey Hess - - Licensed under the GNU GPL version 3 or higher. -} module Types.Crypto ( Cipher(..), - EncryptedCipher(..), + StorableCipher(..), KeyIds(..), ) where @@ -16,5 +16,5 @@ import Utility.Gpg (KeyIds(..)) -- XXX ideally, this would be a locked memory region newtype Cipher = Cipher String -data EncryptedCipher = EncryptedCipher String KeyIds +data StorableCipher = EncryptedCipher String KeyIds | SharedCipher String deriving (Ord, Eq) diff --git a/Utility/Gpg.hs b/Utility/Gpg.hs index 4c798f2732..ff6735ba57 100644 --- a/Utility/Gpg.hs +++ b/Utility/Gpg.hs @@ -94,7 +94,18 @@ findPubKeys for = KeyIds . parse <$> readStrict params pubKey = isPrefixOf "pub:" keyIdField s = split ":" s !! 4 - +{- Creates a block of high-quality random data suitable to use as a cipher. + - It is armored, to avoid newlines, since gpg only reads ciphers up to the + - first newline. -} +genRandom :: Int -> IO String +genRandom size = readStrict + [ Params "--gen-random --armor" + , Param $ show randomquality + , Param $ show size + ] + where + -- 1 is /dev/urandom; 2 is /dev/random + randomquality = 1 :: Int {- A test key. This is provided pre-generated since generating a new gpg - key is too much work (requires too much entropy) for a test suite to diff --git a/debian/changelog b/debian/changelog index 06990a0f8a..7a9b5c6eab 100644 --- a/debian/changelog +++ b/debian/changelog @@ -7,6 +7,9 @@ git-annex (3.20120419) UNRELEASED; urgency=low settings, to allow custom headers to be sent with all HTTP requests. (Requested by the Internet Archive) * uninit: Clear annex.uuid from .git/config. Closes: #670639 + * Added shared cipher mode to encryptable special remotes. This option + avoids gpg key distribution, at the expense of flexability, and with + the requirement that all clones of the git repository be equally trusted. -- Joey Hess Fri, 20 Apr 2012 16:14:08 -0400 diff --git a/doc/encryption.mdwn b/doc/encryption.mdwn index 0f83bb7f90..cc61fea6f7 100644 --- a/doc/encryption.mdwn +++ b/doc/encryption.mdwn @@ -33,3 +33,16 @@ Note that once a key has been given access to a remote, it's not possible to revoke that access, short of deleting the remote. See [[encryption_design|design/encryption]] for other security risks associated with encryption. + +## shared cipher mode + +Alternatively, you can configure git-annex to use a shared cipher to +encrypt data stored in a remote. This shared cipher is stored, +**unencrypted** in the git repository. So it's shared amoung every +clone of the git repository. The advantage is you don't need to set up gpg +keys. The disadvantage is that this is **insecure** unless you +trust every clone of the git repository with access to the encrypted data +stored in the special remote. + +To use shared encryption, specify "encryption=shared" when first setting +up a special remote.