{-# LANGUAGE DoAndIfThenElse #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE CPP #-} module Main where import Debug.Trace import GHC.Exts (Down(..)) import GHC.IO.Exception ( ioException, IOErrorType(..) ) import Data.IORef import Data.Tuple import Data.Binary import Data.OpenPGP as OpenPGP import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy.Char8 as Char8 import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as S8 import Control.Monad import qualified Text.Show.Pretty as PP import Text.PrettyPrint as PP hiding ((<>)) import Data.List import Data.OpenPGP.Util (verify,fingerprint,decryptSecretKey,pgpSign) import Data.Ord import Data.Maybe import Data.Bits import qualified Data.Text as T import Data.Text.Encoding import qualified Codec.Binary.Base32 as Base32 import qualified Codec.Binary.Base64 as Base64 import qualified Crypto.Hash.SHA1 as SHA1 import qualified Crypto.Hash.SHA256 as SHA256 import qualified Crypto.Hash.RIPEMD160 as RIPEMD160 import qualified Crypto.Types.PubKey.ECC as ECC -- import qualified Crypto.Types.PubKey.ECDSA as ECDSA -- import qualified Crypto.PubKey.ECC.ECDSA as ECDSA import Data.Char (toLower) import qualified Crypto.PubKey.RSA as RSA -- import Crypto.Random (newGenIO,SystemRandom) import Data.ASN1.Types import Data.ASN1.Encoding import Data.ASN1.BinaryEncoding import Data.ASN1.BitArray import qualified Data.Foldable as Foldable import qualified Data.Sequence as Sequence import Control.Applicative import System.Environment import System.Directory import System.FilePath import System.Exit import System.Process import System.Posix.IO (fdToHandle,fdRead) import System.Posix.Files import System.Posix.Signals import System.Posix.Types (EpochTime) import System.Process.Internals (runGenProcess_,defaultSignal) import System.IO (hPutStrLn,stderr,withFile,IOMode(..)) import System.IO.Error import ControlMaybe import Data.Char import Control.Arrow (first,second) import Data.Traversable hiding (mapM,forM,sequence) import qualified Data.Traversable as Traversable (mapM,forM,sequence) -- import System.Console.CmdArgs -- import System.Posix.Time import Data.Time.Clock.POSIX import Data.Monoid ((<>)) -- import Data.X509 import qualified Data.Map as Map import DotLock -- import Codec.Crypto.ECC.Base -- hecc package import Text.Printf import qualified CryptoCoins as CryptoCoins import qualified Hosts import Network.Socket -- (SockAddr) import LengthPrefixedBE import Data.Binary.Put (putWord32be,runPut,putByteString) import Data.Binary.Get (runGet) import KeyRing -- instance Default S.ByteString where def = S.empty -- DER-encoded elliptic curve ids nistp256_id = 0x2a8648ce3d030107 secp256k1_id = 0x2b8104000a warn str = hPutStrLn stderr str unprefix c spec = if null (snd p) then swap p else (fst p, tail (snd p)) where p = break (==c) spec {- RSAPrivateKey ::= SEQUENCE { version Version, modulus INTEGER, -- n publicExponent INTEGER, -- e privateExponent INTEGER, -- d prime1 INTEGER, -- p prime2 INTEGER, -- q exponent1 INTEGER, -- d mod (p1) -- ?? d mod (p-1) exponent2 INTEGER, -- d mod (q-1) coefficient INTEGER, -- (inverse of q) mod p otherPrimeInfos OtherPrimeInfos OPTIONAL } -} sshrsa :: Integer -> Integer -> Char8.ByteString sshrsa e n = runPut $ do putWord32be 7 putByteString "ssh-rsa" put (LengthPrefixedBE e) put (LengthPrefixedBE n) decode_sshrsa :: Char8.ByteString -> Maybe RSAPublicKey decode_sshrsa bs = do let (pre,bs1) = Char8.splitAt 11 bs guard $ pre == runPut (putWord32be 7 >> putByteString "ssh-rsa") let rsakey = flip runGet bs1 $ do LengthPrefixedBE e <- get LengthPrefixedBE n <- get return $ RSAKey (MPI n) (MPI e) return rsakey rsaPrivateKeyFromPacket :: Packet -> Maybe RSAPrivateKey rsaPrivateKeyFromPacket pkt@(SecretKeyPacket {}) = do -- public fields... n <- lookup 'n' $ key pkt e <- lookup 'e' $ key pkt -- secret fields MPI d <- lookup 'd' $ key pkt MPI q <- lookup 'p' $ key pkt -- Note: p & q swapped MPI p <- lookup 'q' $ key pkt -- Note: p & q swapped -- Note: Here we fail if 'u' key is missing. -- Ideally, it would be better to compute (inverse q) mod p -- see Algebra.Structures.EuclideanDomain.extendedEuclidAlg -- (package constructive-algebra) coefficient <- lookup 'u' $ key pkt let dmodp1 = MPI $ d `mod` (p - 1) dmodqminus1 = MPI $ d `mod` (q - 1) return $ RSAPrivateKey { rsaN = n , rsaE = e , rsaD = MPI d , rsaP = MPI p , rsaQ = MPI q , rsaDmodP1 = dmodp1 , rsaDmodQminus1 = dmodqminus1 , rsaCoefficient = coefficient } rsaPrivateKeyFromPacket _ = Nothing {- getPackets :: IO [Packet] getPackets = do input <- L.getContents case decodeOrFail input of Right (_,_,Message pkts) -> return pkts Left (_,_,_) -> return [] -} writePEM typ dta = pem where pem = unlines . concat $ [ ["-----BEGIN " <> typ <> "-----"] , split64s dta , ["-----END " <> typ <> "-----"] ] split64s "" = [] split64s dta = line : split64s rest where (line,rest) = splitAt 64 dta -- 64 byte lines isEmbeddedSignature (EmbeddedSignaturePacket {}) = True isEmbeddedSignature _ = False isCertificationSig (CertificationSignature {}) = True isCertificationSig _ = True issuer (IssuerPacket issuer) = Just issuer issuer _ = Nothing backsig (EmbeddedSignaturePacket s) = Just s backsig _ = Nothing isSubkeySignature (SubkeySignature {}) = True isSubkeySignature _ = False isPublicMaster k@(PublicKeyPacket {}) = not $ is_subkey k isPublicMaster _ = False now = floor <$> Data.Time.Clock.POSIX.getPOSIXTime verifyBindings keys nonkeys = (top ++ filter isSubkeySignature embedded,othersigs) where verified = do sig <- signatures (Message nonkeys) let v = verify (Message keys) sig guard (not . null $ signatures_over v) return v (top,othersigs) = partition isSubkeySignature verified embedded = do sub <- top let sigover = signatures_over sub unhashed = sigover >>= unhashed_subpackets subsigs = mapMaybe backsig unhashed -- This should consist only of 0x19 values -- subtypes = map signature_type subsigs -- trace ("subtypes = "++show subtypes) (return ()) -- trace ("issuers: "++show (map signature_issuer subsigs)) (return ()) sig <- signatures (Message ([topkey sub,subkey sub]++subsigs)) let v = verify (Message [subkey sub]) sig guard (not . null $ signatures_over v) return v grip k = drop 32 $ fingerprint k smallpr k = drop 24 $ fingerprint k disjoint_fp ks = {- concatMap group2 $ -} transpose grouped where grouped = groupBy samepr . sortBy (comparing smallpr) $ ks samepr a b = smallpr a == smallpr b {- -- useful for testing group2 :: [a] -> [[a]] group2 (x:y:ys) = [x,y]:group2 ys group2 [x] = [[x]] group2 [] = [] -} verifyBindingsEx :: [Packet] -> ([SignatureOver], [SignatureOver]) -- ^ (binding signatures, other signatures) verifyBindingsEx pkts = bicat . unzip $ do let (keys,_) = partition isKey pkts keys <- disjoint_fp keys return $ verifyBindings keys pkts where bicat (xs,ys) = (concat xs,concat ys) getBindings :: [Packet] -> ( [([Packet],[SignatureOver])] -- ^ other signatures with key sets -- that were used for the verifications , [(Word8, (Packet, Packet), -- (topkey,subkey) [String], -- usage flags [SignatureSubpacket], -- hashed data [Packet])] -- ^ binding signatures ) getBindings pkts = (sigs,bindings) where (sigs,concat->bindings) = unzip $ do let (keys,_) = partition isKey pkts keys <- disjoint_fp keys let (bs,sigs) = verifyBindings keys pkts return . ((keys,sigs),) $ do b <- bs -- trace ("sigs = "++show (map (map signature_issuer . signatures_over) sigs)) bs i <- map signature_issuer (signatures_over b) i <- maybeToList i who <- maybeToList $ find_key fingerprint (Message keys) i let (code,claimants) = case () of _ | who == topkey b -> (1,[]) _ | who == subkey b -> (2,[]) _ -> (0,[who]) let hashed = signatures_over b >>= hashed_subpackets kind = guard (code==1) >> hashed >>= maybeToList . usage return (code,(topkey b,subkey b), kind, hashed,claimants) -- Returned data is simmilar to getBindings but the Word8 codes -- are ORed together. accBindings :: Bits t => [(t, (Packet, Packet), [a], [a1], [a2])] -> [(t, (Packet, Packet), [a], [a1], [a2])] accBindings bs = as where gs = groupBy samePair . sortBy (comparing bindingPair) $ bs as = map (foldl1 combine) gs bindingPair (_,p,_,_,_) = pub2 p where pub2 (a,b) = (pub a, pub b) pub a = fingerprint_material a samePair a b = bindingPair a == bindingPair b combine (ac,p,akind,ahashed,aclaimaints) (bc,_,bkind,bhashed,bclaimaints) = (ac .|. bc,p,akind++bkind,ahashed++bhashed,aclaimaints++bclaimaints) fpmatch grip key = (==) Nothing (fmap (backend (fingerprint key)) grip >>= guard . not) where backend xs ys = and $ zipWith (==) (reverse xs) (reverse ys) listKeys pkts = listKeysFiltered [] pkts ecc_curve k = printf "%x" num :: String where unmpi (MPI num) = num num = maybe 0 unmpi $ lookup 'c' (key k) listKeysFiltered grips pkts = do let (certs,bs) = getBindings pkts as = accBindings bs defaultkind (k:_) hs = k defaultkind [] hs = maybe "subkey" id ( listToMaybe . mapMaybe (fmap usageString . keyflags) $ hs) kinds = map (\(_,_,k,h,_)->defaultkind k h) as kindwidth = maximum $ map length kinds kindcol = min 20 kindwidth code (c,(m,s),_,_,_) = (fingerprint_material m,-c) ownerkey (_,(a,_),_,_,_) = a sameMaster (ownerkey->a) (ownerkey->b) = fingerprint_material a==fingerprint_material b matchgrip _ | null grips = True matchgrip ((code,(top,sub), kind, hashed,claimants):_) | any (flip fpmatch top . Just) grips = True matchgrip _ = False gs = filter matchgrip $ groupBy sameMaster (sortBy (comparing code) as) showsigs claimants = map (\k -> " " ++ "^ signed: " ++ fingerprint k) claimants subs@((_,(top,_),_,_,_):_) <- gs let subkeys = do (code,(top,sub), kind, hashed,claimants) <- subs let ar = case code of 0 -> " ??? " 1 -> " --> " 2 -> " <-- " 3 -> " <-> " formkind = take kindcol $ defaultkind kind hashed ++ repeat ' ' -- torhash = maybe "" id $ derToBase32 <$> derRSA sub (netid,kind') = maybe (0x0,"bitcoin") (\n->(CryptoCoins.publicByteFromName n,n)) $ listToMaybe kind unlines $ concat [ " " -- , grip top , ar , formkind , " " , fingerprint sub -- , " " ++ torhash -- , " " ++ (concatMap (printf "%02X") $ S.unpack (ecc_curve sub)) ] -- ++ ppShow hashed : if isCryptoCoinKey sub -- then (" " ++ "B⃦ " ++ bitcoinAddress sub) : showsigs claimants -- then (" " ++ "BTC " ++ bitcoinAddress sub) : showsigs claimants then (" " ++ "¢ "++kind'++":" ++ bitcoinAddress netid sub) : showsigs claimants else showsigs claimants torkeys = do (code,(top,sub), kind, hashed,claimants) <- subs guard ("tor" `elem` kind) guard (code .&. 0x2 /= 0) maybeToList $ derToBase32 <$> derRSA sub uid = {- maybe "" id . listToMaybe $ -} do (keys,sigs) <- certs sig <- sigs guard (isCertificationSig sig) guard (topkey sig == top) let issuers = do sig_over <- signatures_over sig i <- maybeToList $ signature_issuer sig_over maybeToList $ find_key (matchpr i) (Message keys) (reverse (take 16 (reverse i))) (primary,secondary) = partition (==top) issuers -- trace ("PRIMARY: "++show (map fingerprint primary)) $ return () -- trace ("SECONDARY: "++show (map fingerprint secondary)) $ return () guard (not (null primary)) let UserIDPacket uid = user_id sig parsed = parseUID uid ar = maybe " --> " (const " <-> ") $ do guard (uid_topdomain parsed == "onion" ) guard ( uid_realname parsed `elem` ["","Anonymous"]) guard ( uid_user parsed == "root" ) let subdom0 = L.fromChunks [encodeUtf8 (uid_subdomain parsed)] len = L.length subdom0 subdom = Char8.unpack subdom0 match = ( (==subdom) . take (fromIntegral len)) guard (len >= 16) listToMaybe $ filter match torkeys unlines $ [ " " ++ ar ++ "@" ++ " " ++ uid_full parsed ] ++ showsigs secondary -- (_,sigs) = unzip certs "master-key " ++ fingerprint top ++ "\n" ++ uid ++" ...\n" ++ subkeys ++ "\n" modifyUID (UserIDPacket str) = UserIDPacket str' where (fstname,rst) = break (==' ') str str' = mod fstname ++ rst mod "Bob" = "Bob Fucking" mod x = x modifyUID other = other todo = error "unimplemented" #if MIN_VERSION_base(4,6,0) #else lookupEnv var = handleIO_ (return Nothing) $ fmap Just (getEnv var) #endif unmaybe def = fmap (maybe def id) expandPath path (c:cs) | c/='/' = path ++ "/" ++ (c:cs) | otherwise = c:cs expandPath path [] = [] -- type TimeStamp = Word32 -- | Attempts to lock each file in the list. -- Returns a list of locks and a list of filenames -- that could not be locked. lockFiles :: [FilePath] -> IO ( [(DotLock,FilePath)], [FilePath] ) lockFiles fs = do ls <- forM fs $ \f -> do lk <- dotlock_create f 0 v <- flip (maybe $ return Nothing) lk $ \lk -> do e <- dotlock_take lk (-1) return $ if e==0 then Just lk else Nothing return (v,f) let (lks, fails) = partition (isJust . fst) ls return (map (\(lk,f)->(fromJust lk,f)) lks, map snd fails) unlockFiles lks = forM_ lks $ \(lk,f) -> do -- warn $ "unlocking "++show f dotlock_release lk {- options_from_file :: (forall a. [String] -> Term a -> IO (Either EvalExit a)) -> Term b -> (String,String,Term (Maybe String)) -> ([String],Term (Maybe String)) -> IO [String] options_from_file unwrapCmd term (homevar,appdir,home) (optfile_alts,options_file) = doit where homedir = envhomedir <$> home envhomedir opt = do gnupghome <- lookupEnv homevar >>= \d -> return $ d >>= guard . (/="") >> d home <- flip fmap getHomeDirectory $ \d -> fmap (const d) $ guard (d/="") let homegnupg = (++('/':appdir)) <$> home let val = (opt `mplus` gnupghome `mplus` homegnupg) return $ val doit = do args <- getArgs {- let wants_help = not . null $ filter cryForHelp args where cryForHelp "--help" = True cryForHelp "--version" = True cryForHelp x = and (zipWith (==) x "--help=") -} (o,h) <- do val <- unwrapCmd args (liftA2 (,) options_file homedir) case val of Left e -> return (Nothing,Nothing) Right (o,h) -> (o,) <$> h ofile <- fmap listToMaybe . flip (maybe (return [])) h $ \h -> let optfiles = map (second ((h++"/")++)) (maybe optfile_alts' (:[]) o') optfile_alts' = zip (False:repeat True) optfile_alts o' = fmap (False,) o in filterM (doesFileExist . snd) optfiles args <- flip (maybe $ return args) ofile $ \(forgive,fname) -> do let h' = fromJust h newargs <- (:) <$> pure ("homedir "++h') <*> parseOptionFile fname let toArgs = toHead ("--"++) . words toHead f (x:xs) = f x : xs toHead f [] = [] voidTerm = fmap (const ()) appendArgs as [] = return as appendArgs as (configline:cs) = do let xs = toArgs configline w <-unwrap (xs++as) (voidTerm term,defTI) case w of Left _ -> appendArgs as cs Right _ -> appendArgs (xs++as) cs -- TODO: check errors if forgive = False appendArgs args newargs return args runWithOptionsFile :: (Term (IO b), TermInfo) -> IO b runWithOptionsFile (term,ti) = do as <- options_from_file unwrapCmd term ("GNUPGHOME",".gnupg",opt_homedir) (["keys.conf","gpg.conf-2","gpg.conf"] ,opt_options) q <- eval as (term , ti) q where unwrapCmd args term = unwrap args (term,defTI) runChoiceWithOptionsFile :: (Term (IO b), TermInfo) -> [(Term (IO b), TermInfo)] -> IO b runChoiceWithOptionsFile (realterm,ti) choices = do as <- options_from_file unwrapCmd realterm ("GNUPGHOME",".gnupg",opt_homedir) (["keys.conf","gpg.conf-2","gpg.conf"] ,opt_options) -- putStrLn $ "as = " ++ show as q <- evalChoice as (realterm , ti) choices q where unwrapCmd args t = unwrapChoice args (realterm <:> t,ti) (map (neuter t) choices) neuter term (t,ti) = (t <:> term, ti) data Command = List | Autosign deriving (Eq,Show,Read,Enum) capitolizeFirstLetter (x:xs) = toUpper x : xs capitolizeFirstLetter xs = xs instance ArgVal Command where converter = ( maybe (Left $ text "unknown command") Right . fmap fst . listToMaybe . reads . capitolizeFirstLetter . map toLower , text . map toLower . show ) class AutoMaybe a instance AutoMaybe Command instance (ArgVal a, AutoMaybe a) => ArgVal (Maybe a) where converter = ( toRight Just . fst converter , maybe (text "(unspecified)") id . fmap (snd converter) ) toRight f (Right x) = Right (f x) toRight f (Left y) = Left y cmd :: Term Command cmd = required . pos 0 Nothing $ posInfo { posName = "command" , posDoc = "What action to perform." } a <:> b = flip const <$> a <*> b infixr 2 <:> selectAction cmd actions = actions !! fromEnum cmd cmdInfo :: ArgVal cmd => cmd -> String -> Term a -> (cmd, (Term a, TermInfo)) cmdInfo cmd doc action = ( cmd , ( action , defTI { termName = print cmd , termDoc = doc } ) ) where print = show . snd converter cmdlist :: (Command, (Term (IO ()), TermInfo)) cmdlist = cmdInfo List "list key pairs for which secrets are known" $ (>>= putStrLn . listKeys . unMessage) <$> secret_packets where unMessage (Message pkts) = pkts cmdautosign = cmdInfo Autosign "auto-sign tor-style uids" $ pure (putStrLn "autosign") multiCommand :: TermInfo -> [(Command, (Term a, TermInfo))] -> ( (Term a, TermInfo) , [(Term a, TermInfo)] ) multiCommand ti choices = ( ( selectAction <$> cmd <*> sequenceA (map strip choices) , ti ) , map snd choices ) where selectAction cmd choices = fromJust $ lookup (cmd::Command) choices strip (cmd,(action,_)) = fmap (cmd,) action -} trimCR bs = fst $ S.spanEnd (\x -> x==10 || x==13) bs guessKeyFormat 'P' "ssh-client" = "SSH" guessKeyFormat 'S' "ssh-client" = "PEM" guessKeyFormat 'S' "ssh-host" = "PEM" guessKeyFormat _ _ = "PEM" -- "PGP" writeKeyToFile False "PEM" fname packet = case key_algorithm packet of RSA -> do flip (maybe (return ())) (rsaPrivateKeyFromPacket packet) -- RSAPrivateKey $ \rsa -> do let asn1 = toASN1 rsa [] bs = encodeASN1 DER asn1 dta = Base64.encode (L.unpack bs) output = writePEM "RSA PRIVATE KEY" dta stamp = toEnum . fromEnum $ timestamp packet createDirectoryIfMissing True (takeDirectory fname) handleIO_ (warn $ fname ++ ": write failure") $ do saved_mask <- setFileCreationMask 0o077 writeFile fname output -- Note: The key's timestamp is included in it's fingerprint. -- Therefore, we should attempt to preserve it. setFileTimes fname stamp stamp setFileCreationMask saved_mask return () warn $ fname ++ ": exported" return () algo -> warn $ fname ++ ": unable to export "++show algo++" key "++fingerprint packet readPublicKey :: Char8.ByteString -> RSAPublicKey readPublicKey bs = maybe er id $ do let (pre,bs1) = Char8.splitAt 7 bs guard $ pre == "ssh-rsa" let (sp,bs2) = Char8.span isSpace bs1 guard $ not (Char8.null sp) bs3 <- listToMaybe $ Char8.words bs2 qq <- L.pack `fmap` Base64.decode (Char8.unpack bs3) decode_sshrsa qq where er = error "Unsupported key format" {- getPassphrase cmd = case passphrase_fd cmd of Just fd -> do pwh <- fdToHandle (toEnum fd) fmap trimCR $ S.hGetContents pwh Nothing -> return "" -} #define HOMEOPTION (def &= explicit &= name "homedir" &= typDir ) is40digitHex xs = ys == xs && length ys==40 where ys = filter ishex xs ishex c | '0' <= c && c <= '9' = True | 'A' <= c && c <= 'F' = True | 'a' <= c && c <= 'f' = True ishex c = False flattenKeys :: Bool -> KeyDB -> Message flattenKeys isPublic db = Message $ concatMap (map packet . flattenTop "" isPublic . snd) (prefilter . Map.assocs $ db) where prefilter = if isPublic then id else filter isSecret where isSecret (_,(KeyData (MappedPacket { packet=(SecretKeyPacket {})}) _ _ _)) = True isSecret _ = False writeOutKeyrings :: Map.Map FilePath t -> KeyDB -> IO () writeOutKeyrings lkmap db = do let ks = Map.elems db fs = Map.keys (foldr unionfiles Map.empty ks) where unionfiles (KeyData p _ _ _) m = Map.union m (locations p) fromfile f (KeyData p _ _ _) = Map.member f $ locations p let s = do f <- fs let x = do d@(KeyData p _ _ _) <- filter (fromfile f) (sortByHint f keyMappedPacket ks) n <- maybeToList $ Map.lookup f (locations p) flattenTop f (originallyPublic n) d changes = filter isnew x where isnew p = isNothing (Map.lookup f $ locations p) {- trace (unlines $ ( (f ++ ":") : map (showPacket . packet) x) ++ ( "CHANGES: " : map ((" "++) . showPacket . packet) changes)) $ do -} return (f,(changes,x)) towrites <- fmap catMaybes $ Control.Monad.forM s $ \(f,(changes,x)) -> do let noop = return Nothing write f = return (Just f) case changes of [] -> noop -- warn (f ++": nothing to do.") >> noop cs -> case Map.lookup f lkmap of Just lk -> do forM_ cs $ \c -> warn $ f++": new "++showPacket (packet c) write (f,lk,x) Nothing -> do forM_ cs $ \c -> warn $ f++": missing "++showPacket (packet c) noop forM_ towrites $ \(f,lk,x) -> do let m = Message $ map packet x -- warn $ "writing "++f L.writeFile f (encode m) setHome spec kd = kd { homeSpec = spec , kaction = const $ runKeyRing kd } cross_merge doDecrypt keyrings wallets kd f = do let it = kd { filesToLock = HomeSec:HomePub:map ArgFile keyrings , keyringFiles = keyrings , walletFiles = wallets , kaction = go doDecrypt f } runKeyRing it where go doDecrypt f rt = do let readp n = fmap (n,) (readPacketsFromFile n) readw wk n = fmap (n,) (readPacketsFromWallet wk n) let pass = do ms <- mapM readp (rtRings rt) let grip = rtGrip rt `mplus` (fingerprint <$> fstkey) where fstkey = listToMaybe $ mapMaybe isSecringKey ms where isSecringKey (fn,Message ps) | fn== rtSecring rt = listToMaybe ps isSecringKey _ = Nothing wk = listToMaybe $ do fp <- maybeToList grip elm <- Map.toList db0 guard $ matchSpec (KeyGrip fp) elm return $ keyPacket (snd elm) db0 = foldl' (uncurry . merge) Map.empty ms wms <- mapM (readw wk) (rtWallets rt) let ts = do maybeToList wk (fname,xs) <- wms (_,sub,(_,m)) <- xs (tag,top) <- Map.toList m return (top,fname,sub,tag) importWalletKey db' (top,fname,sub,tag) = do doImportG doDecrypt db' (fmap keykey $ maybeToList wk) tag fname sub db <- foldM importWalletKey db0 ts let cs = do wk <- maybeToList wk let kk = keykey wk KeyData top topsigs uids subs <- maybeToList $ Map.lookup kk db (subkk,SubKey mp sigs) <- Map.toList subs let sub = packet mp guard $ isCryptoCoinKey sub tag <- take 1 $ mapMaybe getCryptoCoinTag (map (packet . fst) sigs) return (tag,mp) -- export wallet keys forM_ (rtWallets rt) $ \n -> do let cs' = do (nw,mp) <- cs -- let fns = Map.keys (locations mp) -- trace ("COIN KEY: "++show fns) $ return () guard . not $ Map.member n (locations mp) let wip = walletImportFormat (CryptoCoins.private_byte_id nw) (packet mp) return (CryptoCoins.network_name nw,wip) handleIO_ (return ()) $ do withFile n AppendMode $ \fh -> do forM_ cs' $ \(net,wip) -> do warn $ n++": new WalletKey "++net hPutStrLn fh wip db' <- f (rtSecring rt,grip) db (rtPubring rt) return (rtRings rt,db') (fsns,db) <- pass let lkmap = Map.fromList $ map (,()) fsns writeOutKeyrings lkmap db toLast f [] = [] toLast f [x] = [f x] toLast f (x:xs) = x : toLast f xs partitionStaticArguments specs args = psa args where smap = Map.fromList specs psa [] = ([],[]) psa (a:as) = case Map.lookup a smap of Nothing -> second (a:) $ psa as Just n -> first ((a:take n as):) $ psa (drop n as) show_wk secring_file grip db = do let sec_db = Map.filter gripmatch db gripmatch (KeyData p _ _ _) = Map.member secring_file (locations p) Message sec = flattenKeys False sec_db putStrLn $ listKeysFiltered (maybeToList grip) sec show_all db = do let Message packets = flattenKeys True db putStrLn $ listKeys packets show_whose_key input_key db = do flip (maybe $ return ()) input_key $ \input_key -> do let ks = whoseKey input_key db case ks of [KeyData k _ uids _] -> do putStrLn $ fingerprint (packet k) mapM_ putStrLn $ Map.keys uids (_:_) -> error "ambiguous" [] -> return () show_pem keyspec wkgrip db = do let s = parseSpec wkgrip keyspec flip (maybe $ warn (keyspec ++ ": not found") >> return ()) (selectPublicKey s db) $ \k -> do let rsa = pkcs8 . fromJust $ rsaKeyFromPacket k der = encodeASN1 DER (toASN1 rsa []) qq = Base64.encode (L.unpack der) putStrLn $ writePEM "PUBLIC KEY" qq -- ("TODO "++show keyspec) show_ssh keyspec wkgrip db = do let s = parseSpec wkgrip keyspec flip (maybe $ warn (keyspec ++ ": not found") >> return ()) (selectPublicKey s db) $ \k -> do let Just (RSAKey (MPI n) (MPI e)) = rsaKeyFromPacket k bs = sshrsa e n blob = Base64.encode (L.unpack bs) putStrLn $ "ssh-rsa " ++ blob show_key keyspec wkgrip db = do let s = parseSpec "" keyspec let ps = do (_,k) <- filterMatches (fst s) (Map.toList db) mp <- flattenTop "" True k return $ packet mp -- putStrLn $ "show key " ++ show s putStrLn $ listKeys ps show_wip keyspec wkgrip db = do let s = parseSpec wkgrip keyspec flip (maybe $ warn (keyspec ++ ": not found") >> return ()) (selectSecretKey s db) $ \k -> do let nwb = maybe 0x80 CryptoCoins.secretByteFromName $ snd s putStrLn $ walletImportFormat nwb k parseSpec :: String -> String -> (KeySpec,Maybe String) parseSpec grip spec = (topspec,subspec) where (topspec0,subspec0) = unprefix '/' spec (toptyp,top) = unprefix ':' topspec0 (subtyp,sub) = unprefix ':' subspec0 topspec = case () of _ | null top && or [ subtyp=="fp" , null subtyp && is40digitHex sub ] -> KeyGrip sub _ | null top && null grip -> KeyUidMatch sub _ | null top -> KeyGrip grip _ | toptyp=="fp" || (null toptyp && is40digitHex top) -> {- trace "using top" $ -} KeyGrip top _ | toptyp=="u" -> KeyUidMatch top _ | otherwise -> KeyUidMatch top subspec = case subtyp of "t" -> Just sub "fp" | top=="" -> Nothing "" | top=="" && is40digitHex sub -> Nothing "" -> listToMaybe sub >> Just sub -- | systemEnv -- This is like System.Process.system except that it lets you set -- some environment variables. systemEnv _ "" = ioException (ioeSetErrorString (mkIOError InvalidArgument "system" Nothing Nothing) "null command") systemEnv vars cmd = do env0 <- getEnvironment let env1 = filter (isNothing . flip lookup vars . fst) env0 env = vars ++ env1 syncProcess "system" $ (shell cmd) {env=Just env} where -- This is a non-exported function from System.Process syncProcess fun c = do -- The POSIX version of system needs to do some manipulation of signal -- handlers. Since we're going to be synchronously waiting for the child, -- we want to ignore ^C in the parent, but handle it the default way -- in the child (using SIG_DFL isn't really correct, it should be the -- original signal handler, but the GHC RTS will have already set up -- its own handler and we don't want to use that). old_int <- installHandler sigINT Ignore Nothing old_quit <- installHandler sigQUIT Ignore Nothing (_,_,_,p) <- runGenProcess_ fun c (Just defaultSignal) (Just defaultSignal) r <- waitForProcess p _ <- installHandler sigINT old_int Nothing _ <- installHandler sigQUIT old_quit Nothing return r doExport doDecrypt (db,use_db) (fname,subspec,ms,cmd) = case ms of [_] -> export (_:_) -> ambiguous [] -> shcmd where ambiguous = error "Key specification is ambiguous." shcmd = do let noop warning = do warn warning return (db,use_db) if null cmd then noop (fname ++ ": missing.") else do let vars = [ ("file",fname) , ("usage",maybe "" id subspec) ] e <- systemEnv vars cmd case e of ExitFailure num -> noop $ fname ++ ": failed external (code="++show num++")" ExitSuccess -> do warn $ fname ++ ": generated" db' <- doImport doDecrypt db (fname,subspec,ms,cmd) return (db', use_db) export = do let [kk] = ms Just (KeyData key _ _ subkeys) = Map.lookup kk use_db p = flip (maybe (Just $ packet key)) subspec $ \tag -> do let subs = Map.elems subkeys doSearch (SubKey sub_mp sigtrusts) = let (_,v,_) = findTag tag (packet key) (packet sub_mp) sigtrusts in fmap fst v==Just True case filter doSearch subs of [SubKey mp _] -> Just $ packet mp [] -> Nothing _ -> ambiguous flip (maybe shcmd) p $ \p -> do pun <- doDecrypt p flip (maybe $ error "Bad passphrase?") pun $ \pun -> do writeKeyToFile False "PEM" fname pun return (db,use_db) {- applyCurve curve x = x*x*x + x*a + b where (a,b)=(geta curve,getb curve) secp256k1_oid = [1,3,132,0,10] secp256k1_curve = ECi l a b p r where -- y² = x³ + 7 (mod p) p = 0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F a = 0 b = 7 -- group order (also order of base point G) r = n n = 0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 -- cofactor h = 1 -- bit length l = 256 secp256k1_G = ECPa secp256k1_curve 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 {- The base point G in compressed form is: G = 02 79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798 and in uncompressed form is: G = 04 79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798 483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8 -} -} cannonical_eckey x y = 0x4:pad32(numToBytes x) ++ pad32(numToBytes y) :: [Word8] where numToBytes n = reverse $ unfoldr getbyte n where getbyte d = do guard (d/=0) let (q,b) = d `divMod` 256 return (fromIntegral b,q) pad32 xs = replicate zlen 0 ++ xs where zlen = 32 - length xs oidToDER ns = S.pack $ b1 : concatMap encode ys where (xs,ys) = splitAt 2 ns b1 = fromIntegral $ foldl' (\a b->a*40+b) 0 xs encode x | x <= 127 = [fromIntegral x] | otherwise = (\(x:xs)-> reverse (x:map (0x80 .|.) xs)) (base128r x) base128r n = unfoldr getbyte n where getbyte d = do guard (d/=0) let (q,b) = d `divMod` 128 return (fromIntegral b,q) nistp256=[1,2,840,10045,3,1,7] nistp256_der=[0x2A,0x86,0x48,0xCE,0x3D,0x03,0x01,0x07] -- "\x2a\x86\x48\xce\x3d\x03\x01\x07" {- OID Curve description Curve name ---------------------------------------------------------------- 1.2.840.10045.3.1.7 NIST Curve P-256 [FIPS 186-2] "NIST P-256" 1.3.132.0.34 NIST Curve P-384 [FIPS 186-2] "NIST P-384" 1.3.132.0.35 NIST Curve P-521 [FIPS 186-2] "NIST P-521" Implementations MUST implement "NIST P-256", "NIST P-384" and "NIST P-521". The hexadecimal representation used in the public and private key encodings are: Curve Name Len Hexadecimal representation of the OID ---------------------------------------------------------------- "NIST P-256" 8 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 "NIST P-384" 6 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22 "NIST P-521" 6 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23 -} bitcoinAddress network_id k = address where Just (MPI x) = lookup 'x' (key k) Just (MPI y) = lookup 'y' (key k) pub = cannonical_eckey x y hash = S.cons network_id . RIPEMD160.hash . SHA256.hash . S.pack $ pub address = base58_encode hash -- gpg supported ECDSA curve: 2A8648CE3D030107 -- 2A 86 48 CE 3D 03 01 07 -- 1,2,134,72,206,61,3,1,7 -- 6*128+0x48 840 -- 0x4e*128+0x3d 10045 -- 1.2.840.10045.3.1.7 --> NIST P-256 -- doBTCImport doDecrypt db (ms,subspec,content) = do let fetchkey = do timestamp <- now let mbk = fmap discardNetworkID $ decode_btc_key timestamp content discardNetworkID = snd return $ maybe (Message []) id mbk let error s = do warn s exitFailure flip (maybe $ error "Cannot import master key.") subspec $ \tag -> do Message parsedkey <- fetchkey flip (maybe $ return db) (listToMaybe parsedkey) $ \key -> do let (m0,tailms) = splitAt 1 ms when (not (null tailms) || null m0) $ error "Key specification is ambiguous." doImportG doDecrypt db m0 tag "" key -- We return into IO in case we want to make a signature here. setHostnames :: (SockAddr -> Bool) -> Hosts.Hosts -> KeyData -> IO KeyData setHostnames pred hosts kd@(KeyData topmp topsigs uids subs) = -- TODO: we are removing the origin from the UID OriginMap, -- when we should be removing origins from the locations -- field of the sig's MappedPacket records. -- Call getHostnames and compare to see if no-op. if not (pred addr) || names0 == names \\ onions then {- trace (unlines [ "setHostnames NO-OP: gpg: "++show (map Char8.unpack onions, map Char8.unpack names0) , " file: "++show (map Char8.unpack names) , " pred: "++show (pred addr)]) -} (return kd) else do -- We should be sure to remove origins so that the data is written -- (but only if something changed). -- Filter all hostnames present in uids -- Write notations into first uid {- trace (unlines [ "setHostnames ACTION: gpg: "++show (map Char8.unpack onions, map Char8.unpack names0) , " file: "++show (map Char8.unpack names) ]) $ do -} return $ KeyData topmp topsigs uids1 subs where topk = packet topmp addr = fingerdress topk names :: [Char8.ByteString] names = Hosts.namesForAddress addr hosts (_,(onions,names0)) = getHostnames kd notations = map (NotationDataPacket True "hostname@" . Char8.unpack) (names \\ onions) isName (NotationDataPacket True "hostname@" _) = True isName _ = False uids0 = fmap zapIfHasName uids fstuid = head $ do p <- map packet $ flattenAllUids "" True uids guard $ isUserID p return $ uidkey p uids1 = Map.adjust addnames fstuid uids0 addnames (sigs,om) = (fmap f ss ++ ts, om ) -- XXX: removed om=Map.empty, preserve UserId origin where (ss,ts) = splitAt 1 sigs f (sig,tm) = if isSignaturePacket (packet sig) then (sig { packet = p', locations=Map.empty }, tm) else (sig, tm) where p' = (packet sig) { unhashed_subpackets=uh } uh = unhashed_subpackets (packet sig) ++ notations zapIfHasName (sigs,om) = if or bs then (sigs',om) -- XXX: removed om=Map.empty to preserve UserID origin else (sigs,om) where (bs, sigs') = unzip $ map unhash sigs unhash (sig,tm) = ( not (null ns) , ( sig { packet = p', locations = Map.empty } , tm ) ) where psig = packet sig p' = if isSignaturePacket psig then psig { unhashed_subpackets = ps } else psig uh = unhashed_subpackets $ psig (ns,ps) = partition isName uh socketFamily (SockAddrInet _ _) = AF_INET socketFamily (SockAddrInet6 _ _ _ _) = AF_INET6 socketFamily (SockAddrUnix _) = AF_UNIX hasFingerDress :: KeyDB -> SockAddr -> Bool hasFingerDress db addr | socketFamily addr/=AF_INET6 = False hasFingerDress db addr = pre=="fd" && isJust (selectPublicKey (KeyGrip g',Nothing) db) where (pre,g) = splitAt 2 $ filter (/=':') $ Hosts.inet_ntop addr g' = map toUpper g fingerdress :: Packet -> SockAddr fingerdress topk = maybe zero id $ Hosts.inet_pton addr_str where zero = SockAddrInet 0 0 addr_str = colons $ "fd" ++ (drop 10 $ map toLower $ fingerprint topk) colons (a:b:c:d:xs@(_:_)) = [a,b,c,d,':'] ++ colons xs colons xs = xs {- onionName :: KeyData -> (SockAddr,L.ByteString) onionName kd = (addr,name) where (addr,(name:_,_)) = getHostnames kd -} getHostnames :: KeyData -> (SockAddr, ([L.ByteString],[L.ByteString])) getHostnames (KeyData topmp _ uids subs) = (addr,(onames,othernames)) where othernames = do mp <- flattenAllUids "" True uids let p = packet mp guard $ isSignaturePacket p uh <- unhashed_subpackets p case uh of NotationDataPacket True "hostname@" v -> return $ Char8.pack v _ -> mzero addr = fingerdress topk -- name = maybe "" id $ listToMaybe onames -- TODO: more than one tor key? topk = packet topmp torkeys = do SubKey k sigs <- Map.elems subs let subk = packet k let sigs' = do torsig <- filter (has_tag "tor") $ map (packet . fst) sigs sig <- (signatures $ Message [topk,subk,torsig]) let v = verify (Message [topk]) sig -- Require parent's signature guard (not . null $ signatures_over v) let unhashed = unhashed_subpackets torsig subsigs = mapMaybe backsig unhashed -- This should consist only of 0x19 values -- subtypes = map signature_type subsigs sig' <- signatures . Message $ [topk,subk]++subsigs let v' = verify (Message [subk]) sig' -- Require subkey's signature guard . not . null $ signatures_over v' return torsig guard (not $ null sigs') return $ subk has_tag tag p = isSignaturePacket p && or [ tag `elem` mapMaybe usage (hashed_subpackets p) , tag `elem` map usageString (mapMaybe keyflags (hashed_subpackets p)) ] -- subkeyPacket (SubKey k _ ) = k onames :: [L.ByteString] onames = map ( (<> ".onion") . Char8.pack . take 16 . torhash ) torkeys whoseKey :: RSAPublicKey -> KeyDB -> [KeyData] whoseKey rsakey db = filter matchkey (Map.elems db) where matchkey (KeyData k _ _ subs) = not . null . filter (ismatch k) $ Map.elems subs ismatch k (SubKey mp sigs) = Just rsakey == rsaKeyFromPacket (packet mp) && any (check (packet k) (packet mp)) sigs check k sub (sig,_) = not . null $ do s <- signatures . Message $ [k,sub,packet sig] fw <- signatures_over $ verify (Message [k]) s subsig <- mapMaybe backsig (unhashed_subpackets sub) subsig_so <- signatures (Message [k,sub,subsig]) guard ( isSubkeySignature subsig_so && isSameKey (topkey subsig_so) k && isSameKey (subkey subsig_so) sub ) s2 <- signatures . Message $ [k,sub,subsig] signatures_over $ verify (Message [sub]) s2 workingKey grip use_db = listToMaybe $ do fp <- maybeToList grip elm <- Map.toList use_db guard $ matchSpec (KeyGrip fp) elm return $ keyPacket (snd elm) has_good_sig wk (KeyData k sigs uids subs) = any goodsig $ Map.toList uids where goodsig (uidstr,(sigs,_)) = not . null $ do sig0 <- fmap (packet . fst) sigs pre_ov <- signatures (Message [packet k, UserIDPacket uidstr, sig0]) signatures_over $ verify (Message [wk]) pre_ov markForImport :: Ord d => Map.Map String a -> Maybe String -> [Char] -> Map.Map d KeyData -> IO (Map.Map d KeyData) markForImport margs grip pubring db = maybe (return db) import_db $ wantToImport where wantToImport = mplus import_f importifauth_f where import_f = do Map.lookup "--import" margs return dont_have importifauth_f = do Map.lookup "--import-if-authentic" margs return isauth dont_have (KeyData p _ _ _) = not . Map.member pubring $ locations p isauth keydata = dont_have keydata && maybe False (`has_good_sig` keydata) wk where wk = workingKey grip db import_db dont_have = do forM_ to_alters $ \(_,KeyData c _ _ _) -> warn $ pubring ++ ": new "++showPacket (packet c) let db' = Map.union (Map.fromList altered) db return db' where to_alters = filter (dont_have . snd) $ Map.toList db altered = map (second append_loc) to_alters append_loc (KeyData p a b c) = KeyData p' a b c where p' = p { locations = Map.insert pubring (origin (secretToPublic (packet p)) (-1)) (locations p) } kiki_usage = do putStr . unlines $ ["kiki - a pgp key editing utility" ,"" ,"kiki [OPTIONS]" ,"" ," kiki merges a set of keyring files into a combined database and then" ," uses the database to update the files so that they have the most complete" ," information." ,"" ," The files pubring.gpg and subring.gpg in the directory specified by the " ," --homedir option are implicitly included in the keyring set." ,"" ," kiki can also import or export secret subkeys by using the --keypairs option." ,"" ," Subkeys that are imported with kiki are given an annotation \"usage@\" which" ," indicates what the key is for. This tag can be used as a SPEC to select a" ," particular key. Master keys may be specified by using fingerprints or by" ," specifying a substring of an associated UID." ,"" ,"Flags:" ," --homedir DIR" ," Where to find the the files secring.gpg and pubring.gpg. The " ," default location is taken from the environment variable " ," GNUPGHOME." ,"" ," --passphrase-fd N" ," Read passphrase from the given file descriptor." ,"" ," --import Add master keys to pubring.gpg. Without this option, only UID" ," and subkey data is updated. " ,"" ," --import-if-authentic" ," Add signed master keys to pubring.gpg. Like --import except that" ," only keys with signatures from the working key (--show-wk) are" ," imported." ,"" ," --autosign Sign all cross-certified tor-style UIDs." ," A tor-style UID is of the form:" ," Anonymous " ," It is considered cross certified if there exists a cross-certified" ," 'tor' subkey corresponding to the address HOSTNAME.onion." ,"" ,"Merging:" ," --keyrings FILE FILE..." ," Provide keyring files other than the implicit secring.gpg and" ," pubring.gpg in the --homedir. This option is implicit unless" ," --keypairs or --wallets is used." ,"" ," --wallets FILE FILE..." ," Provide wallet files with secret crypto-coin keys in Wallet" ," Import Format. The keys will be treated as subkeys of your" ," current working key (the one shown by --show-wk)." ,"" ," --keypairs KEYSPEC KEYSPEC..." ," Each KEYSPEC specifies that a key should match the content and" ," timestamp of an external PKCS #1 private RSA key file." ," " ," KEYSPEC ::= SPEC=FILE{CMD} " ,"" ," If neither SPEC or FILE match any keys, then the CMD will be " ," executed in order to create the FILE." ,"" ,"Output:" ," --show-wk Show fingerprints for the working key (which will be used to" ," make signatures) and all its subkeys and UID." ,"" ," --show-key SPEC" ," Show fingerprints for the specified key and all its subkeys" ," and UID." ,"" ," --show-all Show fingerprints and UIDs and usage tags for all known keys." ,"" ," --show-whose-key" ," Shows the fingerprint and UIDs of the key that owns the one that" ," is input on stdin in ssh-rsa format." ,"" ," --show-pem SPEC" ," Outputs the PKCS #8 public key corresponding to SPEC." ,"" ," --show-ssh SPEC" ," Outputs the ssh-rsa blob for the specified public key." ,"" ," --show-wip SPEC" ," Outputs the secret crypto-coin key in Wallet Input Format." ,"" ," --help Shows this help screen." ] main = do dotlock_init {- args <- cmdArgs $ modes [ Cross_Merge HOMEOPTION (def &= opt ("passphrase"::String) &= typ "FD" &= (help . concat) ["file descriptor from " ,"which to read passphrase"]) (def &= args &= typFile) &= help "Merge multiple secret keyrings to stdout." ] &= program "kiki" &= summary "kiki - a pgp key editing utility" doCmd args -} args_raw <- getArgs let (args,trail1) = break (=="--") args_raw trail = drop 1 trail1 (sargs,margs) = (sargs, foldl' (\m (k:xs)->Map.alter (appendArgs k xs) k m) Map.empty gargs) where (sargs,vargs) = partitionStaticArguments sargspec args sargspec = [ ("--homedir",1) , ("--passphrase-fd",1) , ("--import",0) , ("--autosign",0) , ("--import-if-authentic",0) , ("--show-wk",0) , ("--show-all",0) , ("--show-whose-key",0) , ("--show-key",1) , ("--show-pem",1) , ("--show-ssh",1) , ("--show-wip",1) , ("--help",0) ] argspec = map fst sargspec ++ ["--keyrings" ,"--keypairs" ,"--wallets" ,"--hosts"] -- "--bitcoin-keypairs" -- Disabled. We shouldn't accept private key -- data on the command line. args' = if map (take 1) (take 1 vargs) == ["-"] then vargs else "--keyrings":vargs gargs = (sargs ++) . toLast (++trail) . groupBy (\_ s-> take 1 s /= "-") $ args' appendArgs k xs opt = if k `elem` argspec then Just . maybe xs (++xs) $ opt else error . unlines $ [ "unrecognized option "++k , "Use --help for usage." ] -- putStrLn $ "margs = " ++ show (Map.assocs margs) unkeysRef <- newIORef Map.empty pwRef <- newIORef Nothing let keypairs0 = flip map (maybe [] id $ Map.lookup "--keypairs" margs) $ \specfile -> do let (spec,efilecmd) = break (=='=') specfile guard $ take 1 efilecmd=="=" let filecmd = drop 1 efilecmd let (file,bcmdb0) = break (=='{') filecmd bcmdb = if null bcmdb0 then "{}" else bcmdb0 guard $ take 1 bcmdb=="{" let bdmcb = (dropWhile isSpace . reverse) bcmdb guard $ take 1 bdmcb == "}" let cmd = (drop 1 . reverse . drop 1) bdmcb Just (spec,file,cmd) btcpairs0 = flip map (maybe [] id $ Map.lookup "--bitcoin-keypairs" margs) $ \specfile -> do let (spec,efilecmd) = break (=='=') specfile (spec,protocnt) <- do return $ if take 1 efilecmd=="=" then (spec,drop 1 efilecmd) else ("",spec) let (proto,content) = break (==':') protocnt spec <- return $ if null spec then "bitcoin" else spec return $ if take 1 content =="=" then (spec,proto,drop 1 content) else (spec,"base58",proto) {- publics = flip map (maybe [] id $ Map.lookup "--public" margs) $ \specfile -> do let (spec,efile) = break (=='=') specfile guard $ take 1 efile=="=" let file= drop 1 efile Just (spec,file) -} keyrings_ = maybe [] id $ Map.lookup "--keyrings" margs wallets = maybe [] id $ Map.lookup "--wallets" margs passphrase_fd = concat <$> Map.lookup "--passphrase-fd" margs decrypt wk = do -- warn $ "decryptKey "++fingerprint wk unkeys <- readIORef unkeysRef let kk = keykey wk flip (flip maybe $ return . Just) (Map.lookup kk unkeys) $ do let ret wkun = do writeIORef unkeysRef (Map.insert kk wkun unkeys) return (Just wkun) if symmetric_algorithm wk == Unencrypted then ret wk else do pw <- do pw <- readIORef pwRef flip (flip maybe return) pw $ do case passphrase_fd of Just fd -> do pwh <- fdToHandle (read fd) pw <- fmap trimCR $ S.hGetContents pwh writeIORef pwRef (Just pw) return pw Nothing -> return "" let wkun = do k <- decryptSecretKey pw wk guard (symmetric_algorithm k == Unencrypted) return k maybe (return Nothing) ret wkun when (not . null $ filter isNothing keypairs0) $ do warn "syntax error" exitFailure input_key <- maybe (return Nothing) (const $ fmap (Just . readPublicKey) Char8.getContents) $ Map.lookup "--show-whose-key" margs let keypairs = catMaybes keypairs0 btcpairs = catMaybes btcpairs0 {- putStrLn $ "keypairs = "++show keypairs putStrLn $ "publics = "++show publics putStrLn $ "keyrings = "++show keyrings -} let homespec = join . take 1 <$> Map.lookup "--homedir" margs cross_merge decrypt keyrings_ wallets (setHome homespec KeyRing.empty) $ \(secfile,grip) db pubring -> do use_db0 <- return db let pkeypairs = maybe [] id $ do keygrip <- grip return $ map (\(spec,f,cmd)-> (parseSpec keygrip spec,f,cmd)) keypairs fs <- forM pkeypairs $ \((topspec,subspec),f,cmd) -> do -- Note that it's important to discard the KeyData objects -- returned by filterMatches and retain only the keys. -- Otherwise, the iterations within the foldM would not be -- able to alter them by returning a modified KeyDB. let ms = map fst $ filterMatches topspec (Map.toList db) f_found <- doesFileExist f return (f_found,(f,subspec,ms,cmd)) let (imports,exports) = partition fst fs use_db <- foldM (doImport decrypt) use_db0 (map snd imports) let (btcs,_) = partition isSupportedBTC btcpairs isSupportedBTC (spec,"base58",cnt) = True isSupportedBTC _ = False dblist = Map.toList use_db pbtcs = maybe [] id $ do keygrip <- grip let conv (spec,proto,cnt) = let (topspec,subspec) = parseSpec keygrip spec ms = map fst $ filterMatches topspec dblist in (ms,subspec,cnt) return $ map conv btcs use_db <- foldM (doBTCImport decrypt) use_db pbtcs (ret_db,_) <- foldM (doExport decrypt) (use_db,use_db) (map snd exports) use_db <- flip (maybe $ return use_db) (lookup "--autosign" $ map (\(x:xs)->(x,xs)) sargs) $ \_ -> do let keys = map keyPacket $ Map.elems use_db wk = workingKey grip use_db -- g <- newGenIO -- stamp <- now wkun <- flip (maybe $ return Nothing) wk $ \wk -> do wkun <- decrypt wk maybe (error $ "Bad passphrase?") (return . Just) wkun -- return . snd $ Map.mapAccum (signTorIds stamp wkun keys) g use_db Traversable.mapM (signTorIds wkun keys) use_db use_db <- markForImport margs grip pubring use_db ret_db <- return use_db ret_db <- do let db = ret_db let hns = maybe [] id $ Map.lookup "--hosts" margs hostdbs0 <- mapM (fmap Hosts.decode . L.readFile) hns let gpgnames = map getHostnames $ Map.elems db os = do (addr,(ns,_)) <- gpgnames n <- ns return (addr,n) setOnions hosts = foldl' (flip $ uncurry Hosts.assignName) hosts os -- we ensure .onion names are set properly hostdbs = map setOnions hostdbs0 outgoing_names = do (addr,(_,gns)) <- gpgnames guard . not $ null gns guard $ all (null . Hosts.namesForAddress addr) hostdbs0 return addr -- putStrLn $ "hostdbs = " ++ show hostdbs -- 1. let U = union all the host dbs -- preserving whitespace and comments of the first let u0 = foldl' Hosts.plus Hosts.empty hostdbs -- we filter U to be only finger-dresses u1 = Hosts.filterAddrs (hasFingerDress db) u0 -- let nf h = map Char8.unpack $ Hosts.namesForAddress (fromJust $ Hosts.inet_pton "fdf4:ed98:29c7:6226:9bde:a5b4:d564:3321") h {- putStrLn $ "_ = {\n" ++ show (head hostdbs) ++ "}" putStrLn $ "--> " ++ show (nf (head hostdbs)) putStrLn $ "u0 = {\n" ++ show u0 ++ "}" putStrLn $ "--> " ++ show (nf u0) putStrLn $ "u1 = {\n" ++ show u1 ++ "}" putStrLn $ "--> " ++ show (nf u1) -} -- 2. replace gpg annotations with those in U -- forM use_db db' <- Traversable.mapM (setHostnames (\a -> not $ elem a outgoing_names) u1) db -- 3. add hostnames from gpg for addresses not in U let u = foldl' f u1 ans ans = reverse $ do (addr,(_,ns)) <- gpgnames guard $ addr `elem` outgoing_names -- . null $ Hosts.namesForAddress addr u0 n <- ns return (addr,n) f h (addr,n) = Hosts.assignNewName addr n h {- putStrLn $ "u = {\n" ++ show u ++ "}" putStrLn $ "--> " ++ show (nf u) -} -- 4. for each host db H, union H with U and write it out as H' -- only if there is a non-empty diff forM_ (zip hns $ zip hostdbs0 hostdbs) $ \(fname,(h0,h1)) -> do let h = h1 `Hosts.plus` u d = Hosts.diff h0 h fnamecolon = Char8.pack fname <> ": " {- putStrLn $ "h = {\n" ++ show h ++ "}" putStrLn $ "--> " ++ show (nf h) -} Char8.hPutStrLn stderr $ Char8.unlines (map (fnamecolon <>) d) when (not $ null d) $ L.writeFile fname $ Hosts.encode h return () return db' do -- On last pass, interpret --show-* commands. let shspec = Map.fromList [("--show-wk", const $ show_wk secfile grip) ,("--show-all",const $ show_all) ,("--show-whose-key", const $ show_whose_key input_key) ,("--show-key",\[x] -> show_key x $ maybe "" id grip) ,("--show-pem",\[x] -> show_pem x $ maybe "" id grip) ,("--show-ssh",\[x] -> show_ssh x $ maybe "" id grip) ,("--show-wip",\[x] -> show_wip x $ maybe "" id grip) ,("--help", \_ _ ->kiki_usage)] shargs = mapMaybe (\(x:xs) -> (,xs) <$> Map.lookup x shspec) sargs forM_ shargs $ \(cmd,args) -> cmd args use_db return ret_db return() where getTorKeys pub = do xs <- groupBindings pub (_,(top,sub),us,_,_) <- xs guard ("tor" `elem` us) let torhash = maybe "" id $ derToBase32 <$> derRSA sub return (top,(torhash,sub)) signTorIds selfkey keys kd@(KeyData k ksigs umap submap) = do umap' <- Traversable.mapM signIfTor (Map.mapWithKey (,) umap) return (KeyData k ksigs umap' submap) :: IO KeyData where mkey = packet k signIfTor (str,ps) = if isTorID str then do let uidxs0 = map packet $ flattenUid "" True (str,ps) -- addition<- signSelfAuthTorKeys' selfkey g keys grip timestamp mkey uidxs0 additional <- signSelfAuthTorKeys' selfkey keys grip mkey uidxs0 let ps' = ( map ( (,tmap) . toMappedPacket om) additional ++ fst ps , Map.union om (snd ps) ) toMappedPacket om p = (mappedPacket "" p) {locations=om} om = Map.singleton "--autosign" (origin p (-1)) where p = UserIDPacket str tmap = Map.empty return ps' else return ps torbindings = getTorKeys (map packet $ flattenTop "" True kd) isTorID str = and [ uid_topdomain parsed == "onion" , uid_realname parsed `elem` ["","Anonymous"] , uid_user parsed == "root" , fmap (match . fst) (lookup mkey torbindings) == Just True ] where parsed = parseUID str match = ( (==subdom) . take (fromIntegral len)) subdom0 = L.fromChunks [encodeUtf8 (uid_subdomain parsed)] subdom = Char8.unpack subdom0 len = T.length (uid_subdomain parsed) signSelfAuthTorKeys' selfkey keys grip mainpubkey (uid:xs') = do new_sig <- let wkun = fromJust selfkey tor_ov = torSigOver mainpubkey wkun uid flgs in pgpSign (Message [wkun]) tor_ov SHA1 (fingerprint wkun) return (additional new_sig) -- (uid:sigs,additional,xs'',g') where (sigs, _) = span isSignaturePacket xs' overs sig = signatures $ Message (keys++[mainpubkey,uid,sig]) vs :: [ ( Packet -- signature , Maybe SignatureOver -- Nothing means non-verified , Packet ) -- key who signed ] vs = do sig <- sigs o <- overs sig k <- keys let ov = verify (Message [k]) $ o signatures_over ov return (sig,Just ov,k) selfsigs = filter (\(sig,v,whosign) -> isJust (v >> selfkey >>= guard . (== keykey whosign) . keykey)) vs additional new_sig = do new_sig <- maybeToList new_sig guard $ {- trace (unlines $ [ "selfsigs = "++show (map ((\(_,_,k)->fingerprint k)) selfsigs) , " for mainkey = "++fingerprint mainpubkey] ) -} (null $ selfsigs) signatures_over new_sig {- modsig sig = sig { signature = map id (signature sig) } where plus1 (MPI x) = MPI (x+1) params newtop = public ++ map fst (key newtop) ++ "}" where public = case newtop of PublicKeyPacket {} -> "public{" SecretKeyPacket {} -> if L.null (encrypted_data newtop ) then "secret{" else "encrypted{" _ -> "??????{" traceSig newtop newuid new_sig = (unlines ["mainpubkey:"++ show (fingerprint mainpubkey) ,"new_sig topkey:"++ (show . fingerprint $ newtop) ,"new_sig topkey params: "++ params newtop ,"new_sig user_id:"++ (show newuid) ,"new_sig |over| = " ++ (show . length $ new_sig) ,"new_sig hashed = " ++ (PP.ppShow . concatMap hashed_subpackets $ new_sig) ,"new_sig unhashed = " ++ (show . concatMap unhashed_subpackets $ new_sig) ,"new_sig type: " ++ (show . map signature_type $ new_sig) ,"new_sig signature:" ++ (show . concatMap signature $ new_sig) ,"new_sig isSignaturePacket(over) = " ++ (show . map isSignaturePacket $ new_sig) ,"issuer = " ++ show (map signature_issuer new_sig) ]) -} flgs = if keykey mainpubkey == keykey (fromJust selfkey) then keyFlags0 mainpubkey (map (\(x,_,_)->x) selfsigs) else [] isSameKey a b = sort (key apub) == sort (key bpub) where apub = secretToPublic a bpub = secretToPublic b groupBindings pub = let (_,bindings) = getBindings pub bindings' = accBindings bindings code (c,(m,s),_,_,_) = (fingerprint_material m,-c) ownerkey (_,(a,_),_,_,_) = a sameMaster (ownerkey->a) (ownerkey->b) = fingerprint_material a==fingerprint_material b -- matchgrip ((code,(top,sub), kind, hashed,claimants):_) | fpmatch grip top = True -- matchgrip _ = False gs = {- filter matchgrip $ -} groupBy sameMaster (sortBy (comparing code) bindings') in gs isTopKey p@(SecretKeyPacket {}) | not (is_subkey p) = True isTopKey p@(PublicKeyPacket {}) | not (is_subkey p) = True isTopKey _ = False filterMatches :: KeySpec -> [(KeyKey,KeyData)] -> [(KeyKey,KeyData)] filterMatches spec ks = filter (matchSpec spec) ks selectSecretKey :: (KeySpec,Maybe String) -> KeyDB -> Maybe Packet selectSecretKey (spec,mtag) db = selectKey0 False (spec,mtag) db selectPublicKey :: (KeySpec,Maybe String) -> KeyDB -> Maybe Packet selectPublicKey (spec,mtag) db = selectKey0 True (spec,mtag) db selectKey0 :: Bool -> (KeySpec,Maybe String) -> KeyDB -> Maybe Packet selectKey0 wantPublic (spec,mtag) db = do let Message ps = flattenKeys wantPublic db ys = snd $ seek_key spec ps flip (maybe (listToMaybe ys)) mtag $ \tag -> do let (subspec,ys1) = (KeyTag y tag,ys1) where y:ys1 = ys zs = snd $ seek_key subspec ys1 listToMaybe zs seek_key :: KeySpec -> [Packet] -> ([Packet],[Packet]) seek_key (KeyGrip grip) sec = (pre, subs) where (pre,subs) = break pred sec pred p@(SecretKeyPacket {}) = matchpr grip p == grip pred p@(PublicKeyPacket {}) = matchpr grip p == grip pred _ = False seek_key (KeyTag key tag) ps = if null bs then (ps,[]) else if null qs then let (as',bs') = seek_key (KeyTag key tag) (tail bs) in (as ++ (head bs:as'), bs') else (reverse (tail qs), head qs : reverse rs ++ bs) where (as,bs) = break (\p -> isSignaturePacket p && has_tag tag p && isJust (signature_issuer p) && matchpr (fromJust $ signature_issuer p) key == fromJust (signature_issuer p) ) ps (rs,qs) = break isKey (reverse as) has_tag tag p = tag `elem` mapMaybe usage (hashed_subpackets p) || tag `elem` map usageString (mapMaybe keyflags (hashed_subpackets p)) seek_key (KeyUidMatch pat) ps = if null bs then (ps,[]) else if null qs then let (as',bs') = seek_key (KeyUidMatch pat) (tail bs) in (as ++ (head bs:as'), bs') else (reverse (tail qs), head qs : reverse rs ++ bs) where (as,bs) = break (isInfixOf pat . uidStr) ps (rs,qs) = break isKey (reverse as) uidStr (UserIDPacket s) = s uidStr _ = "" groupTops ps = groupBy (\_ b -> not (isTopKey b)) ps {- makeTorUID g timestamp wkun keyflags topkey torkey = uid:signatures_over sig where torhash sub = maybe "" id $ derToBase32 <$> derRSA sub s = "Anonymous " uid = UserIDPacket s sig = fst $ torsig g topkey wkun uid timestamp keyflags -}