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.
This commit is contained in:
Joey Hess 2012-04-29 14:02:18 -04:00
parent d7a4a9a66b
commit 1c16f616df
7 changed files with 79 additions and 58 deletions

View file

@ -90,7 +90,7 @@ data AnnexState = AnnexState
, shared :: Maybe SharedRepository , shared :: Maybe SharedRepository
, forcetrust :: TrustMap , forcetrust :: TrustMap
, trustmap :: Maybe TrustMap , trustmap :: Maybe TrustMap
, ciphers :: M.Map EncryptedCipher Cipher , ciphers :: M.Map StorableCipher Cipher
, lockpool :: M.Map FilePath Fd , lockpool :: M.Map FilePath Fd
, flags :: M.Map String Bool , flags :: M.Map String Bool
, fields :: M.Map String String , fields :: M.Map String String

View file

@ -3,16 +3,17 @@
- Currently using gpg; could later be modified to support different - Currently using gpg; could later be modified to support different
- crypto backends if neccessary. - crypto backends if neccessary.
- -
- Copyright 2011 Joey Hess <joey@kitenet.net> - Copyright 2011-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Crypto ( module Crypto (
Cipher, Cipher,
EncryptedCipher, StorableCipher(..),
genCipher, genEncryptedCipher,
updateCipher, genSharedCipher,
updateEncryptedCipher,
describeCipher, describeCipher,
storeCipher, storeCipher,
extractCipher, extractCipher,
@ -60,59 +61,55 @@ cipherPassphrase (Cipher c) = drop cipherHalf c
cipherHmac :: Cipher -> String cipherHmac :: Cipher -> String
cipherHmac (Cipher c) = take cipherHalf c cipherHmac (Cipher c) = take cipherHalf c
{- Creates a new Cipher, encrypted as specified in the remote's configuration -} {- Creates a new Cipher, encrypted to the specificed key id. -}
genCipher :: RemoteConfig -> IO EncryptedCipher genEncryptedCipher :: String -> IO StorableCipher
genCipher c = do genEncryptedCipher keyid = do
ks <- configKeyIds c ks <- Gpg.findPubKeys keyid
random <- genrandom random <- Gpg.genRandom cipherSize
encryptCipher (Cipher random) ks 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 {- Creates a new, shared Cipher. -}
- the remote's configuration. -} genSharedCipher :: IO StorableCipher
updateCipher :: RemoteConfig -> EncryptedCipher -> IO EncryptedCipher genSharedCipher = SharedCipher <$> Gpg.genRandom cipherSize
updateCipher c encipher@(EncryptedCipher _ ks) = do
ks' <- configKeyIds c {- Updates an existing Cipher, re-encrypting it to add a keyid. -}
cipher <- decryptCipher c encipher 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') encryptCipher cipher (merge ks ks')
where where
merge (KeyIds a) (KeyIds b) = KeyIds $ a ++ b merge (KeyIds a) (KeyIds b) = KeyIds $ a ++ b
describeCipher :: EncryptedCipher -> String describeCipher :: StorableCipher -> String
describeCipher (SharedCipher _) = "shared cipher"
describeCipher (EncryptedCipher _ (KeyIds ks)) = describeCipher (EncryptedCipher _ (KeyIds ks)) =
"with gpg " ++ keys ks ++ " " ++ unwords ks "with gpg " ++ keys ks ++ " " ++ unwords ks
where where
keys [_] = "key" keys [_] = "key"
keys _ = "keys" keys _ = "keys"
{- Stores an EncryptedCipher in a remote's configuration. -} {- Stores an StorableCipher in a remote's configuration. -}
storeCipher :: RemoteConfig -> EncryptedCipher -> RemoteConfig storeCipher :: RemoteConfig -> StorableCipher -> RemoteConfig
storeCipher c (SharedCipher t) = M.insert "cipher" (toB64 t) c
storeCipher c (EncryptedCipher t ks) = storeCipher c (EncryptedCipher t ks) =
M.insert "cipher" (toB64 t) $ M.insert "cipherkeys" (showkeys ks) c M.insert "cipher" (toB64 t) $ M.insert "cipherkeys" (showkeys ks) c
where where
showkeys (KeyIds l) = join "," l showkeys (KeyIds l) = join "," l
{- Extracts an EncryptedCipher from a remote's configuration. -} {- Extracts an StorableCipher from a remote's configuration. -}
extractCipher :: RemoteConfig -> Maybe EncryptedCipher extractCipher :: RemoteConfig -> Maybe StorableCipher
extractCipher c = extractCipher c =
case (M.lookup "cipher" c, M.lookup "cipherkeys" c) of case (M.lookup "cipher" c, M.lookup "cipherkeys" c) of
(Just t, Just ks) -> Just $ EncryptedCipher (fromB64 t) (readkeys ks) (Just t, Just ks) -> Just $ EncryptedCipher (fromB64 t) (readkeys ks)
(Just t, Nothing) -> Just $ SharedCipher (fromB64 t)
_ -> Nothing _ -> Nothing
where where
readkeys = KeyIds . split "," readkeys = KeyIds . split ","
{- Encrypts a Cipher to the specified KeyIds. -} {- Encrypts a Cipher to the specified KeyIds. -}
encryptCipher :: Cipher -> KeyIds -> IO EncryptedCipher encryptCipher :: Cipher -> KeyIds -> IO StorableCipher
encryptCipher (Cipher c) (KeyIds ks) = do encryptCipher (Cipher c) (KeyIds ks) = do
let ks' = nub $ sort ks -- gpg complains about duplicate recipient keyids let ks' = nub $ sort ks -- gpg complains about duplicate recipient keyids
encipher <- Gpg.pipeStrict (encrypt++recipients ks') c 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" force_recipients = Params "--no-encrypt-to --no-default-recipient"
{- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -} {- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -}
decryptCipher :: RemoteConfig -> EncryptedCipher -> IO Cipher decryptCipher :: StorableCipher -> IO Cipher
decryptCipher _ (EncryptedCipher encipher _) = decryptCipher (SharedCipher t) = return $ Cipher t
Cipher <$> Gpg.pipeStrict decrypt encipher decryptCipher (EncryptedCipher t _) = Cipher <$> Gpg.pipeStrict decrypt t
where where
decrypt = [ Param "--decrypt" ] 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 -> Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a
pass to c i a = to c i $ \h -> a =<< L.hGetContents h 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 :: Cipher -> String -> String
hmacWithCipher c = hmacWithCipher' (cipherHmac c) hmacWithCipher c = hmacWithCipher' (cipherHmac c)
hmacWithCipher' :: String -> String -> String hmacWithCipher' :: String -> String -> String

View file

@ -17,17 +17,22 @@ import Config
{- Encryption setup for a remote. The user must specify whether to use {- 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 - 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 :: RemoteConfig -> Annex RemoteConfig
encryptionSetup c = encryptionSetup c = case (M.lookup "encryption" c, extractCipher c) of
case (M.lookup "encryption" c, extractCipher c) of (Nothing, Nothing) -> error "Specify encryption=key or encryption=none or encryption=shared"
(Nothing, Nothing) -> error "Specify encryption=key or encryption=none" (Just "none", Nothing) -> return c
(Just "none", Nothing) -> return c (Nothing, Just _) -> return c
(Just "none", Just _) -> error "Cannot change encryption type of existing remote." (Just "shared", Just (SharedCipher _)) -> return c
(Nothing, Just _) -> return c (Just "none", Just _) -> cannotchange
(Just _, Nothing) -> use "encryption setup" $ genCipher c (Just "shared", Just (EncryptedCipher _ _)) -> cannotchange
(Just _, Just v) -> use "encryption updated" $ updateCipher c v (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 where
cannotchange = error "Cannot change encryption type of existing remote."
use m a = do use m a = do
cipher <- liftIO a cipher <- liftIO a
showNote $ m ++ " " ++ describeCipher cipher showNote $ m ++ " " ++ describeCipher cipher
@ -78,7 +83,7 @@ remoteCipher c = go $ extractCipher c
Nothing -> decrypt encipher cache Nothing -> decrypt encipher cache
decrypt encipher cache = do decrypt encipher cache = do
showNote "gpg" showNote "gpg"
cipher <- liftIO $ decryptCipher c encipher cipher <- liftIO $ decryptCipher encipher
Annex.changeState (\s -> s { Annex.ciphers = M.insert encipher cipher cache }) Annex.changeState (\s -> s { Annex.ciphers = M.insert encipher cipher cache })
return $ Just cipher return $ Just cipher

View file

@ -1,13 +1,13 @@
{- git-annex crypto types {- git-annex crypto types
- -
- Copyright 2011 Joey Hess <joey@kitenet.net> - Copyright 2011-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Types.Crypto ( module Types.Crypto (
Cipher(..), Cipher(..),
EncryptedCipher(..), StorableCipher(..),
KeyIds(..), KeyIds(..),
) where ) where
@ -16,5 +16,5 @@ import Utility.Gpg (KeyIds(..))
-- XXX ideally, this would be a locked memory region -- XXX ideally, this would be a locked memory region
newtype Cipher = Cipher String newtype Cipher = Cipher String
data EncryptedCipher = EncryptedCipher String KeyIds data StorableCipher = EncryptedCipher String KeyIds | SharedCipher String
deriving (Ord, Eq) deriving (Ord, Eq)

View file

@ -94,7 +94,18 @@ findPubKeys for = KeyIds . parse <$> readStrict params
pubKey = isPrefixOf "pub:" pubKey = isPrefixOf "pub:"
keyIdField s = split ":" s !! 4 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 {- 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 - key is too much work (requires too much entropy) for a test suite to

3
debian/changelog vendored
View file

@ -7,6 +7,9 @@ git-annex (3.20120419) UNRELEASED; urgency=low
settings, to allow custom headers to be sent with all HTTP requests. settings, to allow custom headers to be sent with all HTTP requests.
(Requested by the Internet Archive) (Requested by the Internet Archive)
* uninit: Clear annex.uuid from .git/config. Closes: #670639 * 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 <joeyh@debian.org> Fri, 20 Apr 2012 16:14:08 -0400 -- Joey Hess <joeyh@debian.org> Fri, 20 Apr 2012 16:14:08 -0400

View file

@ -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 possible to revoke that access, short of deleting the remote. See
[[encryption_design|design/encryption]] for other security risks [[encryption_design|design/encryption]] for other security risks
associated with encryption. 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.