Пример #1
0
 def show(self):
     local_critical(
         *self.messages,
         support_hint=self.support_hint,
         ret_code=self.ret_code,
         **self.kwargs,
     )
Пример #2
0
def update(regnual, gnuk, default_password, password, wait_e, keyno, verbose, yes,
           skip_bootloader, green_led):
    """update device's firmware"""

    args = (regnual, gnuk, default_password, password, wait_e, keyno, verbose, yes,
           skip_bootloader, green_led)

    if green_led and (regnual is None or gnuk is None):
        local_critical(
            "You selected the --green-led option, please provide '--regnual' and "
            "'--gnuk' in addition to proceed. ",
            "use one from: https://github.com/Nitrokey/nitrokey-start-firmware)")

    if IS_LINUX:
        with ThreadLog(logger.getChild("dmesg"), "dmesg -w"):
            start_update(*args)
    else:
        start_update(*args)
Пример #3
0
def get_latest_release_data():
    try:
        # @todo: move to confconsts.py
        r = requests.get(
            'https://api.github.com/repos/Nitrokey/nitrokey-start-firmware/releases'
        )
        json = r.json()
        if r.status_code == 403:
            local_critical(
                f"JSON raw data: {json}",
                f"No Github API access, status code: {r.status_code}")
        latest_tag = json[0]

    except Exception as e:
        local_critical("Failed getting release data", e)
        latest_tag = defaultdict(lambda: "unknown")

    return latest_tag
Пример #4
0
def set_identity(identity):
    """set given identity (one of: 0, 1, 2)"""
    if not identity.isdigit():
        local_critical("identity number must be a digit")

    identity = int(identity)
    if identity < 0 or identity > 2:
        local_print("identity must be 0, 1 or 2")

    local_print(f"Setting identity to {identity}")
    for x in range(3):
        try:
            gnuk = get_gnuk_device()
            gnuk.cmd_select_openpgp()
            try:
                gnuk.cmd_set_identity(identity)
            except USBError:
                local_print(f"reset done - now active identity: {identity}")
                break

        except ValueError as e:
            if "No ICC present" in str(e):
                local_print("Could not connect to device, trying to close scdaemon")
                result = check_output(["gpg-connect-agent",
                                       "SCD KILLSCD", "SCD BYE",
                                       "/bye"])  # gpgconf --kill all might be better?
                sleep(3)
            else:
                local_critical(e)
        except Exception as e:
            local_critical(e)
Пример #5
0
def feedkernel(count, serial):
    """Feed random bytes to /dev/random."""

    if os.name != "posix":
        local_critical("This is a Linux-specific command!")

    if not 0 <= count <= 255:
        local_critical(
            f"Number of bytes must be between 0 and 255, you passed {count}")

    p = nkfido2.find(serial)

    RNDADDENTROPY = 0x40085203

    entropy_info_file = "/proc/sys/kernel/random/entropy_avail"
    print(f"entropy before: 0x{open(entropy_info_file).read().strip()}")

    r = p.get_rng(count)

    # man 4 random

    # RNDADDENTROPY
    #       Add some additional entropy to the input pool, incrementing the
    #       entropy count. This differs from writing to /dev/random or
    #       /dev/urandom, which only adds some data but does not increment the
    #       entropy count. The following structure is used:

    #           struct rand_pool_info {
    #               int    entropy_count;
    #               int    buf_size;
    #               __u32  buf[0];
    #           };

    #       Here entropy_count is the value added to (or subtracted from) the
    #       entropy count, and buf is the buffer of size buf_size which gets
    #       added to the entropy pool.

    # maximum 8, tend to be pessimistic
    entropy_bits_per_byte = 2
    t = struct.pack(f"ii{count}s", count * entropy_bits_per_byte, count, r)

    try:
        with open("/dev/random", mode="wb") as fh:
            fcntl.ioctl(fh, RNDADDENTROPY, t)

    except PermissionError as e:
        local_critical(
            "insufficient permissions to use `fnctl.ioctl` on '/dev/random'",
            "please run 'nitropy' with proper permissions",
            e,
        )

    local_print(f"entropy after:  0x{open(entropy_info_file).read().strip()}")
Пример #6
0
def start_update(
    regnual,
    gnuk,
    default_password,
    password,
    wait_e,
    keyno,
    verbose,
    yes,
    skip_bootloader,
    green_led,
):

    # @todo: move to some more generic position...
    local_print("Nitrokey Start firmware update tool")
    # @fixme: especially this, which is to be handle application wide
    logger.debug("Start session {}".format(datetime.now()))
    local_print("Platform: {}".format(platform.platform()))
    local_print("System: {}, is_linux: {}".format(platform.system(), IS_LINUX))
    local_print("Python: {}".format(platform.python_version()))
    local_print("Saving run log to: {}".format(LOG_FN))

    arg_descs = [
        "regnual",
        "gnuk",
        "default_password",
        "password",
        "wait_e",
        "keyno",
        "verbose",
        "yes",
        "skip_bootloader",
        "green_led",
    ]
    args = (
        regnual,
        gnuk,
        default_password,
        "<hidden>",
        wait_e,
        keyno,
        verbose,
        yes,
        skip_bootloader,
        green_led,
    )
    logger.debug("Arguments: " +
                 ", ".join(f"{key}= '{val}'"
                           for key, val in zip(arg_descs, args)))

    passwd = None

    if verbose == 3:
        stream_handler = logging.StreamHandler()
        stream_handler.setLevel(logging.DEBUG)
        stream_handler.setFormatter(logging.Formatter(LOG_FORMAT_STDOUT))
        logger.addHandler(stream_handler)

    if password:
        passwd = password
    elif default_password:
        passwd = DEFAULT_PW3
    if not passwd:
        try:
            passwd = AskUser.hidden("Admin password:"******"aborting update", e)

    local_print("Firmware data to be used:")
    data = get_firmware_file(regnual, FirmwareType.REGNUAL)
    data_upgrade = get_firmware_file(gnuk, FirmwareType.GNUK)

    # Detect devices
    dev_strings = get_devices()
    if len(dev_strings) > 1:
        local_critical(
            "Only one device should be connected",
            "Please remove other devices and retry",
        )

    if dev_strings:
        local_print("Currently connected device strings:")
        print_device(dev_strings[0])
    else:
        local_print("Cannot identify device")

    # @todo: debugging information, log-file only
    local_print(f"initial device strings: {dev_strings}")

    latest_tag = get_latest_release_data()

    local_print(
        f"Please note:",
        f"- Latest firmware available is: ",
        f"  {latest_tag['tag_name']} (published: {latest_tag['published_at']})",
        f"- provided firmware: {gnuk}",
        f"- all data will be removed from the device!",
        f"- do not interrupt update process - the device may not run properly!",
        f"- the process should not take more than 1 minute",
    )
    if yes:
        local_print("Accepted automatically")
    else:
        if not AskUser.strict_yes_no("Do you want to continue?"):
            local_critical("Exiting due to user request", support_hint=False)

    update_done = False
    retries = 3
    for attempt_counter in range(retries):
        try:
            # First 4096-byte in data_upgrade is SYS, so, skip it.
            main(
                wait_e,
                keyno,
                passwd,
                data,
                data_upgrade[4096:],
                skip_bootloader,
                verbosity=verbose,
            )
            update_done = True
            break

        # @todo: add proper exceptions (for each case) here
        except ValueError as e:
            local_print("error while running update", e)
            str_factory_reset = (
                "Please 'factory-reset' your device to "
                "continue (this will delete all user data from the device) "
                "and try again with PIN='12345678'")

            if "No ICC present" in str(e):
                kill_smartcard_services()
                local_print("retrying...")

            else:
                # @fixme run factory reset here since data are lost anyway (rly?)
                if str(e) == ERR_EMPTY_COUNTER:
                    local_critical(
                        "- device returns: 'Attempt counter empty' "
                        "- error for Admin PIN",
                        str_factory_reset,
                        e,
                    )

                if str(e) == ERR_INVALID_PIN:
                    local_critical(
                        "- device returns: 'Invalid PIN' error",
                        "- please retry with correct PIN",
                        e,
                    )
        except Exception as e:
            local_critical("unexpected error", e)

    if not update_done:
        local_critical(
            "",
            "Could not proceed with the update",
            "Please execute one or all of the following and try again:",
            "- re-insert device to the USB slot",
            "- run factory-reset on the device",
            "- close other applications, which could use it (e.g., scdaemon, pcscd)",
        )

    dev_strings_upgraded = None
    takes_long_time = False
    local_print("Currently connected device strings (after upgrade):")
    for i in range(TIME_DETECT_DEVICE_AFTER_UPDATE_S):
        if i > TIME_DETECT_DEVICE_AFTER_UPDATE_LONG_S:
            if not takes_long_time:
                local_print("", "Please reinsert device to the USB slot")
                takes_long_time = True
        time.sleep(1)
        dev_strings_upgraded = get_devices()
        if len(dev_strings_upgraded) > 0:
            local_print()
            print_device(dev_strings_upgraded[0])
            break
        local_print(".", end="", flush=True)

    if not dev_strings_upgraded:
        local_print(
            "",
            "could not connect to the device - might be due to a failed update",
            "please re-insert the device, check version using:",
            "$ nitropy start list",
        )

    local_print(
        f"device can now be safely removed from the USB slot",
        f"final device strings: {dev_strings_upgraded}",
    )

    # @todo: add this to all logs and skip it here
    local_print(f"finishing session {datetime.now()}")
    # @todo: always output this in certain situations... (which ones? errors? warnings?)
    local_print(f"Log saved to: {LOG_FN}")
Пример #7
0
def download_file_or_exit(url):
    resp = requests.get(url)
    if not resp.ok:
        local_critical(f"Cannot download firmware: {url}: {resp.status_code}")
    firmware_data = resp.content
    return firmware_data
Пример #8
0
def update(serial, yes):
    """Update Nitrokey key to latest firmware version."""

    # @fixme: print this and allow user to cancel (if not -y is active)
    #update_url = 'https://update.nitrokey.com/'
    #print('Please use {} to run the firmware update'.format(update_url))
    #return

    IS_LINUX = platform.system() == "Linux"

    logger.debug(f"Start session {datetime.now()}")

    # @fixme: move to generic startup stuff logged into file exclusively!
    local_print("Nitrokey FIDO2 firmware update tool",
                f"Platform: {platform.platform()}",
                f"System: {platform.system()}, is_linux: {IS_LINUX}",
                f"Python: {platform.python_version()}",
                f"Saving run log to: {LOG_FN}", "",
                f"Starting update procedure for Nitrokey FIDO2...")

    from pynitrokey.fido2 import find

    # Determine target key
    client = None
    try:
        client = find(serial)

    except pynitrokey.exceptions.NoSoloFoundError as e:
        local_critical(None,
            "No Nitrokey key found!", e, None,
            "If you are on Linux, are your udev rules up to date?",
            "For more, see: ",
            "  https://www.nitrokey.com/documentation/installation#os:linux",
            None)

    except pynitrokey.exceptions.NonUniqueDeviceError as e:
        local_critical(None,
            "Multiple Nitrokey keys are plugged in!", e, None,
            "Please unplug all but one key", None)

    except Exception as e:
        local_critical(None, "Unhandled error connecting to key", e, None)

    # determine asset url: we want the (signed) json file
    # @fixme: move to confconsts.py ...
    api_base_url = "https://api.github.com/repos"
    api_url = f"{api_base_url}/Nitrokey/nitrokey-fido2-firmware/releases/latest"
    try:
        gh_release_data = json.loads(requests.get(api_url).text)
    except Exception as e:
        local_critical("Failed downloading firmware", e)

    # search asset with `fn` suffix being .json and take its url
    assets = [(x["name"], x["browser_download_url"]) \
              for x in gh_release_data["assets"]]
    download_url = None
    for fn, url in assets:
        if fn.endswith(".json"):
            download_url = url
            break
    if not download_url:
        local_critical("Failed to determine latest release (url)",
                       "assets:", *map(str, assets))

    # download asset url
    # @fixme: move to confconsts.py ...
    local_print(f"Downloading latest firmware: {gh_release_data['tag_name']} "
                f"(published at {gh_release_data['published_at']})")
    tmp_dir = tempfile.gettempdir()
    fw_fn = os.path.join(tmp_dir, "fido2_firmware.json")
    try:
        with open(fw_fn, "wb") as fd:
            firmware = requests.get(download_url)
            fd.write(firmware.content)
    except Exception as e:
        local_critical("Failed downloading firmware", e)

    local_print(f"Firmware saved to {fw_fn}",
                f"Downloaded firmware version: {gh_release_data['tag_name']}")

    # @fixme: whyyyyy is this here, move away... (maybe directly next to `fido2.find()`)
    def get_dev_details():

        # @fixme: why not use `find` here...
        from pynitrokey.fido2 import find_all
        c = find_all()[0]

        _props = c.dev.descriptor
        local_print(f"Device connected:")
        if "serial_number" in _props:
            local_print(f"{_props['serial_number']}: {_props['product_string']}")
        else:
            local_print(f"{_props['path']}: {_props['product_string']}")

        version_raw = c.solo_version()
        major, minor, patch = version_raw[:3]
        locked = "" if len(version_raw) > 3 and version_raw[3] else "unlocked"

        local_print(f"Firmware version: {major}.{minor}.{patch} {locked}", None)

    get_dev_details()

    # ask for permission
    if not yes:
        local_print("This will update your Nitrokey FIDO2")
        if not AskUser.strict_yes_no("Do you want to continue?"):
            local_critical("exiting due to user input...", support_hint=False)

    # Ensure we are in bootloader mode
    if client.is_solo_bootloader():
        local_print("Key already in bootloader mode, continuing...")
    else:
        try:
            local_print("Entering bootloader mode, please confirm with button on key!")
            client.enter_bootloader_or_die()
            time.sleep(0.5)
        except Exception as e:
            local_critical("problem switching to bootloader mode:", e)

    # reconnect and actually flash it...
    try:
        from pynitrokey.fido2 import find
        client = find(serial)
        client.use_hid()
        client.program_file(fw_fn)

    except Exception as e:
        local_critical("problem flashing firmware:", e)

    local_print(None, "After update check")
    tries = 100
    for i in range(tries):
        try:
            get_dev_details()
            break
        except Exception as e:
            if i > tries-1:
                local_critical("Could not connect to device after update", e)
                raise
            time.sleep(0.5)

    local_print("Congratulations, your key was updated to the latest firmware.")
    logger.debug("Finishing session {}".format(datetime.now()))
    local_print("Log saved to: {}".format(LOG_FN))