diff options
author | joe <joe@jerkface.net> | 2016-04-25 20:20:45 -0400 |
---|---|---|
committer | joe <joe@jerkface.net> | 2016-04-25 20:20:45 -0400 |
commit | 6860098ed8f8b56eb5058e0c9c427abaa57021bf (patch) | |
tree | defc0ae2c6bcd08f489628be0633f99e6254a218 /lib/Kiki.hs | |
parent | 3c8536fd92043283d20b9e19ae488e7fe64af236 (diff) |
more work on cokiki (ssh-client)
Diffstat (limited to 'lib/Kiki.hs')
-rw-r--r-- | lib/Kiki.hs | 169 |
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 #-} | ||
1 | module Kiki where | 2 | module Kiki where |
2 | 3 | ||
4 | import Control.Monad | ||
5 | import Control.Applicative | ||
6 | import Data.List | ||
7 | import Data.Maybe | ||
8 | import Data.Ord | ||
9 | import System.Directory | ||
10 | import System.FilePath.Posix | ||
11 | import System.IO | ||
12 | import Data.OpenPGP | ||
13 | import Data.OpenPGP.Util | ||
14 | import qualified Data.Map.Strict as Map | ||
15 | import qualified Codec.Binary.Base64 as Base64 | ||
16 | import Data.ASN1.BinaryEncoding | ||
17 | import Data.ASN1.Encoding | ||
18 | import Data.ASN1.Types | ||
19 | import qualified Data.ByteString.Lazy as L | ||
20 | import qualified Data.ByteString.Lazy.Char8 as Char8 | ||
21 | |||
22 | import CommandLine | ||
23 | import qualified SSHKey as SSH | ||
24 | import KeyRing | ||
25 | |||
3 | -- | | 26 | -- | |
4 | -- Regenerate /var/cache/kiki | 27 | -- Regenerate /var/cache/kiki |
5 | refresh :: IO () | 28 | refresh :: (FilePath -> FilePath) -> CommonArgsParsed -> IO () |
6 | refresh = do | 29 | refresh 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 | |||
40 | data CommonArgsParsed = CommonArgsParsed { cap_homespec :: Maybe String, cap_passfd :: Maybe InputFile } | ||
41 | |||
42 | |||
43 | minimalOp :: CommonArgsParsed -> KeyRingOperation | ||
44 | minimalOp 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 | |||
65 | refreshCache :: KeyRingRuntime -> Maybe FilePath -> IO () | ||
66 | refreshCache 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) | ||
116 | sortOn :: Ord b => (a -> b) -> [a] -> [a] | ||
117 | sortOn f = | ||
118 | map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x)) | ||
119 | #endif | ||
120 | |||
121 | pemFromPacket 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 | |||
128 | show_pem keyspec wkgrip db = either warn putStrLn $ show_pem' keyspec wkgrip db pemFromPacket | ||
129 | |||
130 | show_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 | |||
136 | warn str = hPutStrLn stderr str | ||
137 | |||
138 | show_ssh keyspec wkgrip db = either warn putStrLn $ show_ssh' keyspec wkgrip db | ||
139 | |||
140 | show_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. | ||
148 | interp 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 | |||
160 | sshblobFromPacket 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 | |||