get_passphrase,get_hash_preset,get_label: new test, cleanups, fixes

This commit is contained in:
The MMGen Project 2020-05-30 17:49:22 +00:00
commit c2dc09cbf3
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 242 additions and 76 deletions

View file

@ -204,7 +204,7 @@ def add_user_random(rand_bytes,desc):
return rand_bytes
def get_hash_preset_from_user(hp=g.dfl_hash_preset,desc='data'):
prompt = f'Enter hash preset for {desc},\n or hit ENTER to accept the default value ({hp!r}): '
prompt = f'Enter hash preset for {desc},\nor hit ENTER to accept the default value ({hp!r}): '
while True:
ret = my_raw_input(prompt)
if ret:
@ -218,14 +218,14 @@ def get_hash_preset_from_user(hp=g.dfl_hash_preset,desc='data'):
def get_new_passphrase(desc,passchg=False):
w = '{}passphrase for {}'.format(('','new ')[bool(passchg)], desc)
pw_desc = '{}passphrase for {}'.format(('','new ')[bool(passchg)], desc)
if opt.passwd_file:
pw = ' '.join(get_words_from_file(opt.passwd_file,w))
pw = ' '.join(get_words_from_file(opt.passwd_file,pw_desc))
elif opt.echo_passphrase:
pw = ' '.join(get_words_from_user(f'Enter {w}: '))
pw = ' '.join(get_words_from_user(f'Enter {pw_desc}: '))
else:
for i in range(g.passwd_max_tries):
pw = ' '.join(get_words_from_user(f'Enter {w}: '))
pw = ' '.join(get_words_from_user(f'Enter {pw_desc}: '))
pw_chk = ' '.join(get_words_from_user('Repeat passphrase: '))
dmsg(f'Passphrases: [{pw}] [{pw_chk}]')
if pw == pw_chk:
@ -240,12 +240,12 @@ def get_new_passphrase(desc,passchg=False):
return pw
def get_passphrase(desc,passchg=False):
prompt ='Enter {}passphrase for {}: '.format(('','old ')[bool(passchg)],desc)
pw_desc ='{}passphrase for {}'.format(('','old ')[bool(passchg)],desc)
if opt.passwd_file:
pwfile_reuse_warning(opt.passwd_file)
return ' '.join(get_words_from_file(opt.passwd_file,'passphrase'))
return ' '.join(get_words_from_file(opt.passwd_file,pw_desc))
else:
return ' '.join(get_words_from_user(prompt))
return ' '.join(get_words_from_user(f'Enter {pw_desc}: '))
_salt_len,_sha256_len,_nonce_len = (32,32,32)
@ -257,7 +257,7 @@ def mmgen_encrypt(data,desc='data',hash_preset=''):
m = ('user-requested','default')[hp=='3']
vmsg(f'Encrypting {desc}')
qmsg(f'Using {m} hash preset of {hp!r}')
passwd = get_new_passphrase(desc,{})
passwd = get_new_passphrase(desc)
key = make_key(passwd,salt,hp)
enc_d = encrypt_data(sha256(nonce+data).digest() + nonce + data, key, iv, desc=desc)
return salt+iv+enc_d

View file

@ -274,57 +274,50 @@ class WalletEnc(Wallet):
_msg = {
'choose_passphrase': """
You must choose a passphrase to encrypt your new {} with.
A key will be generated from your passphrase using a hash preset of '{}'.
Please note that no strength checking of passphrases is performed. For
an empty passphrase, just hit ENTER twice.
""".strip()
You must choose a passphrase to encrypt your new {} with.
A key will be generated from your passphrase using a hash preset of '{}'.
Please note that no strength checking of passphrases is performed.
For an empty passphrase, just hit ENTER twice.
"""
}
def _get_hash_preset_from_user(self,hp,desc_suf=''):
n = ('','old ')[self.op=='pwchg_old']
m,n = (('to accept the default',n),('to reuse the old','new '))[self.op=='pwchg_new']
fs = "Enter {}hash preset for {}{}{},\n or hit ENTER {} value ('{}'): "
p = fs.format(
n,
def _get_hash_preset_from_user(self,hp,add_desc=''):
prompt = 'Enter {}hash preset for {}{}{},\nor hit ENTER to {} value ({!r}): '.format(
('old ' if self.op=='pwchg_old' else 'new ' if self.op=='pwchg_new' else ''),
('','new ')[self.op=='new'],
self.desc,
('',' '+desc_suf)[bool(desc_suf)],
m,
hp
)
('',' '+add_desc)[bool(add_desc)],
('accept the default','reuse the old')[self.op=='pwchg_new'],
hp )
while True:
ret = my_raw_input(p)
ret = my_raw_input(prompt)
if ret:
if ret in g.hash_presets:
self.ssdata.hash_preset = ret
return ret
else:
msg('Invalid input. Valid choices are {}'.format(', '.join(g.hash_presets)))
else:
self.ssdata.hash_preset = hp
return hp
def _get_hash_preset(self,desc_suf=''):
def _get_hash_preset(self,add_desc=''):
if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'):
old_hp = self.ss_in.ssdata.hash_preset
if opt.keep_hash_preset:
qmsg(f'Reusing hash preset {old_hp!r} at user request')
self.ssdata.hash_preset = old_hp
hp = old_hp
qmsg(f'Reusing hash preset {hp!r} at user request')
elif opt.hash_preset:
hp = self.ssdata.hash_preset = opt.hash_preset
qmsg(f'Using hash preset {opt.hash_preset!r} requested on command line')
hp = opt.hash_preset
qmsg(f'Using hash preset {hp!r} requested on command line')
else: # Prompt, using old value as default
hp = self._get_hash_preset_from_user(old_hp,desc_suf)
hp = self._get_hash_preset_from_user(old_hp,add_desc)
if (not opt.keep_hash_preset) and self.op == 'pwchg_new':
m = (f'changed to {hp!r}','unchanged')[hp==old_hp]
qmsg(f'Hash preset {m}')
qmsg('Hash preset {}'.format('unchanged' if hp==old_hp else f'changed to {hp!r}'))
elif opt.hash_preset:
self.ssdata.hash_preset = opt.hash_preset
qmsg(f'Using hash preset {opt.hash_preset!r} requested on command line')
hp = opt.hash_preset
qmsg(f'Using hash preset {hp!r} requested on command line')
else:
self._get_hash_preset_from_user(g.dfl_hash_preset,desc_suf)
hp = self._get_hash_preset_from_user(g.dfl_hash_preset,add_desc)
self.ssdata.hash_preset = hp
def _get_new_passphrase(self):
desc = '{}passphrase for {}{}'.format(
@ -337,29 +330,33 @@ an empty passphrase, just hit ENTER twice.
self.passwd_file,
desc,
quiet = pwfile_reuse_warning(self.passwd_file) ))
elif opt.echo_passphrase:
pw = ' '.join(get_words_from_user(f'Enter {desc}: '))
else:
for i in range(g.passwd_max_tries):
qmsg('\n'+fmt(self.msg['choose_passphrase'].format(self.desc,self.ssdata.hash_preset),indent=' '))
if opt.echo_passphrase:
pw = ' '.join(get_words_from_user(f'Enter {desc}: '))
pw_chk = ' '.join(get_words_from_user('Repeat passphrase: '))
dmsg(f'Passphrases: [{pw}] [{pw_chk}]')
if pw == pw_chk:
vmsg('Passphrases match'); break
else: msg('Passphrases do not match. Try again.')
else:
die(2,f'User failed to duplicate passphrase in {g.passwd_max_tries} attempts')
for i in range(g.passwd_max_tries):
pw = ' '.join(get_words_from_user(f'Enter {desc}: '))
pw_chk = ' '.join(get_words_from_user('Repeat passphrase: '))
dmsg(f'Passphrases: [{pw}] [{pw_chk}]')
if pw == pw_chk:
vmsg('Passphrases match')
break
else:
msg('Passphrases do not match. Try again.')
else:
die(2,f'User failed to duplicate passphrase in {g.passwd_max_tries} attempts')
if pw == '':
qmsg('WARNING: Empty passphrase')
self.ssdata.passwd = pw
return pw
def _get_passphrase(self,desc_suf=''):
def _get_passphrase(self,add_desc=''):
desc = '{}passphrase for {}{}'.format(
('','old ')[self.op=='pwchg_old'],
self.desc,
('',' '+desc_suf)[bool(desc_suf)]
('',' '+add_desc)[bool(add_desc)]
)
if self.passwd_file:
ret = ' '.join(get_words_from_file(
@ -367,7 +364,7 @@ an empty passphrase, just hit ENTER twice.
desc,
quiet = pwfile_reuse_warning(self.passwd_file) ))
else:
ret = ' '.join(get_words_from_user('Enter {}: '.format(desc)))
ret = ' '.join(get_words_from_user(f'Enter {desc}: '))
self.ssdata.passwd = ret
def _get_first_pw_and_hp_and_encrypt_seed(self):
@ -382,10 +379,8 @@ an empty passphrase, just hit ENTER twice.
else:
pw = self._get_new_passphrase()
if self.op == 'pwchg_new':
m = ('changed','unchanged')[pw==old_pw]
qmsg('Passphrase {}'.format(m))
qmsg('Passphrase {}'.format('unchanged' if pw==old_pw else 'changed'))
else:
qmsg(self.msg['choose_passphrase'].format(self.desc,d.hash_preset))
self._get_new_passphrase()
d.salt = sha256(get_random(128)).digest()[:g.salt_len]
@ -718,44 +713,43 @@ class MMGenWallet(WalletEnc):
self.label = None
super().__init__(*args,**kwargs)
# logic identical to _get_hash_preset_from_user()
def _get_label_from_user(self,old_lbl=''):
prompt = 'Enter a wallet label, or hit ENTER {}: '.format(
f'to reuse the label {old_lbl.hl()!r}' if old_lbl else 'for no label' )
'to reuse the label {}'.format(old_lbl.hl(encl="''")) if old_lbl else
'for no label' )
while True:
msg_r(prompt)
ret = my_raw_input('')
if ret:
self.ssdata.label = MMGenWalletLabel(ret,on_fail='return')
if self.ssdata.label:
break
lbl = MMGenWalletLabel(ret,on_fail='return')
if lbl:
return lbl
else:
msg('Invalid label. Trying again...')
else:
self.ssdata.label = old_lbl or MMGenWalletLabel('No Label')
break
return self.ssdata.label
return old_lbl or MMGenWalletLabel('No Label')
# nearly identical to _get_hash_preset() - factor?
# logic identical to _get_hash_preset()
def _get_label(self):
if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'label'):
old_lbl = self.ss_in.ssdata.label
if opt.keep_label:
qmsg(f'Reusing label {old_lbl.hl()!r} at user request')
self.ssdata.label = old_lbl
lbl = old_lbl
qmsg('Reusing label {} at user request'.format(lbl.hl(encl="''")))
elif self.label:
qmsg(f'Using label {self.label.hl()!r} requested on command line')
lbl = self.ssdata.label = self.label
lbl = self.label
qmsg('Using label {} requested on command line'.format(lbl.hl(encl="''")))
else: # Prompt, using old value as default
lbl = self._get_label_from_user(old_lbl)
if (not opt.keep_label) and self.op == 'pwchg_new':
m = (f'changed to {lbl!r}','unchanged')[lbl==old_lbl]
qmsg(f'Label {m}')
qmsg('Label {}'.format('unchanged' if lbl==old_lbl else f'changed to {lbl!r}'))
elif self.label:
qmsg(f'Using label {self.label.hl()!r} requested on command line')
self.ssdata.label = self.label
lbl = self.label
qmsg('Using label {} requested on command line'.format(lbl.hl(encl="''")))
else:
self._get_label_from_user()
lbl = self._get_label_from_user()
self.ssdata.label = lbl
def _encrypt(self):
self._get_first_pw_and_hp_and_encrypt_seed()
@ -851,8 +845,8 @@ class MMGenWallet(WalletEnc):
def _decrypt(self):
d = self.ssdata
# Needed for multiple transactions with {}-txsign
suf = ('',os.path.basename(self.infile.name))[bool(opt.quiet)]
self._get_passphrase(desc_suf=suf)
self._get_passphrase(
add_desc = os.path.basename(self.infile.name) if opt.quiet else '' )
key = make_key(d.passwd, d.salt, d.hash_preset)
ret = decrypt_seed(d.enc_seed, key, d.seed_id, d.key_id)
if ret:
@ -1038,8 +1032,8 @@ to exit and re-run the program with the '--old-incog-fmt' option.
def _decrypt(self):
d = self.ssdata
self._get_hash_preset(desc_suf=d.incog_id)
self._get_passphrase(desc_suf=d.incog_id)
self._get_hash_preset(add_desc=d.incog_id)
self._get_passphrase(add_desc=d.incog_id)
# IV is used BOTH to initialize counter and to salt password!
key = make_key(d.passwd, d.iv, d.hash_preset, 'wrapper key')

55
test/misc/get_passphrase.py Executable file
View file

@ -0,0 +1,55 @@
#!/usr/bin/env python3
import sys,os
pn = os.path.abspath(os.path.dirname(sys.argv[0]))
os.chdir(os.path.dirname(os.path.dirname(pn)))
sys.path[0] = os.curdir
from mmgen.common import *
g.color = True
cmd_args = opts.init({
'text': {
'desc': '',
'usage': '',
'options': """
-P, --passwd-file=f a
-p, --hash-preset=p b
-r, --usr-randchars=n c
-L, --label=l d
-m, --keep-label e
"""
}})
from mmgen.crypto import get_passphrase,get_new_passphrase,get_hash_preset_from_user
from mmgen.wallet import Wallet
def crypto():
pw = get_new_passphrase(desc='test script')
msg(f'==> got new passphrase: [{pw}]\n')
pw = get_passphrase(desc='test script')
msg(f'==> got passphrase: [{pw}]\n')
hp = get_hash_preset_from_user(desc='test script')
msg(f'==> got hash preset: [{hp}]')
hp = get_hash_preset_from_user(desc='test script')
msg(f'==> got hash preset: [{hp}]')
def seed():
for n in range(1,3):
msg(f'------- NEW WALLET {n} -------\n')
w1 = Wallet()
msg(f'\n==> got pw,preset,lbl: [{w1.ssdata.passwd}][{w1.ssdata.hash_preset}][{w1.ssdata.label}]\n')
for n in range(1,3):
msg(f'------- PASSCHG {n} -------\n')
w2 = Wallet(ss=w1,passchg=True)
msg(f'\n==> got pw,preset,lbl: [{w2.ssdata.passwd}][{w2.ssdata.hash_preset}][{w2.ssdata.label}]\n')
msg(f'------- WALLET FROM FILE -------\n')
w3 = Wallet(fn='test/ref/FE3C6545-D782B529[128,1].mmdat') # passphrase: 'reference password'
msg(f'\n==> got pw,preset,lbl: [{w3.ssdata.passwd}][{w3.ssdata.hash_preset}][{w3.ssdata.label}]\n')
globals()[cmd_args[0]]()

View file

@ -20,6 +20,9 @@ class TestSuiteInput(TestSuiteBase):
networks = ('btc',)
tmpdir_nums = []
cmd_group = (
('get_passphrase_ui', (1,"hash preset, password and label (wallet.py)", [])),
('get_passphrase_cmdline', (1,"hash preset, password and label (wallet.py - from cmdline)", [])),
('get_passphrase_crypto', (1,"hash preset, password and label (crypto.py)", [])),
('password_entry_noecho', (1,"utf8 password entry", [])),
('password_entry_echo', (1,"utf8 password entry (echoed)", [])),
('mnemonic_entry_mmgen', (1,"stealth mnemonic entry (mmgen)", [])),
@ -37,6 +40,120 @@ class TestSuiteInput(TestSuiteBase):
('dieroll_entry_usrrand', (1,"dieroll entry (base6d) with added user entropy", [])),
)
def get_passphrase_ui(self):
t = self.spawn('test/misc/get_passphrase.py',['--usr-randchars=0','seed'],cmd_dir='.')
# 1 - new wallet, default hp,label;empty pw
t.expect('accept the default.*: ','\n',regex=True)
# bad repeat
t.expect('new MMGen wallet: ','pass1\n')
t.expect('peat passphrase: ','pass2\n')
# good repeat
t.expect('new MMGen wallet: ','\n')
t.expect('peat passphrase: ','\n')
t.expect('mpty pass')
t.expect('no label: ','\n')
t.expect('[][3][No Label]')
# 2 - new wallet, user-selected hp,pw,label
t.expect('accept the default.*: ', '1\n', regex=True)
t.expect('new MMGen wallet: ','pass1\n')
t.expect('peat passphrase: ','pass1\n')
t.expect('no label: ','lbl1\n')
t.expect('[pass1][1][lbl1]')
# 3 - passchg, nothing changes
t.expect('new hash preset')
t.expect('reuse the old value.*: ','\n',regex=True)
t.expect('unchanged')
t.expect('new passphrase.*: ','pass1\n',regex=True)
t.expect('peat passphrase: ','pass1\n')
t.expect('unchanged')
t.expect('reuse the label .*: ','\n',regex=True)
t.expect('unchanged')
t.expect('[pass1][1][lbl1]')
# 4 - passchg, everything changes
t.expect('new hash preset')
t.expect('reuse the old value.*: ','2\n',regex=True)
t.expect(' changed to')
t.expect('new passphrase.*: ','pass2\n',regex=True)
t.expect('peat passphrase: ','pass2\n')
t.expect(' changed')
t.expect('reuse the label .*: ','lbl2\n',regex=True)
t.expect(' changed to')
t.expect('[pass2][2][lbl2]')
# 5 - wallet from file
t.expect('from file')
# bad passphrase
t.expect('passphrase for MMGen wallet: ','bad\n')
t.expect('Trying again')
# good passphrase
t.expect('passphrase for MMGen wallet: ','reference password\n')
t.expect('[reference password][1][No Label]')
t.read()
return t
def get_passphrase_cmdline(self):
open('test/trash/pwfile','w').write('reference password\n')
t = self.spawn('test/misc/get_passphrase.py', [
'--usr-randchars=0',
'--label=MyLabel',
'--passwd-file=test/trash/pwfile',
'--hash-preset=1',
'seed' ],
cmd_dir = '.' )
for foo in range(4):
t.expect('[reference password][1][MyLabel]')
t.read()
return t
def get_passphrase_crypto(self):
t = self.spawn('test/misc/get_passphrase.py',['--usr-randchars=0','crypto'],cmd_dir='.')
# new passwd
t.expect('passphrase for .*: ', 'x\n', regex=True)
t.expect('peat passphrase: ', '\n')
t.expect('passphrase for .*: ', 'pass1\n', regex=True)
t.expect('peat passphrase: ', 'pass1\n')
t.expect('[pass1]')
# existing passwd
t.expect('passphrase for .*: ', 'pass2\n', regex=True)
t.expect('[pass2]')
# hash preset
t.expect('accept the default .*: ', '0\n', regex=True)
t.expect('nvalid')
t.expect('accept the default .*: ', '8\n', regex=True)
t.expect('nvalid')
t.expect('accept the default .*: ', '7\n', regex=True)
t.expect('[7]')
# hash preset (default)
t.expect('accept the default .*: ', '\n', regex=True)
t.expect(f'[{g.dfl_hash_preset}]')
t.read()
return t
def password_entry(self,prompt,cmd_args):
t = self.spawn('test/misc/password_entry.py',cmd_args,cmd_dir='.')
pw = 'abc-α'