core (memory/crypto): replaced PyCrypto by cryptography:

`PyCrypto` is unmaintained for years but was used in SàT for password hashing. This patch
fixes that by replacing `PyCrypto` by the reference `cryptography` module which is well
maintained.

The behaviour stays the same (except that previously async `hash`, `encrypt` and `decrypt`
methods are now synchronous, as they are quick and using a deferToThread may actually be
more resource intensive than using blocking methods).

It is planed to improve `memory.crypto` by using more up-to-date cryptography/hashing
algorithms in the future.

PyCrypto is no more a dependency of SàT
This commit is contained in:
Goffi 2020-02-09 23:50:26 +01:00
commit d0741de2ad

View file

@ -347,7 +347,7 @@ class Memory(object):
finally:
return session_d
auth_d = self.profileAuthenticate(password, profile)
auth_d = defer.ensureDeferred(self.profileAuthenticate(password, profile))
auth_d.addCallback(doStartSession)
return auth_d
@ -381,37 +381,30 @@ class Memory(object):
except KeyError:
return False
def profileAuthenticate(self, password, profile):
async def profileAuthenticate(self, password, profile):
"""Authenticate the profile.
@param password (unicode): the SàT profile password
@param profile: %(doc_profile)s
@return (D): a deferred None in case of success, a failure otherwise.
@return: None in case of success (an exception is raised otherwise)
@raise exceptions.PasswordError: the password does not match
"""
if not password and self.auth_sessions.profileGetUnique(profile):
# XXX: this allows any frontend to connect with the empty password as soon as
# the profile has been authenticated at least once before. It is OK as long as
# submitting a form with empty passwords is restricted to local frontends.
return defer.succeed(None)
return
def check_result(result):
if not result:
log.warning("Authentication failure of profile {}".format(profile))
raise failure.Failure(
exceptions.PasswordError(
"The provided profile password doesn't match."
)
)
return self.newAuthSession(password, profile)
d = self.asyncGetParamA(
sat_cipher = await self.asyncGetParamA(
C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile
)
d.addCallback(lambda sat_cipher: PasswordHasher.verify(password, sat_cipher))
return d.addCallback(check_result)
valid = PasswordHasher.verify(password, sat_cipher)
if not valid:
log.warning(_("Authentication failure of profile {profile}").format(
profile=profile))
raise exceptions.PasswordError("The provided profile password doesn't match.")
return await self.newAuthSession(password, profile)
def newAuthSession(self, key, profile):
async def newAuthSession(self, key, profile):
"""Start a new session for the authenticated profile.
If there is already an existing session, no new one is created
@ -419,21 +412,16 @@ class Memory(object):
@param key: the key to decrypt the personal key
@param profile: %(doc_profile)s
@return: a deferred None value
"""
def gotPersonalKey(personal_key):
"""Create the session for this profile and store the personal key"""
session_data = self.auth_sessions.profileGetUnique(profile)
if not session_data:
self.auth_sessions.newSession(
{C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile
)
log.debug("auth session created for profile %s" % profile)
d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load()
d.addCallback(lambda data: BlockCipher.decrypt(key, data[C.MEMORY_CRYPTO_KEY]))
return d.addCallback(gotPersonalKey)
data = await PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load()
personal_key = BlockCipher.decrypt(key, data[C.MEMORY_CRYPTO_KEY])
# Create the session for this profile and store the personal key
session_data = self.auth_sessions.profileGetUnique(profile)
if not session_data:
self.auth_sessions.newSession(
{C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile
)
log.debug("auth session created for profile %s" % profile)
def purgeProfileSession(self, profile):
"""Delete cache of data of profile
@ -1064,13 +1052,8 @@ class Memory(object):
"""
def gotIndMemory(data):
d = BlockCipher.encrypt(crypto_key, data_value)
def cb(cipher):
data[data_key] = cipher
return data.force(data_key)
return d.addCallback(cb)
data[data_key] = BlockCipher.encrypt(crypto_key, data_value)
return data.force(data_key)
def done(__):
log.debug(