summaryrefslogtreecommitdiff
path: root/lib/Kiki.hs
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2016-04-25 20:20:45 -0400
committerjoe <joe@jerkface.net>2016-04-25 20:20:45 -0400
commit6860098ed8f8b56eb5058e0c9c427abaa57021bf (patch)
treedefc0ae2c6bcd08f489628be0633f99e6254a218 /lib/Kiki.hs
parent3c8536fd92043283d20b9e19ae488e7fe64af236 (diff)
more work on cokiki (ssh-client)
Diffstat (limited to 'lib/Kiki.hs')
-rw-r--r--lib/Kiki.hs169
1 files changed, 165 insertions, 4 deletions
diff --git a/lib/Kiki.hs b/lib/Kiki.hs
index 783b6ed..575cf26 100644
--- a/lib/Kiki.hs
+++ b/lib/Kiki.hs
@@ -1,8 +1,169 @@
1{-# LANGUAGE CPP #-}
1module Kiki where 2module Kiki where
2 3
4import Control.Monad
5import Control.Applicative
6import Data.List
7import Data.Maybe
8import Data.Ord
9import System.Directory
10import System.FilePath.Posix
11import System.IO
12import Data.OpenPGP
13import Data.OpenPGP.Util
14import qualified Data.Map.Strict as Map
15import qualified Codec.Binary.Base64 as Base64
16import Data.ASN1.BinaryEncoding
17import Data.ASN1.Encoding
18import Data.ASN1.Types
19import qualified Data.ByteString.Lazy as L
20import qualified Data.ByteString.Lazy.Char8 as Char8
21
22import CommandLine
23import qualified SSHKey as SSH
24import KeyRing
25
3-- | 26-- |
4-- Regenerate /var/cache/kiki 27-- Regenerate /var/cache/kiki
5refresh :: IO () 28refresh :: (FilePath -> FilePath) -> CommonArgsParsed -> IO ()
6refresh = do 29refresh root homepass = do
7 -- TODO 30 let homepass' = homepass { cap_homespec = fmap root (cap_homespec homepass) }
8 return () 31 KikiResult r report <- runKeyRing $ minimalOp homepass'
32 let mroot = case root "" of
33 "/" -> Nothing
34 "" -> Nothing
35 pth -> Just pth
36 case r of
37 KikiSuccess rt -> refreshCache rt mroot
38 _ -> return () -- XXX: silent fail?
39
40data CommonArgsParsed = CommonArgsParsed { cap_homespec :: Maybe String, cap_passfd :: Maybe InputFile }
41
42
43minimalOp :: CommonArgsParsed -> KeyRingOperation
44minimalOp cap = op
45 where
46 streaminfo = StreamInfo { fill = KF_None
47 , typ = KeyRingFile
48 , spill = KF_All
49 , initializer = NoCreate
50 , access = AutoAccess
51 , transforms = []
52 }
53 op = KeyRingOperation
54 { opFiles = Map.fromList $
55 [ ( HomeSec, streaminfo { access = Sec })
56 , ( HomePub, streaminfo { access = Pub })
57 ]
58 , opPassphrases = do pfile <- maybeToList (cap_passfd cap)
59 return $ PassphraseSpec Nothing Nothing pfile
60 , opTransforms = []
61 , opHome = cap_homespec cap
62 }
63
64
65refreshCache :: KeyRingRuntime -> Maybe FilePath -> IO ()
66refreshCache rt rootdir = do
67
68 let mkpath pth = fromMaybe "" rootdir ++ "/var/cache/kiki/"++pth
69
70 write f bs = do
71 createDirectoryIfMissing True $ takeDirectory f
72 writeFile f bs
73
74 let oname' = do wk <- rtWorkingKey rt
75 -- XXX unnecessary signature check
76 onionNameForContact (keykey wk) (rtKeyDB rt)
77 bUnprivileged = False -- TODO
78 if (oname' == Nothing) && (not bUnprivileged) then error "Missing tor key" else do
79 let oname = fromMaybe "" oname'
80 -- sshcpathpub0 = fromMaybe "" rootdir ++ osHomeDir </> ".ssh" </> "id_rsa.pub"
81 -- sshspathpub0 = fromMaybe "" rootdir ++ "/etc/ssh/ssh_host_rsa_key.pub"
82 -- contactipsec0 = fromMaybe "" rootdir ++ "/etc/ipsec.d/certs/%(onion).pem"
83
84 -- Finally, export public keys if they do not exist.
85 flip (maybe $ warn "missing working key?") (rtGrip rt) $ \grip -> do
86 either warn (write $ mkpath "root/.ssh/id_rsa.pub")
87 $ show_ssh' "ssh-client" grip (rtKeyDB rt)
88 either warn (write $ mkpath "ssh_host_rsa_key.pub")
89 $ show_ssh' "ssh-server" grip (rtKeyDB rt)
90 either warn (write $ mkpath "ipsec.d/certs/" ++ oname++".pem")
91 $ show_pem' "ipsec" grip (rtKeyDB rt) pemFromPacket
92
93 let cs = filter notme (Map.elems $ rtKeyDB rt)
94 kk = keykey (fromJust $ rtWorkingKey rt)
95 notme kd = keykey (keyPacket kd) /= kk
96
97 installConctact kd = do
98 -- The getHostnames command requires a valid cross-signed tor key
99 -- for each onion name returned in (_,(ns,_)).
100 let (_,(ns,_)) = getHostnames kd
101 contactname = fmap Char8.unpack $ listToMaybe ns -- only first onion name.
102 flip (maybe $ return ()) contactname $ \contactname -> do
103
104 let cpath = interp (Map.singleton "onion" contactname) "ipsec.d/certs/%(onion).pem"
105 their_master = packet $ keyMappedPacket kd
106 -- We find all cross-certified ipsec keys for the given cross-certified onion name.
107 ipsecs = sortOn (Down . timestamp)
108 $ getCrossSignedSubkeys their_master (keySubKeys kd) "ipsec"
109 forM_ (take 1 ipsecs) $ \k -> do
110 either warn (write $ mkpath cpath) $ pemFromPacket k
111
112 mapM_ installConctact cs
113
114
115#if !MIN_VERSION_base(4,8,0)
116sortOn :: Ord b => (a -> b) -> [a] -> [a]
117sortOn f =
118 map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x))
119#endif
120
121pemFromPacket k = do
122 let rsa = pkcs8 . fromJust $ rsaKeyFromPacket k
123 der = encodeASN1 DER (toASN1 rsa [])
124 qq = Base64.encode (L.unpack der)
125 return $
126 writePEM "PUBLIC KEY" qq -- ("TODO "++show keyspec)
127
128show_pem keyspec wkgrip db = either warn putStrLn $ show_pem' keyspec wkgrip db pemFromPacket
129
130show_pem' keyspec wkgrip db keyfmt = do
131 let s = parseSpec wkgrip keyspec
132 flip (maybe . Left $ keyspec ++ ": not found")
133 (selectPublicKey s db)
134 keyfmt
135
136warn str = hPutStrLn stderr str
137
138show_ssh keyspec wkgrip db = either warn putStrLn $ show_ssh' keyspec wkgrip db
139
140show_ssh' keyspec wkgrip db = do
141 let s = parseSpec wkgrip keyspec
142 flip (maybe . Left $ keyspec ++ ": not found")
143 (selectPublicKey s db)
144 $ return . sshblobFromPacket
145
146-- |
147-- interpolate %var patterns in a string.
148interp vars raw = es >>= interp1
149 where
150 gs = groupBy (\_ c -> c/='%') raw
151 es = dropWhile null $ gobbleEscapes ("":gs)
152 where gobbleEscapes :: [String] -> [String]
153 gobbleEscapes (a:"%":b:bs) = (a++b) : gobbleEscapes bs
154 gobbleEscapes (g:gs) = g : gobbleEscapes gs
155 gobbleEscapes [] = []
156 interp1 ('%':'(':str) = fromMaybe "" (Map.lookup key vars) ++ drop 1 rest
157 where (key,rest) = break (==')') str
158 interp1 plain = plain
159
160sshblobFromPacket k = blob
161 where
162 Just (RSAKey (MPI n) (MPI e)) = rsaKeyFromPacket k
163 bs = SSH.keyblob (n,e)
164 blob = Char8.unpack bs
165
166ㄧhomedir = Kiki.CommonArgsParsed
167 <$> optional (arg "--homedir")
168 <*> optional (FileDesc <$> read <$> arg "--passphrase-fd")
169