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'

Roundup Issue Tracker: http://roundup-tracker.org/