summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/GnuPGAgent.hs23
-rw-r--r--lib/KeyRing.hs143
-rw-r--r--lib/Kiki.hs47
-rw-r--r--lib/PacketTranscoder.hs137
-rw-r--r--lib/Types.hs38
5 files changed, 229 insertions, 159 deletions
diff --git a/lib/GnuPGAgent.hs b/lib/GnuPGAgent.hs
index 4a0e8c8..5878357 100644
--- a/lib/GnuPGAgent.hs
+++ b/lib/GnuPGAgent.hs
@@ -60,15 +60,15 @@ percentPlusEscape :: String -> String
60percentPlusEscape s = do 60percentPlusEscape s = do
61 c <- s 61 c <- s
62 case c of 62 case c of
63 ' ' -> "+" 63 ' ' -> "+"
64 '+' -> "%2B" 64 '+' -> "%2B"
65 '"' -> "%22" 65 '"' -> "%22"
66 '%' -> "%25" 66 '%' -> "%25"
67 _ | c < ' ' -> printf "%%%02X" (ord c) 67 _ | c < ' ' -> printf "%%%02X" (ord c)
68 _ -> return c 68 _ -> return c
69 69
70clearPassphrase agent key = do 70clearPassphrase agent key = do
71 let cmd = "clear_passphrase "++fingerprint key 71 let cmd = "clear_passphrase --mode=normal "++fingerprint key
72 hPutStrLn (agentHandle agent) cmd 72 hPutStrLn (agentHandle agent) cmd
73 73
74data Query = Query 74data Query = Query
@@ -78,21 +78,22 @@ data Query = Query
78 } 78 }
79 deriving Show 79 deriving Show
80 80
81data QueryMode = AskNot | AskAgain String | Ask 81data QueryMode = AskNot | AskAgain String | AskExisting | AskNew
82 deriving (Show,Eq,Ord) 82 deriving (Show,Eq,Ord)
83 83
84getPassphrase :: GnuPGAgent -> QueryMode -> Query -> IO (Maybe String) 84getPassphrase :: GnuPGAgent -> QueryMode -> Query -> IO (Maybe String)
85getPassphrase agent ask (Query key uid masterkey) = do 85getPassphrase agent ask (Query key uid masterkey) = do
86 let (er0,pr,desc) = prompts key uid masterkey 86 let (er0,pr,desc) = prompts key uid masterkey
87 (er,askopt) = case ask of 87 (er,askopt) = case ask of
88 AskNot -> (er0,"--no-ask") 88 AskNot -> (er0,"--no-ask ")
89 AskAgain ermsg -> (ermsg,"") 89 AskAgain ermsg -> (ermsg,"")
90 Ask -> (er0,"") 90 AskExisting -> (er0,"")
91 AskNew -> (er0,"--repeat=1 ")
91 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])
92 -- putStrLn cmd 93 hPutStrLn stderr $ "gpg-agent <- " ++ cmd
93 hPutStrLn (agentHandle agent) cmd 94 hPutStrLn (agentHandle agent) cmd
94 r0 <- hGetLine (agentHandle agent) 95 r0 <- hGetLine (agentHandle agent)
95 -- putStrLn $ "agent says: " ++ r0 96 -- hPutStrLn stderr $ "agent says: " ++ r0
96 case takeWhile (/=' ') r0 of 97 case takeWhile (/=' ') r0 of
97 "OK" -> hGetLine (agentHandle agent) >>= unhex . drop 3 98 "OK" -> hGetLine (agentHandle agent) >>= unhex . drop 3
98 where 99 where
diff --git a/lib/KeyRing.hs b/lib/KeyRing.hs
index 313258d..e084fcd 100644
--- a/lib/KeyRing.hs
+++ b/lib/KeyRing.hs
@@ -23,6 +23,7 @@
23{-# LANGUAGE DoAndIfThenElse #-} 23{-# LANGUAGE DoAndIfThenElse #-}
24{-# LANGUAGE PatternGuards #-} 24{-# LANGUAGE PatternGuards #-}
25{-# LANGUAGE ForeignFunctionInterface #-} 25{-# LANGUAGE ForeignFunctionInterface #-}
26{-# LANGUAGE LambdaCase #-}
26module KeyRing 27module KeyRing
27 ( 28 (
28 -- * Error Handling 29 -- * Error Handling
@@ -84,6 +85,7 @@ module KeyRing
84 , accBindings 85 , accBindings
85 , isSubkeySignature 86 , isSubkeySignature
86 , torhash 87 , torhash
88 , torUIDFromKey
87 , ParsedCert(..) 89 , ParsedCert(..)
88 , parseCertBlob 90 , parseCertBlob
89 , packetFromPublicRSAKey 91 , packetFromPublicRSAKey
@@ -186,7 +188,7 @@ import Foreign.C.Error ( throwErrnoIfMinus1_ )
186import Foreign.Storable 188import Foreign.Storable
187#endif 189#endif
188import System.FilePath ( takeDirectory ) 190import System.FilePath ( takeDirectory )
189import System.IO (hPutStrLn,withFile,IOMode(..), Handle, hPutStr, stderr) 191import System.IO (hPutStrLn,withFile,IOMode(..), Handle, hPutStr, stderr, hClose)
190import Data.IORef 192import Data.IORef
191import System.Posix.IO ( fdToHandle ) 193import System.Posix.IO ( fdToHandle )
192import qualified Data.Traversable as Traversable 194import qualified Data.Traversable as Traversable
@@ -624,14 +626,6 @@ usageString flgs =
624 626
625 627
626 628
627-- matchpr computes the fingerprint of the given key truncated to
628-- be the same lenght as the given fingerprint for comparison.
629--
630-- matchpr fp = Data.List.Extra.takeEnd (length fp)
631--
632matchpr :: String -> Packet -> String
633matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp
634
635keyFlags :: t -> [Packet] -> [SignatureSubpacket] 629keyFlags :: t -> [Packet] -> [SignatureSubpacket]
636keyFlags wkun uids = keyFlags0 wkun (filter isSignaturePacket uids) 630keyFlags wkun uids = keyFlags0 wkun (filter isSignaturePacket uids)
637 631
@@ -1181,7 +1175,7 @@ writeStampedL ctx f stamp bs = writeStamped0 ctx f stamp (either L.hPut L.writeF
1181-} 1175-}
1182 1176
1183writeStamped :: InputFileContext -> InputFile -> Posix.EpochTime -> String -> IO () 1177writeStamped :: InputFileContext -> InputFile -> Posix.EpochTime -> String -> IO ()
1184writeStamped ctx f stamp str = writeStamped0 ctx f stamp (either hPutStr writeFile) str 1178writeStamped ctx f stamp str = writeStamped0 ctx f stamp (either (\h x-> hPutStr h x >> hClose h) writeFile) str
1185 1179
1186getInputFileTime :: InputFileContext -> InputFile -> IO CTime 1180getInputFileTime :: InputFileContext -> InputFile -> IO CTime
1187getInputFileTime ctx (Pipe fdr fdw) = do 1181getInputFileTime ctx (Pipe fdr fdw) = do
@@ -1222,7 +1216,7 @@ generateSubkey transcode kd' (genparam,StreamInfo { spill = KF_Match tag }) = do
1222 if null subs 1216 if null subs
1223 then do 1217 then do
1224 newkey <- generateKey genparam 1218 newkey <- generateKey genparam
1225 kdr <- insertSubkey transcode (keykey (keyPacket kd)) kd [mkUsage tag] "" newkey 1219 kdr <- insertSubkey transcode (keykey (keyPacket kd)) kd [mkUsage tag] (ArgFile "") newkey
1226 try kdr $ \(newkd,report) -> do 1220 try kdr $ \(newkd,report) -> do
1227 return $ KikiSuccess (newkd, report ++ [("", NewPacket $ showPacket newkey)]) 1221 return $ KikiSuccess (newkd, report ++ [("", NewPacket $ showPacket newkey)])
1228 else do 1222 else do
@@ -1233,7 +1227,7 @@ importSecretKey ::
1233 (PacketTranscoder) 1227 (PacketTranscoder)
1234 -> KikiCondition 1228 -> KikiCondition
1235 (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)]) 1229 (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)])
1236 -> (FilePath, Maybe [Char], [KeyKey], StreamInfo, t) 1230 -> (InputFile, Maybe [Char], [KeyKey], StreamInfo, t)
1237 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)])) 1231 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)]))
1238importSecretKey transcode db' tup = do 1232importSecretKey transcode db' tup = do
1239 try db' $ \(db',report0) -> do 1233 try db' $ \(db',report0) -> do
@@ -1345,10 +1339,6 @@ writeHostsFiles krd ctx (hostdbs0,hostdbs,u1,gpgnames,outgoing_names) = do
1345 return $ map (first $ resolveForReport $ Just ctx) rs 1339 return $ map (first $ resolveForReport $ Just ctx) rs
1346 return $ concat rss 1340 return $ concat rss
1347 1341
1348isSecretKey :: Packet -> Bool
1349isSecretKey (SecretKeyPacket {}) = True
1350isSecretKey _ = False
1351
1352-- | buildKeyDB 1342-- | buildKeyDB
1353-- 1343--
1354-- merge all keyrings, PEM files, and wallets into process memory. 1344-- merge all keyrings, PEM files, and wallets into process memory.
@@ -1368,11 +1358,10 @@ buildKeyDB :: InputFileContext -> Maybe String -> KeyRingOperation
1368 ) 1358 )
1369 ,{- report_imports -} [(FilePath,KikiReportAction)])) 1359 ,{- report_imports -} [(FilePath,KikiReportAction)]))
1370buildKeyDB ctx grip0 keyring = do 1360buildKeyDB ctx grip0 keyring = do
1371 let 1361 let files istyp = do
1372 files istyp = do
1373 (f,stream) <- Map.toList (opFiles keyring) 1362 (f,stream) <- Map.toList (opFiles keyring)
1374 guard (istyp $ typ stream) 1363 guard (istyp $ typ stream)
1375 resolveInputFile ctx f 1364 return f -- resolveInputFile ctx f
1376 1365
1377 ringMap0 = Map.filter (isring . typ) $ opFiles keyring 1366 ringMap0 = Map.filter (isring . typ) $ opFiles keyring
1378 (genMap,ringMap) = Map.partitionWithKey isgen ringMap0 1367 (genMap,ringMap) = Map.partitionWithKey isgen ringMap0
@@ -1392,7 +1381,7 @@ buildKeyDB ctx grip0 keyring = do
1392 _ -> AutoAccess 1381 _ -> AutoAccess
1393 acc -> acc 1382 acc -> acc
1394 1383
1395 readw wk n = fmap (n,) (readPacketsFromWallet wk (ArgFile n)) 1384 readw wk n = fmap (n,) (readPacketsFromWallet wk n)
1396 1385
1397 -- KeyRings (todo: KikiCondition reporting?) 1386 -- KeyRings (todo: KikiCondition reporting?)
1398 (spilled,mwk,grip,accs,keyqs,unspilled) <- do 1387 (spilled,mwk,grip,accs,keyqs,unspilled) <- do
@@ -1418,69 +1407,17 @@ buildKeyDB ctx grip0 keyring = do
1418 -- | keys 1407 -- | keys
1419 -- process ringPackets, and get a map of fingerprint info to 1408 -- process ringPackets, and get a map of fingerprint info to
1420 -- to a packet, remembering it's original file, access. 1409 -- to a packet, remembering it's original file, access.
1421 keys :: Map.Map KeyKey (MappedPacket,Map.Map String [Packet]) 1410 keys :: Map.Map KeyKey (OriginMapped Query)
1422 keys = Map.foldl slurpkeys Map.empty 1411 mwk :: Maybe MappedPacket
1423 $ Map.mapWithKey filterSecrets ringPackets 1412 (mwk, keys) = keyQueries grip ringPackets
1424 where
1425 filterSecrets :: InputFile -> (a,Message) -> [[MappedPacket]]
1426 filterSecrets f (_,Message ps) = keygroups
1427 -- filter (isSecretKey . packet) mps
1428 where
1429 mps = zipWith (mappedPacketWithHint fname) ps [1..]
1430 fname = resolveForReport (Just ctx) f
1431 keygroups = dropWhile (not . isSecretKey . packet . head)
1432 $ groupBy (const $ not . isSecretKey . packet) mps
1433 slurpkeys :: (Map.Map KeyKey (MappedPacket,Map.Map String [Packet]))
1434 -> [[MappedPacket]]
1435 -> (Map.Map KeyKey (MappedPacket,Map.Map String [Packet]))
1436 slurpkeys m pss = Map.unionWith combineKeyKey m m2
1437 where
1438 m2 :: Map.Map KeyKey (MappedPacket, (Map.Map String [Packet]))
1439 m2 = Map.fromList $ map build pss
1440 where
1441 build ps = (kk,(kp,uidmap ps'))
1442 where
1443 (kpkt,ps') = splitAt 1 ps
1444 kp = head kpkt
1445 kk = keykey . packet $ kp
1446 combineKeyKey (mp,um) (mp2,um2) = (mp,Map.unionWith (++) um um2)
1447 uidmap ps = um2
1448 where
1449 ugs = dropWhile (not . isUserID . packet .head) $ groupBy (const $ not . isUserID . packet) ps
1450 um2 = Map.fromList
1451 $ map (\(MappedPacket (UserIDPacket s) _:sigs)->(s,takeWhile isSignaturePacket $ map packet sigs)) ugs
1452 -- | mwk
1453 -- first master key matching the provided grip
1454 -- (the m is for "MappedPacket", wk for working key)
1455 mwk = fst <$> mwkq
1456
1457 main_query = fromMaybe (Query MarkerPacket "anonymous1" Nothing) $ snd <$> mwkq
1458
1459 keyqs :: Map.Map KeyKey (OriginMapped Query)
1460 keyqs = fmap (\(mp,us) -> mp { packet = main_query { queryPacket = packet mp} }) keys
1461
1462 mwkq :: Maybe (MappedPacket,Query)
1463 mwkq = listToMaybe $ do
1464 fp <- maybeToList grip
1465 let matchfp (mp,us)
1466 | not (is_subkey p) && matchpr fp p == fp = Just (mp,query p us)
1467 | otherwise = Nothing
1468 where p = packet mp
1469 -- TODO: check signature on UID packet?
1470 -- TODO: custom queries for subkeys?
1471 query p us = Query p
1472 (fromMaybe "" $ listToMaybe $ Map.keys us)
1473 Nothing -- No subkey queries for now.
1474 Map.elems $ Map.mapMaybe matchfp keys
1475 1413
1476 -- | accs 1414 -- | accs
1477 -- file access(Sec | Pub) lookup table 1415 -- file access(Sec | Pub) lookup table
1478 accs :: Map.Map InputFile Access 1416 accs :: Map.Map InputFile Access
1479 accs = fmap (access . fst) ringPackets 1417 accs = fmap (access . fst) ringPackets
1480 return (spilled,mwk,grip,accs,keyqs,fmap snd unspilled) 1418 return (spilled,mwk,grip,accs,keys,fmap snd unspilled)
1481 1419
1482 putStrLn $ ppShow keyqs 1420 transcode <- makeMemoizingDecrypter keyring ctx (mwk,keyqs)
1483 transcode <- makeMemoizingDecrypter keyring ctx keyqs
1484 let doDecrypt = transcode (Unencrypted,S2K 100 "") 1421 let doDecrypt = transcode (Unencrypted,S2K 100 "")
1485 1422
1486 let wk = fmap packet mwk 1423 let wk = fmap packet mwk
@@ -1548,7 +1485,6 @@ buildKeyDB ctx grip0 keyring = do
1548 let pems = do 1485 let pems = do
1549 (n,stream) <- Map.toList $ opFiles keyring 1486 (n,stream) <- Map.toList $ opFiles keyring
1550 grip <- maybeToList grip 1487 grip <- maybeToList grip
1551 n <- resolveInputFile ctx n
1552 guard $ spillable stream && isSecretKeyFile (typ stream) 1488 guard $ spillable stream && isSecretKeyFile (typ stream)
1553 let us = mapMaybe usageFromFilter [fill stream,spill stream] 1489 let us = mapMaybe usageFromFilter [fill stream,spill stream]
1554 usage <- take 1 us 1490 usage <- take 1 us
@@ -1559,7 +1495,10 @@ buildKeyDB ctx grip0 keyring = do
1559 ms = map fst $ filterMatches topspec (Map.toList db) 1495 ms = map fst $ filterMatches topspec (Map.toList db)
1560 cmd = initializer stream 1496 cmd = initializer stream
1561 return (n,subspec,ms,stream, cmd) 1497 return (n,subspec,ms,stream, cmd)
1562 imports <- filterM (\(n,_,_,_,_) -> doesFileExist n) pems 1498
1499 imports <- filterM (\case (ArgFile n,_,_,_,_) -> doesFileExist n
1500 _ -> return True)
1501 pems
1563 db <- foldM (importSecretKey transcode) (KikiSuccess (db,[])) imports 1502 db <- foldM (importSecretKey transcode) (KikiSuccess (db,[])) imports
1564 try db $ \(db,reportPEMs) -> do 1503 try db $ \(db,reportPEMs) -> do
1565 1504
@@ -1595,6 +1534,9 @@ generateInternals transcode mwk db gens = do
1595torhash :: Packet -> String 1534torhash :: Packet -> String
1596torhash key = fromMaybe "" $ derToBase32 <$> derRSA key 1535torhash key = fromMaybe "" $ derToBase32 <$> derRSA key
1597 1536
1537torUIDFromKey :: Packet -> String
1538torUIDFromKey key = "Anonymous <root@" ++ take 16 (torhash key) ++ ".onion>"
1539
1598derToBase32 :: ByteString -> String 1540derToBase32 :: ByteString -> String
1599derToBase32 = map toLower . base32 . sha1 1541derToBase32 = map toLower . base32 . sha1
1600 where 1542 where
@@ -1827,19 +1769,19 @@ readSecretPEMFile fname = do
1827doImport 1769doImport
1828 :: PacketTranscoder 1770 :: PacketTranscoder
1829 -> Map.Map KeyKey KeyData 1771 -> Map.Map KeyKey KeyData
1830 -> (FilePath, Maybe [Char], [KeyKey], StreamInfo, t) 1772 -> (InputFile, Maybe [Char], [KeyKey], StreamInfo, t)
1831 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)])) 1773 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)]))
1832doImport transcode db (fname,subspec,ms,typ -> typ,_) = do 1774doImport transcode db (fname,subspec,ms,typ -> typ,_) = do
1833 flip (maybe $ return CannotImportMasterKey) 1775 flip (maybe $ return CannotImportMasterKey)
1834 subspec $ \tag -> do 1776 subspec $ \tag -> do
1835 (certs,keys) <- case typ of 1777 (certs,keys) <- case typ of
1836 PEMFile -> do 1778 PEMFile -> do
1837 ps <- readSecretPEMFile (ArgFile fname) 1779 ps <- readSecretPEMFile fname
1838 let (mapMaybe spemCert -> certs,mapMaybe spemPacket-> keys) 1780 let (mapMaybe spemCert -> certs,mapMaybe spemPacket-> keys)
1839 = partition (isJust . spemCert) ps 1781 = partition (isJust . spemCert) ps
1840 return (certs,keys) 1782 return (certs,keys)
1841 DNSPresentation -> do 1783 DNSPresentation -> do
1842 p <- readSecretDNSFile (ArgFile fname) 1784 p <- readSecretDNSFile fname
1843 return ([],[p]) 1785 return ([],[p])
1844 -- TODO Probably we need to move to a new design where signature 1786 -- TODO Probably we need to move to a new design where signature
1845 -- packets are merged into the database in one phase with null 1787 -- packets are merged into the database in one phase with null
@@ -1852,7 +1794,7 @@ doImport transcode db (fname,subspec,ms,typ -> typ,_) = do
1852 try prior $ \(db,report) -> do 1794 try prior $ \(db,report) -> do
1853 let (m0,tailms) = splitAt 1 ms 1795 let (m0,tailms) = splitAt 1 ms
1854 if (not (null tailms) || null m0) 1796 if (not (null tailms) || null m0)
1855 then return $ AmbiguousKeySpec fname 1797 then return $ AmbiguousKeySpec (resolveForReport Nothing fname)
1856 else do 1798 else do
1857 let kk = keykey key 1799 let kk = keykey key
1858 cs = filter (\c -> kk==keykey (pcertKey c)) certs 1800 cs = filter (\c -> kk==keykey (pcertKey c)) certs
@@ -1872,7 +1814,7 @@ doImportG
1872 -> Map.Map KeyKey KeyData 1814 -> Map.Map KeyKey KeyData
1873 -> [KeyKey] -- m0, only head is used 1815 -> [KeyKey] -- m0, only head is used
1874 -> [SignatureSubpacket] -- tags 1816 -> [SignatureSubpacket] -- tags
1875 -> FilePath 1817 -> InputFile
1876 -> Packet 1818 -> Packet
1877 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)])) 1819 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)]))
1878doImportG transcode db m0 tags fname key = do 1820doImportG transcode db m0 tags fname key = do
@@ -1881,13 +1823,16 @@ doImportG transcode db m0 tags fname key = do
1881 kdr <- insertSubkey transcode kk kd tags fname key 1823 kdr <- insertSubkey transcode kk kd tags fname key
1882 try kdr $ \(kd',rrs) -> return $ KikiSuccess (Map.insert kk kd' db, rrs) 1824 try kdr $ \(kd',rrs) -> return $ KikiSuccess (Map.insert kk kd' db, rrs)
1883 1825
1884insertSubkey transcode kk (KeyData top topsigs uids subs) tags fname key0 = do 1826insertSubkey transcode kk (KeyData top topsigs uids subs) tags inputfile key0 = do
1885 let topcipher = symmetric_algorithm $ packet top 1827 let topcipher = symmetric_algorithm $ packet top
1886 tops2k = s2k $ packet top 1828 tops2k = s2k $ packet top
1829 doDecrypt = transcode (Unencrypted,S2K 100 "")
1830 fname = resolveForReport Nothing inputfile
1831 wkun <- doDecrypt top
1832 try wkun $ \wkun -> do
1887 key' <- transcode (topcipher,tops2k) $ mappedPacket "" key0 1833 key' <- transcode (topcipher,tops2k) $ mappedPacket "" key0
1888 try key' $ \key -> do 1834 try key' $ \key -> do
1889 let subkk = keykey key 1835 let subkk = keykey key
1890 doDecrypt = transcode (Unencrypted,S2K 100 "")
1891 (is_new, subkey) = maybe (True, SubKey (mappedPacket fname key) 1836 (is_new, subkey) = maybe (True, SubKey (mappedPacket fname key)
1892 []) 1837 [])
1893 ( (False,) . addOrigin ) 1838 ( (False,) . addOrigin )
@@ -1903,7 +1848,7 @@ insertSubkey transcode kk (KeyData top topsigs uids subs) tags fname key0 = do
1903 1848
1904 istor = do 1849 istor = do
1905 guard ("tor" `elem` mapMaybe usage tags) 1850 guard ("tor" `elem` mapMaybe usage tags)
1906 return $ "Anonymous <root@" ++ take 16 (torhash key) ++ ".onion>" 1851 return $ torUIDFromKey key
1907 1852
1908 uids' <- flip (maybe $ return $ KikiSuccess (uids,[])) istor $ \idstr -> do 1853 uids' <- flip (maybe $ return $ KikiSuccess (uids,[])) istor $ \idstr -> do
1909 let has_torid = do 1854 let has_torid = do
@@ -1913,9 +1858,6 @@ insertSubkey transcode kk (KeyData top topsigs uids subs) tags fname key0 = do
1913 s <- (signatures $ Message (packet top:UserIDPacket idstr:map (packet . fst) sigtrusts)) 1858 s <- (signatures $ Message (packet top:UserIDPacket idstr:map (packet . fst) sigtrusts))
1914 signatures_over $ verify (Message [packet top]) s 1859 signatures_over $ verify (Message [packet top]) s
1915 flip (flip maybe $ const $ return $ KikiSuccess (uids,[])) has_torid $ do 1860 flip (flip maybe $ const $ return $ KikiSuccess (uids,[])) has_torid $ do
1916 wkun <- doDecrypt top
1917
1918 try wkun $ \wkun -> do
1919 1861
1920 let keyflags = keyFlags wkun (map packet $ flattenAllUids fname True uids) 1862 let keyflags = keyFlags wkun (map packet $ flattenAllUids fname True uids)
1921 uid = UserIDPacket idstr 1863 uid = UserIDPacket idstr
@@ -2482,7 +2424,7 @@ initializeMissingPEMFiles operation ctx grip mwk transcode db = do
2482 guard $ case r of 2424 guard $ case r of
2483 ExternallyGeneratedFile -> True 2425 ExternallyGeneratedFile -> True
2484 _ -> False 2426 _ -> False
2485 return (f,subspec,map fst ms,stream,cmd) 2427 return (ArgFile f,subspec,map fst ms,stream,cmd)
2486 2428
2487 try v $ \(db,import_rs) -> do 2429 try v $ \(db,import_rs) -> do
2488 2430
@@ -3143,25 +3085,6 @@ data KeyData = KeyData { keyMappedPacket :: MappedPacket -- main key
3143 3085
3144type KeyDB = Map.Map KeyKey KeyData 3086type KeyDB = Map.Map KeyKey KeyData
3145 3087
3146origin :: Packet -> Int -> OriginFlags
3147origin p n = OriginFlags ispub n
3148 where
3149 ispub = case p of
3150 SecretKeyPacket {} -> False
3151 _ -> True
3152
3153mappedPacket :: FilePath -> Packet -> MappedPacket
3154mappedPacket filename p = MappedPacket
3155 { packet = p
3156 , locations = Map.singleton filename (origin p (-1))
3157 }
3158
3159mappedPacketWithHint :: FilePath -> Packet -> Int -> MappedPacket
3160mappedPacketWithHint filename p hint = MappedPacket
3161 { packet = p
3162 , locations = Map.singleton filename (origin p hint)
3163 }
3164
3165uidkey :: Packet -> String 3088uidkey :: Packet -> String
3166uidkey (UserIDPacket str) = str 3089uidkey (UserIDPacket str) = str
3167 3090
diff --git a/lib/Kiki.hs b/lib/Kiki.hs
index 25c98e2..c042540 100644
--- a/lib/Kiki.hs
+++ b/lib/Kiki.hs
@@ -2,9 +2,10 @@
2{-# LANGUAGE OverloadedStrings #-} 2{-# LANGUAGE OverloadedStrings #-}
3module Kiki where 3module Kiki where
4 4
5import Control.Exception
6import Control.Applicative 5import Control.Applicative
7import Control.Arrow 6import Control.Arrow
7import Control.Concurrent
8import Control.Exception
8import Control.Monad 9import Control.Monad
9import Data.ASN1.BinaryEncoding 10import Data.ASN1.BinaryEncoding
10import Data.ASN1.Encoding 11import Data.ASN1.Encoding
@@ -22,6 +23,7 @@ import System.FilePath.Posix
22import System.IO 23import System.IO
23import System.IO.Temp 24import System.IO.Temp
24import System.IO.Error 25import System.IO.Error
26import System.Posix.IO as Posix (createPipe)
25import System.Posix.User 27import System.Posix.User
26import System.Process 28import System.Process
27import System.Posix.Files 29import System.Posix.Files
@@ -37,6 +39,7 @@ import qualified Data.ByteString.Lazy.Char8 as Char8
37import qualified Data.Map.Strict as Map 39import qualified Data.Map.Strict as Map
38import qualified SSHKey as SSH 40import qualified SSHKey as SSH
39 41
42import GnuPGAgent (Query(..))
40import CommandLine 43import CommandLine
41import KeyRing 44import KeyRing
42import DotLock 45import DotLock
@@ -138,9 +141,9 @@ importAndRefresh root cmn = do
138 141
139 let passfd = cap_passfd cmn 142 let passfd = cap_passfd cmn
140 143
141 pwds <- 144 (torgen,pwds) <-
142 if gotsec 145 if gotsec
143 then return [] 146 then return (Generate 0 $ GenRSA $ 1024 `div` 8, [])
144 else do 147 else do
145 {- ssh-keygen to create master key... 148 {- ssh-keygen to create master key...
146 let mkpath = home ++ "/master-key" 149 let mkpath = home ++ "/master-key"
@@ -154,36 +157,47 @@ importAndRefresh root cmn = do
154 HomeSec 157 HomeSec
155 ( encode $ Message [mk { is_subkey = False }] ) 158 ( encode $ Message [mk { is_subkey = False }] )
156 -} 159 -}
157 master_un <- (\k -> k { is_subkey = False }) <$> generateKey (GenRSA $ 4096 `div` 8 ) 160 master_un <- (\k -> MappedPacket (k { is_subkey = False }) Map.empty) <$> generateKey (GenRSA $ 4096 `div` 8 )
161 tor_un <- generateKey (GenRSA $ 1024 `div` 8 )
162 (read_tor,write_tor) <- Posix.createPipe
163 do rs <- writeKeyToFile (streaminfo { typ = PEMFile, access = Sec, spill = KF_Match "tor", fill = KF_All }) (FileDesc write_tor) tor_un
164 -- outputReport $ map (first show) rs
165 return ()
158 let default_cipher = (CAST5 {- AES128 -}, IteratedSaltedS2K SHA1 4073382889203176146 7864320) 166 let default_cipher = (CAST5 {- AES128 -}, IteratedSaltedS2K SHA1 4073382889203176146 7864320)
159 ctx = InputFileContext secring pubring 167 ctx = InputFileContext secring pubring
168 main_passwds = withAgent $ do pfd <- maybeToList passfd
169 return $ PassphraseSpec Nothing Nothing pfd
160 passwordop = KeyRingOperation 170 passwordop = KeyRingOperation
161 { opFiles = Map.empty 171 { opFiles = Map.empty
162 -- TODO: ask agent for new passphrase 172 -- TODO: ask agent for new passphrase
163 , opPassphrases = do pfd <- maybeToList passfd 173 , opPassphrases = main_passwds
164 return $ PassphraseSpec Nothing Nothing pfd
165 , opHome = homespec 174 , opHome = homespec
166 , opTransforms = [] 175 , opTransforms = []
167 } 176 }
168 transcoder <- makeMemoizingDecrypter passwordop ctx Map.empty 177 let uidentry = Map.singleton (keykey $ packet master_un)
169 master0 <- transcoder default_cipher $ MappedPacket master_un Map.empty 178 $ master_un { packet = Query (packet master_un)
179 (torUIDFromKey tor_un)
180 Nothing
181 }
182 transcoder <- makeMemoizingDecrypter passwordop ctx (Just master_un, uidentry)
183 master0 <- transcoder default_cipher master_un
170 case master0 of 184 case master0 of
171 KikiSuccess master -> do 185 KikiSuccess master -> do
172 mkdirFor secring 186 mkdirFor secring
173 writeInputFileL ctx 187 writeInputFileL ctx
174 HomeSec 188 HomeSec
175 $ encode $ Message [master { is_subkey = False}] 189 $ encode $ Message [master]
176 putStrLn "Wrote master key" 190 putStrLn "Wrote master key"
177 return [PassphraseMemoizer transcoder] 191 return (FileDesc read_tor, [PassphraseMemoizer transcoder])
178 er -> do 192 er -> do
179 hPutStrLn stderr ("warning: " ++ errorString er) 193 hPutStrLn stderr ("warning: " ++ errorString er)
180 hPutStrLn stderr "warning: keys will not be encrypted."; 194 hPutStrLn stderr "warning: keys will not be encrypted.";
181 mkdirFor secring 195 mkdirFor secring
182 writeInputFileL ctx 196 writeInputFileL ctx
183 HomeSec 197 HomeSec
184 $ encode $ Message [master_un { is_subkey = False}] 198 $ encode $ Message [packet master_un]
185 putStrLn "Wrote master key" 199 putStrLn "Wrote master key"
186 return [] 200 return (Generate 0 (GenRSA $ 1024 `div` 8 ), [])
187 gotpub <- doesFileExist pubring 201 gotpub <- doesFileExist pubring
188 when (not gotpub) $ do 202 when (not gotpub) $ do
189 mkdirFor pubring 203 mkdirFor pubring
@@ -233,7 +247,14 @@ importAndRefresh root cmn = do
233 { opFiles = Map.fromList $ 247 { opFiles = Map.fromList $
234 [ ( HomeSec, buildStreamInfo KF_All KeyRingFile ) 248 [ ( HomeSec, buildStreamInfo KF_All KeyRingFile )
235 , ( HomePub, (buildStreamInfo KF_All KeyRingFile) { access = Pub } ) 249 , ( HomePub, (buildStreamInfo KF_All KeyRingFile) { access = Pub } )
236 , ( Generate 0 (GenRSA (1024 `div` 8)), strm { spill = KF_Match "tor" }) 250 , ( torgen , case torgen of
251 FileDesc _ -> StreamInfo { typ = PEMFile
252 , fill = KF_Match "tor"
253 , spill = KF_Match "tor"
254 , access = Sec
255 , initializer = NoCreate
256 , transforms = [] }
257 _ -> strm { spill = KF_Match "tor" })
237 , ( Generate 1 (GenRSA (1024 `div` 8)), strm { spill = KF_Match "ipsec" }) 258 , ( Generate 1 (GenRSA (1024 `div` 8)), strm { spill = KF_Match "ipsec" })
238 , ( ArgFile sshcpath, (peminfo 2048 "ssh-client") ) 259 , ( ArgFile sshcpath, (peminfo 2048 "ssh-client") )
239 , ( ArgFile sshspath, (peminfo 2048 "ssh-server") ) 260 , ( ArgFile sshspath, (peminfo 2048 "ssh-server") )
diff --git a/lib/PacketTranscoder.hs b/lib/PacketTranscoder.hs
index 651b00c..07f235c 100644
--- a/lib/PacketTranscoder.hs
+++ b/lib/PacketTranscoder.hs
@@ -1,5 +1,6 @@
1{-# LANGUAGE TupleSections #-} 1{-# LANGUAGE TupleSections #-}
2{-# LANGUAGE OverloadedStrings #-} 2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE PatternGuards #-}
3module PacketTranscoder where 4module PacketTranscoder where
4 5
5import Control.Monad 6import Control.Monad
@@ -11,7 +12,8 @@ import Data.OpenPGP.Util
11import GnuPGAgent 12import GnuPGAgent
12import qualified Data.ByteString as S 13import qualified Data.ByteString as S
13import qualified Data.ByteString.Char8 as S8 14import qualified Data.ByteString.Char8 as S8
14import qualified Data.Map as Map 15import Data.Map as Map (Map)
16import qualified Data.Map as Map
15import qualified Data.Traversable as Traversable 17import qualified Data.Traversable as Traversable
16import System.IO ( stderr) 18import System.IO ( stderr)
17import System.Posix.IO ( fdToHandle ) 19import System.Posix.IO ( fdToHandle )
@@ -92,9 +94,9 @@ cachedContents maybePrompt ctx fd = do
92 94
93 95
94makeMemoizingDecrypter :: KeyRingOperation -> InputFileContext 96makeMemoizingDecrypter :: KeyRingOperation -> InputFileContext
95 -> Map.Map KeyKey (OriginMapped Query) 97 -> (Maybe MappedPacket, Map.Map KeyKey (OriginMapped Query))
96 -> IO PacketTranscoder 98 -> IO PacketTranscoder
97makeMemoizingDecrypter operation ctx keys = do 99makeMemoizingDecrypter operation ctx (workingkey,keys) = do
98 if null chains then do 100 if null chains then do
99 -- (*) Notice we do not pass ctx to resolveForReport. 101 -- (*) Notice we do not pass ctx to resolveForReport.
100 -- This is because the merge function does not currently use a context 102 -- This is because the merge function does not currently use a context
@@ -144,9 +146,14 @@ makeMemoizingDecrypter operation ctx keys = do
144 -> IO (KikiCondition Packet) 146 -> IO (KikiCondition Packet)
145 doDecrypt unkeysRef pws defpw agent_requested (dest_alg,dest_s2k) mp0 = do 147 doDecrypt unkeysRef pws defpw agent_requested (dest_alg,dest_s2k) mp0 = do
146 unkeys <- readIORef unkeysRef 148 unkeys <- readIORef unkeysRef
147 let (mp,qry) = fromMaybe (mp0,Query (packet mp0) "anonymous2" Nothing) $ do 149 let (mp,qry) = fromMaybe (mp0,Query (packet mp0) "anonymous2" Nothing)
148 k <- Map.lookup kk keys 150 $ mplus (do k <- Map.lookup kk keys
149 return (mergeKeyPacket "decrypt" mp0 (fmap queryPacket k), packet k) 151 return (mergeKeyPacket "decrypt" mp0 (fmap queryPacket k), packet k))
152 (do guard $ is_subkey (packet mp0)
153 working <- fmap packet workingkey
154 q <- fmap packet $ Map.lookup (keykey working) keys
155 return (mp0, Query (packet mp0) (queryUID q) (Just working)))
156
150 wk = packet mp0 157 wk = packet mp0
151 kk = keykey wk 158 kk = keykey wk
152 fs = Map.keys $ locations mp 159 fs = Map.keys $ locations mp
@@ -161,39 +168,60 @@ makeMemoizingDecrypter operation ctx keys = do
161 -- in the 'locations' field, so this would effectively 168 -- in the 'locations' field, so this would effectively
162 -- allow you to run 'decryptIt' on an unencrypted public key 169 -- allow you to run 'decryptIt' on an unencrypted public key
163 -- to obtain it's secret key. 170 -- to obtain it's secret key.
164 (pw,wants_retry) <- getpw (if count>1 then AskAgain "Bad pasphrase." else Ask,qry) 171 (pw,wants_retry) <- getpw (count,qry)
165 let wkun = fromMaybe wk $ do 172 let wkun = fromMaybe wk $ do
166 guard $ symmetric_algorithm (packet mp) /= Unencrypted 173 guard $ symmetric_algorithm (packet mp) /= Unencrypted
167 decryptSecretKey pw (packet mp) 174 decryptSecretKey pw (packet mp)
168 175
176 retryOrFail
177 | Just clear <- wants_retry = if count < 4
178 then tries (count+1) getpw recurse
179 else clear >> recurse
180 | otherwise = recurse
181
169 case symmetric_algorithm wkun of 182 case symmetric_algorithm wkun of
170 183
171 Unencrypted -> do 184 Unencrypted -> do
172 writeIORef unkeysRef (Map.insert kk wkun unkeys) 185 writeIORef unkeysRef (Map.insert kk wkun unkeys)
173 ek <- if dest_alg==Unencrypted 186 ek <- case dest_alg of
174 then return $ Just wkun 187 Unencrypted -> return $ Just wkun
175 else encryptSecretKey pw dest_s2k dest_alg wkun 188 _ -> encryptSecretKey pw dest_s2k dest_alg wkun
176 case ek of 189 case ek of
177 Nothing | wants_retry && count<3 -> tries (count+1) getpw recurse 190 Nothing -> retryOrFail
178 Nothing -> recurse 191 Just wken -> return $ KikiSuccess wken
179 Just wken -> return $ KikiSuccess wken
180 192
181 _ -> recurse 193 _ -> retryOrFail
182 194
183 getpws = (map (const . fmap (,False)) $ mapMaybe (`Map.lookup` pws) fs ++ maybeToList defpw) ++ [ agentpw | agent_requested ] 195 getpws = (map (const . fmap (,Nothing)) $ mapMaybe (`Map.lookup` pws) fs ++ maybeToList defpw) ++ [ agentpw | agent_requested ]
184 196
185 -- TODO: First we should try the master key with AskNot. 197 agentpw (count,qry) = do
186 -- If that fails, we should try the subkey.
187 agentpw (ask,qry) = do
188 s <- session 198 s <- session
189 fromMaybe (return ("",False)) $ do 199 fromMaybe (return ("",Nothing)) $ do
190 s <- s 200 s <- s
191 Just $ do 201 Just $ do
192 case ask of AskAgain _ -> clearPassphrase s (queryPacket qry) 202 let (firsttime,maink) | Just k <- (queryMainKey qry) = (2,k)
193 _ -> return () 203 | otherwise = (1,error "bug in makeMemoizingDecrypter")
194 mbpw <- getPassphrase s ask qry 204
205 alg = symmetric_algorithm (queryPacket qry)
206
207 ask | count<firsttime = AskNot
208 | count>firsttime = AskAgain "Bad passphrase"
209 | count==firsttime = initial_ask
210 where
211 initial_ask | Unencrypted <- alg = AskNew
212 | otherwise = AskExisting
213
214 actual_qry | count<firsttime = qry { queryPacket = maink, queryMainKey = Nothing }
215 | otherwise = qry
216
217 let clear | count > firsttime = clearPassphrase s (queryPacket qry)
218 | otherwise = return ()
219 clear
220 let sanitizeQry qry = (fingerprint $ queryPacket qry, queryUID qry, fmap fingerprint $ queryMainKey qry)
221 putStrLn $ "(count,firsttime,ask,qry,actual_qry)="++show (count,firsttime,ask,sanitizeQry qry, sanitizeQry actual_qry)
222 mbpw <- getPassphrase s ask actual_qry
195 quit s 223 quit s
196 return ( maybe "" S8.pack mbpw, True) 224 return ( maybe "" S8.pack mbpw, guard (ask /= AskNew) >> Just clear )
197 225
198 if symmetric_algorithm wk == dest_alg 226 if symmetric_algorithm wk == dest_alg
199 && ( symmetric_algorithm wk == Unencrypted || s2k wk == dest_s2k ) 227 && ( symmetric_algorithm wk == Unencrypted || s2k wk == dest_s2k )
@@ -202,3 +230,66 @@ makeMemoizingDecrypter operation ctx keys = do
202 (return . KikiSuccess) 230 (return . KikiSuccess)
203 $ Map.lookup kk unkeys 231 $ Map.lookup kk unkeys
204 232
233keyQueries :: Maybe String -> Map InputFile (StreamInfo,Message) -> (Maybe MappedPacket, Map KeyKey (OriginMapped Query))
234keyQueries grip ringPackets = (mwk, fmap makeQuery keys)
235 where
236 makeQuery (maink,mp,us) = mp { packet = q }
237 where q = Query { queryPacket = packet mp
238 , queryUID = concat $ take 1 $ Map.keys $ Map.union us (getUIDS maink)
239 , queryMainKey = if is_subkey (packet mp)
240 then maink `mplus` fmap packet mwk
241 else Nothing
242 }
243
244 getUIDS maink = fromMaybe Map.empty $ do
245 k <- maink
246 (_,_,mus) <- Map.lookup (keykey k) keys
247 return mus
248
249 -- | mwk
250 -- first master key matching the provided grip
251 -- (the m is for "MappedPacket", wk for working key)
252 mwk :: Maybe MappedPacket
253 mwk = listToMaybe $ do
254 fp <- maybeToList grip
255 let matchfp mp
256 | not (is_subkey p) && matchpr fp p == fp = Just mp
257 | otherwise = Nothing
258 where p = packet mp
259 Map.elems $ Map.mapMaybe matchfp $ fmap (\(_,p,_) -> p) $ keys
260
261 keys = Map.foldl slurpkeys Map.empty
262 $ Map.mapWithKey filterSecrets ringPackets
263 where
264 filterSecrets :: InputFile -> (a,Message) -> [[MappedPacket]]
265 filterSecrets f (_,Message ps) = keygroups
266 -- filter (isSecretKey . packet) mps
267 where
268 mps = zipWith (mappedPacketWithHint fname) ps [1..]
269 fname = resolveForReport Nothing f -- (Just ctx) f
270 keygroups = dropWhile (not . isSecretKey . packet . head)
271 $ groupBy (const $ not . isSecretKey . packet) mps
272 slurpkeys :: (Map KeyKey (Maybe Packet,MappedPacket,Map String [Packet]))
273 -> [[MappedPacket]]
274 -> (Map KeyKey (Maybe Packet, MappedPacket,Map String [Packet]))
275 slurpkeys m pss = Map.unionWith combineKeyKey m m2
276 where
277
278 m2 :: Map.Map KeyKey (Maybe Packet, MappedPacket, (Map.Map String [Packet]))
279 m2 = Map.fromList
280 $ drop 1
281 $ scanl' build ([],(Nothing,error "bug in PacketTranscoder (1)", error "bug in PacketTranscoder (2)")) pss
282 where
283 build (_,(main0,_,_)) ps = (kk,(main,kp,uidmap ps'))
284 where
285 main | is_subkey (packet kp) = main0
286 | otherwise = Just $ packet kp
287 (kpkt,ps') = splitAt 1 ps
288 kp = head kpkt
289 kk = keykey . packet $ kp
290 combineKeyKey (master1,mp,um) (master2,mp2,um2) = (master1 `mplus` master2,mp,Map.unionWith (++) um um2)
291 uidmap ps = um2
292 where
293 ugs = dropWhile (not . isUserID . packet .head) $ groupBy (const $ not . isUserID . packet) ps
294 um2 = Map.fromList
295 $ map (\(MappedPacket (UserIDPacket s) _:sigs)->(s,takeWhile isSignaturePacket $ map packet sigs)) ugs
diff --git a/lib/Types.hs b/lib/Types.hs
index 9aa0340..767ee98 100644
--- a/lib/Types.hs
+++ b/lib/Types.hs
@@ -1,12 +1,13 @@
1{-# LANGUAGE DeriveFunctor #-} 1{-# LANGUAGE DeriveFunctor #-}
2module Types where 2module Types where
3 3
4import Data.Map as Map (Map) 4import Data.Map as Map (Map)
5import qualified Data.Map as Map
5import Data.OpenPGP 6import Data.OpenPGP
6import Data.OpenPGP.Util 7import Data.OpenPGP.Util
7import FunctorToMaybe 8import FunctorToMaybe
8import qualified System.Posix.Types as Posix
9import qualified Data.ByteString.Lazy as L 9import qualified Data.ByteString.Lazy as L
10import qualified System.Posix.Types as Posix
10 11
11-- | This type describes an idempotent transformation (merge or import) on a 12-- | This type describes an idempotent transformation (merge or import) on a
12-- set of GnuPG keyrings and other key files. 13-- set of GnuPG keyrings and other key files.
@@ -199,6 +200,26 @@ data OriginMapped a = MappedPacket
199instance Functor OriginMapped where 200instance Functor OriginMapped where
200 fmap f (MappedPacket x ls) = MappedPacket (f x) ls 201 fmap f (MappedPacket x ls) = MappedPacket (f x) ls
201 202
203origin :: Packet -> Int -> OriginFlags
204origin p n = OriginFlags ispub n
205 where
206 ispub = case p of
207 SecretKeyPacket {} -> False
208 _ -> True
209
210mappedPacket :: FilePath -> Packet -> MappedPacket
211mappedPacket filename p = MappedPacket
212 { packet = p
213 , locations = Map.singleton filename (origin p (-1))
214 }
215
216mappedPacketWithHint :: FilePath -> Packet -> Int -> MappedPacket
217mappedPacketWithHint filename p hint = MappedPacket
218 { packet = p
219 , locations = Map.singleton filename (origin p hint)
220 }
221
222
202-- | This type is used to indicate success or failure 223-- | This type is used to indicate success or failure
203-- and in the case of success, return the computed object. 224-- and in the case of success, return the computed object.
204-- The 'FunctorToMaybe' class is implemented to facilitate 225-- The 'FunctorToMaybe' class is implemented to facilitate
@@ -252,6 +273,11 @@ isKey (PublicKeyPacket {}) = True
252isKey (SecretKeyPacket {}) = True 273isKey (SecretKeyPacket {}) = True
253isKey _ = False 274isKey _ = False
254 275
276isSecretKey :: Packet -> Bool
277isSecretKey (SecretKeyPacket {}) = True
278isSecretKey _ = False
279
280
255isUserID :: Packet -> Bool 281isUserID :: Packet -> Bool
256isUserID (UserIDPacket {}) = True 282isUserID (UserIDPacket {}) = True
257isUserID _ = False 283isUserID _ = False
@@ -260,4 +286,12 @@ isTrust :: Packet -> Bool
260isTrust (TrustPacket {}) = True 286isTrust (TrustPacket {}) = True
261isTrust _ = False 287isTrust _ = False
262 288
289-- matchpr computes the fingerprint of the given key truncated to
290-- be the same lenght as the given fingerprint for comparison.
291--
292-- matchpr fp = Data.List.Extra.takeEnd (length fp)
293--
294matchpr :: String -> Packet -> String
295matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp
296
263 297