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
|
, 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
|
||||||
|
|
75
Crypto.hs
75
Crypto.hs
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
(Just "none", Just _) -> error "Cannot change encryption type of existing remote."
|
|
||||||
(Nothing, Just _) -> return c
|
(Nothing, Just _) -> return c
|
||||||
(Just _, Nothing) -> use "encryption setup" $ genCipher c
|
(Just "shared", Just (SharedCipher _)) -> return c
|
||||||
(Just _, Just v) -> use "encryption updated" $ updateCipher c v
|
(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
|
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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
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.
|
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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue