1# ------------------------------------------------------------------------- 2# Copyright (c) Microsoft Corporation. All rights reserved. 3# Licensed under the MIT License. See License.txt in the project root for 4# license information. 5# -------------------------------------------------------------------------- 6 7import os 8from os import urandom 9from json import ( 10 dumps, 11 loads, 12) 13from collections import OrderedDict 14 15from cryptography.hazmat.backends import default_backend 16from cryptography.hazmat.primitives.ciphers import Cipher 17from cryptography.hazmat.primitives.ciphers.algorithms import AES 18from cryptography.hazmat.primitives.ciphers.modes import CBC 19from cryptography.hazmat.primitives.padding import PKCS7 20 21from azure.core.exceptions import HttpResponseError 22 23from .._version import VERSION 24from . import encode_base64, decode_base64_to_bytes 25 26 27_ENCRYPTION_PROTOCOL_V1 = '1.0' 28_ERROR_OBJECT_INVALID = \ 29 '{0} does not define a complete interface. Value of {1} is either missing or invalid.' 30 31 32def _validate_not_none(param_name, param): 33 if param is None: 34 raise ValueError('{0} should not be None.'.format(param_name)) 35 36 37def _validate_key_encryption_key_wrap(kek): 38 # Note that None is not callable and so will fail the second clause of each check. 39 if not hasattr(kek, 'wrap_key') or not callable(kek.wrap_key): 40 raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'wrap_key')) 41 if not hasattr(kek, 'get_kid') or not callable(kek.get_kid): 42 raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'get_kid')) 43 if not hasattr(kek, 'get_key_wrap_algorithm') or not callable(kek.get_key_wrap_algorithm): 44 raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'get_key_wrap_algorithm')) 45 46 47class _EncryptionAlgorithm(object): 48 ''' 49 Specifies which client encryption algorithm is used. 50 ''' 51 AES_CBC_256 = 'AES_CBC_256' 52 53 54class _WrappedContentKey: 55 ''' 56 Represents the envelope key details stored on the service. 57 ''' 58 59 def __init__(self, algorithm, encrypted_key, key_id): 60 ''' 61 :param str algorithm: 62 The algorithm used for wrapping. 63 :param bytes encrypted_key: 64 The encrypted content-encryption-key. 65 :param str key_id: 66 The key-encryption-key identifier string. 67 ''' 68 69 _validate_not_none('algorithm', algorithm) 70 _validate_not_none('encrypted_key', encrypted_key) 71 _validate_not_none('key_id', key_id) 72 73 self.algorithm = algorithm 74 self.encrypted_key = encrypted_key 75 self.key_id = key_id 76 77 78class _EncryptionAgent: 79 ''' 80 Represents the encryption agent stored on the service. 81 It consists of the encryption protocol version and encryption algorithm used. 82 ''' 83 84 def __init__(self, encryption_algorithm, protocol): 85 ''' 86 :param _EncryptionAlgorithm encryption_algorithm: 87 The algorithm used for encrypting the message contents. 88 :param str protocol: 89 The protocol version used for encryption. 90 ''' 91 92 _validate_not_none('encryption_algorithm', encryption_algorithm) 93 _validate_not_none('protocol', protocol) 94 95 self.encryption_algorithm = str(encryption_algorithm) 96 self.protocol = protocol 97 98 99class _EncryptionData: 100 ''' 101 Represents the encryption data that is stored on the service. 102 ''' 103 104 def __init__(self, content_encryption_IV, encryption_agent, wrapped_content_key, 105 key_wrapping_metadata): 106 ''' 107 :param bytes content_encryption_IV: 108 The content encryption initialization vector. 109 :param _EncryptionAgent encryption_agent: 110 The encryption agent. 111 :param _WrappedContentKey wrapped_content_key: 112 An object that stores the wrapping algorithm, the key identifier, 113 and the encrypted key bytes. 114 :param dict key_wrapping_metadata: 115 A dict containing metadata related to the key wrapping. 116 ''' 117 118 _validate_not_none('content_encryption_IV', content_encryption_IV) 119 _validate_not_none('encryption_agent', encryption_agent) 120 _validate_not_none('wrapped_content_key', wrapped_content_key) 121 122 self.content_encryption_IV = content_encryption_IV 123 self.encryption_agent = encryption_agent 124 self.wrapped_content_key = wrapped_content_key 125 self.key_wrapping_metadata = key_wrapping_metadata 126 127 128def _generate_encryption_data_dict(kek, cek, iv): 129 ''' 130 Generates and returns the encryption metadata as a dict. 131 132 :param object kek: The key encryption key. See calling functions for more information. 133 :param bytes cek: The content encryption key. 134 :param bytes iv: The initialization vector. 135 :return: A dict containing all the encryption metadata. 136 :rtype: dict 137 ''' 138 # Encrypt the cek. 139 wrapped_cek = kek.wrap_key(cek) 140 141 # Build the encryption_data dict. 142 # Use OrderedDict to comply with Java's ordering requirement. 143 wrapped_content_key = OrderedDict() 144 wrapped_content_key['KeyId'] = kek.get_kid() 145 wrapped_content_key['EncryptedKey'] = encode_base64(wrapped_cek) 146 wrapped_content_key['Algorithm'] = kek.get_key_wrap_algorithm() 147 148 encryption_agent = OrderedDict() 149 encryption_agent['Protocol'] = _ENCRYPTION_PROTOCOL_V1 150 encryption_agent['EncryptionAlgorithm'] = _EncryptionAlgorithm.AES_CBC_256 151 152 encryption_data_dict = OrderedDict() 153 encryption_data_dict['WrappedContentKey'] = wrapped_content_key 154 encryption_data_dict['EncryptionAgent'] = encryption_agent 155 encryption_data_dict['ContentEncryptionIV'] = encode_base64(iv) 156 encryption_data_dict['KeyWrappingMetadata'] = {'EncryptionLibrary': 'Python ' + VERSION} 157 158 return encryption_data_dict 159 160 161def _dict_to_encryption_data(encryption_data_dict): 162 ''' 163 Converts the specified dictionary to an EncryptionData object for 164 eventual use in decryption. 165 166 :param dict encryption_data_dict: 167 The dictionary containing the encryption data. 168 :return: an _EncryptionData object built from the dictionary. 169 :rtype: _EncryptionData 170 ''' 171 try: 172 if encryption_data_dict['EncryptionAgent']['Protocol'] != _ENCRYPTION_PROTOCOL_V1: 173 raise ValueError("Unsupported encryption version.") 174 except KeyError: 175 raise ValueError("Unsupported encryption version.") 176 wrapped_content_key = encryption_data_dict['WrappedContentKey'] 177 wrapped_content_key = _WrappedContentKey(wrapped_content_key['Algorithm'], 178 decode_base64_to_bytes(wrapped_content_key['EncryptedKey']), 179 wrapped_content_key['KeyId']) 180 181 encryption_agent = encryption_data_dict['EncryptionAgent'] 182 encryption_agent = _EncryptionAgent(encryption_agent['EncryptionAlgorithm'], 183 encryption_agent['Protocol']) 184 185 if 'KeyWrappingMetadata' in encryption_data_dict: 186 key_wrapping_metadata = encryption_data_dict['KeyWrappingMetadata'] 187 else: 188 key_wrapping_metadata = None 189 190 encryption_data = _EncryptionData(decode_base64_to_bytes(encryption_data_dict['ContentEncryptionIV']), 191 encryption_agent, 192 wrapped_content_key, 193 key_wrapping_metadata) 194 195 return encryption_data 196 197 198def _generate_AES_CBC_cipher(cek, iv): 199 ''' 200 Generates and returns an encryption cipher for AES CBC using the given cek and iv. 201 202 :param bytes[] cek: The content encryption key for the cipher. 203 :param bytes[] iv: The initialization vector for the cipher. 204 :return: A cipher for encrypting in AES256 CBC. 205 :rtype: ~cryptography.hazmat.primitives.ciphers.Cipher 206 ''' 207 208 backend = default_backend() 209 algorithm = AES(cek) 210 mode = CBC(iv) 211 return Cipher(algorithm, mode, backend) 212 213 214def _validate_and_unwrap_cek(encryption_data, key_encryption_key=None, key_resolver=None): 215 ''' 216 Extracts and returns the content_encryption_key stored in the encryption_data object 217 and performs necessary validation on all parameters. 218 :param _EncryptionData encryption_data: 219 The encryption metadata of the retrieved value. 220 :param obj key_encryption_key: 221 The key_encryption_key used to unwrap the cek. Please refer to high-level service object 222 instance variables for more details. 223 :param func key_resolver: 224 A function used that, given a key_id, will return a key_encryption_key. Please refer 225 to high-level service object instance variables for more details. 226 :return: the content_encryption_key stored in the encryption_data object. 227 :rtype: bytes[] 228 ''' 229 230 _validate_not_none('content_encryption_IV', encryption_data.content_encryption_IV) 231 _validate_not_none('encrypted_key', encryption_data.wrapped_content_key.encrypted_key) 232 233 if _ENCRYPTION_PROTOCOL_V1 != encryption_data.encryption_agent.protocol: 234 raise ValueError('Encryption version is not supported.') 235 236 content_encryption_key = None 237 238 # If the resolver exists, give priority to the key it finds. 239 if key_resolver is not None: 240 key_encryption_key = key_resolver(encryption_data.wrapped_content_key.key_id) 241 242 _validate_not_none('key_encryption_key', key_encryption_key) 243 if not hasattr(key_encryption_key, 'get_kid') or not callable(key_encryption_key.get_kid): 244 raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'get_kid')) 245 if not hasattr(key_encryption_key, 'unwrap_key') or not callable(key_encryption_key.unwrap_key): 246 raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'unwrap_key')) 247 if encryption_data.wrapped_content_key.key_id != key_encryption_key.get_kid(): 248 raise ValueError('Provided or resolved key-encryption-key does not match the id of key used to encrypt.') 249 # Will throw an exception if the specified algorithm is not supported. 250 content_encryption_key = key_encryption_key.unwrap_key(encryption_data.wrapped_content_key.encrypted_key, 251 encryption_data.wrapped_content_key.algorithm) 252 _validate_not_none('content_encryption_key', content_encryption_key) 253 254 return content_encryption_key 255 256 257def _decrypt_message(message, encryption_data, key_encryption_key=None, resolver=None): 258 ''' 259 Decrypts the given ciphertext using AES256 in CBC mode with 128 bit padding. 260 Unwraps the content-encryption-key using the user-provided or resolved key-encryption-key (kek). 261 Returns the original plaintex. 262 263 :param str message: 264 The ciphertext to be decrypted. 265 :param _EncryptionData encryption_data: 266 The metadata associated with this ciphertext. 267 :param object key_encryption_key: 268 The user-provided key-encryption-key. Must implement the following methods: 269 unwrap_key(key, algorithm) 270 - returns the unwrapped form of the specified symmetric key using the string-specified algorithm. 271 get_kid() 272 - returns a string key id for this key-encryption-key. 273 :param function resolver(kid): 274 The user-provided key resolver. Uses the kid string to return a key-encryption-key 275 implementing the interface defined above. 276 :return: The decrypted plaintext. 277 :rtype: str 278 ''' 279 _validate_not_none('message', message) 280 content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, resolver) 281 282 if _EncryptionAlgorithm.AES_CBC_256 != encryption_data.encryption_agent.encryption_algorithm: 283 raise ValueError('Specified encryption algorithm is not supported.') 284 285 cipher = _generate_AES_CBC_cipher(content_encryption_key, encryption_data.content_encryption_IV) 286 287 # decrypt data 288 decrypted_data = message 289 decryptor = cipher.decryptor() 290 decrypted_data = (decryptor.update(decrypted_data) + decryptor.finalize()) 291 292 # unpad data 293 unpadder = PKCS7(128).unpadder() 294 decrypted_data = (unpadder.update(decrypted_data) + unpadder.finalize()) 295 296 return decrypted_data 297 298 299def encrypt_blob(blob, key_encryption_key): 300 ''' 301 Encrypts the given blob using AES256 in CBC mode with 128 bit padding. 302 Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek). 303 Returns a json-formatted string containing the encryption metadata. This method should 304 only be used when a blob is small enough for single shot upload. Encrypting larger blobs 305 is done as a part of the upload_data_chunks method. 306 307 :param bytes blob: 308 The blob to be encrypted. 309 :param object key_encryption_key: 310 The user-provided key-encryption-key. Must implement the following methods: 311 wrap_key(key)--wraps the specified key using an algorithm of the user's choice. 312 get_key_wrap_algorithm()--returns the algorithm used to wrap the specified symmetric key. 313 get_kid()--returns a string key id for this key-encryption-key. 314 :return: A tuple of json-formatted string containing the encryption metadata and the encrypted blob data. 315 :rtype: (str, bytes) 316 ''' 317 318 _validate_not_none('blob', blob) 319 _validate_not_none('key_encryption_key', key_encryption_key) 320 _validate_key_encryption_key_wrap(key_encryption_key) 321 322 # AES256 uses 256 bit (32 byte) keys and always with 16 byte blocks 323 content_encryption_key = urandom(32) 324 initialization_vector = urandom(16) 325 326 cipher = _generate_AES_CBC_cipher(content_encryption_key, initialization_vector) 327 328 # PKCS7 with 16 byte blocks ensures compatibility with AES. 329 padder = PKCS7(128).padder() 330 padded_data = padder.update(blob) + padder.finalize() 331 332 # Encrypt the data. 333 encryptor = cipher.encryptor() 334 encrypted_data = encryptor.update(padded_data) + encryptor.finalize() 335 encryption_data = _generate_encryption_data_dict(key_encryption_key, content_encryption_key, 336 initialization_vector) 337 encryption_data['EncryptionMode'] = 'FullBlob' 338 339 return dumps(encryption_data), encrypted_data 340 341 342def generate_blob_encryption_data(key_encryption_key): 343 ''' 344 Generates the encryption_metadata for the blob. 345 346 :param bytes key_encryption_key: 347 The key-encryption-key used to wrap the cek associate with this blob. 348 :return: A tuple containing the cek and iv for this blob as well as the 349 serialized encryption metadata for the blob. 350 :rtype: (bytes, bytes, str) 351 ''' 352 encryption_data = None 353 content_encryption_key = None 354 initialization_vector = None 355 if key_encryption_key: 356 _validate_key_encryption_key_wrap(key_encryption_key) 357 content_encryption_key = urandom(32) 358 initialization_vector = urandom(16) 359 encryption_data = _generate_encryption_data_dict(key_encryption_key, 360 content_encryption_key, 361 initialization_vector) 362 encryption_data['EncryptionMode'] = 'FullBlob' 363 encryption_data = dumps(encryption_data) 364 365 return content_encryption_key, initialization_vector, encryption_data 366 367 368def decrypt_blob(require_encryption, key_encryption_key, key_resolver, 369 content, start_offset, end_offset, response_headers): 370 ''' 371 Decrypts the given blob contents and returns only the requested range. 372 373 :param bool require_encryption: 374 Whether or not the calling blob service requires objects to be decrypted. 375 :param object key_encryption_key: 376 The user-provided key-encryption-key. Must implement the following methods: 377 wrap_key(key)--wraps the specified key using an algorithm of the user's choice. 378 get_key_wrap_algorithm()--returns the algorithm used to wrap the specified symmetric key. 379 get_kid()--returns a string key id for this key-encryption-key. 380 :param key_resolver(kid): 381 The user-provided key resolver. Uses the kid string to return a key-encryption-key 382 implementing the interface defined above. 383 :return: The decrypted blob content. 384 :rtype: bytes 385 ''' 386 try: 387 encryption_data = _dict_to_encryption_data(loads(response_headers['x-ms-meta-encryptiondata'])) 388 except: # pylint: disable=bare-except 389 if require_encryption: 390 raise ValueError( 391 'Encryption required, but received data does not contain appropriate metatadata.' + \ 392 'Data was either not encrypted or metadata has been lost.') 393 394 return content 395 396 if encryption_data.encryption_agent.encryption_algorithm != _EncryptionAlgorithm.AES_CBC_256: 397 raise ValueError('Specified encryption algorithm is not supported.') 398 399 blob_type = response_headers['x-ms-blob-type'] 400 401 iv = None 402 unpad = False 403 if 'content-range' in response_headers: 404 content_range = response_headers['content-range'] 405 # Format: 'bytes x-y/size' 406 407 # Ignore the word 'bytes' 408 content_range = content_range.split(' ') 409 410 content_range = content_range[1].split('-') 411 content_range = content_range[1].split('/') 412 end_range = int(content_range[0]) 413 blob_size = int(content_range[1]) 414 415 if start_offset >= 16: 416 iv = content[:16] 417 content = content[16:] 418 start_offset -= 16 419 else: 420 iv = encryption_data.content_encryption_IV 421 422 if end_range == blob_size - 1: 423 unpad = True 424 else: 425 unpad = True 426 iv = encryption_data.content_encryption_IV 427 428 if blob_type == 'PageBlob': 429 unpad = False 430 431 content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, key_resolver) 432 cipher = _generate_AES_CBC_cipher(content_encryption_key, iv) 433 decryptor = cipher.decryptor() 434 435 content = decryptor.update(content) + decryptor.finalize() 436 if unpad: 437 unpadder = PKCS7(128).unpadder() 438 content = unpadder.update(content) + unpadder.finalize() 439 440 return content[start_offset: len(content) - end_offset] 441 442 443def get_blob_encryptor_and_padder(cek, iv, should_pad): 444 encryptor = None 445 padder = None 446 447 if cek is not None and iv is not None: 448 cipher = _generate_AES_CBC_cipher(cek, iv) 449 encryptor = cipher.encryptor() 450 padder = PKCS7(128).padder() if should_pad else None 451 452 return encryptor, padder 453 454 455def encrypt_queue_message(message, key_encryption_key): 456 ''' 457 Encrypts the given plain text message using AES256 in CBC mode with 128 bit padding. 458 Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek). 459 Returns a json-formatted string containing the encrypted message and the encryption metadata. 460 461 :param object message: 462 The plain text messge to be encrypted. 463 :param object key_encryption_key: 464 The user-provided key-encryption-key. Must implement the following methods: 465 wrap_key(key)--wraps the specified key using an algorithm of the user's choice. 466 get_key_wrap_algorithm()--returns the algorithm used to wrap the specified symmetric key. 467 get_kid()--returns a string key id for this key-encryption-key. 468 :return: A json-formatted string containing the encrypted message and the encryption metadata. 469 :rtype: str 470 ''' 471 472 _validate_not_none('message', message) 473 _validate_not_none('key_encryption_key', key_encryption_key) 474 _validate_key_encryption_key_wrap(key_encryption_key) 475 476 # AES256 uses 256 bit (32 byte) keys and always with 16 byte blocks 477 content_encryption_key = os.urandom(32) 478 initialization_vector = os.urandom(16) 479 480 # Queue encoding functions all return unicode strings, and encryption should 481 # operate on binary strings. 482 message = message.encode('utf-8') 483 484 cipher = _generate_AES_CBC_cipher(content_encryption_key, initialization_vector) 485 486 # PKCS7 with 16 byte blocks ensures compatibility with AES. 487 padder = PKCS7(128).padder() 488 padded_data = padder.update(message) + padder.finalize() 489 490 # Encrypt the data. 491 encryptor = cipher.encryptor() 492 encrypted_data = encryptor.update(padded_data) + encryptor.finalize() 493 494 # Build the dictionary structure. 495 queue_message = {'EncryptedMessageContents': encode_base64(encrypted_data), 496 'EncryptionData': _generate_encryption_data_dict(key_encryption_key, 497 content_encryption_key, 498 initialization_vector)} 499 500 return dumps(queue_message) 501 502 503def decrypt_queue_message(message, response, require_encryption, key_encryption_key, resolver): 504 ''' 505 Returns the decrypted message contents from an EncryptedQueueMessage. 506 If no encryption metadata is present, will return the unaltered message. 507 :param str message: 508 The JSON formatted QueueEncryptedMessage contents with all associated metadata. 509 :param bool require_encryption: 510 If set, will enforce that the retrieved messages are encrypted and decrypt them. 511 :param object key_encryption_key: 512 The user-provided key-encryption-key. Must implement the following methods: 513 unwrap_key(key, algorithm) 514 - returns the unwrapped form of the specified symmetric key usingthe string-specified algorithm. 515 get_kid() 516 - returns a string key id for this key-encryption-key. 517 :param function resolver(kid): 518 The user-provided key resolver. Uses the kid string to return a key-encryption-key 519 implementing the interface defined above. 520 :return: The plain text message from the queue message. 521 :rtype: str 522 ''' 523 524 try: 525 message = loads(message) 526 527 encryption_data = _dict_to_encryption_data(message['EncryptionData']) 528 decoded_data = decode_base64_to_bytes(message['EncryptedMessageContents']) 529 except (KeyError, ValueError): 530 # Message was not json formatted and so was not encrypted 531 # or the user provided a json formatted message. 532 if require_encryption: 533 raise ValueError('Message was not encrypted.') 534 535 return message 536 try: 537 return _decrypt_message(decoded_data, encryption_data, key_encryption_key, resolver).decode('utf-8') 538 except Exception as error: 539 raise HttpResponseError( 540 message="Decryption failed.", 541 response=response, 542 error=error) 543