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:
parent
d7a4a9a66b
commit
1c16f616df
7 changed files with 79 additions and 58 deletions
2
Annex.hs
2
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
|
||||
|
|
75
Crypto.hs
75
Crypto.hs
|
@ -3,16 +3,17 @@
|
|||
- Currently using gpg; could later be modified to support different
|
||||
- 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.
|
||||
-}
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{- 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.
|
||||
-}
|
||||
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
3
debian/changelog
vendored
3
debian/changelog
vendored
|
@ -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 <joeyh@debian.org> Fri, 20 Apr 2012 16:14:08 -0400
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue