Add easywks import CLI command
This commit is contained in:
parent
0a337f90ee
commit
492b823215
6 changed files with 51 additions and 6 deletions
13
README.md
13
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
|
[wkd]: https://wiki.gnupg.org/WKD
|
||||||
[wks]: https://wiki.gnupg.org/WKS
|
[wks]: https://wiki.gnupg.org/WKS
|
||||||
[ietf]: https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service-12
|
[ietf]: https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service-12
|
||||||
|
|
|
@ -96,7 +96,7 @@ def remove_pending_key(domain, nonce):
|
||||||
os.unlink(keyfile)
|
os.unlink(keyfile)
|
||||||
|
|
||||||
|
|
||||||
def clean_stale_requests():
|
def clean_stale_requests(args):
|
||||||
stale = (datetime.utcnow() - timedelta(seconds=Config.pending_lifetime)).timestamp()
|
stale = (datetime.utcnow() - timedelta(seconds=Config.pending_lifetime)).timestamp()
|
||||||
for domain in Config.domains:
|
for domain in Config.domains:
|
||||||
pending_dir = os.path.join(Config.working_directory, domain, 'pending')
|
pending_dir = os.path.join(Config.working_directory, domain, 'pending')
|
||||||
|
|
|
@ -38,7 +38,7 @@ def hu(domain: str, userhash: str):
|
||||||
abort(404, 'Not Found')
|
abort(404, 'Not Found')
|
||||||
|
|
||||||
|
|
||||||
def run_server():
|
def run_server(args):
|
||||||
run(host=Config.httpd['host'], port=Config.httpd['port'])
|
run(host=Config.httpd['host'], port=Config.httpd['port'])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ class LmtpdController(Controller):
|
||||||
return LMTP(handler=self.handler, ident=f'EasyWKS {version}', loop=self.loop)
|
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 = LmtpdController(handler=LmtpMailServer(), hostname=Config.lmtpd['host'], port=Config.lmtpd['port'])
|
||||||
controller.start()
|
controller.start()
|
||||||
asyncio.get_event_loop().run_forever()
|
asyncio.get_event_loop().run_forever()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .files import init_working_directory, clean_stale_requests
|
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 .httpd import run_server
|
||||||
from .lmtpd import run_lmtpd
|
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 = sp.add_parser('lmtpd', help='Run a LMTP server to receive mails from your MTA. Also see process.')
|
||||||
server.set_defaults(fn=run_lmtpd)
|
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:])
|
return ap.parse_args(sys.argv[1:])
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,7 +49,7 @@ def main():
|
||||||
Config.load_config(conf)
|
Config.load_config(conf)
|
||||||
init_working_directory()
|
init_working_directory()
|
||||||
if args.fn:
|
if args.fn:
|
||||||
args.fn()
|
args.fn(args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -7,6 +7,7 @@ from .config import Config
|
||||||
from .files import read_pending_key, write_public_key, remove_pending_key, write_pending_key
|
from .files import read_pending_key, write_public_key, remove_pending_key, write_pending_key
|
||||||
from .types import SubmissionRequest, ConfirmationResponse, PublishResponse, ConfirmationRequest, EasyWksError,\
|
from .types import SubmissionRequest, ConfirmationResponse, PublishResponse, ConfirmationRequest, EasyWksError,\
|
||||||
XLOOP_HEADER
|
XLOOP_HEADER
|
||||||
|
from .types import fingerprint
|
||||||
|
|
||||||
from email.message import MIMEPart, Message
|
from email.message import MIMEPart, Message
|
||||||
from email.parser import BytesParser
|
from email.parser import BytesParser
|
||||||
|
@ -172,6 +173,32 @@ def process_mail(mail: bytes):
|
||||||
method(rmsg)
|
method(rmsg)
|
||||||
|
|
||||||
|
|
||||||
def process_mail_from_stdin():
|
def process_mail_from_stdin(args):
|
||||||
mail = sys.stdin.read().encode()
|
mail = sys.stdin.read().encode()
|
||||||
process_mail(mail)
|
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}')
|
||||||
|
|
Loading…
Reference in a new issue