Ver código fonte

Use only unicode icons with fingerprint

pull/544/head
Guillaume Vincent 4 anos atrás
pai
commit
97329c8187
8 arquivos alterados com 177 adições e 349 exclusões
  1. +8
    -10
      cli/lesspass/cli.py
  2. +4
    -4
      cli/lesspass/core.py
  3. +153
    -0
      cli/lesspass/fingerprint.py
  4. +1
    -1
      cli/lesspass/version.py
  5. +0
    -281
      cli/lesspass/visual_fingerprint.py
  6. +4
    -0
      cli/tests/test_cli.py
  7. +7
    -0
      cli/tests/test_fingerprint.py
  8. +0
    -53
      cli/tests/test_visual_fingerprint.py

+ 8
- 10
cli/lesspass/cli.py Ver arquivo

@@ -26,9 +26,10 @@ copyright:
This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law
"""


def range_type(value_string):
value = int(value_string)
if value not in range(5, 35+1):
if value not in range(5, 35 + 1):
raise argparse.ArgumentTypeError("%s is out of range, choose in [5-35]" % value)
return value

@@ -59,10 +60,10 @@ def parse_args(args):
"-L",
"--length",
default=16,
choices=range(5, 35+1),
choices=range(5, 35 + 1),
type=range_type,
help="password length (default: 16, min: 5, max: 35)",
metavar='[5-35]'
metavar="[5-35]",
)
parser.add_argument(
"-C", "--counter", default=1, type=int, help="password counter (default: 1)"
@@ -82,16 +83,13 @@ def parse_args(args):
help="copy the password to clipboard",
)
parser.add_argument(
"--exclude",
default=None,
help="exclude char from generated password",
"--exclude", default=None, help="exclude char from generated password",
)
parser.add_argument(
"-f",
"--fingerprint",
dest="fingerprint",
"--no-fingerprint",
dest="no_fingerprint",
action="store_true",
help="show visual fingerprint of password as you type it"
help="hide visual fingerprint of the master password when you type",
)
parser.add_argument(
"-f",


+ 4
- 4
cli/lesspass/core.py Ver arquivo

@@ -10,7 +10,7 @@ from lesspass.cli import parse_args
from lesspass.profile import create_profile
from lesspass.password import generate_password
from lesspass.clipboard import copy, get_system_copy_command
from lesspass.visual_fingerprint import getpass_with_visual_fingerprint
from lesspass.fingerprint import getpass_with_fingerprint

signal.signal(signal.SIGINT, lambda s, f: sys.exit(0))

@@ -36,10 +36,10 @@ def main(args=sys.argv[1:]):

if not args.master_password:
prompt = "Master Password: "
if args.fingerprint:
args.master_password = getpass_with_visual_fingerprint(prompt)
else:
if args.no_fingerprint:
args.master_password = getpass.getpass(prompt)
else:
args.master_password = getpass_with_fingerprint(prompt)

if not args.master_password:
print("error: argument MASTER_PASSWORD is required but was not provided")


+ 153
- 0
cli/lesspass/fingerprint.py Ver arquivo

@@ -0,0 +1,153 @@
import hmac
import hashlib
import sys
import os
import random
import tty
import termios
import threading

if os.name == "nt":
import msvcrt


icon_names = [
["fa-hashtag", "#️"],
["fa-heart", "❤️"],
["fa-hotel", "🏨"],
["fa-university", "🎓"],
["fa-plug", "🔌"],
["fa-ambulance", "🚑"],
["fa-bus", "🚌"],
["fa-car", "🚗"],
["fa-plane", "✈️"],
["fa-rocket", "🚀"],
["fa-ship", "🚢"],
["fa-subway", "🚇"],
["fa-truck", "🚚"],
["fa-jpy", "💴"],
["fa-eur", "💶"],
["fa-btc", "₿"],
["fa-usd", "💵"],
["fa-gbp", "💷"],
["fa-archive", "🗄️"],
["fa-area-chart", "📈"],
["fa-bed", "🛏️"],
["fa-beer", "🍺"],
["fa-bell", "🔔"],
["fa-binoculars", "🔭"],
["fa-birthday-cake", "🎂"],
["fa-bomb", "💣"],
["fa-briefcase", "💼"],
["fa-bug", "🐛"],
["fa-camera", "📷"],
["fa-cart-plus", "🛒"],
["fa-certificate", "⭐"],
["fa-coffee", "☕"],
["fa-cloud", "☁️"],
["fa-coffee", "☕"],
["fa-comment", "🗨️"],
["fa-cube", "📦"],
["fa-cutlery", "🍴"],
["fa-database", "🖥️"],
["fa-diamond", "💎"],
["fa-exclamation-circle", "❗"],
["fa-eye", "👁️"],
["fa-flag", "🏁"],
["fa-flask", "⚗️"],
["fa-futbol-o", "⚽"],
["fa-gamepad", "🎮"],
["fa-graduation-cap", "🎓"],
]


MAX_ICON_WIDTH = max([len(icon) for icon in icon_names])


def get_icon_name(hash_slice):
index = int(hash_slice, base=16) % len(icon_names)
return icon_names[index][1]


def get_fingerprint(hmac_sha256):
hash1, hash2, hash3 = hmac_sha256[0:6], hmac_sha256[6:12], hmac_sha256[12:18]
fingerprint = []
fingerprint.append(get_icon_name(hash1))
fingerprint.append(get_icon_name(hash2))
fingerprint.append(get_icon_name(hash3))
return fingerprint


def get_hmac_sha256(password_bytes):
return hmac.new(password_bytes, digestmod=hashlib.sha256).hexdigest()


def get_mnemonic(password):
fingerprint = get_fingerprint(get_hmac_sha256(password.encode("utf-8")))
return "{fingerprint_1} {fingerprint_2} {fingerprint_3}".format(
fingerprint_1=fingerprint[0],
fingerprint_2=fingerprint[1],
fingerprint_3=fingerprint[2],
)


def get_fake_mnemonic():
fake_password = "".join(
chr(random.randrange(ord("a"), ord("z") + 1)) for i in range(16)
)
return get_mnemonic(fake_password)


def getchar():
# Returns a single character from standard input
# Credit for this function: (not written by file author)
# jasonrdsouza & mvaganov https://gist.github.com/jasonrdsouza/1901709
ch = ""
if os.name == "nt": # Windows
ch = msvcrt.getch()
else:
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
if ord(ch) == 3: # handle ctrl+C
sys.stdout.write("\n")
quit()
return ch


def getpass_with_fingerprint(prompt):
global semaphore
global stdout_lock

sys.stdout.write(prompt)
sys.stdout.flush()
password = ""
delayed_write = None
while True:
c = getchar()
if delayed_write:
delayed_write.cancel()
if c == "\r":
sys.stdout.write(f"\r{prompt}{get_fake_mnemonic()}\n")
break
elif c == "\x7f": # backspace
password = password[:-1]
else:
password += c
if len(password) != 0:
delayed_write = threading.Timer(
0.5, lambda: sys.stdout.write(f"\r{prompt}{get_mnemonic(password)}")
)
delayed_write.start()
sys.stdout.write(f"\r{prompt}{get_fake_mnemonic()}")
else:
sys.stdout.write(f"\r{prompt}{' '*(MAX_ICON_WIDTH*3)}")
return password


if __name__ == "__main__":
getpass_with_fingerprint("Master password: ")

+ 1
- 1
cli/lesspass/version.py Ver arquivo

@@ -1 +1 @@
__version__ = "9.2.0"
__version__ = "9.3.0"

+ 0
- 281
cli/lesspass/visual_fingerprint.py Ver arquivo

@@ -1,281 +0,0 @@
import hmac
import hashlib
import sys
import os
import random
import tty
import termios
import threading

if os.name == "nt":
import msvcrt


def user_has_icons_in_terminal():
return os.path.exists(os.path.expanduser("~/.fonts/icons-in-terminal.ttf"))


colors_256 = [
"\x1b[38;5;248m", # black #000000
"\x1b[38;5;30m", # dark cyan #074750
"\x1b[38;5;37m", # mid cyan #009191
"\x1b[38;5;211m", # bright pink #FF6CB6
"\x1b[38;5;219m", # cotton candy pink #FFB5DA
"\x1b[38;5;55m", # mid purple #490092
"\x1b[38;5;69m", # sky blue #006CDB
"\x1b[38;5;140m", # lavendar #B66DFF
"\x1b[38;5;81m", # baby blue #6DB5FE
"\x1b[38;5;153m", # white blue #B5DAFE
"\x1b[38;5;88m", # blood red #920000
"\x1b[38;5;94m", # burnt orange #924900
"\x1b[38;5;172m", # orange #DB6D00
"\x1b[38;5;82m", # lime green #24FE23
]
unicode_colors = [
"\x1b[31;40m", # black #000000
"\x1b[36;40m", # dark cyan #074750
"\x1b[36;40m", # mid cyan #009191
"\x1b[35;40m", # bright pink #FF6CB6
"\x1b[35;40m", # cotton candy pink #FFB5DA
"\x1b[35;40m", # mid purple #490092
"\x1b[34;40m", # sky blue #006CDB
"\x1b[35;40m", # lavendar #B66DFF
"\x1b[34;40m", # baby blue #6DB5FE
"\x1b[34;40m", # white blue #B5DAFE
"\x1b[31;40m", # blood red #920000
"\x1b[31;40m", # burnt orange #924900
"\x1b[33;40m", # orange #DB6D00
"\x1b[32;40m", # lime green #24FE23
]
fallback_colors = [
"\x1b[37;40m", # black #000000
"\x1b[30;46m", # dark cyan #074750
"\x1b[30;46m", # mid cyan #009191
"\x1b[30;45m", # bright pink #FF6CB6
"\x1b[30;45m", # cotton candy pink #FFB5DA
"\x1b[30;45m", # mid purple #490092
"\x1b[30;44m", # sky blue #006CDB
"\x1b[30;45m", # lavendar #B66DFF
"\x1b[30;44m", # baby blue #6DB5FE
"\x1b[30;44m", # white blue #B5DAFE
"\x1b[30;41m", # blood red #920000
"\x1b[30;41m", # burnt orange #924900
"\x1b[30;43m", # orange #DB6D00
"\x1b[30;42m", # lime green #24FE23
]
icon_names = [
"hashtag",
"heart",
"hotel",
"university",
"plug",
"ambulance",
"bus",
"car",
"plane",
"rocket",
"ship",
"subway",
"truck",
"japanese yen",
"euro",
"bitcoin",
"U.S. dollar",
"British pound",
"archive",
"area-chart",
"bed",
"beer",
"bell",
"binoculars",
"birthday-cake",
"bomb",
"briefcase",
"bug",
"camera",
"cart-plus",
"certificate",
"coffee",
"cloud",
"coffee",
"comment",
"cube",
"cutlery",
"database",
"diamond",
"exclamation-circle",
"eye",
"flag",
"flask",
"futbol",
"gamepad",
"graduation-cap",
]
icons_in_terminal_icons = {
"hashtag": "\ue33e",
"heart": "\ue0e5",
"hotel": "\ue268", # NOTE: "fa-building" substituted
"university": "\ue644",
"plug": "\ue29d",
"ambulance": "\ue1bf",
"bus": "\ue2bc",
"car": "\ue587",
"plane": "\ue14c",
"rocket": "\ue1f7",
"ship": "\ue2ce",
"subway": "\ue2eb",
"truck": "\ue199",
"japanese yen": "\uec97", # NOTE: Linea circled yen icon substituted
"euro": "\ue714",
"bitcoin": "\ue21a",
"U.S. dollar": "\ue215",
"British pound": "\uec89", # NOTE: Linea circled pound sterling icon substituted
"archive": "\ue244",
"area-chart": "\ue2b4",
"bed": "\ue2e8",
"beer": "\ue1c2",
"bell": "\ue1b9",
"binoculars": "\ue29c",
"birthday-cake": "\ue2b3",
"bomb": "\ue299",
"briefcase": "\ue187",
"bug": "\ue245",
"camera": "\ue10e",
"cart-plus": "\ue2cb",
"certificate": "\ue17a",
"coffee": "\ue1ba",
"cloud": "\ue18b",
"comment": "\ue14f",
"cube": "\ue26c",
"cutlery": "\ue1bb",
"database": "\ue279",
"diamond": "\ue2cd",
"exclamation-circle": "\ue145",
"eye": "\ue149",
"flag": "\ue102",
"flask": "\ue18c",
"futbol": "\ue29a",
"gamepad": "\ue1df",
"graduation-cap": "\ue259",
}


MAX_ICON_WIDTH = max([len(icon) for icon in icon_names])


def get_list_entry(hash_slice, lookup_list):
index = int(hash_slice, base=16) % len(lookup_list)
return lookup_list[index]


def get_color(hash_slice):
if user_has_icons_in_terminal():
if "256" in os.environ["TERM"]:
colors = colors_256
else:
colors = unicode_colors
else:
colors = fallback_colors
return get_list_entry(hash_slice, colors)


def get_icon(hash_slice):
icon_name = get_list_entry(hash_slice, icon_names)
return (
icons_in_terminal_icons[icon_name]
if user_has_icons_in_terminal()
else icon_name
)


def get_fingerprint(hmac_sha256):
hash1, hash2, hash3 = hmac_sha256[0:6], hmac_sha256[6:12], hmac_sha256[12:18]
fingerprint = []
fingerprint.append({"color": get_color(hash1), "icon": get_icon(hash1)})
fingerprint.append({"color": get_color(hash2), "icon": get_icon(hash2)})
fingerprint.append({"color": get_color(hash3), "icon": get_icon(hash3)})
return fingerprint


def get_hmac_sha256(password_bytes):
return hmac.new(password_bytes, digestmod=hashlib.sha256).hexdigest()


def get_fixed_width_text(fingerprint_entry):
color, icon = fingerprint_entry["color"], fingerprint_entry["icon"]
if user_has_icons_in_terminal():
text = f"{color}{icon} \x1b[0m"
else:
text = f"{color}{icon}{' '*(MAX_ICON_WIDTH-len(icon))}\x1b[0m"
return text


def get_mnemonic(password):
fingerprint = get_fingerprint(get_hmac_sha256(password.encode("utf-8")))
return (
f"[ {get_fixed_width_text(fingerprint[0])} "
f"{get_fixed_width_text(fingerprint[1])} "
f"{get_fixed_width_text(fingerprint[2])} ]"
)


def get_fake_mnemonic():
fake_password = "".join(
chr(random.randrange(ord("a"), ord("z") + 1)) for i in range(16)
)
return get_mnemonic(fake_password)


def getchar():
# Returns a single character from standard input
# Credit for this function: (not written by file author)
# jasonrdsouza & mvaganov https://gist.github.com/jasonrdsouza/1901709
ch = ""
if os.name == "nt": # Windows
ch = msvcrt.getch()
else:
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
if ord(ch) == 3: # handle ctrl+C
sys.stdout.write("\n")
quit()
return ch


def getpass_with_visual_fingerprint(prompt):
global semaphore
global stdout_lock

sys.stdout.write(prompt)
sys.stdout.flush()
password = ""
delayed_write = None
while True:
c = getchar()
if delayed_write:
delayed_write.cancel()
if c == "\r":
sys.stdout.write(f"\r{prompt}{get_fake_mnemonic()}\n")
break
elif c == "\x7f": # backspace
password = password[:-1]
else:
password += c
if len(password) != 0:
delayed_write = threading.Timer(
0.5, lambda: sys.stdout.write(f"\r{prompt}{get_mnemonic(password)}")
)
delayed_write.start()
sys.stdout.write(f"\r{prompt}{get_fake_mnemonic()}")
else:
sys.stdout.write(f"\r{prompt}{' '*(MAX_ICON_WIDTH*3)}")
return password


if __name__ == "__main__":
getpass_with_visual_fingerprint("Master password: ")

+ 4
- 0
cli/tests/test_cli.py Ver arquivo

@@ -114,3 +114,7 @@ class TestParseArgs(unittest.TestCase):

def test_parse_args_exclude_single_and_double_quote(self):
self.assertEqual(parse_args(["site", "--exclude", "\"'"]).exclude, "\"'")

def test_parse_no_fingerprint(self):
self.assertTrue(parse_args(["site", "--no-fingerprint"]).no_fingerprint)
self.assertFalse(parse_args(["site"]).no_fingerprint)

+ 7
- 0
cli/tests/test_fingerprint.py Ver arquivo

@@ -0,0 +1,7 @@
from lesspass.fingerprint import get_mnemonic


def test_get_fingerprint():
assert get_mnemonic(b"password") == "⚗️🗄️🍺"
assert get_mnemonic(b"Password12345") == "🚑🛏️💷"
assert get_mnemonic(b"Ma$$W0rld!@#$%^&*()<gamma>") == "📈💷💷"

+ 0
- 53
cli/tests/test_visual_fingerprint.py Ver arquivo

@@ -1,53 +0,0 @@
from lesspass.visual_fingerprint import (
get_fingerprint,
get_hmac_sha256
)


def get_fingerprint_from_password(password_bytes):
return get_fingerprint(get_hmac_sha256(password_bytes))


def test_get_fingerprint():
assert get_fingerprint_from_password(b'password') == [
{
"color": "\x1b[30;45m", # => #FFB5DA
"icon": "flask"
},
{
"color": "\x1b[30;46m", # => #009191
"icon": "archive"
},
{
"color": "\x1b[30;44m", # => #B5DAFE
"icon": "beer"
}
]
assert get_fingerprint_from_password(b'Password12345') == [
{
"color": "\x1b[30;41m", # => #924900
"icon": "ambulance"
},
{
"color": "\x1b[30;44m", # => #6DB5FE
"icon": "bed"
},
{
"color": "\x1b[30;45m", # => #FF6CB6
"icon": "British pound"
}
]
assert get_fingerprint_from_password(b'Ma$$W0rld!@#$%^&*()<gamma>') == [
{
"color": "\x1b[30;44m", # => #B5DAFE
"icon": "area-chart"
},
{
"color": "\x1b[30;45m", # => #490092
"icon": "British pound"
},
{
"color": "\x1b[30;41m", # => #924900
"icon": "British pound"
}
]

Carregando…
Cancelar
Salvar