{-# LANGUAGE DoAndIfThenElse #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE CPP #-} module Main where import Data.IORef 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 Control.Monad -- import qualified Text.Show.Pretty as PP -- import Text.PrettyPrint as PP hiding ((<>)) import Data.List import Data.OpenPGP.Util (verify,fingerprint) import Data.Ord import Data.Maybe import Data.Bits import qualified Data.Text as T import Data.Text.Encoding import qualified Codec.Binary.Base64 as Base64 import qualified Crypto.Hash.SHA256 as SHA256 import qualified Crypto.Hash.RIPEMD160 as RIPEMD160 -- import qualified Crypto.Types.PubKey.ECDSA as ECDSA -- import qualified Crypto.PubKey.ECC.ECDSA as ECDSA -- import Crypto.Random (newGenIO,SystemRandom) import Data.ASN1.Types import Data.ASN1.Encoding import Data.ASN1.BinaryEncoding import Control.Applicative import System.Environment import System.Exit import System.IO (hPutStrLn,stderr) 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.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 LengthPrefixedBE import Data.Binary.Put (putWord32be,runPut,putByteString) import Data.Binary.Get (runGet) import KeyRing import Base58 -- instance Default S.ByteString where def = S.empty warn str = hPutStrLn stderr str {- 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 {- getPackets :: IO [Packet] getPackets = do input <- L.getContents case decodeOrFail input of Right (_,_,Message pkts) -> return pkts Left (_,_,_) -> return [] -} isCertificationSig (CertificationSignature {}) = True isCertificationSig _ = True isSubkeySignature (SubkeySignature {}) = True isSubkeySignature _ = False 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 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 [] = [] -} 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 Slacking" mod x = x modifyUID other = other -} -- type TimeStamp = Word32 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 ) 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 {- 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 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 -} {- onionName :: KeyData -> (SockAddr,L.ByteString) onionName kd = (addr,name) where (addr,(name:_,_)) = getHostnames kd -} 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 $ packet sig) 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 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) {- 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 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 {- putStrLn $ "wallets = "++show wallets putStrLn $ "keypairs = "++show keypairs putStrLn $ "keyrings = "++show keyrings_ putStrLn $ "publics = "++show publics -} let homespec = join . take 1 <$> Map.lookup "--homedir" margs passfd = fmap (FileDesc . read) passphrase_fd pems = flip map keypairs $ \(usage,path,cmd) -> let cmd' = guard (not $ null cmd) >> return cmd in (ArgFile path, (MutableRef cmd', PEMFile usage)) walts = map (\fname -> (ArgFile fname, (MutableRef Nothing, WalletFile))) wallets rings = map (\fname -> (ArgFile fname, (MutableRef Nothing, KeyRingFile passfd))) keyrings_ hosts = maybe [] (map decorate) $ Map.lookup "--hosts" margs where decorate fname = (ArgFile fname, (MutableRef Nothing, Hosts)) importStyle = maybe (\_ _ -> subkeysOnly) (\f rt kd -> f rt kd >> importPublic) $ mplus import_f importifauth_f where import_f = do Map.lookup "--import" margs return $ \rt kd -> Just () importifauth_f = do Map.lookup "--import-if-authentic" margs return guardAuthentic kikiOp = KeyRingOperation { kFiles = Map.fromList $ [ ( HomeSec, (MutableRef Nothing, KeyRingFile passfd) ) , ( HomePub, (MutableRef Nothing, KeyRingFile Nothing) ) ] ++ rings ++ pems ++ walts ++ hosts , kImports = Map.fromList $ [ ( HomePub, importStyle ) ] , kManip = maybe noManip (const doAutosign) $ Map.lookup "--autosign" margs , homeSpec = homespec } KikiResult rt report <- runKeyRing kikiOp case rt of KikiSuccess rt -> do -- interpret --show-* commands. let grip = rtGrip rt let shspec = Map.fromList [("--show-wk", const $ show_wk (rtSecring rt) 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 (rtKeyDB rt) e -> putStrLn $ show (fmap (const ()) e) putStrLn $ show report return() where doAutosign rt kd@(KeyData k ksigs umap submap) = ops where ops = map (\u -> InducerSignature u []) us us = filter torStyle $ Map.keys umap torbindings = getTorKeys (map packet $ flattenTop "" True kd) torStyle str = and [ uid_topdomain parsed == "onion" , uid_realname parsed `elem` ["","Anonymous"] , uid_user parsed == "root" , fmap (match . fst) (lookup (packet k) 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) 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)) 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 {- 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 -}