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