Exemple #1
0
    def _read_data(self, data):
        def process(data):
            if self.decryptor is not None:
                eof = not data
                if self.decoder_data_bytes and data:
                    self.buffered += ''.join([c for c in data if c not in
                                              (' ', '\t', '\r', '\n')])
                else:
                    self.buffered += (data or '')
                data = ''
                for i in (256, 64, 8, 1):
                    batch = max(1, i * self.decoder_data_bytes)
                    while eof or (len(self.buffered) >= batch):
                        if self.decoder_data_bytes:
                            d = self.buffered[:batch]
                            b = self.buffered[batch:]
                            self.buffered = b
                        else:
                            d, self.buffered = self.buffered, ''
                        try:
                            data += self.decryptor(self.decoder(d))
                            eof = False
                        except TypeError:
                            raise IOError('%s: Bad data, failed to decode'
                                          % self.name)
            return (data or '')

        if data is None:
            # EOF!
            if self.state in (self.STATE_BEGIN, self.STATE_HEADER):
                self.state = self.STATE_RAW_DATA
                self.startup_lock.release()
                data, self.buffered = self.buffered, ''
                return process(data) + process(None)
            return process(None)

        if self.expected_outer_sha256:
            # The outer MD5 sum is calculated over all data, but with any
            # CRLF sequences normalized to only LF and the sha256 header
            # itself replaced with a placeholder.
            if self.state in (self.STATE_BEGIN, self.STATE_HEADER):
                sum_data = re.sub(MD5_SUM_RE, MD5_SUM_PLACEHOLDER,
                                  re.sub(SHA_256_RE, SHA_256_PLACEHOLDER, data))
            else:
                sum_data = data
            sum_data = sum_data.replace('\r', '').replace('\n', '\r\n')
            self.outer_sha.update(sum_data)

        if self.state in (
               self.STATE_RAW_DATA, self.STATE_PGP_DATA, self.STATE_ONLY_DATA):
            return process(data)

        if self.state == self.STATE_BEGIN:
            self.buffered += data
            if (len(self.buffered) >= len(self.BEGIN_PGP)
                    and self.buffered.startswith(self.BEGIN_PGP)):
                self.state = self.STATE_PGP_DATA
                if self.gpg_pass:
                    self.gpg_pass.seek(0, 0)
                    passphrase, c = [], self.gpg_pass.read(1)
                    while c != '':
                        passphrase.append(c)
                        c = self.gpg_pass.read(1)
                    self.startup_lock.release()
                    return ''.join(passphrase + ['\n', self.buffered])
                else:
                    self.startup_lock.release()
                    return self.buffered

            # Note: The max() check is OK, because both formats add more
            #       data which covers the difference.
            if len(self.buffered) >= max(len(self.BEGIN_MED),
                                         len(self.BEGIN_MED2)):
                if not (self.buffered.startswith(self.BEGIN_MED) or
                        self.buffered.startswith(self.BEGIN_MED2)):
                    self.state = self.STATE_RAW_DATA
                    self.startup_lock.release()
                    return self.buffered
                if '\r\n\r\n' in self.buffered:
                    header, data = self.buffered.split('\r\n\r\n', 1)
                    headlines = header.strip().split('\r\n')
                    self.state = self.STATE_HEADER
                elif '\n\n' in self.buffered:
                    header, data = self.buffered.split('\n\n', 1)
                    headlines = header.strip().split('\n')
                    self.state = self.STATE_HEADER
                else:
                    return ''
            else:
                return ''

        if self.state == self.STATE_HEADER:
            # State: header and data have been set, header is complete.
            self.buffered = ''

            headers = dict([l.split(': ', 1) for l in headlines if ': ' in l])
            nonce = headers.get('nonce', '')
            self.mep_mutated = self._mutate_key(self.mep_key, nonce)
            self.mep_version = headers.get('X-Mailpile-Encrypted-Data', 'v1')
            self.cipher = headers.get('cipher', self.cipher)

            data_fmt = '%s:%s' % (self.mep_version, self.cipher)
            if data_fmt != PREFERRED_FORMAT:
                DETECTED_OBSOLETE_FORMATS.add(data_fmt)

            eim = self.expected_inner_md5sum = headers.get('md5sum')
            if eim and eim == ('0' * LEN_MD5_SUM):
                self.expected_inner_md5sum = None
            if self.expected_inner_md5sum:
                self.inner_md5.update(self.mep_mutated)
                self.inner_md5.update(nonce)

            eis = self.expected_inner_sha256 = headers.get('sha256')
            if eis and eis == ('0' * LEN_SHA_256):
                self.expected_inner_sha256 = None
            if self.expected_inner_sha256:
                self.inner_sha.update(self.mep_mutated)
                self.inner_sha.update(nonce)

            if self.buffered.startswith(self.BEGIN_MED2):
                self.state = self.STATE_ONLY_DATA
            else:
                self.state = self.STATE_DATA

            if self.cipher == 'aes-128-ctr':
                self.decryptor = aes_ctr_decryptor(self.mep_mutated, nonce)
                self.decoder = base64.b64decode
                # Decode data in chunks this big (multiple of 4 and 16);
                # guarantees workable chunks for both AES-CTR and base64.
                self.decoder_data_bytes = 32 * 1024
            elif self.cipher == 'none':
                self.decryptor = lambda d: d
                self.decoder = base64.b64decode
                self.decoder_data_bytes = 32 * 1024
            elif self.cipher == 'broken':
                self.decryptor = lambda d: d
                self.decoder = lambda d: d
                self.expected_inner_md5sum = None
                self.expected_inner_sha256 = None
            else:
                self.decryptor = None
                data = '\n'.join((self.mep_mutated, data))

            self.startup_lock.release()

        if self.state == self.STATE_ONLY_DATA:
            return process(data)

        if self.state == self.STATE_DATA:
            for delim in (self.END_MED, self.END_PGP):
                if delim in data:
                    for pf in ('\r\n', '\n', ''):
                        if pf + delim in data:
                            data = data.split(pf + delim, 1)[0]
                            self.state = self.STATE_END
                            return process(data)
            return process(data)

        # Error, end and unknown states...
        return ''
Exemple #2
0
    def _read_data(self, data):
        def process(data):
            if self.decryptor is not None:
                eof = not data
                if self.decoder_data_bytes and data:
                    self.buffered += ''.join(
                        [c for c in data if c not in (' ', '\t', '\r', '\n')])
                else:
                    self.buffered += (data or '')
                data = ''
                for i in (256, 64, 8, 1):
                    batch = max(1, i * self.decoder_data_bytes)
                    while eof or (len(self.buffered) >= batch):
                        if self.decoder_data_bytes:
                            d = self.buffered[:batch]
                            b = self.buffered[batch:]
                            self.buffered = b
                        else:
                            d, self.buffered = self.buffered, ''
                        try:
                            data += self.decryptor(self.decoder(d))
                            eof = False
                        except TypeError:
                            raise IOError('%s: Bad data, failed to decode' %
                                          self.name)
            return (data or '')

        if data is None:
            # EOF!
            if self.state in (self.STATE_BEGIN, self.STATE_HEADER):
                self.state = self.STATE_RAW_DATA
                self.startup_lock.release()
                data, self.buffered = self.buffered, ''
                return process(data) + process(None)
            return process(None)

        if self.expected_outer_sha256:
            # The outer MD5 sum is calculated over all data, but with any
            # CRLF sequences normalized to only LF and the sha256 header
            # itself replaced with a placeholder.
            if self.state in (self.STATE_BEGIN, self.STATE_HEADER):
                sum_data = re.sub(
                    MD5_SUM_RE, MD5_SUM_PLACEHOLDER,
                    re.sub(SHA_256_RE, SHA_256_PLACEHOLDER, data))
            else:
                sum_data = data
            sum_data = sum_data.replace('\r', '').replace('\n', '\r\n')
            self.outer_sha.update(sum_data)

        if self.state in (self.STATE_RAW_DATA, self.STATE_PGP_DATA,
                          self.STATE_ONLY_DATA):
            return process(data)

        if self.state == self.STATE_BEGIN:
            self.buffered += data
            if (len(self.buffered) >= len(self.BEGIN_PGP)
                    and self.buffered.startswith(self.BEGIN_PGP)):
                self.state = self.STATE_PGP_DATA
                if self.gpg_pass:
                    self.gpg_pass.seek(0, 0)
                    passphrase, c = [], self.gpg_pass.read(1)
                    while c != '':
                        passphrase.append(c)
                        c = self.gpg_pass.read(1)
                    self.startup_lock.release()
                    return ''.join(passphrase + ['\n', self.buffered])
                else:
                    self.startup_lock.release()
                    return self.buffered

            # Note: The max() check is OK, because both formats add more
            #       data which covers the difference.
            if len(self.buffered) >= max(len(self.BEGIN_MED),
                                         len(self.BEGIN_MED2)):
                if not (self.buffered.startswith(self.BEGIN_MED)
                        or self.buffered.startswith(self.BEGIN_MED2)):
                    self.state = self.STATE_RAW_DATA
                    self.startup_lock.release()
                    return self.buffered
                if '\r\n\r\n' in self.buffered:
                    header, data = self.buffered.split('\r\n\r\n', 1)
                    headlines = header.strip().split('\r\n')
                    self.state = self.STATE_HEADER
                elif '\n\n' in self.buffered:
                    header, data = self.buffered.split('\n\n', 1)
                    headlines = header.strip().split('\n')
                    self.state = self.STATE_HEADER
                else:
                    return ''
            else:
                return ''

        if self.state == self.STATE_HEADER:
            # State: header and data have been set, header is complete.
            self.buffered = ''

            headers = dict([l.split(': ', 1) for l in headlines if ': ' in l])
            nonce = headers.get('nonce', '')
            self.mep_mutated = self._mutate_key(self.mep_key, nonce)
            self.mep_version = headers.get('X-Mailpile-Encrypted-Data', 'v1')
            self.cipher = headers.get('cipher', self.cipher)

            data_fmt = '%s:%s' % (self.mep_version, self.cipher)
            if data_fmt != PREFERRED_FORMAT:
                DETECTED_OBSOLETE_FORMATS.add(data_fmt)

            eim = self.expected_inner_md5sum = headers.get('md5sum')
            if eim and eim == ('0' * LEN_MD5_SUM):
                self.expected_inner_md5sum = None
            if self.expected_inner_md5sum:
                self.inner_md5.update(self.mep_mutated)
                self.inner_md5.update(nonce)

            eis = self.expected_inner_sha256 = headers.get('sha256')
            if eis and eis == ('0' * LEN_SHA_256):
                self.expected_inner_sha256 = None
            if self.expected_inner_sha256:
                self.inner_sha.update(self.mep_mutated)
                self.inner_sha.update(nonce)

            if self.buffered.startswith(self.BEGIN_MED2):
                self.state = self.STATE_ONLY_DATA
            else:
                self.state = self.STATE_DATA

            if self.cipher == 'aes-128-ctr':
                self.decryptor = aes_ctr_decryptor(self.mep_mutated, nonce)
                self.decoder = base64.b64decode
                # Decode data in chunks this big (multiple of 4 and 16);
                # guarantees workable chunks for both AES-CTR and base64.
                self.decoder_data_bytes = 32 * 1024
            elif self.cipher == 'none':
                self.decryptor = lambda d: d
                self.decoder = base64.b64decode
                self.decoder_data_bytes = 32 * 1024
            elif self.cipher == 'broken':
                self.decryptor = lambda d: d
                self.decoder = lambda d: d
                self.expected_inner_md5sum = None
                self.expected_inner_sha256 = None
            else:
                self.decryptor = None
                data = '\n'.join((self.mep_mutated, data))

            self.startup_lock.release()

        if self.state == self.STATE_ONLY_DATA:
            return process(data)

        if self.state == self.STATE_DATA:
            for delim in (self.END_MED, self.END_PGP):
                if delim in data:
                    for pf in ('\r\n', '\n', ''):
                        if pf + delim in data:
                            data = data.split(pf + delim, 1)[0]
                            self.state = self.STATE_END
                            return process(data)
            return process(data)

        # Error, end and unknown states...
        return ''