# (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # tests related to CCC feature # # run simulator without --eff # # import pytest, pdb, requests, re, time, random, json, glob, os, hashlib, base64, uuid from base64 import urlsafe_b64encode from onetimepass import get_totp from helpers import prandom, slip132undo from pysecp256k1.ecdh import ecdh, ECDH_HASHFP_CLS from pysecp256k1 import ec_seckey_verify, ec_pubkey_parse, ec_pubkey_serialize, ec_pubkey_create from mnemonic import Mnemonic from bip32 import BIP32Node from constants import AF_P2WSH from charcodes import KEY_QR, KEY_NFC from bbqr import split_qrs from psbt import BasicPSBT # pubkey for production server. SERVER_PUBKEY = '0231301ec4acec08c1c7d0181f4ffb8be70d693acccc86cccb8f00bf2e00fcabfd' @pytest.fixture def goto_ccc_menu(goto_home, pick_menu_item, is_q1): def doit(): goto_home() pick_menu_item("Advanced/Tools") pick_menu_item("Spending Policy") pick_menu_item("Co-Sign Multisig (CCC)" if is_q1 else "Co-Sign Multi.") return doit def make_session_key(his_pubkey=None): # - second call: given the pubkey of far side, calculate the shared pt on curve # - creates session key based on that while True: my_seckey = prandom(32) try: ec_seckey_verify(my_seckey) break except: continue my_pubkey = ec_pubkey_create(my_seckey) his_pubkey = ec_pubkey_parse(bytes.fromhex(SERVER_PUBKEY)) # do the D-H thing def _py_ckcc_hashfp(output, x, y, data=None): try: m = hashlib.sha256() m.update(x.contents.raw) m.update(y.contents.raw) output.contents.raw = m.digest() return 1 except: return 0 ckcc_hashfp = ECDH_HASHFP_CLS(_py_ckcc_hashfp) shared_key = ecdh(my_seckey, his_pubkey, hashfp=ckcc_hashfp) return shared_key, ec_pubkey_serialize(my_pubkey) @pytest.fixture def make_2fa_url(request): def doit(shared_secret=b'A'*16, nonce='12345678', wallet='Example wallet name', is_q=0, encrypted=False): lh = request.config.getoption("--localhost") base = 'http://127.0.0.1:5070/2fa?' if lh else 'https://coldcard.com/2fa?' assert is_q in {0, 1} assert len(shared_secret) == 16 # base32 assert isinstance(nonce, str) # hex digits or 8 dec digits in Mk4 mode from urllib.parse import quote qs = f'ss={shared_secret}&q={is_q}&g={nonce}&nm={quote(wallet)}' print(f'2fa URL: {qs}') if not encrypted: return base + qs # pick eph key ses_key, pubkey = make_session_key() import pyaes enc = pyaes.AESModeOfOperationCTR(ses_key, pyaes.Counter(0)).encrypt qs = urlsafe_b64encode(pubkey + enc(qs.encode('ascii'))).rstrip(b'=') return base + qs.decode('ascii') return doit @pytest.fixture def roundtrip_2fa(): def doit(url, shared_secret, local=False): if local: url = url.replace('https://coldcard.com/', 'http://127.0.0.1:5070/') if int(time.time() % 30) > 29: # avoid end of time period time.sleep(3) # build right TOTP answer answer = '%06d' % get_totp(shared_secret) assert len(answer) == 6 # send both request and answer at same time (we know it works that way) resp = requests.post(url, data=dict(answer=answer)) # server HTML will have this line in response for our use # if '