def ensure_not_weakly_authenticated(signed_msg, context, error_template='not-signed.txt'): """Make sure that the current principal is not weakly authenticated. NB: While handling an email, the authentication state is stored partly in properties of the message object, and partly in the current security principal. As a consequence this function will only work correctly if the message has just been passed through authenticateEmail -- you can't give it an arbitrary message object. """ cur_principal = get_current_principal() # The security machinery doesn't know about # IWeaklyAuthenticatedPrincipal yet, so do a manual # check. Later we can rely on the security machinery to # cause Unauthorized errors. if IWeaklyAuthenticatedPrincipal.providedBy(cur_principal): if signed_msg.signature is None: error_message = get_error_message( NOT_SIGNED_TEMPLATE, None, context=context) else: import_url = canonical_url( getUtility(ILaunchBag).user) + '/+editpgpkeys' error_message = get_error_message( NO_KEY_TEMPLATE, None, import_url=import_url, context=context) raise IncomingEmailError(error_message)
def test_clearsigned_message(self): # The test keys belong to Sample Person. sender = getUtility(IPersonSet).getByEmail('*****@*****.**') msg = self._get_clearsigned_for_person(sender) principle = authenticateEmail(msg) self.assertIsNot(None, msg.signature) self.assertEqual(sender, principle.person) self.assertFalse(IWeaklyAuthenticatedPrincipal.providedBy(principle))
def test_detached_signature_message(self): # Test a detached correct signature. sender = getUtility(IPersonSet).getByEmail('*****@*****.**') msg = self._get_detached_message_for_person(sender) principle = authenticateEmail(msg) self.assertIsNot(None, msg.signature) self.assertEqual(sender, principle.person) self.assertFalse(IWeaklyAuthenticatedPrincipal.providedBy(principle))
def test_require_strong_email_authentication_and_signed(self): sender = getUtility(IPersonSet).getByEmail('*****@*****.**') sender.require_strong_email_authentication = True msg = self._get_clearsigned_for_person(sender) principal = authenticateEmail(msg) self.assertIsNot(None, msg.signature) self.assertEqual(sender, principal.person) self.assertFalse(IWeaklyAuthenticatedPrincipal.providedBy(principal))
def test_clearsigned_message_wrong_sender(self): # If the message is signed, but the key doesn't belong to the sender, # the principle is set to the sender, but weakly authenticated. sender = self.factory.makePerson() msg = self._get_clearsigned_for_person(sender) principle = authenticateEmail(msg) self.assertIsNot(None, msg.signature) self.assertEqual(sender, principle.person) self.assertTrue(IWeaklyAuthenticatedPrincipal.providedBy(principle))
def test_clearsigned_message(self): # The test keys belong to Sample Person. sender = getUtility(IPersonSet).getByEmail('*****@*****.**') msg = self._get_clearsigned_for_person(sender) principle = authenticateEmail(msg) self.assertIsNot(None, msg.signature) self.assertEqual(sender, principle.person) self.assertFalse( IWeaklyAuthenticatedPrincipal.providedBy(principle))
def test_detached_signature_message(self): # Test a detached correct signature. sender = getUtility(IPersonSet).getByEmail('*****@*****.**') msg = self._get_detached_message_for_person(sender) principle = authenticateEmail(msg) self.assertIsNot(None, msg.signature) self.assertEqual(sender, principle.person) self.assertFalse( IWeaklyAuthenticatedPrincipal.providedBy(principle))
def test_clearsigned_message_wrong_sender(self): # If the message is signed, but the key doesn't belong to the sender, # the principle is set to the sender, but weakly authenticated. sender = self.factory.makePerson() msg = self._get_clearsigned_for_person(sender) principle = authenticateEmail(msg) self.assertIsNot(None, msg.signature) self.assertEqual(sender, principle.person) self.assertTrue( IWeaklyAuthenticatedPrincipal.providedBy(principle))
def test_trailing_whitespace(self): # Trailing whitespace should be ignored when verifying a message's # signature. sender = getUtility(IPersonSet).getByEmail('*****@*****.**') body = ('A message with trailing spaces. \n' 'And tabs\t\t\n' 'Also mixed. \t ') msg = self._get_clearsigned_for_person(sender, body) principle = authenticateEmail(msg) self.assertIsNot(None, msg.signature) self.assertEqual(sender, principle.person) self.assertFalse(IWeaklyAuthenticatedPrincipal.providedBy(principle))
def test_unsigned_message(self): # An unsigned message will not have a signature nor signed content, # and generates a weakly authenticated principle. sender = self.factory.makePerson() email_message = self.factory.makeEmailMessage(sender=sender) msg = signed_message_from_string(email_message.as_string()) self.assertIs(None, msg.signedContent) self.assertIs(None, msg.signature) principle = authenticateEmail(msg) self.assertEqual(sender, principle.person) self.assertTrue(IWeaklyAuthenticatedPrincipal.providedBy(principle)) self.assertIs(None, msg.signature)
def authenticateEmail(mail, signature_timestamp_checker=None): """Authenticates an email by verifying the PGP signature. The mail is expected to be an ISignedMessage. If this completes, it will set the current security principal to be the message sender. :param signature_timestamp_checker: This callable is passed the message signature timestamp, and it can raise an exception if it dislikes it (for example as a replay attack.) This parameter is intended for use in tests. If None, ensure_sane_signature_timestamp is used. """ log = logging.getLogger('process-mail') authutil = getUtility(IPlacelessAuthUtility) principal, dkim_trusted_address = _getPrincipalByDkim(mail) if dkim_trusted_address is None: from_addr = parseaddr(mail['From'])[1] try: principal = authutil.getPrincipalByLogin(from_addr) except TypeError: # The email isn't valid, so don't authenticate principal = None if principal is None: setupInteraction(authutil.unauthenticatedPrincipal()) return None person = IPerson(principal, None) if person.account_status != AccountStatus.ACTIVE: raise InactiveAccount("Mail from a user with an inactive account.") if dkim_trusted_address: log.debug('accepting dkim strongly authenticated mail') setupInteraction(principal, dkim_trusted_address) else: log.debug("attempt gpg authentication for %r" % person) principal = _gpgAuthenticateEmail(mail, principal, person, signature_timestamp_checker) if (IWeaklyAuthenticatedPrincipal.providedBy(principal) and person.require_strong_email_authentication): import_url = canonical_url(getUtility(ILaunchBag).user, view_name='+editpgpkeys') error_message = get_error_message('person-requires-signature.txt', import_url=import_url) raise IncomingEmailError(error_message) return principal
def test_unsigned_message(self): # An unsigned message will not have a signature nor signed content, # and generates a weakly authenticated principle. sender = self.factory.makePerson() email_message = self.factory.makeEmailMessage(sender=sender) msg = signed_message_from_string(email_message.as_string()) self.assertIs(None, msg.signedContent) self.assertIs(None, msg.signature) principle = authenticateEmail(msg) self.assertEqual(sender, principle.person) self.assertTrue( IWeaklyAuthenticatedPrincipal.providedBy(principle)) self.assertIs(None, msg.signature)
def test_trailing_whitespace(self): # Trailing whitespace should be ignored when verifying a message's # signature. sender = getUtility(IPersonSet).getByEmail('*****@*****.**') body = ( 'A message with trailing spaces. \n' 'And tabs\t\t\n' 'Also mixed. \t ') msg = self._get_clearsigned_for_person(sender, body) principle = authenticateEmail(msg) self.assertIsNot(None, msg.signature) self.assertEqual(sender, principle.person) self.assertFalse( IWeaklyAuthenticatedPrincipal.providedBy(principle))
def assertWeaklyAuthenticated(self, principal, signed_message): if not IWeaklyAuthenticatedPrincipal.providedBy(principal): self.fail('expected weak authentication; got strong:\n' + self.get_dkim_log() + '\n\n' + signed_message)