From 9f455ab44d8f229840a5c6a75e0e7b6a88b04f57 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 4 Aug 2014 11:40:37 -0300 Subject: Store decrypted storage secret in memory. --- client/src/leap/soledad/client/secrets.py | 182 +++++++++++++++++------------- 1 file changed, 105 insertions(+), 77 deletions(-) diff --git a/client/src/leap/soledad/client/secrets.py b/client/src/leap/soledad/client/secrets.py index 3c6fc569..621e2d99 100644 --- a/client/src/leap/soledad/client/secrets.py +++ b/client/src/leap/soledad/client/secrets.py @@ -252,13 +252,9 @@ class SoledadSecrets(object): try: self._load_secrets() # try to load from disk except IOError as e: - logger.warning('IOError: %s' % str(e)) - try: - self.storage_secret - return True - except Exception as e: - logger.warning("Couldn't load storage secret: %s" % str(e)) - return False + logger.warning('IOError while loading secrets from disk: %s' % str(e)) + return False + return self.storage_secret is not None def _load_secrets(self): """ @@ -371,15 +367,21 @@ class SoledadSecrets(object): # create salt and key for calculating MAC salt = os.urandom(self.SALT_LENGTH) key = scrypt.hash(self._passphrase_as_string(), salt, buflen=32) + # encrypt secrets + encrypted_secrets = {} + for secret_id in self._secrets: + encrypted_secrets[secret_id] = self._encrypt_storage_secret( + self._secrets[secret_id]) + # create the recovery document data = { - self.STORAGE_SECRETS_KEY: self._secrets, + self.STORAGE_SECRETS_KEY: encrypted_secrets, self.KDF_KEY: self.KDF_SCRYPT, self.KDF_SALT_KEY: binascii.b2a_base64(salt), self.KDF_LENGTH_KEY: len(key), MAC_METHOD_KEY: MacMethods.HMAC, MAC_KEY: hmac.new( key, - json.dumps(self._secrets), + json.dumps(encrypted_secrets), sha256).hexdigest(), } return data @@ -425,10 +427,11 @@ class SoledadSecrets(object): 'contents.') # include secrets in the secret pool. secrets = 0 - for secret_id, secret_data in data[self.STORAGE_SECRETS_KEY].items(): + for secret_id, encrypted_secret in data[self.STORAGE_SECRETS_KEY].items(): if secret_id not in self._secrets: secrets += 1 - self._secrets[secret_id] = secret_data + self._secrets[secret_id] = \ + self._decrypt_storage_secret(encrypted_secret) return secrets, mac def _get_secrets_from_shared_db(self): @@ -480,30 +483,92 @@ class SoledadSecrets(object): # Management of secret for symmetric encryption. # - @property - def storage_secret(self): + def _decrypt_storage_secret(self, encrypted_secret_dict): """ - Return the storage secret. + Decrypt the storage secret. Storage secret is encrypted before being stored. This method decrypts - and returns the stored secret. + and returns the decrypted storage secret. - :return: The storage secret. + :param encrypted_secret_dict: The encrypted storage secret. + :type encrypted_secret_dict: dict + + :return: The decrypted storage secret. :rtype: str """ # calculate the encryption key + if encrypted_secret_dict[self.KDF_KEY] != self.KDF_SCRYPT: + raise Exception("Unknown KDF in stored secret.") key = scrypt.hash( self._passphrase_as_string(), # the salt is stored base64 encoded binascii.a2b_base64( - self._secrets[self._secret_id][self.KDF_SALT_KEY]), + encrypted_secret_dict[self.KDF_SALT_KEY]), buflen=32, # we need a key with 256 bits (32 bytes). ) + if encrypted_secret_dict[self.KDF_LENGTH_KEY] != len(key): + raise Exception("Wrong length of decryption key.") + if encrypted_secret_dict[self.CIPHER_KEY] != self.CIPHER_AES256: + raise Exception("Unknown cipher in stored secret.") # recover the initial value and ciphertext - iv, ciphertext = self._secrets[self._secret_id][self.SECRET_KEY].split( + iv, ciphertext = encrypted_secret_dict[self.SECRET_KEY].split( self.IV_SEPARATOR, 1) ciphertext = binascii.a2b_base64(ciphertext) - return self._crypto.decrypt_sym(ciphertext, key, iv=iv) + decrypted_secret = self._crypto.decrypt_sym(ciphertext, key, iv=iv) + if encrypted_secret_dict[self.LENGTH_KEY] != len(decrypted_secret): + raise Exception("Wrong length of decrypted secret.") + return decrypted_secret + + def _encrypt_storage_secret(self, decrypted_secret): + """ + Encrypt the storage secret. + + An encrypted secret has the following structure: + + { + '': { + 'kdf': 'scrypt', + 'kdf_salt': '' + 'kdf_length': + 'cipher': 'aes256', + 'length': , + 'secret': '', + } + } + + :param decrypted_secret: The decrypted storage secret. + :type decrypted_secret: str + + :return: The encrypted storage secret. + :rtype: dict + """ + # generate random salt + salt = os.urandom(self.SALT_LENGTH) + # get a 256-bit key + key = scrypt.hash(self._passphrase_as_string(), salt, buflen=32) + iv, ciphertext = self._crypto.encrypt_sym(decrypted_secret, key) + encrypted_secret_dict = { + # leap.soledad.crypto submodule uses AES256 for symmetric + # encryption. + self.KDF_KEY: self.KDF_SCRYPT, + self.KDF_SALT_KEY: binascii.b2a_base64(salt), + self.KDF_LENGTH_KEY: len(key), + self.CIPHER_KEY: self.CIPHER_AES256, + self.LENGTH_KEY: len(decrypted_secret), + self.SECRET_KEY: '%s%s%s' % ( + str(iv), self.IV_SEPARATOR, binascii.b2a_base64(ciphertext)), + } + return encrypted_secret_dict + + @property + def storage_secret(self): + """ + Return the storage secret. + + :return: The decrypted storage secret. + :rtype: str + """ + return self._secrets.get(self._secret_id) def set_secret_id(self, secret_id): """ @@ -526,19 +591,6 @@ class SoledadSecrets(object): * SOLEDAD_CREATING_KEYS * SOLEDAD_DONE_CREATING_KEYS - A secret has the following structure: - - { - '': { - 'kdf': 'scrypt', - 'kdf_salt': '' - 'kdf_length': - 'cipher': 'aes256', - 'length': , - 'secret': '', - } - } - :return: The id of the generated secret. :rtype: str """ @@ -548,22 +600,7 @@ class SoledadSecrets(object): self.LOCAL_STORAGE_SECRET_LENGTH + self.REMOTE_STORAGE_SECRET_LENGTH) secret_id = sha256(secret).hexdigest() - # generate random salt - salt = os.urandom(self.SALT_LENGTH) - # get a 256-bit key - key = scrypt.hash(self._passphrase_as_string(), salt, buflen=32) - iv, ciphertext = self._crypto.encrypt_sym(secret, key) - self._secrets[secret_id] = { - # leap.soledad.crypto submodule uses AES256 for symmetric - # encryption. - self.KDF_KEY: self.KDF_SCRYPT, - self.KDF_SALT_KEY: binascii.b2a_base64(salt), - self.KDF_LENGTH_KEY: len(key), - self.CIPHER_KEY: self.CIPHER_AES256, - self.LENGTH_KEY: len(secret), - self.SECRET_KEY: '%s%s%s' % ( - str(iv), self.IV_SEPARATOR, binascii.b2a_base64(ciphertext)), - } + self._secrets[secret_id] = secret self._store_secrets() signal(SOLEDAD_DONE_CREATING_KEYS, self._uuid) return secret_id @@ -596,24 +633,6 @@ class SoledadSecrets(object): # ensure there's a secret for which the passphrase will be changed. if not self._has_secret(): raise NoStorageSecret() - secret = self.storage_secret - # generate random salt - new_salt = os.urandom(self.SALT_LENGTH) - # get a 256-bit key - key = scrypt.hash(new_passphrase.encode('utf-8'), new_salt, buflen=32) - iv, ciphertext = self._crypto.encrypt_sym(secret, key) - # XXX update all secrets in the dict - self._secrets[self._secret_id] = { - # leap.soledad.crypto submodule uses AES256 for symmetric - # encryption. - self.KDF_KEY: self.KDF_SCRYPT, # TODO: remove hard coded kdf - self.KDF_SALT_KEY: binascii.b2a_base64(new_salt), - self.KDF_LENGTH_KEY: len(key), - self.CIPHER_KEY: self.CIPHER_AES256, - self.LENGTH_KEY: len(secret), - self.SECRET_KEY: '%s%s%s' % ( - str(iv), self.IV_SEPARATOR, binascii.b2a_base64(ciphertext)), - } self._passphrase = new_passphrase self._store_secrets() self._put_secrets_in_shared_db() @@ -655,27 +674,36 @@ class SoledadSecrets(object): # TODO: implement. pass - def get_remote_secret(self): + def _get_remote_storage_secret(self): """ Return the secret for remote storage. """ # TODO: implement pass - def get_local_storage_key(self): + + def _get_local_storage_secret(self): """ - Return the local storage key derived from the local storage secret. + Return the local storage secret. + """ + pwd_start = self.REMOTE_STORAGE_SECRET_LENGTH + self.SALT_LENGTH + pwd_end = self.REMOTE_STORAGE_SECRET_LENGTH + self.LOCAL_STORAGE_SECRET_LENGTH + return self.storage_secret[pwd_start:pwd_end] + + def _get_local_storage_salt(self): + """ + Return the local storage salt. """ - # salt indexes salt_start = self.REMOTE_STORAGE_SECRET_LENGTH salt_end = salt_start + self.SALT_LENGTH - # password indexes - pwd_start = salt_end - pwd_end = salt_start + self.LOCAL_STORAGE_SECRET_LENGTH - # calculate the key for local encryption - secret = self.storage_secret + return self.storage_secret[salt_start:salt_end] + + def get_local_storage_key(self): + """ + Return the local storage key derived from the local storage secret. + """ return scrypt.hash( - secret[pwd_start:pwd_end], # the password - secret[salt_start:salt_end], # the salt + self._get_local_storage_secret(), # the password + self._get_local_storage_salt(), # the salt buflen=32, # we need a key with 256 bits (32 bytes) ) -- cgit v1.2.3