From 0558f3d0d4a074d0332d8548a489e964f07abd3a Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Mon, 2 Mar 2026 13:49:45 +0700 Subject: [PATCH 01/11] Modified verify PIN --- cryptnox_cli/command/helper/security.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cryptnox_cli/command/helper/security.py b/cryptnox_cli/command/helper/security.py index b0d65b8..fab5ba8 100644 --- a/cryptnox_cli/command/helper/security.py +++ b/cryptnox_cli/command/helper/security.py @@ -98,7 +98,17 @@ def check_pin_code(card, text: str = "Cryptnox PIN code: ") -> str: print("The card is in easy mode, just press ENTER. The PIN will be from easy mode " "regardless of what you type.") - pin_code = get_pin_code(card, text, [""] if easy_mode else []) + try: + retries = card.verify_pin(None) + if retries is not None: + try_str = "attempt" if retries == 1 else "attempts" + prompt_text = f"Cryptnox PIN code ({retries} {try_str} remaining): " + else: + prompt_text = text + except Exception: + prompt_text = text + + pin_code = get_pin_code(card, prompt_text, [""] if easy_mode else []) if easy_mode: pin_code = EASY_MODE_PIN From 9f4f5cbaeec2282685a48d400308bec087911af0 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Wed, 4 Mar 2026 16:02:01 +0700 Subject: [PATCH 02/11] Modified verify pin --- cryptnox_cli/command/change_puk.py | 41 +++++++++---------- cryptnox_cli/command/command.py | 4 ++ cryptnox_cli/command/helper/security.py | 52 +++++++++++++++---------- 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/cryptnox_cli/command/change_puk.py b/cryptnox_cli/command/change_puk.py index c3e3d8d..02d3692 100644 --- a/cryptnox_cli/command/change_puk.py +++ b/cryptnox_cli/command/change_puk.py @@ -30,26 +30,27 @@ def _execute(self, card: cryptnox_sdk_py.Card) -> int: return -1 easy_mode = security.is_easy_mode(card.info) - if easy_mode: - print("The card is in easy mode, just press ENTER. The PUK will be from EASY mode " - "regardless of what you type.") - puk_code = security.get_puk_code(card, f" Enter the PUK ({card.puk_rule}): ", - [""] if easy_mode else []) - - if easy_mode: - puk_code = security.easy_mode_puk(card) - - card.change_puk(puk_code, puk_code) - - if security.is_easy_mode(card.info): - print(f"Card is in {security.EASY_MODE_TEXT}. Setting same PUK code") - new_puk_code = security.easy_mode_puk(card) - else: - new_puk_code = security.get_puk_code(card, f"Set new PUK code ({card.puk_rule}): ") - - card.change_puk(puk_code, new_puk_code) - - print("PUK changed successfully") + while True: + if easy_mode: + puk_code = security.easy_mode_puk(card) + print(f"Card is in {security.EASY_MODE_TEXT}. Setting same PUK code automatically.") + new_puk_code = puk_code + else: + puk_code = security.get_puk_code(card) + new_puk_code = security.get_puk_code(card, f"Set new PUK code ({card.puk_rule}): ") + + try: + card.change_puk(puk_code, new_puk_code) + break + except cryptnox_sdk_py.exceptions.PukException: + if easy_mode: + print(f"{security.EASY_MODE_TEXT} PUK doesn't work. Try other PUK code.\n") + easy_mode = False + else: + print("Wrong PUK code. Try again.") + + if not easy_mode: + print("PUK changed successfully") return 0 diff --git a/cryptnox_cli/command/command.py b/cryptnox_cli/command/command.py index 1e531ea..22cdaa4 100644 --- a/cryptnox_cli/command/command.py +++ b/cryptnox_cli/command/command.py @@ -89,6 +89,10 @@ def run_execute(self, card) -> int: except cryptnox_sdk_py.exceptions.GenericException as error: print(f"Generic exception with status code: 0x{error.status.hex().upper()}") return -2 + except (cryptnox_sdk_py.exceptions.PinException, + cryptnox_sdk_py.exceptions.PinAuthenticationException, + cryptnox_sdk_py.exceptions.PinBlockedException): + result = -1 except (cryptnox_sdk_py.exceptions.CryptnoxException, NotImplementedError) as error: print(error) result = -1 diff --git a/cryptnox_cli/command/helper/security.py b/cryptnox_cli/command/helper/security.py index fab5ba8..ebb0632 100644 --- a/cryptnox_cli/command/helper/security.py +++ b/cryptnox_cli/command/helper/security.py @@ -95,23 +95,29 @@ def check_pin_code(card, text: str = "Cryptnox PIN code: ") -> str: while not authorized: if easy_mode: - print("The card is in easy mode, just press ENTER. The PIN will be from easy mode " - "regardless of what you type.") - - try: - retries = card.verify_pin(None) - if retries is not None: - try_str = "attempt" if retries == 1 else "attempts" - prompt_text = f"Cryptnox PIN code ({retries} {try_str} remaining): " - else: + print(f"Card is in {EASY_MODE_TEXT}. Using easy mode PIN automatically.") + pin_code = EASY_MODE_PIN + else: + try: + retries = card.verify_pin(None) + if retries is not None: + if retries == 0: + raise cryptnox_sdk_py.exceptions.PinBlockedException( + "PIN is locked. Use your PUK to unlock the card." + ) + try_str = "attempt" if retries == 1 else "attempts" + prompt_text = f"Cryptnox PIN code ({retries} {try_str} remaining): " + else: + prompt_text = text + except cryptnox_sdk_py.exceptions.PinBlockedException: + raise + except cryptnox_sdk_py.exceptions.PinException: + print("Card requires a power cycle. Please remove and re-tap the card, then try again.") + raise + except Exception: prompt_text = text - except Exception: - prompt_text = text - - pin_code = get_pin_code(card, prompt_text, [""] if easy_mode else []) - if easy_mode: - pin_code = EASY_MODE_PIN + pin_code = get_pin_code(card, prompt_text) try: authorized = _check_pin_code(card, pin_code, not easy_mode) @@ -121,6 +127,10 @@ def check_pin_code(card, text: str = "Cryptnox PIN code: ") -> str: easy_mode = False else: raise + except (cryptnox_sdk_py.exceptions.PinAuthenticationException, + cryptnox_sdk_py.exceptions.SoftLock): + print("Card requires a power cycle. Please remove and re-tap the card, then try again.") + raise return pin_code @@ -156,12 +166,12 @@ def process_command_with_puk( while True: if easy_mode: - print(f"The card is in {EASY_MODE_TEXT}, just press ENTER. " - f"The PUK will be from {EASY_MODE_TEXT.upper()} regardless of what you type.") + print(f"Card is in {EASY_MODE_TEXT}. Using easy mode PUK automatically.") - puk_code = get_puk_code(card, allowed_values=[""] if is_easy_mode else []) if easy_mode: puk_code = easy_mode_puk(card) + else: + puk_code = get_puk_code(card) try: result = function(*args, **kwargs, puk=puk_code) @@ -188,10 +198,12 @@ def _check_pin_code(card, pin_code, handle_exception: bool = True) -> bool: if not handle_exception: raise error number_of_retries = error.number_of_retries + print("Wrong PIN code.") if number_of_retries == 0: + print("PIN is locked. Use your PUK to unlock the card.") raise - try_str = "tries" if number_of_retries > 1 else "try" - print(f"Wrong pin code. You have {number_of_retries} {try_str} before the card is locked.") + try_str = "attempt" if number_of_retries == 1 else "attempts" + print(f"{number_of_retries} {try_str} remaining before the card is locked.") return False From 28a241e952e8824b612e3647cc8481b6d9df455c Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Fri, 6 Mar 2026 18:51:52 +0700 Subject: [PATCH 03/11] Modified verify pin --- cryptnox_cli/command/change_puk.py | 7 +++++++ cryptnox_cli/command/command.py | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cryptnox_cli/command/change_puk.py b/cryptnox_cli/command/change_puk.py index 02d3692..189eb38 100644 --- a/cryptnox_cli/command/change_puk.py +++ b/cryptnox_cli/command/change_puk.py @@ -29,6 +29,13 @@ def _execute(self, card: cryptnox_sdk_py.Card) -> int: return -1 + try: + if card.verify_pin(None) == 0: + ui.print_warning("Cannot change PUK while PIN is locked. Use 'unlock_pin' to unlock the card first.") + return -1 + except Exception: + pass + easy_mode = security.is_easy_mode(card.info) while True: diff --git a/cryptnox_cli/command/command.py b/cryptnox_cli/command/command.py index 22cdaa4..8313289 100644 --- a/cryptnox_cli/command/command.py +++ b/cryptnox_cli/command/command.py @@ -90,8 +90,10 @@ def run_execute(self, card) -> int: print(f"Generic exception with status code: 0x{error.status.hex().upper()}") return -2 except (cryptnox_sdk_py.exceptions.PinException, - cryptnox_sdk_py.exceptions.PinAuthenticationException, - cryptnox_sdk_py.exceptions.PinBlockedException): + cryptnox_sdk_py.exceptions.PinAuthenticationException): + result = -1 + except cryptnox_sdk_py.exceptions.PinBlockedException as error: + print(error) result = -1 except (cryptnox_sdk_py.exceptions.CryptnoxException, NotImplementedError) as error: print(error) From e92e0f3c78fcb51fbfe5e20a304dd74652997cdf Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Fri, 6 Mar 2026 19:20:01 +0700 Subject: [PATCH 04/11] Warn user when PIN is locked before changing PUK Co-Authored-By: Claude Sonnet 4.6 --- cryptnox_cli/command/change_puk.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cryptnox_cli/command/change_puk.py b/cryptnox_cli/command/change_puk.py index 189eb38..fb757c0 100644 --- a/cryptnox_cli/command/change_puk.py +++ b/cryptnox_cli/command/change_puk.py @@ -31,8 +31,7 @@ def _execute(self, card: cryptnox_sdk_py.Card) -> int: try: if card.verify_pin(None) == 0: - ui.print_warning("Cannot change PUK while PIN is locked. Use 'unlock_pin' to unlock the card first.") - return -1 + ui.print_warning("PIN is locked. After changing PUK, use 'unlock_pin' to unlock the card.") except Exception: pass From f021889e3610547817416c78c9f6cad1ad40d522 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Fri, 6 Mar 2026 19:27:41 +0700 Subject: [PATCH 05/11] Block change_puk when PIN is locked (FINDING-009) Permanently bricking the card by calling change_puk in LOCKED state. Co-Authored-By: Claude Sonnet 4.6 --- cryptnox_cli/command/change_puk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cryptnox_cli/command/change_puk.py b/cryptnox_cli/command/change_puk.py index fb757c0..189eb38 100644 --- a/cryptnox_cli/command/change_puk.py +++ b/cryptnox_cli/command/change_puk.py @@ -31,7 +31,8 @@ def _execute(self, card: cryptnox_sdk_py.Card) -> int: try: if card.verify_pin(None) == 0: - ui.print_warning("PIN is locked. After changing PUK, use 'unlock_pin' to unlock the card.") + ui.print_warning("Cannot change PUK while PIN is locked. Use 'unlock_pin' to unlock the card first.") + return -1 except Exception: pass From 1d7bc64edae33b022b546d167d1497edfdf6c255 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Fri, 6 Mar 2026 19:44:09 +0700 Subject: [PATCH 06/11] Warn instead of block when changing PUK with PIN locked Co-Authored-By: Claude Sonnet 4.6 --- cryptnox_cli/command/change_puk.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cryptnox_cli/command/change_puk.py b/cryptnox_cli/command/change_puk.py index 189eb38..fb757c0 100644 --- a/cryptnox_cli/command/change_puk.py +++ b/cryptnox_cli/command/change_puk.py @@ -31,8 +31,7 @@ def _execute(self, card: cryptnox_sdk_py.Card) -> int: try: if card.verify_pin(None) == 0: - ui.print_warning("Cannot change PUK while PIN is locked. Use 'unlock_pin' to unlock the card first.") - return -1 + ui.print_warning("PIN is locked. After changing PUK, use 'unlock_pin' to unlock the card.") except Exception: pass From d390e490cc7a681976793a9faf5e9e0b3f6a728e Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Fri, 6 Mar 2026 19:57:16 +0700 Subject: [PATCH 07/11] Modified verify pin --- cryptnox_cli/command/change_puk.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cryptnox_cli/command/change_puk.py b/cryptnox_cli/command/change_puk.py index fb757c0..02d3692 100644 --- a/cryptnox_cli/command/change_puk.py +++ b/cryptnox_cli/command/change_puk.py @@ -29,12 +29,6 @@ def _execute(self, card: cryptnox_sdk_py.Card) -> int: return -1 - try: - if card.verify_pin(None) == 0: - ui.print_warning("PIN is locked. After changing PUK, use 'unlock_pin' to unlock the card.") - except Exception: - pass - easy_mode = security.is_easy_mode(card.info) while True: From 5523f2cb309299b72bb3a6cf01588c7dede07a78 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Fri, 6 Mar 2026 20:08:28 +0700 Subject: [PATCH 08/11] Add CodeQL workflow for security scanning Required by branch protection ruleset to allow PR merge to main. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/codeql.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..bae9da3 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,34 @@ +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '0 0 * * 1' + +permissions: + contents: read + security-events: write + actions: read + +jobs: + analyze: + name: Analyze (python) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: python + queries: security-and-quality + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: CodeQL From 1a2d0ad716094e321c59a8339815580155b54349 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Fri, 6 Mar 2026 20:12:38 +0700 Subject: [PATCH 09/11] Remove path filter from osv-scanner workflow Required to run on all PRs so branch protection code scanning check is satisfied even when requirements files are unchanged. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/osv-scanner.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index bbca8cf..cab0ec8 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -9,9 +9,6 @@ on: - '.github/workflows/osv-scanner.yml' pull_request: branches: [main] - paths: - - 'requirements.txt' - - 'dev-requirements.txt' schedule: # Run weekly on Monday at 00:00 UTC to catch new CVEs - cron: '0 0 * * 1' From 6b516644e2d2afa20a8f0f187bf96f3e320e0304 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Wed, 18 Mar 2026 21:00:08 +0700 Subject: [PATCH 10/11] Modified PinBlockedException --- cryptnox_cli/command/helper/security.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cryptnox_cli/command/helper/security.py b/cryptnox_cli/command/helper/security.py index ebb0632..861bba5 100644 --- a/cryptnox_cli/command/helper/security.py +++ b/cryptnox_cli/command/helper/security.py @@ -194,6 +194,9 @@ def process_command_with_puk( def _check_pin_code(card, pin_code, handle_exception: bool = True) -> bool: try: card.verify_pin(str(pin_code)) + except cryptnox_sdk_py.exceptions.PinBlockedException: + print("PIN is locked. Use the unlock_pin command to unlock it before attempting this operation.") + raise except cryptnox_sdk_py.exceptions.PinException as error: if not handle_exception: raise error From 82dbcc92a15bf1af67e89eb192cfcce0dee62212 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Thu, 19 Mar 2026 22:52:15 +0700 Subject: [PATCH 11/11] Resolved reviews --- cryptnox_cli/command/change_puk.py | 17 ++++++----------- cryptnox_cli/command/helper/security.py | 23 ++++++++--------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/cryptnox_cli/command/change_puk.py b/cryptnox_cli/command/change_puk.py index 02d3692..13e464c 100644 --- a/cryptnox_cli/command/change_puk.py +++ b/cryptnox_cli/command/change_puk.py @@ -33,22 +33,17 @@ def _execute(self, card: cryptnox_sdk_py.Card) -> int: while True: if easy_mode: - puk_code = security.easy_mode_puk(card) print(f"Card is in {security.EASY_MODE_TEXT}. Setting same PUK code automatically.") - new_puk_code = puk_code + break else: puk_code = security.get_puk_code(card) new_puk_code = security.get_puk_code(card, f"Set new PUK code ({card.puk_rule}): ") - try: - card.change_puk(puk_code, new_puk_code) - break - except cryptnox_sdk_py.exceptions.PukException: - if easy_mode: - print(f"{security.EASY_MODE_TEXT} PUK doesn't work. Try other PUK code.\n") - easy_mode = False - else: - print("Wrong PUK code. Try again.") + try: + card.change_puk(puk_code, new_puk_code) + break + except cryptnox_sdk_py.exceptions.PukException: + print("Invalid PUK. Try another PUK code.") if not easy_mode: print("PUK changed successfully") diff --git a/cryptnox_cli/command/helper/security.py b/cryptnox_cli/command/helper/security.py index 861bba5..d8cf54b 100644 --- a/cryptnox_cli/command/helper/security.py +++ b/cryptnox_cli/command/helper/security.py @@ -97,13 +97,14 @@ def check_pin_code(card, text: str = "Cryptnox PIN code: ") -> str: if easy_mode: print(f"Card is in {EASY_MODE_TEXT}. Using easy mode PIN automatically.") pin_code = EASY_MODE_PIN + authorized = True else: try: retries = card.verify_pin(None) if retries is not None: if retries == 0: raise cryptnox_sdk_py.exceptions.PinBlockedException( - "PIN is locked. Use your PUK to unlock the card." + "PIN is locked. Use the unlock_pin command to unlock it." ) try_str = "attempt" if retries == 1 else "attempts" prompt_text = f"Cryptnox PIN code ({retries} {try_str} remaining): " @@ -119,18 +120,12 @@ def check_pin_code(card, text: str = "Cryptnox PIN code: ") -> str: pin_code = get_pin_code(card, prompt_text) - try: - authorized = _check_pin_code(card, pin_code, not easy_mode) - except cryptnox_sdk_py.exceptions.PinException: - if easy_mode: - print(f"{EASY_MODE_TEXT} PIN doesn't work. Try other PIN code.\n") - easy_mode = False - else: + try: + authorized = _check_pin_code(card, pin_code) + except (cryptnox_sdk_py.exceptions.PinAuthenticationException, + cryptnox_sdk_py.exceptions.SoftLock): + print("Card requires a power cycle. Please remove and re-tap the card, then try again.") raise - except (cryptnox_sdk_py.exceptions.PinAuthenticationException, - cryptnox_sdk_py.exceptions.SoftLock): - print("Card requires a power cycle. Please remove and re-tap the card, then try again.") - raise return pin_code @@ -167,8 +162,6 @@ def process_command_with_puk( while True: if easy_mode: print(f"Card is in {EASY_MODE_TEXT}. Using easy mode PUK automatically.") - - if easy_mode: puk_code = easy_mode_puk(card) else: puk_code = get_puk_code(card) @@ -203,7 +196,7 @@ def _check_pin_code(card, pin_code, handle_exception: bool = True) -> bool: number_of_retries = error.number_of_retries print("Wrong PIN code.") if number_of_retries == 0: - print("PIN is locked. Use your PUK to unlock the card.") + print("PIN is locked. Use the unlock_pin command to unlock it.") raise try_str = "attempt" if number_of_retries == 1 else "attempts" print(f"{number_of_retries} {try_str} remaining before the card is locked.")