1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
{- Copyright 2017 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
module Gpg where
import Val
import Hash
import Types
import Crypto
import Data.ByteArray (convert)
import qualified Data.ByteString as B
import qualified Data.ByteString.UTF8 as B8
import System.IO
import System.Posix.IO hiding (createPipe)
import System.Process
import System.Exit
import Control.Exception
import System.Directory
import Control.Concurrent.Async
newtype GpgKeyId = GpgKeyId String
deriving (Show)
newtype GpgSign = GpgSign Bool
myPublicKey :: MySessionKey -> GpgSign -> IO (PerhapsSigned PublicKey)
myPublicKey (MySessionKey _ epk) (GpgSign gpgsign) = do
let pk = PublicKey (Val $ convert epk)
if gpgsign
then gpgSign pk
else return (UnSigned pk)
-- | Sign a debug-me session PublicKey with gpg.
gpgSign :: PublicKey -> IO (PerhapsSigned PublicKey)
gpgSign pk = do
putStrLn "Using gpg to sign the debug-me session key."
-- Write it to a temp file because gpg sometimes is unhappy
-- about password prompting when stdin is not connected to
-- the console.
tmpdir <- getTemporaryDirectory
(tmpfile, tmph) <- openTempFile tmpdir "debug-me.tmp"
B.hPut tmph $ val $ hashValue $ hash pk
hClose tmph
(_, Just hout, _, pid) <- createProcess $
(proc "gpg" ["--output", "-", "--clearsign", "-a", tmpfile])
{ std_out = CreatePipe
}
hSetBinaryMode hout True
sig <- GpgSig . Val <$> B.hGetContents hout
st <- waitForProcess pid
_ <- try (removeFile tmpfile) :: IO (Either IOException ())
case st of
ExitSuccess -> do
-- Verify the just signed data to determine
-- the gpg public key used to sign it. The gpg
-- public key is included in the GpgSigned data.
v <- fst <$> gpgVerifyClearSigned sig
case v of
Just (gpgkeyid, _) -> do
pubkey <- gpgExportPublicKey gpgkeyid
return $ GpgSigned pk sig pubkey
Nothing -> error "gpg sign verification failed"
ExitFailure _ -> error "gpg sign failed"
-- | Export gpg public key in minimal form.
gpgExportPublicKey :: GpgKeyId -> IO GpgKeyExport
gpgExportPublicKey (GpgKeyId gpgkeyid) = do
(_, Just hout, _, pid) <- createProcess $
(proc "gpg" opts)
{ std_out = CreatePipe
}
hSetBinaryMode hout True
b <- B.hGetContents hout
st <- waitForProcess pid
if st == ExitSuccess
then return $ GpgKeyExport $ Val b
else error "gpg --export failed"
where
opts =
[ "-a"
, "--export-options", "no-export-attributes,export-minimal"
, "--export", gpgkeyid
]
gpgImportPublicKey :: GpgKeyExport -> IO ()
gpgImportPublicKey (GpgKeyExport (Val b)) = do
(Just hin, Just hout, Just herr, pid) <- createProcess $
(proc "gpg" [ "--import"] )
{ std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
}
hSetBinaryMode hin True
B.hPut hin b
hClose hin
_ <- B.hGetContents hout
`concurrently` B.hGetContents herr
_ <- waitForProcess pid
return ()
-- | Verify the gpg signature and return the keyid that signed it.
-- Also makes sure that the gpg signed data is the hash of the
-- debug-me PublicKey.
gpgVerify :: PerhapsSigned PublicKey -> IO (Maybe GpgKeyId, SignInfoDesc)
gpgVerify (UnSigned _) = return (Nothing, mempty)
gpgVerify (GpgSigned pk gpgsig keyexport) = do
gpgImportPublicKey keyexport
go =<< gpgVerifyClearSigned gpgsig
where
go (Nothing, s) = return (Nothing, s)
go (Just (gpgkeyid, signeddata), s) = do
let norm = filter (not . B.null) . B8.lines
let pkissigned = norm signeddata == norm (val (hashValue (hash pk)))
return $ if pkissigned
then (Just gpgkeyid, s)
else (Nothing, s)
type SignInfoDesc = B.ByteString
-- | Verify a clearsigned GpgSig, returning the key id used to sign it,
-- and the data that was signed.
--
-- Gpg outputs to stderr information about who signed the
-- data, and that is returned also.
gpgVerifyClearSigned :: GpgSig -> IO (Maybe (GpgKeyId, B.ByteString), SignInfoDesc)
gpgVerifyClearSigned (GpgSig (Val sig)) = do
(statusreadh, statuswriteh) <- createPipe
statuswritefd <- handleToFd statuswriteh
(Just hin, Just hout, Just herr, pid) <- createProcess $
(proc "gpg" (verifyopts statuswritefd))
{ std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
}
closeFd statuswritefd
B.hPut hin sig
hClose hin
hSetBinaryMode hout True
((signeddata, sigdesc), mgpgkeyid) <- B.hGetContents hout
`concurrently` B.hGetContents herr
`concurrently` (parseStatusFd <$> hGetContents statusreadh)
st <- waitForProcess pid
let siginfo = if st == ExitSuccess
then case mgpgkeyid of
Just gpgkeyid -> Just (gpgkeyid, signeddata)
Nothing -> Nothing
else Nothing
return (siginfo, sigdesc)
where
verifyopts statuswritefd =
[ "--status-fd", show statuswritefd
, "--verify"
, "--output", "-"
]
parseStatusFd :: String -> Maybe GpgKeyId
parseStatusFd = go . map words . lines
where
go [] = Nothing
go ((_:"VALIDSIG":_:_:_:_:_:_:_:_:_:keyid:_):_) = Just (GpgKeyId keyid)
go (_:rest) = go rest
|