From 53ce59021adfa5a5fa1d624982c570928786d088 Mon Sep 17 00:00:00 2001 From: guilhem Date: Wed, 28 Aug 2013 04:24:14 +0200 Subject: [PATCH 01/18] Allow revocation of OpenPGP keys. /!\ It is to be noted that revoking a key does NOT necessarily prevent the owner of its private part from accessing data on the remote /!\ The only sound use of `keyid-=` is probably to replace a (sub-)key by another, where the private part of both is owned by the same person/entity: git annex enableremote myremote keyid-=2512E3C7 keyid+=788A3F4C Reference: http://git-annex.branchable.com/bugs/Using_a_revoked_GPG_key/ * Other change introduced by this patch: New keys now need to be added with option `keyid+=`, and the scheme specified (upon initremote only) with `encryption=`. The motivation for this change is to open for new schemes, e.g., strict asymmetric encryption. git annex initremote myremote encryption=hybrid keyid=2512E3C7 git annex enableremote myremote keyid+=788A3F4C --- Crypto.hs | 21 ++++++++++++------ Remote/Helper/Encryptable.hs | 42 ++++++++++++++++++++++-------------- Test.hs | 2 +- Utility/Gpg.hs | 2 +- doc/design/encryption.mdwn | 12 ++++++----- doc/encryption.mdwn | 24 ++++++++++++++------- doc/git-annex.mdwn | 25 +++++++++++++++------ 7 files changed, 83 insertions(+), 45 deletions(-) diff --git a/Crypto.hs b/Crypto.hs index 21b1ae41b5..a86f9f9760 100644 --- a/Crypto.hs +++ b/Crypto.hs @@ -78,15 +78,22 @@ 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 _ (KeyIds ks)) = do + dropKeys <- listKeyIds [ k | (False, k) <- newkeys ] + forM_ dropKeys $ \k -> unless (k `elem` ks) $ + error $ "Key " ++ k ++ " is not granted access." + addKeys <- listKeyIds [ k | (True, k) <- newkeys ] + let ks' = (addKeys ++ ks) \\ dropKeys + when (null ks') $ error "The new access list would become empty." cipher <- decryptCipher encipher - encryptCipher cipher (merge ks ks') + encryptCipher cipher $ KeyIds ks' where - merge (KeyIds a) (KeyIds b) = KeyIds $ a ++ b + listKeyIds = mapM (Gpg.findPubKeys >=*> keyIds) >=*> concat describeCipher :: StorableCipher -> String describeCipher (SharedCipher _) = "shared cipher" diff --git a/Remote/Helper/Encryptable.hs b/Remote/Helper/Encryptable.hs index 22e1c9fc0e..63efcb378c 100644 --- a/Remote/Helper/Encryptable.hs +++ b/Remote/Helper/Encryptable.hs @@ -23,27 +23,37 @@ 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 + Just "none" -> return c + Just "shared" -> use "encryption setup" . genSharedCipher + =<< highRandomQuality + -- hybrid encryption by default + _ | maybe True (== "hybrid") encryption -> + use "encryption setup" . genEncryptedCipher key + =<< highRandomQuality + _ -> error "Specify encryption=none or encryption=shared or encryption=hybrid (default)." + 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) + -- Update an existing cipher if possible. + updateCipher v + | isJust encryption = error "Cannot set encryption type of existing remote." + | otherwise = case v of + SharedCipher{} -> return c + EncryptedCipher{} -> + use "encryption update" $ updateEncryptedCipher newkeys v use m a = do showNote m cipher <- liftIO a showNote $ describeCipher cipher - return $ M.delete "encryption" $ M.delete "highRandomQuality" $ - storeCipher c cipher + return $ flip storeCipher cipher $ foldr M.delete c + [ "keyid", "keyid+", "keyid-" + , "encryption", "highRandomQuality" ] highRandomQuality = (&&) (maybe True ( /= "false") $ M.lookup "highRandomQuality" c) <$> fmap not (Annex.getState Annex.fast) diff --git a/Test.hs b/Test.hs index 3eb330c226..b7b80f9146 100644 --- a/Test.hs +++ b/Test.hs @@ -880,7 +880,7 @@ test_crypto env = "git-annex crypto" ~: intmpclonerepo env $ whenM (Utility.Path let a cmd = git_annex env cmd [ "foo" , "type=directory" - , "encryption=" ++ Utility.Gpg.testKeyId + , "keyid=" ++ Utility.Gpg.testKeyId , "directory=dir" , "highRandomQuality=false" ] diff --git a/Utility/Gpg.hs b/Utility/Gpg.hs index 81180148e5..291b06e1c2 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. diff --git a/doc/design/encryption.mdwn b/doc/design/encryption.mdwn index 6a380abe1a..377de476e5 100644 --- a/doc/design/encryption.mdwn +++ b/doc/design/encryption.mdwn @@ -103,14 +103,16 @@ 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 +revoke a key with `keyid-=`, it is designed for a +[[completely_different_purpose|encryption]]. 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 d93bee9d27..6463827afe 100644 --- a/doc/encryption.mdwn +++ b/doc/encryption.mdwn @@ -6,8 +6,9 @@ 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, +Such an encrypted remote uses strong +[[symmetric_encryptiondesign/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. @@ -34,18 +35,25 @@ 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 + git annex enableremote myremote keyid+=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. +While a key can later be removed from the list, it is to be noted that +it does **not** necessarily prevent the owner of the private material +from accessing data on the remote (which is by design impossible, short +of deleting the remote). In fact the only sound use of `keyid-=` is +probably to replace a (sub-)key by another, where the private part of +both is owned by the same person/entity: + + git annex enableremote myremote keyid-=2512E3C7 keyid+=788A3F4C + +See also [[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 +**unencrypted** in the git repository. So it's shared among 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 diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn index 7cac9087d1..832a3cd686 100644 --- a/doc/git-annex.mdwn +++ b/doc/git-annex.mdwn @@ -312,10 +312,11 @@ subdirectories). (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. + 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: @@ -336,10 +337,20 @@ subdirectories). 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: + set when using initremote. With the exception of some configuration values such + as the encryption scheme scheme, which cannot be changed once the + remote has been created. - git annex enableremote mys3 encryption=friend@example.com + If encryption is enabled and the remote's access limited to one or + more OpenPGP key(s), it is possible to give access to another key ID + by specifing the keyid+= parameter. While a key can later be removed + from the list, it is to be noted that it does NOT necessarily prevent + the owner of the private material from accessing data on the remote + (which is by design impossible, short of deleting the remote); + however, a fine use-case of keyid-= is to replace a revoked key by + a new one superseeding it: + + git annex enableremote mys3 keyid-=revokedkey keyid+=newkey * trust [repository ...] From f8082933e73146361ecc04698039efa1a126ef62 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 29 Aug 2013 14:32:25 -0400 Subject: [PATCH 02/18] clarify --- doc/design/encryption.mdwn | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/design/encryption.mdwn b/doc/design/encryption.mdwn index 377de476e5..cc0dd16842 100644 --- a/doc/design/encryption.mdwn +++ b/doc/design/encryption.mdwn @@ -107,8 +107,9 @@ 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 -revoke a key with `keyid-=`, it is designed for a -[[completely_different_purpose|encryption]]. +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 From 8293ed619ffc637bcea372798cc9b987dda3b3db Mon Sep 17 00:00:00 2001 From: guilhem Date: Sun, 1 Sep 2013 20:12:00 +0200 Subject: [PATCH 03/18] Allow public-key encryption of file content. With the initremote parameters "encryption=pubkey keyid=788A3F4C". /!\ 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 unless the point is to replace a (sub-)key by another. /!\ Also, a test case has been added to ensure that the cipher and file contents are encrypted as specified by the chosen encryption scheme. --- Creds.hs | 2 +- Crypto.hs | 76 +++++++++++---------- Remote/Bup.hs | 2 +- Remote/Directory.hs | 4 +- Remote/Glacier.hs | 2 +- Remote/Helper/Encryptable.hs | 64 ++++++++++++------ Remote/Hook.hs | 4 +- Remote/Rsync.hs | 4 +- Remote/S3.hs | 2 +- Remote/WebDAV.hs | 2 +- Test.hs | 52 +++++++++++++-- Types/Crypto.hs | 10 ++- Utility/Gpg.hs | 124 ++++++++++++++++++++++++++++------- Utility/Gpg/Types.hs | 30 --------- debian/copyright | 4 -- doc/encryption.mdwn | 44 ++++++++++--- doc/git-annex.mdwn | 21 ++++-- 17 files changed, 307 insertions(+), 140 deletions(-) delete mode 100644 Utility/Gpg/Types.hs diff --git a/Creds.hs b/Creds.hs index 7791ce85df..588d67cfe8 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 a86f9f9760..2a2dd3cf2d 100644 --- a/Crypto.hs +++ b/Crypto.hs @@ -23,8 +23,7 @@ module Crypto ( readBytes, encrypt, decrypt, - GpgOpts(..), - getGpgOpts, + Gpg.getGpgEncParams, prop_HmacSha1WithCipher_sane ) where @@ -35,7 +34,6 @@ import Control.Applicative import Common.Annex import qualified Utility.Gpg as Gpg -import Utility.Gpg.Types import Types.Key import Types.Crypto @@ -66,12 +64,17 @@ cipherPassphrase (Cipher c) = drop cipherBeginning c 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 +{- Creates a new Cipher, encrypted to the specified key id. If the + - boolean 'symmetric' is true, use that cipher not only for MAC'ing, + - but also to symmetrically encrypt annexed file contents. Otherwise, + - we don't bother to generate so much random data. -} +genEncryptedCipher :: String -> Bool -> Bool -> IO StorableCipher +genEncryptedCipher keyid symmetric highQuality = do ks <- Gpg.findPubKeys keyid - random <- Gpg.genRandom highQuality cipherSize - encryptCipher (Cipher random) ks + random <- Gpg.genRandom highQuality size + encryptCipher (Cipher random) symmetric ks + where + size = if symmetric then cipherSize else cipherBeginning {- Creates a new, shared Cipher. -} genSharedCipher :: Bool -> IO StorableCipher @@ -83,44 +86,45 @@ genSharedCipher highQuality = updateEncryptedCipher :: [(Bool, String)] -> StorableCipher -> IO StorableCipher updateEncryptedCipher _ SharedCipher{} = undefined updateEncryptedCipher [] encipher = return encipher -updateEncryptedCipher newkeys encipher@(EncryptedCipher _ (KeyIds ks)) = do +updateEncryptedCipher newkeys encipher@(EncryptedCipher _ symmetric (KeyIds ks)) = do dropKeys <- listKeyIds [ k | (False, k) <- newkeys ] forM_ dropKeys $ \k -> unless (k `elem` ks) $ error $ "Key " ++ k ++ " is not granted access." addKeys <- listKeyIds [ k | (True, k) <- newkeys ] let ks' = (addKeys ++ ks) \\ dropKeys - when (null ks') $ error "The new access list would become empty." + when (null ks') $ error "That would empty the access list." cipher <- decryptCipher encipher - encryptCipher cipher $ KeyIds ks' + encryptCipher cipher symmetric $ KeyIds ks' where listKeyIds = mapM (Gpg.findPubKeys >=*> keyIds) >=*> concat describeCipher :: StorableCipher -> String -describeCipher (SharedCipher _) = "shared cipher" -describeCipher (EncryptedCipher _ (KeyIds ks)) = - "with gpg " ++ keys ks ++ " " ++ unwords ks +describeCipher SharedCipher{} = "shared cipher" +describeCipher (EncryptedCipher _ symmetric (KeyIds ks)) = + scheme ++ " with gpg " ++ keys ks ++ " " ++ unwords ks where + scheme = if symmetric then "hybrid cipher" else "pubkey crypto" keys [_] = "key" keys _ = "keys" -{- Encrypts a Cipher to the specified KeyIds. -} -encryptCipher :: Cipher -> KeyIds -> IO StorableCipher -encryptCipher (Cipher c) (KeyIds ks) = do +{- Encrypts a Cipher to the specified KeyIds. The boolean indicates + - whether to encrypt an hybrid cipher (True), which is going to be used + - both for MAC'ing and symmetric encryption of file contents, or for + - MAC'ing only (False), while pubkey crypto is used for file contents. + - -} +encryptCipher :: Cipher -> Bool -> KeyIds -> IO StorableCipher +encryptCipher (Cipher c) symmetric (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" + -- The cipher itself is always encrypted to the given public keys + let params = Gpg.pkEncTo ks' ++ Gpg.stdEncryptionParams False + encipher <- Gpg.pipeStrict params c + return $ EncryptedCipher encipher symmetric (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 @@ -146,15 +150,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 + - 'getGpgEncOpts'). -} +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 diff --git a/Remote/Bup.hs b/Remote/Bup.hs index 9b3675cfa5..9ef335218d 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 6cc75d2f1d..0b3ce443b4 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 c1a53347d8..d81066415a 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 63efcb378c..2f72fb4179 100644 --- a/Remote/Helper/Encryptable.hs +++ b/Remote/Helper/Encryptable.hs @@ -29,34 +29,46 @@ encryptionSetup c = maybe genCipher updateCipher $ extractCipher c 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 by default _ | maybe True (== "hybrid") encryption -> - use "encryption setup" . genEncryptedCipher key + use "encryption setup" . genEncryptedCipher key True =<< highRandomQuality - _ -> error "Specify encryption=none or encryption=shared or encryption=hybrid (default)." + Just "pubkey" -> use "encryption setup" . genEncryptedCipher key False + =<< highRandomQuality + _ -> error $ "Specify " ++ intercalate " or " + (map ("encryption=" ++) + ["none","shared","hybrid (default)","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 - | isJust encryption = error "Cannot set encryption type of existing remote." - | otherwise = case v of - SharedCipher{} -> return c - EncryptedCipher{} -> - use "encryption update" $ updateEncryptedCipher newkeys v + updateCipher v = case v of + SharedCipher{} | maybe True (== "shared") encryption -> return c' + EncryptedCipher _ symmetric _ + | maybe True (== if symmetric 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 $ flip storeCipher cipher $ foldr M.delete c - [ "keyid", "keyid+", "keyid-" - , "encryption", "highRandomQuality" ] + 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. - @@ -121,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) True (readkeys ks) + (Just t, Just ks, Just "pubkey") -> + Just $ EncryptedCipher (fromB64 t) False (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 03f182a160..338d95ce7a 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 a8efd84e7e..4ad0fdadd5 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 582bc2fdab..ce5a1c2eb5 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 (getGpgEncOpts 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 52fc32b3a7..4c3bb5c493 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 (getGpgEncOpts r) cipher (streamMeteredFile src meterupdate) $ readBytes $ storeHelper r enck baseurl user pass diff --git a/Test.hs b/Test.hs index b7b80f9146..f192621534 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 @@ -40,6 +41,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 @@ -872,18 +874,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" - , "keyid=" ++ 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" @@ -891,6 +896,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 @@ -898,8 +913,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 on 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 True + Just cip@(Crypto.EncryptedCipher encipher sym ks') + | checkScheme sym && keysMatch ks' -> + checkKeys cip sym <&&> 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 True = scheme == "hybrid" + checkScheme False = scheme == "pubkey" + checkKeys cip sym = do + cipher <- Crypto.decryptCipher cip + files <- filterM doesFileExist $ + map ("dir" ) $ concatMap (key2files cipher) keys + return (not $ null files) <&&> allM (checkFile sym) files + checkFile sym filename = + Utility.Gpg.checkEncryptionFile filename $ + if sym then Nothing else ks + 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 e97d02ba8e..ee61d08637 100644 --- a/Types/Crypto.hs +++ b/Types/Crypto.hs @@ -24,7 +24,15 @@ 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 Bool KeyIds + -- ^ The Boolean indicates whether the cipher is used + -- both for symmetric encryption of file content and + -- MAC'ing of file names (True), or only for MAC'ing, + -- while file content is encrypted using public-key + -- crypto (False). In the latter case the cipher is + -- twice as short, but we don't want to rely on that + -- only. + | SharedCipher String deriving (Ord, Eq) {- File names are (client-side) MAC'ed on special remotes. diff --git a/Utility/Gpg.hs b/Utility/Gpg.hs index 291b06e1c2..5056e1ce21 100644 --- a/Utility/Gpg.hs +++ b/Utility/Gpg.hs @@ -5,7 +5,7 @@ - Licensed under the GNU GPL version 3 or higher. -} -{-# LANGUAGE CPP #-} +{-# LANGUAGE CPP, FlexibleInstances #-} module Utility.Gpg where @@ -24,6 +24,10 @@ import Utility.Env import Utility.Tmp #endif +import qualified Data.Map as M +import Types.GitConfig +import Types.Remote hiding (setup) + newtype KeyIds = KeyIds { keyIds :: [String] } deriving (Ord, Eq) @@ -32,6 +36,28 @@ newtype KeyIds = KeyIds { keyIds :: [String] } gpgcmd :: FilePath gpgcmd = fromMaybe "gpg" SysConfig.gpg +{- 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" -> pkEncTo $ maybe [] (split ",") $ + M.lookup "cipherkeys" c + _ -> [] + +-- Generate an argument list to asymetrically encrypt to the given recipients. +pkEncTo :: [String] -> [CommandParam] +pkEncTo = concatMap (\r -> [Param "--recipient", Param r]) + +{- Extract the GnuPG options from a Remote. -} +instance LensGpgEncParams (RemoteA a) where + getGpgEncParams r = getGpgEncParams (config r, gitconfig r) + stdParams :: [CommandParam] -> IO [String] stdParams params = do #ifndef mingw32_HOST_OS @@ -48,9 +74,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 +109,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 +121,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 +300,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 d457072074..0000000000 --- a/Utility/Gpg/Types.hs +++ /dev/null @@ -1,30 +0,0 @@ -{- gpg data types - - - - Copyright 2013 guilhem - - - - 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/copyright b/debian/copyright index 5a667adf74..e91cddbb5c 100644 --- a/debian/copyright +++ b/debian/copyright @@ -14,10 +14,6 @@ Copyright: 2011 Bas van Dijk & Roel van Dijk 2012 Joey Hess License: GPL-3+ -Files: Utility/Gpg/Types.hs -Copyright: 2013 guilhem -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 2010 Joey Hess diff --git a/doc/encryption.mdwn b/doc/encryption.mdwn index 6463827afe..2069ee3bbb 100644 --- a/doc/encryption.mdwn +++ b/doc/encryption.mdwn @@ -6,21 +6,23 @@ 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 -[[symmetric_encryptiondesign/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. -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. +If you want to generate a cipher that will be used to symmetrically +encrypt file contents, run `git annex initremote` with +"encryption=hybrid keyid=USERID". The value will be 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. The default MAC algorithm to be applied on the filenames is HMACSHA1. A stronger one, for instance HMACSHA512, one can be chosen upon creation @@ -61,3 +63,27 @@ stored in the special remote. To use shared encryption, specify "encryption=shared" when first setting up a special remote. + +## strict public-key encryption + +Special remotes can also be configured to encrypt file contents using +public-key cryptography. It is significatly slower than symmetric +encryption, but is also generally considered more secure. Note that +because filenames are MAC'ed, a cipher needs to be generated (and +encrypted to the given key ID). + +A disavantage is that is not possible to give/revoke anyone's access to +a non-empty remote. Indeed, although the parameters `keyid+=` and +`keyid-=` still apply, they have **no effect** on files that are already +present on the remote. In fact the only sound use of `keyid+=` and +`keyid-=` is probably, as `keyid-=` for "encryption=hybrid", to replace +a (sub-)key by another. + +Also, since already uploaded files are not re-encrypted, one needs to +keep the private part of removed keys (with `keyid-=`) to be able to +decrypt these files. On the other hand, 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. + +To use strict public-key encryption, specify "encryption=pubkey +keyid=USERID" when first setting up a special remote. diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn index 832a3cd686..fa74f77d77 100644 --- a/doc/git-annex.mdwn +++ b/doc/git-annex.mdwn @@ -308,9 +308,15 @@ subdirectories). 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. + encryption=none to disable encryption, or encryption=shared to use a + shared cipher (stored clear in the git repository), or + encryption=hybrid to encrypt the cipher to an OpenPGP key, or + encryption=pubkey to encrypt file contents using public-key + cryptography. In the two last cases, you also need to specify which + key can access the encrypted special remote, which is done by + specifiying keyid= (gpg needs to be to be able to find a public key + matching that specification, which can be an OpenPGP key ID or an + e-mail address for instance). Note that with encryption enabled, a cryptographic key is created. This requires sufficient entropy. If initremote seems to hang or take @@ -320,7 +326,7 @@ subdirectories). 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 ...] @@ -352,6 +358,13 @@ subdirectories). git annex enableremote mys3 keyid-=revokedkey keyid+=newkey + Also, note that for encrypted special remotes using strict 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 unless the private material of the old and new + access list is all owned by the same (group of) person. + * trust [repository ...] Records that a repository is trusted to not unexpectedly lose From 1587fd42a33923549d07c1e2d70e8207e92c399a Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 18:00:02 -0400 Subject: [PATCH 04/18] fix build (seems getGpgEncOpts got renamed to getGpgEncParams) --- Crypto.hs | 2 +- Remote/S3.hs | 2 +- Remote/WebDAV.hs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Crypto.hs b/Crypto.hs index 2a2dd3cf2d..ed558d8c3d 100644 --- a/Crypto.hs +++ b/Crypto.hs @@ -155,7 +155,7 @@ readBytes a h = L.hGetContents h >>= a - 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 - - 'getGpgEncOpts'). -} + - 'getGpgEncParams'). -} encrypt :: [CommandParam] -> Cipher -> Feeder -> Reader a -> IO a encrypt params cipher = Gpg.feedRead params' pass where diff --git a/Remote/S3.hs b/Remote/S3.hs index ce5a1c2eb5..814dd3a239 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 (getGpgEncOpts 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 4c3bb5c493..c444a7f2bb 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 (getGpgEncOpts r) cipher + liftIO $ encrypt (getGpgEncParams r) cipher (streamMeteredFile src meterupdate) $ readBytes $ storeHelper r enck baseurl user pass From 6f531d903dd7d1f321acbc798411c9b6863e2ac6 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 18:15:38 -0400 Subject: [PATCH 05/18] detailed changelog for the encryption changes --- debian/changelog | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/debian/changelog b/debian/changelog index f880fbcf76..fb24feabe0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -11,6 +11,23 @@ git-annex (4.20130828) UNRELEASED; urgency=low from feeds. * Honor core.sharedrepository when receiving and adding files in direct mode. + * 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.) + * initremote: A new encryption scheme can be selected with + "encryption=pubkey keyid=$KEYID" + This causes files placed on special remotes to be directly + encrypted to the specified key(s). It is not possible to add + other keys later and have them immedately be able to access those + encrypted files, like can be done with the default encryption scheme. + However, pubkey encryption may be considered more secure due to being + and more standard use of gpg that avoids symmetric encryption algorythms, + or just simpler if you're the only person you ever intend to use a + particular encrypted special remote. + (Thanks, guilhem for the patch.) + + -- Joey Hess Tue, 27 Aug 2013 11:03:00 -0400 git-annex (4.20130827) unstable; urgency=low From b17defd43ab7cec4fc1c608cfe6672a361d26234 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 18:39:41 -0400 Subject: [PATCH 06/18] reworded encryption stuff on man page, hopefully clearer and less jargon --- doc/git-annex.mdwn | 54 +++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn index ec972a5815..7afe5fd13c 100644 --- a/doc/git-annex.mdwn +++ b/doc/git-annex.mdwn @@ -307,16 +307,20 @@ 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 encryption=shared to use a - shared cipher (stored clear in the git repository), or - encryption=hybrid to encrypt the cipher to an OpenPGP key, or - encryption=pubkey to encrypt file contents using public-key - cryptography. In the two last cases, you also need to specify which - key can access the encrypted special remote, which is done by - specifiying keyid= (gpg needs to be to be able to find a public key - matching that specification, which can be an OpenPGP key ID or an - e-mail address for instance). + 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 @@ -342,28 +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. With the exception of some configuration values such - as the encryption scheme scheme, which cannot be changed once the - remote has been created. + 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.) - If encryption is enabled and the remote's access limited to one or - more OpenPGP key(s), it is possible to give access to another key ID - by specifing the keyid+= parameter. While a key can later be removed - from the list, it is to be noted that it does NOT necessarily prevent - the owner of the private material from accessing data on the remote - (which is by design impossible, short of deleting the remote); - however, a fine use-case of keyid-= is to replace a revoked key by - a new one superseeding it: + 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 strict public-key + 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 unless the private material of the old and new - access list is all owned by the same (group of) person. + make little sense except in cases like the revoked key example above. * trust [repository ...] From 6e09893160d2bda2812a73fc3e0d3faaef66c62f Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 19:31:36 -0400 Subject: [PATCH 07/18] reword changelog --- debian/changelog | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/debian/changelog b/debian/changelog index fb24feabe0..1f2a04d2e0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -11,20 +11,16 @@ 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.) - * initremote: A new encryption scheme can be selected with - "encryption=pubkey keyid=$KEYID" - This causes files placed on special remotes to be directly - encrypted to the specified key(s). It is not possible to add - other keys later and have them immedately be able to access those - encrypted files, like can be done with the default encryption scheme. - However, pubkey encryption may be considered more secure due to being - and more standard use of gpg that avoids symmetric encryption algorythms, - or just simpler if you're the only person you ever intend to use a - particular encrypted special remote. + * 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 Tue, 27 Aug 2013 11:03:00 -0400 From b30a32209393904e56107ccae3377352e795c3ec Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 20:11:25 -0400 Subject: [PATCH 08/18] reword docs --- doc/encryption.mdwn | 138 ++++++++++++++++++++++++-------------------- 1 file changed, 74 insertions(+), 64 deletions(-) diff --git a/doc/encryption.mdwn b/doc/encryption.mdwn index 2069ee3bbb..cea46045ee 100644 --- a/doc/encryption.mdwn +++ b/doc/encryption.mdwn @@ -15,14 +15,81 @@ 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 generate a cipher that will be used to symmetrically -encrypt file contents, run `git annex initremote` with -"encryption=hybrid keyid=USERID". The value will be 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 initremote newremote type=... encryption=hybrid keyid=KEYID ...` +* `git annex initremote newremote type=... encryption=shared` +* `git annex initremote newremote type=... encryption=pubkey keyid=KEYID ...` + +## 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 stronger one, for instance HMACSHA512, one can be chosen upon creation @@ -30,60 +97,3 @@ 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. - -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 keyid+=788A3F4C - -While a key can later be removed from the list, it is to be noted that -it does **not** necessarily prevent the owner of the private material -from accessing data on the remote (which is by design impossible, short -of deleting the remote). In fact the only sound use of `keyid-=` is -probably to replace a (sub-)key by another, where the private part of -both is owned by the same person/entity: - - git annex enableremote myremote keyid-=2512E3C7 keyid+=788A3F4C - -See also [[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 among 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. - -## strict public-key encryption - -Special remotes can also be configured to encrypt file contents using -public-key cryptography. It is significatly slower than symmetric -encryption, but is also generally considered more secure. Note that -because filenames are MAC'ed, a cipher needs to be generated (and -encrypted to the given key ID). - -A disavantage is that is not possible to give/revoke anyone's access to -a non-empty remote. Indeed, although the parameters `keyid+=` and -`keyid-=` still apply, they have **no effect** on files that are already -present on the remote. In fact the only sound use of `keyid+=` and -`keyid-=` is probably, as `keyid-=` for "encryption=hybrid", to replace -a (sub-)key by another. - -Also, since already uploaded files are not re-encrypted, one needs to -keep the private part of removed keys (with `keyid-=`) to be able to -decrypt these files. On the other hand, 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. - -To use strict public-key encryption, specify "encryption=pubkey -keyid=USERID" when first setting up a special remote. From 3999a860eb12339598fc037254bf7f5137fc665d Mon Sep 17 00:00:00 2001 From: guilhem Date: Thu, 5 Sep 2013 01:09:56 +0200 Subject: [PATCH 09/18] Encryption defaults to 'hybrid' When a keyid= is specified while encryption= is absent. --- Remote/Helper/Encryptable.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Remote/Helper/Encryptable.hs b/Remote/Helper/Encryptable.hs index 2f72fb4179..13e8a6b771 100644 --- a/Remote/Helper/Encryptable.hs +++ b/Remote/Helper/Encryptable.hs @@ -33,15 +33,16 @@ encryptionSetup c = maybe genCipher updateCipher $ extractCipher c Just "none" -> return c Just "shared" -> use "encryption setup" . genSharedCipher =<< highRandomQuality - -- hybrid encryption by default - _ | maybe True (== "hybrid") encryption -> + -- 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 True =<< highRandomQuality Just "pubkey" -> use "encryption setup" . genEncryptedCipher key False =<< highRandomQuality _ -> error $ "Specify " ++ intercalate " or " (map ("encryption=" ++) - ["none","shared","hybrid (default)","pubkey"]) + ["none","shared","hybrid","pubkey"]) ++ "." key = fromMaybe (error "Specifiy keyid=...") $ M.lookup "keyid" c newkeys = maybe [] (\k -> [(True,k)]) (M.lookup "keyid+" c) ++ From 57a15425e806b6791374b0d95d3566e660f20adc Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 21:54:10 -0400 Subject: [PATCH 10/18] wording --- Crypto.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Crypto.hs b/Crypto.hs index ed558d8c3d..f8628abb14 100644 --- a/Crypto.hs +++ b/Crypto.hs @@ -89,10 +89,11 @@ 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 ++ " is not granted access." + error $ "Key " ++ k ++ " was not present; cannot remove." addKeys <- listKeyIds [ k | (True, k) <- newkeys ] let ks' = (addKeys ++ ks) \\ dropKeys - when (null ks') $ error "That would empty the access list." + when (null ks') $ + error "Cannot remove the last key." cipher <- decryptCipher encipher encryptCipher cipher symmetric $ KeyIds ks' where @@ -108,7 +109,7 @@ describeCipher (EncryptedCipher _ symmetric (KeyIds ks)) = keys _ = "keys" {- Encrypts a Cipher to the specified KeyIds. The boolean indicates - - whether to encrypt an hybrid cipher (True), which is going to be used + - whether to encrypt a hybrid cipher (True), which is going to be used - both for MAC'ing and symmetric encryption of file contents, or for - MAC'ing only (False), while pubkey crypto is used for file contents. - -} From 930e6d22d6c0876bd808ec4ede4ac05fb7d07ea4 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 22:18:33 -0400 Subject: [PATCH 11/18] replace an over-explained Bool with a data type This also highlights several places where a Read/Show or similar for the new data type could avoid redundant strings. --- Crypto.hs | 41 +++++++++++++++++------------------- Remote/Helper/Encryptable.hs | 15 ++++++------- Test.hs | 20 +++++++++--------- Types/Crypto.hs | 12 ++++------- 4 files changed, 40 insertions(+), 48 deletions(-) diff --git a/Crypto.hs b/Crypto.hs index f8628abb14..7f65a9c0a9 100644 --- a/Crypto.hs +++ b/Crypto.hs @@ -38,9 +38,9 @@ import Types.Key import Types.Crypto {- 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 @@ -64,17 +64,16 @@ cipherPassphrase (Cipher c) = drop cipherBeginning c cipherMac :: Cipher -> String cipherMac (Cipher c) = take cipherBeginning c -{- Creates a new Cipher, encrypted to the specified key id. If the - - boolean 'symmetric' is true, use that cipher not only for MAC'ing, - - but also to symmetrically encrypt annexed file contents. Otherwise, - - we don't bother to generate so much random data. -} -genEncryptedCipher :: String -> Bool -> Bool -> IO StorableCipher -genEncryptedCipher keyid symmetric highQuality = do +{- Creates a new Cipher, encrypted to the specified key id. -} +genEncryptedCipher :: String -> EncryptedCipherVariant -> Bool -> IO StorableCipher +genEncryptedCipher keyid variant highQuality = do ks <- Gpg.findPubKeys keyid random <- Gpg.genRandom highQuality size - encryptCipher (Cipher random) symmetric ks + encryptCipher (Cipher random) variant ks where - size = if symmetric then cipherSize else cipherBeginning + 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 @@ -100,27 +99,25 @@ updateEncryptedCipher newkeys encipher@(EncryptedCipher _ symmetric (KeyIds ks)) listKeyIds = mapM (Gpg.findPubKeys >=*> keyIds) >=*> concat describeCipher :: StorableCipher -> String -describeCipher SharedCipher{} = "shared cipher" -describeCipher (EncryptedCipher _ symmetric (KeyIds ks)) = +describeCipher (SharedCipher _) = "shared cipher" +describeCipher (EncryptedCipher _ variant (KeyIds ks)) = scheme ++ " with gpg " ++ keys ks ++ " " ++ unwords ks where - scheme = if symmetric then "hybrid cipher" else "pubkey crypto" + scheme = case variant of + HybridCipher -> "hybrid cipher" + PubKeyCipher -> "pubkey crypto" keys [_] = "key" keys _ = "keys" -{- Encrypts a Cipher to the specified KeyIds. The boolean indicates - - whether to encrypt a hybrid cipher (True), which is going to be used - - both for MAC'ing and symmetric encryption of file contents, or for - - MAC'ing only (False), while pubkey crypto is used for file contents. - - -} -encryptCipher :: Cipher -> Bool -> KeyIds -> IO StorableCipher -encryptCipher (Cipher c) symmetric (KeyIds ks) = do +{- Encrypts a Cipher to the specified KeyIds. -} +encryptCipher :: Cipher -> EncryptedCipherVariant -> KeyIds -> IO StorableCipher +encryptCipher (Cipher c) variant (KeyIds ks) = do -- gpg complains about duplicate recipient keyids let ks' = nub $ sort ks -- The cipher itself is always encrypted to the given public keys let params = Gpg.pkEncTo ks' ++ Gpg.stdEncryptionParams False encipher <- Gpg.pipeStrict params c - return $ EncryptedCipher encipher symmetric (KeyIds ks') + return $ EncryptedCipher encipher variant (KeyIds ks') {- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -} decryptCipher :: StorableCipher -> IO Cipher diff --git a/Remote/Helper/Encryptable.hs b/Remote/Helper/Encryptable.hs index 13e8a6b771..01cefe8588 100644 --- a/Remote/Helper/Encryptable.hs +++ b/Remote/Helper/Encryptable.hs @@ -36,9 +36,9 @@ encryptionSetup c = maybe genCipher updateCipher $ extractCipher c -- 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 True + use "encryption setup" . genEncryptedCipher key HybridCipher =<< highRandomQuality - Just "pubkey" -> use "encryption setup" . genEncryptedCipher key False + Just "pubkey" -> use "encryption setup" . genEncryptedCipher key PubKeyCipher =<< highRandomQuality _ -> error $ "Specify " ++ intercalate " or " (map ("encryption=" ++) @@ -51,10 +51,9 @@ encryptionSetup c = maybe genCipher updateCipher $ extractCipher c -- Update an existing cipher if possible. updateCipher v = case v of SharedCipher{} | maybe True (== "shared") encryption -> return c' - EncryptedCipher _ symmetric _ - | maybe True (== if symmetric then "hybrid" else "pubkey") - encryption -> - use "encryption update" $ updateEncryptedCipher newkeys v + 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 @@ -162,9 +161,9 @@ 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) True (readkeys ks) + Just $ EncryptedCipher (fromB64 t) HybridCipher (readkeys ks) (Just t, Just ks, Just "pubkey") -> - Just $ EncryptedCipher (fromB64 t) False (readkeys ks) + Just $ EncryptedCipher (fromB64 t) PubKeyCipher (readkeys ks) (Just t, Nothing, encryption) | maybe True (== "shared") encryption -> Just $ SharedCipher (fromB64 t) _ -> Nothing diff --git a/Test.hs b/Test.hs index d9d1f066b8..7ac87af5bf 100644 --- a/Test.hs +++ b/Test.hs @@ -920,26 +920,26 @@ test_crypto env = "git-annex crypto" ~: TestList $ flip map ["shared","hybrid"," - that all keys are encrypted properly on 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 True - Just cip@(Crypto.EncryptedCipher encipher sym ks') - | checkScheme sym && keysMatch ks' -> - checkKeys cip sym <&&> checkCipher encipher 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 True = scheme == "hybrid" - checkScheme False = scheme == "pubkey" - checkKeys cip sym = do + 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 sym) files - checkFile sym filename = + return (not $ null files) <&&> allM (checkFile mvariant) files + checkFile mvariant filename = Utility.Gpg.checkEncryptionFile filename $ - if sym then Nothing else ks + if mvariant == Just Types.Crypto.PubKeyCipher then ks else Nothing key2files cipher = Locations.keyPaths . Crypto.encryptKey Types.Crypto.HmacSha1 cipher #else diff --git a/Types/Crypto.hs b/Types/Crypto.hs index ee61d08637..8a15ead166 100644 --- a/Types/Crypto.hs +++ b/Types/Crypto.hs @@ -8,6 +8,7 @@ module Types.Crypto ( Cipher(..), StorableCipher(..), + EncryptedCipherVariant(..), KeyIds(..), Mac(..), readMac, @@ -24,16 +25,11 @@ import Utility.Gpg (KeyIds(..)) -- XXX ideally, this would be a locked memory region newtype Cipher = Cipher String -data StorableCipher = EncryptedCipher String Bool KeyIds - -- ^ The Boolean indicates whether the cipher is used - -- both for symmetric encryption of file content and - -- MAC'ing of file names (True), or only for MAC'ing, - -- while file content is encrypted using public-key - -- crypto (False). In the latter case the cipher is - -- twice as short, but we don't want to rely on that - -- only. +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. - The chosen MAC algorithm needs to be same for all files stored on the From 15b8acb8852ca4795db7f9bfbaac603f967ef610 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 22:24:01 -0400 Subject: [PATCH 12/18] redundant comment --- Crypto.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/Crypto.hs b/Crypto.hs index 7f65a9c0a9..33eb38b5bc 100644 --- a/Crypto.hs +++ b/Crypto.hs @@ -114,7 +114,6 @@ encryptCipher :: Cipher -> EncryptedCipherVariant -> KeyIds -> IO StorableCipher encryptCipher (Cipher c) variant (KeyIds ks) = do -- gpg complains about duplicate recipient keyids let ks' = nub $ sort ks - -- The cipher itself is always encrypted to the given public keys let params = Gpg.pkEncTo ks' ++ Gpg.stdEncryptionParams False encipher <- Gpg.pipeStrict params c return $ EncryptedCipher encipher variant (KeyIds ks') From a51f1a4ee44c0cab345068d79d98285f4d022006 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 22:39:25 -0400 Subject: [PATCH 13/18] unimportant tweak fix something my internal haskell parser does a double take at --- Remote/Helper/Encryptable.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Remote/Helper/Encryptable.hs b/Remote/Helper/Encryptable.hs index 01cefe8588..70e3663ea0 100644 --- a/Remote/Helper/Encryptable.hs +++ b/Remote/Helper/Encryptable.hs @@ -50,7 +50,7 @@ encryptionSetup c = maybe genCipher updateCipher $ extractCipher 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' + 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 From 2b9f3cc17579572df490d11771e792b2d2ede618 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 22:47:53 -0400 Subject: [PATCH 14/18] tabs --- Remote/Helper/Encryptable.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Remote/Helper/Encryptable.hs b/Remote/Helper/Encryptable.hs index 70e3663ea0..29e51c0022 100644 --- a/Remote/Helper/Encryptable.hs +++ b/Remote/Helper/Encryptable.hs @@ -143,7 +143,7 @@ cipherKey c k = fmap make <$> remoteCipher c where make ciphertext = (cipContent ciphertext, encryptKey mac ciphertext k) cipContent - | M.lookup "encryption" c /= Just "pubkey" = id + | M.lookup "encryption" c /= Just "pubkey" = id | otherwise = const $ Cipher "" mac = fromMaybe defaultMac $ M.lookup "mac" c >>= readMac From ce53acf4fe40dea46b058ec42ada631927c27be9 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 22:53:58 -0400 Subject: [PATCH 15/18] wording --- Test.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test.hs b/Test.hs index 7ac87af5bf..9eb99379ec 100644 --- a/Test.hs +++ b/Test.hs @@ -917,7 +917,7 @@ test_crypto env = "git-annex crypto" ~: TestList $ flip map ["shared","hybrid"," annexed_present annexedfile where {- Ensure the configuration complies with the encryption scheme, and - - that all keys are encrypted properly on the given directory remote. -} + - 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 From 08f026e886a671be715eda88b8ab21c870df9449 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 23:16:33 -0400 Subject: [PATCH 16/18] keep Utility.Gpg free of dependencies on git-annex --- Crypto.hs | 26 ++++++++++++++++++++++++-- Utility/Gpg.hs | 24 +----------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Crypto.hs b/Crypto.hs index 33eb38b5bc..0ea12c6c19 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,8 +24,8 @@ module Crypto ( feedBytes, readBytes, encrypt, - decrypt, - Gpg.getGpgEncParams, + decrypt, + getGpgEncParams, prop_HmacSha1WithCipher_sane ) where @@ -31,11 +33,13 @@ 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 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 when using the hybrid @@ -175,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/Utility/Gpg.hs b/Utility/Gpg.hs index 5056e1ce21..e27de72182 100644 --- a/Utility/Gpg.hs +++ b/Utility/Gpg.hs @@ -5,7 +5,7 @@ - Licensed under the GNU GPL version 3 or higher. -} -{-# LANGUAGE CPP, FlexibleInstances #-} +{-# LANGUAGE CPP #-} module Utility.Gpg where @@ -24,10 +24,6 @@ import Utility.Env import Utility.Tmp #endif -import qualified Data.Map as M -import Types.GitConfig -import Types.Remote hiding (setup) - newtype KeyIds = KeyIds { keyIds :: [String] } deriving (Ord, Eq) @@ -36,28 +32,10 @@ newtype KeyIds = KeyIds { keyIds :: [String] } gpgcmd :: FilePath gpgcmd = fromMaybe "gpg" SysConfig.gpg -{- 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" -> pkEncTo $ maybe [] (split ",") $ - M.lookup "cipherkeys" c - _ -> [] - -- Generate an argument list to asymetrically encrypt to the given recipients. pkEncTo :: [String] -> [CommandParam] pkEncTo = concatMap (\r -> [Param "--recipient", Param r]) -{- Extract the GnuPG options from a Remote. -} -instance LensGpgEncParams (RemoteA a) where - getGpgEncParams r = getGpgEncParams (config r, gitconfig r) - stdParams :: [CommandParam] -> IO [String] stdParams params = do #ifndef mingw32_HOST_OS From c99902546e6158ca16e9443c98ffeaf1d0b6491c Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 23:24:42 -0400 Subject: [PATCH 17/18] close --- doc/bugs/Using_a_revoked_GPG_key.mdwn | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/bugs/Using_a_revoked_GPG_key.mdwn b/doc/bugs/Using_a_revoked_GPG_key.mdwn index 79659b9330..4e522ab78a 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]] From 47d0219ac87016fc16ba75c663adc30f0cfcbb63 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 4 Sep 2013 23:46:50 -0400 Subject: [PATCH 18/18] update docs for changed initremote encryption syntax --- doc/special_remotes/S3.mdwn | 9 +++------ doc/special_remotes/bup.mdwn | 10 +++------- doc/special_remotes/directory.mdwn | 9 +++------ doc/special_remotes/glacier.mdwn | 9 +++------ doc/special_remotes/hook.mdwn | 13 +++++-------- doc/special_remotes/rsync.mdwn | 14 +++++--------- doc/special_remotes/webdav.mdwn | 11 ++++------- doc/tips/using_Amazon_Glacier.mdwn | 2 +- doc/tips/using_Amazon_S3.mdwn | 2 +- doc/tips/using_box.com_as_a_special_remote.mdwn | 2 +- 10 files changed, 29 insertions(+), 52 deletions(-) diff --git a/doc/special_remotes/S3.mdwn b/doc/special_remotes/S3.mdwn index e15361f3eb..5291a4eb6e 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 f323237b1e..f2d465e779 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). +* `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]]. * `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 4f9263dc0b..4d72e8beee 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 d6dbad59a0..b55b9adbb5 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 d5c708e0af..eaea940a72 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 581ac6b96e..b2a9d23f5d 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 383fddf75a..251075b09a 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 5e7131eeb2..5aac7fa91e 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 19997d0265..0c68c7387d 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 6616d0a1e2..254d0a554b 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.