diff options
Diffstat (limited to 'lib/KeyRing')
-rw-r--r-- | lib/KeyRing/BuildKeyDB.hs | 1402 | ||||
-rw-r--r-- | lib/KeyRing/Types.hs | 394 |
2 files changed, 691 insertions, 1105 deletions
diff --git a/lib/KeyRing/BuildKeyDB.hs b/lib/KeyRing/BuildKeyDB.hs index 1c2a5aa..6de217b 100644 --- a/lib/KeyRing/BuildKeyDB.hs +++ b/lib/KeyRing/BuildKeyDB.hs | |||
@@ -2,13 +2,21 @@ | |||
2 | {-# LANGUAGE DeriveFunctor #-} | 2 | {-# LANGUAGE DeriveFunctor #-} |
3 | {-# LANGUAGE DoAndIfThenElse #-} | 3 | {-# LANGUAGE DoAndIfThenElse #-} |
4 | {-# LANGUAGE ForeignFunctionInterface #-} | 4 | {-# LANGUAGE ForeignFunctionInterface #-} |
5 | {-# LANGUAGE LambdaCase #-} | ||
5 | {-# LANGUAGE OverloadedStrings #-} | 6 | {-# LANGUAGE OverloadedStrings #-} |
6 | {-# LANGUAGE PatternGuards #-} | 7 | {-# LANGUAGE PatternGuards #-} |
7 | {-# LANGUAGE TupleSections #-} | 8 | {-# LANGUAGE TupleSections #-} |
8 | {-# LANGUAGE ViewPatterns #-} | 9 | {-# LANGUAGE ViewPatterns #-} |
9 | module KeyRing.BuildKeyDB where | 10 | module KeyRing.BuildKeyDB where |
10 | import qualified Codec.Binary.Base32 as Base32 | 11 | |
11 | import qualified Codec.Binary.Base64 as Base64 | 12 | #if defined(VERSION_memory) |
13 | import Data.ByteArray.Encoding | ||
14 | import qualified Data.ByteString.Char8 as S8 | ||
15 | import qualified Data.ByteString as S | ||
16 | #elif defined(VERSION_dataenc) | ||
17 | import qualified Codec.Binary.Base32 as Base32 | ||
18 | import qualified Codec.Binary.Base64 as Base64 | ||
19 | #endif | ||
12 | import Control.Applicative (liftA2) | 20 | import Control.Applicative (liftA2) |
13 | import Control.Arrow (first, second) | 21 | import Control.Arrow (first, second) |
14 | import Control.Exception (catch) | 22 | import Control.Exception (catch) |
@@ -17,7 +25,9 @@ import ControlMaybe (handleIO_) | |||
17 | import Data.ASN1.BinaryEncoding (DER (..)) | 25 | import Data.ASN1.BinaryEncoding (DER (..)) |
18 | import Data.ASN1.Encoding (decodeASN1, encodeASN1) | 26 | import Data.ASN1.Encoding (decodeASN1, encodeASN1) |
19 | 27 | ||
20 | import Data.ASN1.Types (fromASN1, toASN1) | 28 | import Data.ASN1.Types (ASN1 (BitString, End, IntVal, Null, OID, Start), |
29 | ASN1ConstructionType (Sequence), ASN1Object, | ||
30 | fromASN1, toASN1) | ||
21 | import Data.Binary | 31 | import Data.Binary |
22 | import Data.Bits ((.&.), (.|.)) | 32 | import Data.Bits ((.&.), (.|.)) |
23 | import Data.Bits (Bits) | 33 | import Data.Bits (Bits) |
@@ -101,6 +111,9 @@ import ScanningParser | |||
101 | import TimeUtil | 111 | import TimeUtil |
102 | 112 | ||
103 | import KeyRing.Types | 113 | import KeyRing.Types |
114 | import Transforms | ||
115 | import PacketTranscoder | ||
116 | import GnuPGAgent | ||
104 | 117 | ||
105 | -- | buildKeyDB | 118 | -- | buildKeyDB |
106 | -- | 119 | -- |
@@ -116,16 +129,15 @@ buildKeyDB :: InputFileContext -> Maybe String -> KeyRingOperation | |||
116 | {- gpgnames -}[(SockAddr, (KeyKey, KeyKey))], | 129 | {- gpgnames -}[(SockAddr, (KeyKey, KeyKey))], |
117 | {- outgoing_names -}[SockAddr]) | 130 | {- outgoing_names -}[SockAddr]) |
118 | ,{- accs -} Map.Map InputFile Access | 131 | ,{- accs -} Map.Map InputFile Access |
119 | ,{- doDecrypt -} MappedPacket -> IO (KikiCondition Packet) | 132 | ,{- doDecrypt -} PacketTranscoder |
120 | ,{- unspilled -} Map.Map InputFile Message | 133 | ,{- unspilled -} Map.Map InputFile Message |
121 | ) | 134 | ) |
122 | ,{- report_imports -} [(FilePath,KikiReportAction)])) | 135 | ,{- report_imports -} [(FilePath,KikiReportAction)])) |
123 | buildKeyDB ctx grip0 keyring = do | 136 | buildKeyDB ctx grip0 keyring = do |
124 | let | 137 | let files istyp = do |
125 | files istyp = do | ||
126 | (f,stream) <- Map.toList (opFiles keyring) | 138 | (f,stream) <- Map.toList (opFiles keyring) |
127 | guard (istyp $ typ stream) | 139 | guard (istyp $ typ stream) |
128 | resolveInputFile ctx f | 140 | return f -- resolveInputFile ctx f |
129 | 141 | ||
130 | ringMap0 = Map.filter (isring . typ) $ opFiles keyring | 142 | ringMap0 = Map.filter (isring . typ) $ opFiles keyring |
131 | (genMap,ringMap) = Map.partitionWithKey isgen ringMap0 | 143 | (genMap,ringMap) = Map.partitionWithKey isgen ringMap0 |
@@ -145,10 +157,10 @@ buildKeyDB ctx grip0 keyring = do | |||
145 | _ -> AutoAccess | 157 | _ -> AutoAccess |
146 | acc -> acc | 158 | acc -> acc |
147 | 159 | ||
148 | readw wk n = fmap (n,) (readPacketsFromWallet wk (ArgFile n)) | 160 | readw wk n = fmap (n,) (readPacketsFromWallet wk n) |
149 | 161 | ||
150 | -- KeyRings (todo: KikiCondition reporting?) | 162 | -- KeyRings (todo: KikiCondition reporting?) |
151 | (spilled,mwk,grip,accs,keys,unspilled) <- do | 163 | (spilled,mwk,grip,accs,keyqs,unspilled) <- do |
152 | #if MIN_VERSION_containers(0,5,0) | 164 | #if MIN_VERSION_containers(0,5,0) |
153 | ringPackets <- Map.traverseWithKey readp ringMap | 165 | ringPackets <- Map.traverseWithKey readp ringMap |
154 | #else | 166 | #else |
@@ -164,39 +176,25 @@ buildKeyDB ctx grip0 keyring = do | |||
164 | 176 | ||
165 | -- | spilled | 177 | -- | spilled |
166 | -- ring packets with info available for export | 178 | -- ring packets with info available for export |
167 | -- | unspilled | 179 | -- | unspilled |
168 | -- the rest | 180 | -- the rest |
169 | (spilled,unspilled) = Map.partition (spillable . fst) ringPackets | 181 | (spilled,unspilled) = Map.partition (spillable . fst) ringPackets |
170 | 182 | ||
171 | -- | keys | 183 | -- | keys |
172 | -- process ringPackets, and get a map of fingerprint info to | 184 | -- process ringPackets, and get a map of fingerprint info to |
173 | -- to a packet, remembering it's original file, access. | 185 | -- to a packet, remembering it's original file, access. |
174 | keys :: Map.Map KeyKey MappedPacket | 186 | keys :: Map.Map KeyKey (OriginMapped Query) |
175 | keys = Map.foldl slurpkeys Map.empty | ||
176 | $ Map.mapWithKey filterSecrets ringPackets | ||
177 | where | ||
178 | filterSecrets f (_,Message ps) = | ||
179 | filter (isSecretKey . packet) | ||
180 | $ zipWith (mappedPacketWithHint fname) ps [1..] | ||
181 | where fname = resolveForReport (Just ctx) f | ||
182 | slurpkeys m ps = m `Map.union` Map.fromList ps' | ||
183 | where ps' = zip (map (keykey . packet) ps) ps | ||
184 | -- | mwk | ||
185 | -- first master key matching the provided grip | ||
186 | -- (the m is for "MappedPacket", wk for working key) | ||
187 | mwk :: Maybe MappedPacket | 187 | mwk :: Maybe MappedPacket |
188 | mwk = listToMaybe $ do | 188 | (mwk, keys) = keyQueries grip ringPackets |
189 | fp <- maybeToList grip | 189 | |
190 | let matchfp mp = not (is_subkey p) && matchpr fp p == fp | ||
191 | where p = packet mp | ||
192 | Map.elems $ Map.filter matchfp keys | ||
193 | -- | accs | 190 | -- | accs |
194 | -- file access(Sec | Pub) lookup table | 191 | -- file access(Sec | Pub) lookup table |
195 | accs :: Map.Map InputFile Access | 192 | accs :: Map.Map InputFile Access |
196 | accs = fmap (access . fst) ringPackets | 193 | accs = fmap (access . fst) ringPackets |
197 | return (spilled,mwk,grip,accs,keys,fmap snd unspilled) | 194 | return (spilled,mwk,grip,accs,keys,fmap snd unspilled) |
198 | 195 | ||
199 | doDecrypt <- makeMemoizingDecrypter keyring ctx keys | 196 | transcode <- makeMemoizingDecrypter keyring ctx (mwk,keyqs) |
197 | let doDecrypt = transcode (Unencrypted,S2K 100 "") | ||
200 | 198 | ||
201 | let wk = fmap packet mwk | 199 | let wk = fmap packet mwk |
202 | rt0 = KeyRingRuntime { rtPubring = homepubPath ctx | 200 | rt0 = KeyRingRuntime { rtPubring = homepubPath ctx |
@@ -205,10 +203,10 @@ buildKeyDB ctx grip0 keyring = do | |||
205 | , rtWorkingKey = wk | 203 | , rtWorkingKey = wk |
206 | , rtRingAccess = accs | 204 | , rtRingAccess = accs |
207 | , rtKeyDB = Map.empty | 205 | , rtKeyDB = Map.empty |
208 | , rtPassphrases = doDecrypt | 206 | , rtPassphrases = transcode |
209 | } | 207 | } |
210 | -- autosigns and deletes | 208 | -- autosigns and deletes |
211 | transformed0 <- do | 209 | transformed0 <- |
212 | let trans :: InputFile -> (StreamInfo,Message) -> IO (KikiCondition (KikiReport,KeyDB)) | 210 | let trans :: InputFile -> (StreamInfo,Message) -> IO (KikiCondition (KikiReport,KeyDB)) |
213 | trans f (info,ps) = do | 211 | trans f (info,ps) = do |
214 | let manip = combineTransforms (transforms info) | 212 | let manip = combineTransforms (transforms info) |
@@ -216,87 +214,84 @@ buildKeyDB ctx grip0 keyring = do | |||
216 | acc = Just Sec /= Map.lookup f accs | 214 | acc = Just Sec /= Map.lookup f accs |
217 | r <- performManipulations doDecrypt rt1 mwk manip | 215 | r <- performManipulations doDecrypt rt1 mwk manip |
218 | try r $ \(rt2,report) -> do | 216 | try r $ \(rt2,report) -> do |
219 | return $ KikiSuccess (report,rtKeyDB rt2) | 217 | return $ KikiSuccess (report,rtKeyDB rt2) |
218 | -- XXX: Unspilled keys are not obtainable from rtKeyDB. | ||
219 | -- If the working key is marked non spillable, then how | ||
220 | -- would we look up it's UID and such? | ||
220 | #if MIN_VERSION_containers(0,5,0) | 221 | #if MIN_VERSION_containers(0,5,0) |
221 | fmap sequenceA $ Map.traverseWithKey trans spilled | 222 | in fmap sequenceA $ Map.traverseWithKey trans spilled |
222 | #else | 223 | #else |
223 | fmap sequenceA $ Traversable.traverse (uncurry trans) $ Map.mapWithKey (,) spilled | 224 | in fmap sequenceA $ Traversable.traverse (uncurry trans) $ Map.mapWithKey (,) spilled |
224 | #endif | 225 | #endif |
225 | try transformed0 $ \transformed -> do | 226 | try transformed0 $ \transformed -> do |
226 | let -- | db_rings - all keyrings combined into one | 227 | let -- | db_rings - all keyrings combined into one |
227 | db_rings :: Map.Map KeyKey KeyData | 228 | db_rings :: Map.Map KeyKey KeyData |
228 | db_rings = Map.foldlWithKey' mergeIt Map.empty transformed | 229 | db_rings = Map.foldlWithKey' mergeIt Map.empty transformed |
229 | where | 230 | where |
230 | mergeIt db f (_,dbtrans) = Map.unionWith mergeKeyData db dbtrans | 231 | mergeIt db f (_,dbtrans) = Map.unionWith mergeKeyData db dbtrans |
231 | -- | reportTrans | 232 | -- | reportTrans |
232 | -- events, indexed by file | 233 | -- events, indexed by file |
233 | reportTrans :: [(FilePath, KikiReportAction)] | 234 | reportTrans :: [(FilePath, KikiReportAction)] |
234 | reportTrans = concat $ Map.elems $ fmap fst transformed | 235 | reportTrans = concat $ Map.elems $ fmap fst transformed |
235 | 236 | ||
236 | -- Wallets | 237 | -- Wallets |
237 | let importWalletKey wk db' (top,fname,sub,tag) = do | 238 | let importWalletKey wk db' (top,fname,sub,tag) = do |
238 | try db' $ \(db',report0) -> do | 239 | try db' $ \(db',report0) -> do |
239 | r <- doImportG doDecrypt | 240 | r <- doImportG transcode |
240 | db' | 241 | db' |
241 | (fmap keykey $ maybeToList wk) | 242 | (fmap keykey $ maybeToList wk) |
242 | [mkUsage tag] | 243 | [mkUsage tag] |
243 | fname | 244 | fname |
244 | sub | 245 | sub |
245 | try r $ \(db'',report) -> do | 246 | try r $ \(db'',report) -> do |
246 | return $ KikiSuccess (db'', report0 ++ report) | 247 | return $ KikiSuccess (db'', report0 ++ report) |
247 | 248 | ||
248 | wms <- mapM (readw wk) (files iswallet) | 249 | wms <- mapM (readw wk) (files iswallet) |
249 | let wallet_keys = do | 250 | let wallet_keys = do |
250 | maybeToList wk | 251 | maybeToList wk |
251 | (fname,xs) <- wms | 252 | (fname,xs) <- wms |
252 | (_,sub,(_,m)) <- xs | 253 | (_,sub,(_,m)) <- xs |
253 | (tag,top) <- Map.toList m | 254 | (tag,top) <- Map.toList m |
254 | return (top,fname,sub,tag) | 255 | return (top,fname,sub,tag) |
255 | db <- foldM (importWalletKey wk) (KikiSuccess (db_rings,[])) wallet_keys | 256 | |
256 | try db $ \(db,reportWallets) -> do | 257 | db <- foldM (importWalletKey wk) (KikiSuccess (db_rings,[])) wallet_keys |
257 | 258 | try db $ \(db,reportWallets) -> do | |
258 | -- PEM files | 259 | |
259 | let pems = do | 260 | -- PEM files |
260 | (n,stream) <- Map.toList $ opFiles keyring | 261 | let pems = do |
261 | grip <- maybeToList grip | 262 | (n,stream) <- Map.toList $ opFiles keyring |
262 | n <- resolveInputFile ctx n | 263 | grip <- maybeToList grip |
263 | guard $ spillable stream && isSecretKeyFile (typ stream) | 264 | guard $ spillable stream && isSecretKeyFile (typ stream) |
264 | let us = mapMaybe usageFromFilter [fill stream,spill stream] | 265 | let us = mapMaybe usageFromFilter [fill stream,spill stream] |
265 | usage <- take 1 us | 266 | usage <- take 1 us |
266 | guard $ all (==usage) $ drop 1 us | 267 | guard $ all (==usage) $ drop 1 us |
267 | -- TODO: KikiCondition reporting for spill/fill usage mismatch? | 268 | -- TODO: KikiCondition reporting for spill/fill usage mismatch? |
268 | -- TODO: parseSpec3 | 269 | -- TODO: parseSpec3 |
269 | let (topspec,subspec) = parseSpec grip usage | 270 | let (topspec,subspec) = parseSpec grip usage |
270 | ms = map fst $ filterMatches topspec (Map.toList db) | 271 | ms = map fst $ filterMatches topspec (Map.toList db) |
271 | cmd = initializer stream | 272 | cmd = initializer stream |
272 | return (n,subspec,ms,stream, cmd) | 273 | return (n,subspec,ms,stream, cmd) |
273 | 274 | ||
274 | imports <- filterM (\(n,_,_,_,_) -> doesFileExist n) pems | 275 | imports <- filterM (\case (ArgFile n,_,_,_,_) -> doesFileExist n |
275 | db <- foldM (importSecretKey doDecrypt) (KikiSuccess (db,[])) imports | 276 | _ -> return True) |
276 | try db $ \(db,reportPEMs) -> do | 277 | pems |
277 | 278 | db <- foldM (importSecretKey transcode) (KikiSuccess (db,[])) imports | |
278 | -- generate keys | 279 | try db $ \(db,reportPEMs) -> do |
279 | let gens = mapMaybe g $ Map.toList genMap | 280 | |
280 | where g (Generate _ params,v) = Just (params,v) | 281 | -- generate keys |
281 | g _ = Nothing | 282 | let gens = mapMaybe g $ Map.toList genMap |
282 | 283 | where g (Generate _ params,v) = Just (params,v) | |
283 | db <- generateInternals doDecrypt mwk db gens | 284 | g _ = Nothing |
284 | try db $ \(db,reportGens) -> do | 285 | |
285 | 286 | db <- generateInternals transcode mwk db gens | |
286 | r <- mergeHostFiles keyring db ctx | 287 | try db $ \(db,reportGens) -> do |
287 | try r $ \((db,hs),reportHosts) -> do | 288 | |
288 | 289 | r <- mergeHostFiles keyring db ctx | |
289 | return $ KikiSuccess ( (db, grip, mwk, hs, accs, doDecrypt, unspilled) | 290 | try r $ \((db,hs),reportHosts) -> do |
290 | , reportTrans ++ reportWallets ++ reportPEMs ++ reportGens ++ reportHosts ) | 291 | |
291 | 292 | return $ KikiSuccess ( (db, grip, mwk, hs, accs, transcode, unspilled) | |
292 | 293 | , reportTrans ++ reportWallets ++ reportPEMs ++ reportGens ++ reportHosts ) | |
293 | resolveInputFile :: InputFileContext -> InputFile -> [FilePath] | 294 | |
294 | resolveInputFile ctx = resolve | ||
295 | where | ||
296 | resolve HomeSec = return (homesecPath ctx) | ||
297 | resolve HomePub = return (homepubPath ctx) | ||
298 | resolve (ArgFile f) = return f | ||
299 | resolve _ = [] | ||
300 | 295 | ||
301 | isring :: FileType -> Bool | 296 | isring :: FileType -> Bool |
302 | isring (KeyRingFile {}) = True | 297 | isring (KeyRingFile {}) = True |
@@ -327,11 +322,12 @@ readPacketsFromWallet wk fname = do | |||
327 | timestamp <- getInputFileTime ctx fname | 322 | timestamp <- getInputFileTime ctx fname |
328 | input <- readInputFileL ctx fname | 323 | input <- readInputFileL ctx fname |
329 | let (ks,_) = slurpWIPKeys timestamp input | 324 | let (ks,_) = slurpWIPKeys timestamp input |
325 | {- | ||
330 | unless (null ks) $ do | 326 | unless (null ks) $ do |
331 | -- decrypt wk | 327 | -- decrypt wk |
332 | -- create sigs | 328 | -- create sigs |
333 | -- return key/sig pairs | 329 | -- return key/sig pairs |
334 | return () | 330 | return () -} |
335 | return $ do | 331 | return $ do |
336 | wk <- maybeToList wk | 332 | wk <- maybeToList wk |
337 | guard (not $ null ks) | 333 | guard (not $ null ks) |
@@ -344,120 +340,11 @@ spillable :: StreamInfo -> Bool | |||
344 | spillable (spill -> KF_None) = False | 340 | spillable (spill -> KF_None) = False |
345 | spillable _ = True | 341 | spillable _ = True |
346 | 342 | ||
347 | isSecretKey :: Packet -> Bool | ||
348 | isSecretKey (SecretKeyPacket {}) = True | ||
349 | isSecretKey _ = False | ||
350 | |||
351 | mappedPacketWithHint :: FilePath -> Packet -> Int -> MappedPacket | ||
352 | mappedPacketWithHint filename p hint = MappedPacket | ||
353 | { packet = p | ||
354 | , locations = Map.singleton filename (origin p hint) | ||
355 | } | ||
356 | |||
357 | resolveForReport :: Maybe InputFileContext -> InputFile -> FilePath | ||
358 | resolveForReport mctx (Pipe fdr fdw) = resolveForReport mctx (ArgFile str) | ||
359 | where str = case (fdr,fdw) of | ||
360 | (0,1) -> "-" | ||
361 | _ -> "&pipe" ++ show (fdr,fdw) | ||
362 | resolveForReport mctx (FileDesc fd) = resolveForReport mctx (ArgFile str) | ||
363 | where str = "&" ++ show fd | ||
364 | resolveForReport mctx f = concat $ resolveInputFile ctx f | ||
365 | where ctx = fromMaybe (InputFileContext "&secret" "&public") mctx | ||
366 | |||
367 | keykey :: Packet -> KeyKey | ||
368 | keykey key = | ||
369 | -- Note: The key's timestamp is normally included in it's fingerprint. | ||
370 | -- This is undesirable for kiki because it causes the same | ||
371 | -- key to be imported multiple times and show as apparently | ||
372 | -- distinct keys with different fingerprints. | ||
373 | -- Thus, we will remove the timestamp. | ||
374 | fingerprint_material (key {timestamp=0}) -- TODO: smaller key? | ||
375 | |||
376 | -- matchpr computes the fingerprint of the given key truncated to | ||
377 | -- be the same lenght as the given fingerprint for comparison. | ||
378 | -- | ||
379 | -- matchpr fp = Data.List.Extra.takeEnd (length fp) | ||
380 | -- | ||
381 | matchpr :: String -> Packet -> String | ||
382 | matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp | ||
383 | |||
384 | makeMemoizingDecrypter :: KeyRingOperation -> InputFileContext | ||
385 | -> Map.Map KeyKey MappedPacket | ||
386 | -> IO (MappedPacket -> IO (KikiCondition Packet)) | ||
387 | makeMemoizingDecrypter operation ctx keys = | ||
388 | if null chains then do | ||
389 | -- (*) Notice we do not pass ctx to resolveForReport. | ||
390 | -- This is because the merge function does not currently use a context | ||
391 | -- and the pws map keys must match the MappedPacket locations. | ||
392 | -- TODO: Perhaps these should both be of type InputFile rather than | ||
393 | -- FilePath? | ||
394 | -- pws :: Map.Map FilePath (IO S.ByteString) | ||
395 | {- | ||
396 | pws <- | ||
397 | Traversable.mapM (cachedContents ctx . fromJust . pwfile . typ) | ||
398 | (Map.mapKeys (resolveForReport Nothing) -- see note (*) note above | ||
399 | $ Map.filter (isJust . pwfile . typ) $ opFiles operation) | ||
400 | -} | ||
401 | let prompt = Just "Enter possibly multi-line passphrase (Press CTRL-D when finished):\n" | ||
402 | pws2 <- | ||
403 | Traversable.mapM (cachedContents prompt ctx) | ||
404 | $ Map.fromList $ mapMaybe | ||
405 | (\spec -> (,passSpecPassFile spec) `fmap` do | ||
406 | guard $ isNothing $ passSpecKeySpec spec | ||
407 | passSpecRingFile spec) | ||
408 | passspecs | ||
409 | defpw <- do | ||
410 | Traversable.mapM (cachedContents prompt ctx . passSpecPassFile) | ||
411 | $ listToMaybe $ filter (\sp -> isNothing (passSpecRingFile sp) | ||
412 | && isNothing (passSpecKeySpec sp)) | ||
413 | $ opPassphrases operation | ||
414 | unkeysRef <- newIORef (Map.empty :: Map.Map KeyKey Packet) | ||
415 | return $ doDecrypt unkeysRef ({- pws `Map.union` -} pws2) defpw | ||
416 | else let PassphraseMemoizer f = head chains | ||
417 | in return f | ||
418 | where | ||
419 | (chains,passspecs) = partition isChain $ opPassphrases operation | ||
420 | where isChain (PassphraseMemoizer {}) = True | ||
421 | isChain _ = False | ||
422 | doDecrypt :: IORef (Map.Map KeyKey Packet) | ||
423 | -> Map.Map FilePath (IO S.ByteString) | ||
424 | -> Maybe (IO S.ByteString) | ||
425 | -> MappedPacket | ||
426 | -> IO (KikiCondition Packet) | ||
427 | doDecrypt unkeysRef pws defpw mp0 = do | ||
428 | unkeys <- readIORef unkeysRef | ||
429 | let mp = fromMaybe mp0 $ do | ||
430 | k <- Map.lookup kk keys | ||
431 | return $ mergeKeyPacket "decrypt" mp0 k | ||
432 | wk = packet mp0 | ||
433 | kk = keykey wk | ||
434 | fs = Map.keys $ locations mp | ||
435 | |||
436 | decryptIt [] = return BadPassphrase | ||
437 | decryptIt (getpw:getpws) = do | ||
438 | -- TODO: This function should use mergeKeyPacket to | ||
439 | -- combine the packet with it's unspilled version before | ||
440 | -- attempting to decrypt it. | ||
441 | pw <- getpw | ||
442 | let wkun = fromMaybe wk $ decryptSecretKey pw (packet mp) | ||
443 | case symmetric_algorithm wkun of | ||
444 | Unencrypted -> do | ||
445 | writeIORef unkeysRef (Map.insert kk wkun unkeys) | ||
446 | return $ KikiSuccess wkun | ||
447 | _ -> decryptIt getpws | ||
448 | |||
449 | getpws = mapMaybe (`Map.lookup` pws) fs ++ maybeToList defpw | ||
450 | |||
451 | case symmetric_algorithm wk of | ||
452 | Unencrypted -> return (KikiSuccess wk) | ||
453 | _ -> maybe (decryptIt getpws) | ||
454 | (return . KikiSuccess) | ||
455 | $ Map.lookup kk unkeys | ||
456 | 343 | ||
457 | -- | combineTransforms | 344 | -- | combineTransforms |
458 | -- remove rundant transforms, and compile the rest to PacketUpdate(s) | 345 | -- remove redundant transforms, and compile the rest to PacketUpdate(s) |
459 | -- | 346 | -- |
460 | -- eqivalent to: | 347 | -- equivalent to: |
461 | -- > combineTransforms = group (sort trans) >>= take 1 >>= resolveTransform t rt kd | 348 | -- > combineTransforms = group (sort trans) >>= take 1 >>= resolveTransform t rt kd |
462 | combineTransforms :: [Transform] -> KeyRingRuntime -> KeyData -> [PacketUpdate] | 349 | combineTransforms :: [Transform] -> KeyRingRuntime -> KeyData -> [PacketUpdate] |
463 | combineTransforms trans rt kd = updates | 350 | combineTransforms trans rt kd = updates |
@@ -490,108 +377,6 @@ merge db inputfile (Message ps) = merge_ db filename qs | |||
490 | updateTrust (PublicKeyPacket {}) _ (pre,t) p = (pre,Map.insert filename p t) -- public | 377 | updateTrust (PublicKeyPacket {}) _ (pre,t) p = (pre,Map.insert filename p t) -- public |
491 | updateTrust _ _ (pre,t) p = (pre,Map.insert filename p t) -- secret | 378 | updateTrust _ _ (pre,t) p = (pre,Map.insert filename p t) -- secret |
492 | 379 | ||
493 | performManipulations :: | ||
494 | (MappedPacket -> IO (KikiCondition Packet)) | ||
495 | -> KeyRingRuntime | ||
496 | -> Maybe MappedPacket | ||
497 | -> (KeyRingRuntime -> KeyData -> [PacketUpdate]) | ||
498 | -> IO (KikiCondition (KeyRingRuntime,KikiReport)) | ||
499 | performManipulations doDecrypt rt wk manip = do | ||
500 | let db = rtKeyDB rt | ||
501 | performAll kd = foldM perform (KikiSuccess (kd, [])) $ manip rt kd | ||
502 | r <- Traversable.mapM performAll db | ||
503 | try (sequenceA r) $ \db -> do | ||
504 | return $ | ||
505 | KikiSuccess (rt {rtKeyDB = fmap fst db}, concatMap snd $ Map.elems db) | ||
506 | where | ||
507 | perform | ||
508 | :: KikiCondition (KeyData, KikiReport) | ||
509 | -> PacketUpdate | ||
510 | -> IO (KikiCondition (KeyData, KikiReport)) | ||
511 | perform kd (InducerSignature uid subpaks) = do | ||
512 | try kd $ \(kd, report) -> do | ||
513 | flip (maybe $ return NoWorkingKey) wk $ \wk' -> do | ||
514 | wkun' <- doDecrypt wk' | ||
515 | try wkun' $ \wkun -> do | ||
516 | let flgs = | ||
517 | if keykey (keyPacket kd) == keykey wkun | ||
518 | then keyFlags0 | ||
519 | (keyPacket kd) | ||
520 | (map (\(x, _, _) -> x) selfsigs) | ||
521 | else [] | ||
522 | sigOver = | ||
523 | makeInducerSig (keyPacket kd) wkun (UserIDPacket uid) $ | ||
524 | flgs ++ subpaks | ||
525 | om = Map.singleton "--autosign" (origin p (-1)) | ||
526 | where | ||
527 | p = UserIDPacket uid | ||
528 | toMappedPacket om p = (mappedPacket "" p) {locations = om} | ||
529 | selfsigs = | ||
530 | filter | ||
531 | (\(sig, v, whosign) -> | ||
532 | isJust | ||
533 | (v >> Just wkun >>= | ||
534 | guard . (== keykey whosign) . keykey)) | ||
535 | vs | ||
536 | keys = map keyPacket $ Map.elems (rtKeyDB rt) | ||
537 | overs sig = | ||
538 | signatures $ | ||
539 | Message (keys ++ [keyPacket kd, UserIDPacket uid, sig]) | ||
540 | vs | ||
541 | :: [(Packet -- signature | ||
542 | , Maybe SignatureOver -- Nothing means non-verified | ||
543 | , Packet -- key who signed | ||
544 | )] | ||
545 | vs = do | ||
546 | x <- maybeToList $ Map.lookup uid (keyUids kd) | ||
547 | sig <- map (packet . fst) (fst x) | ||
548 | o <- overs sig | ||
549 | k <- keys | ||
550 | let ov = verify (Message [k]) $ o | ||
551 | signatures_over ov | ||
552 | return (sig, Just ov, k) | ||
553 | additional new_sig = do | ||
554 | new_sig <- maybeToList new_sig | ||
555 | guard (null $ selfsigs) | ||
556 | signatures_over new_sig | ||
557 | sigr <- pgpSign (Message [wkun]) sigOver SHA1 (fingerprint wkun) | ||
558 | let f :: ([SigAndTrust], OriginMap) -> ([SigAndTrust], OriginMap) | ||
559 | f x = | ||
560 | ( map ((, Map.empty) . toMappedPacket om) (additional sigr) ++ | ||
561 | fst x | ||
562 | , om `Map.union` snd x) | ||
563 | -- XXX: Shouldn't this signature generation show up in the KikiReport ? | ||
564 | return $ | ||
565 | KikiSuccess $ | ||
566 | (kd {keyUids = Map.adjust f uid (keyUids kd)}, report) | ||
567 | perform kd (SubKeyDeletion topk subk) = do | ||
568 | try kd $ \(kd, report) -> do | ||
569 | let kk = keykey $ packet $ keyMappedPacket kd | ||
570 | kd' | ||
571 | | kk /= topk = kd | ||
572 | | otherwise = | ||
573 | kd {keySubKeys = Map.filterWithKey pred $ keySubKeys kd} | ||
574 | pred k _ = k /= subk | ||
575 | ps = | ||
576 | concat $ | ||
577 | maybeToList $ do | ||
578 | SubKey mp sigs <- Map.lookup subk (keySubKeys kd) | ||
579 | return $ | ||
580 | packet mp : | ||
581 | concatMap (\(p, ts) -> packet p : Map.elems ts) sigs | ||
582 | ctx = InputFileContext (rtSecring rt) (rtPubring rt) | ||
583 | rings = [HomeSec, HomePub] >>= resolveInputFile ctx | ||
584 | return $ | ||
585 | KikiSuccess | ||
586 | ( kd' | ||
587 | , report ++ | ||
588 | [(f, DeletedPacket $ showPacket p) | f <- rings, p <- ps]) | ||
589 | |||
590 | try :: Monad m => KikiCondition a -> (a -> m (KikiCondition b)) -> m (KikiCondition b) | ||
591 | try x body = | ||
592 | case functorToEither x of | ||
593 | Left e -> return e | ||
594 | Right x -> body x | ||
595 | 380 | ||
596 | mergeKeyData :: KeyData -> KeyData -> KeyData | 381 | mergeKeyData :: KeyData -> KeyData -> KeyData |
597 | mergeKeyData (KeyData atop asigs auids asubs) | 382 | mergeKeyData (KeyData atop asigs auids asubs) |
@@ -620,40 +405,19 @@ mergeKeyData (KeyData atop asigs auids asubs) | |||
620 | mergeUIDSigs (as,am) (bs,bm) = (foldl' (flip mergeSig) as bs, Map.union am bm) | 405 | mergeUIDSigs (as,am) (bs,bm) = (foldl' (flip mergeSig) as bs, Map.union am bm) |
621 | 406 | ||
622 | doImportG | 407 | doImportG |
623 | :: (MappedPacket -> IO (KikiCondition Packet)) | 408 | :: PacketTranscoder |
624 | -> Map.Map KeyKey KeyData | 409 | -> Map.Map KeyKey KeyData |
625 | -> [KeyKey] -- m0, only head is used | 410 | -> [KeyKey] -- m0, only head is used |
626 | -> [SignatureSubpacket] -- tags | 411 | -> [SignatureSubpacket] -- tags |
627 | -> FilePath | 412 | -> InputFile |
628 | -> Packet | 413 | -> Packet |
629 | -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)])) | 414 | -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)])) |
630 | doImportG doDecrypt db m0 tags fname key = do | 415 | doImportG transcode db m0 tags fname key = do |
631 | let kk = head m0 | 416 | let kk = head m0 |
632 | Just kd@(KeyData top topsigs uids subs) = Map.lookup kk db | 417 | Just kd@(KeyData top topsigs uids subs) = Map.lookup kk db |
633 | kdr <- insertSubkey doDecrypt kk kd tags fname key | 418 | kdr <- insertSubkey transcode kk kd tags fname key |
634 | try kdr $ \(kd',rrs) -> return $ KikiSuccess (Map.insert kk kd' db, rrs) | 419 | try kdr $ \(kd',rrs) -> return $ KikiSuccess (Map.insert kk kd' db, rrs) |
635 | 420 | ||
636 | mkUsage :: String -> SignatureSubpacket | ||
637 | mkUsage tag | ||
638 | | Just flags <- lookup tag specials = | ||
639 | KeyFlagsPacket | ||
640 | { certify_keys = fromEnum flags .&. 0x1 /= 0 | ||
641 | , sign_data = fromEnum flags .&. 0x2 /= 0 | ||
642 | , encrypt_communication = fromEnum flags .&. 0x4 /= 0 | ||
643 | , encrypt_storage = fromEnum flags .&. 0x8 /= 0 | ||
644 | , split_key = False | ||
645 | , authentication = False | ||
646 | , group_key = False | ||
647 | } | ||
648 | where | ||
649 | flagsets = [Special .. VouchSignEncrypt] | ||
650 | specials = map (\f -> (usageString f, f)) flagsets | ||
651 | |||
652 | mkUsage tag = NotationDataPacket | ||
653 | { human_readable = True | ||
654 | , notation_name = "usage@" | ||
655 | , notation_value = tag | ||
656 | } | ||
657 | 421 | ||
658 | iswallet :: FileType -> Bool | 422 | iswallet :: FileType -> Bool |
659 | iswallet (WalletFile {}) = True | 423 | iswallet (WalletFile {}) = True |
@@ -749,32 +513,32 @@ filterMatches :: KeySpec -> [(KeyKey,KeyData)] -> [(KeyKey,KeyData)] | |||
749 | filterMatches spec ks = filter (matchSpec spec . snd) ks | 513 | filterMatches spec ks = filter (matchSpec spec . snd) ks |
750 | 514 | ||
751 | importSecretKey :: | 515 | importSecretKey :: |
752 | (MappedPacket -> IO (KikiCondition Packet)) | 516 | (PacketTranscoder) |
753 | -> KikiCondition | 517 | -> KikiCondition |
754 | (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)]) | 518 | (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)]) |
755 | -> (FilePath, Maybe [Char], [KeyKey], StreamInfo, t) | 519 | -> (InputFile, Maybe [Char], [KeyKey], StreamInfo, t) |
756 | -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)])) | 520 | -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)])) |
757 | importSecretKey doDecrypt db' tup = do | 521 | importSecretKey transcode db' tup = do |
758 | try db' $ \(db',report0) -> do | 522 | try db' $ \(db',report0) -> do |
759 | r <- doImport doDecrypt | 523 | r <- doImport transcode |
760 | db' | 524 | db' |
761 | tup | 525 | tup |
762 | try r $ \(db'',report) -> do | 526 | try r $ \(db'',report) -> do |
763 | return $ KikiSuccess (db'', report0 ++ report) | 527 | return $ KikiSuccess (db'', report0 ++ report) |
764 | 528 | ||
765 | generateInternals :: | 529 | generateInternals :: |
766 | (MappedPacket -> IO (KikiCondition Packet)) | 530 | PacketTranscoder |
767 | -> Maybe MappedPacket | 531 | -> Maybe MappedPacket |
768 | -> Map.Map KeyKey KeyData | 532 | -> Map.Map KeyKey KeyData |
769 | -> [(GenerateKeyParams,StreamInfo)] | 533 | -> [(GenerateKeyParams,StreamInfo)] |
770 | -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)])) | 534 | -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)])) |
771 | generateInternals doDecrypt mwk db gens = do | 535 | generateInternals transcode mwk db gens = do |
772 | case fmap packet mwk >>= \wk -> Map.lookup (keykey wk) db of | 536 | case fmap packet mwk >>= \wk -> Map.lookup (keykey wk) db of |
773 | Just kd0 -> do | 537 | Just kd0 -> do |
774 | kd <- foldM (generateSubkey doDecrypt) (KikiSuccess (kd0,[])) gens | 538 | kd <- foldM (generateSubkey transcode) (KikiSuccess (kd0,[])) gens |
775 | try kd $ \(kd,reportGens) -> do | 539 | try kd $ \(kd,reportGens) -> do |
776 | let kk = keykey $ packet $ fromJust mwk | 540 | let kk = keykey $ packet $ fromJust mwk |
777 | return $ KikiSuccess (Map.insert kk kd db,reportGens) | 541 | return $ KikiSuccess (Map.insert kk kd db,reportGens) |
778 | Nothing -> return $ KikiSuccess (db,[]) | 542 | Nothing -> return $ KikiSuccess (db,[]) |
779 | 543 | ||
780 | mergeHostFiles :: KeyRingOperation -> KeyDB -> InputFileContext | 544 | mergeHostFiles :: KeyRingOperation -> KeyDB -> InputFileContext |
@@ -875,89 +639,6 @@ slurpWIPKeys stamp cs = | |||
875 | else let (ks,js) = slurpWIPKeys stamp xs | 639 | else let (ks,js) = slurpWIPKeys stamp xs |
876 | in maybe (ks,b58:js) (\(net,Message [k])->((net,k):ks,js)) mb | 640 | in maybe (ks,b58:js) (\(net,Message [k])->((net,k):ks,js)) mb |
877 | 641 | ||
878 | origin :: Packet -> Int -> OriginFlags | ||
879 | origin p n = OriginFlags ispub n | ||
880 | where | ||
881 | ispub = case p of | ||
882 | SecretKeyPacket {} -> False | ||
883 | _ -> True | ||
884 | |||
885 | cachedContents :: Maybe S.ByteString -> InputFileContext -> InputFile -> IO (IO S.ByteString) | ||
886 | cachedContents maybePrompt ctx fd = do | ||
887 | ref <- newIORef Nothing | ||
888 | return $ get maybePrompt ref fd | ||
889 | where | ||
890 | trimCR bs = fst $ S.spanEnd (\x -> x==10 || x==13) bs | ||
891 | |||
892 | get maybePrompt ref fd = do | ||
893 | pw <- readIORef ref | ||
894 | flip (flip maybe return) pw $ do | ||
895 | if fd == FileDesc 0 then case maybePrompt of | ||
896 | Just prompt -> S.hPutStr stderr prompt | ||
897 | Nothing -> return () | ||
898 | else return () | ||
899 | pw <- fmap trimCR $ readInputFileS ctx fd | ||
900 | writeIORef ref (Just pw) | ||
901 | return pw | ||
902 | |||
903 | mergeKeyPacket :: String -> MappedPacket -> MappedPacket -> MappedPacket | ||
904 | mergeKeyPacket what key p = | ||
905 | key { packet = minimumBy (keyCompare what) [packet key,packet p] | ||
906 | , locations = Map.union (locations key) (locations p) | ||
907 | } | ||
908 | |||
909 | -- | resolveTransform | ||
910 | resolveTransform :: Transform -> KeyRingRuntime -> KeyData -> [PacketUpdate] | ||
911 | resolveTransform Autosign rt kd@(KeyData k ksigs umap submap) = ops | ||
912 | where | ||
913 | ops = map (\u -> InducerSignature u []) us | ||
914 | us = filter torStyle $ Map.keys umap | ||
915 | torStyle str = and [ uid_topdomain parsed == "onion" | ||
916 | , uid_realname parsed `elem` ["","Anonymous"] | ||
917 | , uid_user parsed == "root" | ||
918 | , fmap (match . fst) (lookup (packet k) torbindings) | ||
919 | == Just True ] | ||
920 | where parsed = parseUID str | ||
921 | match = (==subdom) . take (fromIntegral len) | ||
922 | subdom0 = L.fromChunks [encodeUtf8 (uid_subdomain parsed)] | ||
923 | subdom = Char8.unpack subdom0 | ||
924 | len = T.length (uid_subdomain parsed) | ||
925 | torbindings = getTorKeys (map packet $ flattenTop "" True kd) | ||
926 | getTorKeys pub = do | ||
927 | xs <- groupBindings pub | ||
928 | (_,(top,sub),us,_,_) <- xs | ||
929 | guard ("tor" `elem` us) | ||
930 | let torhash = fromMaybe "" $ derToBase32 <$> derRSA sub | ||
931 | return (top,(torhash,sub)) | ||
932 | |||
933 | groupBindings pub = gs | ||
934 | where (_,bindings) = getBindings pub | ||
935 | bindings' = accBindings bindings | ||
936 | code (c,(m,s),_,_,_) = (fingerprint_material m,-c) | ||
937 | ownerkey (_,(a,_),_,_,_) = a | ||
938 | sameMaster (ownerkey->a) (ownerkey->b) | ||
939 | = fingerprint_material a==fingerprint_material b | ||
940 | gs = groupBy sameMaster (sortBy (comparing code) bindings') | ||
941 | |||
942 | |||
943 | -- (2 of 3) resolveTransform :: Transform -> KeyRingRuntime -> KeyData -> [PacketUpdate] | ||
944 | resolveTransform (DeleteSubkeyByFingerprint fp) rt kd@(KeyData k ksigs umap submap) = fmap (SubKeyDeletion topk) subk | ||
945 | where | ||
946 | topk = keykey $ packet k -- key to master of key to be deleted | ||
947 | subk = do | ||
948 | (k,sub) <- Map.toList submap | ||
949 | guard (map toUpper fp == fingerprint (packet (subkeyMappedPacket sub))) | ||
950 | return k | ||
951 | |||
952 | -- (3 of 3) resolveTransform :: Transform -> KeyRingRuntime -> KeyData -> [PacketUpdate] | ||
953 | resolveTransform (DeleteSubkeyByUsage tag) rt kd@(KeyData k ksigs umap submap) = fmap (SubKeyDeletion topk) subk | ||
954 | where | ||
955 | topk = keykey $ packet k -- key to master of key to be deleted | ||
956 | subk = do | ||
957 | (k,SubKey p sigs) <- Map.toList submap | ||
958 | take 1 $ filter (has_tag tag) $ map (packet . fst) sigs | ||
959 | return k | ||
960 | |||
961 | merge_ :: KeyDB -> FilePath -> [(Packet,Packet,(Packet,Map.Map FilePath Packet))] | 642 | merge_ :: KeyDB -> FilePath -> [(Packet,Packet,(Packet,Map.Map FilePath Packet))] |
962 | -> KeyDB | 643 | -> KeyDB |
963 | merge_ db filename qs = foldl mergeit db (zip [0..] qs) | 644 | merge_ db filename qs = foldl mergeit db (zip [0..] qs) |
@@ -972,140 +653,38 @@ merge_ db filename qs = foldl mergeit db (zip [0..] qs) | |||
972 | 653 | ||
973 | whatP (a,_) = concat . take 1 . words . show $ a | 654 | whatP (a,_) = concat . take 1 . words . show $ a |
974 | 655 | ||
975 | isKey :: Packet -> Bool | ||
976 | isKey (PublicKeyPacket {}) = True | ||
977 | isKey (SecretKeyPacket {}) = True | ||
978 | isKey _ = False | ||
979 | |||
980 | isUserID :: Packet -> Bool | ||
981 | isUserID (UserIDPacket {}) = True | ||
982 | isUserID _ = False | ||
983 | |||
984 | isTrust :: Packet -> Bool | ||
985 | isTrust (TrustPacket {}) = True | ||
986 | isTrust _ = False | ||
987 | 656 | ||
988 | keyPacket :: KeyData -> Packet | 657 | -- insertSubkey :: (MappedPacket -> IO (KikiCondition Packet)) -> t -> KeyData -> [SignatureSubpacket] -> [Char] -> Packet -> IO (KikiCondition (KeyData, [([Char], KikiReportAction)])) |
989 | keyPacket (KeyData k _ _ _) = packet k | 658 | insertSubkey transcode kk (KeyData top topsigs uids subs) tags inputfile key0 = do |
990 | 659 | let topcipher = symmetric_algorithm $ packet top | |
991 | keyFlags0 :: t -> [Packet] -> [SignatureSubpacket] | 660 | tops2k = s2k $ packet top |
992 | keyFlags0 wkun uidsigs = concat | 661 | doDecrypt = transcode (Unencrypted,S2K 100 "") |
993 | [ keyflags | 662 | fname = resolveForReport Nothing inputfile |
994 | , preferredsym | 663 | subkk = keykey key0 |
995 | , preferredhash | 664 | istor = do |
996 | , preferredcomp | 665 | guard ("tor" `elem` mapMaybe usage tags) |
997 | , features ] | 666 | return $ torUIDFromKey key0 |
998 | 667 | addOrigin (SubKey mp sigs) = | |
999 | where | ||
1000 | subs = concatMap hashed_subpackets uidsigs | ||
1001 | keyflags = filterOr isflags subs $ | ||
1002 | KeyFlagsPacket { certify_keys = True | ||
1003 | , sign_data = True | ||
1004 | , encrypt_communication = False | ||
1005 | , encrypt_storage = False | ||
1006 | , split_key = False | ||
1007 | , authentication = False | ||
1008 | , group_key = False | ||
1009 | } | ||
1010 | preferredsym = filterOr ispreferedsym subs $ | ||
1011 | PreferredSymmetricAlgorithmsPacket | ||
1012 | [ AES256 | ||
1013 | , AES192 | ||
1014 | , AES128 | ||
1015 | , CAST5 | ||
1016 | , TripleDES | ||
1017 | ] | ||
1018 | preferredhash = filterOr ispreferedhash subs $ | ||
1019 | PreferredHashAlgorithmsPacket | ||
1020 | [ SHA256 | ||
1021 | , SHA1 | ||
1022 | , SHA384 | ||
1023 | , SHA512 | ||
1024 | , SHA224 | ||
1025 | ] | ||
1026 | preferredcomp = filterOr ispreferedcomp subs $ | ||
1027 | PreferredCompressionAlgorithmsPacket | ||
1028 | [ ZLIB | ||
1029 | , BZip2 | ||
1030 | , ZIP | ||
1031 | ] | ||
1032 | features = filterOr isfeatures subs $ | ||
1033 | FeaturesPacket { supports_mdc = True | ||
1034 | } | ||
1035 | |||
1036 | filterOr pred xs def = if null rs then [def] else rs where rs=filter pred xs | ||
1037 | |||
1038 | isflags (KeyFlagsPacket {}) = True | ||
1039 | isflags _ = False | ||
1040 | ispreferedsym (PreferredSymmetricAlgorithmsPacket {}) = True | ||
1041 | ispreferedsym _ = False | ||
1042 | ispreferedhash (PreferredHashAlgorithmsPacket {}) = True | ||
1043 | ispreferedhash _ = False | ||
1044 | ispreferedcomp (PreferredCompressionAlgorithmsPacket {}) = True | ||
1045 | ispreferedcomp _ = False | ||
1046 | isfeatures (FeaturesPacket {}) = True | ||
1047 | isfeatures _ = False | ||
1048 | |||
1049 | makeInducerSig | ||
1050 | :: Packet | ||
1051 | -> Packet -> Packet -> [SignatureSubpacket] -> SignatureOver | ||
1052 | -- torsig g topk wkun uid timestamp extras = todo | ||
1053 | makeInducerSig topk wkun uid extras | ||
1054 | = CertificationSignature (secretToPublic topk) | ||
1055 | uid | ||
1056 | (sigpackets 0x13 | ||
1057 | subpackets | ||
1058 | subpackets_unh) | ||
1059 | where | ||
1060 | subpackets = -- implicit: [ SignatureCreationTimePacket (fromIntegral timestamp) ] | ||
1061 | tsign | ||
1062 | ++ extras | ||
1063 | subpackets_unh = [IssuerPacket (fingerprint wkun)] | ||
1064 | tsign = if keykey wkun == keykey topk | ||
1065 | then [] -- tsign doesnt make sense for self-signatures | ||
1066 | else [ TrustSignaturePacket 1 120 | ||
1067 | , RegularExpressionPacket regex] | ||
1068 | -- <[^>]+[@.]asdf\.nowhere>$ | ||
1069 | regex = "<[^>]+[@.]"++hostname++">$" | ||
1070 | -- regex = username ++ "@" ++ hostname | ||
1071 | -- username = "[a-zA-Z0-9.][-a-zA-Z0-9.]*\\$?" :: String | ||
1072 | hostname = subdomain' pu ++ "\\." ++ topdomain' pu | ||
1073 | pu = parseUID uidstr where UserIDPacket uidstr = uid | ||
1074 | subdomain' = escape . T.unpack . uid_subdomain | ||
1075 | topdomain' = escape . T.unpack . uid_topdomain | ||
1076 | escape s = concatMap echar s | ||
1077 | where | ||
1078 | echar '|' = "\\|" | ||
1079 | echar '*' = "\\*" | ||
1080 | echar '+' = "\\+" | ||
1081 | echar '?' = "\\?" | ||
1082 | echar '.' = "\\." | ||
1083 | echar '^' = "\\^" | ||
1084 | echar '$' = "\\$" | ||
1085 | echar '\\' = "\\\\" | ||
1086 | echar '[' = "\\[" | ||
1087 | echar ']' = "\\]" | ||
1088 | echar c = [c] | ||
1089 | |||
1090 | insertSubkey :: (MappedPacket -> IO (KikiCondition Packet)) -> t -> KeyData -> [SignatureSubpacket] -> [Char] -> Packet -> IO (KikiCondition (KeyData, [([Char], KikiReportAction)])) | ||
1091 | insertSubkey doDecrypt kk (KeyData top topsigs uids subs) tags fname key = do | ||
1092 | let subkk = keykey key | ||
1093 | (is_new, subkey) = maybe (True, SubKey (mappedPacket fname key) | ||
1094 | []) | ||
1095 | ( (False,) . addOrigin ) | ||
1096 | (Map.lookup subkk subs) | ||
1097 | where | ||
1098 | addOrigin (SubKey mp sigs) = | ||
1099 | let mp' = mp | 668 | let mp' = mp |
1100 | { locations = Map.insert fname | 669 | { locations = Map.insert fname |
1101 | (origin (packet mp) (-1)) | 670 | (origin (packet mp) (-1)) |
1102 | (locations mp) } | 671 | (locations mp) } |
1103 | in SubKey mp' sigs | 672 | in SubKey mp' sigs |
1104 | subs' = Map.insert subkk subkey subs | ||
1105 | 673 | ||
1106 | istor = do | 674 | subkey_result <- do |
1107 | guard ("tor" `elem` mapMaybe usage tags) | 675 | case Map.lookup subkk subs of |
1108 | return $ "Anonymous <root@" ++ take 16 (torhash key) ++ ".onion>" | 676 | Just sub -> return $ KikiSuccess (False,addOrigin sub,Nothing) |
677 | Nothing -> do | ||
678 | wkun' <- doDecrypt top | ||
679 | try wkun' $ \wkun -> do | ||
680 | key' <- transcode (topcipher,tops2k) $ mappedPacket "" key0 | ||
681 | try key' $ \key -> do | ||
682 | return $ KikiSuccess (True, SubKey (mappedPacket fname key) [], Just (wkun,key)) | ||
683 | |||
684 | |||
685 | try subkey_result $ \(is_new,subkey,decrypted) -> do | ||
686 | |||
687 | let subs' = Map.insert subkk subkey subs | ||
1109 | 688 | ||
1110 | uids' <- flip (maybe $ return $ KikiSuccess (uids,[])) istor $ \idstr -> do | 689 | uids' <- flip (maybe $ return $ KikiSuccess (uids,[])) istor $ \idstr -> do |
1111 | let has_torid = do | 690 | let has_torid = do |
@@ -1115,72 +694,58 @@ insertSubkey doDecrypt kk (KeyData top topsigs uids subs) tags fname key = do | |||
1115 | s <- (signatures $ Message (packet top:UserIDPacket idstr:map (packet . fst) sigtrusts)) | 694 | s <- (signatures $ Message (packet top:UserIDPacket idstr:map (packet . fst) sigtrusts)) |
1116 | signatures_over $ verify (Message [packet top]) s | 695 | signatures_over $ verify (Message [packet top]) s |
1117 | flip (flip maybe $ const $ return $ KikiSuccess (uids,[])) has_torid $ do | 696 | flip (flip maybe $ const $ return $ KikiSuccess (uids,[])) has_torid $ do |
1118 | wkun <- doDecrypt top | 697 | |
1119 | 698 | let keyflags = keyFlags (error "dummy argument (insertSubkey)") (map packet $ flattenAllUids fname True uids) | |
1120 | try wkun $ \wkun -> do | 699 | uid = UserIDPacket idstr |
1121 | 700 | -- sig_ov = fst $ torsig g (packet top) wkun uid timestamp keyflags | |
1122 | let keyflags = keyFlags wkun (map packet $ flattenAllUids fname True uids) | 701 | tor_ov = makeInducerSig (packet top) (packet top) uid keyflags |
1123 | uid = UserIDPacket idstr | 702 | wkun' <- maybe (doDecrypt top) (return . KikiSuccess . fst) decrypted |
1124 | -- sig_ov = fst $ torsig g (packet top) wkun uid timestamp keyflags | 703 | try wkun' $ \wkun -> do |
1125 | tor_ov = makeInducerSig (packet top) wkun uid keyflags | 704 | sig_ov <- pgpSign (Message [wkun]) |
1126 | sig_ov <- pgpSign (Message [wkun]) | 705 | tor_ov |
1127 | tor_ov | 706 | SHA1 |
1128 | SHA1 | 707 | (fingerprint wkun) |
1129 | (fingerprint wkun) | 708 | flip (maybe $ return $ KikiSuccess (uids,[(fname, WarnFailedToMakeSignature)])) |
1130 | flip (maybe $ return $ KikiSuccess (uids,[(fname, WarnFailedToMakeSignature)])) | 709 | (sig_ov >>= listToMaybe . signatures_over) |
1131 | (sig_ov >>= listToMaybe . signatures_over) | 710 | $ \sig -> do |
1132 | $ \sig -> do | 711 | let om = Map.singleton fname (origin sig (-1)) |
1133 | let om = Map.singleton fname (origin sig (-1)) | 712 | trust = Map.empty |
1134 | trust = Map.empty | 713 | return $ KikiSuccess |
1135 | return $ KikiSuccess | 714 | ( Map.insert idstr ([( (mappedPacket fname sig) {locations=om} |
1136 | ( Map.insert idstr ([( (mappedPacket fname sig) {locations=om} | 715 | , trust)],om) uids |
1137 | , trust)],om) uids | 716 | , [] ) |
1138 | , [] ) | ||
1139 | 717 | ||
1140 | try uids' $ \(uids',report) -> do | 718 | try uids' $ \(uids',report) -> do |
1141 | 719 | ||
1142 | let SubKey subkey_p subsigs = subkey | 720 | let SubKey subkey_p subsigs = subkey |
1143 | wk = packet top | 721 | wk = packet top |
1144 | (xs',minsig,ys') = findTag tags wk key subsigs | 722 | (xs',minsig,ys') = findTag tags wk key0 subsigs |
1145 | doInsert mbsig = do | 723 | doInsert mbsig = do |
1146 | -- NEW SUBKEY BINDING SIGNATURE | 724 | -- NEW SUBKEY BINDING SIGNATURE |
1147 | sig' <- makeSig doDecrypt top fname subkey_p tags mbsig | 725 | -- XXX: Here I assume that key0 is the unencrypted version |
1148 | try sig' $ \(sig',report) -> do | 726 | -- of subkey_p. TODO: Check this assumption. |
1149 | report <- return $ fmap (fname,) report ++ [(fname, YieldSignature)] | 727 | sig' <- makeSig doDecrypt top fname subkey_p{packet=key0} tags mbsig |
1150 | let subs' = Map.insert subkk | 728 | try sig' $ \(sig',report) -> do |
1151 | (SubKey subkey_p $ xs'++[sig']++ys') | 729 | report <- return $ fmap (fname,) report ++ [(fname, YieldSignature)] |
1152 | subs | 730 | let subs' = Map.insert subkk |
1153 | return $ KikiSuccess ( KeyData top topsigs uids' subs' | 731 | (SubKey subkey_p $ xs'++[sig']++ys') |
1154 | , report ) | 732 | subs |
1155 | 733 | return $ KikiSuccess ( KeyData top topsigs uids' subs' | |
1156 | report <- let f = if is_new then (++[(fname,YieldSecretKeyPacket s)]) | 734 | , report ) |
1157 | else id | 735 | |
1158 | s = show (fmap fst minsig,fingerprint key) | 736 | report <- let f = if is_new then (++[(fname,YieldSecretKeyPacket s)]) |
1159 | in return (f report) | 737 | else id |
1160 | 738 | s = show (fmap fst minsig,fingerprint key0) | |
1161 | case minsig of | 739 | in return (f report) |
1162 | Nothing -> doInsert Nothing -- we need to create a new sig | 740 | |
1163 | Just (True,sig) -> -- we can deduce is_new == False | 741 | case minsig of |
1164 | -- we may need to add a tor id | 742 | Nothing -> doInsert Nothing -- we need to create a new sig |
1165 | return $ KikiSuccess ( KeyData top topsigs uids' subs' | 743 | Just (True,sig) -> -- we can deduce is_new == False |
1166 | , report ) | 744 | -- we may need to add a tor id |
1167 | Just (False,sig) -> doInsert (Just sig) -- We have a sig, but is missing usage@ tag | 745 | return $ KikiSuccess ( KeyData top topsigs uids' subs' |
1168 | 746 | , report ) | |
1169 | mappedPacket :: FilePath -> Packet -> MappedPacket | 747 | Just (False,sig) -> doInsert (Just sig) -- We have a sig, but is missing usage@ tag |
1170 | mappedPacket filename p = MappedPacket | 748 | |
1171 | { packet = p | ||
1172 | , locations = Map.singleton filename (origin p (-1)) | ||
1173 | } | ||
1174 | |||
1175 | showPacket :: Packet -> String | ||
1176 | showPacket p | isKey p = (if is_subkey p | ||
1177 | then showPacket0 p | ||
1178 | else ifSecret p "----Secret-----" "----Public-----") | ||
1179 | ++ " "++show (key_algorithm p)++" "++fingerprint p | ||
1180 | | isUserID p = showPacket0 p ++ " " ++ show (uidkey p) | ||
1181 | | otherwise = showPacket0 p | ||
1182 | showPacket0 :: Show a => a -> [Char] | ||
1183 | showPacket0 p = concat . take 1 $ words (show p) | ||
1184 | 749 | ||
1185 | mergeSig :: SigAndTrust -> [SigAndTrust] -> [SigAndTrust] | 750 | mergeSig :: SigAndTrust -> [SigAndTrust] -> [SigAndTrust] |
1186 | mergeSig sig sigs = | 751 | mergeSig sig sigs = |
@@ -1207,26 +772,6 @@ mergeSig sig sigs = | |||
1207 | 772 | ||
1208 | mergeSameSig a b = b -- trace ("discarding dup "++show a) b | 773 | mergeSameSig a b = b -- trace ("discarding dup "++show a) b |
1209 | 774 | ||
1210 | usageString :: PGPKeyFlags -> String | ||
1211 | usageString flgs = | ||
1212 | case flgs of | ||
1213 | Special -> "special" | ||
1214 | Vouch -> "vouch" -- signkey | ||
1215 | Sign -> "sign" | ||
1216 | VouchSign -> "vouch-sign" | ||
1217 | Communication -> "communication" | ||
1218 | VouchCommunication -> "vouch-communication" | ||
1219 | SignCommunication -> "sign-communication" | ||
1220 | VouchSignCommunication -> "vouch-sign-communication" | ||
1221 | Storage -> "storage" | ||
1222 | VouchStorage -> "vouch-storage" | ||
1223 | SignStorage -> "sign-storage" | ||
1224 | VouchSignStorage -> "vouch-sign-storage" | ||
1225 | Encrypt -> "encrypt" | ||
1226 | VouchEncrypt -> "vouch-encrypt" | ||
1227 | SignEncrypt -> "sign-encrypt" | ||
1228 | VouchSignEncrypt -> "vouch-sign-encrypt" | ||
1229 | |||
1230 | parseSingleSpec :: String -> SingleKeySpec | 775 | parseSingleSpec :: String -> SingleKeySpec |
1231 | parseSingleSpec "*" = AnyMatch | 776 | parseSingleSpec "*" = AnyMatch |
1232 | parseSingleSpec "-" = WorkingKeyMatch | 777 | parseSingleSpec "-" = WorkingKeyMatch |
@@ -1270,66 +815,66 @@ matchSpec (KeyUidMatch pat) (KeyData _ _ uids _) = not $ null us | |||
1270 | us = filter (isInfixOf pat) $ Map.keys uids | 815 | us = filter (isInfixOf pat) $ Map.keys uids |
1271 | 816 | ||
1272 | doImport | 817 | doImport |
1273 | :: (MappedPacket -> IO (KikiCondition Packet)) | 818 | :: PacketTranscoder |
1274 | -> Map.Map KeyKey KeyData | 819 | -> Map.Map KeyKey KeyData |
1275 | -> (FilePath, Maybe [Char], [KeyKey], StreamInfo, t) | 820 | -> (InputFile, Maybe [Char], [KeyKey], StreamInfo, t) |
1276 | -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)])) | 821 | -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)])) |
1277 | doImport doDecrypt db (fname,subspec,ms,typ -> typ,_) = do | 822 | doImport transcode db (fname,subspec,ms,typ -> typ,_) = do |
1278 | flip (maybe $ return CannotImportMasterKey) | 823 | flip (maybe $ return CannotImportMasterKey) |
1279 | subspec $ \tag -> do | 824 | subspec $ \tag -> do |
1280 | (certs,keys) <- case typ of | 825 | (certs,keys) <- case typ of |
1281 | PEMFile -> do | 826 | PEMFile -> do |
1282 | ps <- readSecretPEMFile (ArgFile fname) | 827 | ps <- readSecretPEMFile fname |
1283 | let (mapMaybe spemCert -> certs,mapMaybe spemPacket-> keys) | 828 | let (mapMaybe spemCert -> certs,mapMaybe spemPacket-> keys) |
1284 | = partition (isJust . spemCert) ps | 829 | = partition (isJust . spemCert) ps |
1285 | return (certs,keys) | 830 | return (certs,keys) |
1286 | DNSPresentation -> do | 831 | DNSPresentation -> do |
1287 | p <- readSecretDNSFile (ArgFile fname) | 832 | p <- readSecretDNSFile fname |
1288 | return ([],[p]) | 833 | return ([],[p]) |
1289 | -- TODO Probably we need to move to a new design where signature | 834 | -- TODO Probably we need to move to a new design where signature |
1290 | -- packets are merged into the database in one phase with null | 835 | -- packets are merged into the database in one phase with null |
1291 | -- signatures, and then the signatures are made in the next phase. | 836 | -- signatures, and then the signatures are made in the next phase. |
1292 | -- This would let us merge annotations (like certificates) from | 837 | -- This would let us merge annotations (like certificates) from |
1293 | -- seperate files. | 838 | -- seperate files. |
1294 | foldM (importKey tag certs) (KikiSuccess (db,[])) keys | 839 | foldM (importKey tag certs) (KikiSuccess (db,[])) keys |
1295 | where | 840 | where |
1296 | importKey tag certs prior key = do | 841 | importKey tag certs prior key = do |
1297 | try prior $ \(db,report) -> do | 842 | try prior $ \(db,report) -> do |
1298 | let (m0,tailms) = splitAt 1 ms | 843 | let (m0,tailms) = splitAt 1 ms |
1299 | if (not (null tailms) || null m0) | 844 | if (not (null tailms) || null m0) |
1300 | then return $ AmbiguousKeySpec fname | 845 | then return $ AmbiguousKeySpec (resolveForReport Nothing fname) |
1301 | else do | 846 | else do |
1302 | let kk = keykey key | 847 | let kk = keykey key |
1303 | cs = filter (\c -> kk==keykey (pcertKey c)) certs | 848 | cs = filter (\c -> kk==keykey (pcertKey c)) certs |
1304 | blobs = map mkCertNotation $ nub $ map pcertBlob cs | 849 | blobs = map mkCertNotation $ nub $ map pcertBlob cs |
1305 | mkCertNotation bs = NotationDataPacket | 850 | mkCertNotation bs = NotationDataPacket |
1306 | { human_readable = False | 851 | { human_readable = False |
1307 | , notation_name = "x509cert@" | 852 | , notation_name = "x509cert@" |
1308 | , notation_value = Char8.unpack bs } | 853 | , notation_value = Char8.unpack bs } |
1309 | datedKey = key { timestamp = fromTime $ minimum dates } | 854 | datedKey = key { timestamp = fromTime $ minimum dates } |
1310 | dates = fromTime (timestamp key) : map pcertTimestamp certs | 855 | dates = fromTime (timestamp key) : map pcertTimestamp certs |
1311 | r <- doImportG doDecrypt db m0 (mkUsage tag:blobs) fname datedKey | 856 | r <- doImportG transcode db m0 (mkUsage tag:blobs) fname datedKey |
1312 | try r $ \(db',report') -> do | 857 | try r $ \(db',report') -> do |
1313 | return $ KikiSuccess (db',report++report') | 858 | return $ KikiSuccess (db',report++report') |
1314 | 859 | ||
1315 | generateSubkey :: | 860 | generateSubkey :: |
1316 | (MappedPacket -> IO (KikiCondition Packet)) -- decrypt[ | 861 | PacketTranscoder |
1317 | -> KikiCondition (KeyData, [(FilePath, KikiReportAction)]) -- db | 862 | -> KikiCondition (KeyData, [(FilePath, KikiReportAction)]) -- db |
1318 | -> (GenerateKeyParams, StreamInfo) | 863 | -> (GenerateKeyParams, StreamInfo) |
1319 | -> IO (KikiCondition (KeyData, [(FilePath, KikiReportAction)])) | 864 | -> IO (KikiCondition (KeyData, [(FilePath, KikiReportAction)])) |
1320 | generateSubkey doDecrypt kd' (genparam,StreamInfo { spill = KF_Match tag }) = do | 865 | generateSubkey transcode kd' (genparam,StreamInfo { spill = KF_Match tag }) = do |
1321 | try kd' $ \(kd,report0) -> do | 866 | try kd' $ \(kd,report0) -> do |
1322 | let subs = do | 867 | let subs = do |
1323 | SubKey p sigs <- Map.elems $ keySubKeys kd | 868 | SubKey p sigs <- Map.elems $ keySubKeys kd |
1324 | filter (has_tag tag) $ map (packet . fst) sigs | 869 | filter (has_tag tag) $ map (packet . fst) sigs |
1325 | if null subs | 870 | if null subs |
1326 | then do | 871 | then do |
1327 | newkey <- generateKey genparam | 872 | newkey <- generateKey genparam |
1328 | kdr <- insertSubkey doDecrypt (keykey (keyPacket kd)) kd [mkUsage tag] "" newkey | 873 | kdr <- insertSubkey transcode (keykey (keyPacket kd)) kd [mkUsage tag] (ArgFile "") newkey |
1329 | try kdr $ \(newkd,report) -> do | 874 | try kdr $ \(newkd,report) -> do |
1330 | return $ KikiSuccess (newkd, report ++ [("", NewPacket $ showPacket newkey)]) | 875 | return $ KikiSuccess (newkd, report ++ [("", NewPacket $ showPacket newkey)]) |
1331 | else do | 876 | else do |
1332 | return $ KikiSuccess (kd,report0) | 877 | return $ KikiSuccess (kd,report0) |
1333 | generateSubkey _ kd _ = return kd | 878 | generateSubkey _ kd _ = return kd |
1334 | 879 | ||
1335 | -- | | 880 | -- | |
@@ -1496,12 +1041,6 @@ secp256k1_id = 0x2b8104000a | |||
1496 | "NIST P-521" 6 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23 | 1041 | "NIST P-521" 6 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23 |
1497 | -} | 1042 | -} |
1498 | 1043 | ||
1499 | readInputFileS :: InputFileContext -> InputFile -> IO S.ByteString | ||
1500 | readInputFileS ctx (Pipe fd _) = fdToHandle fd >>= S.hGetContents | ||
1501 | readInputFileS ctx (FileDesc fd) = fdToHandle fd >>= S.hGetContents | ||
1502 | readInputFileS ctx inp = do | ||
1503 | let fname = resolveInputFile ctx inp | ||
1504 | fmap S.concat $ mapM S.readFile fname | ||
1505 | 1044 | ||
1506 | keyCompare :: String -> Packet -> Packet -> Ordering | 1045 | keyCompare :: String -> Packet -> Packet -> Ordering |
1507 | keyCompare what (SecretKeyPacket {}) (PublicKeyPacket {}) = LT | 1046 | keyCompare what (SecretKeyPacket {}) (PublicKeyPacket {}) = LT |
@@ -1514,101 +1053,6 @@ keyCompare what a b = error $ unlines ["Unable to merge "++what++":" | |||
1514 | , PP.ppShow b | 1053 | , PP.ppShow b |
1515 | ] | 1054 | ] |
1516 | 1055 | ||
1517 | parseUID :: String -> UserIDRecord | ||
1518 | parseUID str = UserIDRecord { | ||
1519 | uid_full = str, | ||
1520 | uid_realname = realname, | ||
1521 | uid_user = user, | ||
1522 | uid_subdomain = subdomain, | ||
1523 | uid_topdomain = topdomain | ||
1524 | } | ||
1525 | where | ||
1526 | text = T.pack str | ||
1527 | (T.strip-> realname, T.dropAround isBracket-> email) | ||
1528 | = T.break (=='<') text | ||
1529 | (user, T.drop 1-> hostname) = T.break (=='@') email | ||
1530 | ( T.reverse -> topdomain, | ||
1531 | T.reverse . T.drop 1 -> subdomain) | ||
1532 | = T.break (=='.') . T.reverse $ hostname | ||
1533 | |||
1534 | flattenTop :: FilePath -> Bool -> KeyData -> [MappedPacket] | ||
1535 | flattenTop fname ispub (KeyData key sigs uids subkeys) = | ||
1536 | unk ispub key : | ||
1537 | ( flattenAllUids fname ispub uids | ||
1538 | ++ concatSort fname head (flattenSub fname ispub) (Map.elems subkeys)) | ||
1539 | |||
1540 | derToBase32 :: ByteString -> String | ||
1541 | #if !defined(VERSION_cryptonite) | ||
1542 | derToBase32 = map toLower . Base32.encode . S.unpack . SHA1.hashlazy | ||
1543 | #else | ||
1544 | derToBase32 = map toLower . Base32.encode . S.unpack . sha1 | ||
1545 | where | ||
1546 | sha1 :: L.ByteString -> S.ByteString | ||
1547 | sha1 x = convert (Vincent.hashlazy x :: Vincent.Digest Vincent.SHA1) | ||
1548 | #endif | ||
1549 | |||
1550 | derRSA :: Packet -> Maybe ByteString | ||
1551 | derRSA rsa = do | ||
1552 | k <- rsaKeyFromPacket rsa | ||
1553 | return $ encodeASN1 DER (toASN1 k []) | ||
1554 | |||
1555 | getBindings :: | ||
1556 | [Packet] | ||
1557 | -> | ||
1558 | ( [([Packet],[SignatureOver])] -- other signatures with key sets | ||
1559 | -- that were used for the verifications | ||
1560 | , [(Word8, | ||
1561 | (Packet, Packet), -- (topkey,subkey) | ||
1562 | [String], -- usage flags | ||
1563 | [SignatureSubpacket], -- hashed data | ||
1564 | [Packet])] -- binding signatures | ||
1565 | ) | ||
1566 | getBindings pkts = (sigs,bindings) | ||
1567 | where | ||
1568 | (sigs,concat->bindings) = unzip $ do | ||
1569 | let (keys,_) = partition isKey pkts | ||
1570 | keys <- disjoint_fp keys | ||
1571 | let (bs,sigs) = verifyBindings keys pkts | ||
1572 | return . ((keys,sigs),) $ do | ||
1573 | b <- bs -- trace ("sigs = "++show (map (map signature_issuer . signatures_over) sigs)) bs | ||
1574 | i <- map signature_issuer (signatures_over b) | ||
1575 | i <- maybeToList i | ||
1576 | who <- maybeToList $ find_key fingerprint (Message keys) i | ||
1577 | let (code,claimants) = | ||
1578 | case () of | ||
1579 | _ | who == topkey b -> (1,[]) | ||
1580 | _ | who == subkey b -> (2,[]) | ||
1581 | _ -> (0,[who]) | ||
1582 | let hashed = signatures_over b >>= hashed_subpackets | ||
1583 | kind = guard (code==1) >> hashed >>= maybeToList . usage | ||
1584 | return (code,(topkey b,subkey b), kind, hashed,claimants) | ||
1585 | |||
1586 | -- Returned data is simmilar to getBindings but the Word8 codes | ||
1587 | -- are ORed together. | ||
1588 | accBindings :: | ||
1589 | Bits t => | ||
1590 | [(t, (Packet, Packet), [a], [a1], [a2])] | ||
1591 | -> [(t, (Packet, Packet), [a], [a1], [a2])] | ||
1592 | accBindings bs = as | ||
1593 | where | ||
1594 | gs = groupBy samePair . sortBy (comparing bindingPair) $ bs | ||
1595 | as = map (foldl1 combine) gs | ||
1596 | bindingPair (_,p,_,_,_) = pub2 p | ||
1597 | where | ||
1598 | pub2 (a,b) = (pub a, pub b) | ||
1599 | pub a = fingerprint_material a | ||
1600 | samePair a b = bindingPair a == bindingPair b | ||
1601 | combine (ac,p,akind,ahashed,aclaimaints) | ||
1602 | (bc,_,bkind,bhashed,bclaimaints) | ||
1603 | = (ac .|. bc,p,akind++bkind,ahashed++bhashed,aclaimaints++bclaimaints) | ||
1604 | |||
1605 | subkeyMappedPacket :: SubKey -> MappedPacket | ||
1606 | subkeyMappedPacket (SubKey k _ ) = k | ||
1607 | |||
1608 | has_tag :: String -> Packet -> Bool | ||
1609 | has_tag tag p = isSignaturePacket p | ||
1610 | && or [ tag `elem` mapMaybe usage (hashed_subpackets p) | ||
1611 | , tag `elem` map usageString (mapMaybe keyflags (hashed_subpackets p)) ] | ||
1612 | 1056 | ||
1613 | dbInsertPacket :: KeyData -> FilePath -> (Int,(Packet,Packet,(Packet,Map.Map FilePath Packet))) -> Maybe KeyData | 1057 | dbInsertPacket :: KeyData -> FilePath -> (Int,(Packet,Packet,(Packet,Map.Map FilePath Packet))) -> Maybe KeyData |
1614 | dbInsertPacket kd filename (n,(top,sub,ptt@(p,trustmap))) = update (Just kd) | 1058 | dbInsertPacket kd filename (n,(top,sub,ptt@(p,trustmap))) = update (Just kd) |
@@ -1689,200 +1133,55 @@ secretToPublic pkt@(SecretKeyPacket {}) = | |||
1689 | } | 1133 | } |
1690 | secretToPublic pkt = pkt | 1134 | secretToPublic pkt = pkt |
1691 | 1135 | ||
1692 | sigpackets :: | ||
1693 | Monad m => | ||
1694 | Word8 -> [SignatureSubpacket] -> [SignatureSubpacket] -> m Packet | ||
1695 | sigpackets typ hashed unhashed = return $ | ||
1696 | signaturePacket | ||
1697 | 4 -- version | ||
1698 | typ -- 0x18 subkey binding sig, or 0x19 back-signature | ||
1699 | RSA | ||
1700 | SHA1 | ||
1701 | hashed | ||
1702 | unhashed | ||
1703 | 0 -- Word16 -- Left 16 bits of the signed hash value | ||
1704 | [] -- [MPI] | ||
1705 | |||
1706 | usage :: SignatureSubpacket -> Maybe String | ||
1707 | usage (NotationDataPacket | ||
1708 | { human_readable = True | ||
1709 | , notation_name = "usage@" | ||
1710 | , notation_value = u | ||
1711 | }) = Just u | ||
1712 | usage _ = Nothing | ||
1713 | |||
1714 | torhash :: Packet -> String | ||
1715 | torhash key = fromMaybe "" $ derToBase32 <$> derRSA key | ||
1716 | |||
1717 | keyFlags :: t -> [Packet] -> [SignatureSubpacket] | ||
1718 | keyFlags wkun uids = keyFlags0 wkun (filter isSignaturePacket uids) | ||
1719 | |||
1720 | flattenAllUids :: FilePath -> Bool -> Map.Map String ([SigAndTrust],OriginMap) -> [MappedPacket] | ||
1721 | flattenAllUids fname ispub uids = | ||
1722 | concatSort fname head (flattenUid fname ispub) (Map.assocs uids) | ||
1723 | |||
1724 | -- | Given list of subpackets, a master key, one of its subkeys and a | ||
1725 | -- list of signatures on that subkey, yields: | ||
1726 | -- | ||
1727 | -- * preceding list of signatures | ||
1728 | -- | ||
1729 | -- * The most recent valid signature made by the master key along with a | ||
1730 | -- flag that indicates whether or not all of the supplied subpackets occur in | ||
1731 | -- it or, if no valid signature from the working key is present, Nothing. | ||
1732 | -- | ||
1733 | -- * following list of signatures | ||
1734 | -- | ||
1735 | findTag :: | ||
1736 | [SignatureSubpacket] | ||
1737 | -> Packet | ||
1738 | -> Packet | ||
1739 | -> [(MappedPacket, b)] | ||
1740 | -> ([(MappedPacket, b)], | ||
1741 | Maybe (Bool, (MappedPacket, b)), | ||
1742 | [(MappedPacket, b)]) | ||
1743 | findTag tag topk subkey subsigs = (xs',minsig,ys') | ||
1744 | where | ||
1745 | vs = map (\sig -> | ||
1746 | (sig, do | ||
1747 | sig <- Just (packet . fst $ sig) | ||
1748 | guard (isSignaturePacket sig) | ||
1749 | guard $ flip isSuffixOf | ||
1750 | (fingerprint topk) | ||
1751 | . fromMaybe "%bad%" | ||
1752 | . signature_issuer | ||
1753 | $ sig | ||
1754 | listToMaybe $ | ||
1755 | map (signature_time . verify (Message [topk])) | ||
1756 | (signatures $ Message [topk,subkey,sig]))) | ||
1757 | subsigs | ||
1758 | (xs,ys) = splitAtMinBy (comparing (Down . snd)) vs | ||
1759 | xs' = map fst xs | ||
1760 | ys' = map fst $ if isNothing minsig then ys else drop 1 ys | ||
1761 | minsig = do | ||
1762 | (sig,ov) <- listToMaybe ys | ||
1763 | ov | ||
1764 | let hshed = hashed_subpackets $ packet $ fst sig | ||
1765 | return ( null $ tag \\ hshed, sig) | ||
1766 | |||
1767 | makeSig :: | ||
1768 | (MappedPacket -> IO (KikiCondition Packet)) | ||
1769 | -> MappedPacket | ||
1770 | -> [Char] | ||
1771 | -> MappedPacket | ||
1772 | -> [SignatureSubpacket] | ||
1773 | -> Maybe (MappedPacket, Map.Map k a) | ||
1774 | -> IO (KikiCondition ((MappedPacket, Map.Map k a), [KikiReportAction])) | ||
1775 | makeSig doDecrypt top fname subkey_p tags mbsig = do | ||
1776 | let wk = packet top | ||
1777 | wkun <- doDecrypt top | ||
1778 | try wkun $ \wkun -> do | ||
1779 | let grip = fingerprint wk | ||
1780 | addOrigin new_sig = | ||
1781 | flip | ||
1782 | (maybe $ return FailedToMakeSignature) | ||
1783 | (new_sig >>= listToMaybe . signatures_over) $ \new_sig -> do | ||
1784 | let mp' = mappedPacket fname new_sig | ||
1785 | return $ KikiSuccess (mp', Map.empty) | ||
1786 | parsedkey = [packet subkey_p] | ||
1787 | hashed0 | ||
1788 | | any isFlagsPacket tags = tags | ||
1789 | | otherwise = | ||
1790 | KeyFlagsPacket | ||
1791 | { certify_keys = False | ||
1792 | , sign_data = False | ||
1793 | , encrypt_communication = False | ||
1794 | , encrypt_storage = False | ||
1795 | , split_key = False | ||
1796 | , authentication = True | ||
1797 | , group_key = False | ||
1798 | } : | ||
1799 | tags | ||
1800 | -- implicitly added: | ||
1801 | -- , SignatureCreationTimePacket (fromIntegral timestamp) | ||
1802 | isFlagsPacket (KeyFlagsPacket {}) = True | ||
1803 | isFlagsPacket _ = False | ||
1804 | subgrip = fingerprint (head parsedkey) | ||
1805 | back_sig <- | ||
1806 | pgpSign | ||
1807 | (Message parsedkey) | ||
1808 | (SubkeySignature | ||
1809 | wk | ||
1810 | (head parsedkey) | ||
1811 | (sigpackets 0x19 hashed0 [IssuerPacket subgrip])) | ||
1812 | (if key_algorithm (head parsedkey) == ECDSA | ||
1813 | then SHA256 | ||
1814 | else SHA1) | ||
1815 | subgrip | ||
1816 | let iss = IssuerPacket (fingerprint wk) | ||
1817 | cons_iss back_sig = | ||
1818 | iss : map EmbeddedSignaturePacket (signatures_over back_sig) | ||
1819 | unhashed0 = maybe [iss] cons_iss back_sig | ||
1820 | new_sig <- | ||
1821 | pgpSign | ||
1822 | (Message [wkun]) | ||
1823 | (SubkeySignature wk (head parsedkey) (sigpackets 0x18 hashed0 unhashed0)) | ||
1824 | SHA1 | ||
1825 | grip | ||
1826 | let newSig = do | ||
1827 | r <- addOrigin new_sig | ||
1828 | return $ fmap (, []) r | ||
1829 | flip (maybe newSig) mbsig $ \(mp, trustmap) -> do | ||
1830 | let sig = packet mp | ||
1831 | isCreation (SignatureCreationTimePacket {}) = True | ||
1832 | isCreation _ = False | ||
1833 | isExpiration (SignatureExpirationTimePacket {}) = True | ||
1834 | isExpiration _ = False | ||
1835 | (cs, ps) = partition isCreation (hashed_subpackets sig) | ||
1836 | (es, qs) = partition isExpiration ps | ||
1837 | stamp = listToMaybe . sortBy (comparing Down) $ map unwrap cs | ||
1838 | where | ||
1839 | unwrap (SignatureCreationTimePacket x) = x | ||
1840 | exp = listToMaybe $ sort $ map unwrap es | ||
1841 | where | ||
1842 | unwrap (SignatureExpirationTimePacket x) = x | ||
1843 | expires = liftA2 (+) stamp exp | ||
1844 | timestamp <- now | ||
1845 | if fmap ((< timestamp) . fromIntegral) expires == Just True | ||
1846 | then return $ | ||
1847 | KikiSuccess ((mp, trustmap), [UnableToUpdateExpiredSignature]) | ||
1848 | else do | ||
1849 | let times = | ||
1850 | (:) (SignatureExpirationTimePacket (fromIntegral timestamp)) $ | ||
1851 | maybeToList $ do | ||
1852 | e <- expires | ||
1853 | return $ | ||
1854 | SignatureExpirationTimePacket (e - fromIntegral timestamp) | ||
1855 | sig' = sig {hashed_subpackets = times ++ (qs `union` tags)} | ||
1856 | new_sig <- | ||
1857 | pgpSign | ||
1858 | (Message [wkun]) | ||
1859 | (SubkeySignature wk (packet subkey_p) [sig']) | ||
1860 | SHA1 | ||
1861 | (fingerprint wk) | ||
1862 | newsig <- addOrigin new_sig | ||
1863 | return $ fmap (, []) newsig | ||
1864 | 1136 | ||
1865 | ifSecret :: Packet -> t -> t -> t | 1137 | ifSecret :: Packet -> t -> t -> t |
1866 | ifSecret (SecretKeyPacket {}) t f = t | 1138 | ifSecret (SecretKeyPacket {}) t f = t |
1867 | ifSecret _ t f = f | 1139 | ifSecret _ t f = f |
1868 | 1140 | ||
1869 | uidkey :: Packet -> String | 1141 | instance ASN1Object RSAPrivateKey where |
1870 | uidkey (UserIDPacket str) = str | 1142 | toASN1 rsa@(RSAPrivateKey {}) |
1871 | 1143 | = \xs -> Start Sequence | |
1872 | keyflags :: SignatureSubpacket -> Maybe PGPKeyFlags | 1144 | : IntVal 0 |
1873 | keyflags flgs@(KeyFlagsPacket {}) = | 1145 | : mpiVal rsaN |
1874 | Just . toEnum $ | 1146 | : mpiVal rsaE |
1875 | ( bit 0x1 certify_keys | 1147 | : mpiVal rsaD |
1876 | .|. bit 0x2 sign_data | 1148 | : mpiVal rsaP |
1877 | .|. bit 0x4 encrypt_communication | 1149 | : mpiVal rsaQ |
1878 | .|. bit 0x8 encrypt_storage ) :: Maybe PGPKeyFlags | 1150 | : mpiVal rsaDmodP1 |
1879 | -- other flags: | 1151 | : mpiVal rsaDmodQminus1 |
1880 | -- split_key | 1152 | : mpiVal rsaCoefficient |
1881 | -- authentication (ssh-client) | 1153 | : End Sequence |
1882 | -- group_key | 1154 | : xs |
1883 | where | 1155 | where mpiVal f = IntVal x where MPI x = f rsa |
1884 | bit v f = if f flgs then v else 0 | 1156 | |
1885 | keyflags _ = Nothing | 1157 | fromASN1 ( Start Sequence |
1158 | : IntVal _ -- version | ||
1159 | : IntVal n | ||
1160 | : IntVal e | ||
1161 | : IntVal d | ||
1162 | : IntVal p | ||
1163 | : IntVal q | ||
1164 | : IntVal dmodp1 | ||
1165 | : IntVal dmodqminus1 | ||
1166 | : IntVal coefficient | ||
1167 | : ys) = | ||
1168 | Right ( privkey, tail $ dropWhile notend ys) | ||
1169 | where | ||
1170 | notend (End Sequence) = False | ||
1171 | notend _ = True | ||
1172 | privkey = RSAPrivateKey | ||
1173 | { rsaN = MPI n | ||
1174 | , rsaE = MPI e | ||
1175 | , rsaD = MPI d | ||
1176 | , rsaP = MPI p | ||
1177 | , rsaQ = MPI q | ||
1178 | , rsaDmodP1 = MPI dmodp1 | ||
1179 | , rsaDmodQminus1 = MPI dmodqminus1 | ||
1180 | , rsaCoefficient = MPI coefficient | ||
1181 | } | ||
1182 | fromASN1 _ = | ||
1183 | Left "fromASN1: RSAPrivateKey: unexpected format" | ||
1184 | |||
1886 | 1185 | ||
1887 | readSecretPEMFile :: InputFile -> IO [SecretPEMData] | 1186 | readSecretPEMFile :: InputFile -> IO [SecretPEMData] |
1888 | readSecretPEMFile fname = do | 1187 | readSecretPEMFile fname = do |
@@ -1912,6 +1211,7 @@ readSecretPEMFile fname = do | |||
1912 | mergeDate (tm,_) (Right mb) = (tm,mb) | 1211 | mergeDate (tm,_) (Right mb) = (tm,mb) |
1913 | return $ dta | 1212 | return $ dta |
1914 | 1213 | ||
1214 | |||
1915 | readSecretDNSFile :: InputFile -> IO Packet | 1215 | readSecretDNSFile :: InputFile -> IO Packet |
1916 | readSecretDNSFile fname = do | 1216 | readSecretDNSFile fname = do |
1917 | let ctx = InputFileContext "" "" | 1217 | let ctx = InputFileContext "" "" |
@@ -1992,97 +1292,6 @@ socketFamily (SockAddrUnix _) = AF_UNIX | |||
1992 | selectPublicKey :: (KeySpec,Maybe String) -> KeyDB -> Maybe Packet | 1292 | selectPublicKey :: (KeySpec,Maybe String) -> KeyDB -> Maybe Packet |
1993 | selectPublicKey (spec,mtag) db = selectKey0 True (spec,mtag) db | 1293 | selectPublicKey (spec,mtag) db = selectKey0 True (spec,mtag) db |
1994 | 1294 | ||
1995 | isBracket :: Char -> Bool | ||
1996 | isBracket '<' = True | ||
1997 | isBracket '>' = True | ||
1998 | isBracket _ = False | ||
1999 | |||
2000 | unk :: Bool -> MappedPacket -> MappedPacket | ||
2001 | unk isPublic = if isPublic then toPacket secretToPublic else id | ||
2002 | where toPacket f mp@(MappedPacket {packet=p}) = mp {packet=(f p)} | ||
2003 | |||
2004 | concatSort :: | ||
2005 | FilePath -> ([a] -> MappedPacket) -> (b -> [a]) -> [b] -> [a] | ||
2006 | concatSort fname getp f = concat . sortByHint fname getp . map f | ||
2007 | |||
2008 | flattenSub :: FilePath -> Bool -> SubKey -> [MappedPacket] | ||
2009 | flattenSub fname ispub (SubKey key sigs) = unk ispub key: concatSort fname head (unsig fname ispub) sigs | ||
2010 | |||
2011 | rsaKeyFromPacket :: Packet -> Maybe RSAPublicKey | ||
2012 | rsaKeyFromPacket p | isKey p = do | ||
2013 | n <- lookup 'n' $ key p | ||
2014 | e <- lookup 'e' $ key p | ||
2015 | return $ RSAKey n e | ||
2016 | |||
2017 | rsaKeyFromPacket _ = Nothing | ||
2018 | |||
2019 | disjoint_fp :: [Packet] -> [[Packet]] | ||
2020 | disjoint_fp ks = {- concatMap group2 $ -} transpose grouped | ||
2021 | where | ||
2022 | grouped = groupBy samepr . sortBy (comparing smallpr) $ ks | ||
2023 | samepr a b = smallpr a == smallpr b | ||
2024 | |||
2025 | {- | ||
2026 | -- useful for testing | ||
2027 | group2 :: [a] -> [[a]] | ||
2028 | group2 (x:y:ys) = [x,y]:group2 ys | ||
2029 | group2 [x] = [[x]] | ||
2030 | group2 [] = [] | ||
2031 | -} | ||
2032 | |||
2033 | verifyBindings :: [Packet] -> [Packet] -> ([SignatureOver], [SignatureOver]) | ||
2034 | verifyBindings keys nonkeys = (top ++ filter isSubkeySignature embedded,othersigs) | ||
2035 | where | ||
2036 | verified = do | ||
2037 | sig <- signatures (Message nonkeys) | ||
2038 | let v = verify (Message keys) sig | ||
2039 | guard (not . null $ signatures_over v) | ||
2040 | return v | ||
2041 | (top,othersigs) = partition isSubkeySignature verified | ||
2042 | embedded = do | ||
2043 | sub <- top | ||
2044 | let sigover = signatures_over sub | ||
2045 | unhashed = sigover >>= unhashed_subpackets | ||
2046 | subsigs = mapMaybe backsig unhashed | ||
2047 | -- This should consist only of 0x19 values | ||
2048 | -- subtypes = map signature_type subsigs | ||
2049 | -- trace ("subtypes = "++show subtypes) (return ()) | ||
2050 | -- trace ("issuers: "++show (map signature_issuer subsigs)) (return ()) | ||
2051 | sig <- signatures (Message ([topkey sub,subkey sub]++subsigs)) | ||
2052 | let v = verify (Message [subkey sub]) sig | ||
2053 | guard (not . null $ signatures_over v) | ||
2054 | return v | ||
2055 | |||
2056 | flattenUid :: FilePath -> Bool -> (String,([SigAndTrust],OriginMap)) -> [MappedPacket] | ||
2057 | flattenUid fname ispub (str,(sigs,om)) = | ||
2058 | (mappedPacket "" $ UserIDPacket str) {locations=om} : concatSort fname head (unsig fname ispub) sigs | ||
2059 | |||
2060 | -- | Get the time stamp of a signature. | ||
2061 | -- | ||
2062 | -- Warning: This function checks unhashed_subpackets if no timestamp occurs in | ||
2063 | -- the hashed section. TODO: change this? | ||
2064 | -- | ||
2065 | signature_time :: SignatureOver -> Word32 | ||
2066 | signature_time ov = case (if null cs then ds else cs) of | ||
2067 | [] -> minBound | ||
2068 | xs -> maximum xs | ||
2069 | where | ||
2070 | ps = signatures_over ov | ||
2071 | ss = filter isSignaturePacket ps | ||
2072 | cs = concatMap (concatMap creationTime . hashed_subpackets) ss | ||
2073 | ds = concatMap (concatMap creationTime . unhashed_subpackets) ss | ||
2074 | creationTime (SignatureCreationTimePacket t) = [t] | ||
2075 | creationTime _ = [] | ||
2076 | |||
2077 | splitAtMinBy :: (t -> t -> Ordering) -> [t] -> ([t], [t]) | ||
2078 | splitAtMinBy comp xs = minimumBy comp' xxs | ||
2079 | where | ||
2080 | xxs = zip (inits xs) (tails xs) | ||
2081 | comp' (_,as) (_,bs) = compM (listToMaybe as) (listToMaybe bs) | ||
2082 | compM (Just a) (Just b) = comp a b | ||
2083 | compM Nothing mb = GT | ||
2084 | compM _ _ = LT | ||
2085 | |||
2086 | parseCertBlob :: Bool -> ByteString -> Maybe ParsedCert | 1295 | parseCertBlob :: Bool -> ByteString -> Maybe ParsedCert |
2087 | parseCertBlob comp bs = do | 1296 | parseCertBlob comp bs = do |
2088 | asn1 <- either (const Nothing) Just | 1297 | asn1 <- either (const Nothing) Just |
@@ -2171,15 +1380,18 @@ extractRSAKeyFields kvs = do | |||
2171 | , rsaCoefficient = u } | 1380 | , rsaCoefficient = u } |
2172 | where | 1381 | where |
2173 | parseField blob = MPI <$> m | 1382 | parseField blob = MPI <$> m |
1383 | #if defined(VERSION_memory) | ||
1384 | where m = bigendian <$> functorToMaybe (convertFromBase Base64 $ Char8.toStrict blob) | ||
1385 | bigendian bs = snd $ S.foldl' (\(c,a) w8 -> (c-1, a + 256^c * fromIntegral w8)) (nlen-1,0) bs | ||
1386 | where | ||
1387 | nlen = S.length bs | ||
1388 | #elif defined(VERSION_dataenc) | ||
2174 | where m = bigendian <$> Base64.decode (Char8.unpack blob) | 1389 | where m = bigendian <$> Base64.decode (Char8.unpack blob) |
2175 | |||
2176 | bigendian bs = snd $ foldl' (\(c,a) w8 -> (c-1, a + 256^c * fromIntegral w8)) (nlen-1,0) bs | 1390 | bigendian bs = snd $ foldl' (\(c,a) w8 -> (c-1, a + 256^c * fromIntegral w8)) (nlen-1,0) bs |
2177 | where | 1391 | where |
2178 | nlen = length bs | 1392 | nlen = length bs |
1393 | #endif | ||
2179 | 1394 | ||
2180 | backsig :: SignatureSubpacket -> Maybe Packet | ||
2181 | backsig (EmbeddedSignaturePacket s) = Just s | ||
2182 | backsig _ = Nothing | ||
2183 | 1395 | ||
2184 | selectKey0 :: Bool -> (KeySpec,Maybe String) -> KeyDB -> Maybe Packet | 1396 | selectKey0 :: Bool -> (KeySpec,Maybe String) -> KeyDB -> Maybe Packet |
2185 | selectKey0 wantPublic (spec,mtag) db = do | 1397 | selectKey0 wantPublic (spec,mtag) db = do |
@@ -2190,27 +1402,7 @@ selectKey0 wantPublic (spec,mtag) db = do | |||
2190 | y:ys1 -> listToMaybe $ snd $ seek_key (KeyTag y tag) ys1 | 1402 | y:ys1 -> listToMaybe $ snd $ seek_key (KeyTag y tag) ys1 |
2191 | [] -> Nothing | 1403 | [] -> Nothing |
2192 | 1404 | ||
2193 | sortByHint :: FilePath -> (a -> MappedPacket) -> [a] -> [a] | 1405 | -- TODO: Data.ByteString.Lazy now exports this. |
2194 | sortByHint fname f = sortBy (comparing gethint) | ||
2195 | where | ||
2196 | gethint = maybe defnum originalNum . Map.lookup fname . locations . f | ||
2197 | defnum = -1 | ||
2198 | |||
2199 | unsig :: FilePath -> Bool -> SigAndTrust -> [MappedPacket] | ||
2200 | unsig fname isPublic (sig,trustmap) = | ||
2201 | sig : map (asMapped (-1)) ( take 1 . Map.elems $ Map.filterWithKey f trustmap) | ||
2202 | where | ||
2203 | f n _ = n==fname -- && trace ("fname=n="++show n) True | ||
2204 | asMapped n p = let m = mappedPacket fname p | ||
2205 | in m { locations = fmap (\x->x {originalNum=n}) (locations m) } | ||
2206 | |||
2207 | smallpr :: Packet -> [Char] | ||
2208 | smallpr k = drop 24 $ fingerprint k | ||
2209 | |||
2210 | isSubkeySignature :: SignatureOver -> Bool | ||
2211 | isSubkeySignature (SubkeySignature {}) = True | ||
2212 | isSubkeySignature _ = False | ||
2213 | |||
2214 | toStrict :: L.ByteString -> S.ByteString | 1406 | toStrict :: L.ByteString -> S.ByteString |
2215 | toStrict = foldr1 (<>) . L.toChunks | 1407 | toStrict = foldr1 (<>) . L.toChunks |
2216 | 1408 | ||
diff --git a/lib/KeyRing/Types.hs b/lib/KeyRing/Types.hs new file mode 100644 index 0000000..2383140 --- /dev/null +++ b/lib/KeyRing/Types.hs | |||
@@ -0,0 +1,394 @@ | |||
1 | {-# LANGUAGE DeriveFunctor #-} | ||
2 | module KeyRing.Types where | ||
3 | |||
4 | import Data.Char (isLower,toLower) | ||
5 | import Data.List (groupBy) | ||
6 | import Data.Map as Map (Map) | ||
7 | import qualified Data.Map as Map | ||
8 | import Data.OpenPGP | ||
9 | import Data.OpenPGP.Util | ||
10 | import Data.Time.Clock | ||
11 | import FunctorToMaybe | ||
12 | import qualified Data.ByteString.Lazy as L | ||
13 | import qualified System.Posix.Types as Posix | ||
14 | |||
15 | -- | This type describes an idempotent transformation (merge or import) on a | ||
16 | -- set of GnuPG keyrings and other key files. | ||
17 | data KeyRingOperation = KeyRingOperation | ||
18 | { opFiles :: Map InputFile StreamInfo | ||
19 | -- ^ Indicates files to be read or updated. | ||
20 | , opPassphrases :: [PassphraseSpec] | ||
21 | -- ^ Indicates files or file descriptors where passphrases can be found. | ||
22 | , opTransforms :: [Transform] | ||
23 | -- ^ Transformations to be performed on the key pool after all files have | ||
24 | -- been read and before any have been written. | ||
25 | , opHome :: Maybe FilePath | ||
26 | -- ^ If provided, this is the directory where the 'HomeSec' and 'HomePub' | ||
27 | -- files reside. Otherwise, the evironment variable $GNUPGHOME is consulted | ||
28 | -- and if that is not set, it falls back to $HOME/.gnupg. | ||
29 | } | ||
30 | deriving (Eq,Show) | ||
31 | |||
32 | data InputFile = HomeSec | ||
33 | -- ^ A file named secring.gpg located in the home directory. | ||
34 | -- See 'opHome'. | ||
35 | | HomePub | ||
36 | -- ^ A file named pubring.gpg located in the home directory. | ||
37 | -- See 'opHome'. | ||
38 | | ArgFile FilePath | ||
39 | -- ^ Contents will be read or written from the specified path. | ||
40 | | FileDesc Posix.Fd | ||
41 | -- ^ Contents will be read or written from the specified file | ||
42 | -- descriptor. | ||
43 | | Pipe Posix.Fd Posix.Fd | ||
44 | -- ^ Contents will be read from the first descriptor and updated | ||
45 | -- content will be writen to the second. Note: Don't use Pipe | ||
46 | -- for 'Wallet' files. (TODO: Wallet support) | ||
47 | | Generate Int GenerateKeyParams | ||
48 | -- ^ New key packets will be generated if there is no | ||
49 | -- matching content already in the key pool. The integer is | ||
50 | -- a unique id number so that multiple generations can be | ||
51 | -- inserted into 'opFiles' | ||
52 | deriving (Eq,Ord,Show) | ||
53 | |||
54 | -- | This type describes how 'runKeyRing' will treat a file. | ||
55 | data StreamInfo = StreamInfo | ||
56 | { access :: Access | ||
57 | -- ^ Indicates whether the file is allowed to contain secret information. | ||
58 | , typ :: FileType | ||
59 | -- ^ Indicates the format and content type of the file. | ||
60 | , fill :: KeyFilter | ||
61 | -- ^ This filter controls what packets will be inserted into a file. | ||
62 | , spill :: KeyFilter | ||
63 | -- | ||
64 | -- ^ Use this to indicate whether or not a file's contents should be | ||
65 | -- available for updating other files. Note that although its type is | ||
66 | -- 'KeyFilter', it is usually interpretted as a boolean flag. Details | ||
67 | -- depend on 'typ' and are as follows: | ||
68 | -- | ||
69 | -- 'KeyRingFile': | ||
70 | -- | ||
71 | -- * 'KF_None' - The file's contents will not be shared. | ||
72 | -- | ||
73 | -- * otherwise - The file's contents will be shared. | ||
74 | -- | ||
75 | -- 'PEMFile': | ||
76 | -- | ||
77 | -- * 'KF_None' - The file's contents will not be shared. | ||
78 | -- | ||
79 | -- * 'KF_Match' - The file's key will be shared with the specified owner | ||
80 | -- key and usage tag. If 'fill' is also a 'KF_Match', then it must be | ||
81 | -- equal to this value; changing the usage or owner of a key is not | ||
82 | -- supported via the fill/spill mechanism. | ||
83 | -- | ||
84 | -- * otherwise - Unspecified. Do not use. | ||
85 | -- | ||
86 | -- 'WalletFile': | ||
87 | -- | ||
88 | -- * The 'spill' setting is ignored and the file's contents are shared. | ||
89 | -- (TODO) | ||
90 | -- | ||
91 | -- 'Hosts': | ||
92 | -- | ||
93 | -- * The 'spill' setting is ignored and the file's contents are shared. | ||
94 | -- (TODO) | ||
95 | -- | ||
96 | , initializer :: Initializer | ||
97 | -- ^ If 'typ' is 'PEMFile' and an 'External' 'initializer' string is set, | ||
98 | -- then it is interpretted as a shell command that may be used to create | ||
99 | -- the key if it does not exist. | ||
100 | , transforms :: [Transform] | ||
101 | -- ^ Per-file transformations that occur before the contents of a file are | ||
102 | -- spilled into the common pool. | ||
103 | } | ||
104 | deriving (Eq,Show) | ||
105 | |||
106 | |||
107 | -- | This type is used to indicate where to obtain passphrases. | ||
108 | data PassphraseSpec = PassphraseSpec | ||
109 | { passSpecRingFile :: Maybe FilePath | ||
110 | -- ^ If not Nothing, the passphrase is to be used for packets | ||
111 | -- from this file. | ||
112 | , passSpecKeySpec :: Maybe String | ||
113 | -- ^ Non-Nothing value reserved for future use. | ||
114 | -- (TODO: Use this to implement per-key passphrase associations). | ||
115 | , passSpecPassFile :: InputFile | ||
116 | -- ^ The passphrase will be read from this file or file descriptor. | ||
117 | } | ||
118 | -- | Use this to carry pasphrases from a previous run. | ||
119 | | PassphraseMemoizer { passSpecTranscoder :: PacketTranscoder } | ||
120 | | PassphraseAgent | ||
121 | |||
122 | instance Show PassphraseSpec where | ||
123 | show (PassphraseSpec a b c) = "PassphraseSpec "++show (a,b,c) | ||
124 | show (PassphraseMemoizer _) = "PassphraseMemoizer" | ||
125 | instance Eq PassphraseSpec where | ||
126 | PassphraseSpec a b c == PassphraseSpec d e f | ||
127 | = and [a==d,b==e,c==f] | ||
128 | _ == _ | ||
129 | = False | ||
130 | |||
131 | -- Ord instance for PassphraseSpec generally orders by generality with the most | ||
132 | -- general being greatest and the least general being least. The one exception | ||
133 | -- is the 'PassphraseMemoizer' which is considered least of all even though it | ||
134 | -- is very general. This is so an existing memoizer will be tried first, and | ||
135 | -- if there is none, one will be created that tries the others in order of | ||
136 | -- increasing generality. Key-specialization is considered less general than | ||
137 | -- file-specialization. | ||
138 | instance Ord PassphraseSpec where | ||
139 | compare (PassphraseMemoizer _) (PassphraseMemoizer _) = EQ | ||
140 | compare PassphraseAgent PassphraseAgent = EQ | ||
141 | compare (PassphraseMemoizer _) _ = LT | ||
142 | compare (PassphraseSpec a b c) (PassphraseSpec d e f) | ||
143 | | fmap (const ()) a == fmap (const ()) d | ||
144 | && fmap (const ()) b == fmap (const ()) e = compare (a,b,c) (d,e,f) | ||
145 | compare (PassphraseSpec (Just _) (Just _) _) _ = LT | ||
146 | compare (PassphraseSpec Nothing (Just _) _) _ = LT | ||
147 | compare (PassphraseSpec (Just _) _ _) _ = LT | ||
148 | compare PassphraseAgent _ = GT | ||
149 | |||
150 | data Transform = | ||
151 | Autosign | ||
152 | -- ^ This operation will make signatures for any tor-style UID | ||
153 | -- that matches a tor subkey and thus can be authenticated without | ||
154 | -- requring the judgement of a human user. | ||
155 | -- | ||
156 | -- A tor-style UID is one of the following form: | ||
157 | -- | ||
158 | -- > Anonymous <root@HOSTNAME.onion> | ||
159 | | DeleteSubkeyByFingerprint String | ||
160 | -- ^ Delete the subkey specified by the given fingerprint and any | ||
161 | -- associated signatures on that key. | ||
162 | | DeleteSubkeyByUsage String | ||
163 | -- ^ Delete the subkey specified by the given usage tag and any | ||
164 | -- associated signatures on that key. | ||
165 | | RenameSubkeys String String | ||
166 | -- ^ Replace all subkey signatures matching the first usage tag with | ||
167 | -- fresh signatures that match the second usage tag. | ||
168 | deriving (Eq,Ord,Show) | ||
169 | |||
170 | -- | Use this type to indicate whether a file of type 'KeyRingFile' is expected | ||
171 | -- to contain secret or public PGP key packets. Note that it is not supported | ||
172 | -- to mix both in the same file and that the secret key packets include all of | ||
173 | -- the information contained in their corresponding public key packets. | ||
174 | data Access = AutoAccess -- ^ secret or public as appropriate based on existing content. | ||
175 | -- (see 'rtRingAccess') | ||
176 | | Sec -- ^ secret information | ||
177 | | Pub -- ^ public information | ||
178 | deriving (Eq,Ord,Show) | ||
179 | |||
180 | data FileType = KeyRingFile | ||
181 | | PEMFile | ||
182 | | WalletFile | ||
183 | | DNSPresentation | ||
184 | | Hosts | ||
185 | | SshFile | ||
186 | deriving (Eq,Ord,Enum,Show) | ||
187 | |||
188 | -- type UsageTag = String | ||
189 | data Initializer = NoCreate | Internal GenerateKeyParams | External String | WarnMissing String | ||
190 | deriving (Eq,Ord,Show) | ||
191 | |||
192 | |||
193 | |||
194 | type PacketTranscoder = (SymmetricAlgorithm,S2K) -> MappedPacket -> IO (KikiCondition Packet) | ||
195 | type PacketDecrypter = MappedPacket -> IO (KikiCondition Packet) | ||
196 | |||
197 | -- | Note that the documentation here is intended for when this value is | ||
198 | -- assigned to 'fill'. For other usage, see 'spill'. | ||
199 | data KeyFilter = KF_None -- ^ No keys will be imported. | ||
200 | | KF_Match String -- ^ Only the key that matches the spec will be imported. | ||
201 | | KF_Subkeys -- ^ Subkeys will be imported if their owner key is | ||
202 | -- already in the ring. TODO: Even if their signatures | ||
203 | -- are bad? | ||
204 | | KF_Authentic -- ^ Keys are imported if they belong to an authenticated | ||
205 | -- identity (signed or self-authenticating). | ||
206 | | KF_All -- ^ All keys will be imported. | ||
207 | deriving (Eq,Ord,Show) | ||
208 | |||
209 | -- | The position and acces a packet had before the operation | ||
210 | data OriginFlags = OriginFlags | ||
211 | { originallyPublic :: Bool | ||
212 | -- ^ false if SecretKeyPacket | ||
213 | , originalNum :: Int | ||
214 | -- ^ packets are numbered, starting from 1.. | ||
215 | } deriving Show | ||
216 | |||
217 | type OriginMap = Map FilePath OriginFlags | ||
218 | |||
219 | type MappedPacket = OriginMapped Packet | ||
220 | data OriginMapped a = MappedPacket | ||
221 | { packet :: a | ||
222 | , locations :: OriginMap | ||
223 | } deriving Show | ||
224 | instance Functor OriginMapped where | ||
225 | fmap f (MappedPacket x ls) = MappedPacket (f x) ls | ||
226 | |||
227 | origin :: Packet -> Int -> OriginFlags | ||
228 | origin p n = OriginFlags ispub n | ||
229 | where | ||
230 | ispub = case p of | ||
231 | SecretKeyPacket {} -> False | ||
232 | _ -> True | ||
233 | |||
234 | mappedPacket :: FilePath -> Packet -> MappedPacket | ||
235 | mappedPacket filename p = MappedPacket | ||
236 | { packet = p | ||
237 | , locations = Map.singleton filename (origin p (-1)) | ||
238 | } | ||
239 | |||
240 | mappedPacketWithHint :: FilePath -> Packet -> Int -> MappedPacket | ||
241 | mappedPacketWithHint filename p hint = MappedPacket | ||
242 | { packet = p | ||
243 | , locations = Map.singleton filename (origin p hint) | ||
244 | } | ||
245 | |||
246 | |||
247 | -- | This type is used to indicate success or failure | ||
248 | -- and in the case of success, return the computed object. | ||
249 | -- The 'FunctorToMaybe' class is implemented to facilitate | ||
250 | -- branching on failture. | ||
251 | data KikiCondition a = KikiSuccess a | ||
252 | | FailedToLock [FilePath] | ||
253 | | BadPassphrase | ||
254 | | FailedToMakeSignature | ||
255 | | CantFindHome | ||
256 | | AmbiguousKeySpec FilePath | ||
257 | | CannotImportMasterKey | ||
258 | | NoWorkingKey | ||
259 | | AgentConnectionFailure | ||
260 | | OperationCanceled | ||
261 | deriving ( Functor, Show ) | ||
262 | |||
263 | instance FunctorToMaybe KikiCondition where | ||
264 | functorToMaybe (KikiSuccess a) = Just a | ||
265 | functorToMaybe _ = Nothing | ||
266 | |||
267 | instance Applicative KikiCondition where | ||
268 | pure a = KikiSuccess a | ||
269 | f <*> a = | ||
270 | case functorToEither f of | ||
271 | Right f -> case functorToEither a of | ||
272 | Right a -> pure (f a) | ||
273 | Left err -> err | ||
274 | Left err -> err | ||
275 | |||
276 | uncamel :: String -> String | ||
277 | uncamel str = unwords $ firstWord ++ (toLower .: otherWords) ++ args | ||
278 | where | ||
279 | (.:) = fmap . fmap | ||
280 | ( firstWord , | ||
281 | otherWords ) = splitAt 1 ws | ||
282 | ws = camel >>= groupBy (\_ c -> isLower c) | ||
283 | ( camel, args) = splitAt 1 $ words str | ||
284 | |||
285 | errorString :: KikiCondition a -> String | ||
286 | errorString (KikiSuccess {}) = "success" | ||
287 | errorString e = uncamel . show $ fmap (const ()) e | ||
288 | |||
289 | |||
290 | |||
291 | data InputFileContext = InputFileContext | ||
292 | { homesecPath :: FilePath | ||
293 | , homepubPath :: FilePath | ||
294 | } | ||
295 | |||
296 | |||
297 | -- | The 'KeyKey'-type is used to store the information of a key | ||
298 | -- which is used for finger-printing and as a lookup key into | ||
299 | -- maps. This type may be changed to an actual fingerprint in | ||
300 | -- in the future. | ||
301 | type KeyKey = [L.ByteString] | ||
302 | |||
303 | keykey :: Packet -> KeyKey | ||
304 | keykey key = | ||
305 | -- Note: The key's timestamp is normally included in it's fingerprint. | ||
306 | -- This is undesirable for kiki because it causes the same | ||
307 | -- key to be imported multiple times and show as apparently | ||
308 | -- distinct keys with different fingerprints. | ||
309 | -- Thus, we will remove the timestamp. | ||
310 | fingerprint_material (key {timestamp=0}) -- TODO: smaller key? | ||
311 | |||
312 | isKey :: Packet -> Bool | ||
313 | isKey (PublicKeyPacket {}) = True | ||
314 | isKey (SecretKeyPacket {}) = True | ||
315 | isKey _ = False | ||
316 | |||
317 | isSecretKey :: Packet -> Bool | ||
318 | isSecretKey (SecretKeyPacket {}) = True | ||
319 | isSecretKey _ = False | ||
320 | |||
321 | |||
322 | isUserID :: Packet -> Bool | ||
323 | isUserID (UserIDPacket {}) = True | ||
324 | isUserID _ = False | ||
325 | |||
326 | isTrust :: Packet -> Bool | ||
327 | isTrust (TrustPacket {}) = True | ||
328 | isTrust _ = False | ||
329 | |||
330 | -- matchpr computes the fingerprint of the given key truncated to | ||
331 | -- be the same lenght as the given fingerprint for comparison. | ||
332 | -- | ||
333 | -- matchpr fp = Data.List.Extra.takeEnd (length fp) | ||
334 | -- | ||
335 | matchpr :: String -> Packet -> String | ||
336 | matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp | ||
337 | |||
338 | |||
339 | |||
340 | |||
341 | data KeySpec = | ||
342 | KeyGrip String -- fp: | ||
343 | | KeyTag Packet String -- fp:????/t: | ||
344 | | KeyUidMatch String -- u: | ||
345 | deriving Show | ||
346 | |||
347 | {- | ||
348 | RSAPrivateKey ::= SEQUENCE { | ||
349 | version Version, | ||
350 | modulus INTEGER, -- n | ||
351 | publicExponent INTEGER, -- e | ||
352 | privateExponent INTEGER, -- d | ||
353 | prime1 INTEGER, -- p | ||
354 | prime2 INTEGER, -- q | ||
355 | exponent1 INTEGER, -- d mod (p1) -- ?? d mod (p-1) | ||
356 | exponent2 INTEGER, -- d mod (q-1) | ||
357 | coefficient INTEGER, -- (inverse of q) mod p | ||
358 | otherPrimeInfos OtherPrimeInfos OPTIONAL | ||
359 | } | ||
360 | -} | ||
361 | data RSAPrivateKey = RSAPrivateKey | ||
362 | { rsaN :: MPI | ||
363 | , rsaE :: MPI | ||
364 | , rsaD :: MPI | ||
365 | , rsaP :: MPI | ||
366 | , rsaQ :: MPI | ||
367 | , rsaDmodP1 :: MPI | ||
368 | , rsaDmodQminus1 :: MPI | ||
369 | , rsaCoefficient :: MPI | ||
370 | } | ||
371 | deriving Show | ||
372 | |||
373 | data ParsedCert = ParsedCert | ||
374 | { pcertKey :: Packet | ||
375 | , pcertTimestamp :: UTCTime | ||
376 | , pcertBlob :: L.ByteString | ||
377 | } | ||
378 | deriving (Show,Eq) | ||
379 | |||
380 | data SubkeyStatus = Unsigned | OwnerSigned | CrossSigned | ||
381 | deriving (Eq,Ord,Enum,Show,Read) | ||
382 | |||
383 | data SecretPEMData = PEMPacket Packet | PEMCertificate ParsedCert | ||
384 | deriving (Show,Eq) | ||
385 | |||
386 | data MatchingField = KeyTypeField | UserIDField | GroupIDField deriving (Show,Eq,Ord,Enum) | ||
387 | |||
388 | data SingleKeySpec = FingerprintMatch String | ||
389 | | SubstringMatch (Maybe MatchingField) String | ||
390 | | EmptyMatch | ||
391 | | AnyMatch | ||
392 | | WorkingKeyMatch | ||
393 | deriving (Show,Eq,Ord) | ||
394 | |||