{- Copyright 2016 Joey Hess - - Licensed under the GNU AGPL version 3 or higher. -} {-# LANGUAGE DeriveGeneric, BangPatterns #-} module BackupLog where import Types import Types.Server import Types.Cost import Utility.UserInfo import GHC.Generics import Data.Time.Clock.POSIX import Data.Aeson import Data.Maybe import System.FilePath import System.Directory import System.Posix.Files import qualified Data.ByteString.Lazy as B data BackupLog = BackupLog { logDate :: POSIXTime , logEvent :: BackupEvent } deriving (Show, Generic) instance ToJSON BackupLog instance FromJSON BackupLog -- | Log of a backup. -- -- If an attacker cracks the user's system and finds this stored -- on it, it should not help them recover keys from keysafe. -- -- That's why the Name used is not included; as knowing the name lets -- an attacker download shards and start password cracking. -- -- Including the password entropy does let an attacker avoid trying -- weak passwords and go right to passwords that are strong enough, but -- this should only half the password crack time at worst. data BackupEvent = BackupSkipped SecretKeySource | BackupMade { backupServers :: [ServerName] , backupSecretKeySource :: SecretKeySource , backupPasswordEntropy :: Int } deriving (Show, Generic) matchesSecretKeySource :: SecretKeySource -> BackupLog -> Bool matchesSecretKeySource a (BackupLog _ (BackupSkipped b)) = a == b matchesSecretKeySource a (BackupLog _ (BackupMade { backupSecretKeySource = b })) = a == b instance ToJSON BackupEvent instance FromJSON BackupEvent mkBackupLog :: BackupEvent -> IO BackupLog mkBackupLog evt = BackupLog <$> getPOSIXTime <*> pure evt backupMade :: [Server] -> SecretKeySource -> Entropy UnknownPassword -> BackupEvent backupMade servers sks (Entropy n) = BackupMade { backupServers = map serverName servers , backupSecretKeySource = sks , backupPasswordEntropy = n } backupLogFile :: IO FilePath backupLogFile = do home <- myHomeDir return $ home ".keysafe/backup.log" readBackupLogs :: IO [BackupLog] readBackupLogs = do f <- backupLogFile e <- doesFileExist f if e then fromMaybe [] . decode <$> B.readFile f else return [] storeBackupLog :: BackupLog -> IO () storeBackupLog r = do !rs <- readBackupLogs f <- backupLogFile let d = takeDirectory f createDirectoryIfMissing True d setFileMode d $ ownerReadMode `unionFileModes` ownerWriteMode `unionFileModes` ownerExecuteMode setPermissions d $ setOwnerReadable True $ setOwnerWritable True $ setOwnerExecutable True emptyPermissions B.writeFile f $ encode (r:rs)