class PasswordEncryption(AESinRSAStreamEncryption):
    """ This is a password encryption which uses a pub/private RSSA key pair
        which is specified to secure an AES stream for item encryption.

        What does this class do in detail:

        Usually, an RSA identity would be used for each separate password used
        (shared among all items that are encrypted with this password), and
        the private key would be stored encrypted with the given password.

        This structure just handles AES with the given identity - you need to
        handle storing the RSA identity (including the required private key
        encryption with the password) elsewhere.

        TODO: document where the RSA identities for pw encryption are handled
    """

    def __init__(self, load_from_folder_path, password, aes_info_path=None):
        super().__init__()
        self.load_from_folder_path = load_from_folder_path

        # set the proper stored AES key, if this isn't a new encryption:
        if load_from_folder_path != None:
            if password == None:
                # do nothing for now
                return
            self.load_from_folder(load_from_folder_path)
        else:
            self.crypto_identity = Identity()
        self.stored_password = password

    def unlock(self, password):
        self.stored_password = password
        self.load_from_folder(self.load_from_folder_path)

    def lock(self):
        if hasattr(self, "aes_key"):
            del (self.aes_key)
        if hasattr(self, "crypto_identity"):
            del (self.crypto_identity)
        if hasattr(self, "stored_password"):
            del (self.stored_password)

    def save_to_folder(self, path):
        super().save_encrypted_aes_info(self.crypto_identity, os.path.join(path, "aes_info.data"))
        self.crypto_identity.save_to_file(os.path.join(path, "rsa_identity.data"), passphrase=self.stored_password)

    def load_from_folder(self, path, password=None):
        self.crypto_identity = Identity(os.path.join(path, "rsa_identity.data"), passphrase=password)
        super().load_encrypted_aes_info(self.crypto_identity)
    def __init__(self, target_node_pubkey_path):
        super().__init__()
        self.privkey = None

        # construct public key-only identity with supplied key file:
        self.crypto_identity = Identity(target_node_pubkey_path)

        if self.crypto_identity.has_private():
            self.set_decryption_by_identity(self.crypto_identity)
    def __init__(self, load_from_folder_path, password, aes_info_path=None):
        super().__init__()
        self.load_from_folder_path = load_from_folder_path

        # set the proper stored AES key, if this isn't a new encryption:
        if load_from_folder_path != None:
            if password == None:
                # do nothing for now
                return
            self.load_from_folder(load_from_folder_path)
        else:
            self.crypto_identity = Identity()
        self.stored_password = password
class TargetNodeEncryption(AESinRSAStreamEncryption):
    """ This handles asymmetric encryption for another information node's
        public RSA identity information which must be provided.

        It will then encrypt an AES stream which can only be opened with the
        target node's information key (unless one day RSA is broken of
        course).
    """

    def __init__(self, target_node_pubkey_path):
        super().__init__()
        self.privkey = None

        # construct public key-only identity with supplied key file:
        self.crypto_identity = Identity(target_node_pubkey_path)

        if self.crypto_identity.has_private():
            self.set_decryption_by_identity(self.crypto_identity)

    def set_decryption_private_key(self, target_node_privkey_path, passphrase=None):
        """ Provide an RSA private key to allow this to decrypt data.
        """
        identity = Identity(target_node_privkey_path, passphrase=passphrase)
        self._set_decryption_by_identity(identity)

    def set_decryption_by_identity(self, identity):
        self.crypto_identity = identity
        super().load_encrypted_aes_info(self.crypto_identity)

    def save_to_folder(self, path):
        super().save_encrypted_aes_info(self.crypto_identity, os.path.join(path, "aes_info.data"))

    def load_from_folder(self, path):
        if not self.crypto_identity.has_private():
            raise RuntimeError("no private key provided, cannot decrypt")
        super().load_encrypted_aes_info(self.crypto_identity, os.path.join(path, "aes_info.data"))
    def test_encrypt_decrypt(self):
        identity = Identity()

        # test basic encryption / decryption
        plain_str = b"abc"
        encrypted_str = identity.encrypt(plain_str)
        self.assertNotEqual(plain_str, encrypted_str)
        decrypted_str = identity.decrypt(encrypted_str)
        self.assertEqual(plain_str, decrypted_str)

        # ensure it behaves non-deterministically due to PKCS padding:
        self.assertNotEqual(identity.encrypt(plain_str),
            identity.encrypt(plain_str))

        # ensure a way too long value doesn't give any other error than
        # a ValueError:
        too_long_value = os.urandom(identity.size())
        got_value_error = False
        try:
            result = identity.encrypt(too_long_value)
        except ValueError:
            got_value_error = True
        self.assertTrue(got_value_error)
 def load_from_folder(self, path, password=None):
     self.crypto_identity = Identity(os.path.join(path, "rsa_identity.data"), passphrase=password)
     super().load_encrypted_aes_info(self.crypto_identity)