mirror of
https://github.com/token2/fido2-manage.git
synced 2026-04-09 10:45:39 +00:00
Update gui1.py
This commit is contained in:
344
gui1.py
344
gui1.py
@@ -1,26 +1,3 @@
|
|||||||
def execute_info_command_no_pin(device_digit):
|
|
||||||
"""Execute info command without PIN - just show basic device info"""
|
|
||||||
tree.delete(*tree.get_children())
|
|
||||||
|
|
||||||
# Execute info command without PIN
|
|
||||||
info_command = [FIDO_COMMAND, "-info", "-device", device_digit]
|
|
||||||
try:
|
|
||||||
result = subprocess.run(info_command, capture_output=True, text=True)
|
|
||||||
if result.returncode == 0:
|
|
||||||
for line in result.stdout.splitlines():
|
|
||||||
if ": " in line:
|
|
||||||
key, value = line.split(": ", 1)
|
|
||||||
tree.insert("", tk.END, values=(key, value))
|
|
||||||
else:
|
|
||||||
raise subprocess.CalledProcessError(result.returncode, info_command)
|
|
||||||
except Exception as e:
|
|
||||||
messagebox.showerror(
|
|
||||||
"Error", f"Command execution failed: {e}\nOutput: {result.stderr}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -44,7 +21,7 @@ def detect_terminal():
|
|||||||
if shutil.which(term):
|
if shutil.which(term):
|
||||||
return term, flag
|
return term, flag
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
# Define the command to execute
|
# Define the command to execute
|
||||||
FIDO_COMMAND = "./fido2-manage.sh"
|
FIDO_COMMAND = "./fido2-manage.sh"
|
||||||
|
|
||||||
@@ -54,114 +31,28 @@ TERM, TERM_FLAG = detect_terminal()
|
|||||||
if TERM is None:
|
if TERM is None:
|
||||||
messagebox.showerror("Error", "No supported terminal emulator found. Please install xterm or gnome-terminal.")
|
messagebox.showerror("Error", "No supported terminal emulator found. Please install xterm or gnome-terminal.")
|
||||||
|
|
||||||
|
|
||||||
# Command below for Windows
|
# Command below for Windows
|
||||||
# FIDO_COMMAND = 'fido2-manage-ui.exe'
|
# FIDO_COMMAND = 'fido2-manage-ui.exe'
|
||||||
# Global variable to store the PIN
|
# Global variable to store the PIN
|
||||||
PIN = None
|
PIN = None
|
||||||
|
|
||||||
|
|
||||||
# Function to get device list from fido2-manage-ui.exe
|
# Function to get device list from fido2-manage-ui.exe
|
||||||
def get_device_list():
|
def get_device_list():
|
||||||
try:
|
try:
|
||||||
# Execute the command with '-list' argument and capture the output
|
|
||||||
result = subprocess.run([FIDO_COMMAND, "-list"], capture_output=True, text=True)
|
result = subprocess.run([FIDO_COMMAND, "-list"], capture_output=True, text=True)
|
||||||
# Split the output into lines and return as a list
|
|
||||||
device_list = result.stdout.strip().split("\n")
|
device_list = result.stdout.strip().split("\n")
|
||||||
return device_list
|
return device_list
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Handle exceptions (e.g., file not found or command error)
|
|
||||||
print(f"Error executing device list command: {e}")
|
print(f"Error executing device list command: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
# Function to set the PIN via dialog
|
|
||||||
def get_pin():
|
|
||||||
global PIN
|
|
||||||
PIN = simpledialog.askstring(
|
|
||||||
"PIN Code", "Enter PIN code:", show="*"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Function to execute storage command
|
|
||||||
def execute_storage_command(device_digit):
|
|
||||||
global PIN
|
|
||||||
if PIN is None:
|
|
||||||
messagebox.showwarning("PIN Required", "PIN is required.")
|
|
||||||
return
|
|
||||||
|
|
||||||
command = [FIDO_COMMAND, "-storage", "-pin", PIN, "-device", device_digit]
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = subprocess.run(command, capture_output=True, text=True)
|
|
||||||
if result.returncode == 0:
|
|
||||||
# Parse the output and insert into the treeview
|
|
||||||
for line in reversed(result.stdout.splitlines()):
|
|
||||||
if ": " in line:
|
|
||||||
key, value = line.split(": ", 1)
|
|
||||||
tree.insert("", 0, values=(key, value))
|
|
||||||
else:
|
|
||||||
raise subprocess.CalledProcessError(result.returncode, command)
|
|
||||||
except Exception as e:
|
|
||||||
messagebox.showerror(
|
|
||||||
"Error", f"Command execution failed: {e}\nOutput: {result.stderr}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Function to execute info command and append its output to the grid
|
# Function to execute info command and append its output to the grid
|
||||||
def execute_info_command(device_digit):
|
def execute_info_command(device_digit):
|
||||||
global PIN
|
global PIN
|
||||||
|
|
||||||
if PIN is None:
|
|
||||||
messagebox.showwarning("PIN Required", "PIN is required.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
tree.delete(*tree.get_children())
|
tree.delete(*tree.get_children())
|
||||||
|
|
||||||
# First, try storage command
|
|
||||||
storage_command = [FIDO_COMMAND, "-storage", "-pin", PIN, "-device", device_digit]
|
|
||||||
try:
|
|
||||||
result = subprocess.run(storage_command, capture_output=True, text=True)
|
|
||||||
|
|
||||||
if result.stderr.find("FIDO_ERR_PIN_INVALID") != -1:
|
info_command = [FIDO_COMMAND, "-info", "-device", device_digit]
|
||||||
messagebox.showerror("Error", f"Invalid PIN provided")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if result.stderr.find("FIDO_ERR_PIN_AUTH_BLOCKED") != -1:
|
|
||||||
messagebox.showerror(
|
|
||||||
"Error", f"Wrong PIN provided too many times. Reinsert the key"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if result.stderr.find("FIDO_ERR_PIN_REQUIRED") != -1:
|
|
||||||
messagebox.showwarning(
|
|
||||||
"Warning",
|
|
||||||
"No PIN set for this key. You must set a PIN before managing passkeys."
|
|
||||||
)
|
|
||||||
change_pin_button.config(text="Set PIN", state=tk.ACTIVE, command=set_pin)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if result.stderr.find("FIDO_ERR_INVALID_CBOR") != -1:
|
|
||||||
messagebox.showerror(
|
|
||||||
"Error",
|
|
||||||
f"This is an older key (probably FIDO2.0). No passkey management is possible with this key. Only basic information will be shown.",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if the subprocess was executed successfully
|
|
||||||
if result.returncode == 0:
|
|
||||||
for line in result.stdout.splitlines():
|
|
||||||
if ": " in line:
|
|
||||||
key, value = line.split(": ", 1)
|
|
||||||
tree.insert("", tk.END, values=(key, value))
|
|
||||||
else:
|
|
||||||
raise subprocess.CalledProcessError(result.returncode, storage_command)
|
|
||||||
except Exception as e:
|
|
||||||
messagebox.showerror(
|
|
||||||
"Error", f"Command execution failed: {e}\nOutput: {result.stderr}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Then, try info command
|
|
||||||
info_command = [FIDO_COMMAND, "-info", "-pin", PIN, "-device", device_digit]
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(info_command, capture_output=True, text=True)
|
result = subprocess.run(info_command, capture_output=True, text=True)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
@@ -172,82 +63,86 @@ def execute_info_command(device_digit):
|
|||||||
else:
|
else:
|
||||||
raise subprocess.CalledProcessError(result.returncode, info_command)
|
raise subprocess.CalledProcessError(result.returncode, info_command)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror(
|
messagebox.showerror("Error", f"Command execution failed: {e}\nOutput: {result.stderr}")
|
||||||
"Error", f"Command execution failed: {e}\nOutput: {result.stderr}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
storage_command = f"{FIDO_COMMAND} -storage -device {device_digit}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
child = pexpect.spawn(storage_command, encoding="utf-8", timeout=10)
|
||||||
|
|
||||||
|
index = child.expect([
|
||||||
|
r"Enter PIN for",
|
||||||
|
pexpect.EOF,
|
||||||
|
pexpect.TIMEOUT
|
||||||
|
])
|
||||||
|
|
||||||
|
output = child.before
|
||||||
|
|
||||||
|
if index == 0:
|
||||||
|
pin_button.config(text="Change PIN", state=tk.ACTIVE, command=change_pin)
|
||||||
|
|
||||||
|
if index == 1:
|
||||||
|
messagebox.showwarning(
|
||||||
|
"Warning",
|
||||||
|
"No PIN is set for this key. You must set a PIN before managing passkeys."
|
||||||
|
)
|
||||||
|
pin_button.config(text="Set PIN", state=tk.ACTIVE, command=set_pin)
|
||||||
|
|
||||||
|
if index == 2:
|
||||||
|
if "FIDO_ERR_PIN_REQUIRED" in output:
|
||||||
|
pin_button.config(text="Set PIN", state=tk.ACTIVE, command=set_pin)
|
||||||
|
|
||||||
|
if "FIDO_ERR_PIN_INVALID" in output:
|
||||||
|
messagebox.showerror("Error", f"Invalid PIN provided")
|
||||||
|
|
||||||
|
if "FIDO_ERR_PIN_AUTH_BLOCKED" in output:
|
||||||
|
messagebox.showerror("Error", f"Wrong PIN provided too many times. Reinsert the key")
|
||||||
|
|
||||||
|
if "FIDO_ERR_INVALID_CBOR" in output:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Error",
|
||||||
|
f"This is an older key (probably FIDO2.0). No passkey management is possible with this key. Only basic information will be shown.",
|
||||||
|
)
|
||||||
|
|
||||||
|
messagebox.showerror("Unexpected Device Output", output)
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Command execution failed: {e}\nOutput: {result.stderr}")
|
||||||
|
|
||||||
|
# Function to set the PIN
|
||||||
|
def get_pin():
|
||||||
|
global PIN
|
||||||
|
PIN = simpledialog.askstring("PIN Code", "Enter PIN code:", show="*")
|
||||||
|
|
||||||
# Function to handle selection event
|
# Function to handle selection event
|
||||||
def on_device_selected(event):
|
def on_device_selected(event):
|
||||||
global PIN
|
|
||||||
selected_device = device_var.get()
|
selected_device = device_var.get()
|
||||||
# Extract the digit inside the first pair of square brackets
|
|
||||||
match = re.search(r"\[(\d+)\]", selected_device)
|
match = re.search(r"\[(\d+)\]", selected_device)
|
||||||
PIN = None
|
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
device_digit = match.group(1)
|
device_digit = match.group(1)
|
||||||
# Prompt for PIN
|
execute_info_command(device_digit)
|
||||||
get_pin()
|
passkeys_button.config(state=tk.NORMAL) # Always enable the Passkeys button
|
||||||
if PIN is not None:
|
|
||||||
if execute_info_command(device_digit):
|
|
||||||
check_passkeys_button_state()
|
|
||||||
check_changepin_button_state()
|
|
||||||
else:
|
else:
|
||||||
messagebox.showinfo("Device Selected", "No digit found in the selected device")
|
messagebox.showinfo("Device Selected", "No digit found in the selected device")
|
||||||
|
|
||||||
|
|
||||||
# Function to check if the "passkeys" button should be enabled
|
|
||||||
def check_passkeys_button_state():
|
|
||||||
# Enable passkeys button if device is selected
|
|
||||||
if device_var.get():
|
|
||||||
passkeys_button.config(state=tk.NORMAL)
|
|
||||||
else:
|
|
||||||
passkeys_button.config(state=tk.DISABLED)
|
|
||||||
|
|
||||||
|
|
||||||
# Function to check if the change PIN button should be enabled
|
|
||||||
def check_changepin_button_state():
|
|
||||||
changepin_button_state = tk.DISABLED
|
|
||||||
for child in tree.get_children():
|
|
||||||
values = tree.item(child, "values")
|
|
||||||
if values and len(values) == 2 and values[0] == "remaining rk(s)":
|
|
||||||
try:
|
|
||||||
rk_count = int(values[1])
|
|
||||||
if rk_count > 0:
|
|
||||||
changepin_button_state = tk.NORMAL
|
|
||||||
break
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
change_pin_button.config(state=changepin_button_state)
|
|
||||||
|
|
||||||
|
|
||||||
# Function to handle "passkeys" button click
|
# Function to handle "passkeys" button click
|
||||||
def on_passkeys_button_click():
|
def on_passkeys_button_click():
|
||||||
global PIN
|
global PIN
|
||||||
|
|
||||||
# Get the selected device
|
|
||||||
selected_device = device_var.get()
|
selected_device = device_var.get()
|
||||||
match = re.search(r"\[(\d+)\]", selected_device)
|
match = re.search(r"\[(\d+)\]", selected_device)
|
||||||
if not match:
|
if not match:
|
||||||
messagebox.showinfo("Device Selected", "No digit found in the selected device")
|
messagebox.showinfo("Device Selected", "No digit found in the selected device")
|
||||||
return
|
return
|
||||||
|
|
||||||
device_digit = match.group(1)
|
device_digit = match.group(1)
|
||||||
|
|
||||||
# Prompt for PIN if not already set
|
|
||||||
if PIN is None:
|
if PIN is None:
|
||||||
get_pin()
|
set_pin()
|
||||||
|
if PIN is None:
|
||||||
# Check again if PIN was entered
|
return
|
||||||
if PIN is None:
|
|
||||||
messagebox.showwarning("PIN Required", "PIN is required to manage passkeys.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Execute the command to get resident keys
|
|
||||||
command = [
|
command = [
|
||||||
FIDO_COMMAND,
|
FIDO_COMMAND,
|
||||||
"-residentKeys",
|
"-residentKeys",
|
||||||
@@ -259,17 +154,14 @@ def on_passkeys_button_click():
|
|||||||
try:
|
try:
|
||||||
result = subprocess.run(command, capture_output=True, text=True)
|
result = subprocess.run(command, capture_output=True, text=True)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
# Parse the domains from the output
|
|
||||||
domains = []
|
domains = []
|
||||||
for line in result.stdout.splitlines():
|
for line in result.stdout.splitlines():
|
||||||
match = re.search(r"= (.+)$", line)
|
match = re.search(r"= (.+)$", line)
|
||||||
if match:
|
if match:
|
||||||
domains.append(match.group(1))
|
domains.append(match.group(1))
|
||||||
|
|
||||||
# Execute the command for each domain
|
|
||||||
cumulated_output = []
|
cumulated_output = []
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
|
|
||||||
domain_command = [
|
domain_command = [
|
||||||
FIDO_COMMAND,
|
FIDO_COMMAND,
|
||||||
"-residentKeys",
|
"-residentKeys",
|
||||||
@@ -293,7 +185,6 @@ def on_passkeys_button_click():
|
|||||||
domain_result.returncode, domain_command
|
domain_result.returncode, domain_command
|
||||||
)
|
)
|
||||||
|
|
||||||
# Show the cumulated output in a new window
|
|
||||||
cumulated_output_str = "\n\n".join(cumulated_output)
|
cumulated_output_str = "\n\n".join(cumulated_output)
|
||||||
show_output_in_new_window(cumulated_output_str, device_digit)
|
show_output_in_new_window(cumulated_output_str, device_digit)
|
||||||
else:
|
else:
|
||||||
@@ -303,9 +194,7 @@ def on_passkeys_button_click():
|
|||||||
"Error", f"Command execution failed: {e}\nOutput: {result.stderr}"
|
"Error", f"Command execution failed: {e}\nOutput: {result.stderr}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def set_pin():
|
def set_pin():
|
||||||
"""Set a new PIN on the device with full interactive control"""
|
|
||||||
global PIN
|
global PIN
|
||||||
selected_device = device_var.get()
|
selected_device = device_var.get()
|
||||||
match = re.search(r"\[(\d+)\]", selected_device)
|
match = re.search(r"\[(\d+)\]", selected_device)
|
||||||
@@ -314,32 +203,30 @@ def set_pin():
|
|||||||
|
|
||||||
device_digit = match.group(1)
|
device_digit = match.group(1)
|
||||||
|
|
||||||
# Ask for new PIN
|
while True:
|
||||||
while True:
|
|
||||||
new_pin = simpledialog.askstring(
|
new_pin = simpledialog.askstring(
|
||||||
"New PIN", "Enter your new PIN code:", show="*"
|
"New PIN", "Enter your new PIN code:", show="*"
|
||||||
)
|
)
|
||||||
if new_pin is None: # User cancelled
|
if new_pin is None:
|
||||||
|
PIN = None
|
||||||
return
|
return
|
||||||
|
|
||||||
new_pin_confirmed = simpledialog.askstring(
|
new_pin_confirmed = simpledialog.askstring(
|
||||||
"Confirm new PIN", "Confirm your new PIN code:", show="*"
|
"Confirm new PIN", "Enter your new PIN code:", show="*"
|
||||||
)
|
)
|
||||||
if new_pin_confirmed is None: # User cancelled
|
if new_pin_confirmed is None:
|
||||||
|
PIN = None
|
||||||
return
|
return
|
||||||
|
|
||||||
if new_pin == new_pin_confirmed:
|
if new_pin == new_pin_confirmed:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
messagebox.showerror("Error", "New PIN entries do not match!")
|
messagebox.showerror("Error", "New PIN entries do not match!")
|
||||||
continue
|
|
||||||
|
|
||||||
command = f"{FIDO_COMMAND} -setPIN -device {device_digit}"
|
command = f"{FIDO_COMMAND} -setPIN -device {device_digit}"
|
||||||
|
|
||||||
# Enter new PIN in interactive shell
|
|
||||||
try:
|
try:
|
||||||
child = pexpect.spawn(command, encoding="utf-8", timeout=20)
|
child = pexpect.spawn(command, encoding="utf-8", timeout=20)
|
||||||
|
|
||||||
child.expect("Enter new PIN")
|
child.expect("Enter new PIN")
|
||||||
child.sendline(new_pin)
|
child.sendline(new_pin)
|
||||||
child.expect("Enter the same PIN again")
|
child.expect("Enter the same PIN again")
|
||||||
@@ -350,35 +237,30 @@ def set_pin():
|
|||||||
child.expect(pexpect.EOF)
|
child.expect(pexpect.EOF)
|
||||||
output = child.before.strip()
|
output = child.before.strip()
|
||||||
|
|
||||||
change_pin_button.config(text="Change PIN", state=tk.ACTIVE, command=change_pin)
|
|
||||||
|
|
||||||
if "FIDO_ERR_PIN_POLICY_VIOLATION" in output:
|
if "FIDO_ERR_PIN_POLICY_VIOLATION" in output:
|
||||||
match = re.search(r"minpinlen:\s*(\d+)", output)
|
match = re.search(r"minpinlen:\s*(\d+)", output)
|
||||||
if match:
|
if match:
|
||||||
min_pin_len = match.group(1)
|
min_pin_len = match.group(1)
|
||||||
else:
|
|
||||||
min_pin_len = "unknown"
|
|
||||||
messagebox.showerror(
|
messagebox.showerror(
|
||||||
"PIN not accepted.",
|
"PIN not accepted.",
|
||||||
f"The provided PIN does not fulfill the requirements of your device.\n"
|
f"The provided PIN does not fulfill the requirements of your device.\n"
|
||||||
f"The PIN has to be at least {min_pin_len} long and must not be an easily guessable sequence, like e.g. 123456"
|
f"The PIN has to be at least {min_pin_len} long and must not be an easily guessable sequence, like e.g. 123456"
|
||||||
)
|
)
|
||||||
|
PIN = None
|
||||||
elif "error" in output.lower() or "FIDO_ERR" in output:
|
elif "error" in output.lower() or "FIDO_ERR" in output:
|
||||||
messagebox.showerror("PIN Change Failed", output)
|
messagebox.showerror("PIN Change Failed", output)
|
||||||
|
PIN = None
|
||||||
else:
|
else:
|
||||||
messagebox.showinfo("Success", "PIN successfully set!")
|
messagebox.showinfo("Success", "PIN successfully set!")
|
||||||
|
|
||||||
except pexpect.exceptions.TIMEOUT:
|
except pexpect.exceptions.TIMEOUT:
|
||||||
messagebox.showerror("Timeout", "The device did not respond in time.")
|
messagebox.showerror("Timeout", "The device did not respond in time.")
|
||||||
|
PIN = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("Error", str(e))
|
messagebox.showerror("Error", str(e))
|
||||||
|
PIN = None
|
||||||
|
|
||||||
def change_pin():
|
def change_pin():
|
||||||
"""Change PIN with interactive control and touch detection"""
|
|
||||||
global PIN
|
global PIN
|
||||||
|
|
||||||
if PIN is None:
|
if PIN is None:
|
||||||
get_pin()
|
get_pin()
|
||||||
|
|
||||||
@@ -388,52 +270,42 @@ def change_pin():
|
|||||||
return
|
return
|
||||||
|
|
||||||
device_digit = match.group(1)
|
device_digit = match.group(1)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
old_pin = PIN
|
||||||
|
|
||||||
new_pin = simpledialog.askstring(
|
new_pin = simpledialog.askstring(
|
||||||
"New PIN", "Enter your new PIN code:", show="*"
|
"New PIN", "Enter your new PIN code:", show="*"
|
||||||
)
|
)
|
||||||
if new_pin is None: # User cancelled
|
|
||||||
return
|
|
||||||
|
|
||||||
new_pin_confirmed = simpledialog.askstring(
|
new_pin_confirmed = simpledialog.askstring(
|
||||||
"Confirm new PIN", "Confirm your new PIN code:", show="*"
|
"Confirm new PIN", "Enter your new PIN code:", show="*"
|
||||||
)
|
)
|
||||||
if new_pin_confirmed is None: # User cancelled
|
|
||||||
return
|
|
||||||
|
|
||||||
if new_pin == new_pin_confirmed:
|
if new_pin == new_pin_confirmed:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
messagebox.showerror("Error", "New PIN entries do not match!")
|
messagebox.showerror("Error", "New PIN entries do not match!")
|
||||||
continue
|
|
||||||
|
|
||||||
command = f"{FIDO_COMMAND} -changePIN -device {device_digit}"
|
command = f"{FIDO_COMMAND} -changePIN -device {device_digit}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
child = pexpect.spawn(command, encoding="utf-8", timeout=20)
|
child = pexpect.spawn(command, encoding="utf-8", timeout=20)
|
||||||
|
|
||||||
# --- Detect touch prompt ---
|
|
||||||
i = child.expect([
|
i = child.expect([
|
||||||
"Touch",
|
"Touch",
|
||||||
"Tap",
|
"Tap",
|
||||||
"Waiting for user",
|
"Waiting for user",
|
||||||
"Enter current PIN", # sometimes no touch required
|
"Enter current PIN",
|
||||||
pexpect.EOF,
|
pexpect.EOF,
|
||||||
pexpect.TIMEOUT
|
pexpect.TIMEOUT
|
||||||
], timeout=5)
|
])
|
||||||
|
|
||||||
if i in [0, 1, 2]:
|
if i in [0, 1, 2]:
|
||||||
# Prompt the user in the GUI
|
|
||||||
messagebox.showinfo(
|
messagebox.showinfo(
|
||||||
"Touch Required",
|
"Touch Required",
|
||||||
"Please touch your FIDO security key to continue."
|
"Please touch your FIDO security key to continue."
|
||||||
)
|
)
|
||||||
# Now wait until the key is actually touched
|
child.expect("Enter current PIN")
|
||||||
child.expect("Enter current PIN", timeout=30)
|
|
||||||
|
|
||||||
# Now continue with PIN entry
|
child.sendline(old_pin)
|
||||||
child.sendline(PIN)
|
|
||||||
|
|
||||||
child.expect("Enter new PIN")
|
child.expect("Enter new PIN")
|
||||||
child.sendline(new_pin)
|
child.sendline(new_pin)
|
||||||
@@ -443,7 +315,7 @@ def change_pin():
|
|||||||
PIN = new_pin
|
PIN = new_pin
|
||||||
|
|
||||||
output = child.before.strip()
|
output = child.before.strip()
|
||||||
|
|
||||||
idx = child.expect(["FIDO_ERR_PIN_POLICY_VIOLATION", pexpect.EOF], timeout=1)
|
idx = child.expect(["FIDO_ERR_PIN_POLICY_VIOLATION", pexpect.EOF], timeout=1)
|
||||||
if idx == 0:
|
if idx == 0:
|
||||||
command = f"{FIDO_COMMAND} -info -device {device_digit}"
|
command = f"{FIDO_COMMAND} -info -device {device_digit}"
|
||||||
@@ -451,9 +323,6 @@ def change_pin():
|
|||||||
info.expect(pexpect.EOF)
|
info.expect(pexpect.EOF)
|
||||||
info_text = info.before
|
info_text = info.before
|
||||||
|
|
||||||
print("info_text:\n", info_text)
|
|
||||||
|
|
||||||
# extract minpinlen
|
|
||||||
match = re.search(r"minpinlen:\s*(\d+)", info_text)
|
match = re.search(r"minpinlen:\s*(\d+)", info_text)
|
||||||
if match:
|
if match:
|
||||||
min_pin_len = match.group(1)
|
min_pin_len = match.group(1)
|
||||||
@@ -466,10 +335,8 @@ def change_pin():
|
|||||||
f"The PIN must be at least {min_pin_len} digits long and "
|
f"The PIN must be at least {min_pin_len} digits long and "
|
||||||
f"must not be an easily guessable sequence (e.g. 123456)."
|
f"must not be an easily guessable sequence (e.g. 123456)."
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# If no violation detected, EOF happened normally
|
|
||||||
child.expect(pexpect.EOF)
|
child.expect(pexpect.EOF)
|
||||||
output = child.before.strip()
|
output = child.before.strip()
|
||||||
|
|
||||||
@@ -483,39 +350,29 @@ def change_pin():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("Error", str(e))
|
messagebox.showerror("Error", str(e))
|
||||||
|
|
||||||
|
|
||||||
def refresh_combobox():
|
def refresh_combobox():
|
||||||
"""Refresh device list and reset all states"""
|
device_combobox.set("")
|
||||||
global PIN
|
|
||||||
device_combobox.set("") # Clear the selected value
|
|
||||||
tree.delete(*tree.get_children())
|
tree.delete(*tree.get_children())
|
||||||
passkeys_button.config(state=tk.DISABLED)
|
passkeys_button.config(state=tk.DISABLED)
|
||||||
change_pin_button.config(state=tk.DISABLED)
|
pin_button.config(state=tk.DISABLED)
|
||||||
PIN = None # Reset PIN on refresh
|
|
||||||
device_list = get_device_list()
|
device_list = get_device_list()
|
||||||
if not device_list:
|
if not device_list:
|
||||||
print("No devices found.")
|
print("No devices found.")
|
||||||
device_combobox["values"] = device_list # Update the combobox values
|
device_combobox["values"] = device_list
|
||||||
|
|
||||||
|
|
||||||
# Function to show the output in a new window
|
|
||||||
def show_output_in_new_window(output, device_digit):
|
def show_output_in_new_window(output, device_digit):
|
||||||
# Create a new window
|
|
||||||
new_window = tk.Toplevel(root)
|
new_window = tk.Toplevel(root)
|
||||||
new_window.geometry("800x650")
|
new_window.geometry("800x650")
|
||||||
new_window.title("Resident Keys / Passkeys")
|
new_window.title("Resident Keys / Passkeys")
|
||||||
|
|
||||||
# Create a Treeview widget for displaying output
|
|
||||||
tree_new_window = ttk.Treeview(
|
tree_new_window = ttk.Treeview(
|
||||||
new_window, columns=("Domain", "Credential ID", "User"), show="headings"
|
new_window, columns=("Domain", "Credential ID", "User"), show="headings"
|
||||||
)
|
)
|
||||||
# Set column headings
|
|
||||||
tree_new_window.heading("Domain", text="Domain")
|
tree_new_window.heading("Domain", text="Domain")
|
||||||
tree_new_window.heading("Credential ID", text="Credential ID")
|
tree_new_window.heading("Credential ID", text="Credential ID")
|
||||||
tree_new_window.heading("User", text="User")
|
tree_new_window.heading("User", text="User")
|
||||||
tree_new_window.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)
|
tree_new_window.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)
|
||||||
|
|
||||||
# Add scrollbars to the Treeview
|
|
||||||
tree_scrollbar_y = ttk.Scrollbar(
|
tree_scrollbar_y = ttk.Scrollbar(
|
||||||
new_window, orient="vertical", command=tree_new_window.yview
|
new_window, orient="vertical", command=tree_new_window.yview
|
||||||
)
|
)
|
||||||
@@ -527,7 +384,6 @@ def show_output_in_new_window(output, device_digit):
|
|||||||
tree_scrollbar_x.pack(side="bottom", fill="x")
|
tree_scrollbar_x.pack(side="bottom", fill="x")
|
||||||
tree_new_window.configure(xscrollcommand=tree_scrollbar_x.set)
|
tree_new_window.configure(xscrollcommand=tree_scrollbar_x.set)
|
||||||
|
|
||||||
# Parse the output and insert into the Treeview
|
|
||||||
current_domain = ""
|
current_domain = ""
|
||||||
for line in output.splitlines():
|
for line in output.splitlines():
|
||||||
if line.startswith("Domain: "):
|
if line.startswith("Domain: "):
|
||||||
@@ -540,11 +396,10 @@ def show_output_in_new_window(output, device_digit):
|
|||||||
"", tk.END, values=(current_domain, credential_id, user)
|
"", tk.END, values=(current_domain, credential_id, user)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Function to handle delete passkey button click
|
|
||||||
def show_selected_value():
|
def show_selected_value():
|
||||||
selected_item = tree_new_window.selection()
|
selected_item = tree_new_window.selection()
|
||||||
if selected_item:
|
if selected_item:
|
||||||
value = tree_new_window.item(selected_item, "values")[1] # Get Credential ID
|
value = tree_new_window.item(selected_item, "values")[1]
|
||||||
new_window.destroy()
|
new_window.destroy()
|
||||||
command = [
|
command = [
|
||||||
FIDO_COMMAND,
|
FIDO_COMMAND,
|
||||||
@@ -559,27 +414,21 @@ def show_output_in_new_window(output, device_digit):
|
|||||||
elif sys.platform.startswith("linux"):
|
elif sys.platform.startswith("linux"):
|
||||||
subprocess.Popen([TERM] + TERM_FLAG + command)
|
subprocess.Popen([TERM] + TERM_FLAG + command)
|
||||||
|
|
||||||
# Create the "Delete Passkey" button
|
|
||||||
show_value_button = tk.Button(
|
show_value_button = tk.Button(
|
||||||
new_window, text="Delete Passkey", command=show_selected_value
|
new_window, text="Delete Passkey", command=show_selected_value
|
||||||
)
|
)
|
||||||
show_value_button.pack(pady=10)
|
show_value_button.pack(pady=10)
|
||||||
|
|
||||||
|
|
||||||
def show_about_message():
|
def show_about_message():
|
||||||
messagebox.showinfo(
|
messagebox.showinfo(
|
||||||
"About",
|
"About",
|
||||||
"The FIDO2.1 Security Key Management Tool is a utility designed to manage and interact with FIDO2.1 security keys.\r\n"
|
"The FIDO2.1 Security Key Management Tool is a utility designed to manage and interact with FIDO2.1 security keys.\r\nIt provides functionalities to view information, manage relying parties, and perform various operations on connected FIDO2.1 devices.\r\n\r\n(c)TOKEN2 Sarl\r\nVersoix, Switzerland",
|
||||||
"It provides functionalities to view information, manage passkeys, and perform various operations on connected FIDO2.1 devices.\r\n\r\n"
|
|
||||||
"(c)TOKEN2 Sarl\r\nVersoix, Switzerland\r\n\r\n"
|
|
||||||
"Version 0.2 - Merged Edition",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Create the main application window
|
# Create the main application window
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
root.geometry("700x600") # Width x Height
|
root.geometry("700x600")
|
||||||
root.title("FIDO2.1 Manager - Python version 0.2 - (c) Token2")
|
root.title("FIDO2.1 Manager - Python version 0.1 - (c) Token2")
|
||||||
|
|
||||||
# Create a frame for the first three elements
|
# Create a frame for the first three elements
|
||||||
top_frame = ttk.Frame(root)
|
top_frame = ttk.Frame(root)
|
||||||
@@ -604,7 +453,6 @@ device_combobox.bind("<<ComboboxSelected>>", on_device_selected)
|
|||||||
refresh_button = tk.Button(top_frame, text="Refresh", command=refresh_combobox)
|
refresh_button = tk.Button(top_frame, text="Refresh", command=refresh_combobox)
|
||||||
refresh_button.pack(side=tk.LEFT, padx=10, pady=10)
|
refresh_button.pack(side=tk.LEFT, padx=10, pady=10)
|
||||||
|
|
||||||
|
|
||||||
# Create a Treeview widget for displaying output with scrollbars
|
# Create a Treeview widget for displaying output with scrollbars
|
||||||
tree_frame = ttk.Frame(root)
|
tree_frame = ttk.Frame(root)
|
||||||
tree_frame.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)
|
tree_frame.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)
|
||||||
@@ -621,7 +469,6 @@ tree_scrollbar_y.config(command=tree.yview)
|
|||||||
tree_scrollbar_x.config(command=tree.xview)
|
tree_scrollbar_x.config(command=tree.xview)
|
||||||
tree_scrollbar_y.pack(side="right", fill="y")
|
tree_scrollbar_y.pack(side="right", fill="y")
|
||||||
tree_scrollbar_x.pack(side="bottom", fill="x")
|
tree_scrollbar_x.pack(side="bottom", fill="x")
|
||||||
# Set column headings
|
|
||||||
tree.heading("Key", text="Key")
|
tree.heading("Key", text="Key")
|
||||||
tree.heading("Value", text="Value")
|
tree.heading("Value", text="Value")
|
||||||
tree.pack(expand=True, fill=tk.BOTH)
|
tree.pack(expand=True, fill=tk.BOTH)
|
||||||
@@ -632,16 +479,15 @@ passkeys_button = ttk.Button(
|
|||||||
)
|
)
|
||||||
passkeys_button.pack(side=tk.LEFT, padx=5, pady=10)
|
passkeys_button.pack(side=tk.LEFT, padx=5, pady=10)
|
||||||
|
|
||||||
# Create the "Change PIN" button
|
# Create the "Set PIN" button
|
||||||
change_pin_button = ttk.Button(
|
pin_button = ttk.Button(
|
||||||
root, text="Change PIN", state=tk.DISABLED, command=change_pin
|
root, text="Set PIN", state=tk.DISABLED, command=set_pin
|
||||||
)
|
)
|
||||||
change_pin_button.pack(side=tk.LEFT, padx=5, pady=10)
|
pin_button.pack(side=tk.LEFT, padx=5, pady=10)
|
||||||
|
|
||||||
# Create the "About" button
|
# Create the "About" button
|
||||||
about_button = ttk.Button(root, text="About", command=show_about_message)
|
about_button = ttk.Button(root, text="About", command=show_about_message)
|
||||||
about_button.pack(side=tk.RIGHT, padx=5, pady=10)
|
about_button.pack(side=tk.RIGHT, padx=5, pady=10)
|
||||||
|
|
||||||
|
|
||||||
# Run the Tkinter main loop
|
# Run the Tkinter main loop
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|||||||
Reference in New Issue
Block a user