diff --git a/README.md b/README.md index 19367a5..ddbd85d 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ # HasswordManager +A simple password manager that uses a master password to encrypt and decrypt passwords. \ +The master password is hashed with salt and pepper. \ +The passwords are stored encrypted in sqlite3 database and are decrypted only when needed. + +## Authors: +- Jan Wojciechowski +- Sebastian Jerzykiewicz diff --git a/app/Main.hs b/app/Main.hs index bba6f32..16c0f73 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -51,7 +51,7 @@ create_mpass conn = do create_mpass conn Ut.TooShort -> do Ut.set_red - putStrLn "Password is too short!!!\n" + putStrLn "Password is too short (min 8 symbols)!!!\n" Ut.reset_color create_mpass conn Ut.Empty -> do @@ -62,7 +62,7 @@ create_mpass conn = do input_new_valid_mpass :: IO (Ut.MasterPasswordValidationCases) input_new_valid_mpass = do - (password1:password2:_) <- sequence [putStrLn "Please Enter new MASTER PASSWORD:" >> Ut.get_password, putStrLn "Confirm password:" >> Ut.get_password] + (password1:password2:_) <- sequence [putStrLn "Please Enter new MASTER PASSWORD:" >> Ut.get_password, putStrLn "Enter the password again:" >> Ut.get_password] return $ Ut.validate_password password1 password2 open_hassword_book :: Connection -> IO (String) @@ -83,10 +83,10 @@ 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 "2. Copy an entry password" putStrLn "3. Add a new entry" putStrLn "4. Delete an entry" - putStrLn "5. Update an entry" + putStrLn "5. Update an entry password" putStrLn "6. Change Master Password" putStrLn "7. Close the application" choice <- getLine @@ -105,25 +105,64 @@ application_loop conn mpass = do choice <- get_action_choice case choice of Ut.ListAllEntries -> do - list_all_entries conn - application_loop conn mpass + entries <- Db.get_all_entries conn + if null entries + then do + Ut.clear_screen + Ut.set_red + putStrLn "No entries to list!!!" + Ut.reset_color + application_loop conn mpass + else do + list_all_entries conn + application_loop conn mpass Ut.CopyEntryPassword -> do - copy_entry_pass conn mpass - application_loop conn mpass + entries <- Db.get_all_entries conn + if null entries + then do + Ut.clear_screen + Ut.set_red + putStrLn "No entries to copy a password from!!!" + Ut.reset_color + application_loop conn mpass + else do + copy_entry_pass conn mpass + application_loop conn mpass Ut.AddNewEntry -> do add_entry conn mpass application_loop conn mpass Ut.DeleteEntry -> do - delete_entry conn - application_loop conn mpass + entries <- Db.get_all_entries conn + if null entries + then do + Ut.clear_screen + Ut.set_red + putStrLn "No entries to delete!!!" + Ut.reset_color + application_loop conn mpass + else do + delete_entry conn + application_loop conn mpass Ut.UpdateEntry -> do - update_entry conn mpass - application_loop conn mpass + entries <- Db.get_all_entries conn + if null entries + then do + Ut.clear_screen + Ut.set_red + putStrLn "No entries to update!!!" + Ut.reset_color + application_loop conn mpass + else do + update_entry conn mpass + application_loop conn mpass Ut.ChangeMasterPassword -> do - return () + mpass <- change_mpass conn mpass application_loop conn mpass Ut.InvalidAction -> do + Ut.clear_screen + Ut.set_red putStrLn "Invalid choice!!!" + Ut.reset_color application_loop conn mpass Ut.Exit -> do return () @@ -145,10 +184,12 @@ copy_entry_pass conn mpass = do Just (service, login) -> do entry <- Db.get_entry conn (T.pack service) (T.pack login) setClipboard (Cr.decrypt' mpass (T.unpack $ Db.entryPassword entry)) + Ut.clear_screen Ut.set_green putStrLn "Password copied to clipboard!!!" Ut.reset_color Nothing -> do + Ut.clear_screen Ut.set_red putStrLn "Invalid entry ID!!!" Ut.reset_color @@ -156,13 +197,15 @@ copy_entry_pass conn mpass = do add_entry :: Connection -> String -> IO () add_entry conn mpass = do - putStrLn "Please Enter the service name:" + Ut.clear_screen + putStrLn "Please Enter the service name for the new entry:" service <- getLine - putStrLn "Please Enter the login:" + putStrLn "Please Enter the login for the new entry:" login <- getLine entry_exists <- Db.entry_already_exists conn (T.pack service) (T.pack login) if entry_exists then do + Ut.clear_screen Ut.set_red putStrLn "Entry already exists!!!" Ut.reset_color @@ -171,6 +214,10 @@ add_entry conn mpass = do putStrLn "Please Enter the password:" password <- Ut.get_password Db.add_entry conn (T.pack service) (T.pack login) (T.pack (Cr.encrypt' mpass password)) + Ut.clear_screen + Ut.set_green + putStrLn "Entry added successfully!!!" + Ut.reset_color update_entry :: Connection -> String -> IO () update_entry conn mpass = do @@ -183,7 +230,12 @@ update_entry conn mpass = do putStrLn "Please Enter the new password:" password <- Ut.get_password Db.update_entry conn (T.pack service) (T.pack login) (T.pack (Cr.encrypt' mpass password)) + Ut.clear_screen + Ut.set_green + putStrLn "Entry updated successfully!!!" + Ut.reset_color Nothing -> do + Ut.clear_screen Ut.set_red putStrLn "Invalid entry ID!!!" Ut.reset_color @@ -197,8 +249,56 @@ delete_entry conn = do case result of Just (service, login) -> do Db.delete_entry conn (T.pack service) (T.pack login) + Ut.clear_screen + Ut.set_green + putStrLn "Entry deleted successfully!!!" + Ut.reset_color Nothing -> do + Ut.clear_screen Ut.set_red putStrLn "Invalid entry ID!!!" Ut.reset_color return () + +change_mpass :: Connection -> String -> IO (String) +change_mpass conn mpass = do + putStrLn "Please Enter your current MASTER PASSWORD:" + current_mpass <- Ut.get_password + mpass_valid <- Db.check_if_mpass_valid conn (Cr.hash' $ fromString current_mpass) + if mpass_valid + then do + res <- input_new_valid_mpass + case res of + Ut.Valid new_mpass -> do + Db.insert_new_mpass conn (Cr.hash' $ fromString new_mpass) + entries <- Db.get_all_entries conn + mapM_ (\entry -> Db.update_entry conn (Db.entryService entry) (Db.entryLogin entry) (T.pack (Cr.encrypt' new_mpass (Cr.decrypt' mpass (T.unpack (Db.entryPassword entry)))))) entries + Ut.clear_screen + Ut.set_green + putStrLn "Master Password changed successfully!!!\n" + Ut.reset_color + return new_mpass + Ut.DoNotMatch -> do + Ut.clear_screen + Ut.set_red + putStrLn "Passwords do not match!!!\n" + Ut.reset_color + return mpass + Ut.TooShort -> do + Ut.clear_screen + Ut.set_red + putStrLn "Password is too short (min 8 symbols)!!!\n" + Ut.reset_color + return mpass + Ut.Empty -> do + Ut.clear_screen + Ut.set_red + putStrLn "Password cannot be empty!!!\n" + Ut.reset_color + return mpass + else do + Ut.clear_screen + Ut.set_red + putStrLn "Invalid MASTER PASSWORD!!!\n" + Ut.reset_color + return mpass diff --git a/src/UserInterface.hs b/src/UserInterface.hs index 291b746..73562be 100644 --- a/src/UserInterface.hs +++ b/src/UserInterface.hs @@ -1,6 +1,9 @@ module UserInterface where import qualified Database as Db +import qualified Data.Text as T +import qualified Utils as Ut +import Text.Read (readMaybe) print_entries :: [Db.Entry] -> IO () print_entries entries = do @@ -9,10 +12,18 @@ print_entries entries = do print_entry :: (Int, Db.Entry) -> IO () print_entry (num, entry) = do - putStrLn $ show num ++ " " ++ show (Db.entryService entry) ++ " " ++ show (Db.entryLogin entry) ++ " " ++ "********" + putStrLn $ show num ++ ". " ++ T.unpack (Db.entryService entry) ++ " " ++ T.unpack (Db.entryLogin entry) ++ " " ++ "********" choose_entry :: String -> IO Int choose_entry command = do - putStrLn $ command + putStrLn command entry_id <- getLine - return (read entry_id :: Int) + let res = readMaybe entry_id :: Maybe Int + case res of + Just x -> return x + Nothing -> do + Ut.set_red + putStrLn "Invalid entry ID!!!" + Ut.reset_color + choose_entry command + \ No newline at end of file diff --git a/src/Utils.hs b/src/Utils.hs index 01f2c75..94a4dd2 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -20,7 +20,8 @@ validate_password p1 p2 | otherwise = Valid p1 clear_screen :: IO () -clear_screen = clearScreen +clear_screen = do + clearFromCursorToScreenBeginning set_red :: IO () set_red = setSGR [SetColor Foreground Vivid Red] diff --git a/test/Spec.hs b/test/Spec.hs index cd4753f..7eba381 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1,2 +1,12 @@ +import Test.Hspec +import qualified Crypto as Cr + main :: IO () -main = putStrLn "Test suite not yet implemented" +main = hspec $ do + describe "Crypto" $ do + it "should encrypt and decrypt a message correctly" $ do + let originalMessage = "Hello, World!" + masterPassword = "mysecretpassword" + encryptedMessage = Cr.encrypt' masterPassword originalMessage + decryptedMessage = Cr.decrypt' masterPassword encryptedMessage + decryptedMessage `shouldBe` originalMessage