diff --git a/HasswordManager.cabal b/HasswordManager.cabal index afe4c68..e132257 100644 --- a/HasswordManager.cabal +++ b/HasswordManager.cabal @@ -41,6 +41,7 @@ library , ansi-terminal , base >=4.7 && <5 , bytestring + , cipher-aes , hashable , sqlite-simple , text @@ -62,6 +63,7 @@ executable HasswordManager-exe , ansi-terminal , base >=4.7 && <5 , bytestring + , cipher-aes , hashable , sqlite-simple , text @@ -84,6 +86,7 @@ test-suite HasswordManager-test , ansi-terminal , base >=4.7 && <5 , bytestring + , cipher-aes , hashable , sqlite-simple , text diff --git a/app/Main.hs b/app/Main.hs index 163c72d..8232de4 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -1,9 +1,10 @@ {-# LANGUAGE OverloadedStrings #-} module Main (main) where -import Data.ByteString.UTF8 (fromString) +import Data.ByteString.UTF8 (fromString, toString) import Database.SQLite.Simple import System.Hclip +import qualified Data.Text as T import qualified UserInterface as Ui import qualified Database as Db @@ -13,18 +14,19 @@ import qualified Utils as Ut main :: IO () main = do conn <- Db.init_connection - Ut.clear_screen + + Ut.clear_screen putStrLn "Welcome to Hassword Manager!!!" - welcome_screen conn + + setup_application conn mpass <- open_hassword_book conn - putStrLn mpass - setClipboard mpass - -- application_loop conn + application_loop conn mpass Db.close_connection conn -welcome_screen :: Connection -> IO () -welcome_screen conn = do + +setup_application :: Connection -> IO () +setup_application conn = do first <- Db.is_it_first_app_usage conn if first then do @@ -66,8 +68,8 @@ open_hassword_book :: Connection -> IO (String) open_hassword_book conn = do putStrLn "Please Enter your MASTER PASSWORD:" mpass <- Ut.get_password - res <- Db.check_if_mpass_valid conn (Cr.hash' $ fromString mpass) - if res + mpass_valid <- Db.check_if_mpass_valid conn (Cr.hash' $ fromString mpass) + if mpass_valid then return mpass else do @@ -76,7 +78,74 @@ open_hassword_book conn = do Ut.reset_color open_hassword_book conn -application_loop :: Connection -> IO () -application_loop conn = do - putStrLn "What would you like to do?" - application_loop conn +get_action_choice :: IO (Ut.ActionChoice) +get_action_choice = do + putStrLn "\nWhat would you like to do?" + putStrLn "1. List all entries" + putStrLn "2. Copy entry password" + putStrLn "3. Add a new entry" + putStrLn "4. Delete an entry" + putStrLn "5. Update an entry" + putStrLn "6. Change Master Password" + putStrLn "7. Close the application" + choice <- getLine + case choice of + "1" -> return Ut.ListAllEntries + "2" -> return Ut.CopyEntryPassword + "3" -> return Ut.AddNewEntry + "4" -> return Ut.DeleteEntry + "5" -> return Ut.UpdateEntry + "6" -> return Ut.ChangeMasterPassword + "7" -> return Ut.Exit + _ -> return Ut.InvalidAction + +application_loop :: Connection -> String -> IO () +application_loop conn mpass = do + choice <- get_action_choice + case choice of + Ut.ListAllEntries -> do + list_all_entries conn + application_loop conn mpass + Ut.CopyEntryPassword -> do + return () + application_loop conn mpass + Ut.AddNewEntry -> do + add_entry conn mpass + application_loop conn mpass + Ut.DeleteEntry -> do + return () + application_loop conn mpass + Ut.UpdateEntry -> do + return () + application_loop conn mpass + Ut.ChangeMasterPassword -> do + return () + application_loop conn mpass + Ut.InvalidAction -> do + putStrLn "Invalid choice!!!" + application_loop conn mpass + Ut.Exit -> do + return () + +list_all_entries :: Connection -> IO () +list_all_entries conn = do + entries <- Db.get_all_entries conn + Ui.print_entries entries + +add_entry :: Connection -> String -> IO () +add_entry conn mpass = do + putStrLn "Please Enter the service name:" + service <- getLine + putStrLn "Please Enter the login:" + login <- getLine + entry_exists <- Db.entry_already_exists conn (T.pack service) (T.pack login) + if entry_exists + then do + Ut.set_red + putStrLn "Entry already exists!!!" + Ut.reset_color + return () + else do + putStrLn "Please Enter the password:" + password <- Ut.get_password + Db.add_entry conn (T.pack service) (T.pack login) (T.pack (toString (Cr.encrypt' (fromString mpass) (fromString password)))) diff --git a/package.yaml b/package.yaml index 78b1b5e..21230af 100644 --- a/package.yaml +++ b/package.yaml @@ -27,6 +27,7 @@ dependencies: - utf8-string - ansi-terminal - Hclip +- cipher-aes - base >= 4.7 && < 5 ghc-options: diff --git a/src/Crypto.hs b/src/Crypto.hs index cc1356c..51df6a7 100644 --- a/src/Crypto.hs +++ b/src/Crypto.hs @@ -1,15 +1,28 @@ {-# LANGUAGE OverloadedStrings #-} module Crypto ( - -- encode, - -- decode, + encrypt', + decrypt', hash' ) where import Data.ByteString (ByteString) import Data.Hashable +import Crypto.Cipher.AES pepper :: ByteString pepper = "pepper" hash' :: ByteString -> Int hash' = hashWithSalt 0 . mappend pepper + +encrypt' :: ByteString -> ByteString -> ByteString +encrypt' key plain_text = plain_text + +decrypt' :: ByteString -> ByteString -> ByteString +decrypt' key cypher_text = cypher_text + +-- encrypt' :: ByteString -> ByteString -> ByteString +-- encrypt' = encryptECB . initAES + +-- decrypt' :: ByteString -> ByteString -> ByteString +-- decrypt' = decryptECB . initAES diff --git a/src/Database.hs b/src/Database.hs index c7b5b1d..a300917 100644 --- a/src/Database.hs +++ b/src/Database.hs @@ -1,29 +1,22 @@ {-# LANGUAGE OverloadedStrings #-} -module Database ( - Entry(..), - init_connection, - close_connection, - check_if_mpass_valid, - insert_new_mpass, - is_it_first_app_usage -) where +module Database where import Control.Applicative import qualified Data.Text as T import Database.SQLite.Simple -data Entry = Entry { entryId :: Int, entryLogin :: T.Text, entryPassword :: T.Text } deriving (Show) +data Entry = Entry { entryId :: Int, entryService :: T.Text, entryLogin :: T.Text, entryPassword :: T.Text } deriving (Show) data Mpass = Mpass { mpassId :: Int, hash :: Int } deriving (Show) instance FromRow Entry where - fromRow = Entry <$> field <*> field <*> field + fromRow = Entry <$> field <*> field <*> field <*> field instance FromRow Mpass where fromRow = Mpass <$> field <*> field instance ToRow Entry where - toRow (Entry id_ login password) = toRow (id_, login, password) + toRow (Entry id_ service login password) = toRow (id_, service, login, password) instance ToRow Mpass where toRow (Mpass id_ hash) = toRow (id_, hash) @@ -41,9 +34,9 @@ close_connection conn = close conn create_db :: Connection -> IO () create_db conn = do - execute_ conn "CREATE TABLE IF NOT EXISTS hasswords (entryId INTEGER PRIMARY KEY, entryLogin TEXT, entryPassword TEXT)" - execute_ conn "CREATE TABLE IF NOT EXISTS mpass (mpassId INTEGER PRIMARY KEY, hash INTEGER)" - -- execute conn "INSERT INTO hassword (entryId, entryLogin, entryPassword) VALUES (?,?,?)" (Entry 1 "admin" "admin") + execute_ conn "CREATE TABLE IF NOT EXISTS entries (entryId INTEGER PRIMARY KEY AUTOINCREMENT, entryService TEXT, entryLogin TEXT, entryPassword TEXT)" + execute_ conn "CREATE TABLE IF NOT EXISTS mpass (mpassId INTEGER PRIMARY KEY AUTOINCREMENT, hash INTEGER)" + -- execute conn "INSERT INTO entries (entryId, entryLogin, entryPassword) VALUES (?,?,?)" (Entry 1 "admin" "admin") -- rowId <- lastInsertRowId conn -- r <- query_ conn "SELECT * from hassword" :: IO [Entry] -- mapM_ print r @@ -56,9 +49,36 @@ is_it_first_app_usage conn = do insert_new_mpass :: Connection -> Int -> IO () insert_new_mpass conn hash = do execute_ conn "DELETE FROM mpass" - execute conn "INSERT INTO mpass (mpassId, hash) VALUES (?,?)" (Mpass 1 hash) + execute conn "INSERT INTO mpass (hash) VALUES (?)" (Only hash) check_if_mpass_valid :: Connection -> Int -> IO Bool check_if_mpass_valid conn hash = do r <- query conn "SELECT * from mpass WHERE hash = ?" (Only hash) :: IO [Mpass] return $ length r == 1 + +add_entry :: Connection -> T.Text -> T.Text -> T.Text -> IO () +add_entry conn service login password = do + execute conn "INSERT INTO entries (entryService, entryLogin, entryPassword) VALUES (?,?,?)" (service, login, password) + +update_entry :: Connection -> Int -> T.Text -> IO () +update_entry conn id new_password = do + execute conn "UPDATE entries SET entryPassword = ? WHERE entryId = ?" (new_password, id) + +get_entry :: Connection -> Int -> IO (Maybe Entry) +get_entry conn id = do + r <- query conn "SELECT * from entries WHERE entryId = ?" (Only id) :: IO [Entry] + return $ case r of + [] -> Nothing + [entry] -> Just entry + +get_all_entries :: Connection -> IO [Entry] +get_all_entries conn = query_ conn "SELECT * from entries" + +delete_entry :: Connection -> Int -> IO () +delete_entry conn id = do + execute conn "DELETE FROM entries WHERE entryId = ?" (Only id) + +entry_already_exists :: Connection -> T.Text -> T.Text -> IO Bool +entry_already_exists conn service login = do + r <- query conn "SELECT * from entries WHERE entryService = ? AND entryLogin = ?" (service, login) :: IO [Entry] + return $ length r == 1 diff --git a/src/UserInterface.hs b/src/UserInterface.hs index 54c39da..a93f9b9 100644 --- a/src/UserInterface.hs +++ b/src/UserInterface.hs @@ -1,6 +1,18 @@ module UserInterface - ( someFunc + ( print_entries ) where -someFunc :: IO () -someFunc = putStrLn "someFunc" +import qualified Database as Db + +print_entries :: [Db.Entry] -> IO () +print_entries entries = do + putStrLn "Entries:" + mapM_ print_entry entries + +print_entry :: Db.Entry -> IO () +print_entry entry = do + putStrLn $ "ID: " ++ show (Db.entryId entry) + putStrLn $ "Service: " ++ show (Db.entryService entry) + putStrLn $ "Login: " ++ show (Db.entryLogin entry) + putStrLn $ "Password: *****" + putStrLn "" diff --git a/src/Utils.hs b/src/Utils.hs index fcf476d..225137a 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -6,6 +6,8 @@ import Control.Exception data MasterPasswordValidationCases = Empty | TooShort | DoNotMatch | NotValid | Valid String deriving (Show) +data ActionChoice = ListAllEntries | CopyEntryPassword | AddNewEntry | DeleteEntry | UpdateEntry | ChangeMasterPassword | Exit | InvalidAction deriving (Show) + validate_password :: String -> String -> MasterPasswordValidationCases validate_password p1 p2 | p1 /= p2 = DoNotMatch