Пример #1
0
    def validate_href(self, key, href):
        """
        This function validates that the url is valid.

        It also prefixes the http:// schema to the url
        if it was not passed in by the user.
        """
        if len(href) > 2000:
            raise AppError(f"Url too long in length. Limit: 2000 characters, Length: {len(href)}.")
        
        split_url = urlparse(href)

        # this blog post describes in detail the following regex
        # https://medium.com/@vaghasiyaharryk/9ab484a1b430
        # Summary of rules:
        # Valid url cannot start or end with -
        # The valid chars are in range [A-Za-z0-9-]
        # and there must be between 1 and 63 characters
        # finally restrict the tld to between 2 and 6 chars
        if not re.match(r"^((?!-)[A-Za-z0–9-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$", split_url.netloc):
            raise AppError(f"Invalid url: {href}")
        
        # For now I've decided to reject raw IPv4 and IPv6 addresses, and localhost

        return href
Пример #2
0
 async def process_host_command(self, stream, show_screen):
     """
     If command with one of the prefixes is received
     it will be passed to this method.
     Should return a tuple: 
     - stream (file, BytesIO etc) 
     - meta object with title and note
     """
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix == b"getlabel":
         label = self.get_label()
         obj = {
             "title": "Device's label is: %s" % label,
         }
         stream = BytesIO(label.encode())
         return stream, obj
     elif prefix == b"setlabel":
         label = stream.read().strip().decode('ascii')
         if not label:
             raise AppError('Device label cannot be empty')
         scr = Prompt("\n\nSet device label to: %s\n" % label,
                      "Current device label: %s" % self.get_label())
         res = await show_screen(scr)
         if res is False:
             return None
         self.set_label(label)
         obj = {
             "title": "New device label: %s" % label,
         }
         return BytesIO(label.encode()), obj
     else:
         raise AppError("Invalid command")
Пример #3
0
 async def process_host_command(self, stream, show_fn):
     """
     If command with one of the prefixes is received
     it will be passed to this method.
     Should return a tuple:
     - stream (file, BytesIO etc)
     - meta object with title and note
     """
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix != b"getrandom":
         # WTF? It's not our data...
         raise AppError("Prefix is not valid: %s" % prefix.decode())
     # by default we return 32 bytes
     num_bytes = 32
     try:
         num_bytes = int(stream.read().decode().strip())
     except:
         pass
     if num_bytes < 0:
         raise AppError("Seriously? %d bytes? No..." % num_bytes)
     if num_bytes > 1000:
         raise AppError("Sorry, 1k bytes max.")
     obj = {"title": "Here is your entropy", "note": "%d bytes" % num_bytes}
     return BytesIO(hexlify(get_random_bytes(num_bytes))), obj
Пример #4
0
 async def show_xpub(self, derivation, show_screen):
     self.show_loader(title="Deriving the key...")
     derivation = derivation.rstrip("/")
     net = NETWORKS[self.network]
     xpub = self.keystore.get_xpub(derivation)
     ver = bip32.detect_version(derivation, default="xpub", network=net)
     canonical = xpub.to_base58(net["xpub"])
     slip132 = xpub.to_base58(ver)
     if slip132 == canonical:
         slip132 = None
     fingerprint = hexlify(self.keystore.fingerprint).decode()
     prefix = "[%s%s]" % (
         fingerprint,
         derivation[1:],
     )
     res = await show_screen(
         XPubScreen(xpub=canonical, slip132=slip132, prefix=prefix))
     if res:
         fname = "%s-%s.txt" % (fingerprint, derivation[2:].replace(
             "/", "-"))
         if not platform.is_sd_present():
             raise AppError("SD card is not present")
         platform.mount_sdcard()
         with open(platform.fpath("/sd/%s" % fname), "w") as f:
             f.write(res)
         platform.unmount_sdcard()
         await show_screen(
             Alert("Saved!",
                   "Extended public key is saved to the file:\n\n%s" %
                   fname,
                   button_text="Close"))
Пример #5
0
 async def process_host_command(self, stream, show_screen):
     """
     If command with one of the prefixes is received
     it will be passed to this method.
     Should return a tuple: 
     - stream (file, BytesIO etc) 
     - meta object with title and note
     """
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix != b"hello":
         # WTF? It's not our data...
         raise AppError("Prefix is not valid: %s" % prefix.decode())
     name = stream.read().decode()
     # ask the user if he really wants it
     # build a screen
     scr = Prompt(
         "Say hello?", "Are you sure you want to say hello to\n\n%s?\n\n"
         "Saying hello can compromise your security!" % name)
     # show screen and wait for result
     res = await show_screen(scr)
     # check if he confirmed
     if not res:
         return
     obj = {
         "title": "Hello!",
     }
     d = b"Hello " + name.encode()
     return BytesIO(d), obj
Пример #6
0
 async def process_host_command(self, stream, show_fn):
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix not in self.prefixes:
         # WTF? It's not our data...
         raise AppError("Prefix is not valid: %s" % prefix.decode())
     mnemonic = stream.read().strip().decode()
     if not bip39.mnemonic_is_valid(mnemonic):
         raise AppError("Invalid mnemonic!")
     scr = Prompt("Load this mnemonic to memory?", "Mnemonic:")
     table = MnemonicTable(scr)
     table.align(scr.message, lv.ALIGN.OUT_BOTTOM_MID, 0, 30)
     table.set_mnemonic(mnemonic)
     if await show_fn(scr):
         self.keystore.set_mnemonic(mnemonic)
     return None
Пример #7
0
 def write_file(self, filename, filedata):
     if not platform.is_sd_present():
         raise AppError("SD card is not present")
     platform.mount_sdcard()
     with open(platform.fpath("/sd/%s" % filename), "w") as f:
         f.write(filedata)
     platform.unmount_sdcard()
Пример #8
0
 async def process_host_command(self, stream, show_screen):
     if self.keystore.is_locked:
         raise AppError("Device is locked")
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix == b"slip77":
         if not await show_screen(
                 Prompt(
                     "Confirm the action",
                     "Send master blinding private key\nto the host?\n\n"
                     "Host is requesting your\nSLIP-77 blinding key.\n\n"
                     "It will be able to watch your funds and unblind transactions."
                 )):
             return
         return BytesIO(self.keystore.slip77_key.wif(
             NETWORKS[self.network])), {}
     raise AppError("Unknown command")
Пример #9
0
 async def process_host_command(self, stream, show_screen):
     """
     If command with one of the prefixes is received
     it will be passed to this method.
     Should return a tuple: 
     - stream (file, BytesIO etc) 
     - meta object with title and note
     """
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix != b"signmessage":
         # WTF? It's not our data...
         raise AppError("Prefix is not valid: %s" % prefix.decode())
     # data format: message to sign<space>derivation_path
     # read all and delete all crap at the end (if any)
     # also message should be utf-8 decodable
     data = stream.read().strip().decode()
     if " " not in data:
         raise AppError("Invalid data encoding")
     arr = data.split(" ")
     derivation_path = arr[-1]
     message = " ".join(arr[:-1])
     # if we have fingerprint
     if not derivation_path.startswith("m/"):
         fingerprint = unhexlify(derivation_path[:8])
         if fingerprint != self.keystore.fingerprint:
             raise AppError("Not my fingerprint")
         derivation_path = "m" + derivation_path[8:]
     derivation_path = bip32.parse_path(derivation_path)
     # ask the user if he really wants to sign this message
     scr = Prompt(
         "Sign message with private key at %s?" %
         bip32.path_to_str(derivation_path), "Message:\n%s" % message)
     res = await show_screen(scr)
     if res is False:
         return None
     sig = self.sign_message(derivation_path, message.encode())
     # for GUI we can also return an object with helpful data
     xpub = self.keystore.get_xpub(derivation_path)
     address = script.p2pkh(xpub.key).address()
     obj = {
         "title": "Signature for the message:",
         "note": "using address %s" % address
     }
     return BytesIO(sig), obj
Пример #10
0
    def wrapped_function(*args, **kwargs):
        if request.method in ['POST', 'DELETE']:
            data = request.get_data(cache=False)
            if not data:
                raise AppError('No data was POSTed', 400)

            try:
                request_charset = request.mimetype_params.get('charset')
                if request_charset is not None:
                    data = json.loads(data, encoding=request_charset)
                else:
                    data = json.loads(data)
            except:
                raise AppError('Unable to parse POSTed JSON', 400)

            request.data = data

        return fn(*args, **kwargs)
Пример #11
0
def parse_software_wallet_json(obj):
    """Parse software export json"""
    if "descriptor" not in obj:
        raise AppError("Invalid wallet json")
    # get descriptor without checksum
    desc = obj["descriptor"].split("#")[0]
    # replace /0/* to /{0,1}/* to add change descriptor
    desc = desc.replace("/0/*", "/{0,1}/*")
    label = obj.get("label", "Imported wallet")
    return label, desc
Пример #12
0
 async def process_host_command(self, stream, show_screen):
     if self.keystore.is_locked:
         raise AppError("Device is locked")
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     # get device fingerprint, data is ignored
     if prefix == b"fingerprint":
         return BytesIO(hexlify(self.keystore.fingerprint)), {}
     # get xpub,
     # data: derivation path in human-readable form like m/44h/1h/0
     elif prefix == b"xpub":
         try:
             path = stream.read().strip()
             # convert to list of indexes
             path = bip32.parse_path(path.decode())
         except:
             raise AppError('Invalid path: "%s"' % path.decode())
         # get xpub
         xpub = self.keystore.get_xpub(bip32.path_to_str(path))
         # send back as base58
         return BytesIO(xpub.to_base58(NETWORKS[self.network]["xpub"]).encode()), {}
     raise AppError("Unknown command")
Пример #13
0
 async def process_host_command(self, stream, show_fn):
     # check if we've got filename, not a stream:
     if isinstance(stream, str):
         with open(stream, "rb") as f:
             return await self.process_host_command(f, show_fn)
     # processing stream now
     c = stream.read(16)
     # rewind
     stream.seek(-len(c), 1)
     if c.startswith(b"{"):
         obj = json.load(stream)
         if "descriptor" in obj:
             # this is wallet export json (Specter Desktop, FullyNoded and others)
             return await self.parse_software_wallet_json(obj, show_fn)
     elif c.startswith(b"#") or c.startswith(b"Name:"):
         return await self.parse_cc_wallet_txt(stream, show_fn)
     raise AppError("Failed parsing data")
Пример #14
0
    async def menu(self, show_screen, show_all=False):
        net = NETWORKS[self.network]
        coin = net["bip32"]
        buttons = [
            (None, "Recommended"),
            ("m/84h/%dh/%dh" % (coin, self.account), "Single key"),
            ("m/48h/%dh/%dh/2h" % (coin, self.account), "Multisig"),
            (None, "Other keys"),
        ]
        if show_all:
            buttons += [
                ("m/84h/%dh/%dh" % (coin, self.account),
                 "Single Native Segwit\nm/84h/%dh/%dh" % (coin, self.account)),
                ("m/49h/%dh/%dh" % (coin, self.account),
                 "Single Nested Segwit\nm/49h/%dh/%dh" % (coin, self.account)),
                (
                    "m/48h/%dh/%dh/2h" % (coin, self.account),
                    "Multisig Native Segwit\nm/48h/%dh/%dh/2h" %
                    (coin, self.account),
                ),
                (
                    "m/48h/%dh/%dh/1h" % (coin, self.account),
                    "Multisig Nested Segwit\nm/48h/%dh/%dh/1h" %
                    (coin, self.account),
                ),
            ]
        else:
            buttons += [(0, "Show more keys"), (2, "Change account number"),
                        (1, "Enter custom derivation")]
        # wait for menu selection
        menuitem = await show_screen(
            Menu(buttons,
                 last=(255, None),
                 title="Select the key",
                 note="Current account number: %d" % self.account))

        # process the menu button:
        # back button
        if menuitem == 255:
            return False
        elif menuitem == 0:
            return await self.menu(show_screen, show_all=True)
        elif menuitem == 1:
            der = await show_screen(DerivationScreen())
            if der is not None:
                await self.show_xpub(der, show_screen)
                return True
        elif menuitem == 2:
            account = await show_screen(
                NumericScreen(current_val=str(self.account)))
            if account and int(account) > 0x80000000:
                raise AppError('Account number too large')
            try:
                self.account = int(account)
            except:
                self.account = 0
            return await self.menu(show_screen)
        else:
            await self.show_xpub(menuitem, show_screen)
            return True
        return False
Пример #15
0
 def set_label(self, label):
     try:
         with open(self.path + "/label", "w") as f:
             f.write(label)
     except Exception:
         return AppError("Failed to save new label")
Пример #16
0
    async def menu(self, show_screen, show_all=False):
        net = NETWORKS[self.network]
        coin = net["bip32"]
        if not show_all:
            buttons = [
                (None, "Recommended"),
                ("m/84h/%dh/%dh" % (coin, self.account), "Single key"),
                ("m/48h/%dh/%dh/2h" % (coin, self.account), "Multisig"),
                (None, "Other keys"),
                (0, "Show more keys"),
                (2, "Change account number"),
                (1, "Enter custom derivation"),
                (3, "Export all keys to SD"),
            ]
        else:
            buttons = [
                (None, "Recommended"),
                (
                    "m/84h/%dh/%dh" % (coin, self.account),
                    "Single Native Segwit\nm/84h/%dh/%dh" % (coin, self.account)
                ),
                (
                    "m/48h/%dh/%dh/2h" % (coin, self.account),
                    "Multisig Native Segwit\nm/48h/%dh/%dh/2h" % (coin, self.account),
                ),
                (None, "Other keys"),
            ]
            if self.is_taproot_enabled:
                buttons.append((
                    "m/86h/%dh/%dh" % (coin, self.account),
                    "Single Taproot\nm/86h/%dh/%dh" % (coin, self.account)
                ))
            buttons.extend([
                (
                    "m/49h/%dh/%dh" % (coin, self.account),
                    "Single Nested Segwit\nm/49h/%dh/%dh" % (coin, self.account)
                ),
                (
                    "m/48h/%dh/%dh/1h" % (coin, self.account),
                    "Multisig Nested Segwit\nm/48h/%dh/%dh/1h" % (coin, self.account),
                ),
            ])
        # wait for menu selection
        menuitem = await show_screen(Menu(buttons, last=(255, None),
                                          title="Select the key",
                                          note="Current account number: %d" % self.account))

        # process the menu button:
        # back button
        if menuitem == 255:
            return False
        elif menuitem == 0:
            return await self.menu(show_screen, show_all=True)
        elif menuitem == 1:
            der = await show_screen(DerivationScreen())
            if der is not None:
                await self.show_xpub(der, show_screen)
                return True
        elif menuitem == 3:
            file_format = await self.save_menu(show_screen)
            if file_format:
                filename = self.save_all_to_sd(file_format)
                await show_screen(
                    Alert("Saved!",
                          "Public keys are saved to the file:\n\n%s" % filename,
                          button_text="Close")
                )
        elif menuitem == 2:
            account = await show_screen(NumericScreen(current_val=str(self.account)))
            if account and int(account) > 0x80000000:
                raise AppError('Account number too large')
            try:
                self.account = int(account)
            except:
                self.account = 0
            return await self.menu(show_screen)
        else:
            await self.show_xpub(menuitem, show_screen)
            return True
        return False
Пример #17
0
def parse_cc_wallet_txt(stream):
    """Parse coldcard wallet format"""
    name = "Imported wallet"
    script_type = None
    sigs_required = None
    global_derivation = None
    sigs_total = None
    cosigners = []
    current_derivation = None
    # cycle until we read everything
    char = b"\n"
    while char is not None:
        line, char = read_until(stream, b"\r\n", max_len=300)
        # skip comments
        while char is not None and (line.startswith(b"#")
                                    or len(line.strip()) == 0):
            # BW comment on derivation
            if line.startswith(b"# derivation:"):
                current_derivation = bip32.parse_path(
                    line.split(b":")[1].decode().strip())
            line, char = read_until(stream, b"\r\n", max_len=300)
        if b":" not in line:
            continue
        arr = line.split(b":")
        if len(arr) > 2:
            raise AppError("Invalid file format")
        k, v = [a.strip().decode() for a in arr]
        if k == "Name":
            name = v
        elif k == "Policy":
            nums = [int(num) for num in v.split(" of ")]
            assert len(nums) == 2
            m, n = nums
            assert m > 0 and n >= m
            sigs_required = m
            sigs_total = n
        elif k == "Format":
            assert v in CC_TYPES
            script_type = CC_TYPES[v]
        elif k == "Derivation":
            der = bip32.parse_path(v)
            if len(cosigners) == 0:
                global_derivation = der
            else:
                current_derivation = der
        # fingerprint
        elif len(k) == 8:
            cosigners.append(
                (unhexlify(k), current_derivation
                 or global_derivation, bip32.HDKey.from_string(v)))
            current_derivation = None
    assert None not in [
        global_derivation, sigs_total, sigs_required, script_type, name
    ]
    assert len(cosigners) == sigs_total
    xpubs = [
        "[%s]%s/{0,1}/*" % (bip32.path_to_str(der, fingerprint=fgp), xpub)
        for fgp, der, xpub in cosigners
    ]
    desc = "sortedmulti(%d,%s)" % (sigs_required, ",".join(xpubs))
    for sc in reversed(script_type.split("-")):
        desc = "%s(%s)" % (sc, desc)
    return name, desc