diff options
Diffstat (limited to 'src/leap/mail/service.py')
| -rw-r--r-- | src/leap/mail/service.py | 201 | 
1 files changed, 119 insertions, 82 deletions
diff --git a/src/leap/mail/service.py b/src/leap/mail/service.py index f6e4d11..a99f13a 100644 --- a/src/leap/mail/service.py +++ b/src/leap/mail/service.py @@ -24,7 +24,6 @@ from OpenSSL import SSL  from twisted.mail import smtp  from twisted.internet import reactor  from twisted.internet import defer -from twisted.internet.threads import deferToThread  from twisted.protocols.amp import ssl  from twisted.python import log @@ -111,17 +110,17 @@ class OutgoingMail:          :type recipient: smtp.User          :return: a deferred which delivers the message when fired          """ -        d = deferToThread(lambda: self._maybe_encrypt_and_sign(raw, recipient)) +        d = self._maybe_encrypt_and_sign(raw, recipient)          d.addCallback(self._route_msg)          d.addErrback(self.sendError) -          return d      def sendSuccess(self, smtp_sender_result):          """          Callback for a successful send. -        :param smtp_sender_result: The result from the ESMTPSender from _route_msg +        :param smtp_sender_result: The result from the ESMTPSender from +                                   _route_msg          :type smtp_sender_result: tuple(int, list(tuple))          """          dest_addrstr = smtp_sender_result[1][0][0] @@ -145,7 +144,8 @@ class OutgoingMail:          """          Sends the msg using the ESMTPSenderFactory. -        :param encrypt_and_sign_result: A tuple containing the 'maybe' encrypted message and the recipient +        :param encrypt_and_sign_result: A tuple containing the 'maybe' +                                        encrypted message and the recipient          :type encrypt_and_sign_result: tuple          """          message, recipient = encrypt_and_sign_result @@ -173,7 +173,6 @@ class OutgoingMail:              self._host, self._port, factory,              contextFactory=SSLContextFactory(self._cert, self._key)) -      def _maybe_encrypt_and_sign(self, raw, recipient):          """          Attempt to encrypt and sign the outgoing message. @@ -209,16 +208,20 @@ class OutgoingMail:          :param recipient: The recipient for the message          :type: recipient: smtp.User +        :return: A Deferred that will be fired with a MIMEMultipart message +                 and the original recipient Message +        :rtype: Deferred          """          # pass if the original message's content-type is "multipart/encrypted"          lines = raw.split('\r\n')          origmsg = Parser().parsestr(raw)          if origmsg.get_content_type() == 'multipart/encrypted': -            return origmsg +            return defer.success((origmsg, recipient))          from_address = validate_address(self._from_address)          username, domain = from_address.split('@') +        to_address = validate_address(recipient.dest.addrstr)          # add a nice footer to the outgoing message          # XXX: footer will eventually optional or be removed @@ -230,80 +233,93 @@ class OutgoingMail:          origmsg = Parser().parsestr('\r\n'.join(lines)) -        # get sender and recipient data -        signkey = self._keymanager.get_key(from_address, OpenPGPKey, private=True) -        log.msg("Will sign the message with %s." % signkey.fingerprint) -        to_address = validate_address(recipient.dest.addrstr) -        try: -            # try to get the recipient pubkey -            pubkey = self._keymanager.get_key(to_address, OpenPGPKey) -            log.msg("Will encrypt the message to %s." % pubkey.fingerprint) -            signal(proto.SMTP_START_ENCRYPT_AND_SIGN, -                   "%s,%s" % (self._from_address, to_address)) -            newmsg = self._encrypt_and_sign(origmsg, pubkey, signkey) - +        def signal_encrypt_sign(newmsg):              signal(proto.SMTP_END_ENCRYPT_AND_SIGN,                     "%s,%s" % (self._from_address, to_address)) -        except KeyNotFound: -            # at this point we _can_ send unencrypted mail, because if the -            # configuration said the opposite the address would have been -            # rejected in SMTPDelivery.validateTo(). -            log.msg('Will send unencrypted message to %s.' % to_address) -            signal(proto.SMTP_START_SIGN, self._from_address) -            newmsg = self._sign(origmsg, signkey) -            signal(proto.SMTP_END_SIGN, self._from_address) -        return newmsg, recipient +            return newmsg, recipient +        def signal_sign(newmsg): +            signal(proto.SMTP_END_SIGN, self._from_address) +            return newmsg, recipient + +        def if_key_not_found_send_unencrypted(failure): +            if failure.check(KeyNotFound): +                log.msg('Will send unencrypted message to %s.' % to_address) +                signal(proto.SMTP_START_SIGN, self._from_address) +                d = self._sign(origmsg, from_address) +                d.addCallback(signal_sign) +                return d +            else: +                return failure + +        log.msg("Will encrypt the message with %s and sign with %s." +                % (to_address, from_address)) +        signal(proto.SMTP_START_ENCRYPT_AND_SIGN, +               "%s,%s" % (self._from_address, to_address)) +        d = self._encrypt_and_sign(origmsg, to_address, from_address) +        d.addCallbacks(signal_encrypt_sign, if_key_not_found_send_unencrypted) +        return d -    def _encrypt_and_sign(self, origmsg, pubkey, signkey): +    def _encrypt_and_sign(self, origmsg, encrypt_address, sign_address):          """          Create an RFC 3156 compliang PGP encrypted and signed message using -        C{pubkey} to encrypt and C{signkey} to sign. +        C{encrypt_address} to encrypt and C{sign_address} to sign.          :param origmsg: The original message          :type origmsg: email.message.Message -        :param pubkey: The public key used to encrypt the message. -        :type pubkey: OpenPGPKey -        :param signkey: The private key used to sign the message. -        :type signkey: OpenPGPKey -        :return: The encrypted and signed message -        :rtype: MultipartEncrypted +        :param encrypt_address: The address used to encrypt the message. +        :type encrypt_address: str +        :param sign_address: The address used to sign the message. +        :type sign_address: str + +        :return: A Deferred with the MultipartEncrypted message +        :rtype: Deferred          """          # create new multipart/encrypted message with 'pgp-encrypted' protocol -        newmsg = MultipartEncrypted('application/pgp-encrypted') -        # move (almost) all headers from original message to the new message -        self._fix_headers(origmsg, newmsg, signkey) -        # create 'application/octet-stream' encrypted message -        encmsg = MIMEApplication( -            self._keymanager.encrypt(origmsg.as_string(unixfrom=False), pubkey, -                                     sign=signkey), -            _subtype='octet-stream', _encoder=lambda x: x) -        encmsg.add_header('content-disposition', 'attachment', -                          filename='msg.asc') -        # create meta message -        metamsg = PGPEncrypted() -        metamsg.add_header('Content-Disposition', 'attachment') -        # attach pgp message parts to new message -        newmsg.attach(metamsg) -        newmsg.attach(encmsg) -        return newmsg - - -    def _sign(self, origmsg, signkey): + +        def encrypt(res): +            newmsg, origmsg = res +            d = self._keymanager.encrypt( +                origmsg.as_string(unixfrom=False), +                encrypt_address, OpenPGPKey, sign=sign_address) +            d.addCallback(lambda encstr: (newmsg, encstr)) +            return d + +        def create_encrypted_message(res): +            newmsg, encstr = res +            encmsg = MIMEApplication( +                encstr, _subtype='octet-stream', _encoder=lambda x: x) +            encmsg.add_header('content-disposition', 'attachment', +                              filename='msg.asc') +            # create meta message +            metamsg = PGPEncrypted() +            metamsg.add_header('Content-Disposition', 'attachment') +            # attach pgp message parts to new message +            newmsg.attach(metamsg) +            newmsg.attach(encmsg) +            return newmsg + +        d = self._fix_headers( +            origmsg, +            MultipartEncrypted('application/pgp-encrypted'), +            sign_address) +        d.addCallback(encrypt) +        d.addCallback(create_encrypted_message) +        return d + +    def _sign(self, origmsg, sign_address):          """ -        Create an RFC 3156 compliant PGP signed MIME message using C{signkey}. +        Create an RFC 3156 compliant PGP signed MIME message using +        C{sign_address}.          :param origmsg: The original message          :type origmsg: email.message.Message -        :param signkey: The private key used to sign the message. -        :type signkey: leap.common.keymanager.openpgp.OpenPGPKey -        :return: The signed message. -        :rtype: MultipartSigned +        :param sign_address: The address used to sign the message. +        :type sign_address: str + +        :return: A Deferred with the MultipartSigned message. +        :rtype: Deferred          """ -        # create new multipart/signed message -        newmsg = MultipartSigned('application/pgp-signature', 'pgp-sha512') -        # move (almost) all headers from original message to the new message -        self._fix_headers(origmsg, newmsg, signkey)          # apply base64 content-transfer-encoding          encode_base64_rec(origmsg)          # get message text with headers and replace \n for \r\n @@ -316,17 +332,27 @@ class OutgoingMail:          if origmsg.is_multipart():              if not msgtext.endswith("\r\n"):                  msgtext += "\r\n" -        # calculate signature -        signature = self._keymanager.sign(msgtext, signkey, digest_algo='SHA512', -                                          clearsign=False, detach=True, binary=False) -        sigmsg = PGPSignature(signature) -        # attach original message and signature to new message -        newmsg.attach(origmsg) -        newmsg.attach(sigmsg) -        return newmsg +        def create_signed_message(res): +            (msg, _), signature = res +            sigmsg = PGPSignature(signature) +            # attach original message and signature to new message +            msg.attach(origmsg) +            msg.attach(sigmsg) +            return msg + +        dh = self._fix_headers( +            origmsg, +            MultipartSigned('application/pgp-signature', 'pgp-sha512'), +            sign_address) +        ds = self._keymanager.sign( +            msgtext, sign_address, OpenPGPKey, digest_algo='SHA512', +            clearsign=False, detach=True, binary=False) +        d = defer.gatherResults([dh, ds]) +        d.addCallback(create_signed_message) +        return d -    def _fix_headers(self, origmsg, newmsg, signkey): +    def _fix_headers(self, origmsg, newmsg, sign_address):          """          Move some headers from C{origmsg} to C{newmsg}, delete unwanted          headers from C{origmsg} and add new headers to C{newms}. @@ -360,8 +386,13 @@ class OutgoingMail:          :type origmsg: email.message.Message          :param newmsg: The new message being created.          :type newmsg: email.message.Message -        :param signkey: The key used to sign C{newmsg} -        :type signkey: OpenPGPKey +        :param sign_address: The address used to sign C{newmsg} +        :type sign_address: str + +        :return: A Deferred with a touple: +                 (new Message with the unencrypted headers, +                  original Message with headers removed) +        :rtype: Deferred          """          # move headers from origmsg to newmsg          headers = origmsg.items() @@ -375,11 +406,17 @@ class OutgoingMail:              del (origmsg[hkey])          # add a new message-id to newmsg          newmsg.add_header('Message-Id', smtp.messageid()) -        # add openpgp header to newmsg -        username, domain = signkey.address.split('@') -        newmsg.add_header( -            'OpenPGP', 'id=%s' % signkey.key_id, -            url='https://%s/key/%s' % (domain, username), -            preference='signencrypt')          # delete user-agent from origmsg          del (origmsg['user-agent']) + +        def add_openpgp_header(signkey): +            username, domain = sign_address.split('@') +            newmsg.add_header( +                'OpenPGP', 'id=%s' % signkey.key_id, +                url='https://%s/key/%s' % (domain, username), +                preference='signencrypt') +            return newmsg, origmsg + +        d = self._keymanager.get_key(sign_address, OpenPGPKey, private=True) +        d.addCallback(add_openpgp_header) +        return d  | 
