~launchpad-pqm/launchpad/devel

13668.1.21 by Curtis Hovey
Updated copyrights.
1
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
8687.15.22 by Karl Fogel
Add the copyright header block to the remaining .py files.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
7675.60.10 by Tim Penhey
Add tests for signed message.
3
4
"""Test the SignedMessage class."""
5
6
__metaclass__ = type
7
8
from email.Message import Message
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
9
from email.MIMEMultipart import MIMEMultipart
7675.60.10 by Tim Penhey
Add tests for signed message.
10
from email.MIMEText import MIMEText
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
11
from email.Utils import (
12
    formatdate,
13
    make_msgid,
14
    )
7675.60.10 by Tim Penhey
Add tests for signed message.
15
from textwrap import dedent
16
17
import gpgme
18
from zope.component import getUtility
19
11678.2.5 by Gary Poster
move files as requested by review to lp/services; change things further as lint requests.
20
from lp.registry.interfaces.person import IPersonSet
14455.2.1 by William Grant
Move code and tests.
21
from lp.services.gpg.interfaces import IGPGHandler
11678.2.5 by Gary Poster
move files as requested by review to lp/services; change things further as lint requests.
22
from lp.services.mail.incoming import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
23
    authenticateEmail,
24
    canonicalise_line_endings,
25
    )
13668.1.22 by Curtis Hovey
Sorted imports.
26
from lp.services.mail.interfaces import IWeaklyAuthenticatedPrincipal
27
from lp.services.mail.signedmessage import signed_message_from_string
8440.1.2 by Curtis Hovey
Updated lp.testing imports. Removed the shim
28
from lp.testing import TestCaseWithFactory
29
from lp.testing.factory import GPGSigningContext
14455.2.4 by William Grant
Drop imports from canonical.launchpad.ftests.
30
from lp.testing.gpgkeys import (
31
    import_public_test_keys,
32
    import_secret_test_key,
33
    )
14612.2.1 by William Grant
format-imports on lib/. So many imports.
34
from lp.testing.layers import DatabaseFunctionalLayer
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
35
7675.60.10 by Tim Penhey
Add tests for signed message.
36
37
class TestSignedMessage(TestCaseWithFactory):
11678.2.5 by Gary Poster
move files as requested by review to lp/services; change things further as lint requests.
38
    "Test SignedMessage class correctly extracts and verifies GPG signatures."
7675.60.10 by Tim Penhey
Add tests for signed message.
39
40
    layer = DatabaseFunctionalLayer
41
42
    def setUp(self):
43
        # Login with admin roles as we aren't testing access here.
44
        TestCaseWithFactory.setUp(self, 'admin@canonical.com')
45
        import_public_test_keys()
46
47
    def test_unsigned_message(self):
48
        # An unsigned message will not have a signature nor signed content,
49
        # and generates a weakly authenticated principle.
50
        sender = self.factory.makePerson()
51
        email_message = self.factory.makeEmailMessage(sender=sender)
52
        msg = signed_message_from_string(email_message.as_string())
53
        self.assertIs(None, msg.signedContent)
54
        self.assertIs(None, msg.signature)
10856.2.29 by Stuart Bishop
Fix test_signedmessage tests
55
        principle = authenticateEmail(msg)
7675.60.10 by Tim Penhey
Add tests for signed message.
56
        self.assertEqual(sender, principle.person)
57
        self.assertTrue(
58
            IWeaklyAuthenticatedPrincipal.providedBy(principle))
59
        self.assertIs(None, msg.signature)
60
11658.1.1 by Benji York
add a fix and test for bug-612754
61
    def _get_clearsigned_for_person(self, sender, body=None):
7675.60.10 by Tim Penhey
Add tests for signed message.
62
        # Create a signed message for the sender specified with the test
63
        # secret key.
64
        key = import_secret_test_key()
65
        signing_context = GPGSigningContext(key.fingerprint, password='test')
11658.1.1 by Benji York
add a fix and test for bug-612754
66
        if body is None:
67
            body = dedent("""\
68
                This is a multi-line body.
7675.60.10 by Tim Penhey
Add tests for signed message.
69
11658.1.1 by Benji York
add a fix and test for bug-612754
70
                Sincerely,
71
                Your friendly tester.
72
                """)
7675.60.10 by Tim Penhey
Add tests for signed message.
73
        msg = self.factory.makeSignedMessage(
74
            email_address=sender.preferredemail.email,
75
            body=body, signing_context=signing_context)
76
        self.assertFalse(msg.is_multipart())
77
        return signed_message_from_string(msg.as_string())
78
79
    def test_clearsigned_message_wrong_sender(self):
80
        # If the message is signed, but the key doesn't belong to the sender,
81
        # the principle is set to the sender, but weakly authenticated.
82
        sender = self.factory.makePerson()
83
        msg = self._get_clearsigned_for_person(sender)
10856.2.29 by Stuart Bishop
Fix test_signedmessage tests
84
        principle = authenticateEmail(msg)
7675.60.10 by Tim Penhey
Add tests for signed message.
85
        self.assertIsNot(None, msg.signature)
86
        self.assertEqual(sender, principle.person)
87
        self.assertTrue(
88
            IWeaklyAuthenticatedPrincipal.providedBy(principle))
89
90
    def test_clearsigned_message(self):
91
        # The test keys belong to Sample Person.
92
        sender = getUtility(IPersonSet).getByEmail('test@canonical.com')
93
        msg = self._get_clearsigned_for_person(sender)
10856.2.29 by Stuart Bishop
Fix test_signedmessage tests
94
        principle = authenticateEmail(msg)
7675.60.10 by Tim Penhey
Add tests for signed message.
95
        self.assertIsNot(None, msg.signature)
96
        self.assertEqual(sender, principle.person)
97
        self.assertFalse(
98
            IWeaklyAuthenticatedPrincipal.providedBy(principle))
99
11658.1.1 by Benji York
add a fix and test for bug-612754
100
    def test_trailing_whitespace(self):
101
        # Trailing whitespace should be ignored when verifying a message's
102
        # signature.
103
        sender = getUtility(IPersonSet).getByEmail('test@canonical.com')
11658.1.2 by Benji York
tabs too
104
        body = (
13668.1.18 by Curtis Hovey
Hushed lint.
105
            'A message with trailing spaces.   \n'
106
            'And tabs\t\t\n'
11658.1.2 by Benji York
tabs too
107
            'Also mixed. \t ')
11658.1.1 by Benji York
add a fix and test for bug-612754
108
        msg = self._get_clearsigned_for_person(sender, body)
109
        principle = authenticateEmail(msg)
110
        self.assertIsNot(None, msg.signature)
111
        self.assertEqual(sender, principle.person)
112
        self.assertFalse(
113
            IWeaklyAuthenticatedPrincipal.providedBy(principle))
114
7675.60.10 by Tim Penhey
Add tests for signed message.
115
    def _get_detached_message_for_person(self, sender):
116
        # Return a signed message that contains a detached signature.
117
        body = dedent("""\
118
            This is a multi-line body.
119
120
            Sincerely,
121
            Your friendly tester.""")
122
        to = self.factory.getUniqueEmailAddress()
123
124
        msg = MIMEMultipart()
125
        msg['Message-Id'] = make_msgid('launchpad')
126
        msg['Date'] = formatdate()
127
        msg['To'] = to
128
        msg['From'] = sender.preferredemail.email
129
        msg['Subject'] = 'Sample'
130
131
        body_text = MIMEText(body)
132
        msg.attach(body_text)
133
        # A detached signature is calculated on the entire string content of
134
        # the body message part.
135
        key = import_secret_test_key()
136
        gpghandler = getUtility(IGPGHandler)
137
        signature = gpghandler.signContent(
138
            canonicalise_line_endings(body_text.as_string()),
139
            key.fingerprint, 'test', gpgme.SIG_MODE_DETACH)
140
141
        attachment = Message()
142
        attachment.set_payload(signature)
143
        attachment['Content-Type'] = 'application/pgp-signature'
144
        msg.attach(attachment)
145
        self.assertTrue(msg.is_multipart())
146
        return signed_message_from_string(msg.as_string())
147
148
    def test_detached_signature_message_wrong_sender(self):
149
        # If the message is signed, but the key doesn't belong to the sender,
150
        # the principle is set to the sender, but weakly authenticated.
151
        sender = self.factory.makePerson()
152
        msg = self._get_detached_message_for_person(sender)
10856.2.29 by Stuart Bishop
Fix test_signedmessage tests
153
        principle = authenticateEmail(msg)
7675.60.10 by Tim Penhey
Add tests for signed message.
154
        self.assertIsNot(None, msg.signature)
155
        self.assertEqual(sender, principle.person)
156
        self.assertTrue(
157
            IWeaklyAuthenticatedPrincipal.providedBy(principle))
158
159
    def test_detached_signature_message(self):
160
        # Test a detached correct signature.
161
        sender = getUtility(IPersonSet).getByEmail('test@canonical.com')
162
        msg = self._get_detached_message_for_person(sender)
10856.2.29 by Stuart Bishop
Fix test_signedmessage tests
163
        principle = authenticateEmail(msg)
7675.60.10 by Tim Penhey
Add tests for signed message.
164
        self.assertIsNot(None, msg.signature)
165
        self.assertEqual(sender, principle.person)
166
        self.assertFalse(
167
            IWeaklyAuthenticatedPrincipal.providedBy(principle))