split out Utility.Gpg with the generic gpg interface, from Crypto

This commit is contained in:
Joey Hess 2011-12-20 21:47:56 -04:00
parent bb84f6e4bd
commit c11cfea355
3 changed files with 105 additions and 81 deletions

View file

@ -30,14 +30,10 @@ import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.Map as M import qualified Data.Map as M
import Data.ByteString.Lazy.UTF8 (fromString) import Data.ByteString.Lazy.UTF8 (fromString)
import Data.Digest.Pure.SHA import Data.Digest.Pure.SHA
import System.Posix.Types
import Control.Applicative import Control.Applicative
import Control.Concurrent
import Control.Exception (finally)
import System.Exit
import System.Environment
import Common.Annex import Common.Annex
import qualified Utility.Gpg as Gpg
import Types.Key import Types.Key
import Types.Remote import Types.Remote
import Utility.Base64 import Utility.Base64
@ -71,7 +67,7 @@ genCipher c = do
random <- genrandom random <- genrandom
encryptCipher (Cipher random) ks encryptCipher (Cipher random) ks
where where
genrandom = gpgReadStrict genrandom = Gpg.readStrict
-- Armor the random data, to avoid newlines, -- Armor the random data, to avoid newlines,
-- since gpg only reads ciphers up to the first -- since gpg only reads ciphers up to the first
-- newline. -- newline.
@ -119,7 +115,7 @@ extractCipher c =
encryptCipher :: Cipher -> KeyIds -> IO EncryptedCipher encryptCipher :: Cipher -> KeyIds -> IO EncryptedCipher
encryptCipher (Cipher c) (KeyIds ks) = do encryptCipher (Cipher c) (KeyIds ks) = do
let ks' = nub $ sort ks -- gpg complains about duplicate recipient keyids let ks' = nub $ sort ks -- gpg complains about duplicate recipient keyids
encipher <- gpgPipeStrict (encrypt++recipients ks') c encipher <- Gpg.pipeStrict (encrypt++recipients ks') c
return $ EncryptedCipher encipher (KeyIds ks') return $ EncryptedCipher encipher (KeyIds ks')
where where
encrypt = [ Params "--encrypt" ] encrypt = [ Params "--encrypt" ]
@ -132,7 +128,7 @@ encryptCipher (Cipher c) (KeyIds ks) = do
{- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -} {- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -}
decryptCipher :: RemoteConfig -> EncryptedCipher -> IO Cipher decryptCipher :: RemoteConfig -> EncryptedCipher -> IO Cipher
decryptCipher _ (EncryptedCipher encipher _) = decryptCipher _ (EncryptedCipher encipher _) =
Cipher <$> gpgPipeStrict decrypt encipher Cipher <$> Gpg.pipeStrict decrypt encipher
where where
decrypt = [ Param "--decrypt" ] decrypt = [ Param "--decrypt" ]
@ -150,12 +146,12 @@ encryptKey c k = Key
{- Runs an action, passing it a handle from which it can {- Runs an action, passing it a handle from which it can
- stream encrypted content. -} - stream encrypted content. -}
withEncryptedHandle :: Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a withEncryptedHandle :: Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a
withEncryptedHandle = gpgPassphraseHandle [Params "--symmetric --force-mdc"] . cipherPassphrase withEncryptedHandle = Gpg.passphraseHandle [Params "--symmetric --force-mdc"] . cipherPassphrase
{- Runs an action, passing it a handle from which it can {- Runs an action, passing it a handle from which it can
- stream decrypted content. -} - stream decrypted content. -}
withDecryptedHandle :: Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a withDecryptedHandle :: Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a
withDecryptedHandle = gpgPassphraseHandle [Param "--decrypt"] . cipherPassphrase withDecryptedHandle = Gpg.passphraseHandle [Param "--decrypt"] . cipherPassphrase
{- Streams encrypted content to an action. -} {- Streams encrypted content to an action. -}
withEncryptedContent :: Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a withEncryptedContent :: Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a
@ -169,74 +165,8 @@ pass :: (Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a)
-> Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a -> Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a
pass to c i a = to c i $ \h -> a =<< L.hGetContents h pass to c i a = to c i $ \h -> a =<< L.hGetContents h
gpgParams :: [CommandParam] -> IO [String]
gpgParams params = do
-- Enable batch mode if GPG_AGENT_INFO is set, to avoid extraneous
-- gpg output about password prompts.
e <- catchDefaultIO (getEnv "GPG_AGENT_INFO") ""
let batch = if null e then [] else ["--batch"]
return $ batch ++ defaults ++ toCommand params
where
-- be quiet, even about checking the trustdb
defaults = ["--quiet", "--trust-model", "always"]
{- Runs gpg with some params and returns its stdout, strictly. -}
gpgReadStrict :: [CommandParam] -> IO String
gpgReadStrict params = do
params' <- gpgParams params
pOpen ReadFromPipe "gpg" params' hGetContentsStrict
{- Runs gpg, piping an input value to it, and returninging its stdout,
- strictly. -}
gpgPipeStrict :: [CommandParam] -> String -> IO String
gpgPipeStrict params input = do
params' <- gpgParams params
(pid, fromh, toh) <- hPipeBoth "gpg" params'
_ <- forkIO $ finally (hPutStr toh input) (hClose toh)
output <- hGetContentsStrict fromh
forceSuccess pid
return output
{- Runs gpg with some parameters, first feeding it a passphrase via
- --passphrase-fd, then feeding it an input, and passing a handle
- to its output to an action.
-
- Note that to avoid deadlock with the cleanup stage,
- the action must fully consume gpg's input before returning. -}
gpgPassphraseHandle :: [CommandParam] -> String -> IO L.ByteString -> (Handle -> IO a) -> IO a
gpgPassphraseHandle params passphrase a b = do
-- pipe the passphrase into gpg on a fd
(frompipe, topipe) <- createPipe
_ <- forkIO $ do
toh <- fdToHandle topipe
hPutStrLn toh passphrase
hClose toh
let Fd pfd = frompipe
let passphrasefd = [Param "--passphrase-fd", Param $ show pfd]
params' <- gpgParams $ passphrasefd ++ params
(pid, fromh, toh) <- hPipeBoth "gpg" params'
pid2 <- forkProcess $ do
L.hPut toh =<< a
hClose toh
exitSuccess
hClose toh
ret <- b fromh
-- cleanup
forceSuccess pid
_ <- getProcessStatus True False pid2
closeFd frompipe
return ret
configKeyIds :: RemoteConfig -> IO KeyIds configKeyIds :: RemoteConfig -> IO KeyIds
configKeyIds c = parse <$> gpgReadStrict params configKeyIds c = Gpg.findPubKeys $ configGet c "encryption"
where
params = [Params "--with-colons --list-public-keys",
Param $ configGet c "encryption"]
parse = KeyIds . map keyIdField . filter pubKey . lines
pubKey = isPrefixOf "pub:"
keyIdField s = split ":" s !! 4
configGet :: RemoteConfig -> String -> String configGet :: RemoteConfig -> String -> String
configGet c key = fromMaybe missing $ M.lookup key c configGet c key = fromMaybe missing $ M.lookup key c

View file

@ -5,13 +5,16 @@
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Types.Crypto where module Types.Crypto (
Cipher(..),
EncryptedCipher(..),
KeyIds(..),
) where
import Utility.Gpg (KeyIds(..))
-- XXX ideally, this would be a locked memory region -- XXX ideally, this would be a locked memory region
newtype Cipher = Cipher String newtype Cipher = Cipher String
data EncryptedCipher = EncryptedCipher String KeyIds data EncryptedCipher = EncryptedCipher String KeyIds
deriving (Ord, Eq) deriving (Ord, Eq)
newtype KeyIds = KeyIds [String]
deriving (Ord, Eq)

91
Utility/Gpg.hs Normal file
View file

@ -0,0 +1,91 @@
{- gpg interface
-
- Copyright 2011 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Utility.Gpg where
import qualified Data.ByteString.Lazy.Char8 as L
import System.Posix.Types
import Control.Applicative
import Control.Concurrent
import Control.Exception (finally)
import System.Exit
import System.Environment
import Common
newtype KeyIds = KeyIds [String]
deriving (Ord, Eq)
stdParams :: [CommandParam] -> IO [String]
stdParams params = do
-- Enable batch mode if GPG_AGENT_INFO is set, to avoid extraneous
-- gpg output about password prompts.
e <- catchDefaultIO (getEnv "GPG_AGENT_INFO") ""
let batch = if null e then [] else ["--batch"]
return $ batch ++ defaults ++ toCommand params
where
-- be quiet, even about checking the trustdb
defaults = ["--quiet", "--trust-model", "always"]
{- Runs gpg with some params and returns its stdout, strictly. -}
readStrict :: [CommandParam] -> IO String
readStrict params = do
params' <- stdParams params
pOpen ReadFromPipe "gpg" params' hGetContentsStrict
{- Runs gpg, piping an input value to it, and returninging its stdout,
- strictly. -}
pipeStrict :: [CommandParam] -> String -> IO String
pipeStrict params input = do
params' <- stdParams params
(pid, fromh, toh) <- hPipeBoth "gpg" params'
_ <- forkIO $ finally (hPutStr toh input) (hClose toh)
output <- hGetContentsStrict fromh
forceSuccess pid
return output
{- Runs gpg with some parameters, first feeding it a passphrase via
- --passphrase-fd, then feeding it an input, and passing a handle
- to its output to an action.
-
- Note that to avoid deadlock with the cleanup stage,
- the action must fully consume gpg's input before returning. -}
passphraseHandle :: [CommandParam] -> String -> IO L.ByteString -> (Handle -> IO a) -> IO a
passphraseHandle params passphrase a b = do
-- pipe the passphrase into gpg on a fd
(frompipe, topipe) <- createPipe
_ <- forkIO $ do
toh <- fdToHandle topipe
hPutStrLn toh passphrase
hClose toh
let Fd pfd = frompipe
let passphrasefd = [Param "--passphrase-fd", Param $ show pfd]
params' <- stdParams $ passphrasefd ++ params
(pid, fromh, toh) <- hPipeBoth "gpg" params'
pid2 <- forkProcess $ do
L.hPut toh =<< a
hClose toh
exitSuccess
hClose toh
ret <- b fromh
-- cleanup
forceSuccess pid
_ <- getProcessStatus True False pid2
closeFd frompipe
return ret
{- Finds gpg public keys matching some string. (Could be an email address,
- a key id, or a name. -}
findPubKeys :: String -> IO KeyIds
findPubKeys for = KeyIds . parse <$> readStrict params
where
params = [Params "--with-colons --list-public-keys", Param for]
parse = map keyIdField . filter pubKey . lines
pubKey = isPrefixOf "pub:"
keyIdField s = split ":" s !! 4