diff options
author | joe <joe@jerkface.net> | 2016-08-28 16:10:41 -0400 |
---|---|---|
committer | joe <joe@jerkface.net> | 2016-08-28 16:10:41 -0400 |
commit | 7a579e7b82a2f5707af77f4a7101ce72e57635ac (patch) | |
tree | a5ff6eac4888a5577a6ba89a76dde939af4bc038 /lib | |
parent | 83e97b86973fc63eda92f5b38c112f0d374503c0 (diff) |
Refactored for smaller modules (faster rebuild).
Diffstat (limited to 'lib')
-rw-r--r-- | lib/GnuPGAgent.hs | 4 | ||||
-rw-r--r-- | lib/KeyRing.hs | 428 | ||||
-rw-r--r-- | lib/PacketTranscoder.hs | 204 | ||||
-rw-r--r-- | lib/Types.hs | 263 |
4 files changed, 469 insertions, 430 deletions
diff --git a/lib/GnuPGAgent.hs b/lib/GnuPGAgent.hs index 165fdf2..4a0e8c8 100644 --- a/lib/GnuPGAgent.hs +++ b/lib/GnuPGAgent.hs | |||
@@ -12,7 +12,6 @@ module GnuPGAgent | |||
12 | import Debug.Trace | 12 | import Debug.Trace |
13 | import Control.Monad | 13 | import Control.Monad |
14 | import Data.Char | 14 | import Data.Char |
15 | import Data.String | ||
16 | import Data.OpenPGP | 15 | import Data.OpenPGP |
17 | import Data.OpenPGP.Util | 16 | import Data.OpenPGP.Util |
18 | import Network.Socket | 17 | import Network.Socket |
@@ -195,6 +194,3 @@ mpi_nbits (MPI n) = 8 * fromIntegral len | |||
195 | b | L.head b == 0x0 -> L.length b - 1 | 194 | b | L.head b == 0x0 -> L.length b - 1 |
196 | | otherwise -> L.length b | 195 | | otherwise -> L.length b |
197 | 196 | ||
198 | |||
199 | testkey = SecretKeyPacket {version = 4, timestamp = 1472243938, key_algorithm = RSA, key = [('n',MPI 925098108806375345974135594703587915335809897839280536758475464131261107271275275932941944111979646806461228101207851537942631933552941000008763874178530420365203962506983368285394789190952706134189777248228503641959576566803847321978843353927484729746589488105067415601601958095348374608399772919464713280387221943804165023869848572344992805664813501588760551986737643636299927619834836785540438897016892383261773529795165480003163651143689566476205133258213375814625458146741502313336447508506512546267421431425245630464604235460425475063276208764001900603879017446724640013942643622160007288636580727874256816955228499258020260878806702335205106422310450767943433083341074984990460601274996333576709631004285781450883843918772938883789506765607663117687871326332910317916884385080960167806232865135145253097892026144191502423556603525411279749089026836608340578157620006555362884552555447347323681257897414720771902270571787966952008017289476385955943926940452534284336204814865498532173422146165623516746915729611768809058047983375615970447956865689598628436093143714990376442967204932522864539829901037938858768502028897029767875742018399924904388125541551233394021154526824492768592689377932549076041702724833113848612007956279),('e',MPI 65537)], s2k_useage = 255, s2k = IteratedSaltedS2K SHA1 4073382889203176146 7864320, symmetric_algorithm = CAST5, encrypted_data = fromString "\NUL\ESC\145\219\220k\SOH\147}\236K\165\207\&9g\245Nl\\)l\193\224{\251\180T\240\150\184\164:Fbx\t\SI\143\213\202?\137\158zd\247\188W\227l\NUL\154s\173\NUL\"k\162\243\rh\233\215\181\207\&7\223\DC3^t\187\158\248\177\ENQ\225\\\186\168\EM\177\211\162U\132\229Nx\227\&8\SYN\234\136\229\142;\252\&90L;\161\181\SO\152\198\&4\153\SUBs\235\195\153N\196\194 [\244+\217\242l\217.\183\186\205(\186\NUL\164\143>\215\168\207}\191\172($\168\139)O\ESCq\GS\138\213\243\229<\187\252\153)\NUL\128\136\237\RS\ETX\216\185\176I\239\185\228\v(\251\&4\233\&3$\236\195\NAKr\234\190J\216y\DC1\av\159\164\CAN\EOT\167\202})\128\182j\195\145]\144\r\232(\215\187\&1K\245\170\218\144\179\205\SOH\180-\185\DC1\168l\195\149\196\191\&9\156\196E\253\159h\154II\180\f\r\211\242\167~\214\223\219S\194\239\192\250\211Z\162\NAK\183M\209\230\&1yd\145\SOH\249\129\ESC\147\EM\237y\vK12O\205\ESC\r\224J\188\189\231\132\153JT\151f8\209\220#~S\165Q\249\SOH\182)\182\244\222\198i\180\221\170Q\238X\206\218\222\164gy\239\&7\136\183P\204\&5\NAK&\ESCC\GS\192\202\SO\241x\145MM\207\229\135\151\189,t\231r\194\196\233[\225\136\234\164r\176NXY\157\&2?\129% g\200\222\150\209\DLEQ\144\FS\181\&4#\US\DC28\179\190\240y3gr\170&\194\CAN)3r\235\252\153\EM\211\a\195\251\187\236\&1\197:\192\158\US\US\163v\153\223\141\254\209\206n\178h\140\&1-\fM\b\SOH\207\155\USb.,\NAKw\247\US\225\b)\236\EM\ENQn}\SUB-\193\f\138F\255P\216\242\164\145\136\213\171\252\254t\178\v\207\187\211\229\161\133\238\146\162\166SrT\168\135\244d6]\151\a\153\156\232\207|\152\223\174\EMj\130\240\211\141\203\167Kl\163\179R\152\225\221m\224!\238\176\217\162\158 fv\149wX\226\132\137H\138\235\207vwN\DC1\DEL'T\171\219\222\n\220V@\249U\227\SUBr\223NE\158=c\189\ACK3H\220\174\&3\139\135\254\246\165\EOTT\248\RS\132\160\219\EMb\188\200\165\138\178\163\STX\170\161\248\217\&1\186\&2r~\243\143\145b\154(\138\161\179\217\ACK\176\243\163IC\176\189Q_\206w\188=\254\143=\175\188\ENQaP\197\SI*\151\242m\178\184\208\SYN@\128\143\DC3-J\163\164{\206<\SUBxG\SI\NUL\153%\187\142\&6\f\186O\142\128'\128\150{\165\156e\201\175\159\185\b\NAK\246M\182\&4\SOH\161\231UV\220\148\245$\173\247-C\212\179\190Z$\184\RSZ\130~\t\249\138r?\201\231\200\190m\128%c\204\ENQH3S\140\228\&8\243\NAK\DC4O\218\162\146R\221\134\217%%\164@#\139\a\STX\218Y\132h\ETX(=\245\135\239|rN\\2\250\\\FS\155:p\247\213\252D']*\137\220\128\232\ar\134\DC4\131\194\SUB\169\130\&6\SI\131\151J!\220\135V\210m!\EM\241\134\158v\200~\190;z\237\218\DC3\NULT\164\151\135|\185\EOT\161c\196QA\228.\ENQ\227d\220\128\238\191&Pw\f'\153\180\DC3\201\SI=0\218\130~\167\t5\172EBA\238D\219\208\168\b\252Y\236\220,\144\&1\239\177\n\DC4\DLE\238\v\ETB\168\246\185\212\239\231\212\212sl\254.\197\216\130g\163\&5\211*\150\243g\220\247\140M\190\172\216\250\248\130\207\&5f\223;=}qU?\\\237\243\ENQ\241[\198\248u\139\a\139\175\247\224\252_N\146G\201\NUL\170{\191\237\140\SYNH2\ESCg\RS\233\175}\189\136\250\240\129\US\187\193\194\189\SUBK\SO\209\177F\200\SOH\173\196kw_)_\227\162\186\DC2\132\181\b@\ACKGo\222f\251br\CAN3~\139\DC2U\bQ\241\CAN:\213\135s\138\GSPIk*\236\&2a;o\247\239\202\145\212(2\223\DEL\bz\157\242@\STX\180g\193\202\230\186\135\189\177l\163\216o\230\&6\DC2\198\164\182\&5\ETX%\228\"!\245\ENQ\180\234\ACK\US\174\249\SO\US\168\STX,\ETB\n\249/\177\179\247Fw$\DLEB\ACK\224\231\EOT\ETB\247\213\182v\180\FS\247\205\222+P&\228\213\216\138ez\189N9x\v\228\217\207L}\ETX&\133\206\vRSM)\SOH\217\253\RS\204\252\249p\v\ACKL!u\SI\\\ETXD\128\&9\152\fy\241\202\204\164\151p\142\147c\207)\130\179'm\211\128I\207\ENQ\r\bcMWt\222\156\&1\199\DLE\157\&0z[H\146\SOHg\238\234\185\181\141\172c\245[\NUL\197\205\ENQ\fM\177\230\253\209~^\213W1'\GS\142\249\SIZ\204\254\240\DC3\231=b!\225@\247x\135\135\226\251[\RS&;\135}\196t\SUBi\CAN\DC14]e\206-l\205\SI\253\222\139y\139V\242\150k\248\191\231\195\211W\226t\170\DLE\174\243\186\211\189\152D\216\235\163\220+\194\247!o^F\198\145M", is_subkey = False} | ||
200 | |||
diff --git a/lib/KeyRing.hs b/lib/KeyRing.hs index a055dad..313258d 100644 --- a/lib/KeyRing.hs +++ b/lib/KeyRing.hs | |||
@@ -211,6 +211,8 @@ import FunctorToMaybe | |||
211 | import DotLock | 211 | import DotLock |
212 | import ProcessUtils (systemEnv, ExitCode(ExitFailure, ExitSuccess) ) | 212 | import ProcessUtils (systemEnv, ExitCode(ExitFailure, ExitSuccess) ) |
213 | import GnuPGAgent as Agent | 213 | import GnuPGAgent as Agent |
214 | import Types | ||
215 | import PacketTranscoder | ||
214 | 216 | ||
215 | -- DER-encoded elliptic curve ids | 217 | -- DER-encoded elliptic curve ids |
216 | -- nistp256_id = 0x2a8648ce3d030107 | 218 | -- nistp256_id = 0x2a8648ce3d030107 |
@@ -247,114 +249,6 @@ home = HomeDir | |||
247 | , optfile_alts = ["keys.conf","gpg.conf-2","gpg.conf"] | 249 | , optfile_alts = ["keys.conf","gpg.conf-2","gpg.conf"] |
248 | } | 250 | } |
249 | 251 | ||
250 | data InputFile = HomeSec | ||
251 | -- ^ A file named secring.gpg located in the home directory. | ||
252 | -- See 'opHome'. | ||
253 | | HomePub | ||
254 | -- ^ A file named pubring.gpg located in the home directory. | ||
255 | -- See 'opHome'. | ||
256 | | ArgFile FilePath | ||
257 | -- ^ Contents will be read or written from the specified path. | ||
258 | | FileDesc Posix.Fd | ||
259 | -- ^ Contents will be read or written from the specified file | ||
260 | -- descriptor. | ||
261 | | Pipe Posix.Fd Posix.Fd | ||
262 | -- ^ Contents will be read from the first descriptor and updated | ||
263 | -- content will be writen to the second. Note: Don't use Pipe | ||
264 | -- for 'Wallet' files. (TODO: Wallet support) | ||
265 | | Generate Int GenerateKeyParams | ||
266 | -- ^ New key packets will be generated if there is no | ||
267 | -- matching content already in the key pool. The integer is | ||
268 | -- a unique id number so that multiple generations can be | ||
269 | -- inserted into 'opFiles' | ||
270 | deriving (Eq,Ord,Show) | ||
271 | |||
272 | -- type UsageTag = String | ||
273 | data Initializer = NoCreate | Internal GenerateKeyParams | External String | ||
274 | deriving (Eq,Ord,Show) | ||
275 | |||
276 | data FileType = KeyRingFile | ||
277 | | PEMFile | ||
278 | | WalletFile | ||
279 | | DNSPresentation | ||
280 | | Hosts | ||
281 | deriving (Eq,Ord,Enum,Show) | ||
282 | |||
283 | -- | Use this type to indicate whether a file of type 'KeyRingFile' is expected | ||
284 | -- to contain secret or public PGP key packets. Note that it is not supported | ||
285 | -- to mix both in the same file and that the secret key packets include all of | ||
286 | -- the information contained in their corresponding public key packets. | ||
287 | data Access = AutoAccess -- ^ secret or public as appropriate based on existing content. | ||
288 | -- (see 'rtRingAccess') | ||
289 | | Sec -- ^ secret information | ||
290 | | Pub -- ^ public information | ||
291 | deriving (Eq,Ord,Show) | ||
292 | |||
293 | -- | Note that the documentation here is intended for when this value is | ||
294 | -- assigned to 'fill'. For other usage, see 'spill'. | ||
295 | data KeyFilter = KF_None -- ^ No keys will be imported. | ||
296 | | KF_Match String -- ^ Only the key that matches the spec will be imported. | ||
297 | | KF_Subkeys -- ^ Subkeys will be imported if their owner key is | ||
298 | -- already in the ring. TODO: Even if their signatures | ||
299 | -- are bad? | ||
300 | | KF_Authentic -- ^ Keys are imported if they belong to an authenticated | ||
301 | -- identity (signed or self-authenticating). | ||
302 | | KF_All -- ^ All keys will be imported. | ||
303 | deriving (Eq,Ord,Show) | ||
304 | |||
305 | -- | This type describes how 'runKeyRing' will treat a file. | ||
306 | data StreamInfo = StreamInfo | ||
307 | { access :: Access | ||
308 | -- ^ Indicates whether the file is allowed to contain secret information. | ||
309 | , typ :: FileType | ||
310 | -- ^ Indicates the format and content type of the file. | ||
311 | , fill :: KeyFilter | ||
312 | -- ^ This filter controls what packets will be inserted into a file. | ||
313 | , spill :: KeyFilter | ||
314 | -- | ||
315 | -- ^ Use this to indicate whether or not a file's contents should be | ||
316 | -- available for updating other files. Note that although its type is | ||
317 | -- 'KeyFilter', it is usually interpretted as a boolean flag. Details | ||
318 | -- depend on 'typ' and are as follows: | ||
319 | -- | ||
320 | -- 'KeyRingFile': | ||
321 | -- | ||
322 | -- * 'KF_None' - The file's contents will not be shared. | ||
323 | -- | ||
324 | -- * otherwise - The file's contents will be shared. | ||
325 | -- | ||
326 | -- 'PEMFile': | ||
327 | -- | ||
328 | -- * 'KF_None' - The file's contents will not be shared. | ||
329 | -- | ||
330 | -- * 'KF_Match' - The file's key will be shared with the specified owner | ||
331 | -- key and usage tag. If 'fill' is also a 'KF_Match', then it must be | ||
332 | -- equal to this value; changing the usage or owner of a key is not | ||
333 | -- supported via the fill/spill mechanism. | ||
334 | -- | ||
335 | -- * otherwise - Unspecified. Do not use. | ||
336 | -- | ||
337 | -- 'WalletFile': | ||
338 | -- | ||
339 | -- * The 'spill' setting is ignored and the file's contents are shared. | ||
340 | -- (TODO) | ||
341 | -- | ||
342 | -- 'Hosts': | ||
343 | -- | ||
344 | -- * The 'spill' setting is ignored and the file's contents are shared. | ||
345 | -- (TODO) | ||
346 | -- | ||
347 | , initializer :: Initializer | ||
348 | -- ^ If 'typ' is 'PEMFile' and an 'External' 'initializer' string is set, | ||
349 | -- then it is interpretted as a shell command that may be used to create | ||
350 | -- the key if it does not exist. | ||
351 | , transforms :: [Transform] | ||
352 | -- ^ Per-file transformations that occur before the contents of a file are | ||
353 | -- spilled into the common pool. | ||
354 | } | ||
355 | deriving (Eq,Show) | ||
356 | |||
357 | |||
358 | spillable :: StreamInfo -> Bool | 252 | spillable :: StreamInfo -> Bool |
359 | spillable (spill -> KF_None) = False | 253 | spillable (spill -> KF_None) = False |
360 | spillable _ = True | 254 | spillable _ = True |
@@ -387,10 +281,6 @@ usageFromFilter (KF_Match usage) = return usage | |||
387 | usageFromFilter _ = mzero | 281 | usageFromFilter _ = mzero |
388 | 282 | ||
389 | 283 | ||
390 | type PacketTranscoder = (SymmetricAlgorithm,S2K) -> MappedPacket -> IO (KikiCondition Packet) | ||
391 | |||
392 | type PacketDecrypter = MappedPacket -> IO (KikiCondition Packet) | ||
393 | |||
394 | data KeyRingRuntime = KeyRingRuntime | 284 | data KeyRingRuntime = KeyRingRuntime |
395 | { rtPubring :: FilePath | 285 | { rtPubring :: FilePath |
396 | -- ^ Path to the file represented by 'HomePub' | 286 | -- ^ Path to the file represented by 'HomePub' |
@@ -418,84 +308,6 @@ data KeyRingRuntime = KeyRingRuntime | |||
418 | data PacketUpdate = InducerSignature String [SignatureSubpacket] | 308 | data PacketUpdate = InducerSignature String [SignatureSubpacket] |
419 | | SubKeyDeletion KeyKey KeyKey | 309 | | SubKeyDeletion KeyKey KeyKey |
420 | 310 | ||
421 | -- | This type is used to indicate where to obtain passphrases. | ||
422 | data PassphraseSpec = PassphraseSpec | ||
423 | { passSpecRingFile :: Maybe FilePath | ||
424 | -- ^ If not Nothing, the passphrase is to be used for packets | ||
425 | -- from this file. | ||
426 | , passSpecKeySpec :: Maybe String | ||
427 | -- ^ Non-Nothing value reserved for future use. | ||
428 | -- (TODO: Use this to implement per-key passphrase associations). | ||
429 | , passSpecPassFile :: InputFile | ||
430 | -- ^ The passphrase will be read from this file or file descriptor. | ||
431 | } | ||
432 | -- | Use this to carry pasphrases from a previous run. | ||
433 | | PassphraseMemoizer PacketTranscoder | ||
434 | | PassphraseAgent | ||
435 | |||
436 | instance Show PassphraseSpec where | ||
437 | show (PassphraseSpec a b c) = "PassphraseSpec "++show (a,b,c) | ||
438 | show (PassphraseMemoizer _) = "PassphraseMemoizer" | ||
439 | instance Eq PassphraseSpec where | ||
440 | PassphraseSpec a b c == PassphraseSpec d e f | ||
441 | = and [a==d,b==e,c==f] | ||
442 | _ == _ | ||
443 | = False | ||
444 | |||
445 | |||
446 | |||
447 | data Transform = | ||
448 | Autosign | ||
449 | -- ^ This operation will make signatures for any tor-style UID | ||
450 | -- that matches a tor subkey and thus can be authenticated without | ||
451 | -- requring the judgement of a human user. | ||
452 | -- | ||
453 | -- A tor-style UID is one of the following form: | ||
454 | -- | ||
455 | -- > Anonymous <root@HOSTNAME.onion> | ||
456 | | DeleteSubkeyByFingerprint String | ||
457 | -- ^ Delete the subkey specified by the given fingerprint and any | ||
458 | -- associated signatures on that key. | ||
459 | | DeleteSubkeyByUsage String | ||
460 | -- ^ Delete the subkey specified by the given fingerprint and any | ||
461 | -- associated signatures on that key. | ||
462 | deriving (Eq,Ord,Show) | ||
463 | |||
464 | -- | This type describes an idempotent transformation (merge or import) on a | ||
465 | -- set of GnuPG keyrings and other key files. | ||
466 | data KeyRingOperation = KeyRingOperation | ||
467 | { opFiles :: Map.Map InputFile StreamInfo | ||
468 | -- ^ Indicates files to be read or updated. | ||
469 | , opPassphrases :: [PassphraseSpec] | ||
470 | -- ^ Indicates files or file descriptors where passphrases can be found. | ||
471 | , opTransforms :: [Transform] | ||
472 | -- ^ Transformations to be performed on the key pool after all files have | ||
473 | -- been read and before any have been written. | ||
474 | , opHome :: Maybe FilePath | ||
475 | -- ^ If provided, this is the directory where the 'HomeSec' and 'HomePub' | ||
476 | -- files reside. Otherwise, the evironment variable $GNUPGHOME is consulted | ||
477 | -- and if that is not set, it falls back to $HOME/.gnupg. | ||
478 | } | ||
479 | deriving (Eq,Show) | ||
480 | |||
481 | resolveInputFile :: InputFileContext -> InputFile -> [FilePath] | ||
482 | resolveInputFile ctx = resolve | ||
483 | where | ||
484 | resolve HomeSec = return (homesecPath ctx) | ||
485 | resolve HomePub = return (homepubPath ctx) | ||
486 | resolve (ArgFile f) = return f | ||
487 | resolve _ = [] | ||
488 | |||
489 | resolveForReport :: Maybe InputFileContext -> InputFile -> FilePath | ||
490 | resolveForReport mctx (Pipe fdr fdw) = resolveForReport mctx (ArgFile str) | ||
491 | where str = case (fdr,fdw) of | ||
492 | (0,1) -> "-" | ||
493 | _ -> "&pipe" ++ show (fdr,fdw) | ||
494 | resolveForReport mctx (FileDesc fd) = resolveForReport mctx (ArgFile str) | ||
495 | where str = "&" ++ show fd | ||
496 | resolveForReport mctx f = concat $ resolveInputFile ctx f | ||
497 | where ctx = fromMaybe (InputFileContext "&secret" "&public") mctx | ||
498 | |||
499 | filesToLock :: | 311 | filesToLock :: |
500 | KeyRingOperation -> InputFileContext -> [FilePath] | 312 | KeyRingOperation -> InputFileContext -> [FilePath] |
501 | filesToLock k ctx = do | 313 | filesToLock k ctx = do |
@@ -635,33 +447,6 @@ instance ASN1Object RSAPrivateKey where | |||
635 | 447 | ||
636 | 448 | ||
637 | 449 | ||
638 | -- | This type is used to indicate success or failure | ||
639 | -- and in the case of success, return the computed object. | ||
640 | -- The 'FunctorToMaybe' class is implemented to facilitate | ||
641 | -- branching on failture. | ||
642 | data KikiCondition a = KikiSuccess a | ||
643 | | FailedToLock [FilePath] | ||
644 | | BadPassphrase | ||
645 | | FailedToMakeSignature | ||
646 | | CantFindHome | ||
647 | | AmbiguousKeySpec FilePath | ||
648 | | CannotImportMasterKey | ||
649 | | NoWorkingKey | ||
650 | deriving ( Functor, Show ) | ||
651 | |||
652 | instance FunctorToMaybe KikiCondition where | ||
653 | functorToMaybe (KikiSuccess a) = Just a | ||
654 | functorToMaybe _ = Nothing | ||
655 | |||
656 | instance Applicative KikiCondition where | ||
657 | pure a = KikiSuccess a | ||
658 | f <*> a = | ||
659 | case functorToEither f of | ||
660 | Right f -> case functorToEither a of | ||
661 | Right a -> pure (f a) | ||
662 | Left err -> err | ||
663 | Left err -> err | ||
664 | |||
665 | -- | This type is used to describe events triggered by 'runKeyRing'. In | 450 | -- | This type is used to describe events triggered by 'runKeyRing'. In |
666 | -- addition to normal feedback (e.g. 'NewPacket'), it also may indicate | 451 | -- addition to normal feedback (e.g. 'NewPacket'), it also may indicate |
667 | -- non-fatal IO exceptions (e.g. 'FailedExternal'). Because a | 452 | -- non-fatal IO exceptions (e.g. 'FailedExternal'). Because a |
@@ -1349,18 +1134,6 @@ seek_key (KeyUidMatch pat) ps | |||
1349 | uidStr _ = "" | 1134 | uidStr _ = "" |
1350 | 1135 | ||
1351 | 1136 | ||
1352 | data InputFileContext = InputFileContext | ||
1353 | { homesecPath :: FilePath | ||
1354 | , homepubPath :: FilePath | ||
1355 | } | ||
1356 | |||
1357 | readInputFileS :: InputFileContext -> InputFile -> IO S.ByteString | ||
1358 | readInputFileS ctx (Pipe fd _) = fdToHandle fd >>= S.hGetContents | ||
1359 | readInputFileS ctx (FileDesc fd) = fdToHandle fd >>= S.hGetContents | ||
1360 | readInputFileS ctx inp = do | ||
1361 | let fname = resolveInputFile ctx inp | ||
1362 | fmap S.concat $ mapM S.readFile fname | ||
1363 | |||
1364 | readInputFileL :: InputFileContext -> InputFile -> IO L.ByteString | 1137 | readInputFileL :: InputFileContext -> InputFile -> IO L.ByteString |
1365 | readInputFileL ctx (Pipe fd _) = fdToHandle fd >>= L.hGetContents | 1138 | readInputFileL ctx (Pipe fd _) = fdToHandle fd >>= L.hGetContents |
1366 | readInputFileL ctx (FileDesc fd) = fdToHandle fd >>= L.hGetContents | 1139 | readInputFileL ctx (FileDesc fd) = fdToHandle fd >>= L.hGetContents |
@@ -1436,27 +1209,6 @@ doesInputFileExist ctx f = do | |||
1436 | -} | 1209 | -} |
1437 | 1210 | ||
1438 | 1211 | ||
1439 | -- | Reads contents of an 'InputFile' or returns the cached content from a prior call. | ||
1440 | -- An optional prompt is provided and will be printed on stdout only in the case that | ||
1441 | -- the provided 'InputFile' is 'FileDesc' 0 (i.e. stdin). | ||
1442 | cachedContents :: Maybe S.ByteString -> InputFileContext -> InputFile -> IO (IO S.ByteString) | ||
1443 | cachedContents maybePrompt ctx fd = do | ||
1444 | ref <- newIORef Nothing | ||
1445 | return $ get maybePrompt ref fd | ||
1446 | where | ||
1447 | trimCR bs = fst $ S.spanEnd (\x -> x==10 || x==13) bs | ||
1448 | |||
1449 | get maybePrompt ref fd = do | ||
1450 | pw <- readIORef ref | ||
1451 | flip (flip maybe return) pw $ do | ||
1452 | if fd == FileDesc 0 then case maybePrompt of | ||
1453 | Just prompt -> S.hPutStr stderr prompt | ||
1454 | Nothing -> return () | ||
1455 | else return () | ||
1456 | pw <- fmap trimCR $ readInputFileS ctx fd | ||
1457 | writeIORef ref (Just pw) | ||
1458 | return pw | ||
1459 | |||
1460 | generateSubkey :: | 1212 | generateSubkey :: |
1461 | PacketTranscoder | 1213 | PacketTranscoder |
1462 | -> KikiCondition (KeyData, [(FilePath, KikiReportAction)]) -- db | 1214 | -> KikiCondition (KeyData, [(FilePath, KikiReportAction)]) -- db |
@@ -2589,115 +2341,6 @@ writePEMKeys doDecrypt db exports = do | |||
2589 | try pun $ \pun -> do | 2341 | try pun $ \pun -> do |
2590 | return $ KikiSuccess (fname,stream,pun) | 2342 | return $ KikiSuccess (fname,stream,pun) |
2591 | 2343 | ||
2592 | makeMemoizingDecrypter :: KeyRingOperation -> InputFileContext | ||
2593 | -> Map.Map KeyKey (OriginMapped Query) | ||
2594 | -> IO PacketTranscoder | ||
2595 | makeMemoizingDecrypter operation ctx keys = do | ||
2596 | if null chains then do | ||
2597 | -- (*) Notice we do not pass ctx to resolveForReport. | ||
2598 | -- This is because the merge function does not currently use a context | ||
2599 | -- and the pws map keys must match the MappedPacket locations. | ||
2600 | -- TODO: Perhaps these should both be of type InputFile rather than | ||
2601 | -- FilePath? | ||
2602 | -- pws :: Map.Map FilePath (IO S.ByteString) | ||
2603 | {- | ||
2604 | -- This disabled code obtained password sources from StreamInfo records. | ||
2605 | pws <- | ||
2606 | Traversable.mapM (cachedContents ctx . fromJust . pwfile . typ) | ||
2607 | (Map.mapKeys (resolveForReport Nothing) -- see note (*) note above | ||
2608 | $ Map.filter (isJust . pwfile . typ) $ opFiles operation) | ||
2609 | -} | ||
2610 | let prompt = Just "Enter possibly multi-line passphrase (Press CTRL-D when finished):\n" | ||
2611 | -- List of file-specific password sources. | ||
2612 | pws2 <- | ||
2613 | Traversable.mapM (cachedContents prompt ctx) | ||
2614 | $ Map.fromList $ mapMaybe | ||
2615 | (\spec -> (,passSpecPassFile spec) `fmap` do | ||
2616 | guard $ isNothing $ passSpecKeySpec spec | ||
2617 | passSpecRingFile spec) | ||
2618 | passspecs | ||
2619 | -- List of general password sources. | ||
2620 | defpw <- do | ||
2621 | Traversable.mapM (cachedContents prompt ctx . passSpecPassFile) | ||
2622 | $ listToMaybe $ filter (\sp -> isNothing (passSpecRingFile sp) | ||
2623 | && isNothing (passSpecKeySpec sp)) | ||
2624 | $ passspecs | ||
2625 | unkeysRef <- newIORef (Map.empty :: Map.Map KeyKey Packet) | ||
2626 | return $ doDecrypt unkeysRef ({- pws `Map.union` -} pws2) defpw (not $ null agentspec) | ||
2627 | else let PassphraseMemoizer f = head chains | ||
2628 | in return f | ||
2629 | where | ||
2630 | (chains,passspecs0) = partition isChain $ opPassphrases operation | ||
2631 | where isChain (PassphraseMemoizer {}) = True | ||
2632 | isChain _ = False | ||
2633 | (agentspec,passspecs) = partition isAgent $ opPassphrases operation | ||
2634 | where isAgent PassphraseAgent = True | ||
2635 | isAgent _ = False | ||
2636 | doDecrypt :: IORef (Map.Map KeyKey Packet) | ||
2637 | -> Map.Map FilePath (IO S.ByteString) | ||
2638 | -> Maybe (IO S.ByteString) | ||
2639 | -> Bool | ||
2640 | -> (SymmetricAlgorithm,S2K) | ||
2641 | -> MappedPacket | ||
2642 | -> IO (KikiCondition Packet) | ||
2643 | doDecrypt unkeysRef pws defpw agent_requested (dest_alg,dest_s2k) mp0 = do | ||
2644 | unkeys <- readIORef unkeysRef | ||
2645 | let (mp,qry) = fromMaybe (mp0,Query (packet mp0) "anonymous2" Nothing) $ do | ||
2646 | k <- Map.lookup kk keys | ||
2647 | return (mergeKeyPacket "decrypt" mp0 (fmap queryPacket k), packet k) | ||
2648 | wk = packet mp0 | ||
2649 | kk = keykey wk | ||
2650 | fs = Map.keys $ locations mp | ||
2651 | |||
2652 | decryptIt [] = return BadPassphrase | ||
2653 | decryptIt (getpw:getpws) = tries 1 getpw (decryptIt getpws) | ||
2654 | where | ||
2655 | tries count getpw recurse = do | ||
2656 | -- TODO: This function should use mergeKeyPacket to | ||
2657 | -- combine the packet with it's unspilled version before | ||
2658 | -- attempting to decrypt it. Note: We are uninterested | ||
2659 | -- in the 'locations' field, so this would effectively | ||
2660 | -- allow you to run 'decryptIt' on an unencrypted public key | ||
2661 | -- to obtain it's secret key. | ||
2662 | (pw,wants_retry) <- getpw (if count>1 then AskAgain "Bad pasphrase." else Ask,qry) | ||
2663 | let wkun = fromMaybe wk $ do | ||
2664 | guard $ symmetric_algorithm (packet mp) /= Unencrypted | ||
2665 | decryptSecretKey pw (packet mp) | ||
2666 | |||
2667 | case symmetric_algorithm wkun of | ||
2668 | |||
2669 | Unencrypted -> do | ||
2670 | writeIORef unkeysRef (Map.insert kk wkun unkeys) | ||
2671 | ek <- if dest_alg==Unencrypted | ||
2672 | then return $ Just wkun | ||
2673 | else encryptSecretKey pw dest_s2k dest_alg wkun | ||
2674 | case ek of | ||
2675 | Nothing | wants_retry && count<3 -> tries (count+1) getpw recurse | ||
2676 | Nothing -> recurse | ||
2677 | Just wken -> return $ KikiSuccess wken | ||
2678 | |||
2679 | _ -> recurse | ||
2680 | |||
2681 | getpws = (map (const . fmap (,False)) $ mapMaybe (`Map.lookup` pws) fs ++ maybeToList defpw) ++ [ agentpw | agent_requested ] | ||
2682 | |||
2683 | agentpw (ask,qry) = do | ||
2684 | s <- session | ||
2685 | fromMaybe (return ("",False)) $ do | ||
2686 | s <- s | ||
2687 | Just $ do | ||
2688 | case ask of AskAgain _ -> clearPassphrase s (queryPacket qry) | ||
2689 | _ -> return () | ||
2690 | mbpw <- getPassphrase s ask qry | ||
2691 | quit s | ||
2692 | return ( maybe "" S8.pack mbpw, True) | ||
2693 | |||
2694 | if symmetric_algorithm wk == dest_alg | ||
2695 | && ( symmetric_algorithm wk == Unencrypted || s2k wk == dest_s2k ) | ||
2696 | then return (KikiSuccess wk) | ||
2697 | else maybe (decryptIt getpws) | ||
2698 | (return . KikiSuccess) | ||
2699 | $ Map.lookup kk unkeys | ||
2700 | |||
2701 | performManipulations :: | 2344 | performManipulations :: |
2702 | (PacketDecrypter) | 2345 | (PacketDecrypter) |
2703 | -> KeyRingRuntime | 2346 | -> KeyRingRuntime |
@@ -3162,19 +2805,6 @@ lookupEnv var = | |||
3162 | handleIO_ (return Nothing) $ fmap Just (getEnv var) | 2805 | handleIO_ (return Nothing) $ fmap Just (getEnv var) |
3163 | #endif | 2806 | #endif |
3164 | 2807 | ||
3165 | isKey :: Packet -> Bool | ||
3166 | isKey (PublicKeyPacket {}) = True | ||
3167 | isKey (SecretKeyPacket {}) = True | ||
3168 | isKey _ = False | ||
3169 | |||
3170 | isUserID :: Packet -> Bool | ||
3171 | isUserID (UserIDPacket {}) = True | ||
3172 | isUserID _ = False | ||
3173 | |||
3174 | isTrust :: Packet -> Bool | ||
3175 | isTrust (TrustPacket {}) = True | ||
3176 | isTrust _ = False | ||
3177 | |||
3178 | sigpackets :: | 2808 | sigpackets :: |
3179 | Monad m => | 2809 | Monad m => |
3180 | Word8 -> [SignatureSubpacket] -> [SignatureSubpacket] -> m Packet | 2810 | Word8 -> [SignatureSubpacket] -> [SignatureSubpacket] -> m Packet |
@@ -3497,32 +3127,10 @@ makeSig doDecrypt top fname subkey_p tags mbsig = do | |||
3497 | return $ fmap (,[]) newsig | 3127 | return $ fmap (,[]) newsig |
3498 | 3128 | ||
3499 | 3129 | ||
3500 | -- | The position and acces a packet had before the operation | ||
3501 | data OriginFlags = OriginFlags | ||
3502 | { originallyPublic :: Bool | ||
3503 | -- ^ false if SecretKeyPacket | ||
3504 | , originalNum :: Int | ||
3505 | -- ^ packets are numbered, starting from 1.. | ||
3506 | } deriving Show | ||
3507 | |||
3508 | type OriginMap = Map.Map FilePath OriginFlags | ||
3509 | |||
3510 | type MappedPacket = OriginMapped Packet | ||
3511 | data OriginMapped a = MappedPacket | ||
3512 | { packet :: a | ||
3513 | , locations :: OriginMap | ||
3514 | } deriving Show | ||
3515 | instance Functor OriginMapped where | ||
3516 | fmap f (MappedPacket x ls) = MappedPacket (f x) ls | ||
3517 | |||
3518 | type TrustMap = Map.Map FilePath Packet | 3130 | type TrustMap = Map.Map FilePath Packet |
3519 | type SigAndTrust = ( MappedPacket | 3131 | type SigAndTrust = ( MappedPacket |
3520 | , TrustMap ) -- trust packets | 3132 | , TrustMap ) -- trust packets |
3521 | 3133 | ||
3522 | -- | The 'KeyKey'-type is used to store the information of a key | ||
3523 | -- which is used for finger-printing | ||
3524 | type KeyKey = [ByteString] | ||
3525 | |||
3526 | data SubKey = SubKey MappedPacket [SigAndTrust] deriving Show | 3134 | data SubKey = SubKey MappedPacket [SigAndTrust] deriving Show |
3527 | 3135 | ||
3528 | -- | This is a GPG Identity which includes a master key and all its UIDs and | 3136 | -- | This is a GPG Identity which includes a master key and all its UIDs and |
@@ -3554,15 +3162,6 @@ mappedPacketWithHint filename p hint = MappedPacket | |||
3554 | , locations = Map.singleton filename (origin p hint) | 3162 | , locations = Map.singleton filename (origin p hint) |
3555 | } | 3163 | } |
3556 | 3164 | ||
3557 | keykey :: Packet -> KeyKey | ||
3558 | keykey key = | ||
3559 | -- Note: The key's timestamp is normally included in it's fingerprint. | ||
3560 | -- This is undesirable for kiki because it causes the same | ||
3561 | -- key to be imported multiple times and show as apparently | ||
3562 | -- distinct keys with different fingerprints. | ||
3563 | -- Thus, we will remove the timestamp. | ||
3564 | fingerprint_material (key {timestamp=0}) -- TODO: smaller key? | ||
3565 | |||
3566 | uidkey :: Packet -> String | 3165 | uidkey :: Packet -> String |
3567 | uidkey (UserIDPacket str) = str | 3166 | uidkey (UserIDPacket str) = str |
3568 | 3167 | ||
@@ -3598,29 +3197,6 @@ onionName kd = (addr,name) | |||
3598 | (addr,(name:_,_)) = getHostnames kd | 3197 | (addr,(name:_,_)) = getHostnames kd |
3599 | -} | 3198 | -} |
3600 | 3199 | ||
3601 | -- | Compare different versions if the same key pair. Public versions | ||
3602 | -- are considered greater. If the two packets do not represent the same | ||
3603 | -- key or the packets are not keys at all, an error will result that | ||
3604 | -- includes the context provided as the first argument. | ||
3605 | keyCompare :: String -> Packet -> Packet -> Ordering | ||
3606 | keyCompare what (SecretKeyPacket {}) (PublicKeyPacket {}) = LT | ||
3607 | keyCompare what (PublicKeyPacket {}) (SecretKeyPacket {}) = GT | ||
3608 | keyCompare what a b | keykey a==keykey b = EQ | ||
3609 | keyCompare what a b = error $ unlines ["Unable to merge "++what++":" | ||
3610 | , if isKey a then fingerprint a else "" | ||
3611 | , PP.ppShow a | ||
3612 | , if isKey b then fingerprint b else "" | ||
3613 | , PP.ppShow b | ||
3614 | ] | ||
3615 | |||
3616 | -- | Merge two representations of the same key, prefering secret version | ||
3617 | -- because they have more information. | ||
3618 | mergeKeyPacket :: String -> MappedPacket -> MappedPacket -> MappedPacket | ||
3619 | mergeKeyPacket what key p = | ||
3620 | key { packet = minimumBy (keyCompare what) [packet key,packet p] | ||
3621 | , locations = Map.union (locations key) (locations p) | ||
3622 | } | ||
3623 | |||
3624 | 3200 | ||
3625 | merge_ :: KeyDB -> FilePath -> [(Packet,Packet,(Packet,Map.Map FilePath Packet))] | 3201 | merge_ :: KeyDB -> FilePath -> [(Packet,Packet,(Packet,Map.Map FilePath Packet))] |
3626 | -> KeyDB | 3202 | -> KeyDB |
diff --git a/lib/PacketTranscoder.hs b/lib/PacketTranscoder.hs new file mode 100644 index 0000000..651b00c --- /dev/null +++ b/lib/PacketTranscoder.hs | |||
@@ -0,0 +1,204 @@ | |||
1 | {-# LANGUAGE TupleSections #-} | ||
2 | {-# LANGUAGE OverloadedStrings #-} | ||
3 | module PacketTranscoder where | ||
4 | |||
5 | import Control.Monad | ||
6 | import Data.IORef | ||
7 | import Data.List | ||
8 | import Data.Maybe | ||
9 | import Data.OpenPGP | ||
10 | import Data.OpenPGP.Util | ||
11 | import GnuPGAgent | ||
12 | import qualified Data.ByteString as S | ||
13 | import qualified Data.ByteString.Char8 as S8 | ||
14 | import qualified Data.Map as Map | ||
15 | import qualified Data.Traversable as Traversable | ||
16 | import System.IO ( stderr) | ||
17 | import System.Posix.IO ( fdToHandle ) | ||
18 | import Text.Show.Pretty as PP ( ppShow ) | ||
19 | import Types | ||
20 | |||
21 | -- | Merge two representations of the same key, prefering secret version | ||
22 | -- because they have more information. | ||
23 | mergeKeyPacket :: String -> MappedPacket -> MappedPacket -> MappedPacket | ||
24 | mergeKeyPacket what key p = | ||
25 | key { packet = minimumBy (keyCompare what) [packet key,packet p] | ||
26 | , locations = Map.union (locations key) (locations p) | ||
27 | } | ||
28 | |||
29 | -- | Compare different versions if the same key pair. Public versions | ||
30 | -- are considered greater. If the two packets do not represent the same | ||
31 | -- key or the packets are not keys at all, an error will result that | ||
32 | -- includes the context provided as the first argument. | ||
33 | keyCompare :: String -> Packet -> Packet -> Ordering | ||
34 | keyCompare what (SecretKeyPacket {}) (PublicKeyPacket {}) = LT | ||
35 | keyCompare what (PublicKeyPacket {}) (SecretKeyPacket {}) = GT | ||
36 | keyCompare what a b | keykey a==keykey b = EQ | ||
37 | keyCompare what a b = error $ unlines ["Unable to merge "++what++":" | ||
38 | , if isKey a then fingerprint a else "" | ||
39 | , PP.ppShow a | ||
40 | , if isKey b then fingerprint b else "" | ||
41 | , PP.ppShow b | ||
42 | ] | ||
43 | |||
44 | resolveInputFile :: InputFileContext -> InputFile -> [FilePath] | ||
45 | resolveInputFile ctx = resolve | ||
46 | where | ||
47 | resolve HomeSec = return (homesecPath ctx) | ||
48 | resolve HomePub = return (homepubPath ctx) | ||
49 | resolve (ArgFile f) = return f | ||
50 | resolve _ = [] | ||
51 | |||
52 | resolveForReport :: Maybe InputFileContext -> InputFile -> FilePath | ||
53 | resolveForReport mctx (Pipe fdr fdw) = resolveForReport mctx (ArgFile str) | ||
54 | where str = case (fdr,fdw) of | ||
55 | (0,1) -> "-" | ||
56 | _ -> "&pipe" ++ show (fdr,fdw) | ||
57 | resolveForReport mctx (FileDesc fd) = resolveForReport mctx (ArgFile str) | ||
58 | where str = "&" ++ show fd | ||
59 | resolveForReport mctx f = concat $ resolveInputFile ctx f | ||
60 | where ctx = fromMaybe (InputFileContext "&secret" "&public") mctx | ||
61 | |||
62 | readInputFileS :: InputFileContext -> InputFile -> IO S.ByteString | ||
63 | readInputFileS ctx (Pipe fd _) = fdToHandle fd >>= S.hGetContents | ||
64 | readInputFileS ctx (FileDesc fd) = fdToHandle fd >>= S.hGetContents | ||
65 | readInputFileS ctx inp = do | ||
66 | let fname = resolveInputFile ctx inp | ||
67 | fmap S.concat $ mapM S.readFile fname | ||
68 | |||
69 | |||
70 | |||
71 | -- | Reads contents of an 'InputFile' or returns the cached content from a prior call. | ||
72 | -- An optional prompt is provided and will be printed on stdout only in the case that | ||
73 | -- the provided 'InputFile' is 'FileDesc' 0 (i.e. stdin). | ||
74 | cachedContents :: Maybe S.ByteString -> InputFileContext -> InputFile -> IO (IO S.ByteString) | ||
75 | cachedContents maybePrompt ctx fd = do | ||
76 | ref <- newIORef Nothing | ||
77 | return $ get maybePrompt ref fd | ||
78 | where | ||
79 | trimCR bs = fst $ S.spanEnd (\x -> x==10 || x==13) bs | ||
80 | |||
81 | get maybePrompt ref fd = do | ||
82 | pw <- readIORef ref | ||
83 | flip (flip maybe return) pw $ do | ||
84 | if fd == FileDesc 0 then case maybePrompt of | ||
85 | Just prompt -> S.hPutStr stderr prompt | ||
86 | Nothing -> return () | ||
87 | else return () | ||
88 | pw <- fmap trimCR $ readInputFileS ctx fd | ||
89 | writeIORef ref (Just pw) | ||
90 | return pw | ||
91 | |||
92 | |||
93 | |||
94 | makeMemoizingDecrypter :: KeyRingOperation -> InputFileContext | ||
95 | -> Map.Map KeyKey (OriginMapped Query) | ||
96 | -> IO PacketTranscoder | ||
97 | makeMemoizingDecrypter operation ctx keys = do | ||
98 | if null chains then do | ||
99 | -- (*) Notice we do not pass ctx to resolveForReport. | ||
100 | -- This is because the merge function does not currently use a context | ||
101 | -- and the pws map keys must match the MappedPacket locations. | ||
102 | -- TODO: Perhaps these should both be of type InputFile rather than | ||
103 | -- FilePath? | ||
104 | -- pws :: Map.Map FilePath (IO S.ByteString) | ||
105 | {- | ||
106 | -- This disabled code obtained password sources from StreamInfo records. | ||
107 | pws <- | ||
108 | Traversable.mapM (cachedContents ctx . fromJust . pwfile . typ) | ||
109 | (Map.mapKeys (resolveForReport Nothing) -- see note (*) note above | ||
110 | $ Map.filter (isJust . pwfile . typ) $ opFiles operation) | ||
111 | -} | ||
112 | let prompt = Just "Enter possibly multi-line passphrase (Press CTRL-D when finished):\n" | ||
113 | -- List of file-specific password sources. | ||
114 | pws2 <- | ||
115 | Traversable.mapM (cachedContents prompt ctx) | ||
116 | $ Map.fromList $ mapMaybe | ||
117 | (\spec -> (,passSpecPassFile spec) `fmap` do | ||
118 | guard $ isNothing $ passSpecKeySpec spec | ||
119 | passSpecRingFile spec) | ||
120 | passspecs | ||
121 | -- List of general password sources. | ||
122 | defpw <- do | ||
123 | Traversable.mapM (cachedContents prompt ctx . passSpecPassFile) | ||
124 | $ listToMaybe $ filter (\sp -> isNothing (passSpecRingFile sp) | ||
125 | && isNothing (passSpecKeySpec sp)) | ||
126 | $ passspecs | ||
127 | unkeysRef <- newIORef (Map.empty :: Map.Map KeyKey Packet) | ||
128 | return $ doDecrypt unkeysRef ({- pws `Map.union` -} pws2) defpw (not $ null agentspec) | ||
129 | else let PassphraseMemoizer f = head chains | ||
130 | in return f | ||
131 | where | ||
132 | (chains,passspecs0) = partition isChain $ opPassphrases operation | ||
133 | where isChain (PassphraseMemoizer {}) = True | ||
134 | isChain _ = False | ||
135 | (agentspec,passspecs) = partition isAgent passspecs0 | ||
136 | where isAgent PassphraseAgent = True | ||
137 | isAgent _ = False | ||
138 | doDecrypt :: IORef (Map.Map KeyKey Packet) | ||
139 | -> Map.Map FilePath (IO S.ByteString) | ||
140 | -> Maybe (IO S.ByteString) | ||
141 | -> Bool | ||
142 | -> (SymmetricAlgorithm,S2K) | ||
143 | -> MappedPacket | ||
144 | -> IO (KikiCondition Packet) | ||
145 | doDecrypt unkeysRef pws defpw agent_requested (dest_alg,dest_s2k) mp0 = do | ||
146 | unkeys <- readIORef unkeysRef | ||
147 | let (mp,qry) = fromMaybe (mp0,Query (packet mp0) "anonymous2" Nothing) $ do | ||
148 | k <- Map.lookup kk keys | ||
149 | return (mergeKeyPacket "decrypt" mp0 (fmap queryPacket k), packet k) | ||
150 | wk = packet mp0 | ||
151 | kk = keykey wk | ||
152 | fs = Map.keys $ locations mp | ||
153 | |||
154 | decryptIt [] = return BadPassphrase | ||
155 | decryptIt (getpw:getpws) = tries 1 getpw (decryptIt getpws) | ||
156 | where | ||
157 | tries count getpw recurse = do | ||
158 | -- TODO: This function should use mergeKeyPacket to | ||
159 | -- combine the packet with it's unspilled version before | ||
160 | -- attempting to decrypt it. Note: We are uninterested | ||
161 | -- in the 'locations' field, so this would effectively | ||
162 | -- allow you to run 'decryptIt' on an unencrypted public key | ||
163 | -- to obtain it's secret key. | ||
164 | (pw,wants_retry) <- getpw (if count>1 then AskAgain "Bad pasphrase." else Ask,qry) | ||
165 | let wkun = fromMaybe wk $ do | ||
166 | guard $ symmetric_algorithm (packet mp) /= Unencrypted | ||
167 | decryptSecretKey pw (packet mp) | ||
168 | |||
169 | case symmetric_algorithm wkun of | ||
170 | |||
171 | Unencrypted -> do | ||
172 | writeIORef unkeysRef (Map.insert kk wkun unkeys) | ||
173 | ek <- if dest_alg==Unencrypted | ||
174 | then return $ Just wkun | ||
175 | else encryptSecretKey pw dest_s2k dest_alg wkun | ||
176 | case ek of | ||
177 | Nothing | wants_retry && count<3 -> tries (count+1) getpw recurse | ||
178 | Nothing -> recurse | ||
179 | Just wken -> return $ KikiSuccess wken | ||
180 | |||
181 | _ -> recurse | ||
182 | |||
183 | getpws = (map (const . fmap (,False)) $ mapMaybe (`Map.lookup` pws) fs ++ maybeToList defpw) ++ [ agentpw | agent_requested ] | ||
184 | |||
185 | -- TODO: First we should try the master key with AskNot. | ||
186 | -- If that fails, we should try the subkey. | ||
187 | agentpw (ask,qry) = do | ||
188 | s <- session | ||
189 | fromMaybe (return ("",False)) $ do | ||
190 | s <- s | ||
191 | Just $ do | ||
192 | case ask of AskAgain _ -> clearPassphrase s (queryPacket qry) | ||
193 | _ -> return () | ||
194 | mbpw <- getPassphrase s ask qry | ||
195 | quit s | ||
196 | return ( maybe "" S8.pack mbpw, True) | ||
197 | |||
198 | if symmetric_algorithm wk == dest_alg | ||
199 | && ( symmetric_algorithm wk == Unencrypted || s2k wk == dest_s2k ) | ||
200 | then return (KikiSuccess wk) | ||
201 | else maybe (decryptIt getpws) | ||
202 | (return . KikiSuccess) | ||
203 | $ Map.lookup kk unkeys | ||
204 | |||
diff --git a/lib/Types.hs b/lib/Types.hs new file mode 100644 index 0000000..9aa0340 --- /dev/null +++ b/lib/Types.hs | |||
@@ -0,0 +1,263 @@ | |||
1 | {-# LANGUAGE DeriveFunctor #-} | ||
2 | module Types where | ||
3 | |||
4 | import Data.Map as Map (Map) | ||
5 | import Data.OpenPGP | ||
6 | import Data.OpenPGP.Util | ||
7 | import FunctorToMaybe | ||
8 | import qualified System.Posix.Types as Posix | ||
9 | import qualified Data.ByteString.Lazy as L | ||
10 | |||
11 | -- | This type describes an idempotent transformation (merge or import) on a | ||
12 | -- set of GnuPG keyrings and other key files. | ||
13 | data KeyRingOperation = KeyRingOperation | ||
14 | { opFiles :: Map InputFile StreamInfo | ||
15 | -- ^ Indicates files to be read or updated. | ||
16 | , opPassphrases :: [PassphraseSpec] | ||
17 | -- ^ Indicates files or file descriptors where passphrases can be found. | ||
18 | , opTransforms :: [Transform] | ||
19 | -- ^ Transformations to be performed on the key pool after all files have | ||
20 | -- been read and before any have been written. | ||
21 | , opHome :: Maybe FilePath | ||
22 | -- ^ If provided, this is the directory where the 'HomeSec' and 'HomePub' | ||
23 | -- files reside. Otherwise, the evironment variable $GNUPGHOME is consulted | ||
24 | -- and if that is not set, it falls back to $HOME/.gnupg. | ||
25 | } | ||
26 | deriving (Eq,Show) | ||
27 | |||
28 | data InputFile = HomeSec | ||
29 | -- ^ A file named secring.gpg located in the home directory. | ||
30 | -- See 'opHome'. | ||
31 | | HomePub | ||
32 | -- ^ A file named pubring.gpg located in the home directory. | ||
33 | -- See 'opHome'. | ||
34 | | ArgFile FilePath | ||
35 | -- ^ Contents will be read or written from the specified path. | ||
36 | | FileDesc Posix.Fd | ||
37 | -- ^ Contents will be read or written from the specified file | ||
38 | -- descriptor. | ||
39 | | Pipe Posix.Fd Posix.Fd | ||
40 | -- ^ Contents will be read from the first descriptor and updated | ||
41 | -- content will be writen to the second. Note: Don't use Pipe | ||
42 | -- for 'Wallet' files. (TODO: Wallet support) | ||
43 | | Generate Int GenerateKeyParams | ||
44 | -- ^ New key packets will be generated if there is no | ||
45 | -- matching content already in the key pool. The integer is | ||
46 | -- a unique id number so that multiple generations can be | ||
47 | -- inserted into 'opFiles' | ||
48 | deriving (Eq,Ord,Show) | ||
49 | |||
50 | -- | This type describes how 'runKeyRing' will treat a file. | ||
51 | data StreamInfo = StreamInfo | ||
52 | { access :: Access | ||
53 | -- ^ Indicates whether the file is allowed to contain secret information. | ||
54 | , typ :: FileType | ||
55 | -- ^ Indicates the format and content type of the file. | ||
56 | , fill :: KeyFilter | ||
57 | -- ^ This filter controls what packets will be inserted into a file. | ||
58 | , spill :: KeyFilter | ||
59 | -- | ||
60 | -- ^ Use this to indicate whether or not a file's contents should be | ||
61 | -- available for updating other files. Note that although its type is | ||
62 | -- 'KeyFilter', it is usually interpretted as a boolean flag. Details | ||
63 | -- depend on 'typ' and are as follows: | ||
64 | -- | ||
65 | -- 'KeyRingFile': | ||
66 | -- | ||
67 | -- * 'KF_None' - The file's contents will not be shared. | ||
68 | -- | ||
69 | -- * otherwise - The file's contents will be shared. | ||
70 | -- | ||
71 | -- 'PEMFile': | ||
72 | -- | ||
73 | -- * 'KF_None' - The file's contents will not be shared. | ||
74 | -- | ||
75 | -- * 'KF_Match' - The file's key will be shared with the specified owner | ||
76 | -- key and usage tag. If 'fill' is also a 'KF_Match', then it must be | ||
77 | -- equal to this value; changing the usage or owner of a key is not | ||
78 | -- supported via the fill/spill mechanism. | ||
79 | -- | ||
80 | -- * otherwise - Unspecified. Do not use. | ||
81 | -- | ||
82 | -- 'WalletFile': | ||
83 | -- | ||
84 | -- * The 'spill' setting is ignored and the file's contents are shared. | ||
85 | -- (TODO) | ||
86 | -- | ||
87 | -- 'Hosts': | ||
88 | -- | ||
89 | -- * The 'spill' setting is ignored and the file's contents are shared. | ||
90 | -- (TODO) | ||
91 | -- | ||
92 | , initializer :: Initializer | ||
93 | -- ^ If 'typ' is 'PEMFile' and an 'External' 'initializer' string is set, | ||
94 | -- then it is interpretted as a shell command that may be used to create | ||
95 | -- the key if it does not exist. | ||
96 | , transforms :: [Transform] | ||
97 | -- ^ Per-file transformations that occur before the contents of a file are | ||
98 | -- spilled into the common pool. | ||
99 | } | ||
100 | deriving (Eq,Show) | ||
101 | |||
102 | |||
103 | -- | This type is used to indicate where to obtain passphrases. | ||
104 | data PassphraseSpec = PassphraseSpec | ||
105 | { passSpecRingFile :: Maybe FilePath | ||
106 | -- ^ If not Nothing, the passphrase is to be used for packets | ||
107 | -- from this file. | ||
108 | , passSpecKeySpec :: Maybe String | ||
109 | -- ^ Non-Nothing value reserved for future use. | ||
110 | -- (TODO: Use this to implement per-key passphrase associations). | ||
111 | , passSpecPassFile :: InputFile | ||
112 | -- ^ The passphrase will be read from this file or file descriptor. | ||
113 | } | ||
114 | -- | Use this to carry pasphrases from a previous run. | ||
115 | | PassphraseMemoizer PacketTranscoder | ||
116 | | PassphraseAgent | ||
117 | |||
118 | instance Show PassphraseSpec where | ||
119 | show (PassphraseSpec a b c) = "PassphraseSpec "++show (a,b,c) | ||
120 | show (PassphraseMemoizer _) = "PassphraseMemoizer" | ||
121 | instance Eq PassphraseSpec where | ||
122 | PassphraseSpec a b c == PassphraseSpec d e f | ||
123 | = and [a==d,b==e,c==f] | ||
124 | _ == _ | ||
125 | = False | ||
126 | |||
127 | |||
128 | |||
129 | data Transform = | ||
130 | Autosign | ||
131 | -- ^ This operation will make signatures for any tor-style UID | ||
132 | -- that matches a tor subkey and thus can be authenticated without | ||
133 | -- requring the judgement of a human user. | ||
134 | -- | ||
135 | -- A tor-style UID is one of the following form: | ||
136 | -- | ||
137 | -- > Anonymous <root@HOSTNAME.onion> | ||
138 | | DeleteSubkeyByFingerprint String | ||
139 | -- ^ Delete the subkey specified by the given fingerprint and any | ||
140 | -- associated signatures on that key. | ||
141 | | DeleteSubkeyByUsage String | ||
142 | -- ^ Delete the subkey specified by the given fingerprint and any | ||
143 | -- associated signatures on that key. | ||
144 | deriving (Eq,Ord,Show) | ||
145 | |||
146 | -- | Use this type to indicate whether a file of type 'KeyRingFile' is expected | ||
147 | -- to contain secret or public PGP key packets. Note that it is not supported | ||
148 | -- to mix both in the same file and that the secret key packets include all of | ||
149 | -- the information contained in their corresponding public key packets. | ||
150 | data Access = AutoAccess -- ^ secret or public as appropriate based on existing content. | ||
151 | -- (see 'rtRingAccess') | ||
152 | | Sec -- ^ secret information | ||
153 | | Pub -- ^ public information | ||
154 | deriving (Eq,Ord,Show) | ||
155 | |||
156 | data FileType = KeyRingFile | ||
157 | | PEMFile | ||
158 | | WalletFile | ||
159 | | DNSPresentation | ||
160 | | Hosts | ||
161 | deriving (Eq,Ord,Enum,Show) | ||
162 | |||
163 | -- type UsageTag = String | ||
164 | data Initializer = NoCreate | Internal GenerateKeyParams | External String | ||
165 | deriving (Eq,Ord,Show) | ||
166 | |||
167 | |||
168 | |||
169 | type PacketTranscoder = (SymmetricAlgorithm,S2K) -> MappedPacket -> IO (KikiCondition Packet) | ||
170 | type PacketDecrypter = MappedPacket -> IO (KikiCondition Packet) | ||
171 | |||
172 | -- | Note that the documentation here is intended for when this value is | ||
173 | -- assigned to 'fill'. For other usage, see 'spill'. | ||
174 | data KeyFilter = KF_None -- ^ No keys will be imported. | ||
175 | | KF_Match String -- ^ Only the key that matches the spec will be imported. | ||
176 | | KF_Subkeys -- ^ Subkeys will be imported if their owner key is | ||
177 | -- already in the ring. TODO: Even if their signatures | ||
178 | -- are bad? | ||
179 | | KF_Authentic -- ^ Keys are imported if they belong to an authenticated | ||
180 | -- identity (signed or self-authenticating). | ||
181 | | KF_All -- ^ All keys will be imported. | ||
182 | deriving (Eq,Ord,Show) | ||
183 | |||
184 | -- | The position and acces a packet had before the operation | ||
185 | data OriginFlags = OriginFlags | ||
186 | { originallyPublic :: Bool | ||
187 | -- ^ false if SecretKeyPacket | ||
188 | , originalNum :: Int | ||
189 | -- ^ packets are numbered, starting from 1.. | ||
190 | } deriving Show | ||
191 | |||
192 | type OriginMap = Map FilePath OriginFlags | ||
193 | |||
194 | type MappedPacket = OriginMapped Packet | ||
195 | data OriginMapped a = MappedPacket | ||
196 | { packet :: a | ||
197 | , locations :: OriginMap | ||
198 | } deriving Show | ||
199 | instance Functor OriginMapped where | ||
200 | fmap f (MappedPacket x ls) = MappedPacket (f x) ls | ||
201 | |||
202 | -- | This type is used to indicate success or failure | ||
203 | -- and in the case of success, return the computed object. | ||
204 | -- The 'FunctorToMaybe' class is implemented to facilitate | ||
205 | -- branching on failture. | ||
206 | data KikiCondition a = KikiSuccess a | ||
207 | | FailedToLock [FilePath] | ||
208 | | BadPassphrase | ||
209 | | FailedToMakeSignature | ||
210 | | CantFindHome | ||
211 | | AmbiguousKeySpec FilePath | ||
212 | | CannotImportMasterKey | ||
213 | | NoWorkingKey | ||
214 | deriving ( Functor, Show ) | ||
215 | |||
216 | instance FunctorToMaybe KikiCondition where | ||
217 | functorToMaybe (KikiSuccess a) = Just a | ||
218 | functorToMaybe _ = Nothing | ||
219 | |||
220 | instance Applicative KikiCondition where | ||
221 | pure a = KikiSuccess a | ||
222 | f <*> a = | ||
223 | case functorToEither f of | ||
224 | Right f -> case functorToEither a of | ||
225 | Right a -> pure (f a) | ||
226 | Left err -> err | ||
227 | Left err -> err | ||
228 | |||
229 | data InputFileContext = InputFileContext | ||
230 | { homesecPath :: FilePath | ||
231 | , homepubPath :: FilePath | ||
232 | } | ||
233 | |||
234 | |||
235 | -- | The 'KeyKey'-type is used to store the information of a key | ||
236 | -- which is used for finger-printing and as a lookup key into | ||
237 | -- maps. This type may be changed to an actual fingerprint in | ||
238 | -- in the future. | ||
239 | type KeyKey = [L.ByteString] | ||
240 | |||
241 | keykey :: Packet -> KeyKey | ||
242 | keykey key = | ||
243 | -- Note: The key's timestamp is normally included in it's fingerprint. | ||
244 | -- This is undesirable for kiki because it causes the same | ||
245 | -- key to be imported multiple times and show as apparently | ||
246 | -- distinct keys with different fingerprints. | ||
247 | -- Thus, we will remove the timestamp. | ||
248 | fingerprint_material (key {timestamp=0}) -- TODO: smaller key? | ||
249 | |||
250 | isKey :: Packet -> Bool | ||
251 | isKey (PublicKeyPacket {}) = True | ||
252 | isKey (SecretKeyPacket {}) = True | ||
253 | isKey _ = False | ||
254 | |||
255 | isUserID :: Packet -> Bool | ||
256 | isUserID (UserIDPacket {}) = True | ||
257 | isUserID _ = False | ||
258 | |||
259 | isTrust :: Packet -> Bool | ||
260 | isTrust (TrustPacket {}) = True | ||
261 | isTrust _ = False | ||
262 | |||
263 | |||