diff sat/memory/memory.py @ 3160:330a5f1d9eea

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
author Goffi <goffi@goffi.org>
date Sun, 09 Feb 2020 23:50:26 +0100
parents 60a9e47ef988
children d10b2368684e
line wrap: on
line diff
--- a/sat/memory/memory.py	Sun Feb 09 23:50:21 2020 +0100
+++ b/sat/memory/memory.py	Sun Feb 09 23:50:26 2020 +0100
@@ -347,7 +347,7 @@
                 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 @@
         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 @@
 
         @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 @@
         """
 
         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(