Exemplo n.º 1
0
 def _process_file(self, file, shared_keys):
     if file['t'] == 0 or file['t'] == 1:
         keys = dict(
             keypart.split(':', 1) for keypart in file['k'].split('/')
             if ':' in keypart)
         uid = file['u']
         key = None
         # my objects
         if uid in keys:
             key = decrypt_key(base64_to_a32(keys[uid]), self.master_key)
         # shared folders
         elif 'su' in file and 'sk' in file and ':' in file['k']:
             shared_key = decrypt_key(base64_to_a32(file['sk']),
                                      self.master_key)
             key = decrypt_key(base64_to_a32(keys[file['h']]), shared_key)
             if file['su'] not in shared_keys:
                 shared_keys[file['su']] = {}
             shared_keys[file['su']][file['h']] = shared_key
         # shared files
         elif file['u'] and file['u'] in shared_keys:
             for hkey in shared_keys[file['u']]:
                 shared_key = shared_keys[file['u']][hkey]
                 if hkey in keys:
                     key = keys[hkey]
                     key = decrypt_key(base64_to_a32(key), shared_key)
                     break
         if file['h'] and file['h'] in shared_keys.get('EXP', ()):
             shared_key = shared_keys['EXP'][file['h']]
             encrypted_key = str_to_a32(
                 base64_url_decode(file['k'].split(':')[-1]))
             key = decrypt_key(encrypted_key, shared_key)
             file['shared_folder_key'] = shared_key
         if key is not None:
             # file
             if file['t'] == 0:
                 k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6],
                      key[3] ^ key[7])
                 file['iv'] = key[4:6] + (0, 0)
                 file['meta_mac'] = key[6:8]
             # folder
             else:
                 k = key
             file['key'] = key
             file['k'] = k
             attributes = base64_url_decode(file['a'])
             attributes = decrypt_attr(attributes, k)
             file['a'] = attributes
         # other => wrong object
         elif file['k'] == '':
             file['a'] = False
     elif file['t'] == 2:
         self.root_id = file['h']
         file['a'] = {'n': 'Cloud Drive'}
     elif file['t'] == 3:
         self.inbox_id = file['h']
         file['a'] = {'n': 'Inbox'}
     elif file['t'] == 4:
         self.trashbin_id = file['h']
         file['a'] = {'n': 'Rubbish Bin'}
     return file
Exemplo n.º 2
0
    def _login_process(self, resp, password):
        encrypted_master_key = base64_to_a32(resp['k'])
        self.master_key = decrypt_key(encrypted_master_key, password)
        if 'tsid' in resp:
            tsid = base64_url_decode(resp['tsid'])
            key_encrypted = a32_to_str(
                encrypt_key(str_to_a32(tsid[:16]), self.master_key))
            if key_encrypted == tsid[-16:]:
                self.sid = resp['tsid']
        elif 'csid' in resp:
            encrypted_rsa_private_key = base64_to_a32(resp['privk'])
            rsa_private_key = decrypt_key(encrypted_rsa_private_key,
                                          self.master_key)

            private_key = a32_to_str(rsa_private_key)
            self.rsa_private_key = [0, 0, 0, 0]
            for i in range(4):
                l = int(
                    ((private_key[0]) * 256 + (private_key[1]) + 7) / 8) + 2
                self.rsa_private_key[i] = mpi_to_int(private_key[:l])
                private_key = private_key[l:]

            encrypted_sid = mpi_to_int(base64_url_decode(resp['csid']))
            rsa_decrypter = RSA.construct(
                (self.rsa_private_key[0] * self.rsa_private_key[1], 257,
                 self.rsa_private_key[2], self.rsa_private_key[0],
                 self.rsa_private_key[1]))
            sid = '%x' % rsa_decrypter._decrypt(encrypted_sid)
            sid = binascii.unhexlify('0' + sid if len(sid) % 2 else sid)
            self.sid = base64_url_encode(sid[:43])
Exemplo n.º 3
0
 def get_upload_link(self, file):
     """
     Get a files public link inc. decrypted key
     Requires upload() response as input
     """
     if 'f' in file:
         file = file['f'][0]
         public_handle = self._api_request({'a': 'l', 'n': file['h']})
         file_key = file['k'][file['k'].index(':') + 1:]
         decrypted_key = a32_to_base64(
             decrypt_key(base64_to_a32(file_key), self.master_key))
         return (f'{self.schema}://{self.domain}'
                 f'/#!{public_handle}!{decrypted_key}')
     else:
         raise ValueError('''Upload() response required as input,
                         use get_link() for regular file input''')
Exemplo n.º 4
0
 def _init_shared_keys(self, files, shared_keys):
     """
     Init shared key not associated with a user.
     Seems to happen when a folder is shared,
     some files are exchanged and then the
     folder is un-shared.
     Keys are stored in files['s'] and files['ok']
     """
     ok_dict = {}
     for ok_item in files['ok']:
         shared_key = decrypt_key(base64_to_a32(ok_item['k']),
                                  self.master_key)
         ok_dict[ok_item['h']] = shared_key
     for s_item in files['s']:
         if s_item['u'] not in shared_keys:
             shared_keys[s_item['u']] = {}
         if s_item['h'] in ok_dict:
             shared_keys[s_item['u']][s_item['h']] = ok_dict[s_item['h']]
     self.shared_keys = shared_keys
Exemplo n.º 5
0
    def get_public_file_info(self, file_handle, file_key):
        """
        Get size and name of a public file
        """
        data = self._api_request({'a': 'g', 'p': file_handle, 'ssm': 1})
        if isinstance(data, int):
            raise RequestError(data)

        if 'at' not in data or 's' not in data:
            raise ValueError("Unexpected result", data)

        key = base64_to_a32(file_key)
        k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6],
             key[3] ^ key[7])

        size = data['s']
        unencrypted_attrs = decrypt_attr(base64_url_decode(data['at']), k)
        if not unencrypted_attrs:
            return None
        result = {'size': size, 'name': unencrypted_attrs['n']}
        return result
Exemplo n.º 6
0
    def import_public_file(self,
                           file_handle,
                           file_key,
                           dest_node=None,
                           dest_name=None):
        """
        Import the public file into user account
        """
        # Providing dest_node spare an API call to retrieve it.
        if dest_node is None:
            # Get '/Cloud Drive' folder no dest node specified
            dest_node = self.get_node_by_type(2)[1]

        # Providing dest_name spares an API call to retrieve it.
        if dest_name is None:
            pl_info = self.get_public_file_info(file_handle, file_key)
            dest_name = pl_info['name']

        key = base64_to_a32(file_key)
        k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6],
             key[3] ^ key[7])

        encrypted_key = a32_to_base64(encrypt_key(key, self.master_key))
        encrypted_name = base64_url_encode(encrypt_attr({'n': dest_name}, k))
        return self._api_request({
            'a':
            'p',
            't':
            dest_node['h'],
            'n': [{
                'ph': file_handle,
                't': 0,
                'a': encrypted_name,
                'k': encrypted_key
            }]
        })
Exemplo n.º 7
0
 def _login_user(self, email, password):
     logger.info('Logging in user...')
     email = email.lower()
     get_user_salt_resp = self._api_request({'a': 'us0', 'user': email})
     user_salt = None
     try:
         user_salt = base64_to_a32(get_user_salt_resp['s'])
     except KeyError:
         # v1 user account
         password_aes = prepare_key(str_to_a32(password))
         user_hash = stringhash(email, password_aes)
     else:
         # v2 user account
         pbkdf2_key = hashlib.pbkdf2_hmac(hash_name='sha512',
                                          password=password.encode(),
                                          salt=a32_to_str(user_salt),
                                          iterations=100000,
                                          dklen=32)
         password_aes = str_to_a32(pbkdf2_key[:16])
         user_hash = base64_url_encode(pbkdf2_key[-16:])
     resp = self._api_request({'a': 'us', 'user': email, 'uh': user_hash})
     if isinstance(resp, int):
         raise RequestError(resp)
     self._login_process(resp, password_aes)
Exemplo n.º 8
0
    def _download_file(self,
                       file_handle,
                       file_key,
                       dest_path=None,
                       dest_filename=None,
                       is_public=False,
                       file=None):
        if file is None:
            if is_public:
                file_key = base64_to_a32(file_key)
                file_data = self._api_request({
                    'a': 'g',
                    'g': 1,
                    'p': file_handle
                })
            else:
                file_data = self._api_request({
                    'a': 'g',
                    'g': 1,
                    'n': file_handle
                })

            k = (file_key[0] ^ file_key[4], file_key[1] ^ file_key[5],
                 file_key[2] ^ file_key[6], file_key[3] ^ file_key[7])
            iv = file_key[4:6] + (0, 0)
            meta_mac = file_key[6:8]
        else:
            file_data = self._api_request({'a': 'g', 'g': 1, 'n': file['h']})
            k = file['k']
            iv = file['iv']
            meta_mac = file['meta_mac']

        # Seems to happens sometime... When this occurs, files are
        # inaccessible also in the official also in the official web app.
        # Strangely, files can come back later.
        if 'g' not in file_data:
            raise RequestError('File not accessible anymore')
        file_url = file_data['g']
        file_size = file_data['s']
        attribs = base64_url_decode(file_data['at'])
        attribs = decrypt_attr(attribs, k)

        if dest_filename is not None:
            file_name = dest_filename
        else:
            file_name = attribs['n']

        input_file = requests.get(file_url, stream=True).raw

        if dest_path is None:
            dest_path = ''
        else:
            dest_path += '/'

        with tempfile.NamedTemporaryFile(mode='w+b',
                                         prefix='megapy_',
                                         delete=False) as temp_output_file:
            k_str = a32_to_str(k)
            counter = Counter.new(128,
                                  initial_value=((iv[0] << 32) + iv[1]) << 64)
            aes = AES.new(k_str, AES.MODE_CTR, counter=counter)

            mac_str = '\0' * 16
            mac_encryptor = AES.new(k_str, AES.MODE_CBC,
                                    mac_str.encode("utf8"))
            iv_str = a32_to_str([iv[0], iv[1], iv[0], iv[1]])

            for chunk_start, chunk_size in get_chunks(file_size):
                chunk = input_file.read(chunk_size)
                chunk = aes.decrypt(chunk)
                temp_output_file.write(chunk)

                encryptor = AES.new(k_str, AES.MODE_CBC, iv_str)
                for i in range(0, len(chunk) - 16, 16):
                    block = chunk[i:i + 16]
                    encryptor.encrypt(block)

                # fix for files under 16 bytes failing
                if file_size > 16:
                    i += 16
                else:
                    i = 0

                block = chunk[i:i + 16]
                if len(block) % 16:
                    block += b'\0' * (16 - (len(block) % 16))
                mac_str = mac_encryptor.encrypt(encryptor.encrypt(block))

                file_info = os.stat(temp_output_file.name)
                logger.info('%s of %s downloaded', file_info.st_size,
                            file_size)
            file_mac = str_to_a32(mac_str)
            # check mac integrity
            if (file_mac[0] ^ file_mac[1],
                    file_mac[2] ^ file_mac[3]) != meta_mac:
                raise ValueError('Mismatched mac')
            output_path = Path(dest_path + file_name)
            shutil.move(temp_output_file.name, output_path)
            return output_path