diff options
author | Joey Hess <joey@kitenet.net> | 2013-09-07 18:38:00 -0400 |
---|---|---|
committer | Joey Hess <joey@kitenet.net> | 2013-09-07 18:38:00 -0400 |
commit | eb259a74840fc7e4769a3ba3384b635b3b4ef5be (patch) | |
tree | 3a4df1961af9e3e4b6c58efef1a99885e0a375ca /Remote/GCrypt.hs | |
parent | 98ae5c42b47c7acf47d8436fcd061b1fbc0d9796 (diff) |
partially complete gcrypt remote (local send done; rest not)
This is a git-remote-gcrypt encrypted special remote. Only sending files
in to the remote works, and only for local repositories.
Most of the work so far has involved making initremote work. A particular
problem is that remote setup in this case needs to generate its own uuid,
derivied from the gcrypt-id. That required some larger changes in the code
to support.
For ssh remotes, this will probably just reuse Remote.Rsync's code, so
should be easy enough. And for downloading from a web remote, I will need
to factor out the part of Remote.Git that does that.
One particular thing that will need work is supporting hot-swapping a local
gcrypt remote. I think it needs to store the gcrypt-id in the git config of the
local remote, so that it can check it every time, and compare with the
cached annex-uuid for the remote. If there is a mismatch, it can change
both the cached annex-uuid and the gcrypt-id. That should work, and I laid
some groundwork for it by already reading the remote's config when it's
local. (Also needed for other reasons.)
This commit was sponsored by Daniel Callahan.
Diffstat (limited to 'Remote/GCrypt.hs')
-rw-r--r-- | Remote/GCrypt.hs | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/Remote/GCrypt.hs b/Remote/GCrypt.hs new file mode 100644 index 000000000..f839f6647 --- /dev/null +++ b/Remote/GCrypt.hs @@ -0,0 +1,164 @@ +{- git remotes encrypted using git-remote-gcrypt + - + - Copyright 2013 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Remote.GCrypt (remote, gen) where + +import qualified Data.Map as M + +import Common.Annex +import Types.Remote +import Types.GitConfig +import Types.Crypto +import qualified Git +import qualified Git.Command +import qualified Git.Config +import qualified Git.GCrypt +import qualified Git.Types as Git () +import qualified Annex.Branch +import qualified Annex.Content +import Config +import Config.Cost +import Remote.Helper.Git +import Remote.Helper.Encryptable +import Utility.Metered +import Crypto +import Annex.UUID + +remote :: RemoteType +remote = RemoteType { + typename = "gcrypt", + -- Remote.Git takes care of enumerating gcrypt remotes too, + -- and will call our gen on them. + enumerate = return [], + generate = gen, + setup = gCryptSetup +} + +gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex Remote +gen gcryptr u c gc = do + g <- gitRepo + -- get underlying git repo with real path, not gcrypt path + r <- liftIO $ Git.GCrypt.encryptedRepo g gcryptr + let r' = r { Git.remoteName = Git.remoteName gcryptr } + -- read config of underlying repo if it's local + r'' <- if Git.repoIsLocalUnknown r' + then liftIO $ catchDefaultIO r' $ Git.Config.read r' + else return r' + gen' r'' u c gc + +gen' :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex Remote +gen' r u c gc = new <$> remoteCost gc defcst + where + defcst = if repoCheap r then nearlyCheapRemoteCost else expensiveRemoteCost + new cst = encryptableRemote c + (store this) + (retrieve this) + this + where + this = Remote + { uuid = u + , cost = cst + , name = Git.repoDescribe r + , storeKey = \_ _ _ -> noCrypto + , retrieveKeyFile = \_ _ _ _ -> noCrypto + , retrieveKeyFileCheap = \_ _ -> return False + , removeKey = remove + , hasKey = checkPresent this + , hasKeyCheap = repoCheap r + , whereisKey = Nothing + , config = M.empty + , localpath = localpathCalc r + , repo = r + , gitconfig = gc { remoteGitConfig = Just $ extractGitConfig r } + , readonly = Git.repoIsHttp r + , globallyAvailable = globallyAvailableCalc r + , remotetype = remote + } + +noCrypto :: Annex a +noCrypto = error "cannot use gcrypt remote without encryption enabled" + +gCryptSetup :: Maybe UUID -> RemoteConfig -> Annex (RemoteConfig, UUID) +gCryptSetup mu c = go $ M.lookup "gitrepo" c + where + remotename = fromJust (M.lookup "name" c) + go Nothing = error "Specify gitrepo=" + go (Just gitrepo) = do + c' <- encryptionSetup c + inRepo $ Git.Command.run + [ Params "remote add" + , Param remotename + , Param $ Git.GCrypt.urlPrefix ++ gitrepo + ] + + {- Configure gcrypt to use the same list of keyids that + - were passed to initremote, unless shared encryption + - was used. -} + case extractCipher c' of + Nothing -> noCrypto + Just (EncryptedCipher _ _ (KeyIds { keyIds = ks})) -> + setConfig (ConfigKey $ Git.GCrypt.remoteParticipantConfigKey remotename) (unwords ks) + _ -> noop + + {- Run a git fetch and a push to the git repo in order to get + - its gcrypt-id set up, so that later git annex commands + - will use the remote as a ggcrypt remote. The fetch is + - needed if the repo already exists; the push is needed + - if the repo has not yet been initialized by gcrypt. -} + void $ inRepo $ Git.Command.runBool + [ Param "fetch" + , Param remotename + ] + void $ inRepo $ Git.Command.runBool + [ Param "push" + , Param remotename + , Param $ show $ Annex.Branch.fullname + ] + g <- inRepo Git.Config.reRead + case Git.GCrypt.remoteRepoId g (Just remotename) of + Nothing -> error "unable to determine gcrypt-id of remote" + Just v -> do + let u = genUUIDInNameSpace gCryptNameSpace v + if Just u == mu || mu == Nothing + then return (c', u) + else error "uuid mismatch" + +store :: Remote -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool +store r (cipher, enck) k p + | not $ Git.repoIsUrl (repo r) = guardUsable (repo r) False $ + sendwith $ \meterupdate h -> do + createDirectoryIfMissing True $ parentDir dest + readBytes (meteredWriteFile meterupdate dest) h + return True + | Git.repoIsSsh (repo r) = sendwith $ \h -> undefined + | otherwise = error "storing on non-ssh remote repo not supported" + where + dest = gCryptLocation r enck + sendwith a = metered (Just p) k $ \meterupdate -> + Annex.Content.sendAnnex k noop $ \src -> + liftIO $ catchBoolIO $ + encrypt (getGpgEncParams r) cipher (feedFile src) (a meterupdate) + +retrieve :: Remote -> (Cipher, Key) -> Key -> FilePath -> MeterUpdate -> Annex Bool +retrieve r (cipher, enck) k d p = undefined + +remove :: Key -> Annex Bool +remove k = undefined + +checkPresent :: Remote -> Key -> Annex (Either String Bool) +checkPresent r k + | not $ Git.repoIsUrl (repo r) = + guardUsable (repo r) unknown $ + liftIO $ catchDefaultIO unknown $ + Right <$> doesFileExist (gCryptLocation r k) + | Git.repoIsSsh (repo r) = undefined + | otherwise = error "storing on non-ssh remote repo not supported" + where + unknown = Left $ "unable to check " ++ Git.repoDescribe (repo r) ++ show (repo r) + +gCryptLocation :: Remote -> Key -> FilePath +gCryptLocation r key = Git.repoLocation (repo r) </> annexLocation key hashDirLower |