Skip to content

Commit cd4d625

Browse files
author
Ralf Schlatterbeck
committed
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.
1 parent 72cc797 commit cd4d625

File tree

2 files changed

+207
-27
lines changed

2 files changed

+207
-27
lines changed

roundup/mailgw.py

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class node. Any parts of other types are each stored in separate files
9292
from roundup.hyperdb import iter_roles
9393

9494
try:
95-
import pyme, pyme.core, pyme.gpgme
95+
import pyme, pyme.core, pyme.constants, pyme.constants.sigsum
9696
except ImportError:
9797
pyme = None
9898

@@ -156,39 +156,31 @@ def gpgh_key_getall(key, attr):
156156
''' return list of given attribute for all uids in
157157
a key
158158
'''
159-
u = key.uids
160-
while u:
159+
for u in key.uids:
161160
yield getattr(u, attr)
162-
u = u.next
163161

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):
162+
def check_pgp_sigs(sigs, gpgctx, author):
171163
''' Theoretically a PGP message can have several signatures. GPGME
172-
returns status on all signatures in a linked list. Walk that
173-
linked list looking for the author's signature
164+
returns status on all signatures in a list. Walk that list
165+
looking for the author's signature
174166
'''
175-
for sig in gpgh_sigs(sig):
167+
for sig in sigs:
176168
key = gpgctx.get_key(sig.fpr, False)
177169
# we really only care about the signature of the user who
178170
# submitted the email
179171
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:
181173
return True
182174
else:
183175
# try to narrow down the actual problem to give a more useful
184176
# message in our bounce
185-
if sig.summary & pyme.gpgme.GPGME_SIGSUM_KEY_MISSING:
177+
if sig.summary & pyme.constants.sigsum.KEY_MISSING:
186178
raise MailUsageError, \
187179
_("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:
189181
raise MailUsageError, \
190182
_("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:
192184
raise MailUsageError, \
193185
_("Message signed with a revoked key: %s") % sig.fpr
194186
else:
@@ -511,7 +503,6 @@ def verify_signature(self, author):
511503
raise MailUsageError, \
512504
_("No PGP signature found in message.")
513505

514-
context = pyme.core.Context()
515506
# msg.getbody() is skipping over some headers that are
516507
# required to be present for verification to succeed so
517508
# we'll do this by hand
@@ -526,6 +517,7 @@ def verify_signature(self, author):
526517
msg_data = pyme.core.Data(canonical_msg)
527518
sig_data = pyme.core.Data(sig.getbody())
528519

520+
context = pyme.core.Context()
529521
context.op_verify(sig_data, msg_data, None)
530522

531523
# check all signatures for validity
@@ -995,7 +987,7 @@ def pgp_role():
995987
"""
996988
if self.config.PGP_ROLES:
997989
return self.db.user.has_role(self.author,
998-
iter_roles(self.config.PGP_ROLES))
990+
*iter_roles(self.config.PGP_ROLES))
999991
else:
1000992
return True
1001993

test/test_mailgw.py

Lines changed: 195 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515

1616
import unittest, tempfile, os, shutil, errno, imp, sys, difflib, rfc822, time
1717

18+
19+
try:
20+
import pyme, pyme.core
21+
except ImportError:
22+
pyme = None
23+
24+
1825
from cStringIO import StringIO
1926

2027
if not os.environ.has_key('SENDMAILDEBUG'):
@@ -116,13 +123,13 @@ def compareStrings(self, s2, s1, replace={}):
116123

117124
return res
118125

119-
class MailgwTestCase(unittest.TestCase, DiffHelper):
126+
class MailgwTestAbstractBase(unittest.TestCase, DiffHelper):
120127
count = 0
121128
schema = 'classic'
122129
def setUp(self):
123130
self.old_translate_ = mailgw._
124131
roundupdb._ = mailgw._ = i18n.get_translation(language='C').gettext
125-
MailgwTestCase.count = MailgwTestCase.count + 1
132+
self.__class__.count = self.__class__.count + 1
126133

127134
# and open the database / "instance"
128135
self.db = memorydb.create('admin')
@@ -169,6 +176,8 @@ def _get_mail(self):
169176
finally:
170177
f.close()
171178

179+
# Normal test-case used for both non-pgp test and a test while pgp
180+
# is enabled, so this test is run in both test suites.
172181
def testEmptyMessage(self):
173182
nodeid = self._handle_mail('''Content-Type: text/plain;
174183
charset="iso-8859-1"
@@ -183,6 +192,9 @@ def testEmptyMessage(self):
183192
assert not os.path.exists(SENDMAILDEBUG)
184193
self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...')
185194

195+
196+
class MailgwTestCase(MailgwTestAbstractBase):
197+
186198
def testMessageWithFromInIt(self):
187199
nodeid = self._handle_mail('''Content-Type: text/plain;
188200
charset="iso-8859-1"
@@ -1780,7 +1792,7 @@ def testNewUserAuthor(self):
17801792
""")
17811793
assert not body_diff, body_diff
17821794
else:
1783-
raise AssertionError, "Unathorized not raised when handling mail"
1795+
raise AssertionError, "Unauthorized not raised when handling mail"
17841796

17851797
# Make sure list of users is the same as before.
17861798
m = self.db.user.list()
@@ -2950,17 +2962,193 @@ def testForwardedMessageAttachment(self):
29502962
fileid = self.db.msg.get(msgid, 'files')[0]
29512963
self.assertEqual(self.db.file.get(fileid, 'type'), 'message/rfc822')
29522964

2965+
pgp_test_key = """
2966+
-----BEGIN PGP PRIVATE KEY BLOCK-----
2967+
Version: GnuPG v1.4.10 (GNU/Linux)
2968+
2969+
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
3021+
=3uFZ
3022+
-----END PGP PRIVATE KEY BLOCK-----
3023+
"""
3024+
3025+
john_doe_key = """
3026+
-----BEGIN PGP PRIVATE KEY BLOCK-----
3027+
Version: GnuPG v1.4.10 (GNU/Linux)
3028+
3029+
lQHYBE6NwvABBACxg7QqV2qHywwM3wae6HAHJVEo7EeYA6Lv0pZlW3Aw4CCCnpgJ
3030+
jA7CekGFcmGmoCaN9ezuVAPTgUlK4yt8a7P6cT0vw1q341Om9IEKAu59RpNZN/H9
3031+
6GfZ95bU51W/hdTFysH1DRwbCR3MowvLeA6Pk4cZlPsYHD0SD3De2i1BewARAQAB
3032+
AAP+IRi4L6jKwPS3k3LFrj0SHhL0Fdgv5QTQjTxLNCyfN02iYhglqqoFWncm3jWc
3033+
RU/YwGEYwrrBV97kBmVihzkhfgFRsxynE9PMGKKEAuRcAl21RPJDFA6Dlnp6M2No
3034+
rR6eoAhrlZ8+KsK9JaXSMalzO/Yh4u3mOinq3f3XL96wAEkCAMAxeZMF5pnXARNR
3035+
Y7u2clhNNnLuf+BzpENCFMaWzWPyTcvbf4xNK7ZHPxFVZpX5/qAPJ8rnTaOTHxnN
3036+
5PgqbO8CAOxyrTw/muakTJLg+FXdn8BgxZGJXMT7KmkU9SReefjo7c1WlnZxKIAy
3037+
6vLIG8WMGpdfCFDve0YLr/GGyDtOjDUB/RN3gn6qnAJThBnVk2wESZVx41fihbIF
3038+
ACCKc9heFskzwurtvvp+bunM3quwrSH1hWvxiWJlDmGSn8zQFypGChifgLQZSm9o
3039+
biBEb2UgPGpvaG5AdGVzdC50ZXN0Poi4BBMBAgAiBQJOjcLwAhsDBgsJCAcDAgYV
3040+
CAIJCgsEFgIDAQIeAQIXgAAKCRC/z7qg+FujnPWiA/9T5SOGraRNIVVIyvJvYwkG
3041+
OTAfQ0K3QMlLoQMPmaEbx9Q+isF15M9sOMcl1XGO4UNWuCPIIN8z/y/OLgAB0ZuL
3042+
GlnAPPOOZ+MlaUXiMYo8oi416QZrMDf2H/Nkc10csiXm+zMl8RqeIQBEeljNyJ+t
3043+
MG1EWn/PHTwFTd/VePuQdJ0B2AROjcLwAQQApw+72jKy0/wqg5SAtnVSkA1F3Jna
3044+
/OG+ufz5dX57jkMFRvFoksWIWqHmiCjdE5QV8j+XTnjElhLsmrgjl7aAFveb30R6
3045+
ImmcpKMN31vAp4RZlnyYbYUCY4IXFuz3n1CaUL+mRx5yNJykrZNfpWNf2pwozkZq
3046+
lcDI69ymIW5acXUAEQEAAQAD/R7Jdf98l1scngMYo228ikYUxBqm2eX/fiQNXDWM
3047+
ZR2u+TJ9O53MvFejfXX7Pd6lTDQUBwDFncjgXO0YYSrMzabhqpqoKLqOIpZmBuWC
3048+
Hh1lvcFoIYoDR2LkiJ9EPBUEVUBDsUO8ajkILEE3G+DDpCaf9Vo82lCVyhDESqyt
3049+
v4lxAgDOLpoq1Whv5Ejr6FifTWytCiQjH2P1SmePlQmy6oEJRUYA1t4zYrzCJUX8
3050+
VAvPjh9JXilP6mhDbyQArWllewV9AgDPbVOf75ktRwfhje26tZsukqWYJCc1XvoH
3051+
3PTzA7vH1HZZq7dvxa87PiSnkOLEsIAsI+4jpeMxpPlQRxUvHf1ZAf9rK3v3HMJ/
3052+
2xVzwK24Oaj+g2O7D/fdqtLFGe5S5JobnTyp9xArDAhaZ/AKfDMYjUIKMP+bdNAf
3053+
y8fQUtuawFltm1GInwQYAQIACQUCTo3C8AIbDAAKCRC/z7qg+FujnDzYA/9EU6Pv
3054+
Ci1+DCtxjnq7IOvOjqExhFNGvN9Dw17Tl8HcyW3if9v5RxeSWYKl0DhzVdzMQgH/
3055+
78q4F4W1q2IkB7SCpXizHLIc3eh8iZkbWZE+CGPvTpqyF03Yi16qhxpAbkGs2Yhq
3056+
jTx5oJ4CL5fybBOZLg+BTlK4HIee6xEcbNoq+A==
3057+
=ZKBW
3058+
-----END PGP PRIVATE KEY BLOCK-----
3059+
"""
3060+
3061+
ownertrust = """
3062+
723762CD5A5FECB76DC72DF85ADCFD5631C38741:6:
3063+
2940C247A1FBAD508A1AF24BBFCFBAA0F85BA39C:6:
3064+
"""
3065+
3066+
class MailgwPGPTestCase(MailgwTestAbstractBase):
3067+
pgphome = 'pgp-test-home'
3068+
def setUp(self):
3069+
MailgwTestAbstractBase.setUp(self)
3070+
self.db.security.addRole (name = 'pgp', description = 'PGP Role')
3071+
self.instance.config['PGP_HOMEDIR'] = self.pgphome
3072+
self.instance.config['PGP_ROLES'] = 'pgp'
3073+
self.instance.config['PGP_ENABLE'] = True
3074+
self.db.user.set(self.john_id, roles='User,pgp')
3075+
os.mkdir(self.pgphome)
3076+
os.environ['GNUPGHOME'] = self.pgphome
3077+
ctx = pyme.core.Context()
3078+
key = pyme.core.Data(pgp_test_key)
3079+
ctx.op_import(key)
3080+
key = pyme.core.Data(john_doe_key)
3081+
ctx.op_import(key)
3082+
# trust-modelling with pyme isn't working in 0.8.1
3083+
# based on libgpgme11 1.2.0, also tried in C -- same thing.
3084+
otrust = os.popen ('gpg --import-ownertrust 2> /dev/null', 'w')
3085+
otrust.write(ownertrust)
3086+
otrust.close()
3087+
3088+
def tearDown(self):
3089+
MailgwTestAbstractBase.tearDown(self)
3090+
if os.path.exists(self.pgphome):
3091+
shutil.rmtree(self.pgphome)
3092+
3093+
def testUnsignedMessage(self):
3094+
self.assertRaises(MailUsageError, self._handle_mail,
3095+
'''Content-Type: text/plain;
3096+
charset="iso-8859-1"
3097+
From: John Doe <[email protected]>
3098+
3099+
Message-Id: <dummy_test_message_id>
3100+
Subject: [issue] Testing non-signed message...
3101+
3102+
This is no pgp signed message.
3103+
''')
3104+
3105+
def testSignedMessage(self):
3106+
nodeid = self._handle_mail('''Content-Disposition: inline
3107+
From: John Doe <[email protected]>
3108+
3109+
Subject: [issue] Testing signed message...
3110+
Content-Type: multipart/signed; micalg=pgp-sha1;
3111+
protocol="application/pgp-signature"; boundary="cWoXeonUoKmBZSoM"
3112+
3113+
3114+
--cWoXeonUoKmBZSoM
3115+
Content-Type: text/plain; charset=us-ascii
3116+
Content-Disposition: inline
3117+
3118+
This is a pgp signed message.
3119+
3120+
--cWoXeonUoKmBZSoM
3121+
Content-Type: application/pgp-signature; name="signature.asc"
3122+
Content-Description: Digital signature
3123+
Content-Disposition: inline
3124+
3125+
-----BEGIN PGP SIGNATURE-----
3126+
Version: GnuPG v1.4.10 (GNU/Linux)
3127+
3128+
iJwEAQECAAYFAk6N4A4ACgkQv8+6oPhbo5x5nAP/d7R7SxTvLoVESI+1r7eDXp1J
3129+
LvBVU2EF3YFYKBHMLcWmjG92fNjnHX6NENTEhTeBynba5IPEwUfITC+7PmgPmQkA
3130+
VXnFZnwraHxsYgyFsVFN1kkTSbwRUlWl9+nTEsr0yBLTpZN0QSIDcwu+i/xVcg+t
3131+
ZQ4K6R3m3AOw7BLdvZs=
3132+
=wpYk
3133+
-----END PGP SIGNATURE-----
3134+
3135+
--cWoXeonUoKmBZSoM--
3136+
''')
3137+
m = self.db.issue.get (nodeid, 'messages') [0]
3138+
self.assertEqual(self.db.msg.get(m, 'content'),
3139+
'This is a pgp signed message.')
3140+
29533141
def test_suite():
29543142
suite = unittest.TestSuite()
29553143
suite.addTest(unittest.makeSuite(MailgwTestCase))
3144+
if pyme is not None:
3145+
suite.addTest(unittest.makeSuite(MailgwPGPTestCase))
3146+
else:
3147+
print "Skipping PGP tests"
29563148
return suite
29573149

29583150
if __name__ == '__main__':
29593151
runner = unittest.TextTestRunner()
29603152
unittest.main(testRunner=runner)
29613153

29623154
# vim: set filetype=python sts=4 sw=4 et si :
2963-
2964-
2965-
2966-

0 commit comments

Comments
 (0)