summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Joey Hess <joey@kitenet.net>2013-09-05 00:09:11 -0400
committerGravatar Joey Hess <joey@kitenet.net>2013-09-05 00:09:11 -0400
commit94718242872b9c17ccc1e4888328b124cb02dcac (patch)
treeb46d29358889081d3d28f3c8a6190b97371cb030
parent48e7a83f8613690432f100886760135cfa3b5b0f (diff)
parent2f3b108d49c534e828cec8adceef7defc942b67f (diff)
Merge branch 'encryption'
-rw-r--r--Creds.hs2
-rw-r--r--Crypto.hs112
-rw-r--r--Remote/Bup.hs2
-rw-r--r--Remote/Directory.hs4
-rw-r--r--Remote/Glacier.hs2
-rw-r--r--Remote/Helper/Encryptable.hs84
-rw-r--r--Remote/Hook.hs4
-rw-r--r--Remote/Rsync.hs4
-rw-r--r--Remote/S3.hs2
-rw-r--r--Remote/WebDAV.hs2
-rw-r--r--Test.hs52
-rw-r--r--Types/Crypto.hs6
-rw-r--r--Utility/Gpg.hs102
-rw-r--r--Utility/Gpg/Types.hs30
-rw-r--r--debian/changelog13
-rw-r--r--debian/copyright4
-rw-r--r--doc/bugs/Using_a_revoked_GPG_key.mdwn3
-rw-r--r--doc/design/encryption.mdwn13
-rw-r--r--doc/encryption.mdwn112
-rw-r--r--doc/git-annex.mdwn58
-rw-r--r--doc/special_remotes/S3.mdwn9
-rw-r--r--doc/special_remotes/bup.mdwn12
-rw-r--r--doc/special_remotes/directory.mdwn9
-rw-r--r--doc/special_remotes/glacier.mdwn9
-rw-r--r--doc/special_remotes/hook.mdwn13
-rw-r--r--doc/special_remotes/rsync.mdwn14
-rw-r--r--doc/special_remotes/webdav.mdwn11
-rw-r--r--doc/tips/using_Amazon_Glacier.mdwn2
-rw-r--r--doc/tips/using_Amazon_S3.mdwn2
-rw-r--r--doc/tips/using_box.com_as_a_special_remote.mdwn2
30 files changed, 450 insertions, 244 deletions
diff --git a/Creds.hs b/Creds.hs
index 7791ce85d..588d67cfe 100644
--- a/Creds.hs
+++ b/Creds.hs
@@ -52,7 +52,7 @@ setRemoteCredPair c storage = go =<< getRemoteCredPair c storage
return c
storeconfig creds key (Just cipher) = do
- s <- liftIO $ encrypt (GpgOpts []) cipher
+ s <- liftIO $ encrypt [] cipher
(feedBytes $ L.pack $ encodeCredPair creds)
(readBytes $ return . L.unpack)
return $ M.insert key (toB64 s) c
diff --git a/Crypto.hs b/Crypto.hs
index 21b1ae41b..0ea12c6c1 100644
--- a/Crypto.hs
+++ b/Crypto.hs
@@ -8,6 +8,8 @@
- Licensed under the GNU GPL version 3 or higher.
-}
+{-# LANGUAGE FlexibleInstances #-}
+
module Crypto (
Cipher,
KeyIds(..),
@@ -22,9 +24,8 @@ module Crypto (
feedBytes,
readBytes,
encrypt,
- decrypt,
- GpgOpts(..),
- getGpgOpts,
+ decrypt,
+ getGpgEncParams,
prop_HmacSha1WithCipher_sane
) where
@@ -32,17 +33,18 @@ module Crypto (
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Lazy.UTF8 (fromString)
import Control.Applicative
+import qualified Data.Map as M
import Common.Annex
import qualified Utility.Gpg as Gpg
-import Utility.Gpg.Types
import Types.Key
import Types.Crypto
+import Types.Remote
{- The beginning of a Cipher is used for MAC'ing; the remainder is used
- - as the GPG symmetric encryption passphrase. Note that the cipher
- - itself is base-64 encoded, hence the string is longer than
- - 'cipherSize': 683 characters, padded to 684.
+ - as the GPG symmetric encryption passphrase when using the hybrid
+ - scheme. Note that the cipher itself is base-64 encoded, hence the
+ - string is longer than 'cipherSize': 683 characters, padded to 684.
-
- The 256 first characters that feed the MAC represent at best 192
- 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
{- Creates a new Cipher, encrypted to the specified key id. -}
-genEncryptedCipher :: String -> Bool -> IO StorableCipher
-genEncryptedCipher keyid highQuality = do
+genEncryptedCipher :: String -> EncryptedCipherVariant -> Bool -> IO StorableCipher
+genEncryptedCipher keyid variant highQuality = do
ks <- Gpg.findPubKeys keyid
- random <- Gpg.genRandom highQuality cipherSize
- encryptCipher (Cipher random) ks
+ random <- Gpg.genRandom highQuality size
+ 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. -}
genSharedCipher :: Bool -> IO StorableCipher
genSharedCipher highQuality =
SharedCipher <$> Gpg.genRandom highQuality 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
+{- Updates an existing Cipher, re-encrypting it to add or remove keyids,
+ - depending on whether the first component is True or False. -}
+updateEncryptedCipher :: [(Bool, String)] -> StorableCipher -> IO StorableCipher
+updateEncryptedCipher _ SharedCipher{} = undefined
+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
- encryptCipher cipher (merge ks ks')
+ encryptCipher cipher symmetric $ KeyIds ks'
where
- merge (KeyIds a) (KeyIds b) = KeyIds $ a ++ b
+ listKeyIds = mapM (Gpg.findPubKeys >=*> keyIds) >=*> concat
describeCipher :: StorableCipher -> String
describeCipher (SharedCipher _) = "shared cipher"
-describeCipher (EncryptedCipher _ (KeyIds ks)) =
- "with gpg " ++ keys ks ++ " " ++ unwords ks
+describeCipher (EncryptedCipher _ variant (KeyIds ks)) =
+ scheme ++ " with gpg " ++ keys ks ++ " " ++ unwords ks
where
+ scheme = case variant of
+ HybridCipher -> "hybrid cipher"
+ PubKeyCipher -> "pubkey crypto"
keys [_] = "key"
keys _ = "keys"
{- Encrypts a Cipher to the specified KeyIds. -}
-encryptCipher :: Cipher -> KeyIds -> IO StorableCipher
-encryptCipher (Cipher c) (KeyIds ks) = do
+encryptCipher :: Cipher -> EncryptedCipherVariant -> KeyIds -> IO StorableCipher
+encryptCipher (Cipher c) variant (KeyIds ks) = do
-- gpg complains about duplicate recipient keyids
let ks' = nub $ sort ks
- encipher <- Gpg.pipeStrict (Params "--encrypt" : recipients ks') c
- return $ EncryptedCipher encipher (KeyIds ks')
- where
- 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"
+ let params = Gpg.pkEncTo ks' ++ Gpg.stdEncryptionParams False
+ encipher <- Gpg.pipeStrict params c
+ return $ EncryptedCipher encipher variant (KeyIds ks')
{- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -}
decryptCipher :: StorableCipher -> IO Cipher
decryptCipher (SharedCipher t) = return $ Cipher t
-decryptCipher (EncryptedCipher t _) =
+decryptCipher (EncryptedCipher t _ _) =
Cipher <$> Gpg.pipeStrict [ Param "--decrypt" ] t
{- 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 a h = L.hGetContents h >>= a
-{- Runs a Feeder action, that generates content that is symmetrically encrypted
- - with the Cipher using the given GnuPG options, and then read by the Reader
- - action. -}
-encrypt :: GpgOpts -> Cipher -> Feeder -> Reader a -> IO a
-encrypt opts = Gpg.feedRead ( Params "--symmetric --force-mdc" : toParams opts )
- . cipherPassphrase
+{- Runs a Feeder action, that generates content that is symmetrically
+ - encrypted with the Cipher (unless it is empty, in which case
+ - public-key encryption is used) using the given gpg options, and then
+ - read by the Reader action. Note: For public-key encryption,
+ - recipients MUST be included in 'params' (for instance using
+ - '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
- - 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 = Gpg.feedRead [Param "--decrypt"] . cipherPassphrase
@@ -161,3 +179,21 @@ prop_HmacSha1WithCipher_sane :: Bool
prop_HmacSha1WithCipher_sane = known_good == macWithCipher' HmacSha1 "foo" "bar"
where
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)
diff --git a/Remote/Bup.hs b/Remote/Bup.hs
index 9b3675cfa..9ef335218 100644
--- a/Remote/Bup.hs
+++ b/Remote/Bup.hs
@@ -133,7 +133,7 @@ storeEncrypted r buprepo (cipher, enck) k _p =
sendAnnex k (rollback enck buprepo) $ \src -> do
params <- bupSplitParams r buprepo enck []
liftIO $ catchBoolIO $
- encrypt (getGpgOpts r) cipher (feedFile src) $ \h ->
+ encrypt (getGpgEncParams r) cipher (feedFile src) $ \h ->
pipeBup params (Just h) Nothing
retrieve :: BupRepo -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex Bool
diff --git a/Remote/Directory.hs b/Remote/Directory.hs
index 6cc75d2f1..0b3ce443b 100644
--- a/Remote/Directory.hs
+++ b/Remote/Directory.hs
@@ -41,7 +41,7 @@ gen r u c gc = do
cst <- remoteCost gc cheapRemoteCost
let chunksize = chunkSize c
return $ encryptableRemote c
- (storeEncrypted dir (getGpgOpts gc) chunksize)
+ (storeEncrypted dir (getGpgEncParams (c,gc)) chunksize)
(retrieveEncrypted dir chunksize)
Remote {
uuid = u,
@@ -129,7 +129,7 @@ store d chunksize k _f p = sendAnnex k (void $ remove d k) $ \src ->
storeSplit meterupdate chunksize dests
=<< 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 ->
metered (Just p) k $ \meterupdate ->
storeHelper d chunksize enck k $ \dests ->
diff --git a/Remote/Glacier.hs b/Remote/Glacier.hs
index c1a53347d..d81066415 100644
--- a/Remote/Glacier.hs
+++ b/Remote/Glacier.hs
@@ -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
metered (Just p) k $ \meterupdate ->
storeHelper r enck $ \h ->
- encrypt (getGpgOpts r) cipher (feedFile src)
+ encrypt (getGpgEncParams r) cipher (feedFile src)
(readBytes $ meteredWrite meterupdate h)
retrieve :: Remote -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex Bool
diff --git a/Remote/Helper/Encryptable.hs b/Remote/Helper/Encryptable.hs
index 22e1c9fc0..29e51c002 100644
--- a/Remote/Helper/Encryptable.hs
+++ b/Remote/Helper/Encryptable.hs
@@ -23,30 +23,52 @@ import Utility.Metered
- 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 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
+encryptionSetup c = maybe genCipher updateCipher $ extractCipher c
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
showNote m
cipher <- liftIO a
showNote $ describeCipher cipher
- return $ M.delete "encryption" $ M.delete "highRandomQuality" $
- storeCipher c cipher
+ return $ storeCipher c' cipher
highRandomQuality =
(&&) (maybe True ( /= "false") $ M.lookup "highRandomQuality" c)
<$> 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.
-
@@ -111,27 +133,39 @@ embedCreds c
| isJust (M.lookup "cipherkeys" c) && isJust (M.lookup "cipher" c) = True
| 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 c k = maybe Nothing make <$> remoteCipher c
+cipherKey c k = fmap make <$> remoteCipher c
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
{- 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) =
+storeCipher c (EncryptedCipher t _ ks) =
M.insert "cipher" (toB64 t) $ M.insert "cipherkeys" (showkeys ks) c
where
showkeys (KeyIds l) = intercalate "," l
{- 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
+extractCipher c = case (M.lookup "cipher" c,
+ M.lookup "cipherkeys" c,
+ M.lookup "encryption" c) of
+ (Just t, Just ks, encryption) | maybe True (== "hybrid") encryption ->
+ 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
readkeys = KeyIds . split ","
diff --git a/Remote/Hook.hs b/Remote/Hook.hs
index 03f182a16..338d95ce7 100644
--- a/Remote/Hook.hs
+++ b/Remote/Hook.hs
@@ -38,7 +38,7 @@ gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex Remote
gen r u c gc = do
cst <- remoteCost gc expensiveRemoteCost
return $ encryptableRemote c
- (storeEncrypted hooktype $ getGpgOpts gc)
+ (storeEncrypted hooktype $ getGpgEncParams (c,gc))
(retrieveEncrypted hooktype)
Remote {
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 ->
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 ->
sendAnnex k (void $ remove h enck) $ \src -> do
liftIO $ encrypt gpgOpts cipher (feedFile src) $
diff --git a/Remote/Rsync.hs b/Remote/Rsync.hs
index a8efd84e7..4ad0fdadd 100644
--- a/Remote/Rsync.hs
+++ b/Remote/Rsync.hs
@@ -55,7 +55,7 @@ gen r u c gc = do
let o = RsyncOpts url (transport ++ opts) escape
islocal = rsyncUrlIsPath $ rsyncUrl o
return $ encryptableRemote c
- (storeEncrypted o $ getGpgOpts gc)
+ (storeEncrypted o $ getGpgEncParams (c,gc))
(retrieveEncrypted o)
Remote
{ uuid = u
@@ -137,7 +137,7 @@ rsyncUrls o k = map use annexHashes
store :: RsyncOpts -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool
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 ->
sendAnnex k (void $ remove o enck) $ \src -> do
liftIO $ encrypt gpgOpts cipher (feedFile src) $
diff --git a/Remote/S3.hs b/Remote/S3.hs
index 582bc2fda..814dd3a23 100644
--- a/Remote/S3.hs
+++ b/Remote/S3.hs
@@ -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.
-- (An alternative would be chunking to to a constant size.)
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
s3Bool =<< storeHelper (conn, bucket) r enck p tmp
diff --git a/Remote/WebDAV.hs b/Remote/WebDAV.hs
index 52fc32b3a..c444a7f2b 100644
--- a/Remote/WebDAV.hs
+++ b/Remote/WebDAV.hs
@@ -94,7 +94,7 @@ storeEncrypted :: Remote -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
storeEncrypted r (cipher, enck) k p = metered (Just p) k $ \meterupdate ->
davAction r False $ \(baseurl, user, pass) ->
sendAnnex k (void $ remove r enck) $ \src ->
- liftIO $ encrypt (getGpgOpts r) cipher
+ liftIO $ encrypt (getGpgEncParams r) cipher
(streamMeteredFile src meterupdate) $
readBytes $ storeHelper r enck baseurl user pass
diff --git a/Test.hs b/Test.hs
index ec70c4ecb..9eb99379e 100644
--- a/Test.hs
+++ b/Test.hs
@@ -29,6 +29,7 @@ import qualified Backend
import qualified Git.CurrentRepo
import qualified Git.Filename
import qualified Locations
+import qualified Types.Crypto
import qualified Types.KeySource
import qualified Types.Backend
import qualified Types.TrustLevel
@@ -41,6 +42,7 @@ import qualified Logs.Unused
import qualified Logs.Transfer
import qualified Logs.Presence
import qualified Remote
+import qualified Remote.Helper.Encryptable
import qualified Types.Key
import qualified Types.Messages
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
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
Utility.Gpg.testTestHarness @? "test harness self-test failed"
Utility.Gpg.testHarness $ do
createDirectory "dir"
- let a cmd = git_annex env cmd
+ let a cmd = git_annex env cmd $
[ "foo"
, "type=directory"
- , "encryption=" ++ Utility.Gpg.testKeyId
+ , "encryption=" ++ scheme
, "directory=dir"
, "highRandomQuality=false"
- ]
+ ] ++ if scheme `elem` ["hybrid","pubkey"]
+ then ["keyid=" ++ Utility.Gpg.testKeyId]
+ else []
a "initremote" @? "initremote failed"
not <$> a "initremote" @? "initremote failed to fail when run twice in a row"
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"
annexed_present annexedfile
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
git_annex env "drop" [annexedfile, "--numcopies=2"] @? "drop failed"
annexed_notpresent annexedfile
@@ -900,8 +915,35 @@ test_crypto env = "git-annex crypto" ~: intmpclonerepo env $ whenM (Utility.Path
annexed_present annexedfile
not <$> git_annex env "drop" [annexedfile, "--numcopies=2"] @? "drop failed to fail"
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
- putStrLn "gpg testing not implemented on Windows"
+ putStrLn "gpg testing not implemented on Windows"
#endif
-- This is equivilant to running git-annex, but it's all run in-process
diff --git a/Types/Crypto.hs b/Types/Crypto.hs
index e97d02ba8..8a15ead16 100644
--- a/Types/Crypto.hs
+++ b/Types/Crypto.hs
@@ -8,6 +8,7 @@
module Types.Crypto (
Cipher(..),
StorableCipher(..),
+ EncryptedCipherVariant(..),
KeyIds(..),
Mac(..),
readMac,
@@ -24,7 +25,10 @@ import Utility.Gpg (KeyIds(..))
-- XXX ideally, this would be a locked memory region
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)
{- File names are (client-side) MAC'ed on special remotes.
diff --git a/Utility/Gpg.hs b/Utility/Gpg.hs
index 81180148e..e27de7218 100644
--- a/Utility/Gpg.hs
+++ b/Utility/Gpg.hs
@@ -24,7 +24,7 @@ import Utility.Env
import Utility.Tmp
#endif
-newtype KeyIds = KeyIds [String]
+newtype KeyIds = KeyIds { keyIds :: [String] }
deriving (Ord, Eq)
{- If a specific gpg command was found at configure time, use it.
@@ -32,6 +32,10 @@ newtype KeyIds = KeyIds [String]
gpgcmd :: FilePath
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 params = do
#ifndef mingw32_HOST_OS
@@ -48,9 +52,21 @@ stdParams params = do
return $ defaults ++ toCommand params
#endif
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"]
+{- 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. -}
readStrict :: [CommandParam] -> IO String
readStrict params = do
@@ -71,10 +87,11 @@ pipeStrict params input = do
hClose to
hGetContentsStrict from
-{- Runs gpg with some parameters. First sends it a passphrase via
- - --passphrase-fd. Then runs a feeder action that is passed a handle and
- - should write to it all the data to input to gpg. Finally, runs
- - a reader action that is passed a handle to gpg's output.
+{- Runs gpg with some parameters. First sends it a passphrase (unless it
+ - is empty) via '--passphrase-fd'. Then runs a feeder action that is
+ - passed a handle and should write to it all the data to input to gpg.
+ - 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
- the passphrase.
@@ -82,27 +99,28 @@ pipeStrict params input = do
- Note that to avoid deadlock with the cleanup stage,
- the reader must fully consume gpg's input before returning. -}
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
- -- pipe the passphrase into gpg on a fd
- (frompipe, topipe) <- createPipe
- void $ forkIO $ do
- toh <- fdToHandle topipe
- hPutStrLn toh passphrase
- hClose toh
- let Fd pfd = frompipe
- let passphrasefd = [Param "--passphrase-fd", Param $ show pfd]
+ -- pipe the passphrase into gpg on a fd
+ (frompipe, topipe) <- createPipe
+ void $ forkIO $ do
+ toh <- fdToHandle topipe
+ hPutStrLn toh passphrase
+ hClose toh
+ let Fd pfd = frompipe
+ let passphrasefd = [Param "--passphrase-fd", Param $ show pfd]
- params' <- stdParams $ [Param "--batch"] ++ passphrasefd ++ params
- closeFd frompipe `after` go params'
+ params' <- stdParams $ Param "--batch" : passphrasefd ++ params
+ closeFd frompipe `after` go params'
#else
- -- store the passphrase in a temp file for gpg
- withTmpFile "gpg" $ \tmpfile h -> do
- hPutStr h passphrase
- hClose h
+ -- store the passphrase in a temp file for gpg
+ withTmpFile "gpg" $ \tmpfile h -> do
+ hPutStr h passphrase
+ hClose h
let passphrasefile = [Param "--passphrase-file", File tmpfile]
- params' <- stdParams $ [Param "--batch"] ++ passphrasefile ++ params
- go params'
+ go =<< stdParams $ Param "--batch" : passphrasefile ++ params
#endif
where
go params' = withBothHandles createProcessSuccess (proc gpgcmd params')
@@ -260,3 +278,41 @@ testTestHarness = do
keys <- testHarness $ findPubKeys testKeyId
return $ KeyIds [testKeyId] == keys
#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
diff --git a/Utility/Gpg/Types.hs b/Utility/Gpg/Types.hs
deleted file mode 100644
index d45707207..000000000
--- a/Utility/Gpg/Types.hs
+++ /dev/null
@@ -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
diff --git a/debian/changelog b/debian/changelog
index f880fbcf7..1f2a04d2e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -11,6 +11,19 @@ git-annex (4.20130828) UNRELEASED; urgency=low
from feeds.
* Honor core.sharedrepository when receiving and adding files in direct
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
diff --git a/debian/copyright b/debian/copyright
index 5a667adf7..e91cddbb5 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -14,10 +14,6 @@ Copyright: 2011 Bas van Dijk & Roel van Dijk
2012 Joey Hess <joey@kitenet.net>
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/*
Copyright: 2007 Henrik Nyh <http://henrik.nyh.se/>
2010 Joey Hess <joey@kitenet.net>
diff --git a/doc/bugs/Using_a_revoked_GPG_key.mdwn b/doc/bugs/Using_a_revoked_GPG_key.mdwn
index 79659b933..4e522ab78 100644
--- a/doc/bugs/Using_a_revoked_GPG_key.mdwn
+++ b/doc/bugs/Using_a_revoked_GPG_key.mdwn
@@ -29,3 +29,6 @@ git-annex: user error (gpg ["--quiet","--trust-model","always","--encrypt","--no
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]]
diff --git a/doc/design/encryption.mdwn b/doc/design/encryption.mdwn
index 6a380abe1..cc0dd1684 100644
--- a/doc/design/encryption.mdwn
+++ b/doc/design/encryption.mdwn
@@ -103,14 +103,17 @@ use the special remote.
## risks
-A risk of this scheme is that, once the symmetric cipher has been obtained, it
-allows full access to all the encrypted content. This scheme does not allow
-revoking a given gpg key access to the cipher, since anyone with such a key
-could have already decrypted the cipher and stored a copy.
+A risk of this scheme is that, once the symmetric cipher has been
+obtained, it allows full access to all the encrypted content. Indeed
+anyone owning a key that used to be granted access could already have
+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
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
filenames and metadata of files stored in the encrypted remote anyway,
and can access whatever content is stored locally.
diff --git a/doc/encryption.mdwn b/doc/encryption.mdwn
index d93bee9d2..cea46045e 100644
--- a/doc/encryption.mdwn
+++ b/doc/encryption.mdwn
@@ -6,50 +6,94 @@ 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
git repository.
-Such an encrypted remote uses strong GPG encryption on the contents of files,
-as well as HMAC hashing of the filenames. The size of the encrypted files,
-and access patterns of the data, should be the only clues to what is
-stored in such a remote.
+Such an encrypted remote uses strong ([[symmetric|design/encryption]] or
+asymmetric) encryption on the contents of files, as well as HMAC hashing
+of the filenames. The size of the encrypted files, and access patterns
+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
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 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
-"encryption=USERID". The value will be passed to `gpg` to find encryption keys.
-Typically, you will say "encryption=2512E3C7" to use a specific gpg key.
-Or, you might say "encryption=joey@kitenet.net" to search for matching keys.
+* `git annex initremote newremote type=... encryption=hybrid keyid=KEYID ...`
+* `git annex initremote newremote type=... encryption=shared`
+* `git annex initremote newremote type=... encryption=pubkey keyid=KEYID ...`
-The default MAC algorithm to be applied on the filenames is HMACSHA1. A
-stronger one, for instance HMACSHA512, one can be chosen upon creation
-of the special remote with the option `mac=HMACSHA512`. The available
-MAC algorithms are HMACSHA1, HMACSHA224, HMACSHA256, HMACSHA384, and
-HMACSHA512. Note that it is not possible to change algorithm for a
-non-empty remote.
+## 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 ...
-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:
+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.
- git annex enableremote myremote encryption=788A3F4C
+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:
-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.
+ git annex enableremote myremote keyid+=788A3F4C
-## shared cipher mode
+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 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.
+**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
+stronger one, for instance HMACSHA512, one can be chosen upon creation
+of the special remote with the option `mac=HMACSHA512`. The available
+MAC algorithms are HMACSHA1, HMACSHA224, HMACSHA256, HMACSHA384, and
+HMACSHA512. Note that it is not possible to change algorithm for a
+non-empty remote.
diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn
index 269588add..7afe5fd13 100644
--- a/doc/git-annex.mdwn
+++ b/doc/git-annex.mdwn
@@ -307,19 +307,30 @@ subdirectories).
types of special remotes need different configuration values. The
command will prompt for parameters as needed.
- All special remotes support encryption. You must either specify
- encryption=none to disable encryption, or use encryption=keyid
- (or encryption=emailaddress) to specify a gpg key that can access
- the encrypted special remote.
-
- Note that with encryption enabled, a gpg 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.
+ All special remotes support encryption. You can either specify
+ `encryption=none` to disable encryption, or specify
+ `encryption=hybrid keyid=$keyid ...` to specify a gpg key id (or an email
+ address accociated with a key.
+
+ There are actually three schemes that can be used for management of the
+ encryption keys. When using the encryption=hybrid scheme, additional
+ gpg keys can be given access to the encrypted special remote easily
+ (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:
- 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 ...]
@@ -335,11 +346,28 @@ subdirectories).
For example, the directory special remote requires a directory= parameter.
This command can also be used to modify the configuration of an existing
- special remote, by specifying new values for parameters that were originally
- set when using initremote. For example, to add a new gpg key to the keys
- that can access an encrypted remote:
-
- git annex enableremote mys3 encryption=friend@example.com
+ special remote, by specifying new values for parameters that were
+ originally set when using initremote. (However, some settings such as
+ the as the encryption scheme cannot be changed once a special remote
+ has been created.)
+
+ 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 ...]
diff --git a/doc/special_remotes/S3.mdwn b/doc/special_remotes/S3.mdwn
index e15361f3e..5291a4eb6 100644
--- a/doc/special_remotes/S3.mdwn
+++ b/doc/special_remotes/S3.mdwn
@@ -15,13 +15,10 @@ can read inside the local git repository.
A number of parameters can be passed to `git annex initremote` to configure
the S3 remote.
-* `encryption` - Required. Either "none" to disable encryption (not recommended),
- 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).
+* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
+ See [[encryption]].
- Note that additional gpg keys can be given access to a remote by
- running enableremote with the new key id. See [[encryption]].
+* `keyid` - Specifies the gpg key to use for [[encryption]].
* `embedcreds` - Optional. Set to "yes" embed the login credentials inside
the git repository, which allows other clones to also access them. This is
diff --git a/doc/special_remotes/bup.mdwn b/doc/special_remotes/bup.mdwn
index f323237b1..f2d465e77 100644
--- a/doc/special_remotes/bup.mdwn
+++ b/doc/special_remotes/bup.mdwn
@@ -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:
-* `encryption` - Required. Either "none" to disable encryption of content
- stored in bup (ssh will still be used to transport it securely),
- 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
- running enableremote with the new key id. See [[encryption]].
+* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
+ See [[encryption]].
+
+* `keyid` - Specifies the gpg key to use for [[encryption]].
* `buprepo` - Required. This is passed to `bup` as the `--remote`
to use to store data. To create the repository,`bup init` will be run.
diff --git a/doc/special_remotes/directory.mdwn b/doc/special_remotes/directory.mdwn
index 4f9263dc0..4d72e8bee 100644
--- a/doc/special_remotes/directory.mdwn
+++ b/doc/special_remotes/directory.mdwn
@@ -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
remote:
-* `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 decrypt the encrypted data.
+* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
+ See [[encryption]].
- Note that additional gpg keys can be given access to a remote by
- running enableremote with the new key id. See [[encryption]].
+* `keyid` - Specifies the gpg key to use for [[encryption]].
* `chunksize` - Avoid storing files larger than the specified size in the
directory. For use on directories on mount points that have file size
diff --git a/doc/special_remotes/glacier.mdwn b/doc/special_remotes/glacier.mdwn
index d6dbad59a..b55b9adbb 100644
--- a/doc/special_remotes/glacier.mdwn
+++ b/doc/special_remotes/glacier.mdwn
@@ -21,13 +21,10 @@ can read inside the local git repository.
A number of parameters can be passed to `git annex initremote` to configure
the Glacier remote.
-* `encryption` - Required. Either "none" to disable encryption (not recommended),
- 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).
+* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
+ See [[encryption]].
- Note that additional gpg keys can be given access to a remote by
- running enableremote with the new key id. See [[encryption]].
+* `keyid` - Specifies the gpg key to use for [[encryption]].
* `embedcreds` - Optional. Set to "yes" embed the login credentials inside
the git repository, which allows other clones to also access them. This is
diff --git a/doc/special_remotes/hook.mdwn b/doc/special_remotes/hook.mdwn
index d5c708e0a..eaea940a7 100644
--- a/doc/special_remotes/hook.mdwn
+++ b/doc/special_remotes/hook.mdwn
@@ -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`:
-* `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
this remote.
+* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
+ See [[encryption]].
+
+* `keyid` - Specifies the gpg key to use for [[encryption]].
+
## hooks
Each type of hook remote is specified by a collection of hook commands.
diff --git a/doc/special_remotes/rsync.mdwn b/doc/special_remotes/rsync.mdwn
index 581ac6b96..b2a9d23f5 100644
--- a/doc/special_remotes/rsync.mdwn
+++ b/doc/special_remotes/rsync.mdwn
@@ -2,26 +2,22 @@ This special remote type rsyncs file contents to somewhere else.
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"
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"
## configuration
These parameters can be passed to `git annex initremote` to configure rsync:
-* `encryption` - Required. Either "none" to disable encryption of content
- stored on the remote rsync server,
- 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.
+* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
+ See [[encryption]].
- Note that additional gpg keys can be given access to a remote by
- running enableremote with the new key id. See [[encryption]].
+* `keyid` - Specifies the gpg key to use for [[encryption]].
* `rsyncurl` - Required. This is the url or `hostname:/directory` to
pass to rsync to tell it where to store content.
diff --git a/doc/special_remotes/webdav.mdwn b/doc/special_remotes/webdav.mdwn
index 383fddf75..251075b09 100644
--- a/doc/special_remotes/webdav.mdwn
+++ b/doc/special_remotes/webdav.mdwn
@@ -10,13 +10,10 @@ can read inside the local git repository.
A number of parameters can be passed to `git annex initremote` to configure
the webdav remote.
-* `encryption` - Required. Either "none" to disable encryption (not recommended),
- 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).
+* `encryption` - One of "none", "hybrid", "shared", or "pubkey".
+ See [[encryption]].
- Note that additional gpg keys can be given access to a remote by
- running enableremote with the new key id. See [[encryption]].
+* `keyid` - Specifies the gpg key to use for [[encryption]].
* `embedcreds` - Optional. Set to "yes" embed the login credentials inside
the git repository, which allows other clones to also access them. This is
@@ -42,4 +39,4 @@ the webdav remote.
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
diff --git a/doc/tips/using_Amazon_Glacier.mdwn b/doc/tips/using_Amazon_Glacier.mdwn
index 5e7131eeb..5aac7fa91 100644
--- a/doc/tips/using_Amazon_Glacier.mdwn
+++ b/doc/tips/using_Amazon_Glacier.mdwn
@@ -16,7 +16,7 @@ like "2512E3C7"
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
The configuration for the Glacier remote is stored in git. So to make another
diff --git a/doc/tips/using_Amazon_S3.mdwn b/doc/tips/using_Amazon_S3.mdwn
index 19997d026..0c68c7387 100644
--- a/doc/tips/using_Amazon_S3.mdwn
+++ b/doc/tips/using_Amazon_S3.mdwn
@@ -14,7 +14,7 @@ like "2512E3C7"
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
# git annex describe cloud "at Amazon's US datacenter"
describe cloud ok
diff --git a/doc/tips/using_box.com_as_a_special_remote.mdwn b/doc/tips/using_box.com_as_a_special_remote.mdwn
index 6616d0a1e..254d0a554 100644
--- a/doc/tips/using_box.com_as_a_special_remote.mdwn
+++ b/doc/tips/using_box.com_as_a_special_remote.mdwn
@@ -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]].
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
breaks up large files into chunks before that limit is reached.