From 41de3b57047d96fcc563564c7549ccc93caa9b77 Mon Sep 17 00:00:00 2001 From: s3lph <1375407-s3lph@users.noreply.gitlab.com> Date: Tue, 31 Jan 2023 20:44:53 +0100 Subject: [PATCH] feat: implement policy_flags and apply these policies to submission requests --- .gitlab-ci.yml | 3 +- easywks/config.py | 41 +++++++++++++ easywks/files.py | 11 +++- easywks/process.py | 84 +++++++++++++++++++++++--- easywks/types.py | 12 ++-- package/debian/easywks/etc/easywks.yml | 54 ++++++++++++++++- 6 files changed, 183 insertions(+), 22 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ead1f34..a7ba00c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -45,7 +45,6 @@ easywksserver_gpgwksclient: - | cat > /tmp/easywks.yml < -1 and len(request.revoked_keys) > maxrevoke: + raise EasyWksError(f'Submission request contains {len(request.revoked_keys)} revoked keys. ' + f'Policy permits not more than {maxrevoke}') + uid: PGPUID + revfprs = [fingerprint(request.key)] + [fingerprint(rk) for rk in request.revoked_keys] + for key in [request.key] + request.revoked_keys: + # Policy: Strip user attribute (image) UIDs + if policy.get(POLICY_MAILBOX_ONLY, False) or policy.get(EWP_STRIP_UA_UIDS, False): + for uid in list(key.userattributes): + uid._parent = None + key._uids.remove(uid) + # Policy: Reject keys as invalid if they contain UIDs with non-empty name or comment parts + if policy.get(POLICY_MAILBOX_ONLY, False): + for uid in list(key.userids): + if uid.email == '' or uid.name != '' or uid.comment != '': + raise EasyWksError('This WKS server only accepts UIDs without name and comment parts') + # Policy: Strip all UIDs except the one being verified + if policy.get(EWP_STRIP_UNVERIFIED_UIDS, False): + for uid in list(key.userids): + if uid.email != request.submitter_address: + uid._parent = None + key._uids.remove(uid) + # Policy: Strip all 3rd party signatures from they key + if policy.get(EWP_STRIP_3RDPARTY_SIGNATURES, False): + for uid in list(key.userids): + for sig in list(uid.third_party_certifications): + # Keep signatures signed by the revoked keys + sig: PGPSignature + if sig.signer_fingerprint not in revfprs: + uid._signatures.remove(sig) + # Policy: Produce minimal transportable keys + if policy.get(EWP_MINIMIZE_REVOKED_KEYS, False): + for key in request.revoked_keys: + for uid in list(key.userids): + # Delete all but the submitter UIDs, and all 3rd party signatures + if uid.email != request.submitter_address: + uid._parent = None + key._uids.remove(uid) + else: + for sig in list(uid.third_party_certifications): + uid._signatures.remove(sig) + # Delete UAs + for uid in list(key.userattributes): + uid._parent = None + key._uids.remove(uid) + # Delete subkeys + for subkey in key._children.values(): + subkey._parent = None + key._children.clear() + + def process_mail(mail: bytes): try: msg: Message = BytesParser(policy=default).parsebytes(mail) @@ -170,17 +228,25 @@ def process_mail(mail: bytes): except FileNotFoundError: raise EasyWksError('There is no submission request for this email address, or it has expired. ' 'Please resubmit your submission request.') - # this throws an error if signature verification fails - request.verify_signature(key) + # TODO: Config.permit_unsigned_response is deprecated, but for now retained for backwards compatibility + if not Config[sender_domain].policy_flags.get(EWP_PERMIT_UNSIGNED_RESPONSE, False) and \ + not Config.permit_unsigned_response: + # this throws an error if signature verification fails + request.verify_signature(key) response: PublishResponse = request.get_publish_response(key) - rmsg = response.create_signed_message() write_public_key(sender_domain, sender_mail, key, revoked_keys) remove_pending_key(sender_domain, request.nonce) else: request: SubmissionRequest = _parse_submission_request(decrypted, submission_address, sender_mail) - response: ConfirmationRequest = request.confirmation_request() - rmsg = response.create_signed_message() - write_pending_key(sender_domain, response.nonce, request.key, request.revoked_keys) + policy = Config[sender_domain].policy_flags + _apply_submission_policy(request, policy) + if policy.get(POLICY_AUTH_SUBMIT, False): + response = PublishResponse(request.submitter_address, request.submission_address, request.key) + write_public_key(sender_domain, sender_mail, request.key, request.revoked_keys) + else: + response: ConfirmationRequest = request.confirmation_request() + write_pending_key(sender_domain, response.nonce, request.key, request.revoked_keys) + rmsg = response.create_signed_message() except EasyWksError as e: rmsg = e.create_message(sender_mail, submission_address) method = get_mailing_method(Config.mailing_method) diff --git a/easywks/types.py b/easywks/types.py index b2d75d9..6b09d2f 100644 --- a/easywks/types.py +++ b/easywks/types.py @@ -13,7 +13,7 @@ from pgpy import PGPKey, PGPMessage, PGPUID from pgpy.errors import PGPError from .crypto import pgp_sign -from .config import Config, render_message +from .config import render_message from .util import create_nonce, fingerprint @@ -145,13 +145,9 @@ class ConfirmationResponse: def verify_signature(self, key: PGPKey): if not self._msg.is_signed: - if not Config.permit_unsigned_response: - raise EasyWksError('The confirmation response is not signed. If you used an automated tool such as ' - 'gpg-wks-client for submitting your response, please update said tool or try ' - 'responding manually.') - else: - # Unsigned, but permitted - return + raise EasyWksError('The confirmation response is not signed. If you used an automated tool such as ' + 'gpg-wks-client for submitting your response, please update said tool or try ' + 'responding manually.') uid: PGPUID = key.get_uid(self._submitter_addr) if uid is None or uid.email != self._submitter_addr: raise EasyWksError(f'UID {self._submitter_addr} not found in PGP key') diff --git a/package/debian/easywks/etc/easywks.yml b/package/debian/easywks/etc/easywks.yml index 86d7530..6e4ca46 100644 --- a/package/debian/easywks/etc/easywks.yml +++ b/package/debian/easywks/etc/easywks.yml @@ -11,6 +11,10 @@ # older version of the WKS standard where signing the confirmation # response is only recommended, but not required. Set this option to # true if you want to accept such unsigned responses. +# +# This option is deprecated and will be removed in a future release. +# It is replaced by the me.s3lph.easywks_permit-unsigned-response +# per-domain policy flag. #permit_unsigned_response: false # Port configuration for the webserver. Put this behind a @@ -74,4 +78,52 @@ domains: # If you want the PGP key for this domain to be password-protected, # or if you're supplying your own password-protected key, set the # passphrase here: - #passphrase: "Correct Horse Battery Staple" \ No newline at end of file + #passphrase: "Correct Horse Battery Staple" + # Policy flags control behavior of the submission process for this + # domain. Supports most of the standard policy flags (see + # https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service-15#section-4.5 + # for details) as well as some EasyWKS-namespaced flags. + policy_flags: + + # The mail server provider does only accept keys with only a + # mailbox in the User ID. In particular User IDs with a real name + # in addition to the mailbox will be rejected as invalid. + #mailbox-only: false + + # The submission of the mail to the server is done using an + # authenticated connection. Thus the submitted key will be + # published immediately without any confirmation request. + #auth-submit: false + + # This keyword can be used to explicitly claim the support of a + # specific version of the Web Key Directory update protocol. This + # is in general not needed but implementations may have + # workarounds for providers which only support an old protocol + # version. If these providers update to a newer version they + # should add this keyword so that the implementation can disable + # the workaround. The value is an integer corresponding to the + # respective draft revision number. + #protocol-version: null + + # Some clients (including recent versions of gpg-wks-client + # follow an older version of the WKS standard where signing the + # confirmation response is only recommended, but not required. + # Set this option to true if you want to accept such unsigned + # responses. + #me.s3lph.easywks_permit-unsigned-response: false + + # Remove all UIDs except the one being verified. + #me.s3lph.easywks_strip-unverified-uids: false + + # Remove user attribute (i.e. photo) UIDs. + #me.s3lph.easywks_strip-ua-uids: false + + # Remove all third party certifications fom the submitted keys. + # Certifications issued by revoked keys submitted in the same + # submission request are exempt from this policy. + #me.s3lph.easywks_strip-3rdparty-signatures: false + + # Maximal number of revoked keys that can be submitted alongside + # a valid key. If this flag is absent or has value -1, an + # unlimited number of revoked keys is permitted. + #me.s3lph.easywks_max-revoked-keys: -1 \ No newline at end of file