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.
This commit is contained in:
parent
f8082933e7
commit
8293ed619f
17 changed files with 307 additions and 140 deletions
|
@ -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
|
||||
|
|
|
@ -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 ","
|
||||
|
|
|
@ -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 (getGpgEncOpts 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 (getGpgEncOpts r) cipher
|
||||
(streamMeteredFile src meterupdate) $
|
||||
readBytes $ storeHelper r enck baseurl user pass
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue