{-# LANGUAGE DoAndIfThenElse #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE CPP #-} module Main ( main ) where import Control.Applicative import Control.Monad import Data.ASN1.BinaryEncoding import Data.ASN1.Encoding import Data.ASN1.Types import Data.Binary import Data.Bits import Data.Char import Data.IORef import Data.List import Data.Maybe import Data.OpenPGP import Data.Ord import Data.Text.Encoding import System.Environment import System.Exit import System.IO (hPutStrLn,stderr) import qualified Codec.Binary.Base64 as Base64 import qualified Crypto.Hash.RIPEMD160 as RIPEMD160 import qualified Crypto.Hash.SHA256 as SHA256 import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy.Char8 as Char8 import qualified Data.Map as Map import qualified Data.Text as T import Control.Arrow (first,second) import Data.Binary.Get (runGet) import Data.Binary.Put (putWord32be,runPut,putByteString) import DotLock import LengthPrefixedBE import KeyRing import Base58 import qualified CryptoCoins import Data.OpenPGP.Util (verify,fingerprint) -- {-# ANN module ("HLint: ignore Eta reduce"::String) #-} -- {-# ANN module ("HLint: ignore Use camelCase"::String) #-} {- - - accBindings :: forall t a a1 a2. Bits t => [(t, (Packet, Packet), [a], [a1], [a2])] -> [(t, (Packet, Packet), [a], [a1], [a2])] bitcoinAddress :: Word8 -> Packet -> String cannonical_eckey :: forall b b1. (Integral b1, Integral b) => b -> b1 -> [Word8] commands :: [(String, String)] decode_sshrsa :: Char8.ByteString -> Maybe RSAPublicKey disjoint_fp :: [Packet] -> [[Packet]] doAutosign :: forall t. t -> KeyData -> [PacketUpdate] fpmatch :: Maybe [Char] -> Packet -> Bool getBindings :: [Packet] -> ([([Packet], [SignatureOver])], [(Word8, (Packet, Packet), [String], [SignatureSubpacket], [Packet])]) isCertificationSig :: SignatureOver -> Bool isSubkeySignature :: SignatureOver -> Bool kiki :: forall a. (Eq a, Data.String.IsString a) => a -> [[Char]] -> IO () kiki_sync_help :: IO () listKeys :: [Packet] -> [Char] listKeysFiltered :: [[Char]] -> [Packet] -> [Char] :Main.main :: IO () main :: IO () partitionStaticArguments :: forall a. Ord a => [(a, Int)] -> [a] -> ([[a]], [a]) readPublicKey :: Char8.ByteString -> RSAPublicKey show_all :: KeyDB -> IO () show_key :: forall t. String -> t -> Map.Map KeyRing.KeyKey KeyData -> IO () show_pem :: String -> String -> KeyDB -> IO () show_ssh :: String -> String -> KeyDB -> IO () show_whose_key :: Maybe RSAPublicKey -> KeyDB -> IO () show_wip :: String -> String -> KeyDB -> IO () show_wk :: FilePath -> Maybe [Char] -> Map.Map KeyRing.KeyKey KeyData -> IO () smallpr :: Packet -> [Char] sshrsa :: Integer -> Integer -> Char8.ByteString toLast :: forall x. (x -> x) -> [x] -> [x] verifyBindings :: [Packet] -> [Packet] -> ([SignatureOver], [SignatureOver]) warn :: String -> IO () whoseKey :: RSAPublicKey -> KeyDB -> [KeyData] - - -} warn str = hPutStrLn stderr str 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 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 listKeysFiltered grips pkts = do let (certs,bs) = getBindings pkts as = accBindings bs defaultkind (k:_) hs = k defaultkind [] hs = fromMaybe "subkey" ( 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 = fromMaybe "" $ 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 = {- fromMaybe "" . 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" {- - modify a UID to test the verify function properly - fails modifyUID (UserIDPacket str) = UserIDPacket str' where (fstname,rst) = break (==' ') str str' = mod fstname ++ rst mod "Bob" = "Bob Slacking" mod x = x modifyUID other = other -} readPublicKey :: Char8.ByteString -> RSAPublicKey readPublicKey bs = fromMaybe er $ 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" -- | Returns the given list with its last element modified. toLast :: (x -> x) -> [x] -> [x] toLast f [] = [] toLast f [x] = [f x] toLast f (x:xs) = x : toLast f xs -- partitionStaticArguments :: Ord a => [(a, Int)] -> [a] -> ([[a]], [a]) 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 = 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 . void $ warn (keyspec ++ ": not found")) (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 . void $ warn (keyspec ++ ": not found")) (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 $ void (warn (keyspec ++ ": not found"))) (selectSecretKey s db) $ \k -> do let nwb = maybe 0x80 CryptoCoins.secretByteFromName $ snd s putStrLn $ walletImportFormat nwb k 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 whoseKey :: RSAPublicKey -> KeyDB -> [KeyData] whoseKey rsakey db = filter matchkey (Map.elems db) where matchkey (KeyData k _ _ subs) = any (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 isSameKey a b = sort (key apub) == sort (key bpub) where apub = secretToPublic a bpub = secretToPublic b kiki_sync_help = 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." -} ["kiki sync [options...]" ,"" ,"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..." ," A keypair is a secret key coupled with it's corresponding public" ," key, both of which are ordinarily stored in a single file in pem" ," format. Users incognisant of the fact that the public key (which" ," is also stored separately) is in this file, often think of it as" ," their secret key file." ,"" ," 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} " ,"" ," The form of SPEC is documented below. If there is only one master" ," key in your keyring and only one key is used for each purpose, then" ," it is possible for SPEC in this case to merely be a tag which offers" ," information about what this key is used for, for example, any of" ," `tor', `ssh-client', `ssh-host', or `strongswan' will do." ,"" ," 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." ,"" -} ,"Specifying keys on the kiki command line:" ,"" ," SPEC ::= MASTER/SUBKEY" ,"" ," SPEC indicates a specific key in the keyring, in it's longest incarnation," ," it is of the form MASTER/SUBKEY where MASTER and SUBKEY are documented below." ," If kiki can infer the key unambiguously, either via the command in question or" ," the contents of the keyring, then it is permissable to ommit either MASTER or" ," SUBKEY, in which case the slash may also be ommitted unless it is used via its" ," position to indicate whether a SUBKEY or MASTER is intended." ,"" ," MASTER may be any of" ," * The tail end of a fingerprint prefixed by 'fp:'" ," * A sub-string of a user id (without slashes) prefixed by 'u:'" ," * 40 characters of hexidecimal (kiki will assume this to be a fingerprint)" ," * A sub-string of a user id (without slashes, the prefix 'u:' is optional)" ,"" ," SUBKEY may be any of" ," * The tail end of a fingerprint prefixed by 'fp:'" ," * An exact match of a usage tag prefixed by 't:'" ," * 40 characters of hexidecimal (kiki will assume this to be a fingerprint)" ," * An exact match of a usage tag (The prefix 't:' is optional)" ,"" ," In parsing the spec, kiki will attempt to match the string to one of the" ," above formats, in the order presented." ,"" ," Examples of valid SPEC strings:" ,"" ," fp:4A39F/tor" ," u:joe/tor" ," u:joe/t:tor" ," u:joe/fp:4abf30" ," joe/tor" ," 5E24CD442AA6965D2012E62A905C24185D5379C2" ] doAutosign rt kd@(KeyData k ksigs umap submap) = ops where ops = map (\u -> InducerSignature u []) us us = filter torStyle $ Map.keys umap 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) torbindings = getTorKeys (map packet $ flattenTop "" True kd) getTorKeys pub = do xs <- groupBindings pub (_,(top,sub),us,_,_) <- xs guard ("tor" `elem` us) let torhash = fromMaybe "" $ derToBase32 <$> derRSA sub return (top,(torhash,sub)) groupBindings pub = gs where (_,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 gs = groupBy sameMaster (sortBy (comparing code) bindings') processArgs sargspec polyVariadicArgs defaultPoly args_raw = (sargs,margs) where (args,trail1) = break (=="--") args_raw trail = drop 1 trail1 commonArgSpec = [ ("--homedir",1) , ("--passphrase-fd",1) ] (sargs,margs) = (sargs, foldl' (\m (k:xs)->Map.alter (appendArgs k xs) k m) Map.empty gargs) where (sargs,vargs) = partitionStaticArguments (commonArgSpec ++ sargspec) args argspec = map fst sargspec ++ polyVariadicArgs args' = if map (take 1) (take 1 vargs) == ["-"] then vargs else defaultPoly:vargs -- grouped args 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." ] data CommonArgsParsed = CommonArgsParsed { cap_homespec :: Maybe String, cap_passfd :: Maybe InputFile } parseCommonArgs margs = CommonArgsParsed { cap_homespec = homespec, cap_passfd = passfd } where passphrase_fd = concat <$> Map.lookup "--passphrase-fd" margs homespec = join . take 1 <$> Map.lookup "--homedir" margs passfd = fmap (FileDesc . read) passphrase_fd parseKeySpecs = map $ \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) --kiki :: (Eq a, Data.String.IsString a) => a -> [String] -> IO () kiki "sync" args_raw = do let (sargs,margs) = processArgs sargspec polyVariadicArgs "--keyrings" args_raw sargspec = [ ("--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) ] polyVariadicArgs = ["--keyrings" ,"--keypairs" ,"--wallets" ,"--hosts"] -- putStrLn $ "margs = " ++ show (Map.assocs margs) unkeysRef <- newIORef Map.empty pwRef <- newIORef Nothing let keypairs0 = parseKeySpecs (fromMaybe [] $ Map.lookup "--keypairs" margs) keyrings_ = fromMaybe [] $ Map.lookup "--keyrings" margs wallets = fromMaybe [] $ Map.lookup "--wallets" margs passphrase_fd = concat <$> Map.lookup "--passphrase-fd" margs when (any isNothing keypairs0) $ do warn "Syntax error in key pair specification" exitFailure input_key <- maybe (return Nothing) (const $ fmap (Just . readPublicKey) Char8.getContents) $ Map.lookup "--show-whose-key" margs let keypairs = catMaybes keypairs0 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 $ fromMaybe "" grip) ,("--show-pem",\[x] -> show_pem x $ fromMaybe "" grip) ,("--show-ssh",\[x] -> show_ssh x $ fromMaybe "" grip) ,("--show-wip",\[x] -> show_wip x $ fromMaybe "" grip)-} ,("--help", \_ _ ->kiki_sync_help)] shargs = mapMaybe (\(x:xs) -> (,xs) <$> Map.lookup x shspec) sargs forM_ shargs $ \(cmd,args) -> cmd args (rtKeyDB rt) err -> putStrLn $ errorString err forM_ report $ \(fname,act) -> do putStrLn $ fname ++ ": " ++ reportString act kiki "working-key" args = do kiki "sync" ["--show-wk"] -- Generic help kiki "help" [] = do putStrLn "Valid commands are:" let longest = maximum $ map (length . fst) commands pad cmd = take (longest+3) $ cmd ++ repeat ' ' forM commands $ \(cmd,help) -> do putStrLn $ " " ++ pad cmd ++ help putStr . unlines $ ["" ,"See 'kiki help ' for more information on a specific command." ] return () kiki "help" args = forM_ args $ \arg -> case lookup arg commands of Nothing -> putStrLn $ "No help available for commmand '" ++ arg ++ "'." _ -> kiki arg ["--help"] kiki "show" args = return () commands :: [(String,String)] commands = [ ( "help", "display usage information" ) , ( "sync", "update key files of various kinds by propogating information" ) , ( "show", "display information from your keyrings") , ( "working-key", "show the current working master key and its subkeys" ) ] main = do dotlock_init args_raw <- getArgs case args_raw of [] -> kiki "working-key" [] cmd : args | cmd `elem` map fst commands -> kiki cmd args _ -> kiki "help" args_raw