Mercurial > libervia-backend
comparison 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 |
comparison
equal
deleted
inserted
replaced
3159:30e08d904208 | 3160:330a5f1d9eea |
---|---|
345 ) | 345 ) |
346 session_d.addCallback(createSession) | 346 session_d.addCallback(createSession) |
347 finally: | 347 finally: |
348 return session_d | 348 return session_d |
349 | 349 |
350 auth_d = self.profileAuthenticate(password, profile) | 350 auth_d = defer.ensureDeferred(self.profileAuthenticate(password, profile)) |
351 auth_d.addCallback(doStartSession) | 351 auth_d.addCallback(doStartSession) |
352 return auth_d | 352 return auth_d |
353 | 353 |
354 if self.host.initialised.called: | 354 if self.host.initialised.called: |
355 return defer.succeed(None).addCallback(backendInitialised) | 355 return defer.succeed(None).addCallback(backendInitialised) |
379 # the session is starting but not started yet | 379 # the session is starting but not started yet |
380 return not isinstance(self._entities_cache[profile], defer.Deferred) | 380 return not isinstance(self._entities_cache[profile], defer.Deferred) |
381 except KeyError: | 381 except KeyError: |
382 return False | 382 return False |
383 | 383 |
384 def profileAuthenticate(self, password, profile): | 384 async def profileAuthenticate(self, password, profile): |
385 """Authenticate the profile. | 385 """Authenticate the profile. |
386 | 386 |
387 @param password (unicode): the SàT profile password | 387 @param password (unicode): the SàT profile password |
388 @param profile: %(doc_profile)s | 388 @return: None in case of success (an exception is raised otherwise) |
389 @return (D): a deferred None in case of success, a failure otherwise. | |
390 @raise exceptions.PasswordError: the password does not match | 389 @raise exceptions.PasswordError: the password does not match |
391 """ | 390 """ |
392 if not password and self.auth_sessions.profileGetUnique(profile): | 391 if not password and self.auth_sessions.profileGetUnique(profile): |
393 # XXX: this allows any frontend to connect with the empty password as soon as | 392 # XXX: this allows any frontend to connect with the empty password as soon as |
394 # the profile has been authenticated at least once before. It is OK as long as | 393 # the profile has been authenticated at least once before. It is OK as long as |
395 # submitting a form with empty passwords is restricted to local frontends. | 394 # submitting a form with empty passwords is restricted to local frontends. |
396 return defer.succeed(None) | 395 return |
397 | 396 |
398 def check_result(result): | 397 sat_cipher = await self.asyncGetParamA( |
399 if not result: | |
400 log.warning("Authentication failure of profile {}".format(profile)) | |
401 raise failure.Failure( | |
402 exceptions.PasswordError( | |
403 "The provided profile password doesn't match." | |
404 ) | |
405 ) | |
406 return self.newAuthSession(password, profile) | |
407 | |
408 d = self.asyncGetParamA( | |
409 C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile | 398 C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile |
410 ) | 399 ) |
411 d.addCallback(lambda sat_cipher: PasswordHasher.verify(password, sat_cipher)) | 400 valid = PasswordHasher.verify(password, sat_cipher) |
412 return d.addCallback(check_result) | 401 if not valid: |
413 | 402 log.warning(_("Authentication failure of profile {profile}").format( |
414 def newAuthSession(self, key, profile): | 403 profile=profile)) |
404 raise exceptions.PasswordError("The provided profile password doesn't match.") | |
405 return await self.newAuthSession(password, profile) | |
406 | |
407 async def newAuthSession(self, key, profile): | |
415 """Start a new session for the authenticated profile. | 408 """Start a new session for the authenticated profile. |
416 | 409 |
417 If there is already an existing session, no new one is created | 410 If there is already an existing session, no new one is created |
418 The personal key is loaded encrypted from a PersistentDict before being decrypted. | 411 The personal key is loaded encrypted from a PersistentDict before being decrypted. |
419 | 412 |
420 @param key: the key to decrypt the personal key | 413 @param key: the key to decrypt the personal key |
421 @param profile: %(doc_profile)s | 414 @param profile: %(doc_profile)s |
422 @return: a deferred None value | 415 """ |
423 """ | 416 data = await PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load() |
424 | 417 personal_key = BlockCipher.decrypt(key, data[C.MEMORY_CRYPTO_KEY]) |
425 def gotPersonalKey(personal_key): | 418 # Create the session for this profile and store the personal key |
426 """Create the session for this profile and store the personal key""" | 419 session_data = self.auth_sessions.profileGetUnique(profile) |
427 session_data = self.auth_sessions.profileGetUnique(profile) | 420 if not session_data: |
428 if not session_data: | 421 self.auth_sessions.newSession( |
429 self.auth_sessions.newSession( | 422 {C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile |
430 {C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile | 423 ) |
431 ) | 424 log.debug("auth session created for profile %s" % profile) |
432 log.debug("auth session created for profile %s" % profile) | |
433 | |
434 d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load() | |
435 d.addCallback(lambda data: BlockCipher.decrypt(key, data[C.MEMORY_CRYPTO_KEY])) | |
436 return d.addCallback(gotPersonalKey) | |
437 | 425 |
438 def purgeProfileSession(self, profile): | 426 def purgeProfileSession(self, profile): |
439 """Delete cache of data of profile | 427 """Delete cache of data of profile |
440 @param profile: %(doc_profile)s""" | 428 @param profile: %(doc_profile)s""" |
441 log.info(_("[%s] Profile session purge" % profile)) | 429 log.info(_("[%s] Profile session purge" % profile)) |
1062 @param profile: %(profile_doc)s | 1050 @param profile: %(profile_doc)s |
1063 @return: a deferred None value | 1051 @return: a deferred None value |
1064 """ | 1052 """ |
1065 | 1053 |
1066 def gotIndMemory(data): | 1054 def gotIndMemory(data): |
1067 d = BlockCipher.encrypt(crypto_key, data_value) | 1055 data[data_key] = BlockCipher.encrypt(crypto_key, data_value) |
1068 | 1056 return data.force(data_key) |
1069 def cb(cipher): | |
1070 data[data_key] = cipher | |
1071 return data.force(data_key) | |
1072 | |
1073 return d.addCallback(cb) | |
1074 | 1057 |
1075 def done(__): | 1058 def done(__): |
1076 log.debug( | 1059 log.debug( |
1077 _("Personal data (%(ns)s, %(key)s) has been successfuly encrypted") | 1060 _("Personal data (%(ns)s, %(key)s) has been successfuly encrypted") |
1078 % {"ns": C.MEMORY_CRYPTO_NAMESPACE, "key": data_key} | 1061 % {"ns": C.MEMORY_CRYPTO_NAMESPACE, "key": data_key} |