#!/usr/bin/env python3 """FlaskPaste command-line client.""" import argparse import base64 import hashlib import json import os import ssl import sys import urllib.error import urllib.request from datetime import UTC, datetime, timedelta from pathlib import Path # Optional cryptography support (for encryption and cert generation) try: from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.x509.oid import NameOID HAS_CRYPTO = True except ImportError: HAS_CRYPTO = False def get_config(): """Load configuration from environment or config file.""" config = { "server": os.environ.get("FLASKPASTE_SERVER", "http://localhost:5000"), "cert_sha1": os.environ.get("FLASKPASTE_CERT_SHA1", ""), "client_cert": os.environ.get("FLASKPASTE_CLIENT_CERT", ""), "client_key": os.environ.get("FLASKPASTE_CLIENT_KEY", ""), "ca_cert": os.environ.get("FLASKPASTE_CA_CERT", ""), } # Try config file config_file = Path.home() / ".config" / "fpaste" / "config" if config_file.exists(): for line in config_file.read_text().splitlines(): line = line.strip() if line and not line.startswith("#") and "=" in line: key, value = line.split("=", 1) key = key.strip().lower() value = value.strip().strip('"').strip("'") if key == "server": config["server"] = value elif key == "cert_sha1": config["cert_sha1"] = value elif key == "client_cert": config["client_cert"] = value elif key == "client_key": config["client_key"] = value elif key == "ca_cert": config["ca_cert"] = value return config def create_ssl_context(config): """Create SSL context for mTLS if certificates are configured.""" client_cert = config.get("client_cert", "") client_key = config.get("client_key", "") ca_cert = config.get("ca_cert", "") if not client_cert: return None ctx = ssl.create_default_context() # Load CA certificate if specified if ca_cert: ctx.load_verify_locations(ca_cert) # Load client certificate and key try: ctx.load_cert_chain(certfile=client_cert, keyfile=client_key or None) except ssl.SSLError as e: die(f"failed to load client certificate: {e}") except FileNotFoundError as e: die(f"certificate file not found: {e}") return ctx def request(url, method="GET", data=None, headers=None, ssl_context=None): """Make HTTP request and return response.""" headers = headers or {} # User-configured server URL, audit is expected req = urllib.request.Request(url, data=data, headers=headers, method=method) # noqa: S310 try: # User-configured server URL, audit is expected with urllib.request.urlopen(req, timeout=30, context=ssl_context) as resp: # noqa: S310 return resp.status, resp.read(), dict(resp.headers) except urllib.error.HTTPError as e: return e.code, e.read(), dict(e.headers) except urllib.error.URLError as e: die(f"Connection failed: {e.reason}") def die(msg, code=1): """Print error and exit.""" print(f"error: {msg}", file=sys.stderr) sys.exit(code) def encrypt_content(plaintext): """Encrypt content with AES-256-GCM. Returns (ciphertext, key).""" if not HAS_CRYPTO: die("encryption requires 'cryptography' package: pip install cryptography") key = os.urandom(32) nonce = os.urandom(12) # 96-bit nonce for GCM aesgcm = AESGCM(key) ciphertext = aesgcm.encrypt(nonce, plaintext, None) return nonce + ciphertext, key def decrypt_content(blob, key): """Decrypt AES-256-GCM encrypted content.""" if not HAS_CRYPTO: die("decryption requires 'cryptography' package: pip install cryptography") if len(blob) < 12: die("encrypted content too short") nonce, ciphertext = blob[:12], blob[12:] aesgcm = AESGCM(key) try: return aesgcm.decrypt(nonce, ciphertext, None) except Exception: die("decryption failed (wrong key or corrupted data)") def encode_key(key): """Encode key as URL-safe base64.""" return base64.urlsafe_b64encode(key).decode().rstrip("=") def decode_key(encoded): """Decode URL-safe base64 key.""" # Add padding if needed padding = 4 - (len(encoded) % 4) if padding != 4: encoded += "=" * padding try: return base64.urlsafe_b64decode(encoded) except Exception: die("invalid encryption key in URL") def solve_pow(nonce, difficulty): """Solve proof-of-work challenge. Find a number N such that SHA256(nonce:N) has `difficulty` leading zero bits. """ n = 0 target_bytes = (difficulty + 7) // 8 # Bytes to check while True: work = f"{nonce}:{n}".encode() hash_bytes = hashlib.sha256(work).digest() # Count leading zero bits zero_bits = 0 for byte in hash_bytes[: target_bytes + 1]: if byte == 0: zero_bits += 8 else: zero_bits += 8 - byte.bit_length() break if zero_bits >= difficulty: return n n += 1 # Progress indicator for high difficulty if n % 100000 == 0: print(f"\rsolving pow: {n} attempts...", end="", file=sys.stderr) return n def get_challenge(config): """Fetch PoW challenge from server.""" url = config["server"].rstrip("/") + "/challenge" status, body, _ = request(url, ssl_context=config.get("ssl_context")) if status != 200: return None data = json.loads(body) if not data.get("enabled"): return None return data def cmd_create(args, config): """Create a new paste.""" # Read content from file or stdin if args.file: if args.file == "-": content = sys.stdin.buffer.read() else: path = Path(args.file) if not path.exists(): die(f"file not found: {args.file}") content = path.read_bytes() else: # No file specified, read from stdin if sys.stdin.isatty(): die("no input provided (pipe data or specify file)") content = sys.stdin.buffer.read() if not content: die("empty content") # Encrypt by default (unless --no-encrypt) encryption_key = None if not getattr(args, "no_encrypt", False): if not HAS_CRYPTO: die("encryption requires 'cryptography' package (use -E to disable)") if not args.quiet: print("encrypting...", end="", file=sys.stderr) content, encryption_key = encrypt_content(content) if not args.quiet: print(" done", file=sys.stderr) headers = {} if config["cert_sha1"]: headers["X-SSL-Client-SHA1"] = config["cert_sha1"] # Add burn-after-read header if args.burn: headers["X-Burn-After-Read"] = "true" # Add custom expiry header if args.expiry: headers["X-Expiry"] = str(args.expiry) # Add password header if args.password: headers["X-Paste-Password"] = args.password # Get and solve PoW challenge if required challenge = get_challenge(config) if challenge: if not args.quiet: print(f"solving pow (difficulty={challenge['difficulty']})...", end="", file=sys.stderr) solution = solve_pow(challenge["nonce"], challenge["difficulty"]) if not args.quiet: print(" done", file=sys.stderr) headers["X-PoW-Token"] = challenge["token"] headers["X-PoW-Solution"] = str(solution) url = config["server"].rstrip("/") + "/" status, body, _ = request( url, method="POST", data=content, headers=headers, ssl_context=config.get("ssl_context") ) if status == 201: data = json.loads(body) # Append encryption key to URL fragment if encrypted key_fragment = "" if encryption_key: key_fragment = "#" + encode_key(encryption_key) if args.raw: print(config["server"].rstrip("/") + data["raw"] + key_fragment) elif args.quiet: print(data["id"] + key_fragment) else: print(config["server"].rstrip("/") + data["url"] + key_fragment) else: try: err = json.loads(body).get("error", body.decode()) except (json.JSONDecodeError, UnicodeDecodeError): err = body.decode(errors="replace") die(f"create failed ({status}): {err}") def cmd_get(args, config): """Retrieve a paste.""" # Parse URL for paste ID and optional encryption key fragment url_input = args.id encryption_key = None # Extract key from URL fragment (#...) if "#" in url_input: url_input, key_encoded = url_input.rsplit("#", 1) if key_encoded: encryption_key = decode_key(key_encoded) paste_id = url_input.split("/")[-1] # Handle full URLs base = config["server"].rstrip("/") # Build headers for password-protected pastes headers = {} if args.password: headers["X-Paste-Password"] = args.password if args.meta: url = f"{base}/{paste_id}" status, body, _ = request(url, headers=headers, ssl_context=config.get("ssl_context")) if status == 200: data = json.loads(body) print(f"id: {data['id']}") print(f"mime_type: {data['mime_type']}") print(f"size: {data['size']}") print(f"created_at: {data['created_at']}") if encryption_key: print("encrypted: yes (key in URL)") if data.get("password_protected"): print("protected: yes (password required)") elif status == 401: die("password required (-p)") elif status == 403: die("invalid password") else: die(f"not found: {paste_id}") else: url = f"{base}/{paste_id}/raw" ssl_ctx = config.get("ssl_context") status, body, _ = request(url, headers=headers, ssl_context=ssl_ctx) if status == 200: # Decrypt if encryption key was provided if encryption_key: body = decrypt_content(body, encryption_key) if args.output: Path(args.output).write_bytes(body) print(f"saved: {args.output}", file=sys.stderr) else: # Write binary to stdout sys.stdout.buffer.write(body) # Add newline if content doesn't end with one and stdout is tty if sys.stdout.isatty() and body and not body.endswith(b"\n"): sys.stdout.buffer.write(b"\n") elif status == 401: die("password required (-p)") elif status == 403: die("invalid password") else: die(f"not found: {paste_id}") def cmd_delete(args, config): """Delete a paste.""" if not config["cert_sha1"]: die("authentication required (set FLASKPASTE_CERT_SHA1)") paste_id = args.id.split("/")[-1] base = config["server"].rstrip("/") url = f"{base}/{paste_id}" headers = {"X-SSL-Client-SHA1": config["cert_sha1"]} status, _, _ = request( url, method="DELETE", headers=headers, ssl_context=config.get("ssl_context") ) if status == 200: print(f"deleted: {paste_id}") elif status == 404: die(f"not found: {paste_id}") elif status == 403: die("permission denied (not owner)") elif status == 401: die("authentication failed") else: die(f"delete failed ({status})") def cmd_info(args, config): """Show server info.""" url = config["server"].rstrip("/") + "/" status, body, _ = request(url, ssl_context=config.get("ssl_context")) if status == 200: data = json.loads(body) print(f"server: {config['server']}") print(f"name: {data.get('name', 'unknown')}") print(f"version: {data.get('version', 'unknown')}") else: die("failed to connect to server") def cmd_pki_status(args, config): """Show PKI status and CA information.""" url = config["server"].rstrip("/") + "/pki" status, body, _ = request(url, ssl_context=config.get("ssl_context")) if status == 404: die("PKI not enabled on this server") elif status != 200: die(f"failed to get PKI status ({status})") data = json.loads(body) print(f"pki enabled: {data.get('enabled', False)}") print(f"ca exists: {data.get('ca_exists', False)}") if data.get("ca_exists"): print(f"common name: {data.get('common_name', 'unknown')}") print(f"fingerprint: {data.get('fingerprint_sha1', 'unknown')}") if data.get("created_at"): print(f"created: {data.get('created_at')}") if data.get("expires_at"): print(f"expires: {data.get('expires_at')}") print(f"download: {config['server'].rstrip('/')}{data.get('download', '/pki/ca.crt')}") elif data.get("hint"): print(f"hint: {data.get('hint')}") def cmd_pki_issue(args, config): """Request a new client certificate from the server CA.""" url = config["server"].rstrip("/") + "/pki/issue" headers = {"Content-Type": "application/json"} if config["cert_sha1"]: headers["X-SSL-Client-SHA1"] = config["cert_sha1"] payload = {"common_name": args.name} data = json.dumps(payload).encode() status, body, _ = request( url, method="POST", data=data, headers=headers, ssl_context=config.get("ssl_context") ) if status == 404: # Could be PKI disabled or no CA try: err = json.loads(body).get("error", "PKI not available") except (json.JSONDecodeError, UnicodeDecodeError): err = "PKI not available" die(err) elif status == 400: try: err = json.loads(body).get("error", "bad request") except (json.JSONDecodeError, UnicodeDecodeError): err = "bad request" die(err) elif status != 201: die(f"certificate issuance failed ({status})") result = json.loads(body) # Determine output directory out_dir = Path(args.output) if args.output else Path.home() / ".config" / "fpaste" out_dir.mkdir(parents=True, exist_ok=True) # File paths key_file = out_dir / "client.key" cert_file = out_dir / "client.crt" # Check for existing files if not args.force: if key_file.exists(): die(f"key file exists: {key_file} (use --force)") if cert_file.exists(): die(f"cert file exists: {cert_file} (use --force)") # Write files key_file.write_text(result["private_key_pem"]) key_file.chmod(0o600) cert_file.write_text(result["certificate_pem"]) fingerprint = result.get("fingerprint_sha1", "unknown") print(f"key: {key_file}", file=sys.stderr) print(f"certificate: {cert_file}", file=sys.stderr) print(f"fingerprint: {fingerprint}", file=sys.stderr) print(f"serial: {result.get('serial', 'unknown')}", file=sys.stderr) print(f"common name: {result.get('common_name', args.name)}", file=sys.stderr) # Update config file if requested if args.configure: config_file = Path.home() / ".config" / "fpaste" / "config" config_file.parent.mkdir(parents=True, exist_ok=True) # Read existing config existing = {} if config_file.exists(): for line in config_file.read_text().splitlines(): line = line.strip() if line and not line.startswith("#") and "=" in line: k, v = line.split("=", 1) existing[k.strip().lower()] = v.strip() # Update values existing["client_cert"] = str(cert_file) existing["client_key"] = str(key_file) existing["cert_sha1"] = fingerprint # Write config lines = [f"{k} = {v}" for k, v in sorted(existing.items())] config_file.write_text("\n".join(lines) + "\n") print(f"config: {config_file} (updated)", file=sys.stderr) # Output fingerprint to stdout for easy capture print(fingerprint) def cmd_pki_download(args, config): """Download the CA certificate from the server.""" url = config["server"].rstrip("/") + "/pki/ca.crt" status, body, _ = request(url, ssl_context=config.get("ssl_context")) if status == 404: die("CA certificate not available (PKI disabled or CA not generated)") elif status != 200: die(f"failed to download CA certificate ({status})") # Determine output if args.output: out_path = Path(args.output) out_path.write_bytes(body) print(f"saved: {out_path}", file=sys.stderr) # Calculate and show fingerprint if cryptography available if HAS_CRYPTO: cert = x509.load_pem_x509_certificate(body) # SHA1 is standard for X.509 fingerprints fp = hashlib.sha1(cert.public_bytes(serialization.Encoding.DER)).hexdigest() # noqa: S324 print(f"fingerprint: {fp}", file=sys.stderr) # Update config if requested if args.configure: config_file = Path.home() / ".config" / "fpaste" / "config" config_file.parent.mkdir(parents=True, exist_ok=True) existing = {} if config_file.exists(): for line in config_file.read_text().splitlines(): line = line.strip() if line and not line.startswith("#") and "=" in line: k, v = line.split("=", 1) existing[k.strip().lower()] = v.strip() existing["ca_cert"] = str(out_path) lines = [f"{k} = {v}" for k, v in sorted(existing.items())] config_file.write_text("\n".join(lines) + "\n") print(f"config: {config_file} (updated)", file=sys.stderr) else: # Output to stdout sys.stdout.buffer.write(body) def cmd_cert(args, config): """Generate a self-signed client certificate for mTLS authentication.""" if not HAS_CRYPTO: die("certificate generation requires 'cryptography' package: pip install cryptography") # Determine output directory out_dir = Path(args.output) if args.output else Path.home() / ".config" / "fpaste" out_dir.mkdir(parents=True, exist_ok=True) # File paths key_file = out_dir / "client.key" cert_file = out_dir / "client.crt" # Check for existing files if not args.force: if key_file.exists(): die(f"key file exists: {key_file} (use --force)") if cert_file.exists(): die(f"cert file exists: {cert_file} (use --force)") # Generate private key if args.algorithm == "rsa": key_size = args.bits or 4096 print(f"generating {key_size}-bit RSA key...", file=sys.stderr) private_key = rsa.generate_private_key( public_exponent=65537, key_size=key_size, ) elif args.algorithm == "ec": curve_name = args.curve or "secp384r1" curves = { "secp256r1": ec.SECP256R1(), "secp384r1": ec.SECP384R1(), "secp521r1": ec.SECP521R1(), } if curve_name not in curves: die(f"unsupported curve: {curve_name} (use: secp256r1, secp384r1, secp521r1)") print(f"generating EC key ({curve_name})...", file=sys.stderr) private_key = ec.generate_private_key(curves[curve_name]) else: die(f"unsupported algorithm: {args.algorithm}") # Certificate subject cn = args.name or os.environ.get("USER", "fpaste-client") subject = issuer = x509.Name( [ x509.NameAttribute(NameOID.COMMON_NAME, cn), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "FlaskPaste Client"), ] ) # Validity period days = args.days or 365 now = datetime.now(UTC) # Build certificate cert_builder = ( x509.CertificateBuilder() .subject_name(subject) .issuer_name(issuer) .public_key(private_key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now) .not_valid_after(now + timedelta(days=days)) .add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=True, ) .add_extension( x509.KeyUsage( digital_signature=True, key_encipherment=True, content_commitment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False, ), critical=True, ) .add_extension( x509.ExtendedKeyUsage([x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH]), critical=False, ) ) # Sign certificate print("signing certificate...", file=sys.stderr) certificate = cert_builder.sign(private_key, hashes.SHA256()) # Calculate SHA1 fingerprint (standard for X.509) cert_der = certificate.public_bytes(serialization.Encoding.DER) fingerprint = hashlib.sha1(cert_der).hexdigest() # noqa: S324 # Serialize private key if args.password_key: key_encryption = serialization.BestAvailableEncryption(args.password_key.encode("utf-8")) else: key_encryption = serialization.NoEncryption() key_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=key_encryption, ) # Serialize certificate cert_pem = certificate.public_bytes(serialization.Encoding.PEM) # Write files key_file.write_bytes(key_pem) key_file.chmod(0o600) # Restrict permissions cert_file.write_bytes(cert_pem) print(f"key: {key_file}", file=sys.stderr) print(f"certificate: {cert_file}", file=sys.stderr) print(f"fingerprint: {fingerprint}", file=sys.stderr) print(f"valid for: {days} days", file=sys.stderr) print(f"common name: {cn}", file=sys.stderr) # Update config file if requested if args.configure: config_file = Path.home() / ".config" / "fpaste" / "config" config_file.parent.mkdir(parents=True, exist_ok=True) # Read existing config existing = {} if config_file.exists(): for line in config_file.read_text().splitlines(): line = line.strip() if line and not line.startswith("#") and "=" in line: k, v = line.split("=", 1) existing[k.strip().lower()] = v.strip() # Update values existing["client_cert"] = str(cert_file) existing["client_key"] = str(key_file) existing["cert_sha1"] = fingerprint # Write config lines = [f"{k} = {v}" for k, v in sorted(existing.items())] config_file.write_text("\n".join(lines) + "\n") print(f"config: {config_file} (updated)", file=sys.stderr) # Output fingerprint to stdout for easy capture print(fingerprint) def is_file_path(arg): """Check if argument looks like a file path.""" if not arg or arg.startswith("-"): return False # Check if it's an existing file if Path(arg).exists(): return True # Check if it looks like a path (contains / or \ or common extensions) if "/" in arg or "\\" in arg: return True # Check for common file extensions if "." in arg and not arg.startswith("."): ext = arg.rsplit(".", 1)[-1].lower() if ext in ("txt", "md", "py", "js", "json", "yaml", "yml", "xml", "html", "css", "sh", "bash", "c", "cpp", "h", "go", "rs", "java", "rb", "php", "sql", "log", "conf", "cfg", "ini", "png", "jpg", "jpeg", "gif", "pdf", "zip", "tar", "gz"): return True return False def main(): # Pre-process arguments: if first positional looks like a file, insert "create" args_to_parse = sys.argv[1:] commands = {"create", "c", "new", "get", "g", "delete", "d", "rm", "info", "i", "cert", "pki"} top_level_opts = {"-s", "--server", "-h", "--help"} # Find insertion point for "create" command insert_pos = 0 has_command = False file_pos = -1 i = 0 while i < len(args_to_parse): arg = args_to_parse[i] if arg in ("-s", "--server"): insert_pos = i + 2 # After -s value i += 2 continue if arg in ("-h", "--help"): i += 1 insert_pos = i continue if arg.startswith("-"): # Unknown option - might be for create subcommand i += 1 continue # Found positional argument if arg in commands: has_command = True break elif is_file_path(arg): file_pos = i break i += 1 # Insert "create" before file path if no command found if not has_command and file_pos >= 0: args_to_parse.insert(insert_pos, "create") parser = argparse.ArgumentParser( prog="fpaste", description="FlaskPaste command-line client", epilog="Shortcut: fpaste is equivalent to fpaste create ", ) parser.add_argument( "-s", "--server", help="server URL (env: FLASKPASTE_SERVER)", ) subparsers = parser.add_subparsers(dest="command", metavar="command") # create p_create = subparsers.add_parser("create", aliases=["c", "new"], help="create paste") p_create.add_argument("file", nargs="?", help="file to upload (- for stdin)") p_create.add_argument("-E", "--no-encrypt", action="store_true", help="disable encryption") p_create.add_argument("-b", "--burn", action="store_true", help="burn after read") p_create.add_argument("-x", "--expiry", type=int, metavar="SEC", help="expiry in seconds") p_create.add_argument("-p", "--password", metavar="PASS", help="password protect") p_create.add_argument("-r", "--raw", action="store_true", help="output raw URL") p_create.add_argument("-q", "--quiet", action="store_true", help="output ID only") # get p_get = subparsers.add_parser("get", aliases=["g"], help="retrieve paste") p_get.add_argument("id", help="paste ID or URL") p_get.add_argument("-o", "--output", help="save to file") p_get.add_argument("-p", "--password", metavar="PASS", help="password for protected paste") p_get.add_argument("-m", "--meta", action="store_true", help="show metadata only") # delete p_delete = subparsers.add_parser("delete", aliases=["d", "rm"], help="delete paste") p_delete.add_argument("id", help="paste ID or URL") # info subparsers.add_parser("info", aliases=["i"], help="show server info") # cert p_cert = subparsers.add_parser("cert", help="generate client certificate") p_cert.add_argument("-o", "--output", metavar="DIR", help="output directory") p_cert.add_argument( "-a", "--algorithm", choices=["rsa", "ec"], default="ec", help="key algorithm (default: ec)" ) p_cert.add_argument("-b", "--bits", type=int, metavar="N", help="RSA key size (default: 4096)") p_cert.add_argument( "-c", "--curve", metavar="CURVE", help="EC curve: secp256r1, secp384r1, secp521r1" ) p_cert.add_argument("-d", "--days", type=int, metavar="N", help="validity period in days") p_cert.add_argument("-n", "--name", metavar="CN", help="common name (default: $USER)") p_cert.add_argument("--password-key", metavar="PASS", help="encrypt private key with password") p_cert.add_argument( "--configure", action="store_true", help="update config file with generated cert paths" ) p_cert.add_argument("-f", "--force", action="store_true", help="overwrite existing files") # pki (with subcommands) p_pki = subparsers.add_parser("pki", help="PKI operations (server-issued certificates)") pki_sub = p_pki.add_subparsers(dest="pki_command", metavar="subcommand") # pki status pki_sub.add_parser("status", help="show PKI status and CA info") # pki issue p_pki_issue = pki_sub.add_parser("issue", help="request certificate from server CA") p_pki_issue.add_argument( "-n", "--name", required=True, metavar="CN", help="common name for certificate (required)" ) p_pki_issue.add_argument( "-o", "--output", metavar="DIR", help="output directory (default: ~/.config/fpaste)" ) p_pki_issue.add_argument( "--configure", action="store_true", help="update config file with issued cert paths" ) p_pki_issue.add_argument("-f", "--force", action="store_true", help="overwrite existing files") # pki download p_pki_download = pki_sub.add_parser("download", aliases=["dl"], help="download CA certificate") p_pki_download.add_argument( "-o", "--output", metavar="FILE", help="save to file (default: stdout)" ) p_pki_download.add_argument( "--configure", action="store_true", help="update config file with CA cert path (requires -o)", ) args = parser.parse_args(args_to_parse) config = get_config() if args.server: config["server"] = args.server # Create SSL context for mTLS if configured config["ssl_context"] = create_ssl_context(config) if not args.command: # Default: create from stdin if data is piped if not sys.stdin.isatty(): args.command = "create" args.file = None args.no_encrypt = False # Encrypt by default args.burn = False args.expiry = None args.password = None args.raw = False args.quiet = False else: parser.print_help() sys.exit(0) if args.command in ("create", "c", "new"): cmd_create(args, config) elif args.command in ("get", "g"): cmd_get(args, config) elif args.command in ("delete", "d", "rm"): cmd_delete(args, config) elif args.command in ("info", "i"): cmd_info(args, config) elif args.command == "cert": cmd_cert(args, config) elif args.command == "pki": if args.pki_command == "status": cmd_pki_status(args, config) elif args.pki_command == "issue": cmd_pki_issue(args, config) elif args.pki_command in ("download", "dl"): cmd_pki_download(args, config) else: # Show pki help if no subcommand parser.parse_args(["pki", "--help"]) if __name__ == "__main__": main()