Updated crypto manager to use cryptography instead of pyCrypto

This commit is contained in:
Adolfo Gómez García 2020-02-18 01:46:24 +01:00
parent 2b4a0113a2
commit 3bc08d6b86
3 changed files with 71 additions and 36 deletions

View File

@ -308,8 +308,6 @@ def webPassword(request: HttpRequest) -> str:
The password is stored at session using a simple scramble algorithm that keeps the password splited at The password is stored at session using a simple scramble algorithm that keeps the password splited at
session (db) and client browser cookies. This method uses this two values to recompose the user password session (db) and client browser cookies. This method uses this two values to recompose the user password
so we can provide it to remote sessions. so we can provide it to remote sessions.
@param request: DJango Request
@return: Unscrambled user password
""" """
return cryptoManager().symDecrpyt(request.session.get(PASS_KEY, ''), getUDSCookie(request)) # recover as original unicode string return cryptoManager().symDecrpyt(request.session.get(PASS_KEY, ''), getUDSCookie(request)) # recover as original unicode string

View File

@ -130,7 +130,7 @@ class DelayedTaskRunner:
now = getSqlDatetime() now = getSqlDatetime()
exec_time = now + timedelta(seconds=delay) exec_time = now + timedelta(seconds=delay)
cls = instance.__class__ cls = instance.__class__
instanceDump = encoders.encode(pickle.dumps(instance), 'base64', asText=True) instanceDump = encoders.encodeAsStr(pickle.dumps(instance), 'base64')
typeName = str(cls.__module__ + '.' + cls.__name__) typeName = str(cls.__module__ + '.' + cls.__name__)
logger.debug('Inserting delayed task %s with %s bytes (%s)', typeName, len(instanceDump), exec_time) logger.debug('Inserting delayed task %s with %s bytes (%s)', typeName, len(instanceDump), exec_time)

View File

@ -30,40 +30,41 @@
""" """
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import typing
import hashlib import hashlib
import array import array
import uuid import uuid
import struct import struct
import random import random
import string import string
import logging import logging
import typing
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
# Deprecating these. On future versions will only use
# cryptography libraries. Keep here for backwards compat with
# 1.x 2.x encriptions methods
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from Crypto.Cipher import AES
from Crypto.Random import atfork # type: ignore from Crypto.Random import atfork # type: ignore
from OpenSSL import crypto
from django.conf import settings from django.conf import settings
from uds.core.util import encoders from uds.core.util import encoders
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# To generate an rsa key, first we need the crypt module
# next, we do:
# from Crypto.PublicKey import RSA
# import os
# RSA.generate(1024, os.urandom).exportKey()
class CryptoManager: class CryptoManager:
instance = None instance = None
def __init__(self): def __init__(self):
self._rsa = RSA.importKey(settings.RSA_KEY) self._rsa = serialization.load_pem_private_key( settings.RSA_KEY.encode(), password=None, backend=default_backend())
self._oldRsa = RSA.importKey(settings.RSA_KEY)
self._namespace = uuid.UUID('627a37a5-e8db-431a-b783-73f7d20b4934') self._namespace = uuid.UUID('627a37a5-e8db-431a-b783-73f7d20b4934')
self._counter = 0 self._counter = 0
@ -94,30 +95,61 @@ class CryptoManager:
if isinstance(value, str): if isinstance(value, str):
value = value.encode('utf-8') value = value.encode('utf-8')
atfork() return encoders.encodeAsStr(
return typing.cast(str, encoders.encode((self._rsa.encrypt(value, b'')[0]), 'base64', asText=True)) self._rsa.public_key().encrypt(
value,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
),
'base64'
)
# atfork()
# return typing.cast(str, encoders.encode((self._rsa.encrypt(value, b'')[0]), 'base64', asText=True))
def decrypt(self, value: typing.Union[str, bytes]) -> str: def decrypt(self, value: typing.Union[str, bytes]) -> str:
if isinstance(value, str): if isinstance(value, str):
value = value.encode('utf-8') value = value.encode('utf-8')
# import inspect
data: bytes = typing.cast(bytes, encoders.decode(value, 'base64'))
decrypted: bytes
try: try:
atfork() # First, try new "cryptografy" decrpypting
return str(self._rsa.decrypt(encoders.decode(value, 'base64')).decode('utf-8')) decrypted = self._rsa.decrypt(
except Exception: data,
logger.exception('Decripting: %s', value) padding.OAEP(
# logger.error(inspect.stack()) mgf=padding.MGF1(algorithm=hashes.SHA256()),
return 'decript error' algorithm=hashes.SHA256(),
label=None
)
)
except Exception: # If fails, try old method
try:
atfork()
decrypted = self._oldRsa.decrypt(encoders.decode(value, 'base64'))
except Exception:
logger.exception('Decripting: %s', value)
# logger.error(inspect.stack())
return 'decript error'
return decrypted.decode()
def AESCrypt(self, text: bytes, key: bytes, base64: bool = False) -> bytes: def AESCrypt(self, text: bytes, key: bytes, base64: bool = False) -> bytes:
# First, match key to 16 bytes. If key is over 16, create a new one based on key of 16 bytes length # First, match key to 16 bytes. If key is over 16, create a new one based on key of 16 bytes length
cipher = AES.new(CryptoManager.AESKey(key, 16), AES.MODE_CBC, 'udsinitvectoruds') cipher = Cipher(algorithms.AES(CryptoManager.AESKey(key, 16)), modes.CBC(b'udsinitvectoruds'), backend=default_backend())
rndStr = self.randomString(cipher.block_size).encode('utf8') rndStr = self.randomString(16).encode() # Same as block size of CBC (that is 16 here)
paddedLength = ((len(text) + 4 + 15) // 16) * 16 paddedLength = ((len(text) + 4 + 15) // 16) * 16
toEncode = struct.pack('>i', len(text)) + text + rndStr[:paddedLength - len(text) - 4] toEncode = struct.pack('>i', len(text)) + text + rndStr[:paddedLength - len(text) - 4]
encoded = cipher.encrypt(toEncode) encryptor = cipher.encryptor()
encoded = encryptor.update(toEncode) + encryptor.finalize()
if base64: if base64:
return typing.cast(bytes, encoders.encode(encoded, 'base64', asText=False)) # Return as binary return typing.cast(bytes, encoders.encode(encoded, 'base64')) # Return as binary
return encoded return encoded
@ -125,8 +157,10 @@ class CryptoManager:
if base64: if base64:
text = typing.cast(bytes, encoders.decode(text, 'base64')) text = typing.cast(bytes, encoders.decode(text, 'base64'))
cipher = AES.new(CryptoManager.AESKey(key, 16), AES.MODE_CBC, 'udsinitvectoruds') cipher = Cipher(algorithms.AES(CryptoManager.AESKey(key, 16)), modes.CBC(b'udsinitvectoruds'), backend=default_backend())
toDecode = cipher.decrypt(text) decryptor = cipher.decryptor()
toDecode = decryptor.update(text) + decryptor.finalize()
return toDecode[4:4 + struct.unpack('>i', toDecode[:4])[0]] return toDecode[4:4 + struct.unpack('>i', toDecode[:4])[0]]
def xor(self, s1: typing.Union[str, bytes], s2: typing.Union[str, bytes]) -> bytes: def xor(self, s1: typing.Union[str, bytes], s2: typing.Union[str, bytes]) -> bytes:
@ -153,12 +187,15 @@ class CryptoManager:
raise e raise e
return pk return pk
def loadCertificate(self, certificate: str): def loadCertificate(self, certificate: typing.Union[str, bytes]):
if isinstance(certificate, str):
certificate = certificate.encode()
# If invalid certificate, will raise an exception
try: try:
cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate) return x509.load_pem_x509_certificate(certificate, default_backend())
except crypto.Error as e: except Exception:
raise Exception(e.message[0][2]) raise Exception('Invalid certificate')
return cert
def certificateString(self, certificate: str) -> str: def certificateString(self, certificate: str) -> str:
return certificate.replace('-----BEGIN CERTIFICATE-----', '').replace('-----END CERTIFICATE-----', '').replace('\n', '') return certificate.replace('-----BEGIN CERTIFICATE-----', '').replace('-----END CERTIFICATE-----', '').replace('\n', '')