From 492b8232153b4839c67429c250c21b7f26e9c98b Mon Sep 17 00:00:00 2001 From: s3lph <1375407-s3lph@users.noreply.gitlab.com> Date: Sat, 26 Nov 2022 19:00:16 +0100 Subject: [PATCH] Add easywks import CLI command --- README.md | 13 +++++++++++++ easywks/files.py | 2 +- easywks/httpd.py | 2 +- easywks/lmtpd.py | 2 +- easywks/main.py | 9 +++++++-- easywks/process.py | 29 ++++++++++++++++++++++++++++- 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a77a46d..4d55e0b 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,19 @@ Encrypt live everybody is. ``` +## Manual Key Import + +In addition to WKS, EasyWKS also provides a command line interface for +importing keys from standard input. This feature is mainly intended +to be used for technical email accounts where using WKS might prove to +be difficult: + +```console?prompt=$,  +$ cat pubkey.asc | easywks import +Skipping foreign email john.doe@notmydepartment.org +Imported key A58D3221F8079F35FF084890505A563492A56583 for email john.doe@example.org +``` + [wkd]: https://wiki.gnupg.org/WKD [wks]: https://wiki.gnupg.org/WKS [ietf]: https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service-12 diff --git a/easywks/files.py b/easywks/files.py index 8081ea9..fef5ed8 100644 --- a/easywks/files.py +++ b/easywks/files.py @@ -96,7 +96,7 @@ def remove_pending_key(domain, nonce): os.unlink(keyfile) -def clean_stale_requests(): +def clean_stale_requests(args): stale = (datetime.utcnow() - timedelta(seconds=Config.pending_lifetime)).timestamp() for domain in Config.domains: pending_dir = os.path.join(Config.working_directory, domain, 'pending') diff --git a/easywks/httpd.py b/easywks/httpd.py index 6b5de70..848ab5e 100644 --- a/easywks/httpd.py +++ b/easywks/httpd.py @@ -38,7 +38,7 @@ def hu(domain: str, userhash: str): abort(404, 'Not Found') -def run_server(): +def run_server(args): run(host=Config.httpd['host'], port=Config.httpd['port']) diff --git a/easywks/lmtpd.py b/easywks/lmtpd.py index eb924a1..d9456d3 100644 --- a/easywks/lmtpd.py +++ b/easywks/lmtpd.py @@ -34,7 +34,7 @@ class LmtpdController(Controller): return LMTP(handler=self.handler, ident=f'EasyWKS {version}', loop=self.loop) -def run_lmtpd(): +def run_lmtpd(args): controller = LmtpdController(handler=LmtpMailServer(), hostname=Config.lmtpd['host'], port=Config.lmtpd['port']) controller.start() asyncio.get_event_loop().run_forever() diff --git a/easywks/main.py b/easywks/main.py index 7af0d5a..eb55233 100644 --- a/easywks/main.py +++ b/easywks/main.py @@ -1,7 +1,7 @@ from .config import Config from .files import init_working_directory, clean_stale_requests -from .process import process_mail_from_stdin +from .process import process_mail_from_stdin, process_key_from_stdin from .httpd import run_server from .lmtpd import run_lmtpd @@ -32,6 +32,11 @@ def parse_arguments(): server = sp.add_parser('lmtpd', help='Run a LMTP server to receive mails from your MTA. Also see process.') server.set_defaults(fn=run_lmtpd) + imp = sp.add_parser('import', help='Import a public key from stdin directly into the WKD without WKS verification.') + imp.add_argument('--uid', '-u', type=str, action='append', + help='Limit import to a subset of the key\'s UIDs. Can be provided multiple times.') + imp.set_defaults(fn=process_key_from_stdin) + return ap.parse_args(sys.argv[1:]) @@ -44,7 +49,7 @@ def main(): Config.load_config(conf) init_working_directory() if args.fn: - args.fn() + args.fn(args) if __name__ == '__main__': diff --git a/easywks/process.py b/easywks/process.py index 7089d37..bd0ef80 100644 --- a/easywks/process.py +++ b/easywks/process.py @@ -7,6 +7,7 @@ from .config import Config from .files import read_pending_key, write_public_key, remove_pending_key, write_pending_key from .types import SubmissionRequest, ConfirmationResponse, PublishResponse, ConfirmationRequest, EasyWksError,\ XLOOP_HEADER +from .types import fingerprint from email.message import MIMEPart, Message from email.parser import BytesParser @@ -172,6 +173,32 @@ def process_mail(mail: bytes): method(rmsg) -def process_mail_from_stdin(): +def process_mail_from_stdin(args): mail = sys.stdin.read().encode() process_mail(mail) + + +def process_key_from_stdin(args): + try: + pubkey, _ = PGPKey.from_blob(sys.stdin.read()) + except PGPError: + raise EasyWksError('Input is not a valid public key.') + if not pubkey.is_public: + raise EasyWksError('Input is not a valid public key.') + + for uid in pubkey.userids: + # Skip user attributes (e.g. photo ids) + if not uid.is_uid or len(uid.email) == 0: + continue + # If a UID filter was provided on the command line, apply it + if args.uid is not None and len(args.uid) > 0 and uid.email not in args.uid: + print(f'Skipping ignored email {uid.email}') + continue + local, domain = uid.email.split('@', 1) + # Skip keys we're not responsible for + if domain not in Config.domains: + print(f'Skipping foreign email {uid.email}') + continue + # All checks passed, importing key + write_public_key(domain, uid.email, pubkey) + print(f'Imported key {fingerprint(pubkey)} for email {uid.email}')