##// END OF EJS Templates
encryption: unified and rewrote encryption modules to be consistent no matter what algo is used....
super-admin -
r4995:676da06b default
parent child Browse files
Show More
@@ -0,0 +1,48 b''
1 from rhodecode.lib.str_utils import safe_bytes
2 from rhodecode.lib.encrypt import encrypt_data, validate_and_decrypt_data
3 from rhodecode.lib.encrypt2 import Encryptor
4
5 ALLOWED_ALGOS = ['aes', 'fernet']
6
7
8 def get_default_algo():
9 import rhodecode
10 return rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
11
12
13 def encrypt_value(value: bytes, enc_key: bytes, algo: str = ''):
14 if not algo:
15 # not explicit algo, just use what's set by config
16 algo = get_default_algo()
17 if algo not in ALLOWED_ALGOS:
18 ValueError(f'Bad encryption algorithm, should be {ALLOWED_ALGOS}, got: {algo}')
19
20 enc_key = safe_bytes(enc_key)
21 value = safe_bytes(value)
22
23 if algo == 'aes':
24 return encrypt_data(value, enc_key=enc_key)
25 if algo == 'fernet':
26 return Encryptor(enc_key).encrypt(value)
27
28 return value
29
30
31 def decrypt_value(value: bytes, enc_key: bytes, algo: str = '', strict_mode: bool = False):
32
33 if not algo:
34 # not explicit algo, just use what's set by config
35 algo = get_default_algo()
36 if algo not in ALLOWED_ALGOS:
37 ValueError(f'Bad encryption algorithm, should be {ALLOWED_ALGOS}, got: {algo}')
38
39 enc_key = safe_bytes(enc_key)
40 value = safe_bytes(value)
41 safe = not strict_mode
42
43 if algo == 'aes':
44 return validate_and_decrypt_data(value, enc_key, safe=safe)
45 if algo == 'fernet':
46 return Encryptor(enc_key).decrypt(value, safe=safe)
47
48 return value
@@ -24,16 +24,14 b' Generic encryption library for RhodeCode'
24 """
24 """
25
25
26 import base64
26 import base64
27 import logging
27
28
28 from Crypto.Cipher import AES
29 from Crypto.Cipher import AES
29 from Crypto import Random
30 from Crypto import Random
30 from Crypto.Hash import HMAC, SHA256
31 from Crypto.Hash import HMAC, SHA256
31
32
32 from rhodecode.lib.str_utils import safe_bytes
33 from rhodecode.lib.str_utils import safe_bytes, safe_str
33
34 from rhodecode.lib.exceptions import signature_verification_error
34
35 class SignatureVerificationError(Exception):
36 pass
37
35
38
36
39 class InvalidDecryptedValue(str):
37 class InvalidDecryptedValue(str):
@@ -47,9 +45,13 b' class InvalidDecryptedValue(str):'
47 content = '<{}({}...)>'.format(cls.__name__, content[:16])
45 content = '<{}({}...)>'.format(cls.__name__, content[:16])
48 return str.__new__(cls, content)
46 return str.__new__(cls, content)
49
47
48 KEY_FORMAT = b'enc$aes_hmac${1}'
49
50
50
51 class AESCipher(object):
51 class AESCipher(object):
52 def __init__(self, key, hmac=False, strict_verification=True):
52
53 def __init__(self, key: bytes, hmac=False, strict_verification=True):
54
53 if not key:
55 if not key:
54 raise ValueError('passed key variable is empty')
56 raise ValueError('passed key variable is empty')
55 self.strict_verification = strict_verification
57 self.strict_verification = strict_verification
@@ -67,13 +69,13 b' class AESCipher(object):'
67 self.hmac_key, data_without_sig, digestmod=SHA256).digest()
69 self.hmac_key, data_without_sig, digestmod=SHA256).digest()
68 return org_hmac_signature == recomputed_hmac
70 return org_hmac_signature == recomputed_hmac
69
71
70 def encrypt(self, raw):
72 def encrypt(self, raw: bytes):
71 raw = self._pad(raw)
73 raw = self._pad(raw)
72 iv = Random.new().read(AES.block_size)
74 iv = Random.new().read(AES.block_size)
73 cipher = AES.new(self.key, AES.MODE_CBC, iv)
75 cipher = AES.new(self.key, AES.MODE_CBC, iv)
74 enc_value = cipher.encrypt(raw)
76 enc_value = cipher.encrypt(raw)
75
77
76 hmac_signature = ''
78 hmac_signature = b''
77 if self.hmac:
79 if self.hmac:
78 # compute hmac+sha256 on iv + enc text, we use
80 # compute hmac+sha256 on iv + enc text, we use
79 # encrypt then mac method to create the signature
81 # encrypt then mac method to create the signature
@@ -82,57 +84,69 b' class AESCipher(object):'
82
84
83 return base64.b64encode(iv + enc_value + hmac_signature)
85 return base64.b64encode(iv + enc_value + hmac_signature)
84
86
85 def decrypt(self, enc):
87 def decrypt(self, enc, safe=True) -> bytes | InvalidDecryptedValue:
86 enc_org = enc
88 enc_org = enc
89 try:
87 enc = base64.b64decode(enc)
90 enc = base64.b64decode(enc)
91 except Exception:
92 logging.exception('Failed Base64 decode')
93 raise signature_verification_error('Failed Base64 decode')
88
94
89 if self.hmac and len(enc) > self.hmac_size:
95 if self.hmac and len(enc) > self.hmac_size:
90 if self.verify_hmac_signature(enc):
96 if self.verify_hmac_signature(enc):
91 # cut off the HMAC verification digest
97 # cut off the HMAC verification digest
92 enc = enc[:-self.hmac_size]
98 enc = enc[:-self.hmac_size]
93 else:
99 else:
94 if self.strict_verification:
95 raise SignatureVerificationError(
96 "Encryption signature verification failed. "
97 "Please check your secret key, and/or encrypted value. "
98 "Secret key is stored as "
99 "`rhodecode.encrypted_values.secret` or "
100 "`beaker.session.secret` inside .ini file")
101
100
102 return InvalidDecryptedValue(enc_org)
101 decrypt_fail = InvalidDecryptedValue(safe_str(enc_org))
102 if safe:
103 return decrypt_fail
104 raise signature_verification_error(decrypt_fail)
103
105
104 iv = enc[:AES.block_size]
106 iv = enc[:AES.block_size]
105 cipher = AES.new(self.key, AES.MODE_CBC, iv)
107 cipher = AES.new(self.key, AES.MODE_CBC, iv)
106 return self._unpad(cipher.decrypt(enc[AES.block_size:]))
108 return self._unpad(cipher.decrypt(enc[AES.block_size:]))
107
109
108 def _pad(self, s):
110 def _pad(self, s):
109 return (s + (self.block_size - len(s) % self.block_size)
111 block_pad = (self.block_size - len(s) % self.block_size)
110 * chr(self.block_size - len(s) % self.block_size))
112 return s + block_pad * safe_bytes(chr(block_pad))
111
113
112 @staticmethod
114 @staticmethod
113 def _unpad(s):
115 def _unpad(s):
114 return s[:-ord(s[len(s)-1:])]
116 return s[:-ord(s[len(s)-1:])]
115
117
116
118
117 def validate_and_get_enc_data(enc_data, enc_key, enc_strict_mode):
119 def validate_and_decrypt_data(enc_data, enc_key, enc_strict_mode=False, safe=True):
120 enc_data = safe_str(enc_data)
121
118 parts = enc_data.split('$', 3)
122 parts = enc_data.split('$', 3)
119 if not len(parts) == 3:
123 if len(parts) != 3:
120 # probably not encrypted values
124 raise ValueError(f'Encrypted Data has invalid format, expected {KEY_FORMAT}, got {parts}')
121 return enc_data
125
122 else:
126 enc_type = parts[1]
127 enc_data_part = parts[2]
128
123 if parts[0] != 'enc':
129 if parts[0] != 'enc':
124 # parts ok but without our header ?
130 # parts ok but without our header ?
125 return enc_data
131 return enc_data
126
132
127 # at that stage we know it's our encryption
133 # at that stage we know it's our encryption
128 if parts[1] == 'aes':
134 if enc_type == 'aes':
129 decrypted_data = AESCipher(enc_key).decrypt(parts[2])
135 decrypted_data = AESCipher(enc_key).decrypt(enc_data_part, safe=safe)
130 elif parts[1] == 'aes_hmac':
136 elif enc_type == 'aes_hmac':
131 decrypted_data = AESCipher(
137 decrypted_data = AESCipher(
132 enc_key, hmac=True,
138 enc_key, hmac=True,
133 strict_verification=enc_strict_mode).decrypt(parts[2])
139 strict_verification=enc_strict_mode).decrypt(enc_data_part, safe=safe)
140
134 else:
141 else:
135 raise ValueError(
142 raise ValueError(
136 'Encryption type part is wrong, must be `aes` '
143 f'Encryption type part is wrong, must be `aes` '
137 'or `aes_hmac`, got `%s` instead' % (parts[1]))
144 f'or `aes_hmac`, got `{enc_type}` instead')
145
138 return decrypted_data
146 return decrypted_data
147
148
149 def encrypt_data(data, enc_key: bytes):
150 enc_key = safe_bytes(enc_key)
151 enc_value = AESCipher(enc_key, hmac=True).encrypt(safe_bytes(data))
152 return KEY_FORMAT.replace(b'{1}', enc_value)
@@ -5,12 +5,27 b' from cryptography.hazmat.backends import'
5 from cryptography.hazmat.primitives import hashes
5 from cryptography.hazmat.primitives import hashes
6 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
6 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
7
7
8 from rhodecode.lib.str_utils import safe_str
9 from rhodecode.lib.exceptions import signature_verification_error
10
11
12 class InvalidDecryptedValue(str):
13
14 def __new__(cls, content):
15 """
16 This will generate something like this::
17 <InvalidDecryptedValue(QkWusFgLJXR6m42v...)>
18 And represent a safe indicator that encryption key is broken
19 """
20 content = '<{}({}...)>'.format(cls.__name__, content[:16])
21 return str.__new__(cls, content)
22
8
23
9 class Encryptor(object):
24 class Encryptor(object):
10 key_format = 'enc2$salt:{}$data:{}'
25 key_format = b'enc2$salt:{1}$data:{2}'
11 pref_len = 5 # salt:, data:
26 pref_len = 5 # salt:, data:
12
27
13 def __init__(self, enc_key):
28 def __init__(self, enc_key: bytes):
14 self.enc_key = enc_key
29 self.enc_key = enc_key
15
30
16 def b64_encode(self, data):
31 def b64_encode(self, data):
@@ -35,9 +50,9 b' class Encryptor(object):'
35 return Fernet(key)
50 return Fernet(key)
36
51
37 def _get_parts(self, enc_data):
52 def _get_parts(self, enc_data):
38 parts = enc_data.split('$', 3)
53 parts = enc_data.split(b'$', 3)
39 if len(parts) != 3:
54 if len(parts) != 3:
40 raise ValueError('Encrypted Data has invalid format, expected {}'.format(self.key_format))
55 raise ValueError(f'Encrypted Data has invalid format, expected {self.key_format}, got {parts}')
41 prefix, salt, enc_data = parts
56 prefix, salt, enc_data = parts
42
57
43 try:
58 try:
@@ -49,13 +64,13 b' class Encryptor(object):'
49 enc_data = enc_data[self.pref_len:]
64 enc_data = enc_data[self.pref_len:]
50 return prefix, salt, enc_data
65 return prefix, salt, enc_data
51
66
52 def encrypt(self, data):
67 def encrypt(self, data) -> bytes:
53 salt = os.urandom(64)
68 salt = os.urandom(64)
54 encryptor = self.get_encryptor(salt)
69 encryptor = self.get_encryptor(salt)
55 enc_data = encryptor.encrypt(data)
70 enc_data = encryptor.encrypt(data)
56 return self.key_format.format(self.b64_encode(salt), enc_data)
71 return self.key_format.replace(b'{1}', self.b64_encode(salt)).replace(b'{2}', enc_data)
57
72
58 def decrypt(self, data, safe=True):
73 def decrypt(self, data, safe=True) -> bytes | InvalidDecryptedValue:
59 parts = self._get_parts(data)
74 parts = self._get_parts(data)
60 salt = parts[1]
75 salt = parts[1]
61 enc_data = parts[2]
76 enc_data = parts[2]
@@ -63,7 +78,7 b' class Encryptor(object):'
63 try:
78 try:
64 return encryptor.decrypt(enc_data)
79 return encryptor.decrypt(enc_data)
65 except (InvalidToken,):
80 except (InvalidToken,):
81 decrypt_fail = InvalidDecryptedValue(safe_str(data))
66 if safe:
82 if safe:
67 return ''
83 return decrypt_fail
68 else:
84 raise signature_verification_error(decrypt_fail)
69 raise
@@ -63,10 +63,10 b' from rhodecode.lib.utils2 import ('
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib import ext_json
67 from rhodecode.lib import enc_utils
66 from rhodecode.lib.ext_json import json
68 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
69 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
@@ -81,7 +81,7 b' log = logging.getLogger(__name__)'
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = ''
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
@@ -184,12 +184,7 b' class EncryptedTextValue(TypeDecorator):'
184 'ie. not starting with enc$ or enc2$')
184 'ie. not starting with enc$ or enc2$')
185
185
186 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
186 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
187 if algo == 'aes':
187 return enc_utils.encrypt_value(value, enc_key=ENCRYPTION_KEY, algo=algo)
188 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
189 elif algo == 'fernet':
190 return Encryptor(ENCRYPTION_KEY).encrypt(value)
191 else:
192 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
193
188
194 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
195 """
190 """
@@ -200,15 +195,9 b' class EncryptedTextValue(TypeDecorator):'
200 if not value:
195 if not value:
201 return value
196 return value
202
197
203 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
204 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
198 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
205 if algo == 'aes':
199
206 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
200 return enc_utils.decrypt_value(value, enc_key=ENCRYPTION_KEY, strict_mode=enc_strict_mode)
207 elif algo == 'fernet':
208 return Encryptor(ENCRYPTION_KEY).decrypt(value)
209 else:
210 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
211 return decrypted_data
212
201
213
202
214 class BaseModel(object):
203 class BaseModel(object):
@@ -308,13 +297,13 b' class BaseModel(object):'
308 attr_name, value, exist_in_session)
297 attr_name, value, exist_in_session)
309
298
310 def __repr__(self):
299 def __repr__(self):
311 if hasattr(self, '__unicode__'):
300 if hasattr(self, '__str__'):
312 # python repr needs to return str
301 # python repr needs to return str
313 try:
302 try:
314 return safe_str(self.__unicode__())
303 return self.__str__()
315 except UnicodeDecodeError:
304 except UnicodeDecodeError:
316 pass
305 pass
317 return '<DB:%s>' % (self.__class__.__name__)
306 return f'<DB:{self.__class__.__name__}>'
318
307
319
308
320 class RhodeCodeSetting(Base, BaseModel):
309 class RhodeCodeSetting(Base, BaseModel):
@@ -358,7 +347,7 b' class RhodeCodeSetting(Base, BaseModel):'
358 # decode the encrypted value
347 # decode the encrypted value
359 if 'encrypted' in self.app_settings_type:
348 if 'encrypted' in self.app_settings_type:
360 cipher = EncryptedTextValue()
349 cipher = EncryptedTextValue()
361 v = safe_unicode(cipher.process_result_value(v, None))
350 v = safe_str(cipher.process_result_value(v, None))
362
351
363 converter = self.SETTINGS_TYPES.get(_type) or \
352 converter = self.SETTINGS_TYPES.get(_type) or \
364 self.SETTINGS_TYPES['unicode']
353 self.SETTINGS_TYPES['unicode']
@@ -375,7 +364,7 b' class RhodeCodeSetting(Base, BaseModel):'
375 # encode the encrypted value
364 # encode the encrypted value
376 if 'encrypted' in self.app_settings_type:
365 if 'encrypted' in self.app_settings_type:
377 cipher = EncryptedTextValue()
366 cipher = EncryptedTextValue()
378 val = safe_unicode(cipher.process_bind_param(val, None))
367 val = safe_str(cipher.process_bind_param(val, None))
379 self._app_settings_value = val
368 self._app_settings_value = val
380
369
381 @hybrid_property
370 @hybrid_property
@@ -395,8 +384,8 b' class RhodeCodeSetting(Base, BaseModel):'
395 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
384 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
396 .all()
385 .all()
397
386
398 def __unicode__(self):
387 def __str__(self):
399 return u"<%s('%s:%s[%s]')>" % (
388 return "<%s('%s:%s[%s]')>" % (
400 self.__class__.__name__,
389 self.__class__.__name__,
401 self.app_settings_name, self.app_settings_value,
390 self.app_settings_name, self.app_settings_value,
402 self.app_settings_type
391 self.app_settings_type
General Comments 0
You need to be logged in to leave comments. Login now