Commit 245788f3 authored by Benjamin REED's avatar Benjamin REED

working build, added some stuff, notably restore command and overwriting as...

working build, added some stuff, notably restore command and overwriting as basically reusing a name and saving the old use of it. TODO is permanent overwrite & renaming command and i think thats everything.
parent b7a19d09
......@@ -16,6 +16,10 @@ USAGES = {
"dbg": "dbg ( Prints a debug output of the manager's state )",
"list": "list [ <label> ] (Prints the passwords currently stored 'label' = the password you want to decrypt)",
"save": "save - Saves the state to the file originally inputted.", # TODO: make it possible to save to a new file
"drop": "drop <label> - deletes the selected label from the list of passwords. This can be undone with the 'undo' command, however if 'drop' is used on an already deleted password, the password is removed forever from the database.",
"undo": "undo - undoes the latest deletion/modification",
"history": "history - prints all the deleted passwords",
"cfg": "config [ <param=newparam> ] - configures the current program state or lists it when run without arguments. Example: 'config hints=False'",
}
DEFAULT_KEYGEN_ITERATIONS = 100_000
LOWERCASE_ALPHABET = set([chr(e) for e in range(ord('a'), ord('a') + 25)])
......@@ -29,6 +33,17 @@ CHARSET_MAP = {
"s": SPECIAL,
}
def confirm(msg, yes, no):
i = input(msg + f" ({yes}/{no})")
i.strip()
s = i.lower()
if s == yes:
return True
elif s == no:
return False
else:
return False
def derive_key(password: bytes, salt: bytes, iterations: int = DEFAULT_KEYGEN_ITERATIONS) -> bytes:
kdf = PBKDF2HMAC(
algorithm = hashes.SHA256(), length=32, salt=salt,
......@@ -78,6 +93,8 @@ class State:
raise Exception
self.passwords = {}
self.to_remove = []
self.overwritten = []
self.key = key
self.head = head
......@@ -94,7 +111,16 @@ class State:
if len(parts) != 2:
print(f"Error parsing password on line {i}: {line}\n ^^^^ -> Expected comma.")
continue
self.passwords[parts[0]] = parts[1].encode()
l = parts[0].split("(")
if len(l) != 1 and l[0] == "rm":
part = parts[0].split("(")[1][:-1]
self.passwords[part] = parts[1].encode()
self.to_remove.append(part)
elif len(l) != 1 and l[0] == "old":
self.overwritten.append(l[1][:-1])
self.passwords[parts[0]] = parts[1].encode()
else:
self.passwords[parts[0]] = parts[1].encode()
file.close()
......@@ -111,8 +137,18 @@ class State:
else:
return b"[ERROR: no such label found]"
def serialize_password(self, key, value):
if key in self.to_remove:
return f"rm({key}),{value.decode('utf-8')}"
# elif key in self.overwritten:
# del self.overwritten[self.overwritten.index(key)]
# return f"old({key}),{value.decode('utf-8')}"
else:
return f"{key},{value.decode('utf-8')}"
def save(self):
content = self.head + "\n" + "\n".join(map(lambda x: f"{x[0]},{x[1].decode('utf-8')}", self.passwords.items()))
content = self.head + "\n" + "\n".join(map(lambda x: self.serialize_password(x[0], x[1]), self.passwords.items()))
file = open(self.filepath, 'w')
file.write(content)
......@@ -166,9 +202,14 @@ def main():
return main()
else:
return
except Exception:
except Exception as e:
print(f"Error parsing database {filepath}: {e}")
return
# TODO: make this serialized (somehow... :[)
cfg = {
"hints": True
}
print("Welcome to SmashPass. Type 'h' or 'help' for help")
while True:
......@@ -182,16 +223,44 @@ def main():
command = args[0]
if command == "q" or command == "quit" or command == "exit":
break
if len(args) == 2:
if args[1] == "f" or args[1] == "force" or args[1] == "nosave":
if confirm("Are you sure you want to exit without saving?", "y", "n"):
return
else:
break
if command == "cfg" or command == "config" or command == "settings":
if len(args) < 2:
print(f"Settings: hints: {cfg['hints']}, ...")
continue
s = args[1].split("=")
if len(s) < 2:
print(f"Incorrect format for modifying program parameters. Usage: {USAGES['cfg']}")
continue
if s[0] not in cfg:
print(f"Unknown cfg parameter '{s[0]}'. Type 'cfg' for a list of config parameters.")
continue
try:
cfg[s[0]] = s[1]
except:
print(f"Incorrect format for cfg parameter {s[0]}, expected type {type(cfg[s[0]])}")
elif command == "help" or command == "h" or command == "?":
if len(args) == 1:
print(USAGES.keys())
print("Available commands: ")
print("\t" + ", ".join(USAGES.keys()))
if cfg['hints'] == True:
print("(Hint: type 'help <command name>' for more detailed info about that command)")
else:
cmd = args[1]
if not cmd in USAGES:
print("Unknown command, you may have mistyped it.")
continue
print(USAGES[cmd])
elif command == "echo":
if len(args) < 2:
print(f"Too few arguments for {command}, usage: {USAGES[command]}")
print(f"Too few arguments for {command}, usage: {USAGES['echo']}")
continue
print(" ".join(args[1:]))
elif command == "dbg":
......@@ -202,14 +271,74 @@ def main():
print(f"State saved to {filepath}!")
except:
print("There was an error saving, maybe back up your stuff now. Sorry")
elif command == "drop" or command == "delete" or command == "del" or command == "rm":
if len(args) < 2:
print(f"Too few arguments for command {command}, usage: {USAGES['drop']}")
continue
label = args[1].strip().lower()
if label in state.to_remove:
if confirm(f"Are you sure you want to PERMANANTLY DELETE {label}? FOREVER????? (Note: this CANNOT be undone)", "y", "n"):
del state.passwords[label]
del state.to_remove[state.to_remove.index(label)]
print(f"Password '{label}' was permanently removed from database. This cannot be undone.")
elif label in state.passwords:
if confirm(f"Are you sure you want to delete {label}?", "y", "n"):
state.to_remove.append(label)
print(f"Password '{label}' removed from database.")
if cfg['hints'] == True:
print("(Hint: use 'undo' to undo this operation, and 'history' to see previous deleted passwords and restore them)")
else:
print(f"Password '{label}' was not found, have you spelt it correctly?")
elif command == "undo" or command == "un":
if len(state.to_remove) == 0:
print("Already on latest change")
continue
ltst = state.to_remove.pop()
print(f"Undone latest deletion: {ltst}")
elif command == "restore" or command == "res" or command == "r":
if len(args) < 2:
print(f"Too few arguments for command {command}. Usage: {USAGES['restore']}")
continue
args[1].strip()
og_label = args[1].lower()
label = og_label
if og_label in state.passwords and (og_label in state.overwritten or og_label in state.to_remove):
print("You will have to rename this password to restore it.")
label_in = input("New label (If left empty, a number will be used): ")
if label_in == "":
label = str(len(state.passwords) + 1) # oh so hacky, i hate python and i want my damn state to be operated on by its own damn functions. I hate this.
else:
label_in.strip()
label = label_in.lower()
if og_label in state.to_remove:
del state.to_remove[state.to_remove.index(og_label)]
print(f"Restored {og_label}")
elif og_label in state.overwritten:
del state.overwritten[state.overwritten.index(og_label)]
state.passwords[label] = state.passwords[f"old({og_label})"]
del state.passwords[f"old({og_label})"]
print(f"Restored {og_label}")
else:
print(f"Label '{og_label}' could not be found in deleted or overwritten passwords")
elif command == "history" or command == "his" or command == "undolist":
print("Deleted:")
for remd in state.to_remove:
print(f"{remd}: [...]", end=' ')
print("\nOverwritten:")
for over in state.overwritten:
print(f"{over}: [...]", end=' ')
print("")
elif command == "new": # Form: new <length> [ <label> ]
if len(args) < 2:
print(f"Too few arguments for {command}, usage: {USAGES[command]}")
print(f"Too few arguments for {command}, usage: {USAGES['new']}")
continue
try:
length = int(args[1])
except:
print(f"Error parsing length as number, usage: {USAGES[command]}")
print(f"Error parsing length as number, usage: {USAGES['new']}")
continue
if len(args) >= 3:
......@@ -217,6 +346,20 @@ def main():
else:
label = ""
if label in state.passwords:
print(f"Error generating new password, label '{label}' is already in use.")
if label in state.overwritten:
if confim("Do you want to PERMANENTLY OVERWRITE {label}? (THIS CANNOT BE UNDONE)", "y", "n"):
todo
if confirm("Would you like to replace it?", "y", "n"):
state.overwritten.append(label)
state.passwords[f"old({label})"] = state.passwords[label]
print(f"Overwriting '{label}' with new password...")
if cfg['hints'] == True:
print("(Hint: use the 'restore' command to undo this action)")
else:
continue
charset = "all"
chars = set()
......@@ -242,8 +385,10 @@ def main():
# if state.passwords.len() >= 10:
# TODO: make it possible to scroll thru a list of passwords
for l in state.passwords:
print(f"{l}: [...]")
print("(Type 'list <key>' to decrypt your chosen password")
if l not in state.to_remove and "old" not in l:
print(f"{l}: [...]")
if cfg['hints'] == True:
print("(Hint: type 'list <key>' to decrypt your chosen password")
elif len(args) == 2:
print(state.get_password(args[1]).decode('utf-8'))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment