From 06acdbd77714da3e89ff338a6dd27b517035c5a6 Mon Sep 17 00:00:00 2001 From: deusbalatro Date: Mon, 9 Jun 2025 10:58:08 +0300 Subject: [PATCH] refactor(cli): add CLI utility entry point and fix rsa_private_key typo - Correct typo: rsa -> rsa_private_key - Refactor cli.py so functions can be used by both interfaces - Introduce wikideck_cli.py as a CLI utility entry point - Public and private keys can now be saved separately --- cli.py | 124 +++++++++++++++++++++++++++----------- wikideck_cli.py | 155 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+), 34 deletions(-) create mode 100755 wikideck_cli.py diff --git a/cli.py b/cli.py index 9b4821d..e33ad77 100644 --- a/cli.py +++ b/cli.py @@ -17,8 +17,8 @@ WIKIDECK_URL = os.getenv('WIKIDECK_URL', 'http://localhost:8080/'); rsa_private_key: Optional[rsa.RSAPrivateKey] = None; msg_array: list[str] = [""]; -private_key_file: str = "private_key.pem"; - +private_key_file: str = "privatekey.pem"; +public_key_file: str = "publickey.pem"; def main(): @@ -30,11 +30,12 @@ def main(): print("\n--- Wikideck CLI ---"); print("1. Create RSA Keypair"); - print("2. Save RSA Keypair"); + print("2. Save Private Key"); print("3. Load RSA Keypair"); - print("4. Mine a Block"); - print("5. Generate a Transaction"); - print("6. View Deck"); + print("4. Save Public Key") + print("5. Mine a Block"); + print("6. Generate a Transaction"); + print("7. View Deck"); print("0. Exit"); print("\n-------------------"); @@ -49,43 +50,62 @@ def main(): if choice == 0: break; elif choice == 1: - opt_generate_private_key(65537, 2048); + opt_generate_keypair(65537, 2048); elif choice == 2: - opt_save_private_key(); + opt_save_private_key(rsa_private_key); elif choice == 3: opt_load_private_key(); elif choice == 4: - opt_mine_block(); + opt_save_public_key(rsa_private_key); elif choice == 5: - opt_generate_transaction(); + opt_mine_block(); elif choice == 6: + opt_generate_transaction(); + elif choice == 7: opt_view_deck(); else: msg_array[0] = "Invalid Input. Please Choose a Number Between 0-6."; clear_console(); -def opt_generate_private_key(exponent, size): +def opt_generate_keypair(exponent=65537, size=2048): global rsa_private_key; rsa_private_key = rsa.generate_private_key( public_exponent = exponent, key_size = size, ) msg_array[0] = "RSA Keypair Generated"; + return rsa_private_key; -def opt_save_private_key(): - if rsa_private_key is None: - msg_array[0] = "No RSA Keypair to Save. Please Generate one first."; +def opt_save_private_key(rsa, filename=private_key_file): + if rsa is None: + msg_array[0] = "No RSA Keypair to extract private key. Please generate one first."; return None; else: - with open(private_key_file, "wb") as file: - pem = rsa_private_key.private_bytes( + with open(filename, "wb") as file: + pem = rsa.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() ) file.write(pem); - msg_array[0] = f"RSA Private Key Saved to {private_key_file}"; + msg_array[0] = f"RSA Private Key Saved to {filename}"; + +def opt_save_public_key(rsa, filename=public_key_file): + if rsa is None: + msg_array[0] = "No RSA Keypair to extract public key. Please generate one first."; + return None; + + public_key = rsa.public_key(); + + public_pem = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with open(filename, "wb") as file: + file.write(public_pem); + msg_array[0] = f"RSA Public Key Saved to {filename}"; def opt_load_private_key(): global rsa_private_key; @@ -100,10 +120,28 @@ def opt_load_private_key(): ) msg_array[0] = f"RSA Private Key Loaded From {private_key_file}"; -def opt_mine_block(): +def get_private_key_from_file(path): + if not os.path.exists(path): + msg_array[0] = f"No key file found ({path} missing)"; + return None; + + with open(path, "rb") as file: + key = serialization.load_pem_private_key( + file.read(), + password=None, + ); + msg_array[0] = f"RSA Private Key Gotten From {path}"; #inaccessible + + return key; + +def opt_mine_block(path = None): global rsa_private_key; + if (path): + rsa_private_key = get_private_key_from_file(path); + if not (rsa_private_key): + return None; if rsa_private_key is None: msg_array[0] = "You must load or generate an RSA keypair first."; @@ -127,29 +165,45 @@ def opt_mine_block(): except Exception as e: msg_array[0] = f"Error during mining: {str(e)}"; -def opt_generate_transaction(): +def opt_generate_transaction(path = None, recv = None): global rsa_private_key; + if (path): + rsa_private_key = get_private_key_from_file(path); + if not (rsa_private_key): + return None; + + if rsa_private_key is None: msg_array[0] = "You must load or generate an RSA keypair first."; return; - print("\nPaste the recipient's RSA Public Key (PEM format). End with an Empty Line (Press enter twice): "); - lines = []; + if not (recv): + print("\nPaste the recipient's RSA Public Key (PEM format). End with an Empty Line (Press enter twice): "); + lines = []; + while True: + line = input(); + if not line.strip(): + break; + lines.append(line); - while True: - line = input(); - if not line.strip(): - break; - lines.append(line); + receiver_pem = "\n".join(lines); + + try: + receiver_key = serialization.load_pem_public_key(receiver_pem.encode("utf-8")); + except Exception: + msg_array[0] = "Invalid public key format"; + return; - receiver_pem = "\n".join(lines); - - try: - receiver_key = serialization.load_pem_public_key(receiver_pem.encode("utf-8")); - except Exception: - msg_array[0] = "Invalid public key format"; - return; + else: + if not os.path.isfile(recv): + msg_array[0] = f"Reciever public key file not found {recv}"; + return None; + with open(recv, "rb") as recv_file: + receiver_key = serialization.load_pem_public_key(recv_file.read()); + + + try: @@ -193,6 +247,7 @@ def opt_generate_transaction(): except Exception as e: msg_array[0] = f"Error creating transaction: {str(e)}"; + def opt_view_deck(): global rsa_private_key; @@ -201,7 +256,7 @@ def opt_view_deck(): return; try: - cards = fetch_cards_json(rsa); + cards = fetch_cards_json(rsa_private_key); if len(cards) <= 0: @@ -214,6 +269,7 @@ def opt_view_deck(): except Exception as e: msg_array[0] = f"Error fetching cards: {str(e)}"; + def fetch_cards_json(rsa): pem = rsa.public_key().public_bytes( encoding=serialization.Encoding.PEM, diff --git a/wikideck_cli.py b/wikideck_cli.py new file mode 100755 index 0000000..ca7aa69 --- /dev/null +++ b/wikideck_cli.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 + +import argparse +import glob +import os +from cli import opt_generate_keypair +from cli import opt_save_private_key +from cli import opt_load_private_key +from cli import opt_save_public_key +from cli import opt_mine_block +from cli import opt_generate_transaction +from cli import opt_view_deck +from cli import msg_array +from cli import get_private_key_from_file +from cli import fetch_cards_json + +DEFAULT_FILENAME: str = "privatekey"; + +def resolve_path(path: str | None) -> str | None: + if not path or path.strip() == "": + return None; + + path = path.strip(); + + if not path.endswith(".pem"): + path += ".pem"; + + if os.path.exists(path) and os.path.isfile(path): + return path; + + return None; + + +def resolve_path_for_key_creation(filename:str | None) -> str: + default_name = "privatekey.pem"; + + if not filename or filename.strip() == "": + return default_name; + + filename = filename.strip(); + + if os.path.isdir(filename): + return os.path.join(filename, default_name); + + if not filename.endswith(".pem"): + filename += ".pem"; + + dir_path = os.path.dirname(filename); + if dir_path and not os.path.exists(dir_path): + os.makedirs(dir_path); + + return filename; + +def get_first_pem_file(): + pem_files = glob.glob("*.pem"); + + if pem_files: + first_pem = min(pem_files, key=os.path.getmtime); + return first_pem; + else: + return None; + +def handle_createkey(args): + rsa_key = opt_generate_keypair(); + filepath = resolve_path_for_key_creation(args.output); + opt_save_private_key(rsa_key, filepath); + print(msg_array[0]); + public_path = f"pub_{filepath}" + opt_save_public_key(rsa_key, public_path); + +def handle_mine(args): + file = resolve_path(args.use); + if not file: + file = get_first_pem_file(); + if not file: + msg_array[0] = "No valid private key found"; + return; + + opt_mine_block(file); + + +def handle_tx(args): + + file = resolve_path(args.use); + if not file: + file = get_first_pem_file(); + if not file: + msg_array[0] = "No valid private key found"; + return; + + recv = resolve_path(args.receiver); + if args.receiver and not recv: + msg_array[0] = "No valid public key found (receiver)"; + return; + + opt_generate_transaction(file, recv); + + +def handle_deck(args): + file = resolve_path(args.use); + if not file: + file = get_first_pem_file(); + if not file: + msg_array[0] = "No valid private key found"; + return; + + rsa = get_private_key_from_file(file); + if rsa is None: + return; + + cards = fetch_cards_json(rsa); + + if len(cards) < 1: + msg_array[0] = "You have no cards to view"; + return; + + for i, card in enumerate(cards): + print(f"{i+1}. Card ID: {card['cardId']}, Page ID: {card['pageId']}"); + + +def build_parser(): + parser = argparse.ArgumentParser(description="Wikideck CLI"); + subparsers = parser.add_subparsers(dest="command", required=True); + + #createkey + p_create = subparsers.add_parser("createkey",aliases=["ck"], help="Create an RSA keypair and save it to the current directory"); + p_create.add_argument("-O", "--output", help="Optional custom filename (.pem)"); + p_create.set_defaults(func=handle_createkey); + + #mine + p_mine = subparsers.add_parser("mine", help="Mine a block, using the first ./*.pem by default."); + p_mine.add_argument("-u", "--use", help="Use specified keypair (path to .pem file)"); + p_mine.set_defaults(func=handle_mine); + + #transaction + p_transaction = subparsers.add_parser("transaction", aliases=["tx"], help="Generate a transaction, using the first ./*.pem by default."); + p_transaction.add_argument("-u", "--use", help="Use specified keypair (path to .pem file)"); + p_transaction.add_argument("-to", "--receiver", help="Send to specified public key (path to .pem file)"); + p_transaction.set_defaults(func=handle_tx); + + #deck + p_deck = subparsers.add_parser("deck", help="Show all cards tied to the private key, using the first ./*.pem by default."); + p_deck.add_argument("-u", "--use", help="Use specified keypair (path to .pem file)"); + p_deck.set_defaults(func=handle_deck); + + return parser; + +def main(): + parser = build_parser(); + args = parser.parse_args(); + args.func(args); + print(msg_array[0]); + +if __name__ == "__main__": + main();