Skip to content

Commit

Permalink
Merge pull request #185 from Nitrokey/nkfido2-update
Browse files Browse the repository at this point in the history
Correct Nitrokey FIDO2 update
  • Loading branch information
szszszsz authored Feb 9, 2022
2 parents 3b0d9e6 + 950e084 commit 8a16a1e
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 30 deletions.
93 changes: 64 additions & 29 deletions pynitrokey/cli/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@
help="Serial number of Nitrokey to use. Prefix with 'device=' to provide device file, e.g. 'device=/dev/hidraw5'.",
default=None,
)
@click.option("-y", "yes", default=False, is_flag=True, help="agree to everything")
def update(serial, yes):
@click.option("-y", "--yes", default=False, is_flag=True, help="agree to everything")
@click.option("-f", "--force", default=False, is_flag=True, help="force")
def update(serial, yes, force):
"""Update Nitrokey key to latest firmware version."""

# @fixme: print this and allow user to cancel (if not -y is active)
Expand Down Expand Up @@ -72,7 +73,7 @@ def update(serial, yes):
None,
"If you are on Linux, are your udev rules up to date?",
"For more, see: ",
" https://www.nitrokey.com/documentation/installation#os:linux",
" https://docs.nitrokey.com/fido2/linux/index.html#troubleshooting",
None,
)

Expand All @@ -93,6 +94,7 @@ def update(serial, yes):
# @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"
gh_release_data = None
try:
gh_release_data = json.loads(requests.get(api_url).text)
except Exception as e:
Expand All @@ -110,32 +112,53 @@ def update(serial, yes):
"Failed to determine latest release (url)", "assets:", *map(str, assets)
)

# download asset url
# @fixme: move to confconsts.py ...
import os.path
local_print(
f"Downloading latest firmware: {gh_release_data['tag_name']} "
f"(published at {gh_release_data['published_at']})"
f"Found latest firmware: {os.path.basename(download_url)}\n"
f"\t\t(published at {gh_release_data['published_at']}, under tag {gh_release_data['tag_name']})"
)
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']}",
)

ver = client.solo_version()
local_print(f"\tCurrent Firmware version: {ver[0]}.{ver[1]}.{ver[2]}")

# if the downloaded firmware version is the same as the current one, skip update unless force switch is provided
# if f'firmware-{ver[0]}.{ver[1]}.{ver[2]}' in gh_release_data['tag_name'] and not force:
if f'firmware-{ver[0]}.{ver[1]}.{ver[2]}' in download_url:
if not force:
local_critical(
"Your firmware is up-to-date!\n"
"Use --force flag to run update process anyway.",
support_hint=False)
else:
local_print("Firmware is up-to-date. Continue due to --force switch applied.")

def download_firmware():
# 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"Current Firmware version: {ver[0]}.{ver[1]}.{ver[2]}")
local_print(
f"\tFirmware saved to {fw_fn}",
f"\tDownloaded firmware version: {gh_release_data['tag_name']}",
)
return fw_fn
fw_fn = download_firmware()

# ask for permission
if not yes:
local_print("")
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)
Expand All @@ -145,21 +168,33 @@ def update(serial, yes):
local_print("Key already in bootloader mode, continuing...")
else:
try:
local_print("Entering bootloader mode, please confirm with button on key!")
local_print("Entering bootloader mode, please confirm with button on key! (long 10 second press)")
client.use_hid()
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:
client = find(serial)
client.use_hid()
client.program_file(fw_fn)

except Exception as e:
local_critical("problem flashing firmware:", e)
time.sleep(1.0)

def connect_and_flash():
# reconnect and actually flash it...
# fail after 5 attempts
exc = None
for _ in range(5):
try:
client = find(serial)
client.use_hid()
client.program_file(fw_fn)
break
except Exception as e:
time.sleep(0.5)
exc = e
# todo log exception info from each failed iteration
else:
local_critical("problem flashing firmware:", exc)

connect_and_flash()

local_print(None, "After update version check...")

Expand Down
2 changes: 1 addition & 1 deletion pynitrokey/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def local_print(*messages, **kwargs):
logger.exception(item)
passed_exc = item
item = repr(item)
item = "Exception encountered: " + item
item = "\tException encountered: " + item

# just a newline, don't log to file...
elif item is None or item == "":
Expand Down

0 comments on commit 8a16a1e

Please sign in to comment.