Mercurial > p > roundup > code
comparison roundup/mailgw.py @ 4540:bf67fed13ef9
Fix PGP implementation
The pyme API has changed significantly since this worked (API is now
better). Add regression-test for PGP support (this isn't run if pyme
isn't installed). We're testing only reception of a signed message for
now.
| author | Ralf Schlatterbeck <schlatterbeck@users.sourceforge.net> |
|---|---|
| date | Thu, 06 Oct 2011 21:02:09 +0000 |
| parents | c246f176e7bb |
| children | 62239a524beb |
comparison
equal
deleted
inserted
replaced
| 4539:c246f176e7bb | 4540:bf67fed13ef9 |
|---|---|
| 90 from roundup.mailer import Mailer, MessageSendError | 90 from roundup.mailer import Mailer, MessageSendError |
| 91 from roundup.i18n import _ | 91 from roundup.i18n import _ |
| 92 from roundup.hyperdb import iter_roles | 92 from roundup.hyperdb import iter_roles |
| 93 | 93 |
| 94 try: | 94 try: |
| 95 import pyme, pyme.core, pyme.gpgme | 95 import pyme, pyme.core, pyme.constants, pyme.constants.sigsum |
| 96 except ImportError: | 96 except ImportError: |
| 97 pyme = None | 97 pyme = None |
| 98 | 98 |
| 99 SENDMAILDEBUG = os.environ.get('SENDMAILDEBUG', '') | 99 SENDMAILDEBUG = os.environ.get('SENDMAILDEBUG', '') |
| 100 | 100 |
| 154 | 154 |
| 155 def gpgh_key_getall(key, attr): | 155 def gpgh_key_getall(key, attr): |
| 156 ''' return list of given attribute for all uids in | 156 ''' return list of given attribute for all uids in |
| 157 a key | 157 a key |
| 158 ''' | 158 ''' |
| 159 u = key.uids | 159 for u in key.uids: |
| 160 while u: | |
| 161 yield getattr(u, attr) | 160 yield getattr(u, attr) |
| 162 u = u.next | 161 |
| 163 | 162 def check_pgp_sigs(sigs, gpgctx, author): |
| 164 def gpgh_sigs(sig): | |
| 165 ''' more pythonic iteration over GPG signatures ''' | |
| 166 while sig: | |
| 167 yield sig | |
| 168 sig = sig.next | |
| 169 | |
| 170 def check_pgp_sigs(sig, gpgctx, author): | |
| 171 ''' Theoretically a PGP message can have several signatures. GPGME | 163 ''' Theoretically a PGP message can have several signatures. GPGME |
| 172 returns status on all signatures in a linked list. Walk that | 164 returns status on all signatures in a list. Walk that list |
| 173 linked list looking for the author's signature | 165 looking for the author's signature |
| 174 ''' | 166 ''' |
| 175 for sig in gpgh_sigs(sig): | 167 for sig in sigs: |
| 176 key = gpgctx.get_key(sig.fpr, False) | 168 key = gpgctx.get_key(sig.fpr, False) |
| 177 # we really only care about the signature of the user who | 169 # we really only care about the signature of the user who |
| 178 # submitted the email | 170 # submitted the email |
| 179 if key and (author in gpgh_key_getall(key, 'email')): | 171 if key and (author in gpgh_key_getall(key, 'email')): |
| 180 if sig.summary & pyme.gpgme.GPGME_SIGSUM_VALID: | 172 if sig.summary & pyme.constants.sigsum.VALID: |
| 181 return True | 173 return True |
| 182 else: | 174 else: |
| 183 # try to narrow down the actual problem to give a more useful | 175 # try to narrow down the actual problem to give a more useful |
| 184 # message in our bounce | 176 # message in our bounce |
| 185 if sig.summary & pyme.gpgme.GPGME_SIGSUM_KEY_MISSING: | 177 if sig.summary & pyme.constants.sigsum.KEY_MISSING: |
| 186 raise MailUsageError, \ | 178 raise MailUsageError, \ |
| 187 _("Message signed with unknown key: %s") % sig.fpr | 179 _("Message signed with unknown key: %s") % sig.fpr |
| 188 elif sig.summary & pyme.gpgme.GPGME_SIGSUM_KEY_EXPIRED: | 180 elif sig.summary & pyme.constants.sigsum.KEY_EXPIRED: |
| 189 raise MailUsageError, \ | 181 raise MailUsageError, \ |
| 190 _("Message signed with an expired key: %s") % sig.fpr | 182 _("Message signed with an expired key: %s") % sig.fpr |
| 191 elif sig.summary & pyme.gpgme.GPGME_SIGSUM_KEY_REVOKED: | 183 elif sig.summary & pyme.constants.sigsum.KEY_REVOKED: |
| 192 raise MailUsageError, \ | 184 raise MailUsageError, \ |
| 193 _("Message signed with a revoked key: %s") % sig.fpr | 185 _("Message signed with a revoked key: %s") % sig.fpr |
| 194 else: | 186 else: |
| 195 raise MailUsageError, \ | 187 raise MailUsageError, \ |
| 196 _("Invalid PGP signature detected.") | 188 _("Invalid PGP signature detected.") |
| 509 | 501 |
| 510 if sig.gettype() != 'application/pgp-signature': | 502 if sig.gettype() != 'application/pgp-signature': |
| 511 raise MailUsageError, \ | 503 raise MailUsageError, \ |
| 512 _("No PGP signature found in message.") | 504 _("No PGP signature found in message.") |
| 513 | 505 |
| 514 context = pyme.core.Context() | |
| 515 # msg.getbody() is skipping over some headers that are | 506 # msg.getbody() is skipping over some headers that are |
| 516 # required to be present for verification to succeed so | 507 # required to be present for verification to succeed so |
| 517 # we'll do this by hand | 508 # we'll do this by hand |
| 518 msg.fp.seek(0) | 509 msg.fp.seek(0) |
| 519 # according to rfc 3156 the data "MUST first be converted | 510 # according to rfc 3156 the data "MUST first be converted |
| 524 # TODO: what about character set conversion? | 515 # TODO: what about character set conversion? |
| 525 canonical_msg = re.sub('(?<!\r)\n', '\r\n', msg.fp.read()) | 516 canonical_msg = re.sub('(?<!\r)\n', '\r\n', msg.fp.read()) |
| 526 msg_data = pyme.core.Data(canonical_msg) | 517 msg_data = pyme.core.Data(canonical_msg) |
| 527 sig_data = pyme.core.Data(sig.getbody()) | 518 sig_data = pyme.core.Data(sig.getbody()) |
| 528 | 519 |
| 520 context = pyme.core.Context() | |
| 529 context.op_verify(sig_data, msg_data, None) | 521 context.op_verify(sig_data, msg_data, None) |
| 530 | 522 |
| 531 # check all signatures for validity | 523 # check all signatures for validity |
| 532 result = context.op_verify_result() | 524 result = context.op_verify_result() |
| 533 check_pgp_sigs(result.signatures, context, author) | 525 check_pgp_sigs(result.signatures, context, author) |
| 993 """ if PGP_ROLES is specified the user must have a Role in the list | 985 """ if PGP_ROLES is specified the user must have a Role in the list |
| 994 or we will skip PGP processing | 986 or we will skip PGP processing |
| 995 """ | 987 """ |
| 996 if self.config.PGP_ROLES: | 988 if self.config.PGP_ROLES: |
| 997 return self.db.user.has_role(self.author, | 989 return self.db.user.has_role(self.author, |
| 998 iter_roles(self.config.PGP_ROLES)) | 990 *iter_roles(self.config.PGP_ROLES)) |
| 999 else: | 991 else: |
| 1000 return True | 992 return True |
| 1001 | 993 |
| 1002 if self.config.PGP_ENABLE and pgp_role(): | 994 if self.config.PGP_ENABLE and pgp_role(): |
| 1003 assert pyme, 'pyme is not installed' | 995 assert pyme, 'pyme is not installed' |
