auth checks

This commit is contained in:
Peter D. Gray 2024-10-10 14:08:21 -04:00 committed by scgbckbone
parent 531ae613c7
commit 00d8c841f7
3 changed files with 98 additions and 23 deletions

View File

@ -791,7 +791,8 @@ class ApproveTransaction(UserAuthorizedAction):
async def interact(self):
# Prompt user w/ details and get approval
from glob import dis, hsm_active
from ccc import CCCFeature, PolicyViolationException
from ccc import CCCFeature
from exceptions import CCCPolicyViolationError
# step 1: parse PSBT from PSRAM into in-memory objects.
@ -826,23 +827,12 @@ class ApproveTransaction(UserAuthorizedAction):
dis.progress_bar_show(0.85)
if CCCFeature.is_enabled():
if self.psbt.active_multisig and (ccc_c_xfp in self.psbt.active_multisig.xfp_paths):
CCCFeature.validate_tx(self.psbt)
self.psbt.sign_it(CCCFeature.get_encoded_secret(), ccc_c_xfp)
except FraudulentChangeOutput as exc:
print('FraudulentChangeOutput: ' + exc.args[0])
return await self.failure(exc.args[0], title='Change Fraud')
except FatalPSBTIssue as exc:
print('FatalPSBTIssue: ' + exc.args[0])
return await self.failure(exc.args[0])
except PolicyViolationException as exc:
ch = await ux_show_story(
exc.args[0]+"\n\n Would you like to sign with A key?",
title='Policy Violation')
if ch != "y":
return
except BaseException as exc:
del self.psbt
gc.collect()
@ -855,6 +845,10 @@ class ApproveTransaction(UserAuthorizedAction):
return await self.failure(msg, exc)
# early test for spending policy; not an error if violates policy
# - might add warnings
could_ccc_sign, needs_2fa = CCCFeature.could_sign(self.psbt)
# step 2: figure out what we are approving, so we can get sign-off
# - outputs, amounts
# - fee
@ -954,7 +948,7 @@ class ApproveTransaction(UserAuthorizedAction):
return await self.failure(msg)
if ch not in 'yb':
# they don't want to!
# they don't want to sign!
self.refused = True
await ux_dramatic_pause("Refused.", 1)
@ -964,11 +958,27 @@ class ApproveTransaction(UserAuthorizedAction):
self.done()
return
if needs_2fa and could_ccc_sign:
# They still need to pass web2fa challenge (but it meets other specs ok)
try:
await CCCFeature.web2fa_challenge()
except:
could_ccc_sign = False
ch = await ux_show_story("Will not add CCC signature. Proceed anyway?")
if ch != 'y':
return await self.failure("2FA Failed")
# do the actual signing.
try:
dis.fullscreen('Wait...')
gc.collect() # visible delay caused by this but also sign_it() below
self.psbt.sign_it()
if could_ccc_sign:
dis.fullscreen('CCC Sign...')
gc.collect()
CCCFeature.sign_psbt(self.psbt)
except FraudulentChangeOutput as exc:
return await self.failure(exc.args[0], title='Change Fraud')
except MemoryError:

View File

@ -10,11 +10,9 @@ from menu import MenuSystem, MenuItem
from seed import seed_words_to_encoded_secret
from stash import SecretStash, len_to_numwords
from charcodes import KEY_QR, KEY_CANCEL, KEY_NFC
from exceptions import CCCPolicyViolationError
class PolicyViolationException(Exception):
pass
class CCCFeature:
@classmethod
def is_enabled(cls):
@ -44,9 +42,7 @@ class CCCFeature:
def get_xfp(cls):
# just the XFP
ccc = settings.get('ccc')
if ccc:
return ccc['c_xfp']
return ccc['c_xfp'] if ccc else None
@classmethod
def init_setup(cls, words):
@ -103,15 +99,79 @@ class CCCFeature:
settings.save()
@classmethod
def validate_tx(cls, psbt):
policy = cls.get_policy()
magnitude = policy.get("mag", None)
async def meets_policy(cls, psbt):
# Does policy allow signing this? Else raise why
pol = cls.get_policy()
# mag
magnitude = pol.get("mag", None)
outgoing = psbt.total_value_out - psbt.total_change_value
if magnitude < 1000:
# it is a BTC, convert to sats
magnitude = magnitude * 100000000
if outgoing > magnitude:
raise PolicyViolationException("magnitude")
raise CCCPolicyViolationError("magnitude")
# vel
# whitelist
# web2fa
# - slow, requires UX, and they might not acheive it...
# - wait until about to do signature
if pol.get('web2fa', False):
psbt.warnings.append(('CCC', 'Web 2FA required.'))
return True
@classmethod
def could_sign(cls, psbt):
# We are looking at a PSBT: can we sign it, and would we?
# - if we **could** but will not, due to policy, add warning msg
# - return (we could sign, needs2fa step)
if not cls.is_enabled:
return False, False
ms = psbt.active_multisig
if not ms:
# single-sig CCC not supported
return False, False
xfp = cls.get_xfp()
if (xfp not in ms.xfp_paths):
# does not involve us
return False, False
try:
# check policy
needs_2fa = cls.meets_policy(psbt)
except CCCPolicyViolationError:
psbt.warning.append(('CCC', "Violates spending policy. Won't sign."))
return False, False
return True, needs_2fa
@classmethod
async def web2fa_challenge(cls):
# they are trying to sign something, so make them get our their phone
# - at this point they have already ok'ed the details of the txn
# - and we have approved other elements of the spending policy.
# - TODO: maybe show wallet name? or even txn details?? (but info leak to Coinkite)
pol = cls.get_policy()
ss = pol.get('web2fa')
assert ss
ok = await web2fa.perform_web2fa('Approve CCC Transaction', ss)
if not ok:
raise CCCPolicyViolationError
@classmethod
def sign_psbt(cls, psbt):
# do the math
# TODO: capture the block height if vel is defined; no going back after this pt.
psbt.sign_it(cls.get_encoded_secret(), cls.get_xfp())
def render_mag_value(mag):
# handle integer bitcoins, and satoshis in same value
@ -544,6 +604,7 @@ async def gen_or_import():
elif ch == '6':
# pick existing from Seed Vault
enc = await SeedVaultChooserMenu.pick(words_only=True)
if not enc: return None
words = SecretStash.decode_words(enc)
await enable_step1(words)

View File

@ -51,4 +51,8 @@ class QRDecodeExplained(ValueError):
class UnknownAddressExplained(ValueError):
pass
# We're not going to co-sign using CCC feature
class CCCPolicyViolationError(RuntimeError):
pass
# EOF