diff options
-rw-r--r-- | kiki.hs | 12 | ||||
-rw-r--r-- | lib/GnuPGAgent.hs | 41 | ||||
-rw-r--r-- | lib/KeyRing.hs | 157 | ||||
-rw-r--r-- | lib/Kiki.hs | 17 |
4 files changed, 171 insertions, 56 deletions
@@ -1146,8 +1146,8 @@ sync bExport bImport bSecret cmdarg args_raw = do | |||
1146 | ++ pems | 1146 | ++ pems |
1147 | ++ if bSecret then walts else [] | 1147 | ++ if bSecret then walts else [] |
1148 | ++ hosts | 1148 | ++ hosts |
1149 | , opPassphrases = do pfile <- maybeToList passfd | 1149 | , opPassphrases = withAgent $ do pfile <- maybeToList passfd |
1150 | return $ PassphraseSpec Nothing Nothing pfile | 1150 | return $ PassphraseSpec Nothing Nothing pfile |
1151 | , opTransforms = maybe [] (const [Autosign]) $ Map.lookup "--autosign" margs | 1151 | , opTransforms = maybe [] (const [Autosign]) $ Map.lookup "--autosign" margs |
1152 | , opHome = homespec | 1152 | , opHome = homespec |
1153 | } | 1153 | } |
@@ -1252,8 +1252,8 @@ kiki "show" args = do | |||
1252 | ++ pems | 1252 | ++ pems |
1253 | ++ walts | 1253 | ++ walts |
1254 | ++ hosts | 1254 | ++ hosts |
1255 | , opPassphrases = do pfile <- maybeToList passfd | 1255 | , opPassphrases = withAgent $ do pfile <- maybeToList passfd |
1256 | return $ PassphraseSpec Nothing Nothing pfile | 1256 | return $ PassphraseSpec Nothing Nothing pfile |
1257 | , opTransforms = [] | 1257 | , opTransforms = [] |
1258 | , opHome = homespec | 1258 | , opHome = homespec |
1259 | } | 1259 | } |
@@ -1537,8 +1537,8 @@ kiki "delete" args = do | |||
1537 | [ ( HomeSec, buildStreamInfo KF_All KeyRingFile ) | 1537 | [ ( HomeSec, buildStreamInfo KF_All KeyRingFile ) |
1538 | , ( HomePub, buildStreamInfo KF_All KeyRingFile ) | 1538 | , ( HomePub, buildStreamInfo KF_All KeyRingFile ) |
1539 | ] | 1539 | ] |
1540 | , opPassphrases = do pfile <- maybeToList passfd | 1540 | , opPassphrases = withAgent $ do pfile <- maybeToList passfd |
1541 | return $ PassphraseSpec Nothing Nothing pfile | 1541 | return $ PassphraseSpec Nothing Nothing pfile |
1542 | , opTransforms = map DeleteSubkeyByFingerprint fps | 1542 | , opTransforms = map DeleteSubkeyByFingerprint fps |
1543 | , opHome = homespec | 1543 | , opHome = homespec |
1544 | } | 1544 | } |
diff --git a/lib/GnuPGAgent.hs b/lib/GnuPGAgent.hs index 8bffd1b..165fdf2 100644 --- a/lib/GnuPGAgent.hs +++ b/lib/GnuPGAgent.hs | |||
@@ -3,6 +3,8 @@ | |||
3 | {-# LANGUAGE PatternGuards #-} | 3 | {-# LANGUAGE PatternGuards #-} |
4 | module GnuPGAgent | 4 | module GnuPGAgent |
5 | ( session | 5 | ( session |
6 | , Query(..) | ||
7 | , QueryMode(..) | ||
6 | , getPassphrase | 8 | , getPassphrase |
7 | , clearPassphrase | 9 | , clearPassphrase |
8 | , quit ) where | 10 | , quit ) where |
@@ -44,6 +46,12 @@ session = do | |||
44 | connect sock (SockAddrUnix (gpghome ++ "/S.gpg-agent")) | 46 | connect sock (SockAddrUnix (gpghome ++ "/S.gpg-agent")) |
45 | agent <- socketToHandle sock ReadWriteMode | 47 | agent <- socketToHandle sock ReadWriteMode |
46 | hSetBuffering agent LineBuffering | 48 | hSetBuffering agent LineBuffering |
49 | lookupEnv "DISPLAY" >>= \case | ||
50 | Just display -> do hPutStrLn agent ("option putenv DISPLAY="++display) | ||
51 | _ <- hGetLine agent | ||
52 | return () | ||
53 | Nothing -> return () | ||
54 | -- TODO: GPG_TTY | ||
47 | return $ Just $ GnuPGAgent agent | 55 | return $ Just $ GnuPGAgent agent |
48 | Nothing -> do | 56 | Nothing -> do |
49 | hPutStrLn stderr "Unable to find home directory." | 57 | hPutStrLn stderr "Unable to find home directory." |
@@ -64,23 +72,42 @@ clearPassphrase agent key = do | |||
64 | let cmd = "clear_passphrase "++fingerprint key | 72 | let cmd = "clear_passphrase "++fingerprint key |
65 | hPutStrLn (agentHandle agent) cmd | 73 | hPutStrLn (agentHandle agent) cmd |
66 | 74 | ||
67 | getPassphrase :: GnuPGAgent -> Bool -> Packet -> String -> Maybe Packet -> IO (Maybe String) | 75 | data Query = Query |
68 | getPassphrase agent ask key uid masterkey = do | 76 | { queryPacket :: Packet |
69 | let askopt = if ask then "" else "--no-ask " | 77 | , queryUID :: String |
70 | (er,pr,desc) = prompts key uid masterkey | 78 | , queryMainKey :: Maybe Packet |
79 | } | ||
80 | deriving Show | ||
81 | |||
82 | data QueryMode = AskNot | AskAgain String | Ask | ||
83 | deriving (Show,Eq,Ord) | ||
84 | |||
85 | getPassphrase :: GnuPGAgent -> QueryMode -> Query -> IO (Maybe String) | ||
86 | getPassphrase agent ask (Query key uid masterkey) = do | ||
87 | let (er0,pr,desc) = prompts key uid masterkey | ||
88 | (er,askopt) = case ask of | ||
89 | AskNot -> (er0,"--no-ask") | ||
90 | AskAgain ermsg -> (ermsg,"") | ||
91 | Ask -> (er0,"") | ||
71 | cmd = "get_passphrase "++askopt++fingerprint key++" "++unwords (map percentPlusEscape [er,pr,desc]) | 92 | cmd = "get_passphrase "++askopt++fingerprint key++" "++unwords (map percentPlusEscape [er,pr,desc]) |
72 | putStrLn cmd | 93 | -- putStrLn cmd |
73 | hPutStrLn (agentHandle agent) cmd | 94 | hPutStrLn (agentHandle agent) cmd |
74 | r0 <- hGetLine (agentHandle agent) | 95 | r0 <- hGetLine (agentHandle agent) |
96 | -- putStrLn $ "agent says: " ++ r0 | ||
75 | case takeWhile (/=' ') r0 of | 97 | case takeWhile (/=' ') r0 of |
76 | "OK" -> hGetLine (agentHandle agent) >>= unhex . drop 3 | 98 | "OK" -> hGetLine (agentHandle agent) >>= unhex . drop 3 |
77 | where | 99 | where |
78 | #if defined(VERSION_memory) | 100 | #if defined(VERSION_memory) |
79 | unhex hx = case convertFromBase Base16 (S8.pack hx) of | 101 | unhex hx = case convertFromBase Base16 (S8.pack hx) of |
80 | Left e -> return Nothing | 102 | Left e -> do |
103 | -- Useful for debugging but insecure generally ;) | ||
104 | -- putStrLn $ "convertFromBase error for input "++show hx++": "++show e | ||
105 | return Nothing | ||
81 | Right bs -> return $ Just $ S8.unpack bs | 106 | Right bs -> return $ Just $ S8.unpack bs |
82 | #elif defined(VERSION_dataenc) | 107 | #elif defined(VERSION_dataenc) |
83 | unhex hx = return $ fmap (map $ chr . fromIntegral) $ Base16.decode hx | 108 | unhex hx = maybe (return () {- putStrLn $ "dataenc error for input "++show hx -}) |
109 | return | ||
110 | $ fmap (map $ chr . fromIntegral) $ Base16.decode hx | ||
84 | #endif | 111 | #endif |
85 | "ERR" -> return Nothing | 112 | "ERR" -> return Nothing |
86 | 113 | ||
diff --git a/lib/KeyRing.hs b/lib/KeyRing.hs index ae2d14d..a055dad 100644 --- a/lib/KeyRing.hs +++ b/lib/KeyRing.hs | |||
@@ -47,7 +47,8 @@ module KeyRing | |||
47 | , KeyFilter(..) | 47 | , KeyFilter(..) |
48 | -- * Results of a KeyRing Operation | 48 | -- * Results of a KeyRing Operation |
49 | , KeyRingRuntime(..) | 49 | , KeyRingRuntime(..) |
50 | , MappedPacket(..) | 50 | , OriginMapped(..) |
51 | , MappedPacket | ||
51 | , KeyDB | 52 | , KeyDB |
52 | , KeyData(..) | 53 | , KeyData(..) |
53 | , SubKey(..) | 54 | , SubKey(..) |
@@ -209,6 +210,7 @@ import Base58 | |||
209 | import FunctorToMaybe | 210 | import FunctorToMaybe |
210 | import DotLock | 211 | import DotLock |
211 | import ProcessUtils (systemEnv, ExitCode(ExitFailure, ExitSuccess) ) | 212 | import ProcessUtils (systemEnv, ExitCode(ExitFailure, ExitSuccess) ) |
213 | import GnuPGAgent as Agent | ||
212 | 214 | ||
213 | -- DER-encoded elliptic curve ids | 215 | -- DER-encoded elliptic curve ids |
214 | -- nistp256_id = 0x2a8648ce3d030107 | 216 | -- nistp256_id = 0x2a8648ce3d030107 |
@@ -429,6 +431,7 @@ data PassphraseSpec = PassphraseSpec | |||
429 | } | 431 | } |
430 | -- | Use this to carry pasphrases from a previous run. | 432 | -- | Use this to carry pasphrases from a previous run. |
431 | | PassphraseMemoizer PacketTranscoder | 433 | | PassphraseMemoizer PacketTranscoder |
434 | | PassphraseAgent | ||
432 | 435 | ||
433 | instance Show PassphraseSpec where | 436 | instance Show PassphraseSpec where |
434 | show (PassphraseSpec a b c) = "PassphraseSpec "++show (a,b,c) | 437 | show (PassphraseSpec a b c) = "PassphraseSpec "++show (a,b,c) |
@@ -1433,6 +1436,9 @@ doesInputFileExist ctx f = do | |||
1433 | -} | 1436 | -} |
1434 | 1437 | ||
1435 | 1438 | ||
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). | ||
1436 | cachedContents :: Maybe S.ByteString -> InputFileContext -> InputFile -> IO (IO S.ByteString) | 1442 | cachedContents :: Maybe S.ByteString -> InputFileContext -> InputFile -> IO (IO S.ByteString) |
1437 | cachedContents maybePrompt ctx fd = do | 1443 | cachedContents maybePrompt ctx fd = do |
1438 | ref <- newIORef Nothing | 1444 | ref <- newIORef Nothing |
@@ -1637,7 +1643,7 @@ buildKeyDB ctx grip0 keyring = do | |||
1637 | readw wk n = fmap (n,) (readPacketsFromWallet wk (ArgFile n)) | 1643 | readw wk n = fmap (n,) (readPacketsFromWallet wk (ArgFile n)) |
1638 | 1644 | ||
1639 | -- KeyRings (todo: KikiCondition reporting?) | 1645 | -- KeyRings (todo: KikiCondition reporting?) |
1640 | (spilled,mwk,grip,accs,keys,unspilled) <- do | 1646 | (spilled,mwk,grip,accs,keyqs,unspilled) <- do |
1641 | #if MIN_VERSION_containers(0,5,0) | 1647 | #if MIN_VERSION_containers(0,5,0) |
1642 | ringPackets <- Map.traverseWithKey readp ringMap | 1648 | ringPackets <- Map.traverseWithKey readp ringMap |
1643 | #else | 1649 | #else |
@@ -1660,32 +1666,69 @@ buildKeyDB ctx grip0 keyring = do | |||
1660 | -- | keys | 1666 | -- | keys |
1661 | -- process ringPackets, and get a map of fingerprint info to | 1667 | -- process ringPackets, and get a map of fingerprint info to |
1662 | -- to a packet, remembering it's original file, access. | 1668 | -- to a packet, remembering it's original file, access. |
1663 | keys :: Map.Map KeyKey MappedPacket | 1669 | keys :: Map.Map KeyKey (MappedPacket,Map.Map String [Packet]) |
1664 | keys = Map.foldl slurpkeys Map.empty | 1670 | keys = Map.foldl slurpkeys Map.empty |
1665 | $ Map.mapWithKey filterSecrets ringPackets | 1671 | $ Map.mapWithKey filterSecrets ringPackets |
1666 | where | 1672 | where |
1667 | filterSecrets f (_,Message ps) = | 1673 | filterSecrets :: InputFile -> (a,Message) -> [[MappedPacket]] |
1668 | filter (isSecretKey . packet) | 1674 | filterSecrets f (_,Message ps) = keygroups |
1669 | $ zipWith (mappedPacketWithHint fname) ps [1..] | 1675 | -- filter (isSecretKey . packet) mps |
1670 | where fname = resolveForReport (Just ctx) f | 1676 | where |
1671 | slurpkeys m ps = m `Map.union` Map.fromList ps' | 1677 | mps = zipWith (mappedPacketWithHint fname) ps [1..] |
1672 | where ps' = zip (map (keykey . packet) ps) ps | 1678 | fname = resolveForReport (Just ctx) f |
1679 | keygroups = dropWhile (not . isSecretKey . packet . head) | ||
1680 | $ groupBy (const $ not . isSecretKey . packet) mps | ||
1681 | slurpkeys :: (Map.Map KeyKey (MappedPacket,Map.Map String [Packet])) | ||
1682 | -> [[MappedPacket]] | ||
1683 | -> (Map.Map KeyKey (MappedPacket,Map.Map String [Packet])) | ||
1684 | slurpkeys m pss = Map.unionWith combineKeyKey m m2 | ||
1685 | where | ||
1686 | m2 :: Map.Map KeyKey (MappedPacket, (Map.Map String [Packet])) | ||
1687 | m2 = Map.fromList $ map build pss | ||
1688 | where | ||
1689 | build ps = (kk,(kp,uidmap ps')) | ||
1690 | where | ||
1691 | (kpkt,ps') = splitAt 1 ps | ||
1692 | kp = head kpkt | ||
1693 | kk = keykey . packet $ kp | ||
1694 | combineKeyKey (mp,um) (mp2,um2) = (mp,Map.unionWith (++) um um2) | ||
1695 | uidmap ps = um2 | ||
1696 | where | ||
1697 | ugs = dropWhile (not . isUserID . packet .head) $ groupBy (const $ not . isUserID . packet) ps | ||
1698 | um2 = Map.fromList | ||
1699 | $ map (\(MappedPacket (UserIDPacket s) _:sigs)->(s,takeWhile isSignaturePacket $ map packet sigs)) ugs | ||
1673 | -- | mwk | 1700 | -- | mwk |
1674 | -- first master key matching the provided grip | 1701 | -- first master key matching the provided grip |
1675 | -- (the m is for "MappedPacket", wk for working key) | 1702 | -- (the m is for "MappedPacket", wk for working key) |
1676 | mwk :: Maybe MappedPacket | 1703 | mwk = fst <$> mwkq |
1677 | mwk = listToMaybe $ do | 1704 | |
1705 | main_query = fromMaybe (Query MarkerPacket "anonymous1" Nothing) $ snd <$> mwkq | ||
1706 | |||
1707 | keyqs :: Map.Map KeyKey (OriginMapped Query) | ||
1708 | keyqs = fmap (\(mp,us) -> mp { packet = main_query { queryPacket = packet mp} }) keys | ||
1709 | |||
1710 | mwkq :: Maybe (MappedPacket,Query) | ||
1711 | mwkq = listToMaybe $ do | ||
1678 | fp <- maybeToList grip | 1712 | fp <- maybeToList grip |
1679 | let matchfp mp = not (is_subkey p) && matchpr fp p == fp | 1713 | let matchfp (mp,us) |
1714 | | not (is_subkey p) && matchpr fp p == fp = Just (mp,query p us) | ||
1715 | | otherwise = Nothing | ||
1680 | where p = packet mp | 1716 | where p = packet mp |
1681 | Map.elems $ Map.filter matchfp keys | 1717 | -- TODO: check signature on UID packet? |
1718 | -- TODO: custom queries for subkeys? | ||
1719 | query p us = Query p | ||
1720 | (fromMaybe "" $ listToMaybe $ Map.keys us) | ||
1721 | Nothing -- No subkey queries for now. | ||
1722 | Map.elems $ Map.mapMaybe matchfp keys | ||
1723 | |||
1682 | -- | accs | 1724 | -- | accs |
1683 | -- file access(Sec | Pub) lookup table | 1725 | -- file access(Sec | Pub) lookup table |
1684 | accs :: Map.Map InputFile Access | 1726 | accs :: Map.Map InputFile Access |
1685 | accs = fmap (access . fst) ringPackets | 1727 | accs = fmap (access . fst) ringPackets |
1686 | return (spilled,mwk,grip,accs,keys,fmap snd unspilled) | 1728 | return (spilled,mwk,grip,accs,keyqs,fmap snd unspilled) |
1687 | 1729 | ||
1688 | transcode <- makeMemoizingDecrypter keyring ctx keys | 1730 | putStrLn $ ppShow keyqs |
1731 | transcode <- makeMemoizingDecrypter keyring ctx keyqs | ||
1689 | let doDecrypt = transcode (Unencrypted,S2K 100 "") | 1732 | let doDecrypt = transcode (Unencrypted,S2K 100 "") |
1690 | 1733 | ||
1691 | let wk = fmap packet mwk | 1734 | let wk = fmap packet mwk |
@@ -1707,6 +1750,9 @@ buildKeyDB ctx grip0 keyring = do | |||
1707 | r <- performManipulations doDecrypt rt1 mwk manip | 1750 | r <- performManipulations doDecrypt rt1 mwk manip |
1708 | try r $ \(rt2,report) -> do | 1751 | try r $ \(rt2,report) -> do |
1709 | return $ KikiSuccess (report,rtKeyDB rt2) | 1752 | return $ KikiSuccess (report,rtKeyDB rt2) |
1753 | -- XXX: Unspilled keys are not obtainable from rtKeyDB. | ||
1754 | -- If the working key is marked non spillable, then how | ||
1755 | -- would we look up it's UID and such? | ||
1710 | #if MIN_VERSION_containers(0,5,0) | 1756 | #if MIN_VERSION_containers(0,5,0) |
1711 | in fmap sequenceA $ Map.traverseWithKey trans spilled | 1757 | in fmap sequenceA $ Map.traverseWithKey trans spilled |
1712 | #else | 1758 | #else |
@@ -2544,7 +2590,7 @@ writePEMKeys doDecrypt db exports = do | |||
2544 | return $ KikiSuccess (fname,stream,pun) | 2590 | return $ KikiSuccess (fname,stream,pun) |
2545 | 2591 | ||
2546 | makeMemoizingDecrypter :: KeyRingOperation -> InputFileContext | 2592 | makeMemoizingDecrypter :: KeyRingOperation -> InputFileContext |
2547 | -> Map.Map KeyKey MappedPacket | 2593 | -> Map.Map KeyKey (OriginMapped Query) |
2548 | -> IO PacketTranscoder | 2594 | -> IO PacketTranscoder |
2549 | makeMemoizingDecrypter operation ctx keys = do | 2595 | makeMemoizingDecrypter operation ctx keys = do |
2550 | if null chains then do | 2596 | if null chains then do |
@@ -2555,12 +2601,14 @@ makeMemoizingDecrypter operation ctx keys = do | |||
2555 | -- FilePath? | 2601 | -- FilePath? |
2556 | -- pws :: Map.Map FilePath (IO S.ByteString) | 2602 | -- pws :: Map.Map FilePath (IO S.ByteString) |
2557 | {- | 2603 | {- |
2604 | -- This disabled code obtained password sources from StreamInfo records. | ||
2558 | pws <- | 2605 | pws <- |
2559 | Traversable.mapM (cachedContents ctx . fromJust . pwfile . typ) | 2606 | Traversable.mapM (cachedContents ctx . fromJust . pwfile . typ) |
2560 | (Map.mapKeys (resolveForReport Nothing) -- see note (*) note above | 2607 | (Map.mapKeys (resolveForReport Nothing) -- see note (*) note above |
2561 | $ Map.filter (isJust . pwfile . typ) $ opFiles operation) | 2608 | $ Map.filter (isJust . pwfile . typ) $ opFiles operation) |
2562 | -} | 2609 | -} |
2563 | let prompt = Just "Enter possibly multi-line passphrase (Press CTRL-D when finished):\n" | 2610 | let prompt = Just "Enter possibly multi-line passphrase (Press CTRL-D when finished):\n" |
2611 | -- List of file-specific password sources. | ||
2564 | pws2 <- | 2612 | pws2 <- |
2565 | Traversable.mapM (cachedContents prompt ctx) | 2613 | Traversable.mapM (cachedContents prompt ctx) |
2566 | $ Map.fromList $ mapMaybe | 2614 | $ Map.fromList $ mapMaybe |
@@ -2568,55 +2616,80 @@ makeMemoizingDecrypter operation ctx keys = do | |||
2568 | guard $ isNothing $ passSpecKeySpec spec | 2616 | guard $ isNothing $ passSpecKeySpec spec |
2569 | passSpecRingFile spec) | 2617 | passSpecRingFile spec) |
2570 | passspecs | 2618 | passspecs |
2619 | -- List of general password sources. | ||
2571 | defpw <- do | 2620 | defpw <- do |
2572 | Traversable.mapM (cachedContents prompt ctx . passSpecPassFile) | 2621 | Traversable.mapM (cachedContents prompt ctx . passSpecPassFile) |
2573 | $ listToMaybe $ filter (\sp -> isNothing (passSpecRingFile sp) | 2622 | $ listToMaybe $ filter (\sp -> isNothing (passSpecRingFile sp) |
2574 | && isNothing (passSpecKeySpec sp)) | 2623 | && isNothing (passSpecKeySpec sp)) |
2575 | $ opPassphrases operation | 2624 | $ passspecs |
2576 | unkeysRef <- newIORef (Map.empty :: Map.Map KeyKey Packet) | 2625 | unkeysRef <- newIORef (Map.empty :: Map.Map KeyKey Packet) |
2577 | return $ doDecrypt unkeysRef ({- pws `Map.union` -} pws2) defpw | 2626 | return $ doDecrypt unkeysRef ({- pws `Map.union` -} pws2) defpw (not $ null agentspec) |
2578 | else let PassphraseMemoizer f = head chains | 2627 | else let PassphraseMemoizer f = head chains |
2579 | in return f | 2628 | in return f |
2580 | where | 2629 | where |
2581 | (chains,passspecs) = partition isChain $ opPassphrases operation | 2630 | (chains,passspecs0) = partition isChain $ opPassphrases operation |
2582 | where isChain (PassphraseMemoizer {}) = True | 2631 | where isChain (PassphraseMemoizer {}) = True |
2583 | isChain _ = False | 2632 | isChain _ = False |
2633 | (agentspec,passspecs) = partition isAgent $ opPassphrases operation | ||
2634 | where isAgent PassphraseAgent = True | ||
2635 | isAgent _ = False | ||
2584 | doDecrypt :: IORef (Map.Map KeyKey Packet) | 2636 | doDecrypt :: IORef (Map.Map KeyKey Packet) |
2585 | -> Map.Map FilePath (IO S.ByteString) | 2637 | -> Map.Map FilePath (IO S.ByteString) |
2586 | -> Maybe (IO S.ByteString) | 2638 | -> Maybe (IO S.ByteString) |
2639 | -> Bool | ||
2587 | -> (SymmetricAlgorithm,S2K) | 2640 | -> (SymmetricAlgorithm,S2K) |
2588 | -> MappedPacket | 2641 | -> MappedPacket |
2589 | -> IO (KikiCondition Packet) | 2642 | -> IO (KikiCondition Packet) |
2590 | doDecrypt unkeysRef pws defpw (dest_alg,dest_s2k) mp0 = do | 2643 | doDecrypt unkeysRef pws defpw agent_requested (dest_alg,dest_s2k) mp0 = do |
2591 | unkeys <- readIORef unkeysRef | 2644 | unkeys <- readIORef unkeysRef |
2592 | let mp = fromMaybe mp0 $ do | 2645 | let (mp,qry) = fromMaybe (mp0,Query (packet mp0) "anonymous2" Nothing) $ do |
2593 | k <- Map.lookup kk keys | 2646 | k <- Map.lookup kk keys |
2594 | return $ mergeKeyPacket "decrypt" mp0 k | 2647 | return (mergeKeyPacket "decrypt" mp0 (fmap queryPacket k), packet k) |
2595 | wk = packet mp0 | 2648 | wk = packet mp0 |
2596 | kk = keykey wk | 2649 | kk = keykey wk |
2597 | fs = Map.keys $ locations mp | 2650 | fs = Map.keys $ locations mp |
2598 | 2651 | ||
2599 | decryptIt [] = return BadPassphrase | 2652 | decryptIt [] = return BadPassphrase |
2600 | decryptIt (getpw:getpws) = do | 2653 | decryptIt (getpw:getpws) = tries 1 getpw (decryptIt getpws) |
2601 | -- TODO: This function should use mergeKeyPacket to | 2654 | where |
2655 | tries count getpw recurse = do | ||
2656 | -- TODO: This function should use mergeKeyPacket to | ||
2602 | -- combine the packet with it's unspilled version before | 2657 | -- combine the packet with it's unspilled version before |
2603 | -- attempting to decrypt it. | 2658 | -- attempting to decrypt it. Note: We are uninterested |
2604 | pw <- getpw | 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) | ||
2605 | let wkun = fromMaybe wk $ do | 2663 | let wkun = fromMaybe wk $ do |
2606 | guard $ symmetric_algorithm (packet mp) /= Unencrypted | 2664 | guard $ symmetric_algorithm (packet mp) /= Unencrypted |
2607 | decryptSecretKey pw (packet mp) | 2665 | decryptSecretKey pw (packet mp) |
2666 | |||
2608 | case symmetric_algorithm wkun of | 2667 | case symmetric_algorithm wkun of |
2668 | |||
2609 | Unencrypted -> do | 2669 | Unencrypted -> do |
2610 | writeIORef unkeysRef (Map.insert kk wkun unkeys) | 2670 | writeIORef unkeysRef (Map.insert kk wkun unkeys) |
2611 | ek <- if dest_alg==Unencrypted | 2671 | ek <- if dest_alg==Unencrypted |
2612 | then return $ Just wkun | 2672 | then return $ Just wkun |
2613 | else encryptSecretKey pw dest_s2k dest_alg wkun | 2673 | else encryptSecretKey pw dest_s2k dest_alg wkun |
2614 | case ek of | 2674 | case ek of |
2615 | Nothing -> return $ BadPassphrase | 2675 | Nothing | wants_retry && count<3 -> tries (count+1) getpw recurse |
2616 | Just wken -> return $ KikiSuccess wken | 2676 | Nothing -> recurse |
2617 | _ -> decryptIt getpws | 2677 | Just wken -> return $ KikiSuccess wken |
2678 | |||
2679 | _ -> recurse | ||
2618 | 2680 | ||
2619 | getpws = mapMaybe (`Map.lookup` pws) fs ++ maybeToList defpw | 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) | ||
2620 | 2693 | ||
2621 | if symmetric_algorithm wk == dest_alg | 2694 | if symmetric_algorithm wk == dest_alg |
2622 | && ( symmetric_algorithm wk == Unencrypted || s2k wk == dest_s2k ) | 2695 | && ( symmetric_algorithm wk == Unencrypted || s2k wk == dest_s2k ) |
@@ -3434,10 +3507,13 @@ data OriginFlags = OriginFlags | |||
3434 | 3507 | ||
3435 | type OriginMap = Map.Map FilePath OriginFlags | 3508 | type OriginMap = Map.Map FilePath OriginFlags |
3436 | 3509 | ||
3437 | data MappedPacket = MappedPacket | 3510 | type MappedPacket = OriginMapped Packet |
3438 | { packet :: Packet | 3511 | data OriginMapped a = MappedPacket |
3512 | { packet :: a | ||
3439 | , locations :: OriginMap | 3513 | , locations :: OriginMap |
3440 | } deriving Show | 3514 | } deriving Show |
3515 | instance Functor OriginMapped where | ||
3516 | fmap f (MappedPacket x ls) = MappedPacket (f x) ls | ||
3441 | 3517 | ||
3442 | type TrustMap = Map.Map FilePath Packet | 3518 | type TrustMap = Map.Map FilePath Packet |
3443 | type SigAndTrust = ( MappedPacket | 3519 | type SigAndTrust = ( MappedPacket |
@@ -3521,19 +3597,26 @@ onionName kd = (addr,name) | |||
3521 | where | 3597 | where |
3522 | (addr,(name:_,_)) = getHostnames kd | 3598 | (addr,(name:_,_)) = getHostnames kd |
3523 | -} | 3599 | -} |
3600 | |||
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. | ||
3524 | keyCompare :: String -> Packet -> Packet -> Ordering | 3605 | keyCompare :: String -> Packet -> Packet -> Ordering |
3525 | keyCompare what (SecretKeyPacket {}) (PublicKeyPacket {}) = LT | 3606 | keyCompare what (SecretKeyPacket {}) (PublicKeyPacket {}) = LT |
3526 | keyCompare what (PublicKeyPacket {}) (SecretKeyPacket {}) = GT | 3607 | keyCompare what (PublicKeyPacket {}) (SecretKeyPacket {}) = GT |
3527 | keyCompare what a b | keykey a==keykey b = EQ | 3608 | keyCompare what a b | keykey a==keykey b = EQ |
3528 | keyCompare what a b = error $ unlines ["Unable to merge "++what++":" | 3609 | keyCompare what a b = error $ unlines ["Unable to merge "++what++":" |
3529 | , fingerprint a | 3610 | , if isKey a then fingerprint a else "" |
3530 | , PP.ppShow a | 3611 | , PP.ppShow a |
3531 | , fingerprint b | 3612 | , if isKey b then fingerprint b else "" |
3532 | , PP.ppShow b | 3613 | , PP.ppShow b |
3533 | ] | 3614 | ] |
3534 | 3615 | ||
3616 | -- | Merge two representations of the same key, prefering secret version | ||
3617 | -- because they have more information. | ||
3535 | mergeKeyPacket :: String -> MappedPacket -> MappedPacket -> MappedPacket | 3618 | mergeKeyPacket :: String -> MappedPacket -> MappedPacket -> MappedPacket |
3536 | mergeKeyPacket what key p = | 3619 | mergeKeyPacket what key p = |
3537 | key { packet = minimumBy (keyCompare what) [packet key,packet p] | 3620 | key { packet = minimumBy (keyCompare what) [packet key,packet p] |
3538 | , locations = Map.union (locations key) (locations p) | 3621 | , locations = Map.union (locations key) (locations p) |
3539 | } | 3622 | } |
diff --git a/lib/Kiki.hs b/lib/Kiki.hs index a134680..25c98e2 100644 --- a/lib/Kiki.hs +++ b/lib/Kiki.hs | |||
@@ -41,6 +41,10 @@ import CommandLine | |||
41 | import KeyRing | 41 | import KeyRing |
42 | import DotLock | 42 | import DotLock |
43 | 43 | ||
44 | withAgent :: [PassphraseSpec] -> [PassphraseSpec] | ||
45 | withAgent [] = [PassphraseAgent] | ||
46 | withAgent ps = ps | ||
47 | |||
44 | ciphername Unencrypted = "-" | 48 | ciphername Unencrypted = "-" |
45 | ciphername TripleDES = "3des" | 49 | ciphername TripleDES = "3des" |
46 | ciphername (SymmetricAlgorithm w8) = "cipher-"++show w8 | 50 | ciphername (SymmetricAlgorithm w8) = "cipher-"++show w8 |
@@ -87,8 +91,8 @@ minimalOp cap = op | |||
87 | [ ( HomeSec, streaminfo { access = Sec }) | 91 | [ ( HomeSec, streaminfo { access = Sec }) |
88 | , ( HomePub, streaminfo { access = Pub }) | 92 | , ( HomePub, streaminfo { access = Pub }) |
89 | ] | 93 | ] |
90 | , opPassphrases = do pfile <- maybeToList (cap_passfd cap) | 94 | , opPassphrases = withAgent $ do pfile <- maybeToList (cap_passfd cap) |
91 | return $ PassphraseSpec Nothing Nothing pfile | 95 | return $ PassphraseSpec Nothing Nothing pfile |
92 | , opTransforms = [] | 96 | , opTransforms = [] |
93 | , opHome = cap_homespec cap | 97 | , opHome = cap_homespec cap |
94 | } | 98 | } |
@@ -155,6 +159,7 @@ importAndRefresh root cmn = do | |||
155 | ctx = InputFileContext secring pubring | 159 | ctx = InputFileContext secring pubring |
156 | passwordop = KeyRingOperation | 160 | passwordop = KeyRingOperation |
157 | { opFiles = Map.empty | 161 | { opFiles = Map.empty |
162 | -- TODO: ask agent for new passphrase | ||
158 | , opPassphrases = do pfd <- maybeToList passfd | 163 | , opPassphrases = do pfd <- maybeToList passfd |
159 | return $ PassphraseSpec Nothing Nothing pfd | 164 | return $ PassphraseSpec Nothing Nothing pfd |
160 | , opHome = homespec | 165 | , opHome = homespec |
@@ -233,16 +238,16 @@ importAndRefresh root cmn = do | |||
233 | , ( ArgFile sshcpath, (peminfo 2048 "ssh-client") ) | 238 | , ( ArgFile sshcpath, (peminfo 2048 "ssh-client") ) |
234 | , ( ArgFile sshspath, (peminfo 2048 "ssh-server") ) | 239 | , ( ArgFile sshspath, (peminfo 2048 "ssh-server") ) |
235 | ] | 240 | ] |
236 | , opPassphrases = pwds ++ do pfd <- maybeToList passfd | 241 | , opPassphrases = withAgent $ pwds ++ do pfd <- maybeToList passfd |
237 | return $ PassphraseSpec Nothing Nothing pfd | 242 | return $ PassphraseSpec Nothing Nothing pfd |
238 | , opHome = homespec | 243 | , opHome = homespec |
239 | , opTransforms = [] | 244 | , opTransforms = [] |
240 | } | 245 | } |
241 | -- doNothing = return () | 246 | -- doNothing = return () |
242 | nop = KeyRingOperation | 247 | nop = KeyRingOperation |
243 | { opFiles = Map.empty | 248 | { opFiles = Map.empty |
244 | , opPassphrases = do pfd <- maybeToList passfd | 249 | , opPassphrases = withAgent $ do pfd <- maybeToList passfd |
245 | return $ PassphraseSpec Nothing Nothing pfd | 250 | return $ PassphraseSpec Nothing Nothing pfd |
246 | , opHome=homespec, opTransforms = [] | 251 | , opHome=homespec, opTransforms = [] |
247 | } | 252 | } |
248 | -- if bUnprivileged then doNothing else mkdirFor torpath | 253 | -- if bUnprivileged then doNothing else mkdirFor torpath |