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