summaryrefslogtreecommitdiff
path: root/lib/Kiki.hs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Kiki.hs')
-rw-r--r--lib/Kiki.hs182
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 #-}
3module Kiki where 3module Kiki where
4 4
5import Control.Exception
6import Control.Applicative 5import Control.Applicative
7import Control.Arrow 6import Control.Arrow
7import Control.Concurrent
8import Control.Exception
8import Control.Monad 9import Control.Monad
9import Data.ASN1.BinaryEncoding 10import Data.ASN1.BinaryEncoding
10import Data.ASN1.Encoding 11import Data.ASN1.Encoding
11import Data.ASN1.Types 12import Data.ASN1.Types
12import Data.Binary 13import Data.Binary
14import Data.Char
13import Data.List 15import Data.List
14import Data.Maybe 16import Data.Maybe
15import Data.Monoid 17import Data.Monoid
@@ -21,11 +23,17 @@ import System.FilePath.Posix as FilePath
21import System.IO 23import System.IO
22import System.IO.Temp 24import System.IO.Temp
23import System.IO.Error 25import System.IO.Error
26import System.Posix.IO as Posix (createPipe)
24import System.Posix.User 27import System.Posix.User
25import System.Process 28import System.Process
26import System.Posix.Files 29import System.Posix.Files
27import qualified Data.Traversable as T (mapM) 30import qualified Data.Traversable as T (mapM)
31#if defined(VERSION_memory)
32import qualified Data.ByteString.Char8 as S8
33import Data.ByteArray.Encoding
34#elif defined(VERSION_dataenc)
28import qualified Codec.Binary.Base64 as Base64 35import qualified Codec.Binary.Base64 as Base64
36#endif
29import qualified Data.ByteString.Lazy as L 37import qualified Data.ByteString.Lazy as L
30import qualified Data.ByteString.Lazy.Char8 as Char8 38import qualified Data.ByteString.Lazy.Char8 as Char8
31import qualified Data.Map.Strict as Map 39import qualified Data.Map.Strict as Map
@@ -33,10 +41,50 @@ import qualified SSHKey as SSH
33import Network.Socket -- (SockAddr) 41import Network.Socket -- (SockAddr)
34import ProcessUtils 42import ProcessUtils
35 43
44import GnuPGAgent (Query(..))
36import CommandLine 45import CommandLine
37import KeyRing 46import KeyRing
38import DotLock 47import DotLock
39 48
49withAgent :: [PassphraseSpec] -> [PassphraseSpec]
50withAgent [] = [PassphraseAgent]
51withAgent ps = ps
52
53ciphername Unencrypted = "-"
54ciphername TripleDES = "3des"
55ciphername (SymmetricAlgorithm w8) = "cipher-"++show w8
56ciphername c = map toLower $ show c
57
58cipherFromString "clear" = Unencrypted
59cipherFromString "unencrypted" = Unencrypted
60cipherFromString 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
82ciphers :: [SymmetricAlgorithm]
83ciphers = 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
42refresh :: (FilePath -> FilePath) -> CommonArgsParsed -> IO () 90refresh :: (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
98importAndRefresh :: (FilePath -> FilePath) -> CommonArgsParsed -> IO () 146importAndRefresh :: (FilePath -> FilePath) -> CommonArgsParsed -> SymmetricAlgorithm -> IO ()
99importAndRefresh root cmn = do 147importAndRefresh 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 =
449pemFromPacket k = do 574pemFromPacket 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{-
494replaceSshServerKeys root cmn = do 624replaceSshServerKeys 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
512slash :: String -> String -> String 643slash :: String -> String -> String
513slash "/" ('/':xs) = '/':xs 644slash "/" ('/':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
526kikiOptions :: ( [(String,Int)], [String] ) 660kikiOptions :: ( [(String,Int)], [String] )
527kikiOptions = ( ss, ps ) 661kikiOptions = ( 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 = []