summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cokiki.hs2
-rw-r--r--kiki.hs135
-rw-r--r--lib/Kiki.hs172
3 files changed, 161 insertions, 148 deletions
diff --git a/cokiki.hs b/cokiki.hs
index 055be9b..8e013d6 100644
--- a/cokiki.hs
+++ b/cokiki.hs
@@ -40,7 +40,7 @@ main = do
40 ["ssh-server"] -> pure (whenRoot sshServer) 40 ["ssh-server"] -> pure (whenRoot sshServer)
41 ["strongswan"] -> pure (whenRoot strongswan) 41 ["strongswan"] -> pure (whenRoot strongswan)
42 _ -> pure $ hPutStr stderr usage 42 _ -> pure $ hPutStr stderr usage
43 spec = fancy [("--chroot",1),("--passphrase-fd",1),("--homedir",1)] [] "" 43 spec = uncurry fancy Kiki.kikiOptions ""
44 case runArgs (parseInvocation spec args) sel of 44 case runArgs (parseInvocation spec args) sel of
45 Left e -> hPutStrLn stderr $ usageErrorMessage e 45 Left e -> hPutStrLn stderr $ usageErrorMessage e
46 Right io -> io 46 Right io -> io
diff --git a/kiki.hs b/kiki.hs
index f239410..3f1843f 100644
--- a/kiki.hs
+++ b/kiki.hs
@@ -1417,142 +1417,13 @@ kiki "init" args | "--help" `elem` args = do
1417 , " --chroot=ROOTDIR" 1417 , " --chroot=ROOTDIR"
1418 , " Use ROOTDIR for input of ssh keys and export files to" 1418 , " Use ROOTDIR for input of ssh keys and export files to"
1419 , " ROOTDIR/var/cache/kiki instead of the current system path." 1419 , " ROOTDIR/var/cache/kiki instead of the current system path."
1420 , " When this option is specified, the GNUPGHOME environment" 1420 , " When this option is specified, the GNUPGHOME environment"
1421 , " variable is ignored and you must use --homedir to specify" 1421 , " variable is ignored and you must use --homedir to specify"
1422 , " a value other than /root/.gnupg." 1422 , " a value other than /root/.gnupg."
1423 , "" 1423 , ""
1424 ] ++ documentHomeDir ++ [""] ++ documentPassphraseFDFlag True True True 1424 ] ++ documentHomeDir ++ [""] ++ documentPassphraseFDFlag True True True
1425kiki "init" args = do
1426 me <- getEffectiveUserID
1427 {-
1428 if me/=0 then error "This command requires root." else do
1429 -}
1430 let as = lefts $ map splitArg args
1431 lefts = mapMaybe isLeft where { isLeft (Left x) = Just x; isLeft _ = Nothing }
1432 bads = map fst as \\ ["passphrase-fd","homedir","chroot"]
1433 if not (null bads) then error ("Bad option: " ++ unwords bads) else do
1434 let rootdir = fmap (fromMaybe "") $ lookup "chroot" as
1435 let noChrootArg = rootdir == Nothing
1436 bUnprivileged = (me/=0) && noChrootArg
1437 if rootdir==Just "" then error "--chroot requires an argument" else do
1438 -- maybe id fchroot rootdir $ do
1439 args <- return $ map (second $ fromMaybe "") as
1440
1441 let homespec = mplus (slash <$> rootdir <*> lookup "homedir" args )
1442 (fmap (++"/root/.gnupg") rootdir)
1443 sshkeygen size = Just $ concat [ "mkdir -p \"$(dirname $file)\" && "
1444 , "ssh-keygen -P \"\" -q -f $file -b "
1445 , show size ]
1446 mkdirFor path = do
1447 let dir = takeDirectory path
1448 -- putStrLn $ "mkdirFor " ++ show dir
1449 createDirectoryIfMissing True dir
1450 -- ssl = Just "mkdir -p \"$(dirname $file)\" && openssl genrsa -out $file 1024"
1451 (home,secring,pubring,mbwk) <- unconditionally $ getHomeDir homespec
1452 osHomeDir <- if bUnprivileged then getHomeDirectory else return "/root"
1453 putStrLn $ "gnupg home = " ++ show (home,secring,pubring,mbwk)
1454 putStrLn $ "os home = " ++ show osHomeDir
1455 -- gnupg home = ("TESTS/tmpgh","TESTS/tmpgh/secring.gpg","TESTS/tmpgh/pubring.gpg",Nothing)
1456 -- os home = "/root"
1457
1458
1459 -- Generate secring.gpg if it does not exist...
1460 gotsec <- doesFileExist secring
1461 when (not gotsec) $ do
1462 {- ssh-keygen to create master key...
1463 let mkpath = home ++ "/master-key"
1464 mkdirFor mkpath
1465 e <- systemEnv [ ("file",mkpath) ] (fromJust $ sshkeygen 4096)
1466 case e of
1467 ExitFailure num -> error "ssh-keygen failed to create master key"
1468 ExitSuccess -> return ()
1469 [PEMPacket mk] <- readSecretPEMFile (ArgFile mkpath)
1470 writeInputFileL (InputFileContext secring pubring)
1471 HomeSec
1472 ( encode $ Message [mk { is_subkey = False }] )
1473 -}
1474 master <- (\k -> k { is_subkey = False }) <$> generateKey (GenRSA $ 4096 `div` 8 )
1475 mkdirFor secring
1476 writeInputFileL (InputFileContext secring pubring)
1477 HomeSec
1478 $ encode $ Message [master { is_subkey = False}]
1479
1480 gotpub <- doesFileExist pubring
1481 when (not gotpub) $ do
1482 mkdirFor pubring
1483 writeInputFileL (InputFileContext secring pubring)
1484 HomePub
1485 ( encode $ Message [] )
1486
1487 -- Old paths..
1488 --
1489 -- Private
1490 -- pem tor /var/lib/tor/samizdat/private_key
1491 -- pem ssh-client %(home)/.ssh/id_rsa
1492 -- pem ssh-server /etc/ssh/ssh_host_rsa_key
1493 -- pem ipsec /etc/ipsec.d/private/%(onion).pem
1494
1495 -- Public
1496 -- ssh-client %(home)/.ssh/id_rsa.pub
1497 -- ssh-server /etc/ssh/ssh_host_rsa_key.pub
1498 -- ipsec /etc/ipsec.d/certs/%(onion).pem
1499
1500 -- First, we ensure that the tor key exists and is imported
1501 -- so that we know where to put the strongswan key.
1502 let passfd = fmap (FileDesc . read) $ lookup "passphrase-fd" args
1503 strm = StreamInfo { typ = KeyRingFile
1504 , fill = KF_None
1505 , spill = KF_All
1506 , access = AutoAccess
1507 , initializer = NoCreate
1508 , transforms = [] }
1509 buildStreamInfo rtyp ftyp = StreamInfo { typ = ftyp
1510 , fill = rtyp
1511 , spill = KF_All
1512 , access = AutoAccess
1513 , initializer = NoCreate
1514 , transforms = [] }
1515 peminfo bits usage =
1516 StreamInfo { typ = PEMFile
1517 , fill = KF_None -- KF_Match usage
1518 , spill = KF_Match usage
1519 , access = Sec
1520 , initializer = Internal (GenRSA $ bits `div` 8)
1521 , transforms = []
1522 }
1523 sshcpath = fromMaybe "" rootdir ++ osHomeDir ++ ".ssh/id_rsa"
1524 sshspath = fromMaybe "" rootdir ++ "/etc/ssh/ssh_host_rsa_key"
1525 op = KeyRingOperation
1526 { opFiles = Map.fromList $
1527 [ ( HomeSec, buildStreamInfo KF_All KeyRingFile )
1528 , ( HomePub, (buildStreamInfo KF_All KeyRingFile) { access = Pub } )
1529 , ( Generate 0 (GenRSA (1024 `div` 8)), strm { spill = KF_Match "tor" })
1530 , ( Generate 1 (GenRSA (1024 `div` 8)), strm { spill = KF_Match "ipsec" })
1531 , ( ArgFile sshcpath, (peminfo 2048 "ssh-client") )
1532 , ( ArgFile sshspath, (peminfo 2048 "ssh-server") )
1533 ]
1534 , opPassphrases = do pfd <- maybeToList passfd
1535 return $ PassphraseSpec Nothing Nothing pfd
1536 , opHome = homespec
1537 , opTransforms = []
1538 }
1539 -- doNothing = return ()
1540 nop = KeyRingOperation
1541 { opFiles = Map.empty
1542 , opPassphrases = do pfd <- maybeToList passfd
1543 return $ PassphraseSpec Nothing Nothing pfd
1544 , opHome=homespec, opTransforms = []
1545 }
1546 -- if bUnprivileged then doNothing else mkdirFor torpath
1547 KikiResult rt report <- runKeyRing (if bUnprivileged then nop else op)
1548 forM_ report $ \(fname,act) -> do
1549 putStrLn $ fname ++ ": " ++ reportString act
1550 rt <- case rt of
1551 BadPassphrase ->
1552 error "Operation requires correct passphrase. (Hint: Use --passphrase-fd=0 to input it on stdin.)"
1553 _ -> unconditionally $ return rt
1554 1425
1555 when (not bUnprivileged) $ refreshCache rt rootdir 1426kiki "init" args = run args $ importAndRefresh <$> ㄧchroot <*> ㄧhomedir
1556 1427
1557kiki "delete" args | "--help" `elem` args = do 1428kiki "delete" args | "--help" `elem` args = do
1558 putStr . unlines $ 1429 putStr . unlines $
diff --git a/lib/Kiki.hs b/lib/Kiki.hs
index ec34542..1682811 100644
--- a/lib/Kiki.hs
+++ b/lib/Kiki.hs
@@ -1,26 +1,29 @@
1{-# LANGUAGE CPP #-} 1{-# LANGUAGE CPP #-}
2module Kiki where 2module Kiki where
3 3
4import Control.Monad
5import Control.Applicative 4import Control.Applicative
5import Control.Arrow
6import Control.Monad
7import Data.ASN1.BinaryEncoding
8import Data.ASN1.Encoding
9import Data.ASN1.Types
10import Data.Binary
6import Data.List 11import Data.List
7import Data.Maybe 12import Data.Maybe
13import Data.OpenPGP
14import Data.OpenPGP.Util
8import Data.Ord 15import Data.Ord
9import System.Directory 16import System.Directory
10import System.FilePath.Posix 17import System.FilePath.Posix
11import System.IO 18import System.IO
12import Data.OpenPGP 19import System.Posix.User
13import Data.OpenPGP.Util
14import qualified Data.Map.Strict as Map
15import qualified Codec.Binary.Base64 as Base64 20import 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 21import qualified Data.ByteString.Lazy as L
20import qualified Data.ByteString.Lazy.Char8 as Char8 22import qualified Data.ByteString.Lazy.Char8 as Char8
23import qualified Data.Map.Strict as Map
24import qualified SSHKey as SSH
21 25
22import CommandLine 26import CommandLine
23import qualified SSHKey as SSH
24import KeyRing 27import KeyRing
25 28
26-- | 29-- |
@@ -70,6 +73,139 @@ minimalOp cap = op
70 , opHome = cap_homespec cap 73 , opHome = cap_homespec cap
71 } 74 }
72 75
76run :: [String] -> Args (IO ()) -> IO ()
77run args x =
78 case runArgs (parseInvocation (uncurry fancy kikiOptions "") args) x of
79 Left e -> hPutStrLn stderr $ usageErrorMessage e
80 Right io -> io
81
82importAndRefresh :: (FilePath -> FilePath) -> CommonArgsParsed -> IO ()
83importAndRefresh root cmn = do
84 let rootdir = do guard (root "x" /= "x")
85 Just $ root ""
86
87 me <- getEffectiveUserID
88
89 let noChrootArg = rootdir == Nothing
90 bUnprivileged = (me/=0) && noChrootArg
91 if rootdir==Just "" then error "--chroot requires an argument" else do
92
93 let homespec = mplus (slash <$> rootdir <*> cap_homespec cmn)
94 (fmap (++"/root/.gnupg") rootdir)
95 sshkeygen size = Just $ concat [ "mkdir -p \"$(dirname $file)\" && "
96 , "ssh-keygen -P \"\" -q -f $file -b "
97 , show size ]
98 mkdirFor path = do
99 let dir = takeDirectory path
100 -- putStrLn $ "mkdirFor " ++ show dir
101 createDirectoryIfMissing True dir
102 -- ssl = Just "mkdir -p \"$(dirname $file)\" && openssl genrsa -out $file 1024"
103 (home,secring,pubring,mbwk) <- unconditionally $ getHomeDir homespec
104 osHomeDir <- if bUnprivileged then getHomeDirectory else return "/root"
105 putStrLn $ "gnupg home = " ++ show (home,secring,pubring,mbwk)
106 putStrLn $ "os home = " ++ show osHomeDir
107 -- gnupg home = ("TESTS/tmpgh","TESTS/tmpgh/secring.gpg","TESTS/tmpgh/pubring.gpg",Nothing)
108 -- os home = "/root"
109
110
111 -- Generate secring.gpg if it does not exist...
112 gotsec <- doesFileExist secring
113 when (not gotsec) $ do
114 {- ssh-keygen to create master key...
115 let mkpath = home ++ "/master-key"
116 mkdirFor mkpath
117 e <- systemEnv [ ("file",mkpath) ] (fromJust $ sshkeygen 4096)
118 case e of
119 ExitFailure num -> error "ssh-keygen failed to create master key"
120 ExitSuccess -> return ()
121 [PEMPacket mk] <- readSecretPEMFile (ArgFile mkpath)
122 writeInputFileL (InputFileContext secring pubring)
123 HomeSec
124 ( encode $ Message [mk { is_subkey = False }] )
125 -}
126 master <- (\k -> k { is_subkey = False }) <$> generateKey (GenRSA $ 4096 `div` 8 )
127 mkdirFor secring
128 writeInputFileL (InputFileContext secring pubring)
129 HomeSec
130 $ encode $ Message [master { is_subkey = False}]
131
132 gotpub <- doesFileExist pubring
133 when (not gotpub) $ do
134 mkdirFor pubring
135 writeInputFileL (InputFileContext secring pubring)
136 HomePub
137 ( encode $ Message [] )
138
139 -- Old paths..
140 --
141 -- Private
142 -- pem tor /var/lib/tor/samizdat/private_key
143 -- pem ssh-client %(home)/.ssh/id_rsa
144 -- pem ssh-server /etc/ssh/ssh_host_rsa_key
145 -- pem ipsec /etc/ipsec.d/private/%(onion).pem
146
147 -- Public
148 -- ssh-client %(home)/.ssh/id_rsa.pub
149 -- ssh-server /etc/ssh/ssh_host_rsa_key.pub
150 -- ipsec /etc/ipsec.d/certs/%(onion).pem
151
152 -- First, we ensure that the tor key exists and is imported
153 -- so that we know where to put the strongswan key.
154 let passfd = cap_passfd cmn
155 strm = StreamInfo { typ = KeyRingFile
156 , fill = KF_None
157 , spill = KF_All
158 , access = AutoAccess
159 , initializer = NoCreate
160 , transforms = [] }
161 buildStreamInfo rtyp ftyp = StreamInfo { typ = ftyp
162 , fill = rtyp
163 , spill = KF_All
164 , access = AutoAccess
165 , initializer = NoCreate
166 , transforms = [] }
167 peminfo bits usage =
168 StreamInfo { typ = PEMFile
169 , fill = KF_None -- KF_Match usage
170 , spill = KF_Match usage
171 , access = Sec
172 , initializer = Internal (GenRSA $ bits `div` 8)
173 , transforms = []
174 }
175 sshcpath = fromMaybe "" rootdir ++ osHomeDir ++ ".ssh/id_rsa"
176 sshspath = fromMaybe "" rootdir ++ "/etc/ssh/ssh_host_rsa_key"
177 op = KeyRingOperation
178 { opFiles = Map.fromList $
179 [ ( HomeSec, buildStreamInfo KF_All KeyRingFile )
180 , ( HomePub, (buildStreamInfo KF_All KeyRingFile) { access = Pub } )
181 , ( Generate 0 (GenRSA (1024 `div` 8)), strm { spill = KF_Match "tor" })
182 , ( Generate 1 (GenRSA (1024 `div` 8)), strm { spill = KF_Match "ipsec" })
183 , ( ArgFile sshcpath, (peminfo 2048 "ssh-client") )
184 , ( ArgFile sshspath, (peminfo 2048 "ssh-server") )
185 ]
186 , opPassphrases = do pfd <- maybeToList passfd
187 return $ PassphraseSpec Nothing Nothing pfd
188 , opHome = homespec
189 , opTransforms = []
190 }
191 -- doNothing = return ()
192 nop = KeyRingOperation
193 { opFiles = Map.empty
194 , opPassphrases = do pfd <- maybeToList passfd
195 return $ PassphraseSpec Nothing Nothing pfd
196 , opHome=homespec, opTransforms = []
197 }
198 -- if bUnprivileged then doNothing else mkdirFor torpath
199 KikiResult rt report <- runKeyRing (if bUnprivileged then nop else op)
200 forM_ report $ \(fname,act) -> do
201 putStrLn $ fname ++ ": " ++ reportString act
202 rt <- case rt of
203 BadPassphrase ->
204 error "Operation requires correct passphrase. (Hint: Use --passphrase-fd=0 to input it on stdin.)"
205 _ -> unconditionally $ return rt
206
207 when (not bUnprivileged) $ refreshCache rt rootdir
208
73 209
74refreshCache :: KeyRingRuntime -> Maybe FilePath -> IO () 210refreshCache :: KeyRingRuntime -> Maybe FilePath -> IO ()
75refreshCache rt rootdir = do 211refreshCache rt rootdir = do
@@ -172,10 +308,6 @@ sshblobFromPacket k = blob
172 bs = SSH.keyblob (n,e) 308 bs = SSH.keyblob (n,e)
173 blob = Char8.unpack bs 309 blob = Char8.unpack bs
174 310
175ㄧhomedir = Kiki.CommonArgsParsed
176 <$> optional (arg "--homedir")
177 <*> optional (FileDesc <$> read <$> arg "--passphrase-fd")
178
179replaceSshServerKeys root cmn = do 311replaceSshServerKeys root cmn = do
180 let homepass' = cmn { cap_homespec = fmap root (cap_homespec cmn) } 312 let homepass' = cmn { cap_homespec = fmap root (cap_homespec cmn) }
181 replaceSSH op = op { opFiles = files } 313 replaceSSH op = op { opFiles = files }
@@ -194,12 +326,22 @@ replaceSshServerKeys root cmn = do
194 pth -> Just pth 326 pth -> Just pth
195 err -> hPutStrLn stderr $ errorString err 327 err -> hPutStrLn stderr $ errorString err
196 328
197ㄧchroot :: Args (FilePath -> FilePath)
198ㄧchroot = pure (\r a -> slash r a) <*> arg "--chroot" <|> pure id
199
200slash :: String -> String -> String 329slash :: String -> String -> String
201slash "/" ('/':xs) = '/':xs 330slash "/" ('/':xs) = '/':xs
202slash "" ('/':xs) = '/':xs 331slash "" ('/':xs) = '/':xs
203slash "" xs = '/':xs 332slash "" xs = '/':xs
204slash (y:ys) xs = y:slash ys xs 333slash (y:ys) xs = y:slash ys xs
205 334
335ㄧchroot :: Args (FilePath -> FilePath)
336ㄧchroot = pure (\r a -> slash r a) <*> arg "--chroot" <|> pure id
337
338ㄧhomedir :: Args CommonArgsParsed
339ㄧhomedir = CommonArgsParsed
340 <$> optional (arg "--homedir")
341 <*> optional (FileDesc <$> read <$> arg "--passphrase-fd")
342
343kikiOptions :: ( [(String,Int)], [String] )
344kikiOptions = ( ss, ps )
345 where
346 ss = [("--chroot",1),("--passphrase-fd",1),("--homedir",1)]
347 ps = []