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
This commit is contained in:
parent
484daf5b39
commit
53ce59021a
7 changed files with 83 additions and 45 deletions
21
Crypto.hs
21
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"
|
||||
|
|
|
@ -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)
|
||||
|
|
2
Test.hs
2
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"
|
||||
]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ...]
|
||||
|
||||
|
|
Loading…
Reference in a new issue