diff options
Diffstat (limited to 'lib/Kiki.hs')
-rw-r--r-- | lib/Kiki.hs | 182 |
1 files changed, 158 insertions, 24 deletions
diff --git a/lib/Kiki.hs b/lib/Kiki.hs index f5490e0..468394f 100644 --- a/lib/Kiki.hs +++ b/lib/Kiki.hs | |||
@@ -2,14 +2,16 @@ | |||
2 | {-# LANGUAGE OverloadedStrings #-} | 2 | {-# LANGUAGE OverloadedStrings #-} |
3 | module Kiki where | 3 | module Kiki where |
4 | 4 | ||
5 | import Control.Exception | ||
6 | import Control.Applicative | 5 | import Control.Applicative |
7 | import Control.Arrow | 6 | import Control.Arrow |
7 | import Control.Concurrent | ||
8 | import Control.Exception | ||
8 | import Control.Monad | 9 | import Control.Monad |
9 | import Data.ASN1.BinaryEncoding | 10 | import Data.ASN1.BinaryEncoding |
10 | import Data.ASN1.Encoding | 11 | import Data.ASN1.Encoding |
11 | import Data.ASN1.Types | 12 | import Data.ASN1.Types |
12 | import Data.Binary | 13 | import Data.Binary |
14 | import Data.Char | ||
13 | import Data.List | 15 | import Data.List |
14 | import Data.Maybe | 16 | import Data.Maybe |
15 | import Data.Monoid | 17 | import Data.Monoid |
@@ -21,11 +23,17 @@ import System.FilePath.Posix as FilePath | |||
21 | import System.IO | 23 | import System.IO |
22 | import System.IO.Temp | 24 | import System.IO.Temp |
23 | import System.IO.Error | 25 | import System.IO.Error |
26 | import System.Posix.IO as Posix (createPipe) | ||
24 | import System.Posix.User | 27 | import System.Posix.User |
25 | import System.Process | 28 | import System.Process |
26 | import System.Posix.Files | 29 | import System.Posix.Files |
27 | import qualified Data.Traversable as T (mapM) | 30 | import qualified Data.Traversable as T (mapM) |
31 | #if defined(VERSION_memory) | ||
32 | import qualified Data.ByteString.Char8 as S8 | ||
33 | import Data.ByteArray.Encoding | ||
34 | #elif defined(VERSION_dataenc) | ||
28 | import qualified Codec.Binary.Base64 as Base64 | 35 | import qualified Codec.Binary.Base64 as Base64 |
36 | #endif | ||
29 | import qualified Data.ByteString.Lazy as L | 37 | import qualified Data.ByteString.Lazy as L |
30 | import qualified Data.ByteString.Lazy.Char8 as Char8 | 38 | import qualified Data.ByteString.Lazy.Char8 as Char8 |
31 | import qualified Data.Map.Strict as Map | 39 | import qualified Data.Map.Strict as Map |
@@ -33,10 +41,50 @@ import qualified SSHKey as SSH | |||
33 | import Network.Socket -- (SockAddr) | 41 | import Network.Socket -- (SockAddr) |
34 | import ProcessUtils | 42 | import ProcessUtils |
35 | 43 | ||
44 | import GnuPGAgent (Query(..)) | ||
36 | import CommandLine | 45 | import CommandLine |
37 | import KeyRing | 46 | import KeyRing |
38 | import DotLock | 47 | import DotLock |
39 | 48 | ||
49 | withAgent :: [PassphraseSpec] -> [PassphraseSpec] | ||
50 | withAgent [] = [PassphraseAgent] | ||
51 | withAgent ps = ps | ||
52 | |||
53 | ciphername Unencrypted = "-" | ||
54 | ciphername TripleDES = "3des" | ||
55 | ciphername (SymmetricAlgorithm w8) = "cipher-"++show w8 | ||
56 | ciphername c = map toLower $ show c | ||
57 | |||
58 | cipherFromString "clear" = Unencrypted | ||
59 | cipherFromString "unencrypted" = Unencrypted | ||
60 | cipherFromString s = | ||
61 | case filter ( (== s) . ciphername) ciphers of | ||
62 | x:_ -> x | ||
63 | -- _ | all isHexDigit s -> unhex s | ||
64 | _ -> error $ "known ciphers: "++unwords (map ciphername ciphers) | ||
65 | {- | ||
66 | where | ||
67 | #if defined(VERSION_memory) | ||
68 | unhex hx = case convertFromBase Base16 (S8.pack hx) of | ||
69 | Left e -> do | ||
70 | -- Useful for debugging but insecure generally ;) | ||
71 | -- putStrLn $ "convertFromBase error for input "++show hx++": "++show e | ||
72 | return Nothing | ||
73 | Right bs -> return $ Just $ S8.unpack bs | ||
74 | #elif defined(VERSION_dataenc) | ||
75 | unhex hx = maybe (return () {- putStrLn $ "dataenc error for input "++show hx -}) | ||
76 | return | ||
77 | $ fmap (map $ chr . fromIntegral) $ Base16.decode hx | ||
78 | #endif | ||
79 | -} | ||
80 | |||
81 | |||
82 | ciphers :: [SymmetricAlgorithm] | ||
83 | ciphers = takeWhile notFallback $ map toEnum $ [0..4]++[7..] | ||
84 | where | ||
85 | notFallback (SymmetricAlgorithm _) = False | ||
86 | notFallback _ = True | ||
87 | |||
40 | -- | | 88 | -- | |
41 | -- Regenerate /var/cache/kiki | 89 | -- Regenerate /var/cache/kiki |
42 | refresh :: (FilePath -> FilePath) -> CommonArgsParsed -> IO () | 90 | refresh :: (FilePath -> FilePath) -> CommonArgsParsed -> IO () |
@@ -78,8 +126,8 @@ minimalOp cap = op | |||
78 | [ ( HomeSec, streaminfo { access = Sec }) | 126 | [ ( HomeSec, streaminfo { access = Sec }) |
79 | , ( HomePub, streaminfo { access = Pub }) | 127 | , ( HomePub, streaminfo { access = Pub }) |
80 | ] | 128 | ] |
81 | , opPassphrases = do pfile <- maybeToList (cap_passfd cap) | 129 | , opPassphrases = withAgent $ do pfile <- maybeToList (cap_passfd cap) |
82 | return $ PassphraseSpec Nothing Nothing pfile | 130 | return $ PassphraseSpec Nothing Nothing pfile |
83 | , opTransforms = [] | 131 | , opTransforms = [] |
84 | , opHome = cap_homespec cap | 132 | , opHome = cap_homespec cap |
85 | } | 133 | } |
@@ -95,8 +143,8 @@ outputReport report = do | |||
95 | forM_ report $ \(fname,act) -> do | 143 | forM_ report $ \(fname,act) -> do |
96 | putStrLn $ fname ++ ": " ++ reportString act | 144 | putStrLn $ fname ++ ": " ++ reportString act |
97 | 145 | ||
98 | importAndRefresh :: (FilePath -> FilePath) -> CommonArgsParsed -> IO () | 146 | importAndRefresh :: (FilePath -> FilePath) -> CommonArgsParsed -> SymmetricAlgorithm -> IO () |
99 | importAndRefresh root cmn = do | 147 | importAndRefresh root cmn cipher = do |
100 | let rootdir = do guard (root "x" /= "x") | 148 | let rootdir = do guard (root "x" /= "x") |
101 | Just $ root "" | 149 | Just $ root "" |
102 | 150 | ||
@@ -122,7 +170,13 @@ importAndRefresh root cmn = do | |||
122 | old_umask <- setFileCreationMask(0o077); | 170 | old_umask <- setFileCreationMask(0o077); |
123 | -- Generate secring.gpg if it does not exist... | 171 | -- Generate secring.gpg if it does not exist... |
124 | gotsec <- doesFileExist secring | 172 | gotsec <- doesFileExist secring |
125 | when (not gotsec) $ do | 173 | |
174 | let passfd = cap_passfd cmn | ||
175 | |||
176 | (torgen,pwds) <- | ||
177 | if gotsec | ||
178 | then return (Generate 0 $ GenRSA $ 1024 `div` 8, []) | ||
179 | else do | ||
126 | {- ssh-keygen to create master key... | 180 | {- ssh-keygen to create master key... |
127 | let mkpath = home ++ "/master-key" | 181 | let mkpath = home ++ "/master-key" |
128 | mkdirFor mkpath | 182 | mkdirFor mkpath |
@@ -135,12 +189,46 @@ importAndRefresh root cmn = do | |||
135 | HomeSec | 189 | HomeSec |
136 | ( encode $ Message [mk { is_subkey = False }] ) | 190 | ( encode $ Message [mk { is_subkey = False }] ) |
137 | -} | 191 | -} |
138 | master <- (\k -> k { is_subkey = False }) <$> generateKey (GenRSA $ 4096 `div` 8 ) | 192 | master_un <- (\k -> MappedPacket (k { is_subkey = False }) Map.empty) <$> generateKey (GenRSA $ 4096 `div` 8 ) |
139 | mkdirFor secring | 193 | tor_un <- generateKey (GenRSA $ 1024 `div` 8 ) |
140 | writeInputFileL (InputFileContext secring pubring) | 194 | (read_tor,write_tor) <- Posix.createPipe |
141 | HomeSec | 195 | do rs <- writeKeyToFile (streaminfo { typ = PEMFile, access = Sec, spill = KF_Match "tor", fill = KF_All }) (FileDesc write_tor) tor_un |
142 | $ encode $ Message [master { is_subkey = False}] | 196 | -- outputReport $ map (first show) rs |
143 | 197 | return () | |
198 | let cipher's2k = (cipher {- AES128 -}, IteratedSaltedS2K SHA1 4073382889203176146 7864320) | ||
199 | ctx = InputFileContext secring pubring | ||
200 | main_passwds = withAgent $ do pfd <- maybeToList passfd | ||
201 | return $ PassphraseSpec Nothing Nothing pfd | ||
202 | passwordop = KeyRingOperation | ||
203 | { opFiles = Map.empty | ||
204 | , opPassphrases = main_passwds | ||
205 | , opHome = homespec | ||
206 | , opTransforms = [] | ||
207 | } | ||
208 | let uidentry = Map.singleton (keykey $ packet master_un) | ||
209 | $ master_un { packet = Query (packet master_un) | ||
210 | (torUIDFromKey tor_un) | ||
211 | Nothing | ||
212 | } | ||
213 | transcoder <- makeMemoizingDecrypter passwordop ctx (Just master_un, uidentry) | ||
214 | master0 <- transcoder cipher's2k master_un | ||
215 | case master0 of | ||
216 | KikiSuccess master -> do | ||
217 | mkdirFor secring | ||
218 | writeInputFileL ctx | ||
219 | HomeSec | ||
220 | $ encode $ Message [master] | ||
221 | putStrLn "Wrote master key" | ||
222 | return (FileDesc read_tor, [PassphraseMemoizer transcoder]) | ||
223 | er -> do | ||
224 | hPutStrLn stderr ("warning: " ++ errorString er) | ||
225 | hPutStrLn stderr "warning: keys will not be encrypted."; | ||
226 | mkdirFor secring | ||
227 | writeInputFileL ctx | ||
228 | HomeSec | ||
229 | $ encode $ Message [packet master_un] | ||
230 | putStrLn "Wrote master key" | ||
231 | return (Generate 0 (GenRSA $ 1024 `div` 8 ), []) | ||
144 | gotpub <- doesFileExist pubring | 232 | gotpub <- doesFileExist pubring |
145 | when (not gotpub) $ do | 233 | when (not gotpub) $ do |
146 | mkdirFor pubring | 234 | mkdirFor pubring |
@@ -164,8 +252,7 @@ importAndRefresh root cmn = do | |||
164 | 252 | ||
165 | -- First, we ensure that the tor key exists and is imported | 253 | -- First, we ensure that the tor key exists and is imported |
166 | -- so that we know where to put the strongswan key. | 254 | -- so that we know where to put the strongswan key. |
167 | let passfd = cap_passfd cmn | 255 | let strm = StreamInfo { typ = KeyRingFile |
168 | strm = StreamInfo { typ = KeyRingFile | ||
169 | , fill = KF_None | 256 | , fill = KF_None |
170 | , spill = KF_All | 257 | , spill = KF_All |
171 | , access = AutoAccess | 258 | , access = AutoAccess |
@@ -191,21 +278,30 @@ importAndRefresh root cmn = do | |||
191 | { opFiles = Map.fromList $ | 278 | { opFiles = Map.fromList $ |
192 | [ ( HomeSec, buildStreamInfo KF_All KeyRingFile ) | 279 | [ ( HomeSec, buildStreamInfo KF_All KeyRingFile ) |
193 | , ( HomePub, (buildStreamInfo KF_All KeyRingFile) { access = Pub } ) | 280 | , ( HomePub, (buildStreamInfo KF_All KeyRingFile) { access = Pub } ) |
194 | , ( Generate 0 (GenRSA (1024 `div` 8)), strm { spill = KF_Match "tor" }) | 281 | , ( torgen , case torgen of |
195 | , ( Generate 1 (GenRSA (1024 `div` 8)), strm { spill = KF_Match "ipsec" }) | 282 | FileDesc _ -> StreamInfo { typ = PEMFile |
283 | , fill = KF_Match "tor" | ||
284 | , spill = KF_Match "tor" | ||
285 | , access = Sec | ||
286 | , initializer = NoCreate | ||
287 | , transforms = [] } | ||
288 | _ -> strm { spill = KF_Match "tor" }) | ||
289 | , ( Generate 1 (GenRSA (2048 `div` 8)), strm { spill = KF_Match "ipsec" }) | ||
196 | , ( ArgFile sshcpath, (peminfo 2048 "ssh-client") ) | 290 | , ( ArgFile sshcpath, (peminfo 2048 "ssh-client") ) |
197 | , ( ArgFile sshspath, (peminfo 2048 "ssh-server") ) | 291 | , ( ArgFile sshspath, (peminfo 2048 "ssh-server") ) |
292 | , ( Generate 2 (GenRSA (4096 `div` 8)), strm { spill = KF_Match "encrypt" }) | ||
293 | , ( Generate 3 (GenRSA (4096 `div` 8)), strm { spill = KF_Match "sign" }) | ||
198 | ] | 294 | ] |
199 | , opPassphrases = do pfd <- maybeToList passfd | 295 | , opPassphrases = withAgent $ pwds ++ do pfd <- maybeToList passfd |
200 | return $ PassphraseSpec Nothing Nothing pfd | 296 | return $ PassphraseSpec Nothing Nothing pfd |
201 | , opHome = homespec | 297 | , opHome = homespec |
202 | , opTransforms = [] | 298 | , opTransforms = [] |
203 | } | 299 | } |
204 | -- doNothing = return () | 300 | -- doNothing = return () |
205 | nop = KeyRingOperation | 301 | nop = KeyRingOperation |
206 | { opFiles = Map.empty | 302 | { opFiles = Map.empty |
207 | , opPassphrases = do pfd <- maybeToList passfd | 303 | , opPassphrases = withAgent $ do pfd <- maybeToList passfd |
208 | return $ PassphraseSpec Nothing Nothing pfd | 304 | return $ PassphraseSpec Nothing Nothing pfd |
209 | , opHome=homespec, opTransforms = [] | 305 | , opHome=homespec, opTransforms = [] |
210 | } | 306 | } |
211 | -- if bUnprivileged then doNothing else mkdirFor torpath | 307 | -- if bUnprivileged then doNothing else mkdirFor torpath |
@@ -299,12 +395,39 @@ refreshCache rt rootdir = do | |||
299 | wkkd = rtKeyDB rt Map.! keykey wk | 395 | wkkd = rtKeyDB rt Map.! keykey wk |
300 | getSecret tag = sortOn (Down . timestamp) | 396 | getSecret tag = sortOn (Down . timestamp) |
301 | $ getSubkeys Unsigned wk (keySubKeys wkkd) tag | 397 | $ getSubkeys Unsigned wk (keySubKeys wkkd) tag |
302 | 398 | exportOp = withOutgoing $ minimalOp (CommonArgsParsed (Just $ takeDirectory $ rtPubring rt) | |
399 | Nothing) | ||
400 | where | ||
401 | withOutgoing op = op | ||
402 | { opFiles = opFiles op `Map.union` Map.fromList outgoing_secrets | ||
403 | , opPassphrases = [PassphraseMemoizer (rtPassphrases rt)] | ||
404 | } | ||
405 | outgoing_secrets = | ||
406 | [ send "ipsec" (mkpath "ipsec.d/private/" ++ Char8.unpack oname++".pem") "missing ipsec key?" | ||
407 | , send "ssh-client" (mkpath "root/.ssh/id_rsa") "missing ssh-client key?" | ||
408 | , send "ssh-server" (mkpath "ssh_host_rsa_key") "missing ssh host key?" | ||
409 | , send "tor" (mkpath "tor/private_key") "missing tor key?" | ||
410 | ] | ||
411 | send usage path warning = | ||
412 | ( ArgFile path, StreamInfo { typ = PEMFile | ||
413 | , fill = KF_Match usage | ||
414 | , spill = KF_None | ||
415 | , access = Sec | ||
416 | , initializer = WarnMissing warning | ||
417 | , transforms = [] | ||
418 | }) | ||
419 | KikiResult rt' report <- runKeyRing exportOp | ||
420 | |||
421 | {- | ||
303 | let writeSecret tag path warning = do | 422 | let writeSecret tag path warning = do |
304 | let my_ks :: [Packet] | 423 | let my_ks :: [Packet] |
305 | my_ks = getSecret "ipsec" | 424 | my_ks = getSecret tag |
306 | case my_ks of | 425 | case my_ks of |
307 | sec:_ -> do report <- writeKeyToFile streaminfo { typ = PEMFile | 426 | se0:_ -> do sc1 <- rtPassphrases rt (Unencrypted,S2K 100 "") $ MappedPacket se0 Map.empty |
427 | let sec = case sc1 of | ||
428 | KikiSuccess s -> s | ||
429 | _ -> se0 | ||
430 | report <- writeKeyToFile streaminfo { typ = PEMFile | ||
308 | , access = Sec | 431 | , access = Sec |
309 | , spill = KF_All | 432 | , spill = KF_All |
310 | } | 433 | } |
@@ -335,6 +458,7 @@ refreshCache rt rootdir = do | |||
335 | writeSecret "tor" | 458 | writeSecret "tor" |
336 | (mkpath "tor/private_key") | 459 | (mkpath "tor/private_key") |
337 | "missing tor key?" | 460 | "missing tor key?" |
461 | -} | ||
338 | 462 | ||
339 | -- Finally, export public keys if they do not exist. | 463 | -- Finally, export public keys if they do not exist. |
340 | either warn (write $ mkpath "root/.ssh/id_rsa.pub") | 464 | either warn (write $ mkpath "root/.ssh/id_rsa.pub") |
@@ -364,6 +488,7 @@ refreshCache rt rootdir = do | |||
364 | ipsecs :: [Packet] | 488 | ipsecs :: [Packet] |
365 | ipsecs = sortOn (Down . timestamp) | 489 | ipsecs = sortOn (Down . timestamp) |
366 | $ getSubkeys CrossSigned their_master (keySubKeys kd) "ipsec" | 490 | $ getSubkeys CrossSigned their_master (keySubKeys kd) "ipsec" |
491 | -- ++ getSubkeys CrossSigned their_master (keySubKeys kd) "strongswan" | ||
367 | sshs :: [Packet] | 492 | sshs :: [Packet] |
368 | sshs = sortOn (Down . timestamp) | 493 | sshs = sortOn (Down . timestamp) |
369 | $ getSubkeys CrossSigned their_master (keySubKeys kd) "ssh-server" | 494 | $ getSubkeys CrossSigned their_master (keySubKeys kd) "ssh-server" |
@@ -449,7 +574,11 @@ sortOn f = | |||
449 | pemFromPacket k = do | 574 | pemFromPacket k = do |
450 | let rsa = pkcs8 . fromJust $ rsaKeyFromPacket k | 575 | let rsa = pkcs8 . fromJust $ rsaKeyFromPacket k |
451 | der = encodeASN1 DER (toASN1 rsa []) | 576 | der = encodeASN1 DER (toASN1 rsa []) |
577 | #if defined(VERSION_memory) | ||
578 | qq = S8.unpack $ convertToBase Base64 (L.toStrict der) | ||
579 | #elif defined(VERSION_dataenc) | ||
452 | qq = Base64.encode (L.unpack der) | 580 | qq = Base64.encode (L.unpack der) |
581 | #endif | ||
453 | return $ | 582 | return $ |
454 | writePEM "PUBLIC KEY" qq -- ("TODO "++show keyspec) | 583 | writePEM "PUBLIC KEY" qq -- ("TODO "++show keyspec) |
455 | 584 | ||
@@ -491,6 +620,7 @@ sshblobFromPacketL k = do | |||
491 | RSAKey (MPI n) (MPI e) <- rsaKeyFromPacket k | 620 | RSAKey (MPI n) (MPI e) <- rsaKeyFromPacket k |
492 | return $ SSH.keyblob (n,e) | 621 | return $ SSH.keyblob (n,e) |
493 | 622 | ||
623 | {- | ||
494 | replaceSshServerKeys root cmn = do | 624 | replaceSshServerKeys root cmn = do |
495 | let homepass' = cmn { cap_homespec = fmap root (cap_homespec cmn) } | 625 | let homepass' = cmn { cap_homespec = fmap root (cap_homespec cmn) } |
496 | replaceSSH op = op { opFiles = files } | 626 | replaceSSH op = op { opFiles = files } |
@@ -508,6 +638,7 @@ replaceSshServerKeys root cmn = do | |||
508 | "" -> Nothing | 638 | "" -> Nothing |
509 | pth -> Just pth | 639 | pth -> Just pth |
510 | err -> hPutStrLn stderr $ errorString err | 640 | err -> hPutStrLn stderr $ errorString err |
641 | -} | ||
511 | 642 | ||
512 | slash :: String -> String -> String | 643 | slash :: String -> String -> String |
513 | slash "/" ('/':xs) = '/':xs | 644 | slash "/" ('/':xs) = '/':xs |
@@ -523,8 +654,11 @@ slash (y:ys) xs = y:slash ys xs | |||
523 | <$> optional (arg "--homedir") | 654 | <$> optional (arg "--homedir") |
524 | <*> optional (FileDesc <$> read <$> arg "--passphrase-fd") | 655 | <*> optional (FileDesc <$> read <$> arg "--passphrase-fd") |
525 | 656 | ||
657 | ㄧcipher :: Args SymmetricAlgorithm | ||
658 | ㄧcipher = fromMaybe CAST5 <$> optional (cipherFromString <$> arg "--cipher") | ||
659 | |||
526 | kikiOptions :: ( [(String,Int)], [String] ) | 660 | kikiOptions :: ( [(String,Int)], [String] ) |
527 | kikiOptions = ( ss, ps ) | 661 | kikiOptions = ( ss, ps ) |
528 | where | 662 | where |
529 | ss = [("--chroot",1),("--passphrase-fd",1),("--homedir",1)] | 663 | ss = [("--chroot",1),("--passphrase-fd",1),("--homedir",1),("--cipher",1)] |
530 | ps = [] | 664 | ps = [] |