From 871369e7c94f78dd666e2a59fe8d3d16feb119b8 Mon Sep 17 00:00:00 2001 From: q Date: Fri, 19 Jul 2024 12:14:09 +0300 Subject: [PATCH 1/2] new version: display info about a key, to show the keyid and recipient name --- qgpg/__init__.py | 105 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/qgpg/__init__.py b/qgpg/__init__.py index c88d59f..4330494 100644 --- a/qgpg/__init__.py +++ b/qgpg/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -__version__ = "20240627.1" +__version__ = "20240719.0" import argparse import os @@ -8,6 +8,7 @@ import shutil import sys import tempfile import time +from datetime import datetime from getpass import getpass try: @@ -149,21 +150,20 @@ Usage action="store_true", help="Decrypt files", ) + commands_grp.add_argument( + "--info", + "-i", + default=False, + action="store_true", + help="Show key info", + ) crypt_grp = parser.add_argument_group("Encryption", "Encryption and Decryption options") crypt_grp.add_argument( "--key", help="Path to key file. For keygen: private key to write; encrypt: public key file, decrypt: private key file. If not defined, symmetric password encryption is used.", ) - keygen_grp = parser.add_argument_group( - "Keygen", "Options for keygen. Note: --key is required for keygen, see above." - ) - keygen_grp.add_argument( - "--name", - help="Name of the owner of the key (usually email) Default: %(default)s", - required=False, - default="recipient@address", - ) + crypt_grp.add_argument( "--recursive", "-r", @@ -177,6 +177,19 @@ Usage action="store_true", help="Overwrite existing files", ) + keygen_grp = parser.add_argument_group( + "Keygen", "Options for keygen. Note: --key is required for keygen, see Encryption." + ) + keygen_grp.add_argument( + "--name", + help="Name of the owner of the key (usually email) Default: %(default)s", + required=False, + default="recipient@address", + ) + info_grp = parser.add_argument_group( + "Info", "Options for info. Note: --key is required for info. Give the private key file name." + ) + misc_grp = parser.add_argument_group("Misc", "Other options") misc_grp.add_argument("--width", help="Console width in characters. Defaults to auto detect.") misc_grp.add_argument("--no-progress", help="Disable progress meter.", default=False, action="store_true") @@ -191,17 +204,22 @@ Usage ) parsed = parser.parse_args() - for cmd in zip(("keygen", "encrypt", "decrypt"), (parsed.keygen, parsed.encrypt, parsed.decrypt)): + for cmd in zip( + ("keygen", "encrypt", "decrypt", "info"), (parsed.keygen, parsed.encrypt, parsed.decrypt, parsed.info) + ): if cmd[1]: parsed.command = cmd[0] - if parsed.command == "keygen": + + if parsed.command in ("keygen", "info"): if parsed.key is None: - parser.error("--key required for keygen") + parser.error(f"--key required for {parsed.command}") return parsed class Collector: + """Data collector for file streaming. Prints progress""" + def __init__(self, filename, width, bufcount): self.progress = MiniProgress(bufcount, width) @@ -217,6 +235,8 @@ class Collector: class Processor: + """Main processor for the program""" + def __init__(self): self.opts = get_opts() self.homedir = tempfile.TemporaryDirectory() @@ -230,6 +250,10 @@ class Processor: self.keygen() filelist = [] + if self.opts.command == "info": + self.key_info() + filelist = [] + if self.opts.command in ("encrypt", "decrypt"): if not self.symmetric: import_result = self.gpg.import_keys_file(self.opts.key) @@ -257,7 +281,7 @@ class Processor: self.homedir.cleanup() def set_phrase(self, twice=False): - + """Sets self.phrase. if `twice` asks user for phrase twice.""" if not self.phrase is None: # phrase already set return @@ -281,6 +305,7 @@ class Processor: sys.exit(1) def get_filelist(self, root, recurse, direction): + """returns a file list: if encrypting, lists files without .gpg, if decrytping, lists files with .gpg""" if not recurse: if os.path.isfile(root): return [root] @@ -298,6 +323,7 @@ class Processor: return filelist def keygen(self): + """Main key generator function""" if not self.opts.force: for f in (self.opts.key, self.opts.key + ".pub"): if os.path.exists(f): @@ -325,7 +351,52 @@ class Processor: os.chmod(self.opts.key, 0o600) print(f"Generated {self.opts.key} and {self.opts.key}.pub", file=sys.stderr) + def key_info(self): + """Main key info printing function""" + + def print_key(key): + + key_type = "NA" + if key.get("type") == "sec": + key_type = "Private" + if key.get("type") == "pub": + key_type = "Public" + + try: + key_date = datetime.fromtimestamp(int(key["date"])).isoformat() + except Exception as e: + print(e, file=sys.stderr) + key_date = "NA" + + print( + f"""Fingerprint: {key.get('fingerprint')} +KeyId: {' '.join(string_chunk(key.get('keyid',''),8))} +Name: {', '.join(key.get('uids'))} +Type: {key_type} +Date: {key_date} +KeyLength: {key.get('length')}""" + ) + + import_result = self.gpg.import_keys_file(self.opts.key) + if len(import_result.fingerprints) == 0: + self.homedir.cleanup() + print(f"File does not contain private or public GPG keys!", file=sys.stderr) + sys.exit(1) + + keys = self.gpg.list_keys(True) + + for key in keys: + # Private keys + print_key(key) + if len(keys) > 0: + # if private keys found, do not print public keys + return + for key in self.gpg.list_keys(): + # only public keys in the list + print_key(key) + def process_single(self, in_file): + """encrypt or decrypt single file""" if self.opts.command == "encrypt": auto_path = in_file + self.suffix @@ -368,11 +439,17 @@ class Processor: def strip_prefix(s, prefix): + """Return string without a prefix""" if s.startswith(prefix): return s[len(prefix) :] return s[:] +def string_chunk(string, length): + """Split string in to even chunks""" + return (string[0 + i : length + i] for i in range(0, len(string), length)) + + def main(): Processor() From a127edb9c3d9303c5eb8a7a8c9a48af0e70898bd Mon Sep 17 00:00:00 2001 From: q Date: Fri, 19 Jul 2024 13:18:22 +0300 Subject: [PATCH 2/2] add help on --info --- qgpg/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qgpg/__init__.py b/qgpg/__init__.py index 4330494..ae4801d 100644 --- a/qgpg/__init__.py +++ b/qgpg/__init__.py @@ -124,6 +124,8 @@ Usage GPGPASS=mysecretpassword qgpg --key ./mykey --decrypt file.gpg file.txt - Symmetric encryption (Just leave out the --key): qgpg --encrypt file.txt file.gpg +- Check who owns the private key: + qgpg --info --key ./mykey """, )