예제 #1
0
class SecureDropbox(object):
    def __init__(self, username, password, dropbox_folder_path):
        self.user = User(username, password)
        self.dropbox_handler = None
        self.KMS_handler = None
        self.cryptor = None
        self.dropbox_folder_path = dropbox_folder_path
        self.secure_dropbox_folder_path = self.dropbox_folder_path + r'\Secure Dropbox'
        self.ini_file = self.secure_dropbox_folder_path + os.path.sep + self.user.username + CONFIG.INI_FILE

    def initialize(self):

        self.dropbox_handler = DropboxHandler()
        self.cryptor = Cryptor()
        self.KMS_handler = KMS_Handler(self.cryptor)
        if self.login():
            with open(self.ini_file, 'w') as ini_file:
                pickle.dump(self.user, ini_file, True)
            return True
        else:
            return False

    def share_file(self, user_to_share_with, file_to_share):

        sharing_info = self.dropbox_handler.client.media(self.path_local_2_dropbox(file_to_share))

        url = sharing_info['url']
        expires = sharing_info['expires']
        doc_id = os.path.basename(file_to_share)

        receipient_RSA_pub_key = self.KMS_handler.download_sharing_recipient_RSA_pub_key(self.user.username, self.user.password, self.user.token, user_to_share_with).read()
        if receipient_RSA_pub_key:
            doc_key = self.cryptor.RSA_encryptor(self.user.doc_keychain[doc_id], receipient_RSA_pub_key)
        else:
            doc_key = None

        if doc_key:
            return self.KMS_handler.share_file(self.user.username, self.user.password, self.user.token, user_to_share_with, doc_id, doc_key, url, expires)
        else:
            return False
    def shared_file(self):
        return self.KMS_handler.shared_file(self.user.username, self.user.password, self.user.token)

    def login(self):
        self.user.token, self.user.RSA_pub_key, self.user.RSA_priv_key, self.user.doc_keychain = \
            self.KMS_handler.login(self.user.username, self.user.password)

        if self.user.token and self.user.token != CONFIG.INVALID_USER_INCORRECT_INFO:
            # if valid user then decrypt the encrypted RSA private key with user's password through pbkdf2
            
            print self.user.RSA_priv_key
            
            self.user.RSA_priv_key = \
             self.cryptor.AES_decryptor(self.user.RSA_priv_key, self.cryptor.pad_AES_key(self.user.password))
             
            # decrypt the doc_keys with RSA private key
            print self.user.doc_keychain
            if len(self.user.doc_keychain) != 0:
                for key in self.user.doc_keychain.keys():
                    self.user.doc_keychain[key] = \
                        self.cryptor.RSA_decryptor(self.user.doc_keychain[key], self.user.RSA_priv_key)
            return True
        else:
            return False

    def register(self, username, password):
        
        private_key_path = '%s_priv.pem' % (username)
        f = open(private_key_path, 'w')
        f.close()
        public_key_path = '%s_pub.pem' % (username)
        f = open(public_key_path, 'w')
        f.close()
        
        print 'register :: generating RSA keys...'
        M2Crypto.Rand.rand_seed (os.urandom (1024))
        RSA_key = M2Crypto.RSA.gen_key (1024, 65537)
        RSA_key.save_key (private_key_path, None)
        RSA_key.save_pub_key (public_key_path)
        
        f = open(public_key_path)
        RSA_public_key = f.read()
        f.close()
        os.remove(public_key_path)
        
        f = open(private_key_path)
        RSA_private_key = f.read()
        f.close()
        os.remove(private_key_path)
        
        RSA_private_key = self.cryptor.AES_encryptor(RSA_private_key, self.cryptor.pad_AES_key(password))
        
        return self.KMS_handler.register(username, password, RSA_public_key, RSA_private_key)

    def show_file_list(self):

        file_list = []

        local_file_list = os.listdir(self.secure_dropbox_folder_path)
        KMS_file_list = \
            self.KMS_handler.download_doc_keychain(self.user.username, self.user.password, self.user.token)\
                .keys()

        for filename in local_file_list:
            modification_time = \
                time.ctime(os.stat(self.secure_dropbox_folder_path + os.path.sep + filename).st_mtime)

            if filename in KMS_file_list:
                sync_state = 'sync'
            else:
                sync_state = 'out-sync'

            file_list_node = [filename, modification_time, sync_state]
            file_list.append(file_list_node)

        return file_list

    def delete_file(self, file_to_delete):
        file_path = file_to_delete
        print file_path
        doc_id = os.path.basename(file_to_delete)
        print "doc_id: %s" % doc_id
        res = self.KMS_handler.delete_doc_keychain(self.user.username, self.user.password, self.user.token, doc_id).read()
        print res
        if res == CONFIG.DELETE_KEY_SUCCEED:
            try:
                os.remove(file_path)
            except OSError as e:  # name the Exception `e`
                print "Failed with:", e.strerror  # look what it says
                print "Error code:", e.code 
            return True
        else:
            return False

    def generate_doc_id(self, file_path):
        # [email protected]
        doc_id = os.path.basename(file_path) + '_' + self.user.username + '.enc'
        return doc_id

    def read_file(self, file_to_read):
        doc_id = os.path.basename(file_to_read)
        doc_key = self.user.doc_keychain[doc_id]

        temp_file_path = self.secure_dropbox_folder_path + os.path.sep + str(time.time())

        self.cryptor.decrypt_file(doc_key, file_to_read, temp_file_path)

        with open(temp_file_path) as f:
            result = f.read()

        os.remove(temp_file_path)
        return result

    def load_file_into_secure_dropbox(self, file_to_load):

        doc_id = self.generate_doc_id(file_to_load)
        doc_key = self.cryptor.generate_new_doc_key()

        # encrypt the file with doc key
        target_file = self.secure_dropbox_folder_path + os.path.sep + doc_id
        self.cryptor.encrypt_file(doc_key, file_to_load, target_file)

        # encrypt the doc key with RSA public key before uploading to KMS
        print self.user.RSA_pub_key
        doc_key = self.cryptor.RSA_encryptor(doc_key, self.user.RSA_pub_key)

        upload_result = self.KMS_handler.upload_doc_key(self.user.username, self.user.password, self.user.token, doc_id, doc_key)
        print upload_result
        self.user.doc_keychain = self.KMS_handler.update_doc_keychain(self.user.username, self.user.password, self.user.token)

        print self.user.RSA_priv_key
        for key in self.user.doc_keychain.keys():
            self.user.doc_keychain[key] = \
                self.cryptor.RSA_decryptor(self.user.doc_keychain[key], self.user.RSA_priv_key)

        return upload_result

    def path_local_2_dropbox(self, local_path):
        dropbox_path = '/Secure Dropbox/' + os.path.basename(local_path)
        return dropbox_path

    def read_shared_file(self, doc_key, url):
        raw_doc_key = self.cryptor.RSA_decryptor(doc_key, self.user.RSA_priv_key)
        
        encrypted_doc_content = urllib2.urlopen(url).read()
        encrypted_doc_content.replace(binascii.a2b_hex('0d0a') , binascii.a2b_hex('0a'))
        
        temp_enc_file_path = str(time.time())
        
        f = open(temp_enc_file_path, 'wb')
        f.write(encrypted_doc_content)
        f.close()
        
        temp_file_path = self.secure_dropbox_folder_path + os.path.sep + str(time.time()) + '.enc'

        print raw_doc_key
        self.cryptor.decrypt_file(raw_doc_key, temp_enc_file_path, temp_file_path)

        with open(temp_file_path) as f:
            result = f.read()

        # os.remove(temp_file_path)
        # os.remove(temp_enc_file_path)
        return result
예제 #2
0
class SecureDropbox(object):
    '''
    SecureDropbox is the main functional class in the application. 
    
    This module include the user control, dropbox API handler, KMS handler and a cryptor instance 
    for the everywhere usage of cryptography operations.The instance of SecureDropbox is created and 
    manipulated by SecureDropboxUI instance. It's the instance to be created when internet accessable
    
    Attributes:
        user: A User type instance.
        dropbox_handler: contains the client token get from Dropbox via OAuth. 
        KMS_handler: contains the method to call rest API of KMS server.
        cryptor: A cryptor instance include the embodiment of AES256, RSA256 and PBKDF2.
        dropbox_folder_path: auto detected in the file system. Please insure there is no folder with same name
            in your file system. It will leads to the encrypted file is load into a regular local folder so no synchronization
            operation would be done by Dropbox client which will dramatically impact the file sharing with other user
            and multi-client mechanism since there is no file actually synchronized to the cloud. 
        secure_dropbox_folder_path: located in dropbox_folder_path. The only folder that SecureDropbox would do with.
        ini_file: ini_file include a pickle instance of user. It's generated based on the fetched kms information when login
        and fetched when using the local when there is no internet access. The RSA key pair and key chains for AES are stored
        
    '''
    def __init__(self, username, password, dropbox_folder_path):
        
        '''
            initialize the SecureDropbox instance.
            
            parameter:
                username: username of potential user.
                password: accordant password of username
                dropbox_folder_path: achieved by UI instance.
                
            return:
                None
        '''
        # generate a user instance. this step is just after user inputing the login information.
        # only attributes of username and password is initialized here. Other attributes left None.
        self.user = User(username, password)
        
        # these three attributes would be initialized in the start method.
        self.dropbox_handler = None
        self.KMS_handler = None
        self.cryptor = None
        
        # declare the dropbox_folder_path and the secure_dropbox_folder_path that application will operate with.
        self.dropbox_folder_path = dropbox_folder_path
        self.secure_dropbox_folder_path = self.dropbox_folder_path + r'\Secure Dropbox'
        
        # if secure_dropbox_folder_path is not existed or not a folder, generate a new one.  
        if not os.path.isdir(self.secure_dropbox_folder_path):
            os.mkdir(self.secure_dropbox_folder_path)
        
        # specify the path. File will be created in the start method.
        self.ini_file = self.secure_dropbox_folder_path + os.path.sep + self.user.username + CONFIG.INI_FILE

    def generate_pickle_key(self):
        '''
            generate the AES256 key for encrypting the pickle file. 
            salt is fixed so essentially just a procedure of hash the plain password via PBKDF2 infrastructure.
            
            parameter:
                None
                
            return:
                AES256 key 
        '''
        # 64-bit fixed salt
        salt = '12345678'
        
        # hash the plain text user password and get a 256-bit AES key.
        key = PBKDF2(self.user.password, salt).read(32)
        
        return key
    
    def encrypt_ini(self):
        '''
            encrypt the ini file with AES256 and pickle key
            
            parameter:
                None
                
            return:
                None 
        '''
        
        # generate the pickle_key based on user password
        pickle_key = self.generate_pickle_key()
        
        # encrypt the ini file and genearte a temp file.
        self.cryptor.encrypt_file(pickle_key, self.ini_file, self.ini_file + '.enc')
        
        # remove the temp file and rename the encrypted ini file.
        os.remove(self.ini_file)
        os.rename(self.ini_file + '.enc', self.ini_file)
        
        # uncomment this to set the ini file hidden. The default configuration allow several
        # SecureDorpbox Account using same local dropbox client folder, which is logically inappropriate.
        
        # win32api.SetFileAttributes(self.ini_file, win32con.FILE_ATTRIBUTE_HIDDEN)

    def initialize(self):
        '''
            initialize essential instances required in the SecureDropbox.
            
            parameter:
                None
                
            return:
                flag indicate the initialization succeed or not.
        '''
        
        # initialize the handlers. KMS handler needs a cryptor instance so passed as parameter to the 
        # constructor of KMS_Handler.
        self.dropbox_handler = DropboxHandler()
        self.cryptor = Cryptor()
        self.KMS_handler = KMS_Handler(self.cryptor)
        
        # self.login initialize the user information include RSA key pair and doc_key_chain if login result
        # is positive. 
        if self.login():
            
            # The very first design is only one user existing in the system at one time. 
            # Delete other user's configuration file.
            # Now the logic retain all users' ini file and allow simultaneously usage which is not logically appropriate.   
            filenames = os.listdir(self.secure_dropbox_folder_path)
            for element in filenames:
                if str(element).endswith('.ini') and self.user.username not in str(element):
                    pass
                    # uncomment this to not remove but conceal the file.
                    # os.remove(self.secure_dropbox_folder_path + os.path.sep + element)
            
            # dump the user instance into ini_file
            with open(self.ini_file, 'w') as ini_file:
                pickle.dump(self.user, ini_file, True)
            
            # encrypt the ini_file
            self.encrypt_ini()
            
            return True
        else:
            
            # login failed
            print ("login failed. Unauthorized user or Server not available")
            
            return False

    def share_file(self, user_to_share_with, file_to_share):

        '''
        share the file with specified user.
        
        parameters:
            user_to_share_with: the user's SecureDropbox account
            file_to_share: file path in local pattern
            
        return:
            flag indicates the file sharing succeed or not.
        '''
        
        # get the file sharing info via the dropbox /media restful API.
        # before passing the file_to_share, convert it to dropbox path pattern.
        sharing_info = self.dropbox_handler.client.media(self.path_local_2_dropbox(file_to_share))

        # sharing information. by sending http request to the url, a pure content of the file is returned.
        # A timestamp expire value is decided by dropbox and returned. By default it's six hours until the 
        # url expires. 
        url = sharing_info['url']
        expires = sharing_info['expires']
        
        # generate doc_id based on file path
        doc_id = os.path.basename(file_to_share)

        # get sharing receipient's RSA pub_key.
        response = self.KMS_handler.download_sharing_recipient_RSA_pub_key(self.user.username, self.user.password, self.user.token, user_to_share_with)
        receipient_RSA_pub_key = response.read()
        
        # if no such a user or pub key fetching failed.
        if receipient_RSA_pub_key == CONFIG.FETCH_PUBLIC_KEY_FAILED:
            return False

        if receipient_RSA_pub_key:
            # encrypt the doc_key with receipient's RSA pub key so that receipient could decrypt by the RSA private key
            # held by him own.
            doc_key = self.cryptor.RSA_encryptor(self.user.doc_keychain[doc_id], receipient_RSA_pub_key)
        else:
            doc_key = None

        if doc_key:
            # send the sharing record include the doc metadata, url and expires returned from dropbox.
            return self.KMS_handler.share_file(self.user.username, self.user.password, self.user.token, user_to_share_with, doc_id, doc_key, url, expires)
        else:
            return False
        
    def shared_file(self):
        '''
        get the shared file list from KMS server
        
        parameter:
            None
        
        return:
            shared file list
        '''
        
        # call the shared_file method via KMS_handler and return the result
        return self.KMS_handler.shared_file(self.user.username, self.user.password, self.user.token)

    def login(self):
        '''
            login into the SecureDropbox KMS server. 
            
            parameter:
                None
            
            return:
                flag indicate the login succeed or not
        '''
        
        # call the login method via KMS_handler and the token(timestamp) RSA key pairs and doc key_chain
        # are returned if login succeed. Else token = -1 and other three are None.
        self.user.token, self.user.RSA_pub_key, self.user.RSA_priv_key, self.user.doc_keychain = \
            self.KMS_handler.login(self.user.username, self.user.password)

        # if it's a valid login request, initialize the user information
        if self.user.token and self.user.token != CONFIG.INVALID_USER_INCORRECT_INFO:
            # if valid user then decrypt the encrypted RSA private key with user's password through pbkdf2
            
            # decrypt the RSA private key via own password
            self.user.RSA_priv_key = \
             self.cryptor.AES_decryptor(self.user.RSA_priv_key, self.cryptor.pad_AES_key(self.user.password))
             
            print 'User KMS configuration done'
            
            # decrypt the doc_keys with RSA private key
            if len(self.user.doc_keychain) != 0:
                for key in self.user.doc_keychain.keys():
                    self.user.doc_keychain[key] = \
                        self.cryptor.RSA_decryptor(self.user.doc_keychain[key], self.user.RSA_priv_key)
    
            return True
        
        else:
            
            return False

    def register(self, username, password):
        
        '''
            register a new account in the KMS server
            
            parameter:
                username: username want to register
                password: password want to use
                
            return:
                registration result from KMS server.
        '''
        
        # create temp .pem file ready for storing the RSA key pair.
        private_key_path = '%s_priv.pem' % (username)
        f = open(private_key_path, 'w')
        f.close()
        public_key_path = '%s_pub.pem' % (username)
        f = open(public_key_path, 'w')
        f.close()
        
        print 'register :: generating RSA keys...'
        
        # generating RSA key pair.
        # get a random string as rand_seed
        M2Crypto.Rand.rand_seed (os.urandom (1024))
        
        # generate the RSA key pair via gen_key interface.
        RSA_key = M2Crypto.RSA.gen_key (1024, 65537)
        
        # save both private key and public key
        RSA_key.save_key (private_key_path, None)
        RSA_key.save_pub_key (public_key_path)
        
        # read the public key to memory
        f = open(public_key_path)
        RSA_public_key = f.read()
        f.close()
        os.remove(public_key_path)
        
        # read the private key to memory
        f = open(private_key_path)
        RSA_private_key = f.read()
        f.close()
        os.remove(private_key_path)
        
        # encrypt the RSA private key with password before uploading to the server.
        RSA_private_key = self.cryptor.AES_encryptor(RSA_private_key, self.cryptor.pad_AES_key(password))
        
        # upload RSA key pair and user metadata to registration api and get the return value
        return self.KMS_handler.register(username, password, RSA_public_key, RSA_private_key)

    def show_file_list(self):

        '''
        get file_list record from KMS server for certain user.
        
        parameter:
            None
            
        return:
            a list type file_list include filename, last modification and synchronization situation.
        '''
        file_list = []
        serial_number = 1
        
        # get the local file_list
        local_file_list = os.listdir(self.secure_dropbox_folder_path)
        
        # get file list on KMS server via download_doc_keychain interface of KMS_handler
        KMS_file_list = \
            self.KMS_handler.download_doc_keychain(self.user.username, self.user.password, self.user.token)\
                .keys()

        # matching the local and online file list and getting the synchronization situation
        for filename in local_file_list:
            
            # if it's a file loaded by SecureDropbox properly. The judgement is based on suffix.
            if self.user.username in filename and str(filename).endswith('.enc'):
                
                # indicate the last modification time of certain file. Further function could be done like 
                # version control according to last modification time 
                modification_time = \
                    time.ctime(os.stat(self.secure_dropbox_folder_path + os.path.sep + filename).st_mtime)
    
                # tag a synchronization flag to the file. If the file exists both onlinely and locally
                if filename in KMS_file_list:
                    sync_state = 'sync'
                else:
                    sync_state = 'out-sync'
    
                # generate a file node and append it to the list to be returned.
                file_list_node = [str(serial_number), filename, modification_time, sync_state]
                file_list.append(file_list_node)

                serial_number += 1
        return file_list

    def delete_file(self, file_to_delete):
        '''
        delete a file loaded via SecureDropbox. This method include deleting the local file and KMS server record
        
        parameter:
            file_to_delete: local file name to be deleted.
            
        return:
            flag indicated file deleting succeed or not.
        '''
        
        file_path = file_to_delete
        
        print 'Deleting' + file_path
        # generate doc id based on file local path.
        doc_id = os.path.basename(file_to_delete)
        
        print "Deleting doc_id: %s" % doc_id
        # send the delete file request to KMS server.
        res = self.KMS_handler.delete_doc_keychain(self.user.username, self.user.password, self.user.token, doc_id).read()

        
        if res == CONFIG.DELETE_KEY_SUCCEED:
            try:
                # delete local file after positive response from KMS server
                os.remove(file_path)
            except OSError as e:  # name the Exception `e`
                print "Failed with:", e.strerror  # look what it says
                print "Error code:", e.code 
            return True
        else:
            return False

    def generate_doc_id(self, file_path):
        '''
        generate the doc id.
        
        parameters: 
            file_path: local file system path of the file
        
        return:
            doc_id include username and end up with .enc
        '''
        # [email protected]
        doc_id = os.path.basename(file_path) + '_' + self.user.username + '.enc'
        return doc_id

    def read_file(self, file_to_read):
        '''
            read the AES256 encrypted local file.
            
            parameter:
                file_to_read: local file system path of the file to be read.
                
            return:
                file content in plain text
        '''
        
        # get the doc_id and corresponding AES doc_key
        doc_id = os.path.basename(file_to_read)
        doc_key = self.user.doc_keychain[doc_id]

        temp_file_path = self.secure_dropbox_folder_path + os.path.sep + str(time.time())
        
        # decrypt the target file and put it into temp_file_path
        self.cryptor.decrypt_file(doc_key, file_to_read, temp_file_path)

        # get file content and delete the temp file 
        with open(temp_file_path) as f:
            result = f.read()
        os.remove(temp_file_path)
        
        return result

    def load_file_into_secure_dropbox(self, file_to_load):

        '''
        load a plaintext file into secure dropbox
        the load procedure includes encrypting the target file and copying the file
        to the securedropbox folder in Dropbox app folder.
        
        parameter:
            file_to_load: target file to load in local file system
            
        return:
            upload result returned from KMS server
        '''
        
        # get doc id and corresponding doc key.
        doc_id = self.generate_doc_id(file_to_load)
        doc_key = self.cryptor.generate_new_doc_key()

        # encrypt the file with doc key
        target_file = self.secure_dropbox_folder_path + os.path.sep + doc_id
        self.cryptor.encrypt_file(doc_key, file_to_load, target_file)

        # encrypt the doc key with RSA public key before uploading to KMS
        # print self.user.RSA_pub_key
        doc_key = self.cryptor.RSA_encryptor(doc_key, self.user.RSA_pub_key)

        # upload the encrypted doc_key to KMS
        upload_result = self.KMS_handler.upload_doc_key(self.user.username, self.user.password, self.user.token, doc_id, doc_key)
        # print upload_result
        
        # update local doc key chain. Since everytime reading a file is not through the KMS server but only
        # get doc key from local. So it's essential to update the doc key chain once it is changed.
        self.user.doc_keychain = self.KMS_handler.update_doc_keychain(self.user.username, self.user.password, self.user.token)

        # decrypt the doc keychain. the doc key chain get from KMS server is encrypted with AES256 and user's password as 
        # key seed.
        for key in self.user.doc_keychain.keys():
            self.user.doc_keychain[key] = \
                self.cryptor.RSA_decryptor(self.user.doc_keychain[key], self.user.RSA_priv_key)

        # dump the new doc key chain information and encrypt it.
        with open(self.ini_file, 'w') as ini_file:
            pickle.dump(self.user, ini_file, True)
        self.encrypt_ini()
        
        return upload_result

    def path_local_2_dropbox(self, local_path):
        '''
        Convert local path format to dropbox style(linux)
        
        parameters:
            local_path: path of file in local style
            
        return dropbox path style
        '''
        # add the prefix of Secure dropbox folder to dropbox_path
        dropbox_path = '/Secure Dropbox/' + os.path.basename(local_path)
        return dropbox_path

    def read_shared_file(self, doc_key, url):
        '''
        read shared file in the dropbox. There is no actually file instance to be read. 
        The content of shared file is fetched thourgh url which generated by /media interface of dropbox.
        the shared file has a expiration limitation so once the sharing time is up, KMS server will delete the
        sharing record so that sharing receipient would not get this sharing information locally.
        
        parameter:
            doc_key: encrypted doc key with receipient's RSA pub key. 
            url: sharing url generated by /media interface of Dropbox core API.
            
        return:
            decrypted file content
        '''
        
        # decrypt the doc key with receipient's own private key.
        raw_doc_key = self.cryptor.RSA_decryptor(doc_key, self.user.RSA_priv_key)
        
        # fetch file content by sending get request to the target url.
        encrypted_doc_content = urllib2.urlopen(url).read()
        
        # This is a bug-fixing step. Because of different coding style that dropbox is using, 
        # the file you upload would be distorted with regard to the way how to represent the return character.
        # In win7 it's coded as '0a' but dropbox change it to '0d0a'(more the just a return)
        # It causes an incorrect deciphering procedure since the cipher are changed. 
        encrypted_doc_content.replace(binascii.a2b_hex('0d0a') , binascii.a2b_hex('0a'))
        
        # write the content into a temporary file.
        temp_enc_file_path = str(time.time())
        f = open(temp_enc_file_path, 'wb')
        f.write(encrypted_doc_content)
        f.close()
        
        temp_file_path = self.secure_dropbox_folder_path + os.path.sep + str(time.time()) + '.enc'

        # print raw_doc_key
        # decrypt the file with doc key that just decrypted with receipients' own password.
        self.cryptor.decrypt_file(raw_doc_key, temp_enc_file_path, temp_file_path)

        # get plain text file content
        with open(temp_file_path) as f:
            result = f.read()
            
        # uncomment this to leave log file existed for decryption procedure.
        # os.remove(temp_file_path)
        # os.remove(temp_enc_file_path)
        return result