debug-me 1.20170509 → 1.20170510
raw patch · 11 files changed
+249/−16 lines, 11 filesbinary-added
Files
- CHANGELOG +16/−0
- ControlWindow.hs +3/−0
- Crypto.hs +1/−1
- Gpg/Keyring.hs +73/−0
- Gpg/Wot.hs +4/−1
- Hash.hs +18/−7
- Makefile +3/−0
- Verify.hs +110/−0
- debug-me.1 +10/−3
- debug-me.cabal +11/−4
- developer-keyring.gpg binary
CHANGELOG view
@@ -1,3 +1,19 @@+debug-me (1.20170520) unstable; urgency=medium++ * debug-me is available in Debian unstable.+ * gpg keyrings in /usr/share/debug-me/ will be checked+ to see if a connecting person is a known developer of software+ installed on the system, and so implicitly trusted already.+ Software packages/projects can install keyrings to that location.+ (Thanks to Sean Whitton for the idea.)+ * make install installs /usr/share/debug-me/a_debug-me_developer.gpg,+ which contains the key of Joey Hess. (stack and cabal installs don't+ include this file because they typically don't install system-wide)+ * debug-me.cabal: Added dependency on time.+ * stack.yaml: Update to new posix-pty version.++ -- Joey Hess <id@joeyh.name> Sat, 20 May 2017 17:13:11 -0400+ debug-me (1.20170509) unstable; urgency=medium * Server: Use "postmaster" as default --from-email address
ControlWindow.hs view
@@ -15,6 +15,7 @@ import VirtualTerminal import Gpg import Gpg.Wot+import Gpg.Keyring import Output import System.IO@@ -163,6 +164,8 @@ ws <- downloadWotStats gpgkeyid putStrLn $ unlines $ map sanitizeForDisplay $ describeWot ws ss+ mapM_ (putStrLn . keyringToDeveloperDesc ws)+ =<< findKeyringsContaining gpgkeyid promptconnect where promptconnect :: IO ()
Crypto.hs view
@@ -31,7 +31,7 @@ instance Hashable a => Signed (Activity a) where getSignature = activitySignature hashExceptSignature (Activity a mpa mpe mt _s) = hash $- Tagged "Activity" [hash a, hash mpa, hash mpe, hash mt]+ Tagged "Activity" [hash a, hashOfMaybeUnsafe mpa, hashOfMaybeUnsafe mpe, hash mt] instance Signed Control where getSignature = controlSignature
+ Gpg/Keyring.hs view
@@ -0,0 +1,73 @@+{- Copyright 2017 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++-- | Gpg keyrings for debug-me++module Gpg.Keyring where++import Gpg+import qualified Gpg.Wot++import System.FilePath+import Data.Char+import System.Directory+import Data.Time.Clock+import Data.Time.Format+import System.Process+import System.Exit++keyringDir :: FilePath+keyringDir = "/usr/share/debug-me/keyring"++data Keyring = Keyring FilePath UTCTime++keyringToDeveloperDesc :: Maybe (Gpg.Wot.WotStats) -> Keyring -> String+keyringToDeveloperDesc mws (Keyring f mtime) =+ name ++ " is " ++ desc ++ " \t(as of " ++ showtime mtime ++ ")"+ where+ name = maybe "This person" Gpg.Wot.wotStatName mws+ desc = map sanitize $ dropExtension $ takeFileName f+ sanitize '_' = ' '+ sanitize c+ | isAlphaNum c || c `elem` "-+" = c+ | otherwise = '?'+ showtime = formatTime defaultTimeLocale "%c"++findKeyringsContaining :: GpgKeyId -> IO [Keyring]+findKeyringsContaining k = + go [] . map (keyringDir </>) =<< getDirectoryContents keyringDir+ where+ go c [] = return c+ go c (f:fs) = do+ isfile <- doesFileExist f+ if isfile && takeExtension f == ".gpg"+ then do+ inkeyring <- isInKeyring k f+ if inkeyring+ then do+ mtime <- getModificationTime f+ let keyring = Keyring f mtime+ go (keyring : c) fs+ else go c fs+ else go c fs++-- | Check if the gpg key is included in the keyring file.+--+-- Similar to gpgv, this does not check if the key is revoked or expired,+-- only if it's included in the keyring.+isInKeyring :: GpgKeyId -> FilePath -> IO Bool+isInKeyring (GpgKeyId k) f = do+ -- gpg assumes non-absolute keyring files are relative to ~/.gnupg/+ absf <- makeAbsolute f+ let p = proc "gpg"+ -- Avoid reading any keyrings except the specified one.+ [ "--no-options"+ , "--no-default-keyring"+ , "--no-auto-check-trustdb"+ , "--keyring", absf+ , "--list-key", k+ ]+ (exitcode, _, _) <- readCreateProcessWithExitCode p ""+ return (exitcode == ExitSuccess)
Gpg/Wot.hs view
@@ -107,13 +107,16 @@ , theirname ++ " is probably a real person." ] where- theirname = stripEmail (uid (key ws))+ theirname = wotStatName ws sigs = cross_sigs ws ++ other_sigs ws bestconnectedsigs = sortOn rank sigs describeWot Nothing _ = [ "" , "Their identity cannot be verified!" ]++wotStatName :: WotStats -> String+wotStatName ws = stripEmail (uid (key ws)) stripEmail :: String -> String stripEmail = unwords . takeWhile (not . ("<" `isPrefixOf`)) . words
Hash.hs view
@@ -41,7 +41,7 @@ instance Hashable a => Hashable (Activity a) where hash (Activity a mps mpe mt s) = hash $ Tagged "Activity"- [hash a, hash mps, hash mpe, hash mt, hash s]+ [hash a, hashOfMaybeUnsafe mps, hashOfMaybeUnsafe mpe, hash mt, hash s] instance Hashable Entered where hash v = hash $ Tagged "Entered"@@ -52,7 +52,7 @@ instance Hashable ControlAction where hash (EnteredRejected h1 h2) = hash $ Tagged "EnteredRejected"- [hash h1, hash h2]+ [hash h1, hashOfMaybeUnsafe h2] hash (SessionKey pk v) = hash $ Tagged "SessionKey" [hash pk, hash v] hash (SessionKeyAccepted pk) = hash $ Tagged "SessionKeyAccepted" pk hash (SessionKeyRejected pk) = hash $ Tagged "SessionKeyRejected" pk@@ -83,10 +83,21 @@ instance Hashable [Hash] where hash = hash . B.concat . map (val . hashValue) --- | Hash empty string for Nothing+-- | Hash a Maybe Hash, such that +-- hash Nothing /= hash (Just (hash (mempty :: B.ByteString))) instance Hashable (Maybe Hash) where- hash Nothing = hash ()- hash (Just v) = hash v+ hash (Just v) = hash (val (hashValue v))+ hash Nothing = hash (mempty :: B.ByteString) -instance Hashable () where- hash () = hash (mempty :: B.ByteString)+-- | Hash a Maybe Hash using the Hash value as-is, or the hash of the empty+-- string for Nothing.+--+-- Note that this is only safe to use when the input value can't possibly+-- itself be the hash of an empty string. For example, the hash of an+-- Activity is safe, because it's the hash of a non-empty string.+--+-- This is only used to avoid breaking backwards compatability; the+-- above instance for Maybe Hash should be used for anything new.+hashOfMaybeUnsafe :: Maybe Hash -> Hash+hashOfMaybeUnsafe (Just v) = hash v+hashOfMaybeUnsafe Nothing = hash (mempty :: B.ByteString)
Makefile view
@@ -61,6 +61,9 @@ install -m 0755 debug-me.init $(DESTDIR)$(PREFIX)/etc/init.d/debug-me install -d $(DESTDIR)$(PREFIX)/etc/default install -m 0644 debug-me.default $(DESTDIR)$(PREFIX)/etc/default/debug-me+ install -d $(DESTDIR)$(PREFIX)/usr/share/debug-me/keyring+ install -m 0655 developer-keyring.gpg \+ $(DESTDIR)$(PREFIX)/usr/share/debug-me/keyring/a_debug-me_developer.gpg install-mans: install -d $(DESTDIR)$(PREFIX)/usr/share/man/man1
+ Verify.hs view
@@ -0,0 +1,110 @@+{- Copyright 2017 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Verify (verify) where++import Types+import Log+import CmdLine+import Crypto+import Gpg+import Hash+import PrevActivity+import Output++import Control.Concurrent.STM+import Data.Maybe+import Data.ByteString.UTF8 (toString)++verify :: VerifyOpts -> IO ()+verify opts = go 1 startState =<< streamLog (verifyLogFile opts)+ where+ go _ _ [] = putStrLn "Log file verified. All signatures and hashes are valid."+ go lineno st (Right lmsg:rest) = do+ -- The log may have hashes removed, as it went over the+ -- wire; restore hashes.+ let ra = mkRecentActivity st+ msg <- atomically $ restoreHashes ra $ + MissingHashes $ loggedMessage lmsg++ -- Learn session keys before verifying signatures.+ st' <- case msg of+ User (ControlMessage (Control { control = SessionKey sk _ })) ->+ addSessionKey lineno sk st+ Developer (ControlMessage (Control { control = SessionKey sk _ })) ->+ addSessionKey lineno sk st+ _ -> return st++ case (verifySigned (sigVerifier st') msg, verifyHashChain msg st') of+ (True, True) -> return ()+ (False, _) -> lineError lineno + "Failed to verify message signature."+ (_, False) -> lineError lineno+ "Invalid hash chain."++ go (succ lineno) (addPrevHash msg st') rest+ go lineno _ (Left l:_) = lineError lineno $+ "Failed to parse a line of the log: " ++ l++lineError :: Integer -> String -> a+lineError lineno err = error $ "Line " ++ show lineno ++ ": " ++ err++data State = State+ { sigVerifier :: SigVerifier+ , prevHashes :: [Hash]+ }+ deriving (Show)++startState :: State+startState = State+ { sigVerifier = mempty+ , prevHashes = mempty -- ^ in reverse order+ }++mkRecentActivity :: State -> RecentActivity+mkRecentActivity st = return (sigVerifier st, prevHashes st)++addSessionKey :: Integer -> PerhapsSigned PublicKey -> State -> IO State+addSessionKey lineno p@(GpgSigned pk _ _) st = do+ (mkid, gpgoutput) <- gpgVerify p+ putStr $ unlines $ map sanitizeForDisplay $ lines $ toString gpgoutput+ case mkid of+ Nothing -> lineError lineno "Bad GnuPG signature."+ Just _ -> do+ putStrLn "The person above participated in the debug-me session."+ addSessionKey lineno (UnSigned pk) st+addSessionKey _lineno (UnSigned pk) st = do+ let v = mkSigVerifier pk+ return $ st { sigVerifier = sigVerifier st `mappend` v }++-- | Add the hash of the message to the state.+addPrevHash :: AnyMessage -> State -> State+addPrevHash am s = case mh of+ Just h -> s { prevHashes = h : prevHashes s }+ Nothing -> s+ where+ mh = case am of+ User (ActivityMessage m) -> Just (hash m)+ User (ControlMessage _) -> Nothing+ Developer (ActivityMessage m) -> Just (hash m)+ Developer (ControlMessage _) -> Nothing++-- | Verify that prevActivity and prevEntered point to previously+-- seen hashes.+--+-- While Role.User and Role.Developer enforce rules about which+-- hashes they may point to, here we don't check such rules. If the log+-- continues with other messages referring to this one, then this message+-- must have met the rules, and if it did not, then this message is+-- irrelevant.+verifyHashChain :: AnyMessage -> State -> Bool+verifyHashChain am s = case am of+ User (ControlMessage _) -> True+ Developer (ControlMessage _) -> True+ User (ActivityMessage m) -> checkm m+ Developer (ActivityMessage m) -> checkm m+ where+ checkm m = all (\h -> h `elem` prevHashes s)+ (catMaybes [prevActivity m, prevEntered m])
debug-me.1 view
@@ -14,13 +14,16 @@ A debug-me session is logged and signed with the developer's GnuPG key, producing a chain of evidence of what they saw and what they did. So the developer's good reputation is leveraged to make debug-me secure.+If you trust a developer to ship software to your computer,+you can trust them to debug-me. .PP When you start debug-me without any options, it will connect to a debug-me server, and print out an url that you can give to the developer to get them connected to you. Then debug-me will show you their GnuPG key and who-has signed it. If the developer has a good reputation, you can proceed-to let them type into your console in a debug-me session. Once the-session is done, the debug-me server will email you the signed+has signed it, and will let you know if they are a known developer+of software on your computer. If the developer has a good reputation, you+can proceed to let them type into your console in a debug-me session. Once+the session is done, the debug-me server will email you the signed evidence of what the developer did in the session. .PP It's a good idea to watch the debug-me session. The developer should be@@ -101,6 +104,10 @@ .IP "~/.debug-me/log/remote/" When using debug-me to connect to a remote session, the session will be logged to here.+.UP "/usr/share/debug-me/keyring/*.gpg"+When verifying a developer's gpg key, debug-me checks if it's listed in +the keyrings in this directory, which can be provided by software installed+on the computer. .SH SEE ALSO <https://debug-me.branchable.com/> .PP
debug-me.cabal view
@@ -1,5 +1,5 @@ Name: debug-me-Version: 1.20170509+Version: 1.20170510 Cabal-Version: >= 1.8 Maintainer: Joey Hess <joey@kitenet.net> Author: Joey Hess@@ -20,13 +20,16 @@ A debug-me session is logged and signed with the developer's GnuPG key, producing a chain of evidence of what they saw and what they did. So the developer's good reputation is leveraged to make debug-me secure.+ If you trust a developer to ship software to your computer,+ you can trust them to debug-me. . When you start debug-me without any options, it will connect to a debug-me server, and print out an url that you can give to the developer to get them connected to you. Then debug-me will show you their GnuPG key and who- has signed it. If the developer has a good reputation, you can proceed- to let them type into your console in a debug-me session. Once the- session is done, the debug-me server will email you the signed+ has signed it, and will let you know if they are a known developer+ of software on your computer. If the developer has a good reputation,+ you can proceed to let them type into your console in a debug-me session.+ Once the session is done, the debug-me server will email you the signed evidence of what the developer did in the session. . If the developer did do something bad, you'd have proof that they cannot@@ -40,6 +43,7 @@ debug-me.service debug-me.init debug-me.default+ developer-keyring.gpg Executable debug-me Main-Is: debug-me.hs@@ -81,6 +85,7 @@ , utf8-string (>= 1.0) , network-uri (>= 2.6) , mime-mail (>= 0.4)+ , time (>= 1.6) Other-Modules: ControlWindow ControlSocket@@ -90,6 +95,7 @@ Graphviz Gpg Gpg.Wot+ Gpg.Keyring Hash JSON Log@@ -109,6 +115,7 @@ SessionID Types Val+ Verify VirtualTerminal WebSockets
+ developer-keyring.gpg view
binary file changed (absent → 5646 bytes)