Merge branch 'encryption'
This commit is contained in:
commit
fc7b5cfe7d
30 changed files with 448 additions and 242 deletions
2
Creds.hs
2
Creds.hs
|
@ -52,7 +52,7 @@ setRemoteCredPair c storage = go =<< getRemoteCredPair c storage
|
||||||
return c
|
return c
|
||||||
|
|
||||||
storeconfig creds key (Just cipher) = do
|
storeconfig creds key (Just cipher) = do
|
||||||
s <- liftIO $ encrypt (GpgOpts []) cipher
|
s <- liftIO $ encrypt [] cipher
|
||||||
(feedBytes $ L.pack $ encodeCredPair creds)
|
(feedBytes $ L.pack $ encodeCredPair creds)
|
||||||
(readBytes $ return . L.unpack)
|
(readBytes $ return . L.unpack)
|
||||||
return $ M.insert key (toB64 s) c
|
return $ M.insert key (toB64 s) c
|
||||||
|
|
110
Crypto.hs
110
Crypto.hs
|
@ -8,6 +8,8 @@
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
|
||||||
module Crypto (
|
module Crypto (
|
||||||
Cipher,
|
Cipher,
|
||||||
KeyIds(..),
|
KeyIds(..),
|
||||||
|
@ -23,8 +25,7 @@ module Crypto (
|
||||||
readBytes,
|
readBytes,
|
||||||
encrypt,
|
encrypt,
|
||||||
decrypt,
|
decrypt,
|
||||||
GpgOpts(..),
|
getGpgEncParams,
|
||||||
getGpgOpts,
|
|
||||||
|
|
||||||
prop_HmacSha1WithCipher_sane
|
prop_HmacSha1WithCipher_sane
|
||||||
) where
|
) where
|
||||||
|
@ -32,17 +33,18 @@ module Crypto (
|
||||||
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 Control.Applicative
|
import Control.Applicative
|
||||||
|
import qualified Data.Map as M
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import qualified Utility.Gpg as Gpg
|
import qualified Utility.Gpg as Gpg
|
||||||
import Utility.Gpg.Types
|
|
||||||
import Types.Key
|
import Types.Key
|
||||||
import Types.Crypto
|
import Types.Crypto
|
||||||
|
import Types.Remote
|
||||||
|
|
||||||
{- The beginning of a Cipher is used for MAC'ing; the remainder is used
|
{- The beginning of a Cipher is used for MAC'ing; the remainder is used
|
||||||
- as the GPG symmetric encryption passphrase. Note that the cipher
|
- as the GPG symmetric encryption passphrase when using the hybrid
|
||||||
- itself is base-64 encoded, hence the string is longer than
|
- scheme. Note that the cipher itself is base-64 encoded, hence the
|
||||||
- 'cipherSize': 683 characters, padded to 684.
|
- string is longer than 'cipherSize': 683 characters, padded to 684.
|
||||||
-
|
-
|
||||||
- The 256 first characters that feed the MAC represent at best 192
|
- The 256 first characters that feed the MAC represent at best 192
|
||||||
- bytes of entropy. However that's more than enough for both the
|
- bytes of entropy. However that's more than enough for both the
|
||||||
|
@ -67,53 +69,63 @@ cipherMac :: Cipher -> String
|
||||||
cipherMac (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 -> Bool -> IO StorableCipher
|
genEncryptedCipher :: String -> EncryptedCipherVariant -> Bool -> IO StorableCipher
|
||||||
genEncryptedCipher keyid highQuality = do
|
genEncryptedCipher keyid variant highQuality = do
|
||||||
ks <- Gpg.findPubKeys keyid
|
ks <- Gpg.findPubKeys keyid
|
||||||
random <- Gpg.genRandom highQuality cipherSize
|
random <- Gpg.genRandom highQuality size
|
||||||
encryptCipher (Cipher random) ks
|
encryptCipher (Cipher random) variant ks
|
||||||
|
where
|
||||||
|
size = case variant of
|
||||||
|
HybridCipher -> cipherSize -- used for MAC + symmetric
|
||||||
|
PubKeyCipher -> cipherBeginning -- only used for MAC
|
||||||
|
|
||||||
{- Creates a new, shared Cipher. -}
|
{- Creates a new, shared Cipher. -}
|
||||||
genSharedCipher :: Bool -> IO StorableCipher
|
genSharedCipher :: Bool -> IO StorableCipher
|
||||||
genSharedCipher highQuality =
|
genSharedCipher highQuality =
|
||||||
SharedCipher <$> Gpg.genRandom highQuality cipherSize
|
SharedCipher <$> Gpg.genRandom highQuality cipherSize
|
||||||
|
|
||||||
{- Updates an existing Cipher, re-encrypting it to add a keyid. -}
|
{- Updates an existing Cipher, re-encrypting it to add or remove keyids,
|
||||||
updateEncryptedCipher :: String -> StorableCipher -> IO StorableCipher
|
- depending on whether the first component is True or False. -}
|
||||||
updateEncryptedCipher _ (SharedCipher _) = undefined
|
updateEncryptedCipher :: [(Bool, String)] -> StorableCipher -> IO StorableCipher
|
||||||
updateEncryptedCipher keyid encipher@(EncryptedCipher _ ks) = do
|
updateEncryptedCipher _ SharedCipher{} = undefined
|
||||||
ks' <- Gpg.findPubKeys keyid
|
updateEncryptedCipher [] encipher = return encipher
|
||||||
|
updateEncryptedCipher newkeys encipher@(EncryptedCipher _ symmetric (KeyIds ks)) = do
|
||||||
|
dropKeys <- listKeyIds [ k | (False, k) <- newkeys ]
|
||||||
|
forM_ dropKeys $ \k -> unless (k `elem` ks) $
|
||||||
|
error $ "Key " ++ k ++ " was not present; cannot remove."
|
||||||
|
addKeys <- listKeyIds [ k | (True, k) <- newkeys ]
|
||||||
|
let ks' = (addKeys ++ ks) \\ dropKeys
|
||||||
|
when (null ks') $
|
||||||
|
error "Cannot remove the last key."
|
||||||
cipher <- decryptCipher encipher
|
cipher <- decryptCipher encipher
|
||||||
encryptCipher cipher (merge ks ks')
|
encryptCipher cipher symmetric $ KeyIds ks'
|
||||||
where
|
where
|
||||||
merge (KeyIds a) (KeyIds b) = KeyIds $ a ++ b
|
listKeyIds = mapM (Gpg.findPubKeys >=*> keyIds) >=*> concat
|
||||||
|
|
||||||
describeCipher :: StorableCipher -> String
|
describeCipher :: StorableCipher -> String
|
||||||
describeCipher (SharedCipher _) = "shared cipher"
|
describeCipher (SharedCipher _) = "shared cipher"
|
||||||
describeCipher (EncryptedCipher _ (KeyIds ks)) =
|
describeCipher (EncryptedCipher _ variant (KeyIds ks)) =
|
||||||
"with gpg " ++ keys ks ++ " " ++ unwords ks
|
scheme ++ " with gpg " ++ keys ks ++ " " ++ unwords ks
|
||||||
where
|
where
|
||||||
|
scheme = case variant of
|
||||||
|
HybridCipher -> "hybrid cipher"
|
||||||
|
PubKeyCipher -> "pubkey crypto"
|
||||||
keys [_] = "key"
|
keys [_] = "key"
|
||||||
keys _ = "keys"
|
keys _ = "keys"
|
||||||
|
|
||||||
{- Encrypts a Cipher to the specified KeyIds. -}
|
{- Encrypts a Cipher to the specified KeyIds. -}
|
||||||
encryptCipher :: Cipher -> KeyIds -> IO StorableCipher
|
encryptCipher :: Cipher -> EncryptedCipherVariant -> KeyIds -> IO StorableCipher
|
||||||
encryptCipher (Cipher c) (KeyIds ks) = do
|
encryptCipher (Cipher c) variant (KeyIds ks) = do
|
||||||
-- gpg complains about duplicate recipient keyids
|
-- gpg complains about duplicate recipient keyids
|
||||||
let ks' = nub $ sort ks
|
let ks' = nub $ sort ks
|
||||||
encipher <- Gpg.pipeStrict (Params "--encrypt" : recipients ks') c
|
let params = Gpg.pkEncTo ks' ++ Gpg.stdEncryptionParams False
|
||||||
return $ EncryptedCipher encipher (KeyIds ks')
|
encipher <- Gpg.pipeStrict params c
|
||||||
where
|
return $ EncryptedCipher encipher variant (KeyIds ks')
|
||||||
recipients l = force_recipients :
|
|
||||||
concatMap (\k -> [Param "--recipient", Param k]) l
|
|
||||||
-- Force gpg to only encrypt to the specified
|
|
||||||
-- recipients, not configured defaults.
|
|
||||||
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 :: StorableCipher -> IO Cipher
|
decryptCipher :: StorableCipher -> IO Cipher
|
||||||
decryptCipher (SharedCipher t) = return $ Cipher t
|
decryptCipher (SharedCipher t) = return $ Cipher t
|
||||||
decryptCipher (EncryptedCipher t _) =
|
decryptCipher (EncryptedCipher t _ _) =
|
||||||
Cipher <$> Gpg.pipeStrict [ Param "--decrypt" ] t
|
Cipher <$> Gpg.pipeStrict [ Param "--decrypt" ] 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
|
||||||
|
@ -139,15 +151,21 @@ feedBytes = flip L.hPut
|
||||||
readBytes :: (L.ByteString -> IO a) -> Reader a
|
readBytes :: (L.ByteString -> IO a) -> Reader a
|
||||||
readBytes a h = L.hGetContents h >>= a
|
readBytes a h = L.hGetContents h >>= a
|
||||||
|
|
||||||
{- Runs a Feeder action, that generates content that is symmetrically encrypted
|
{- Runs a Feeder action, that generates content that is symmetrically
|
||||||
- with the Cipher using the given GnuPG options, and then read by the Reader
|
- encrypted with the Cipher (unless it is empty, in which case
|
||||||
- action. -}
|
- public-key encryption is used) using the given gpg options, and then
|
||||||
encrypt :: GpgOpts -> Cipher -> Feeder -> Reader a -> IO a
|
- read by the Reader action. Note: For public-key encryption,
|
||||||
encrypt opts = Gpg.feedRead ( Params "--symmetric --force-mdc" : toParams opts )
|
- recipients MUST be included in 'params' (for instance using
|
||||||
. cipherPassphrase
|
- 'getGpgEncParams'). -}
|
||||||
|
encrypt :: [CommandParam] -> Cipher -> Feeder -> Reader a -> IO a
|
||||||
|
encrypt params cipher = Gpg.feedRead params' pass
|
||||||
|
where
|
||||||
|
pass = cipherPassphrase cipher
|
||||||
|
params' = params ++ Gpg.stdEncryptionParams (not $ null pass)
|
||||||
|
|
||||||
{- Runs a Feeder action, that generates content that is decrypted with the
|
{- Runs a Feeder action, that generates content that is decrypted with the
|
||||||
- Cipher, and read by the Reader action. -}
|
- Cipher (or using a private key if the Cipher is empty), and read by the
|
||||||
|
- Reader action. -}
|
||||||
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
|
||||||
|
|
||||||
|
@ -161,3 +179,21 @@ prop_HmacSha1WithCipher_sane :: Bool
|
||||||
prop_HmacSha1WithCipher_sane = known_good == macWithCipher' HmacSha1 "foo" "bar"
|
prop_HmacSha1WithCipher_sane = known_good == macWithCipher' HmacSha1 "foo" "bar"
|
||||||
where
|
where
|
||||||
known_good = "46b4ec586117154dacd49d664e5d63fdc88efb51"
|
known_good = "46b4ec586117154dacd49d664e5d63fdc88efb51"
|
||||||
|
|
||||||
|
{- Return some options suitable for GnuPG encryption, symmetric or not. -}
|
||||||
|
class LensGpgEncParams a where getGpgEncParams :: a -> [CommandParam]
|
||||||
|
|
||||||
|
{- Extract the GnuPG options from a pair of a Remote Config and a Remote
|
||||||
|
- Git Config. If the remote is configured to use public-key encryption,
|
||||||
|
- look up the recipient keys and add them to the option list. -}
|
||||||
|
instance LensGpgEncParams (RemoteConfig, RemoteGitConfig) where
|
||||||
|
getGpgEncParams (c,gc) = map Param (remoteAnnexGnupgOptions gc) ++ recipients
|
||||||
|
where
|
||||||
|
recipients = case M.lookup "encryption" c of
|
||||||
|
Just "pubkey" -> Gpg.pkEncTo $ maybe [] (split ",") $
|
||||||
|
M.lookup "cipherkeys" c
|
||||||
|
_ -> []
|
||||||
|
|
||||||
|
{- Extract the GnuPG options from a Remote. -}
|
||||||
|
instance LensGpgEncParams (RemoteA a) where
|
||||||
|
getGpgEncParams r = getGpgEncParams (config r, gitconfig r)
|
||||||
|
|
|
@ -133,7 +133,7 @@ storeEncrypted r buprepo (cipher, enck) k _p =
|
||||||
sendAnnex k (rollback enck buprepo) $ \src -> do
|
sendAnnex k (rollback enck buprepo) $ \src -> do
|
||||||
params <- bupSplitParams r buprepo enck []
|
params <- bupSplitParams r buprepo enck []
|
||||||
liftIO $ catchBoolIO $
|
liftIO $ catchBoolIO $
|
||||||
encrypt (getGpgOpts r) cipher (feedFile src) $ \h ->
|
encrypt (getGpgEncParams r) cipher (feedFile src) $ \h ->
|
||||||
pipeBup params (Just h) Nothing
|
pipeBup params (Just h) Nothing
|
||||||
|
|
||||||
retrieve :: BupRepo -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex Bool
|
retrieve :: BupRepo -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex Bool
|
||||||
|
|
|
@ -41,7 +41,7 @@ gen r u c gc = do
|
||||||
cst <- remoteCost gc cheapRemoteCost
|
cst <- remoteCost gc cheapRemoteCost
|
||||||
let chunksize = chunkSize c
|
let chunksize = chunkSize c
|
||||||
return $ encryptableRemote c
|
return $ encryptableRemote c
|
||||||
(storeEncrypted dir (getGpgOpts gc) chunksize)
|
(storeEncrypted dir (getGpgEncParams (c,gc)) chunksize)
|
||||||
(retrieveEncrypted dir chunksize)
|
(retrieveEncrypted dir chunksize)
|
||||||
Remote {
|
Remote {
|
||||||
uuid = u,
|
uuid = u,
|
||||||
|
@ -129,7 +129,7 @@ store d chunksize k _f p = sendAnnex k (void $ remove d k) $ \src ->
|
||||||
storeSplit meterupdate chunksize dests
|
storeSplit meterupdate chunksize dests
|
||||||
=<< L.readFile src
|
=<< L.readFile src
|
||||||
|
|
||||||
storeEncrypted :: FilePath -> GpgOpts -> ChunkSize -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
|
storeEncrypted :: FilePath -> [CommandParam] -> ChunkSize -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
|
||||||
storeEncrypted d gpgOpts chunksize (cipher, enck) k p = sendAnnex k (void $ remove d enck) $ \src ->
|
storeEncrypted d gpgOpts chunksize (cipher, enck) k p = sendAnnex k (void $ remove d enck) $ \src ->
|
||||||
metered (Just p) k $ \meterupdate ->
|
metered (Just p) k $ \meterupdate ->
|
||||||
storeHelper d chunksize enck k $ \dests ->
|
storeHelper d chunksize enck k $ \dests ->
|
||||||
|
|
|
@ -95,7 +95,7 @@ storeEncrypted :: Remote -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
|
||||||
storeEncrypted r (cipher, enck) k p = sendAnnex k (void $ remove r enck) $ \src -> do
|
storeEncrypted r (cipher, enck) k p = sendAnnex k (void $ remove r enck) $ \src -> do
|
||||||
metered (Just p) k $ \meterupdate ->
|
metered (Just p) k $ \meterupdate ->
|
||||||
storeHelper r enck $ \h ->
|
storeHelper r enck $ \h ->
|
||||||
encrypt (getGpgOpts r) cipher (feedFile src)
|
encrypt (getGpgEncParams r) cipher (feedFile src)
|
||||||
(readBytes $ meteredWrite meterupdate h)
|
(readBytes $ meteredWrite meterupdate h)
|
||||||
|
|
||||||
retrieve :: Remote -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex Bool
|
retrieve :: Remote -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex Bool
|
||||||
|
|
|
@ -23,30 +23,52 @@ import Utility.Metered
|
||||||
- updated to be accessible to an additional encryption key. Or the user
|
- updated to be accessible to an additional encryption key. Or the user
|
||||||
- could opt to use a shared cipher, which is stored unencrypted. -}
|
- could opt to use a shared cipher, which is stored unencrypted. -}
|
||||||
encryptionSetup :: RemoteConfig -> Annex RemoteConfig
|
encryptionSetup :: RemoteConfig -> Annex RemoteConfig
|
||||||
encryptionSetup c = case (M.lookup "encryption" c, extractCipher c) of
|
encryptionSetup c = maybe genCipher updateCipher $ extractCipher c
|
||||||
(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
|
|
||||||
=<< highRandomQuality
|
|
||||||
(Just keyid, Nothing) -> use "encryption setup" . genEncryptedCipher keyid
|
|
||||||
=<< highRandomQuality
|
|
||||||
(Just keyid, Just v) -> use "encryption update" $ updateEncryptedCipher keyid v
|
|
||||||
where
|
where
|
||||||
cannotchange = error "Cannot change encryption type of existing remote."
|
-- The type of encryption
|
||||||
|
encryption = M.lookup "encryption" c
|
||||||
|
-- Generate a new cipher, depending on the chosen encryption scheme
|
||||||
|
genCipher = case encryption of
|
||||||
|
_ | M.member "cipher" c || M.member "cipherkeys" c -> cannotchange
|
||||||
|
Just "none" -> return c
|
||||||
|
Just "shared" -> use "encryption setup" . genSharedCipher
|
||||||
|
=<< highRandomQuality
|
||||||
|
-- hybrid encryption is the default when a keyid is
|
||||||
|
-- specified but no encryption
|
||||||
|
_ | maybe (M.member "keyid" c) (== "hybrid") encryption ->
|
||||||
|
use "encryption setup" . genEncryptedCipher key HybridCipher
|
||||||
|
=<< highRandomQuality
|
||||||
|
Just "pubkey" -> use "encryption setup" . genEncryptedCipher key PubKeyCipher
|
||||||
|
=<< highRandomQuality
|
||||||
|
_ -> error $ "Specify " ++ intercalate " or "
|
||||||
|
(map ("encryption=" ++)
|
||||||
|
["none","shared","hybrid","pubkey"])
|
||||||
|
++ "."
|
||||||
|
key = fromMaybe (error "Specifiy keyid=...") $ M.lookup "keyid" c
|
||||||
|
newkeys = maybe [] (\k -> [(True,k)]) (M.lookup "keyid+" c) ++
|
||||||
|
maybe [] (\k -> [(False,k)]) (M.lookup "keyid-" c)
|
||||||
|
cannotchange = error "Cannot set encryption type of existing remotes."
|
||||||
|
-- Update an existing cipher if possible.
|
||||||
|
updateCipher v = case v of
|
||||||
|
SharedCipher _ | maybe True (== "shared") encryption -> return c'
|
||||||
|
EncryptedCipher _ variant _
|
||||||
|
| maybe True (== if variant == HybridCipher then "hybrid" else "pubkey") encryption ->
|
||||||
|
use "encryption update" $ updateEncryptedCipher newkeys v
|
||||||
|
_ -> cannotchange
|
||||||
use m a = do
|
use m a = do
|
||||||
showNote m
|
showNote m
|
||||||
cipher <- liftIO a
|
cipher <- liftIO a
|
||||||
showNote $ describeCipher cipher
|
showNote $ describeCipher cipher
|
||||||
return $ M.delete "encryption" $ M.delete "highRandomQuality" $
|
return $ storeCipher c' cipher
|
||||||
storeCipher c cipher
|
|
||||||
highRandomQuality =
|
highRandomQuality =
|
||||||
(&&) (maybe True ( /= "false") $ M.lookup "highRandomQuality" c)
|
(&&) (maybe True ( /= "false") $ M.lookup "highRandomQuality" c)
|
||||||
<$> fmap not (Annex.getState Annex.fast)
|
<$> fmap not (Annex.getState Annex.fast)
|
||||||
|
c' = foldr M.delete c
|
||||||
|
-- git-annex used to remove 'encryption' as well, since
|
||||||
|
-- it was redundant; we now need to keep it for
|
||||||
|
-- public-key incryption, hence we leave it on newer
|
||||||
|
-- remotes (while being backward-compatible).
|
||||||
|
[ "keyid", "keyid+", "keyid-", "highRandomQuality" ]
|
||||||
|
|
||||||
{- Modifies a Remote to support encryption.
|
{- Modifies a Remote to support encryption.
|
||||||
-
|
-
|
||||||
|
@ -111,27 +133,39 @@ embedCreds c
|
||||||
| isJust (M.lookup "cipherkeys" c) && isJust (M.lookup "cipher" c) = True
|
| isJust (M.lookup "cipherkeys" c) && isJust (M.lookup "cipher" c) = True
|
||||||
| otherwise = False
|
| otherwise = False
|
||||||
|
|
||||||
{- Gets encryption Cipher, and encrypted version of Key. -}
|
{- Gets encryption Cipher, and encrypted version of Key. In case we want
|
||||||
|
- asymmetric encryption, leave the first empty, but encrypt the Key
|
||||||
|
- regardless. (Empty ciphers imply asymmetric encryption.) We could
|
||||||
|
- also check how long is the cipher (MAC'ing-only ciphers are shorter),
|
||||||
|
- but we don't want to rely on that only. -}
|
||||||
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 = fmap make <$> remoteCipher c
|
||||||
where
|
where
|
||||||
make ciphertext = Just (ciphertext, encryptKey mac ciphertext k)
|
make ciphertext = (cipContent ciphertext, encryptKey mac ciphertext k)
|
||||||
|
cipContent
|
||||||
|
| M.lookup "encryption" c /= Just "pubkey" = id
|
||||||
|
| otherwise = const $ Cipher ""
|
||||||
mac = fromMaybe defaultMac $ M.lookup "mac" c >>= readMac
|
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
|
||||||
storeCipher c (SharedCipher t) = M.insert "cipher" (toB64 t) c
|
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) = intercalate "," l
|
showkeys (KeyIds l) = intercalate "," l
|
||||||
|
|
||||||
{- Extracts an StorableCipher from a remote's configuration. -}
|
{- Extracts an StorableCipher from a remote's configuration. -}
|
||||||
extractCipher :: RemoteConfig -> Maybe StorableCipher
|
extractCipher :: RemoteConfig -> Maybe StorableCipher
|
||||||
extractCipher c =
|
extractCipher c = case (M.lookup "cipher" c,
|
||||||
case (M.lookup "cipher" c, M.lookup "cipherkeys" c) of
|
M.lookup "cipherkeys" c,
|
||||||
(Just t, Just ks) -> Just $ EncryptedCipher (fromB64 t) (readkeys ks)
|
M.lookup "encryption" c) of
|
||||||
(Just t, Nothing) -> Just $ SharedCipher (fromB64 t)
|
(Just t, Just ks, encryption) | maybe True (== "hybrid") encryption ->
|
||||||
_ -> Nothing
|
Just $ EncryptedCipher (fromB64 t) HybridCipher (readkeys ks)
|
||||||
|
(Just t, Just ks, Just "pubkey") ->
|
||||||
|
Just $ EncryptedCipher (fromB64 t) PubKeyCipher (readkeys ks)
|
||||||
|
(Just t, Nothing, encryption) | maybe True (== "shared") encryption ->
|
||||||
|
Just $ SharedCipher (fromB64 t)
|
||||||
|
_ -> Nothing
|
||||||
where
|
where
|
||||||
readkeys = KeyIds . split ","
|
readkeys = KeyIds . split ","
|
||||||
|
|
|
@ -38,7 +38,7 @@ gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex Remote
|
||||||
gen r u c gc = do
|
gen r u c gc = do
|
||||||
cst <- remoteCost gc expensiveRemoteCost
|
cst <- remoteCost gc expensiveRemoteCost
|
||||||
return $ encryptableRemote c
|
return $ encryptableRemote c
|
||||||
(storeEncrypted hooktype $ getGpgOpts gc)
|
(storeEncrypted hooktype $ getGpgEncParams (c,gc))
|
||||||
(retrieveEncrypted hooktype)
|
(retrieveEncrypted hooktype)
|
||||||
Remote {
|
Remote {
|
||||||
uuid = u,
|
uuid = u,
|
||||||
|
@ -118,7 +118,7 @@ store :: HookName -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool
|
||||||
store h k _f _p = sendAnnex k (void $ remove h k) $ \src ->
|
store h k _f _p = sendAnnex k (void $ remove h k) $ \src ->
|
||||||
runHook h "store" k (Just src) $ return True
|
runHook h "store" k (Just src) $ return True
|
||||||
|
|
||||||
storeEncrypted :: HookName -> GpgOpts -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
|
storeEncrypted :: HookName -> [CommandParam] -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
|
||||||
storeEncrypted h gpgOpts (cipher, enck) k _p = withTmp enck $ \tmp ->
|
storeEncrypted h gpgOpts (cipher, enck) k _p = withTmp enck $ \tmp ->
|
||||||
sendAnnex k (void $ remove h enck) $ \src -> do
|
sendAnnex k (void $ remove h enck) $ \src -> do
|
||||||
liftIO $ encrypt gpgOpts cipher (feedFile src) $
|
liftIO $ encrypt gpgOpts cipher (feedFile src) $
|
||||||
|
|
|
@ -55,7 +55,7 @@ gen r u c gc = do
|
||||||
let o = RsyncOpts url (transport ++ opts) escape
|
let o = RsyncOpts url (transport ++ opts) escape
|
||||||
islocal = rsyncUrlIsPath $ rsyncUrl o
|
islocal = rsyncUrlIsPath $ rsyncUrl o
|
||||||
return $ encryptableRemote c
|
return $ encryptableRemote c
|
||||||
(storeEncrypted o $ getGpgOpts gc)
|
(storeEncrypted o $ getGpgEncParams (c,gc))
|
||||||
(retrieveEncrypted o)
|
(retrieveEncrypted o)
|
||||||
Remote
|
Remote
|
||||||
{ uuid = u
|
{ uuid = u
|
||||||
|
@ -137,7 +137,7 @@ rsyncUrls o k = map use annexHashes
|
||||||
store :: RsyncOpts -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool
|
store :: RsyncOpts -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool
|
||||||
store o k _f p = sendAnnex k (void $ remove o k) $ rsyncSend o p k False
|
store o k _f p = sendAnnex k (void $ remove o k) $ rsyncSend o p k False
|
||||||
|
|
||||||
storeEncrypted :: RsyncOpts -> GpgOpts -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
|
storeEncrypted :: RsyncOpts -> [CommandParam] -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
|
||||||
storeEncrypted o gpgOpts (cipher, enck) k p = withTmp enck $ \tmp ->
|
storeEncrypted o gpgOpts (cipher, enck) k p = withTmp enck $ \tmp ->
|
||||||
sendAnnex k (void $ remove o enck) $ \src -> do
|
sendAnnex k (void $ remove o enck) $ \src -> do
|
||||||
liftIO $ encrypt gpgOpts cipher (feedFile src) $
|
liftIO $ encrypt gpgOpts cipher (feedFile src) $
|
||||||
|
|
|
@ -129,7 +129,7 @@ storeEncrypted r (cipher, enck) k p = s3Action r False $ \(conn, bucket) ->
|
||||||
-- To get file size of the encrypted content, have to use a temp file.
|
-- To get file size of the encrypted content, have to use a temp file.
|
||||||
-- (An alternative would be chunking to to a constant size.)
|
-- (An alternative would be chunking to to a constant size.)
|
||||||
withTmp enck $ \tmp -> sendAnnex k (void $ remove' r enck) $ \src -> do
|
withTmp enck $ \tmp -> sendAnnex k (void $ remove' r enck) $ \src -> do
|
||||||
liftIO $ encrypt (getGpgOpts r) cipher (feedFile src) $
|
liftIO $ encrypt (getGpgEncParams r) cipher (feedFile src) $
|
||||||
readBytes $ L.writeFile tmp
|
readBytes $ L.writeFile tmp
|
||||||
s3Bool =<< storeHelper (conn, bucket) r enck p tmp
|
s3Bool =<< storeHelper (conn, bucket) r enck p tmp
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ storeEncrypted :: Remote -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
|
||||||
storeEncrypted r (cipher, enck) k p = metered (Just p) k $ \meterupdate ->
|
storeEncrypted r (cipher, enck) k p = metered (Just p) k $ \meterupdate ->
|
||||||
davAction r False $ \(baseurl, user, pass) ->
|
davAction r False $ \(baseurl, user, pass) ->
|
||||||
sendAnnex k (void $ remove r enck) $ \src ->
|
sendAnnex k (void $ remove r enck) $ \src ->
|
||||||
liftIO $ encrypt (getGpgOpts r) cipher
|
liftIO $ encrypt (getGpgEncParams r) cipher
|
||||||
(streamMeteredFile src meterupdate) $
|
(streamMeteredFile src meterupdate) $
|
||||||
readBytes $ storeHelper r enck baseurl user pass
|
readBytes $ storeHelper r enck baseurl user pass
|
||||||
|
|
||||||
|
|
52
Test.hs
52
Test.hs
|
@ -29,6 +29,7 @@ import qualified Backend
|
||||||
import qualified Git.CurrentRepo
|
import qualified Git.CurrentRepo
|
||||||
import qualified Git.Filename
|
import qualified Git.Filename
|
||||||
import qualified Locations
|
import qualified Locations
|
||||||
|
import qualified Types.Crypto
|
||||||
import qualified Types.KeySource
|
import qualified Types.KeySource
|
||||||
import qualified Types.Backend
|
import qualified Types.Backend
|
||||||
import qualified Types.TrustLevel
|
import qualified Types.TrustLevel
|
||||||
|
@ -41,6 +42,7 @@ import qualified Logs.Unused
|
||||||
import qualified Logs.Transfer
|
import qualified Logs.Transfer
|
||||||
import qualified Logs.Presence
|
import qualified Logs.Presence
|
||||||
import qualified Remote
|
import qualified Remote
|
||||||
|
import qualified Remote.Helper.Encryptable
|
||||||
import qualified Types.Key
|
import qualified Types.Key
|
||||||
import qualified Types.Messages
|
import qualified Types.Messages
|
||||||
import qualified Config
|
import qualified Config
|
||||||
|
@ -874,18 +876,21 @@ test_bup_remote env = "git-annex bup remote" ~: intmpclonerepo env $ when Build.
|
||||||
|
|
||||||
-- gpg is not a build dependency, so only test when it's available
|
-- gpg is not a build dependency, so only test when it's available
|
||||||
test_crypto :: TestEnv -> Test
|
test_crypto :: TestEnv -> Test
|
||||||
test_crypto env = "git-annex crypto" ~: intmpclonerepo env $ whenM (Utility.Path.inPath Utility.Gpg.gpgcmd) $ do
|
test_crypto env = "git-annex crypto" ~: TestList $ flip map ["shared","hybrid","pubkey"] $
|
||||||
|
\scheme -> TestCase $ intmpclonerepo env $ whenM (Utility.Path.inPath Utility.Gpg.gpgcmd) $ do
|
||||||
#ifndef mingw32_HOST_OS
|
#ifndef mingw32_HOST_OS
|
||||||
Utility.Gpg.testTestHarness @? "test harness self-test failed"
|
Utility.Gpg.testTestHarness @? "test harness self-test failed"
|
||||||
Utility.Gpg.testHarness $ do
|
Utility.Gpg.testHarness $ do
|
||||||
createDirectory "dir"
|
createDirectory "dir"
|
||||||
let a cmd = git_annex env cmd
|
let a cmd = git_annex env cmd $
|
||||||
[ "foo"
|
[ "foo"
|
||||||
, "type=directory"
|
, "type=directory"
|
||||||
, "encryption=" ++ Utility.Gpg.testKeyId
|
, "encryption=" ++ scheme
|
||||||
, "directory=dir"
|
, "directory=dir"
|
||||||
, "highRandomQuality=false"
|
, "highRandomQuality=false"
|
||||||
]
|
] ++ if scheme `elem` ["hybrid","pubkey"]
|
||||||
|
then ["keyid=" ++ Utility.Gpg.testKeyId]
|
||||||
|
else []
|
||||||
a "initremote" @? "initremote failed"
|
a "initremote" @? "initremote failed"
|
||||||
not <$> a "initremote" @? "initremote failed to fail when run twice in a row"
|
not <$> a "initremote" @? "initremote failed to fail when run twice in a row"
|
||||||
a "enableremote" @? "enableremote failed"
|
a "enableremote" @? "enableremote failed"
|
||||||
|
@ -893,6 +898,16 @@ test_crypto env = "git-annex crypto" ~: intmpclonerepo env $ whenM (Utility.Path
|
||||||
git_annex env "get" [annexedfile] @? "get of file failed"
|
git_annex env "get" [annexedfile] @? "get of file failed"
|
||||||
annexed_present annexedfile
|
annexed_present annexedfile
|
||||||
git_annex env "copy" [annexedfile, "--to", "foo"] @? "copy --to encrypted remote failed"
|
git_annex env "copy" [annexedfile, "--to", "foo"] @? "copy --to encrypted remote failed"
|
||||||
|
(c,k) <- annexeval $ do
|
||||||
|
uuid <- Remote.nameToUUID "foo"
|
||||||
|
rs <- Logs.Remote.readRemoteLog
|
||||||
|
Just (k,_) <- Backend.lookupFile annexedfile
|
||||||
|
return (fromJust $ M.lookup uuid rs, k)
|
||||||
|
let key = if scheme `elem` ["hybrid","pubkey"]
|
||||||
|
then Just $ Utility.Gpg.KeyIds [Utility.Gpg.testKeyId]
|
||||||
|
else Nothing
|
||||||
|
testEncryptedRemote scheme key c [k] @? "invalid crypto setup"
|
||||||
|
|
||||||
annexed_present annexedfile
|
annexed_present annexedfile
|
||||||
git_annex env "drop" [annexedfile, "--numcopies=2"] @? "drop failed"
|
git_annex env "drop" [annexedfile, "--numcopies=2"] @? "drop failed"
|
||||||
annexed_notpresent annexedfile
|
annexed_notpresent annexedfile
|
||||||
|
@ -900,8 +915,35 @@ test_crypto env = "git-annex crypto" ~: intmpclonerepo env $ whenM (Utility.Path
|
||||||
annexed_present annexedfile
|
annexed_present annexedfile
|
||||||
not <$> git_annex env "drop" [annexedfile, "--numcopies=2"] @? "drop failed to fail"
|
not <$> git_annex env "drop" [annexedfile, "--numcopies=2"] @? "drop failed to fail"
|
||||||
annexed_present annexedfile
|
annexed_present annexedfile
|
||||||
|
where
|
||||||
|
{- Ensure the configuration complies with the encryption scheme, and
|
||||||
|
- that all keys are encrypted properly for the given directory remote. -}
|
||||||
|
testEncryptedRemote scheme ks c keys = case Remote.Helper.Encryptable.extractCipher c of
|
||||||
|
Just cip@Crypto.SharedCipher{} | scheme == "shared" && isNothing ks ->
|
||||||
|
checkKeys cip Nothing
|
||||||
|
Just cip@(Crypto.EncryptedCipher encipher v ks')
|
||||||
|
| checkScheme v && keysMatch ks' ->
|
||||||
|
checkKeys cip (Just v) <&&> checkCipher encipher ks'
|
||||||
|
_ -> return False
|
||||||
|
where
|
||||||
|
keysMatch (Utility.Gpg.KeyIds ks') =
|
||||||
|
maybe False (\(Utility.Gpg.KeyIds ks2) ->
|
||||||
|
sort (nub ks2) == sort (nub ks')) ks
|
||||||
|
checkCipher encipher = Utility.Gpg.checkEncryptionStream encipher . Just
|
||||||
|
checkScheme Types.Crypto.HybridCipher = scheme == "hybrid"
|
||||||
|
checkScheme Types.Crypto.PubKeyCipher = scheme == "pubkey"
|
||||||
|
checkKeys cip mvariant = do
|
||||||
|
cipher <- Crypto.decryptCipher cip
|
||||||
|
files <- filterM doesFileExist $
|
||||||
|
map ("dir" </>) $ concatMap (key2files cipher) keys
|
||||||
|
return (not $ null files) <&&> allM (checkFile mvariant) files
|
||||||
|
checkFile mvariant filename =
|
||||||
|
Utility.Gpg.checkEncryptionFile filename $
|
||||||
|
if mvariant == Just Types.Crypto.PubKeyCipher then ks else Nothing
|
||||||
|
key2files cipher = Locations.keyPaths .
|
||||||
|
Crypto.encryptKey Types.Crypto.HmacSha1 cipher
|
||||||
#else
|
#else
|
||||||
putStrLn "gpg testing not implemented on Windows"
|
putStrLn "gpg testing not implemented on Windows"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
-- This is equivilant to running git-annex, but it's all run in-process
|
-- This is equivilant to running git-annex, but it's all run in-process
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
module Types.Crypto (
|
module Types.Crypto (
|
||||||
Cipher(..),
|
Cipher(..),
|
||||||
StorableCipher(..),
|
StorableCipher(..),
|
||||||
|
EncryptedCipherVariant(..),
|
||||||
KeyIds(..),
|
KeyIds(..),
|
||||||
Mac(..),
|
Mac(..),
|
||||||
readMac,
|
readMac,
|
||||||
|
@ -24,7 +25,10 @@ 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 StorableCipher = EncryptedCipher String KeyIds | SharedCipher String
|
data StorableCipher = EncryptedCipher String EncryptedCipherVariant KeyIds
|
||||||
|
| SharedCipher String
|
||||||
|
deriving (Ord, Eq)
|
||||||
|
data EncryptedCipherVariant = HybridCipher | PubKeyCipher
|
||||||
deriving (Ord, Eq)
|
deriving (Ord, Eq)
|
||||||
|
|
||||||
{- File names are (client-side) MAC'ed on special remotes.
|
{- File names are (client-side) MAC'ed on special remotes.
|
||||||
|
|
102
Utility/Gpg.hs
102
Utility/Gpg.hs
|
@ -24,7 +24,7 @@ import Utility.Env
|
||||||
import Utility.Tmp
|
import Utility.Tmp
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
newtype KeyIds = KeyIds [String]
|
newtype KeyIds = KeyIds { keyIds :: [String] }
|
||||||
deriving (Ord, Eq)
|
deriving (Ord, Eq)
|
||||||
|
|
||||||
{- If a specific gpg command was found at configure time, use it.
|
{- If a specific gpg command was found at configure time, use it.
|
||||||
|
@ -32,6 +32,10 @@ newtype KeyIds = KeyIds [String]
|
||||||
gpgcmd :: FilePath
|
gpgcmd :: FilePath
|
||||||
gpgcmd = fromMaybe "gpg" SysConfig.gpg
|
gpgcmd = fromMaybe "gpg" SysConfig.gpg
|
||||||
|
|
||||||
|
-- Generate an argument list to asymetrically encrypt to the given recipients.
|
||||||
|
pkEncTo :: [String] -> [CommandParam]
|
||||||
|
pkEncTo = concatMap (\r -> [Param "--recipient", Param r])
|
||||||
|
|
||||||
stdParams :: [CommandParam] -> IO [String]
|
stdParams :: [CommandParam] -> IO [String]
|
||||||
stdParams params = do
|
stdParams params = do
|
||||||
#ifndef mingw32_HOST_OS
|
#ifndef mingw32_HOST_OS
|
||||||
|
@ -48,9 +52,21 @@ stdParams params = do
|
||||||
return $ defaults ++ toCommand params
|
return $ defaults ++ toCommand params
|
||||||
#endif
|
#endif
|
||||||
where
|
where
|
||||||
-- be quiet, even about checking the trustdb
|
-- Be quiet, even about checking the trustdb. If the one of the
|
||||||
|
-- default param is already present in 'params', don't include it
|
||||||
|
-- twice in the output list.
|
||||||
defaults = ["--quiet", "--trust-model", "always"]
|
defaults = ["--quiet", "--trust-model", "always"]
|
||||||
|
|
||||||
|
{- Usual options for symmetric / public-key encryption. -}
|
||||||
|
stdEncryptionParams :: Bool -> [CommandParam]
|
||||||
|
stdEncryptionParams symmetric = [enc symmetric, Param "--force-mdc"]
|
||||||
|
where
|
||||||
|
enc True = Param "--symmetric"
|
||||||
|
-- Force gpg to only encrypt to the specified recipients, not
|
||||||
|
-- configured defaults. Recipients are assumed to be specified in
|
||||||
|
-- elsewhere.
|
||||||
|
enc False = Params "--encrypt --no-encrypt-to --no-default-recipient"
|
||||||
|
|
||||||
{- Runs gpg with some params and returns its stdout, strictly. -}
|
{- Runs gpg with some params and returns its stdout, strictly. -}
|
||||||
readStrict :: [CommandParam] -> IO String
|
readStrict :: [CommandParam] -> IO String
|
||||||
readStrict params = do
|
readStrict params = do
|
||||||
|
@ -71,10 +87,11 @@ pipeStrict params input = do
|
||||||
hClose to
|
hClose to
|
||||||
hGetContentsStrict from
|
hGetContentsStrict from
|
||||||
|
|
||||||
{- Runs gpg with some parameters. First sends it a passphrase via
|
{- Runs gpg with some parameters. First sends it a passphrase (unless it
|
||||||
- --passphrase-fd. Then runs a feeder action that is passed a handle and
|
- is empty) via '--passphrase-fd'. Then runs a feeder action that is
|
||||||
- should write to it all the data to input to gpg. Finally, runs
|
- passed a handle and should write to it all the data to input to gpg.
|
||||||
- a reader action that is passed a handle to gpg's output.
|
- Finally, runs a reader action that is passed a handle to gpg's
|
||||||
|
- output.
|
||||||
-
|
-
|
||||||
- Runs gpg in batch mode; this is necessary to avoid gpg 2.x prompting for
|
- Runs gpg in batch mode; this is necessary to avoid gpg 2.x prompting for
|
||||||
- the passphrase.
|
- the passphrase.
|
||||||
|
@ -82,27 +99,28 @@ pipeStrict params input = do
|
||||||
- Note that to avoid deadlock with the cleanup stage,
|
- Note that to avoid deadlock with the cleanup stage,
|
||||||
- the reader must fully consume gpg's input before returning. -}
|
- the reader must fully consume gpg's input before returning. -}
|
||||||
feedRead :: [CommandParam] -> String -> (Handle -> IO ()) -> (Handle -> IO a) -> IO a
|
feedRead :: [CommandParam] -> String -> (Handle -> IO ()) -> (Handle -> IO a) -> IO a
|
||||||
feedRead params passphrase feeder reader = do
|
feedRead params passphrase feeder reader = if null passphrase
|
||||||
|
then go =<< stdParams (Param "--batch" : params)
|
||||||
|
else do
|
||||||
#ifndef mingw32_HOST_OS
|
#ifndef mingw32_HOST_OS
|
||||||
-- pipe the passphrase into gpg on a fd
|
-- pipe the passphrase into gpg on a fd
|
||||||
(frompipe, topipe) <- createPipe
|
(frompipe, topipe) <- createPipe
|
||||||
void $ forkIO $ do
|
void $ forkIO $ do
|
||||||
toh <- fdToHandle topipe
|
toh <- fdToHandle topipe
|
||||||
hPutStrLn toh passphrase
|
hPutStrLn toh passphrase
|
||||||
hClose toh
|
hClose toh
|
||||||
let Fd pfd = frompipe
|
let Fd pfd = frompipe
|
||||||
let passphrasefd = [Param "--passphrase-fd", Param $ show pfd]
|
let passphrasefd = [Param "--passphrase-fd", Param $ show pfd]
|
||||||
|
|
||||||
params' <- stdParams $ [Param "--batch"] ++ passphrasefd ++ params
|
params' <- stdParams $ Param "--batch" : passphrasefd ++ params
|
||||||
closeFd frompipe `after` go params'
|
closeFd frompipe `after` go params'
|
||||||
#else
|
#else
|
||||||
-- store the passphrase in a temp file for gpg
|
-- store the passphrase in a temp file for gpg
|
||||||
withTmpFile "gpg" $ \tmpfile h -> do
|
withTmpFile "gpg" $ \tmpfile h -> do
|
||||||
hPutStr h passphrase
|
hPutStr h passphrase
|
||||||
hClose h
|
hClose h
|
||||||
let passphrasefile = [Param "--passphrase-file", File tmpfile]
|
let passphrasefile = [Param "--passphrase-file", File tmpfile]
|
||||||
params' <- stdParams $ [Param "--batch"] ++ passphrasefile ++ params
|
go =<< stdParams $ Param "--batch" : passphrasefile ++ params
|
||||||
go params'
|
|
||||||
#endif
|
#endif
|
||||||
where
|
where
|
||||||
go params' = withBothHandles createProcessSuccess (proc gpgcmd params')
|
go params' = withBothHandles createProcessSuccess (proc gpgcmd params')
|
||||||
|
@ -260,3 +278,41 @@ testTestHarness = do
|
||||||
keys <- testHarness $ findPubKeys testKeyId
|
keys <- testHarness $ findPubKeys testKeyId
|
||||||
return $ KeyIds [testKeyId] == keys
|
return $ KeyIds [testKeyId] == keys
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef mingw32_HOST_OS
|
||||||
|
checkEncryptionFile :: FilePath -> Maybe KeyIds -> IO Bool
|
||||||
|
checkEncryptionFile filename keys =
|
||||||
|
checkGpgPackets keys =<< readStrict params
|
||||||
|
where
|
||||||
|
params = [Params "--list-packets --list-only", File filename]
|
||||||
|
|
||||||
|
checkEncryptionStream :: String -> Maybe KeyIds -> IO Bool
|
||||||
|
checkEncryptionStream stream keys =
|
||||||
|
checkGpgPackets keys =<< pipeStrict params stream
|
||||||
|
where
|
||||||
|
params = [Params "--list-packets --list-only"]
|
||||||
|
|
||||||
|
{- Parses an OpenPGP packet list, and checks whether data is
|
||||||
|
- symmetrically encrypted (keys is Nothing), or encrypted to some
|
||||||
|
- public key(s).
|
||||||
|
- /!\ The key needs to be in the keyring! -}
|
||||||
|
checkGpgPackets :: Maybe KeyIds -> String -> IO Bool
|
||||||
|
checkGpgPackets keys str = do
|
||||||
|
let (asym,sym) = partition (pubkeyEncPacket `isPrefixOf`) $
|
||||||
|
filter (\l' -> pubkeyEncPacket `isPrefixOf` l' ||
|
||||||
|
symkeyEncPacket `isPrefixOf` l') $
|
||||||
|
takeWhile (/= ":encrypted data packet:") $
|
||||||
|
lines str
|
||||||
|
case (keys,asym,sym) of
|
||||||
|
(Nothing, [], [_]) -> return True
|
||||||
|
(Just (KeyIds ks), ls, []) -> do
|
||||||
|
-- Find the master key associated with the
|
||||||
|
-- encryption subkey.
|
||||||
|
ks' <- concat <$> mapM (findPubKeys >=*> keyIds)
|
||||||
|
[ k | k:"keyid":_ <- map (reverse . words) ls ]
|
||||||
|
return $ sort (nub ks) == sort (nub ks')
|
||||||
|
_ -> return False
|
||||||
|
where
|
||||||
|
pubkeyEncPacket = ":pubkey enc packet: "
|
||||||
|
symkeyEncPacket = ":symkey enc packet: "
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
{- gpg data types
|
|
||||||
-
|
|
||||||
- Copyright 2013 guilhem <guilhem@fripost.org>
|
|
||||||
-
|
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
|
||||||
-}
|
|
||||||
|
|
||||||
module Utility.Gpg.Types where
|
|
||||||
|
|
||||||
import Utility.SafeCommand
|
|
||||||
import Types.GitConfig
|
|
||||||
import Types.Remote
|
|
||||||
|
|
||||||
{- GnuPG options. -}
|
|
||||||
type GpgOpt = String
|
|
||||||
newtype GpgOpts = GpgOpts [GpgOpt]
|
|
||||||
|
|
||||||
toParams :: GpgOpts -> [CommandParam]
|
|
||||||
toParams (GpgOpts opts) = map Param opts
|
|
||||||
|
|
||||||
class LensGpgOpts a where
|
|
||||||
getGpgOpts :: a -> GpgOpts
|
|
||||||
|
|
||||||
{- Extract the GnuPG options from a Remote Git Config. -}
|
|
||||||
instance LensGpgOpts RemoteGitConfig where
|
|
||||||
getGpgOpts = GpgOpts . remoteAnnexGnupgOptions
|
|
||||||
|
|
||||||
{- Extract the GnuPG options from a Remote. -}
|
|
||||||
instance LensGpgOpts (RemoteA a) where
|
|
||||||
getGpgOpts = getGpgOpts . gitconfig
|
|
13
debian/changelog
vendored
13
debian/changelog
vendored
|
@ -11,6 +11,19 @@ git-annex (4.20130828) UNRELEASED; urgency=low
|
||||||
from feeds.
|
from feeds.
|
||||||
* Honor core.sharedrepository when receiving and adding files in direct
|
* Honor core.sharedrepository when receiving and adding files in direct
|
||||||
mode.
|
mode.
|
||||||
|
* initremote: Syntax change when setting up encrypted special remote.
|
||||||
|
Now use: encryption=hybrid keyid=$KEYID
|
||||||
|
(Thanks, guilhem for the patch.)
|
||||||
|
* enableremote: gpg keys can be removed from those a remote encrypts
|
||||||
|
to by passing "keyid-=$KEYID". keyid+= is also provided, although
|
||||||
|
"encryption=$KEYID" can also be used as always.
|
||||||
|
(Thanks, guilhem for the patch.)
|
||||||
|
* Added encryption=pubkey scheme, which encrypts to public keys directly
|
||||||
|
rather than the hybrid approach. See documentation for advantages
|
||||||
|
and disadvantages, but encryption=hybrid is the recommended scheme still.
|
||||||
|
(Thanks, guilhem for the patch.)
|
||||||
|
|
||||||
|
-- Joey Hess <joeyh@debian.org> Tue, 27 Aug 2013 11:03:00 -0400
|
||||||
|
|
||||||
git-annex (4.20130827) unstable; urgency=low
|
git-annex (4.20130827) unstable; urgency=low
|
||||||
|
|
||||||
|
|
4
debian/copyright
vendored
4
debian/copyright
vendored
|
@ -14,10 +14,6 @@ Copyright: 2011 Bas van Dijk & Roel van Dijk
|
||||||
2012 Joey Hess <joey@kitenet.net>
|
2012 Joey Hess <joey@kitenet.net>
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: Utility/Gpg/Types.hs
|
|
||||||
Copyright: 2013 guilhem <guilhem@fripost.org>
|
|
||||||
License: GPL-3+
|
|
||||||
|
|
||||||
Files: doc/logo* */favicon.ico standalone/osx/git-annex.app/Contents/Resources/git-annex.icns standalone/android/icons/*
|
Files: doc/logo* */favicon.ico standalone/osx/git-annex.app/Contents/Resources/git-annex.icns standalone/android/icons/*
|
||||||
Copyright: 2007 Henrik Nyh <http://henrik.nyh.se/>
|
Copyright: 2007 Henrik Nyh <http://henrik.nyh.se/>
|
||||||
2010 Joey Hess <joey@kitenet.net>
|
2010 Joey Hess <joey@kitenet.net>
|
||||||
|
|
|
@ -29,3 +29,6 @@ git-annex: user error (gpg ["--quiet","--trust-model","always","--encrypt","--no
|
||||||
failed
|
failed
|
||||||
git-annex: enableremote: 1 failed
|
git-annex: enableremote: 1 failed
|
||||||
"""]]
|
"""]]
|
||||||
|
|
||||||
|
> [[done]]; can now use: `git annex enableremote foo keyid-=REVOKEDKEY
|
||||||
|
> keyid+=NEWKEY` to remove it, and add a new key. --[[Joey]]
|
||||||
|
|
|
@ -103,14 +103,17 @@ use the special remote.
|
||||||
|
|
||||||
## risks
|
## risks
|
||||||
|
|
||||||
A risk of this scheme is that, once the symmetric cipher has been obtained, it
|
A risk of this scheme is that, once the symmetric cipher has been
|
||||||
allows full access to all the encrypted content. This scheme does not allow
|
obtained, it allows full access to all the encrypted content. Indeed
|
||||||
revoking a given gpg key access to the cipher, since anyone with such a key
|
anyone owning a key that used to be granted access could already have
|
||||||
could have already decrypted the cipher and stored a copy.
|
decrypted the cipher and stored a copy. While it is in possible to
|
||||||
|
remove a key with `keyid-=`, it is designed for a
|
||||||
|
[[completely_different_purpose|/encryption]] and does not actually revoke
|
||||||
|
access.
|
||||||
|
|
||||||
If git-annex stores the decrypted symmetric cipher in memory, then there
|
If git-annex stores the decrypted symmetric cipher in memory, then there
|
||||||
is a risk that it could be intercepted from there by an attacker. Gpg
|
is a risk that it could be intercepted from there by an attacker. Gpg
|
||||||
amelorates these type of risks by using locked memory. For git-annex, note
|
ameliorates these type of risks by using locked memory. For git-annex, note
|
||||||
that an attacker with local machine access can tell at least all the
|
that an attacker with local machine access can tell at least all the
|
||||||
filenames and metadata of files stored in the encrypted remote anyway,
|
filenames and metadata of files stored in the encrypted remote anyway,
|
||||||
and can access whatever content is stored locally.
|
and can access whatever content is stored locally.
|
||||||
|
|
|
@ -6,20 +6,90 @@ Encryption is needed when using [[special_remotes]] like Amazon S3, where
|
||||||
file content is sent to an untrusted party who does not have access to the
|
file content is sent to an untrusted party who does not have access to the
|
||||||
git repository.
|
git repository.
|
||||||
|
|
||||||
Such an encrypted remote uses strong GPG encryption on the contents of files,
|
Such an encrypted remote uses strong ([[symmetric|design/encryption]] or
|
||||||
as well as HMAC hashing of the filenames. The size of the encrypted files,
|
asymmetric) encryption on the contents of files, as well as HMAC hashing
|
||||||
and access patterns of the data, should be the only clues to what is
|
of the filenames. The size of the encrypted files, and access patterns
|
||||||
stored in such a remote.
|
of the data, should be the only clues to what is stored in such a
|
||||||
|
remote.
|
||||||
|
|
||||||
You should decide whether to use encryption with a special remote before
|
You should decide whether to use encryption with a special remote before
|
||||||
any data is stored in it. So, `git annex initremote` requires you
|
any data is stored in it. So, `git annex initremote` requires you
|
||||||
to specify "encryption=none" when first setting up a remote in order
|
to specify "encryption=none" when first setting up a remote in order
|
||||||
to disable encryption.
|
to disable encryption. To use encryption, you run
|
||||||
|
run `git-annex initremote` in one of these ways:
|
||||||
|
|
||||||
If you want to use encryption, run `git annex initremote` with
|
* `git annex initremote newremote type=... encryption=hybrid keyid=KEYID ...`
|
||||||
"encryption=USERID". The value will be passed to `gpg` to find encryption keys.
|
* `git annex initremote newremote type=... encryption=shared`
|
||||||
Typically, you will say "encryption=2512E3C7" to use a specific gpg key.
|
* `git annex initremote newremote type=... encryption=pubkey keyid=KEYID ...`
|
||||||
Or, you might say "encryption=joey@kitenet.net" to search for matching keys.
|
|
||||||
|
## hybrid encryption keys
|
||||||
|
|
||||||
|
The [[hybrid_key_design|design/encryption]] allows additional
|
||||||
|
encryption keys to be added on to a special remote later. Due to this
|
||||||
|
flexability, it is the default and recommended encryption scheme.
|
||||||
|
|
||||||
|
git annex initremote newremote type=... [encryption=hybrid] keyid=KEYID ...
|
||||||
|
|
||||||
|
Here the KEYID(s) are passed to `gpg` to find encryption keys.
|
||||||
|
Typically, you will say "keyid=2512E3C7" to use a specific gpg key.
|
||||||
|
Or, you might say "keyid=joey@kitenet.net" to search for matching keys.
|
||||||
|
|
||||||
|
To add a new key and allow it to access all the content that is stored
|
||||||
|
in the encrypted special remote, just run `git annex
|
||||||
|
enableremote` specifying the new encryption key:
|
||||||
|
|
||||||
|
git annex enableremote myremote keyid+=788A3F4C
|
||||||
|
|
||||||
|
While a key can later be removed from the list, note that
|
||||||
|
that will **not** necessarily prevent the owner of the key
|
||||||
|
from accessing data on the remote (which is by design impossible to prevent,
|
||||||
|
short of deleting the remote). In fact the only sound use of `keyid-=` is
|
||||||
|
probably to replace a revoked key:
|
||||||
|
|
||||||
|
git annex enableremote myremote keyid-=2512E3C7 keyid+=788A3F4C
|
||||||
|
|
||||||
|
See also [[encryption_design|design/encryption]] for other security
|
||||||
|
risks associated with encryption.
|
||||||
|
|
||||||
|
## shared encryption key
|
||||||
|
|
||||||
|
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 among every
|
||||||
|
clone of the git repository.
|
||||||
|
|
||||||
|
git annex initremote newremote type=... encryption=shared
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## regular public key encryption
|
||||||
|
|
||||||
|
This alternative simply encrypts the files in the special remotes to one or
|
||||||
|
more public keys. It might be considered more secure due to its simplicity
|
||||||
|
and since it's exactly the way everyone else uses gpg.
|
||||||
|
|
||||||
|
git annex initremote newremote type=.... encryption=pubkey keyid=KEYID ...
|
||||||
|
|
||||||
|
A disavantage is that is not easy to later add additional public keys
|
||||||
|
to the special remote. While the `enableremote` parameters `keyid+=` and
|
||||||
|
`keyid-=` can be used, they have **no effect** on files that are already
|
||||||
|
present on the remote. Probably the only use for these parameters is
|
||||||
|
to replace a revoked key:
|
||||||
|
|
||||||
|
git annex enableremote myremote keyid-=2512E3C7 keyid+=788A3F4C
|
||||||
|
|
||||||
|
But even in this case, since the files are not re-encrypted, the revoked
|
||||||
|
key has to be kept around to be able to decrypt those files.
|
||||||
|
(Of course, if the reason for revocation is
|
||||||
|
that the key has been compromised, it is **insecure** to leave files
|
||||||
|
encrypted using that old key, and the user should re-encrypt everything.)
|
||||||
|
|
||||||
|
(Because filenames are MAC'ed, a cipher still needs to be
|
||||||
|
generated (and encrypted to the given key IDs).)
|
||||||
|
|
||||||
|
## MAC algorithm
|
||||||
|
|
||||||
The default MAC algorithm to be applied on the filenames is HMACSHA1. A
|
The default MAC algorithm to be applied on the filenames is HMACSHA1. A
|
||||||
stronger one, for instance HMACSHA512, one can be chosen upon creation
|
stronger one, for instance HMACSHA512, one can be chosen upon creation
|
||||||
|
@ -27,29 +97,3 @@ of the special remote with the option `mac=HMACSHA512`. The available
|
||||||
MAC algorithms are HMACSHA1, HMACSHA224, HMACSHA256, HMACSHA384, and
|
MAC algorithms are HMACSHA1, HMACSHA224, HMACSHA256, HMACSHA384, and
|
||||||
HMACSHA512. Note that it is not possible to change algorithm for a
|
HMACSHA512. Note that it is not possible to change algorithm for a
|
||||||
non-empty remote.
|
non-empty remote.
|
||||||
|
|
||||||
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 access content that has already been stored in the special remote.
|
|
||||||
To add a new key, just run `git annex enableremote` specifying the
|
|
||||||
new encryption key:
|
|
||||||
|
|
||||||
git annex enableremote myremote encryption=788A3F4C
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
|
@ -307,19 +307,30 @@ subdirectories).
|
||||||
types of special remotes need different configuration values. The
|
types of special remotes need different configuration values. The
|
||||||
command will prompt for parameters as needed.
|
command will prompt for parameters as needed.
|
||||||
|
|
||||||
All special remotes support encryption. You must either specify
|
All special remotes support encryption. You can either specify
|
||||||
encryption=none to disable encryption, or use encryption=keyid
|
`encryption=none` to disable encryption, or specify
|
||||||
(or encryption=emailaddress) to specify a gpg key that can access
|
`encryption=hybrid keyid=$keyid ...` to specify a gpg key id (or an email
|
||||||
the encrypted special remote.
|
address accociated with a key.
|
||||||
|
|
||||||
Note that with encryption enabled, a gpg key is created. This requires
|
There are actually three schemes that can be used for management of the
|
||||||
sufficient entropy. If initremote seems to hang or take a long time
|
encryption keys. When using the encryption=hybrid scheme, additional
|
||||||
while generating the key, you may want to ctrl-c it and re-run with --fast,
|
gpg keys can be given access to the encrypted special remote easily
|
||||||
which causes it to use a lower-quality source of randomness.
|
(without re-encrypting everything). When using encryption=shared,
|
||||||
|
a shared key is generated and stored in the git repository, allowing
|
||||||
|
anyone who can clone the git repository to access it. Finally, when using
|
||||||
|
encryption=pubkey, content in the special remote is directly encrypted
|
||||||
|
to the specified gpg keys, and additional ones cannot easily be given
|
||||||
|
access.
|
||||||
|
|
||||||
|
Note that with encryption enabled, a cryptographic key is created.
|
||||||
|
This requires sufficient entropy. If initremote seems to hang or take
|
||||||
|
a long time while generating the key, you may want to ctrl-c it and
|
||||||
|
re-run with --fast, which causes it to use a lower-quality source of
|
||||||
|
randomness.
|
||||||
|
|
||||||
Example Amazon S3 remote:
|
Example Amazon S3 remote:
|
||||||
|
|
||||||
git annex initremote mys3 type=S3 encryption=me@example.com datacenter=EU
|
git annex initremote mys3 type=S3 encryption=hybrid keyid=me@example.com datacenter=EU
|
||||||
|
|
||||||
* enableremote name [param=value ...]
|
* enableremote name [param=value ...]
|
||||||
|
|
||||||
|
@ -335,11 +346,28 @@ subdirectories).
|
||||||
For example, the directory special remote requires a directory= parameter.
|
For example, the directory special remote requires a directory= parameter.
|
||||||
|
|
||||||
This command can also be used to modify the configuration of an existing
|
This command can also be used to modify the configuration of an existing
|
||||||
special remote, by specifying new values for parameters that were originally
|
special remote, by specifying new values for parameters that were
|
||||||
set when using initremote. For example, to add a new gpg key to the keys
|
originally set when using initremote. (However, some settings such as
|
||||||
that can access an encrypted remote:
|
the as the encryption scheme cannot be changed once a special remote
|
||||||
|
has been created.)
|
||||||
|
|
||||||
git annex enableremote mys3 encryption=friend@example.com
|
The gpg keys that an encrypted special remote is encrypted to can be
|
||||||
|
changed using the keyid+= and keyid-= parameters. These respectively
|
||||||
|
add and remove keys from the list. However, note that removing a key
|
||||||
|
does NOT necessarily prevent the key's owner from accessing data
|
||||||
|
in the encrypted special remote
|
||||||
|
(which is by design impossible, short of deleting the remote).
|
||||||
|
|
||||||
|
One use-case of keyid-= is to replace a revoked key with
|
||||||
|
a new key:
|
||||||
|
|
||||||
|
git annex enableremote mys3 keyid-=revokedkey keyid+=newkey
|
||||||
|
|
||||||
|
Also, note that for encrypted special remotes using plain public-key
|
||||||
|
encryption (encryption=pubkey), adding or removing a key has NO effect
|
||||||
|
on files that have already been copied to the remote. Hence using
|
||||||
|
keyid+= and keyid-= with such remotes should be used with care, and
|
||||||
|
make little sense except in cases like the revoked key example above.
|
||||||
|
|
||||||
* trust [repository ...]
|
* trust [repository ...]
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,10 @@ can read inside the local git repository.
|
||||||
A number of parameters can be passed to `git annex initremote` to configure
|
A number of parameters can be passed to `git annex initremote` to configure
|
||||||
the S3 remote.
|
the S3 remote.
|
||||||
|
|
||||||
* `encryption` - Required. Either "none" to disable encryption (not recommended),
|
* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
|
||||||
or a value that can be looked up (using gpg -k) to find a gpg encryption
|
See [[encryption]].
|
||||||
key that will be given access to the remote, or "shared" which allows
|
|
||||||
every clone of the repository to access the encrypted data (use with caution).
|
|
||||||
|
|
||||||
Note that additional gpg keys can be given access to a remote by
|
* `keyid` - Specifies the gpg key to use for [[encryption]].
|
||||||
running enableremote with the new key id. See [[encryption]].
|
|
||||||
|
|
||||||
* `embedcreds` - Optional. Set to "yes" embed the login credentials inside
|
* `embedcreds` - Optional. Set to "yes" embed the login credentials inside
|
||||||
the git repository, which allows other clones to also access them. This is
|
the git repository, which allows other clones to also access them. This is
|
||||||
|
|
|
@ -19,14 +19,10 @@ for example; or clone bup's git repository to further back it up.
|
||||||
|
|
||||||
These parameters can be passed to `git annex initremote` to configure bup:
|
These parameters can be passed to `git annex initremote` to configure bup:
|
||||||
|
|
||||||
* `encryption` - Required. Either "none" to disable encryption of content
|
* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
|
||||||
stored in bup (ssh will still be used to transport it securely),
|
See [[encryption]].
|
||||||
or a value that can be looked up (using gpg -k) to find a gpg encryption
|
|
||||||
key that will be given access to the remote, or "shared" which allows
|
|
||||||
every clone of the repository to access the encrypted data (use with caution).
|
|
||||||
|
|
||||||
Note that additional gpg keys can be given access to a remote by
|
* `keyid` - Specifies the gpg key to use for [[encryption]].
|
||||||
running enableremote with the new key id. See [[encryption]].
|
|
||||||
|
|
||||||
* `buprepo` - Required. This is passed to `bup` as the `--remote`
|
* `buprepo` - Required. This is passed to `bup` as the `--remote`
|
||||||
to use to store data. To create the repository,`bup init` will be run.
|
to use to store data. To create the repository,`bup init` will be run.
|
||||||
|
|
|
@ -14,13 +14,10 @@ Instead, you should use a regular `git clone` of your git-annex repository.
|
||||||
These parameters can be passed to `git annex initremote` to configure the
|
These parameters can be passed to `git annex initremote` to configure the
|
||||||
remote:
|
remote:
|
||||||
|
|
||||||
* `encryption` - Required. Either "none" to disable encryption,
|
* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
|
||||||
or a value that can be looked up (using gpg -k) to find a gpg encryption
|
See [[encryption]].
|
||||||
key that will be given access to the remote, or "shared" which allows
|
|
||||||
every clone of the repository to decrypt the encrypted data.
|
|
||||||
|
|
||||||
Note that additional gpg keys can be given access to a remote by
|
* `keyid` - Specifies the gpg key to use for [[encryption]].
|
||||||
running enableremote with the new key id. See [[encryption]].
|
|
||||||
|
|
||||||
* `chunksize` - Avoid storing files larger than the specified size in the
|
* `chunksize` - Avoid storing files larger than the specified size in the
|
||||||
directory. For use on directories on mount points that have file size
|
directory. For use on directories on mount points that have file size
|
||||||
|
|
|
@ -21,13 +21,10 @@ can read inside the local git repository.
|
||||||
A number of parameters can be passed to `git annex initremote` to configure
|
A number of parameters can be passed to `git annex initremote` to configure
|
||||||
the Glacier remote.
|
the Glacier remote.
|
||||||
|
|
||||||
* `encryption` - Required. Either "none" to disable encryption (not recommended),
|
* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
|
||||||
or a value that can be looked up (using gpg -k) to find a gpg encryption
|
See [[encryption]].
|
||||||
key that will be given access to the remote, or "shared" which allows
|
|
||||||
every clone of the repository to access the encrypted data (use with caution).
|
|
||||||
|
|
||||||
Note that additional gpg keys can be given access to a remote by
|
* `keyid` - Specifies the gpg key to use for [[encryption]].
|
||||||
running enableremote with the new key id. See [[encryption]].
|
|
||||||
|
|
||||||
* `embedcreds` - Optional. Set to "yes" embed the login credentials inside
|
* `embedcreds` - Optional. Set to "yes" embed the login credentials inside
|
||||||
the git repository, which allows other clones to also access them. This is
|
the git repository, which allows other clones to also access them. This is
|
||||||
|
|
|
@ -25,17 +25,14 @@ Can you spot the potential data loss bugs in the above simple example?
|
||||||
|
|
||||||
These parameters can be passed to `git annex initremote`:
|
These parameters can be passed to `git annex initremote`:
|
||||||
|
|
||||||
* `encryption` - Required. Either "none" to disable encryption,
|
|
||||||
or a value that can be looked up (using gpg -k) to find a gpg encryption
|
|
||||||
key that will be given access to the remote, or "shared" which allows
|
|
||||||
every clone of the repository to access the encrypted data.
|
|
||||||
|
|
||||||
Note that additional gpg keys can be given access to a remote by
|
|
||||||
running enableremote with the new key id. See [[encryption]].
|
|
||||||
|
|
||||||
* `hooktype` - Required. This specifies a collection of hooks to use for
|
* `hooktype` - Required. This specifies a collection of hooks to use for
|
||||||
this remote.
|
this remote.
|
||||||
|
|
||||||
|
* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
|
||||||
|
See [[encryption]].
|
||||||
|
|
||||||
|
* `keyid` - Specifies the gpg key to use for [[encryption]].
|
||||||
|
|
||||||
## hooks
|
## hooks
|
||||||
|
|
||||||
Each type of hook remote is specified by a collection of hook commands.
|
Each type of hook remote is specified by a collection of hook commands.
|
||||||
|
|
|
@ -2,26 +2,22 @@ This special remote type rsyncs file contents to somewhere else.
|
||||||
|
|
||||||
Setup example:
|
Setup example:
|
||||||
|
|
||||||
# git annex initremote myrsync type=rsync rsyncurl=rsync://rsync.example.com/myrsync encryption=joey@kitenet.net
|
# git annex initremote myrsync type=rsync rsyncurl=rsync://rsync.example.com/myrsync keyid=joey@kitenet.net
|
||||||
# git annex describe myrsync "rsync server"
|
# git annex describe myrsync "rsync server"
|
||||||
|
|
||||||
Or for using rsync over SSH
|
Or for using rsync over SSH
|
||||||
|
|
||||||
# git annex initremote myrsync type=rsync rsyncurl=ssh.example.com:/myrsync encryption=joey@kitenet.net
|
# git annex initremote myrsync type=rsync rsyncurl=ssh.example.com:/myrsync keyid=joey@kitenet.net
|
||||||
# git annex describe myrsync "rsync server"
|
# git annex describe myrsync "rsync server"
|
||||||
|
|
||||||
## configuration
|
## configuration
|
||||||
|
|
||||||
These parameters can be passed to `git annex initremote` to configure rsync:
|
These parameters can be passed to `git annex initremote` to configure rsync:
|
||||||
|
|
||||||
* `encryption` - Required. Either "none" to disable encryption of content
|
* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
|
||||||
stored on the remote rsync server,
|
See [[encryption]].
|
||||||
or a value that can be looked up (using gpg -k) to find a gpg encryption
|
|
||||||
key that will be given access to the remote, or "shared" which allows
|
|
||||||
every clone of the repository to decrypt the encrypted data.
|
|
||||||
|
|
||||||
Note that additional gpg keys can be given access to a remote by
|
* `keyid` - Specifies the gpg key to use for [[encryption]].
|
||||||
running enableremote with the new key id. See [[encryption]].
|
|
||||||
|
|
||||||
* `rsyncurl` - Required. This is the url or `hostname:/directory` to
|
* `rsyncurl` - Required. This is the url or `hostname:/directory` to
|
||||||
pass to rsync to tell it where to store content.
|
pass to rsync to tell it where to store content.
|
||||||
|
|
|
@ -10,13 +10,10 @@ can read inside the local git repository.
|
||||||
A number of parameters can be passed to `git annex initremote` to configure
|
A number of parameters can be passed to `git annex initremote` to configure
|
||||||
the webdav remote.
|
the webdav remote.
|
||||||
|
|
||||||
* `encryption` - Required. Either "none" to disable encryption (not recommended),
|
* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
|
||||||
or a value that can be looked up (using gpg -k) to find a gpg encryption
|
See [[encryption]].
|
||||||
key that will be given access to the remote, or "shared" which allows
|
|
||||||
every clone of the repository to access the encrypted data (use with caution).
|
|
||||||
|
|
||||||
Note that additional gpg keys can be given access to a remote by
|
* `keyid` - Specifies the gpg key to use for [[encryption]].
|
||||||
running enableremote with the new key id. See [[encryption]].
|
|
||||||
|
|
||||||
* `embedcreds` - Optional. Set to "yes" embed the login credentials inside
|
* `embedcreds` - Optional. Set to "yes" embed the login credentials inside
|
||||||
the git repository, which allows other clones to also access them. This is
|
the git repository, which allows other clones to also access them. This is
|
||||||
|
@ -42,4 +39,4 @@ the webdav remote.
|
||||||
|
|
||||||
Setup example:
|
Setup example:
|
||||||
|
|
||||||
# WEBDAV_USERNAME=joey@kitenet.net WEBDAV_PASSWORD=xxxxxxx git annex initremote box.com type=webdav url=https://www.box.com/dav/git-annex chunksize=75mb encryption=joey@kitenet.net
|
# WEBDAV_USERNAME=joey@kitenet.net WEBDAV_PASSWORD=xxxxxxx git annex initremote box.com type=webdav url=https://www.box.com/dav/git-annex chunksize=75mb keyid=joey@kitenet.net
|
||||||
|
|
|
@ -16,7 +16,7 @@ like "2512E3C7"
|
||||||
|
|
||||||
Next, create the Glacier remote.
|
Next, create the Glacier remote.
|
||||||
|
|
||||||
# git annex initremote glacier type=glacier encryption=2512E3C7
|
# git annex initremote glacier type=glacier keyid=2512E3C7
|
||||||
initremote glacier (encryption setup with gpg key C910D9222512E3C7) (gpg) ok
|
initremote glacier (encryption setup with gpg key C910D9222512E3C7) (gpg) ok
|
||||||
|
|
||||||
The configuration for the Glacier remote is stored in git. So to make another
|
The configuration for the Glacier remote is stored in git. So to make another
|
||||||
|
|
|
@ -14,7 +14,7 @@ like "2512E3C7"
|
||||||
|
|
||||||
Next, create the S3 remote, and describe it.
|
Next, create the S3 remote, and describe it.
|
||||||
|
|
||||||
# git annex initremote cloud type=S3 encryption=2512E3C7
|
# git annex initremote cloud type=S3 keyid=2512E3C7
|
||||||
initremote cloud (encryption setup with gpg key C910D9222512E3C7) (checking bucket) (creating bucket in US) (gpg) ok
|
initremote cloud (encryption setup with gpg key C910D9222512E3C7) (checking bucket) (creating bucket in US) (gpg) ok
|
||||||
# git annex describe cloud "at Amazon's US datacenter"
|
# git annex describe cloud "at Amazon's US datacenter"
|
||||||
describe cloud ok
|
describe cloud ok
|
||||||
|
|
|
@ -5,7 +5,7 @@ for providing 50 gb of free storage if you sign up with its Android client.
|
||||||
git-annex can use Box as a [[special remote|special_remotes]].
|
git-annex can use Box as a [[special remote|special_remotes]].
|
||||||
Recent versions of git-annex make this very easy to set up:
|
Recent versions of git-annex make this very easy to set up:
|
||||||
|
|
||||||
WEBDAV_USERNAME=you@example.com WEBDAV_PASSWORD=xxxxxxx git annex initremote box.com type=webdav url=https://www.box.com/dav/git-annex chunksize=75mb encryption=you@example.com
|
WEBDAV_USERNAME=you@example.com WEBDAV_PASSWORD=xxxxxxx git annex initremote box.com type=webdav url=https://www.box.com/dav/git-annex chunksize=75mb encryption=shared
|
||||||
|
|
||||||
Note the use of chunksize; Box has a 100 mb maximum file size, and this
|
Note the use of chunksize; Box has a 100 mb maximum file size, and this
|
||||||
breaks up large files into chunks before that limit is reached.
|
breaks up large files into chunks before that limit is reached.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue