diff options
-rw-r--r-- | cokiki.hs | 2 | ||||
-rw-r--r-- | kiki.hs | 135 | ||||
-rw-r--r-- | lib/Kiki.hs | 172 |
3 files changed, 161 insertions, 148 deletions
@@ -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 |
@@ -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 |
1425 | kiki "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 | 1426 | kiki "init" args = run args $ importAndRefresh <$> ㄧchroot <*> ㄧhomedir |
1556 | 1427 | ||
1557 | kiki "delete" args | "--help" `elem` args = do | 1428 | kiki "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 #-} |
2 | module Kiki where | 2 | module Kiki where |
3 | 3 | ||
4 | import Control.Monad | ||
5 | import Control.Applicative | 4 | import Control.Applicative |
5 | import Control.Arrow | ||
6 | import Control.Monad | ||
7 | import Data.ASN1.BinaryEncoding | ||
8 | import Data.ASN1.Encoding | ||
9 | import Data.ASN1.Types | ||
10 | import Data.Binary | ||
6 | import Data.List | 11 | import Data.List |
7 | import Data.Maybe | 12 | import Data.Maybe |
13 | import Data.OpenPGP | ||
14 | import Data.OpenPGP.Util | ||
8 | import Data.Ord | 15 | import Data.Ord |
9 | import System.Directory | 16 | import System.Directory |
10 | import System.FilePath.Posix | 17 | import System.FilePath.Posix |
11 | import System.IO | 18 | import System.IO |
12 | import Data.OpenPGP | 19 | import System.Posix.User |
13 | import Data.OpenPGP.Util | ||
14 | import qualified Data.Map.Strict as Map | ||
15 | import qualified Codec.Binary.Base64 as Base64 | 20 | 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 | 21 | import qualified Data.ByteString.Lazy as L |
20 | import qualified Data.ByteString.Lazy.Char8 as Char8 | 22 | import qualified Data.ByteString.Lazy.Char8 as Char8 |
23 | import qualified Data.Map.Strict as Map | ||
24 | import qualified SSHKey as SSH | ||
21 | 25 | ||
22 | import CommandLine | 26 | import CommandLine |
23 | import qualified SSHKey as SSH | ||
24 | import KeyRing | 27 | import 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 | ||
76 | run :: [String] -> Args (IO ()) -> IO () | ||
77 | run args x = | ||
78 | case runArgs (parseInvocation (uncurry fancy kikiOptions "") args) x of | ||
79 | Left e -> hPutStrLn stderr $ usageErrorMessage e | ||
80 | Right io -> io | ||
81 | |||
82 | importAndRefresh :: (FilePath -> FilePath) -> CommonArgsParsed -> IO () | ||
83 | importAndRefresh 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 | ||
74 | refreshCache :: KeyRingRuntime -> Maybe FilePath -> IO () | 210 | refreshCache :: KeyRingRuntime -> Maybe FilePath -> IO () |
75 | refreshCache rt rootdir = do | 211 | refreshCache 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 | |||
179 | replaceSshServerKeys root cmn = do | 311 | replaceSshServerKeys 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 | |||
200 | slash :: String -> String -> String | 329 | slash :: String -> String -> String |
201 | slash "/" ('/':xs) = '/':xs | 330 | slash "/" ('/':xs) = '/':xs |
202 | slash "" ('/':xs) = '/':xs | 331 | slash "" ('/':xs) = '/':xs |
203 | slash "" xs = '/':xs | 332 | slash "" xs = '/':xs |
204 | slash (y:ys) xs = y:slash ys xs | 333 | slash (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 | |||
343 | kikiOptions :: ( [(String,Int)], [String] ) | ||
344 | kikiOptions = ( ss, ps ) | ||
345 | where | ||
346 | ss = [("--chroot",1),("--passphrase-fd",1),("--homedir",1)] | ||
347 | ps = [] | ||