azure-storage-blob 12.19.0b1__py3-none-any.whl → 12.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- azure/storage/blob/__init__.py +17 -5
- azure/storage/blob/_blob_client.py +26 -6
- azure/storage/blob/_blob_service_client.py +11 -4
- azure/storage/blob/_container_client.py +45 -17
- azure/storage/blob/_download.py +5 -4
- azure/storage/blob/_encryption.py +254 -165
- azure/storage/blob/_generated/_azure_blob_storage.py +21 -3
- azure/storage/blob/_generated/_configuration.py +4 -11
- azure/storage/blob/_generated/_serialization.py +41 -49
- azure/storage/blob/_generated/aio/_azure_blob_storage.py +23 -3
- azure/storage/blob/_generated/aio/_configuration.py +4 -11
- azure/storage/blob/_generated/aio/operations/_append_blob_operations.py +24 -58
- azure/storage/blob/_generated/aio/operations/_blob_operations.py +123 -306
- azure/storage/blob/_generated/aio/operations/_block_blob_operations.py +37 -86
- azure/storage/blob/_generated/aio/operations/_container_operations.py +98 -289
- azure/storage/blob/_generated/aio/operations/_page_blob_operations.py +51 -150
- azure/storage/blob/_generated/aio/operations/_service_operations.py +49 -125
- azure/storage/blob/_generated/models/_models_py3.py +31 -31
- azure/storage/blob/_generated/operations/_append_blob_operations.py +25 -59
- azure/storage/blob/_generated/operations/_blob_operations.py +123 -306
- azure/storage/blob/_generated/operations/_block_blob_operations.py +39 -88
- azure/storage/blob/_generated/operations/_container_operations.py +100 -291
- azure/storage/blob/_generated/operations/_page_blob_operations.py +52 -151
- azure/storage/blob/_generated/operations/_service_operations.py +50 -126
- azure/storage/blob/_lease.py +1 -0
- azure/storage/blob/_models.py +3 -4
- azure/storage/blob/_serialize.py +1 -0
- azure/storage/blob/_shared/authentication.py +1 -1
- azure/storage/blob/_shared/avro/avro_io.py +0 -6
- azure/storage/blob/_shared/avro/avro_io_async.py +0 -6
- azure/storage/blob/_shared/avro/datafile.py +0 -4
- azure/storage/blob/_shared/avro/datafile_async.py +0 -4
- azure/storage/blob/_shared/avro/schema.py +4 -4
- azure/storage/blob/_shared/base_client.py +72 -87
- azure/storage/blob/_shared/base_client_async.py +115 -27
- azure/storage/blob/_shared/models.py +120 -27
- azure/storage/blob/_shared/parser.py +7 -6
- azure/storage/blob/_shared/policies.py +96 -66
- azure/storage/blob/_shared/policies_async.py +48 -21
- azure/storage/blob/_shared/response_handlers.py +14 -16
- azure/storage/blob/_shared/shared_access_signature.py +3 -3
- azure/storage/blob/_shared_access_signature.py +48 -33
- azure/storage/blob/_upload_helpers.py +4 -7
- azure/storage/blob/_version.py +1 -1
- azure/storage/blob/aio/__init__.py +13 -4
- azure/storage/blob/aio/_blob_client_async.py +17 -6
- azure/storage/blob/aio/_blob_service_client_async.py +6 -3
- azure/storage/blob/aio/_container_client_async.py +34 -13
- azure/storage/blob/aio/_download_async.py +11 -10
- azure/storage/blob/aio/_encryption_async.py +72 -0
- azure/storage/blob/aio/_lease_async.py +1 -1
- azure/storage/blob/aio/_upload_helpers.py +8 -10
- {azure_storage_blob-12.19.0b1.dist-info → azure_storage_blob-12.20.0.dist-info}/METADATA +11 -11
- azure_storage_blob-12.20.0.dist-info/RECORD +81 -0
- {azure_storage_blob-12.19.0b1.dist-info → azure_storage_blob-12.20.0.dist-info}/WHEEL +1 -1
- azure_storage_blob-12.19.0b1.dist-info/RECORD +0 -80
- {azure_storage_blob-12.19.0b1.dist-info → azure_storage_blob-12.20.0.dist-info}/LICENSE +0 -0
- {azure_storage_blob-12.19.0b1.dist-info → azure_storage_blob-12.20.0.dist-info}/top_level.txt +0 -0
@@ -5,8 +5,8 @@
|
|
5
5
|
# license information.
|
6
6
|
# --------------------------------------------------------------------------
|
7
7
|
|
8
|
-
import os
|
9
8
|
import math
|
9
|
+
import os
|
10
10
|
import sys
|
11
11
|
import warnings
|
12
12
|
from collections import OrderedDict
|
@@ -15,7 +15,9 @@ from json import (
|
|
15
15
|
dumps,
|
16
16
|
loads,
|
17
17
|
)
|
18
|
-
from typing import Any,
|
18
|
+
from typing import Any, Callable, Dict, IO, Optional, Tuple, TYPE_CHECKING
|
19
|
+
from typing import OrderedDict as TypedOrderedDict
|
20
|
+
from typing_extensions import Protocol
|
19
21
|
|
20
22
|
from cryptography.hazmat.backends import default_backend
|
21
23
|
from cryptography.hazmat.primitives.ciphers import Cipher
|
@@ -28,7 +30,12 @@ from azure.core.exceptions import HttpResponseError
|
|
28
30
|
from azure.core.utils import CaseInsensitiveDict
|
29
31
|
|
30
32
|
from ._version import VERSION
|
31
|
-
from ._shared import
|
33
|
+
from ._shared import decode_base64_to_bytes, encode_base64
|
34
|
+
|
35
|
+
if TYPE_CHECKING:
|
36
|
+
from azure.core.pipeline import PipelineResponse
|
37
|
+
from cryptography.hazmat.primitives.ciphers import AEADEncryptionContext
|
38
|
+
from cryptography.hazmat.primitives.padding import PaddingContext
|
32
39
|
|
33
40
|
|
34
41
|
_ENCRYPTION_PROTOCOL_V1 = '1.0'
|
@@ -41,12 +48,27 @@ _ERROR_OBJECT_INVALID = \
|
|
41
48
|
'{0} does not define a complete interface. Value of {1} is either missing or invalid.'
|
42
49
|
|
43
50
|
|
44
|
-
|
51
|
+
class KeyEncryptionKey(Protocol):
|
52
|
+
|
53
|
+
def wrap_key(self, key: bytes) -> bytes:
|
54
|
+
...
|
55
|
+
|
56
|
+
def unwrap_key(self, key: bytes, algorithm: str) -> bytes:
|
57
|
+
...
|
58
|
+
|
59
|
+
def get_kid(self) -> str:
|
60
|
+
...
|
61
|
+
|
62
|
+
def get_key_wrap_algorithm(self) -> str:
|
63
|
+
...
|
64
|
+
|
65
|
+
|
66
|
+
def _validate_not_none(param_name: str, param: Any):
|
45
67
|
if param is None:
|
46
68
|
raise ValueError(f'{param_name} should not be None.')
|
47
69
|
|
48
70
|
|
49
|
-
def _validate_key_encryption_key_wrap(kek):
|
71
|
+
def _validate_key_encryption_key_wrap(kek: KeyEncryptionKey):
|
50
72
|
# Note that None is not callable and so will fail the second clause of each check.
|
51
73
|
if not hasattr(kek, 'wrap_key') or not callable(kek.wrap_key):
|
52
74
|
raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'wrap_key'))
|
@@ -57,7 +79,7 @@ def _validate_key_encryption_key_wrap(kek):
|
|
57
79
|
|
58
80
|
|
59
81
|
class StorageEncryptionMixin(object):
|
60
|
-
def _configure_encryption(self, kwargs):
|
82
|
+
def _configure_encryption(self, kwargs: Dict[str, Any]):
|
61
83
|
self.require_encryption = kwargs.get("require_encryption", False)
|
62
84
|
self.encryption_version = kwargs.get("encryption_version", "1.0")
|
63
85
|
self.key_encryption_key = kwargs.get("key_encryption_key")
|
@@ -80,15 +102,17 @@ class _EncryptionAlgorithm(object):
|
|
80
102
|
class _WrappedContentKey:
|
81
103
|
"""
|
82
104
|
Represents the envelope key details stored on the service.
|
83
|
-
|
84
|
-
:param str algorithm:
|
85
|
-
The algorithm used for wrapping.
|
86
|
-
:param bytes encrypted_key:
|
87
|
-
The encrypted content-encryption-key.
|
88
|
-
:param str key_id:
|
89
|
-
The key-encryption-key identifier string.
|
90
105
|
"""
|
91
|
-
|
106
|
+
|
107
|
+
def __init__(self, algorithm: str, encrypted_key: bytes, key_id: str) -> None:
|
108
|
+
"""
|
109
|
+
:param str algorithm:
|
110
|
+
The algorithm used for wrapping.
|
111
|
+
:param bytes encrypted_key:
|
112
|
+
The encrypted content-encryption-key.
|
113
|
+
:param str key_id:
|
114
|
+
The key-encryption-key identifier string.
|
115
|
+
"""
|
92
116
|
_validate_not_none('algorithm', algorithm)
|
93
117
|
_validate_not_none('encrypted_key', encrypted_key)
|
94
118
|
_validate_not_none('key_id', key_id)
|
@@ -102,16 +126,17 @@ class _EncryptedRegionInfo:
|
|
102
126
|
"""
|
103
127
|
Represents the length of encryption elements.
|
104
128
|
This is only used for Encryption V2.
|
105
|
-
|
106
|
-
:param int data_length:
|
107
|
-
The length of the encryption region data (not including nonce + tag).
|
108
|
-
:param str nonce_length:
|
109
|
-
The length of nonce used when encrypting.
|
110
|
-
:param int tag_length:
|
111
|
-
The length of the encryption tag.
|
112
129
|
"""
|
113
130
|
|
114
|
-
def __init__(self, data_length, nonce_length, tag_length):
|
131
|
+
def __init__(self, data_length: int, nonce_length: int, tag_length: int) -> None:
|
132
|
+
"""
|
133
|
+
:param int data_length:
|
134
|
+
The length of the encryption region data (not including nonce + tag).
|
135
|
+
:param int nonce_length:
|
136
|
+
The length of nonce used when encrypting.
|
137
|
+
:param int tag_length:
|
138
|
+
The length of the encryption tag.
|
139
|
+
"""
|
115
140
|
_validate_not_none('data_length', data_length)
|
116
141
|
_validate_not_none('nonce_length', nonce_length)
|
117
142
|
_validate_not_none('tag_length', tag_length)
|
@@ -125,14 +150,15 @@ class _EncryptionAgent:
|
|
125
150
|
"""
|
126
151
|
Represents the encryption agent stored on the service.
|
127
152
|
It consists of the encryption protocol version and encryption algorithm used.
|
128
|
-
|
129
|
-
:param _EncryptionAlgorithm encryption_algorithm:
|
130
|
-
The algorithm used for encrypting the message contents.
|
131
|
-
:param str protocol:
|
132
|
-
The protocol version used for encryption.
|
133
153
|
"""
|
134
154
|
|
135
|
-
def __init__(self, encryption_algorithm, protocol):
|
155
|
+
def __init__(self, encryption_algorithm: _EncryptionAlgorithm, protocol: str) -> None:
|
156
|
+
"""
|
157
|
+
:param _EncryptionAlgorithm encryption_algorithm:
|
158
|
+
The algorithm used for encrypting the message contents.
|
159
|
+
:param str protocol:
|
160
|
+
The protocol version used for encryption.
|
161
|
+
"""
|
136
162
|
_validate_not_none('encryption_algorithm', encryption_algorithm)
|
137
163
|
_validate_not_none('protocol', protocol)
|
138
164
|
|
@@ -143,30 +169,30 @@ class _EncryptionAgent:
|
|
143
169
|
class _EncryptionData:
|
144
170
|
"""
|
145
171
|
Represents the encryption data that is stored on the service.
|
146
|
-
|
147
|
-
:param Optional[bytes] content_encryption_IV:
|
148
|
-
The content encryption initialization vector.
|
149
|
-
Required for AES-CBC (V1).
|
150
|
-
:param Optional[_EncryptedRegionInfo] encrypted_region_info:
|
151
|
-
The info about the authenticated block sizes.
|
152
|
-
Required for AES-GCM (V2).
|
153
|
-
:param _EncryptionAgent encryption_agent:
|
154
|
-
The encryption agent.
|
155
|
-
:param _WrappedContentKey wrapped_content_key:
|
156
|
-
An object that stores the wrapping algorithm, the key identifier,
|
157
|
-
and the encrypted key bytes.
|
158
|
-
:param dict key_wrapping_metadata:
|
159
|
-
A dict containing metadata related to the key wrapping.
|
160
172
|
"""
|
161
173
|
|
162
174
|
def __init__(
|
163
|
-
self,
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
175
|
+
self, content_encryption_IV: Optional[bytes],
|
176
|
+
encrypted_region_info: Optional[_EncryptedRegionInfo],
|
177
|
+
encryption_agent: _EncryptionAgent,
|
178
|
+
wrapped_content_key: _WrappedContentKey,
|
179
|
+
key_wrapping_metadata: Dict[str, Any]
|
180
|
+
) -> None:
|
181
|
+
"""
|
182
|
+
:param Optional[bytes] content_encryption_IV:
|
183
|
+
The content encryption initialization vector.
|
184
|
+
Required for AES-CBC (V1).
|
185
|
+
:param Optional[_EncryptedRegionInfo] encrypted_region_info:
|
186
|
+
The info about the autenticated block sizes.
|
187
|
+
Required for AES-GCM (V2).
|
188
|
+
:param _EncryptionAgent encryption_agent:
|
189
|
+
The encryption agent.
|
190
|
+
:param _WrappedContentKey wrapped_content_key:
|
191
|
+
An object that stores the wrapping algorithm, the key identifier,
|
192
|
+
and the encrypted key bytes.
|
193
|
+
:param Dict[str, Any] key_wrapping_metadata:
|
194
|
+
A dict containing metadata related to the key wrapping.
|
195
|
+
"""
|
170
196
|
_validate_not_none('encryption_agent', encryption_agent)
|
171
197
|
_validate_not_none('wrapped_content_key', wrapped_content_key)
|
172
198
|
|
@@ -191,15 +217,15 @@ class GCMBlobEncryptionStream:
|
|
191
217
|
it's streamed. Data is read and encrypted in regions. The stream
|
192
218
|
will use the same encryption key and will generate a guaranteed unique
|
193
219
|
nonce for each encryption region.
|
194
|
-
|
195
|
-
:param bytes content_encryption_key: The encryption key to use.
|
196
|
-
:param BinaryIO data_stream: The data stream to read data from.
|
197
220
|
"""
|
198
221
|
def __init__(
|
199
|
-
self,
|
200
|
-
|
201
|
-
|
202
|
-
|
222
|
+
self, content_encryption_key: bytes,
|
223
|
+
data_stream: IO[bytes],
|
224
|
+
) -> None:
|
225
|
+
"""
|
226
|
+
:param bytes content_encryption_key: The encryption key to use.
|
227
|
+
:param IO[bytes] data_stream: The data stream to read data from.
|
228
|
+
"""
|
203
229
|
self.content_encryption_key = content_encryption_key
|
204
230
|
self.data_stream = data_stream
|
205
231
|
|
@@ -235,28 +261,30 @@ class GCMBlobEncryptionStream:
|
|
235
261
|
# No more data to read
|
236
262
|
break
|
237
263
|
|
238
|
-
self.current = self.
|
264
|
+
self.current = encrypt_data_v2(data, self.nonce_counter, self.content_encryption_key)
|
265
|
+
# IMPORTANT: Must increment the nonce each time.
|
266
|
+
self.nonce_counter += 1
|
239
267
|
|
240
268
|
return result.getvalue()
|
241
269
|
|
242
|
-
def _encrypt_region(self, data: bytes) -> bytes:
|
243
|
-
"""
|
244
|
-
Encrypt the given region of data using AES-GCM. The result
|
245
|
-
includes the data in the form: nonce + ciphertext + tag.
|
246
270
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
# Each region MUST use a different nonce
|
252
|
-
nonce = self.nonce_counter.to_bytes(_GCM_NONCE_LENGTH, 'big')
|
253
|
-
self.nonce_counter += 1
|
271
|
+
def encrypt_data_v2(data: bytes, nonce: int, key: bytes) -> bytes:
|
272
|
+
"""
|
273
|
+
Encrypts the given data using the given nonce and key using AES-GCM.
|
274
|
+
The result includes the data in the form: nonce + ciphertext + tag.
|
254
275
|
|
255
|
-
|
276
|
+
:param bytes data: The raw data to encrypt.
|
277
|
+
:param int nonce: The nonce to use for encryption.
|
278
|
+
:param bytes key: The encryption key to use for encryption.
|
279
|
+
:return: The encrypted bytes in the form: nonce + ciphertext + tag.
|
280
|
+
:rtype: bytes
|
281
|
+
"""
|
282
|
+
nonce_bytes = nonce.to_bytes(_GCM_NONCE_LENGTH, 'big')
|
283
|
+
aesgcm = AESGCM(key)
|
256
284
|
|
257
|
-
|
258
|
-
|
259
|
-
|
285
|
+
# Returns ciphertext + tag
|
286
|
+
ciphertext_with_tag = aesgcm.encrypt(nonce_bytes, data, None)
|
287
|
+
return nonce_bytes + ciphertext_with_tag
|
260
288
|
|
261
289
|
|
262
290
|
def is_encryption_v2(encryption_data: Optional[_EncryptionData]) -> bool:
|
@@ -268,7 +296,7 @@ def is_encryption_v2(encryption_data: Optional[_EncryptionData]) -> bool:
|
|
268
296
|
:rtype: bool
|
269
297
|
"""
|
270
298
|
# If encryption_data is None, assume no encryption
|
271
|
-
return encryption_data and encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V2
|
299
|
+
return bool(encryption_data and (encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V2))
|
272
300
|
|
273
301
|
|
274
302
|
def modify_user_agent_for_encryption(
|
@@ -376,6 +404,9 @@ def get_adjusted_download_range_and_offset(
|
|
376
404
|
elif encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V2:
|
377
405
|
start_offset, end_offset = 0, end
|
378
406
|
|
407
|
+
if encryption_data.encrypted_region_info is None:
|
408
|
+
raise ValueError("Missing required metadata for Encryption V2")
|
409
|
+
|
379
410
|
nonce_length = encryption_data.encrypted_region_info.nonce_length
|
380
411
|
data_length = encryption_data.encrypted_region_info.data_length
|
381
412
|
tag_length = encryption_data.encrypted_region_info.tag_length
|
@@ -420,17 +451,17 @@ def parse_encryption_data(metadata: Dict[str, Any]) -> Optional[_EncryptionData]
|
|
420
451
|
return None
|
421
452
|
|
422
453
|
|
423
|
-
def adjust_blob_size_for_encryption(size: int, encryption_data:
|
454
|
+
def adjust_blob_size_for_encryption(size: int, encryption_data: _EncryptionData) -> int:
|
424
455
|
"""
|
425
456
|
Adjusts the given blob size for encryption by subtracting the size of
|
426
457
|
the encryption data (nonce + tag). This only has an affect for encryption V2.
|
427
458
|
|
428
459
|
:param int size: The original blob size.
|
429
|
-
:param
|
460
|
+
:param _EncryptionData encryption_data: The encryption data to determine version and sizes.
|
430
461
|
:return: The new blob size.
|
431
462
|
:rtype: int
|
432
463
|
"""
|
433
|
-
if is_encryption_v2(encryption_data):
|
464
|
+
if is_encryption_v2(encryption_data) and encryption_data.encrypted_region_info is not None:
|
434
465
|
nonce_length = encryption_data.encrypted_region_info.nonce_length
|
435
466
|
data_length = encryption_data.encrypted_region_info.data_length
|
436
467
|
tag_length = encryption_data.encrypted_region_info.tag_length
|
@@ -443,17 +474,22 @@ def adjust_blob_size_for_encryption(size: int, encryption_data: Optional[_Encryp
|
|
443
474
|
return size
|
444
475
|
|
445
476
|
|
446
|
-
def _generate_encryption_data_dict(
|
447
|
-
|
477
|
+
def _generate_encryption_data_dict(
|
478
|
+
kek: KeyEncryptionKey,
|
479
|
+
cek: bytes,
|
480
|
+
iv: Optional[bytes],
|
481
|
+
version: str
|
482
|
+
) -> TypedOrderedDict[str, Any]:
|
483
|
+
"""
|
448
484
|
Generates and returns the encryption metadata as a dict.
|
449
485
|
|
450
|
-
:param
|
486
|
+
:param KeyEncryptionKey kek: The key encryption key. See calling functions for more information.
|
451
487
|
:param bytes cek: The content encryption key.
|
452
488
|
:param Optional[bytes] iv: The initialization vector. Only required for AES-CBC.
|
453
489
|
:param str version: The client encryption version used.
|
454
490
|
:return: A dict containing all the encryption metadata.
|
455
|
-
:rtype:
|
456
|
-
|
491
|
+
:rtype: Dict[str, Any]
|
492
|
+
"""
|
457
493
|
# Encrypt the cek.
|
458
494
|
if version == _ENCRYPTION_PROTOCOL_V1:
|
459
495
|
wrapped_cek = kek.wrap_key(cek)
|
@@ -483,20 +519,20 @@ def _generate_encryption_data_dict(kek, cek, iv, version):
|
|
483
519
|
encrypted_region_info['DataLength'] = _GCM_REGION_DATA_LENGTH
|
484
520
|
encrypted_region_info['NonceLength'] = _GCM_NONCE_LENGTH
|
485
521
|
|
486
|
-
encryption_data_dict = OrderedDict()
|
522
|
+
encryption_data_dict: TypedOrderedDict[str, Any] = OrderedDict()
|
487
523
|
encryption_data_dict['WrappedContentKey'] = wrapped_content_key
|
488
524
|
encryption_data_dict['EncryptionAgent'] = encryption_agent
|
489
525
|
if version == _ENCRYPTION_PROTOCOL_V1:
|
490
526
|
encryption_data_dict['ContentEncryptionIV'] = encode_base64(iv)
|
491
527
|
elif version == _ENCRYPTION_PROTOCOL_V2:
|
492
528
|
encryption_data_dict['EncryptedRegionInfo'] = encrypted_region_info
|
493
|
-
encryption_data_dict['KeyWrappingMetadata'] = {'EncryptionLibrary': 'Python ' + VERSION}
|
529
|
+
encryption_data_dict['KeyWrappingMetadata'] = OrderedDict({'EncryptionLibrary': 'Python ' + VERSION})
|
494
530
|
|
495
531
|
return encryption_data_dict
|
496
532
|
|
497
533
|
|
498
|
-
def _dict_to_encryption_data(encryption_data_dict):
|
499
|
-
|
534
|
+
def _dict_to_encryption_data(encryption_data_dict: Dict[str, Any]) -> _EncryptionData:
|
535
|
+
"""
|
500
536
|
Converts the specified dictionary to an EncryptionData object for
|
501
537
|
eventual use in decryption.
|
502
538
|
|
@@ -504,7 +540,7 @@ def _dict_to_encryption_data(encryption_data_dict):
|
|
504
540
|
The dictionary containing the encryption data.
|
505
541
|
:return: an _EncryptionData object built from the dictionary.
|
506
542
|
:rtype: _EncryptionData
|
507
|
-
|
543
|
+
"""
|
508
544
|
try:
|
509
545
|
protocol = encryption_data_dict['EncryptionAgent']['Protocol']
|
510
546
|
if protocol not in [_ENCRYPTION_PROTOCOL_V1, _ENCRYPTION_PROTOCOL_V2]:
|
@@ -547,15 +583,15 @@ def _dict_to_encryption_data(encryption_data_dict):
|
|
547
583
|
return encryption_data
|
548
584
|
|
549
585
|
|
550
|
-
def _generate_AES_CBC_cipher(cek, iv):
|
551
|
-
|
586
|
+
def _generate_AES_CBC_cipher(cek: bytes, iv: bytes) -> Cipher:
|
587
|
+
"""
|
552
588
|
Generates and returns an encryption cipher for AES CBC using the given cek and iv.
|
553
589
|
|
554
590
|
:param bytes[] cek: The content encryption key for the cipher.
|
555
591
|
:param bytes[] iv: The initialization vector for the cipher.
|
556
592
|
:return: A cipher for encrypting in AES256 CBC.
|
557
593
|
:rtype: ~cryptography.hazmat.primitives.ciphers.Cipher
|
558
|
-
|
594
|
+
"""
|
559
595
|
|
560
596
|
backend = default_backend()
|
561
597
|
algorithm = AES(cek)
|
@@ -563,21 +599,30 @@ def _generate_AES_CBC_cipher(cek, iv):
|
|
563
599
|
return Cipher(algorithm, mode, backend)
|
564
600
|
|
565
601
|
|
566
|
-
def _validate_and_unwrap_cek(
|
567
|
-
|
602
|
+
def _validate_and_unwrap_cek(
|
603
|
+
encryption_data: _EncryptionData,
|
604
|
+
key_encryption_key: Optional[KeyEncryptionKey] = None,
|
605
|
+
key_resolver: Optional[Callable[[str], KeyEncryptionKey]] = None
|
606
|
+
) -> bytes:
|
607
|
+
"""
|
568
608
|
Extracts and returns the content_encryption_key stored in the encryption_data object
|
569
609
|
and performs necessary validation on all parameters.
|
570
610
|
:param _EncryptionData encryption_data:
|
571
611
|
The encryption metadata of the retrieved value.
|
572
|
-
:param
|
573
|
-
The
|
574
|
-
|
575
|
-
|
612
|
+
:param Optional[KeyEncryptionKey] key_encryption_key:
|
613
|
+
The user-provided key-encryption-key. Must implement the following methods:
|
614
|
+
wrap_key(key)
|
615
|
+
- Wraps the specified key using an algorithm of the user's choice.
|
616
|
+
get_key_wrap_algorithm()
|
617
|
+
- Returns the algorithm used to wrap the specified symmetric key.
|
618
|
+
get_kid()
|
619
|
+
- Returns a string key id for this key-encryption-key.
|
620
|
+
:param Optional[Callable[[str], KeyEncryptionKey]] key_resolver:
|
576
621
|
A function used that, given a key_id, will return a key_encryption_key. Please refer
|
577
622
|
to high-level service object instance variables for more details.
|
578
|
-
:return:
|
579
|
-
:rtype: bytes
|
580
|
-
|
623
|
+
:return: The content_encryption_key stored in the encryption_data object.
|
624
|
+
:rtype: bytes
|
625
|
+
"""
|
581
626
|
|
582
627
|
_validate_not_none('encrypted_key', encryption_data.wrapped_content_key.encrypted_key)
|
583
628
|
|
@@ -589,13 +634,14 @@ def _validate_and_unwrap_cek(encryption_data, key_encryption_key=None, key_resol
|
|
589
634
|
else:
|
590
635
|
raise ValueError('Specified encryption version is not supported.')
|
591
636
|
|
592
|
-
content_encryption_key = None
|
637
|
+
content_encryption_key: Optional[bytes] = None
|
593
638
|
|
594
639
|
# If the resolver exists, give priority to the key it finds.
|
595
640
|
if key_resolver is not None:
|
596
641
|
key_encryption_key = key_resolver(encryption_data.wrapped_content_key.key_id)
|
597
642
|
|
598
|
-
|
643
|
+
if key_encryption_key is None:
|
644
|
+
raise ValueError("Unable to decrypt. key_resolver and key_encryption_key cannot both be None.")
|
599
645
|
if not hasattr(key_encryption_key, 'get_kid') or not callable(key_encryption_key.get_kid):
|
600
646
|
raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'get_kid'))
|
601
647
|
if not hasattr(key_encryption_key, 'unwrap_key') or not callable(key_encryption_key.unwrap_key):
|
@@ -603,8 +649,9 @@ def _validate_and_unwrap_cek(encryption_data, key_encryption_key=None, key_resol
|
|
603
649
|
if encryption_data.wrapped_content_key.key_id != key_encryption_key.get_kid():
|
604
650
|
raise ValueError('Provided or resolved key-encryption-key does not match the id of key used to encrypt.')
|
605
651
|
# Will throw an exception if the specified algorithm is not supported.
|
606
|
-
content_encryption_key = key_encryption_key.unwrap_key(
|
607
|
-
|
652
|
+
content_encryption_key = key_encryption_key.unwrap_key(
|
653
|
+
encryption_data.wrapped_content_key.encrypted_key,
|
654
|
+
encryption_data.wrapped_content_key.algorithm)
|
608
655
|
|
609
656
|
# For V2, the version is included with the cek. We need to validate it
|
610
657
|
# and remove it from the actual cek.
|
@@ -622,27 +669,34 @@ def _validate_and_unwrap_cek(encryption_data, key_encryption_key=None, key_resol
|
|
622
669
|
return content_encryption_key
|
623
670
|
|
624
671
|
|
625
|
-
def _decrypt_message(
|
672
|
+
def _decrypt_message(
|
673
|
+
message: bytes,
|
674
|
+
encryption_data: _EncryptionData,
|
675
|
+
key_encryption_key: Optional[KeyEncryptionKey] = None,
|
676
|
+
resolver: Optional[Callable[[str], KeyEncryptionKey]] = None
|
677
|
+
) -> bytes:
|
626
678
|
"""
|
627
679
|
Decrypts the given ciphertext using AES256 in CBC mode with 128 bit padding.
|
628
680
|
Unwraps the content-encryption-key using the user-provided or resolved key-encryption-key (kek).
|
629
|
-
Returns the original
|
681
|
+
Returns the original plaintext.
|
630
682
|
|
631
|
-
:param
|
683
|
+
:param bytes message:
|
632
684
|
The ciphertext to be decrypted.
|
633
685
|
:param _EncryptionData encryption_data:
|
634
686
|
The metadata associated with this ciphertext.
|
635
|
-
:param
|
687
|
+
:param Optional[KeyEncryptionKey] key_encryption_key:
|
636
688
|
The user-provided key-encryption-key. Must implement the following methods:
|
637
|
-
|
638
|
-
-
|
689
|
+
wrap_key(key)
|
690
|
+
- Wraps the specified key using an algorithm of the user's choice.
|
691
|
+
get_key_wrap_algorithm()
|
692
|
+
- Returns the algorithm used to wrap the specified symmetric key.
|
639
693
|
get_kid()
|
640
|
-
-
|
641
|
-
:param Callable resolver:
|
694
|
+
- Returns a string key id for this key-encryption-key.
|
695
|
+
:param Optional[Callable[[str], KeyEncryptionKey]] resolver:
|
642
696
|
The user-provided key resolver. Uses the kid string to return a key-encryption-key
|
643
697
|
implementing the interface defined above.
|
644
698
|
:return: The decrypted plaintext.
|
645
|
-
:rtype:
|
699
|
+
:rtype: bytes
|
646
700
|
"""
|
647
701
|
_validate_not_none('message', message)
|
648
702
|
content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, resolver)
|
@@ -654,9 +708,8 @@ def _decrypt_message(message, encryption_data, key_encryption_key=None, resolver
|
|
654
708
|
cipher = _generate_AES_CBC_cipher(content_encryption_key, encryption_data.content_encryption_IV)
|
655
709
|
|
656
710
|
# decrypt data
|
657
|
-
decrypted_data = message
|
658
711
|
decryptor = cipher.decryptor()
|
659
|
-
decrypted_data = (decryptor.update(
|
712
|
+
decrypted_data = (decryptor.update(message) + decryptor.finalize())
|
660
713
|
|
661
714
|
# unpad data
|
662
715
|
unpadder = PKCS7(128).unpadder()
|
@@ -667,7 +720,10 @@ def _decrypt_message(message, encryption_data, key_encryption_key=None, resolver
|
|
667
720
|
if not block_info or not block_info.nonce_length:
|
668
721
|
raise ValueError("Missing required metadata for decryption.")
|
669
722
|
|
670
|
-
|
723
|
+
if encryption_data.encrypted_region_info is None:
|
724
|
+
raise ValueError("Missing required metadata for Encryption V2")
|
725
|
+
|
726
|
+
nonce_length = int(encryption_data.encrypted_region_info.nonce_length)
|
671
727
|
|
672
728
|
# First bytes are the nonce
|
673
729
|
nonce = message[:nonce_length]
|
@@ -682,8 +738,8 @@ def _decrypt_message(message, encryption_data, key_encryption_key=None, resolver
|
|
682
738
|
return decrypted_data
|
683
739
|
|
684
740
|
|
685
|
-
def encrypt_blob(blob, key_encryption_key, version):
|
686
|
-
|
741
|
+
def encrypt_blob(blob: bytes, key_encryption_key: KeyEncryptionKey, version: str) -> Tuple[str, bytes]:
|
742
|
+
"""
|
687
743
|
Encrypts the given blob using the given encryption protocol version.
|
688
744
|
Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek).
|
689
745
|
Returns a json-formatted string containing the encryption metadata. This method should
|
@@ -692,15 +748,18 @@ def encrypt_blob(blob, key_encryption_key, version):
|
|
692
748
|
|
693
749
|
:param bytes blob:
|
694
750
|
The blob to be encrypted.
|
695
|
-
:param
|
751
|
+
:param KeyEncryptionKey key_encryption_key:
|
696
752
|
The user-provided key-encryption-key. Must implement the following methods:
|
697
|
-
wrap_key(key)
|
698
|
-
|
699
|
-
|
753
|
+
wrap_key(key)
|
754
|
+
- Wraps the specified key using an algorithm of the user's choice.
|
755
|
+
get_key_wrap_algorithm()
|
756
|
+
- Returns the algorithm used to wrap the specified symmetric key.
|
757
|
+
get_kid()
|
758
|
+
- Returns a string key id for this key-encryption-key.
|
700
759
|
:param str version: The client encryption version to use.
|
701
760
|
:return: A tuple of json-formatted string containing the encryption metadata and the encrypted blob data.
|
702
761
|
:rtype: (str, bytes)
|
703
|
-
|
762
|
+
"""
|
704
763
|
|
705
764
|
_validate_not_none('blob', blob)
|
706
765
|
_validate_not_none('key_encryption_key', key_encryption_key)
|
@@ -741,17 +800,21 @@ def encrypt_blob(blob, key_encryption_key, version):
|
|
741
800
|
return dumps(encryption_data), encrypted_data
|
742
801
|
|
743
802
|
|
744
|
-
def generate_blob_encryption_data(
|
745
|
-
|
803
|
+
def generate_blob_encryption_data(
|
804
|
+
key_encryption_key: Optional[KeyEncryptionKey],
|
805
|
+
version: str
|
806
|
+
) -> Tuple[Optional[bytes], Optional[bytes], Optional[str]]:
|
807
|
+
"""
|
746
808
|
Generates the encryption_metadata for the blob.
|
747
809
|
|
748
|
-
:param
|
810
|
+
:param Optional[KeyEncryptionKey] key_encryption_key:
|
749
811
|
The key-encryption-key used to wrap the cek associate with this blob.
|
750
812
|
:param str version: The client encryption version to use.
|
751
813
|
:return: A tuple containing the cek and iv for this blob as well as the
|
752
814
|
serialized encryption metadata for the blob.
|
753
|
-
:rtype: (bytes, Optional[bytes], str)
|
754
|
-
|
815
|
+
:rtype: (Optional[bytes], Optional[bytes], Optional[str])
|
816
|
+
"""
|
817
|
+
|
755
818
|
encryption_data = None
|
756
819
|
content_encryption_key = None
|
757
820
|
initialization_vector = None
|
@@ -761,37 +824,42 @@ def generate_blob_encryption_data(key_encryption_key, version):
|
|
761
824
|
# Initialization vector only needed for V1
|
762
825
|
if version == _ENCRYPTION_PROTOCOL_V1:
|
763
826
|
initialization_vector = os.urandom(16)
|
764
|
-
|
827
|
+
encryption_data_dict = _generate_encryption_data_dict(key_encryption_key,
|
765
828
|
content_encryption_key,
|
766
829
|
initialization_vector,
|
767
830
|
version)
|
768
|
-
|
769
|
-
encryption_data = dumps(
|
831
|
+
encryption_data_dict['EncryptionMode'] = 'FullBlob'
|
832
|
+
encryption_data = dumps(encryption_data_dict)
|
770
833
|
|
771
834
|
return content_encryption_key, initialization_vector, encryption_data
|
772
835
|
|
773
836
|
|
774
837
|
def decrypt_blob( # pylint: disable=too-many-locals,too-many-statements
|
775
|
-
require_encryption,
|
776
|
-
key_encryption_key,
|
777
|
-
key_resolver,
|
778
|
-
content,
|
779
|
-
start_offset,
|
780
|
-
end_offset,
|
781
|
-
response_headers
|
838
|
+
require_encryption: bool,
|
839
|
+
key_encryption_key: KeyEncryptionKey,
|
840
|
+
key_resolver: Optional[Callable[[str], KeyEncryptionKey]],
|
841
|
+
content: bytes,
|
842
|
+
start_offset: int,
|
843
|
+
end_offset: int,
|
844
|
+
response_headers: Dict[str, Any]
|
845
|
+
) -> bytes:
|
782
846
|
"""
|
783
847
|
Decrypts the given blob contents and returns only the requested range.
|
784
848
|
|
785
849
|
:param bool require_encryption:
|
786
850
|
Whether the calling blob service requires objects to be decrypted.
|
787
|
-
:param
|
851
|
+
:param KeyEncryptionKey key_encryption_key:
|
788
852
|
The user-provided key-encryption-key. Must implement the following methods:
|
789
|
-
wrap_key(key)
|
790
|
-
|
791
|
-
|
792
|
-
|
853
|
+
wrap_key(key)
|
854
|
+
- Wraps the specified key using an algorithm of the user's choice.
|
855
|
+
get_key_wrap_algorithm()
|
856
|
+
- Returns the algorithm used to wrap the specified symmetric key.
|
857
|
+
get_kid()
|
858
|
+
- Returns a string key id for this key-encryption-key.
|
859
|
+
:param key_resolver:
|
793
860
|
The user-provided key resolver. Uses the kid string to return a key-encryption-key
|
794
861
|
implementing the interface defined above.
|
862
|
+
:type key_resolver: Optional[Callable[[str], KeyEncryptionKey]]
|
795
863
|
:param bytes content:
|
796
864
|
The encrypted blob content.
|
797
865
|
:param int start_offset:
|
@@ -827,7 +895,7 @@ def decrypt_blob( # pylint: disable=too-many-locals,too-many-statements
|
|
827
895
|
if version == _ENCRYPTION_PROTOCOL_V1:
|
828
896
|
blob_type = response_headers['x-ms-blob-type']
|
829
897
|
|
830
|
-
iv = None
|
898
|
+
iv: Optional[bytes] = None
|
831
899
|
unpad = False
|
832
900
|
if 'content-range' in response_headers:
|
833
901
|
content_range = response_headers['content-range']
|
@@ -857,6 +925,9 @@ def decrypt_blob( # pylint: disable=too-many-locals,too-many-statements
|
|
857
925
|
if blob_type == 'PageBlob':
|
858
926
|
unpad = False
|
859
927
|
|
928
|
+
if iv is None:
|
929
|
+
raise ValueError("Missing required metadata for Encryption V1")
|
930
|
+
|
860
931
|
cipher = _generate_AES_CBC_cipher(content_encryption_key, iv)
|
861
932
|
decryptor = cipher.decryptor()
|
862
933
|
|
@@ -872,6 +943,9 @@ def decrypt_blob( # pylint: disable=too-many-locals,too-many-statements
|
|
872
943
|
total_size = len(content)
|
873
944
|
offset = 0
|
874
945
|
|
946
|
+
if encryption_data.encrypted_region_info is None:
|
947
|
+
raise ValueError("Missing required metadata for Encryption V2")
|
948
|
+
|
875
949
|
nonce_length = encryption_data.encrypted_region_info.nonce_length
|
876
950
|
data_length = encryption_data.encrypted_region_info.data_length
|
877
951
|
tag_length = encryption_data.encrypted_region_info.tag_length
|
@@ -899,7 +973,11 @@ def decrypt_blob( # pylint: disable=too-many-locals,too-many-statements
|
|
899
973
|
raise ValueError('Specified encryption version is not supported.')
|
900
974
|
|
901
975
|
|
902
|
-
def get_blob_encryptor_and_padder(
|
976
|
+
def get_blob_encryptor_and_padder(
|
977
|
+
cek: Optional[bytes],
|
978
|
+
iv: Optional[bytes],
|
979
|
+
should_pad: bool
|
980
|
+
) -> Tuple[Optional["AEADEncryptionContext"], Optional["PaddingContext"]]:
|
903
981
|
encryptor = None
|
904
982
|
padder = None
|
905
983
|
|
@@ -911,23 +989,26 @@ def get_blob_encryptor_and_padder(cek, iv, should_pad):
|
|
911
989
|
return encryptor, padder
|
912
990
|
|
913
991
|
|
914
|
-
def encrypt_queue_message(message, key_encryption_key, version):
|
915
|
-
|
992
|
+
def encrypt_queue_message(message: str, key_encryption_key: KeyEncryptionKey, version: str) -> str:
|
993
|
+
"""
|
916
994
|
Encrypts the given plain text message using the given protocol version.
|
917
995
|
Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek).
|
918
996
|
Returns a json-formatted string containing the encrypted message and the encryption metadata.
|
919
997
|
|
920
|
-
:param
|
998
|
+
:param str message:
|
921
999
|
The plain text message to be encrypted.
|
922
|
-
:param
|
1000
|
+
:param KeyEncryptionKey key_encryption_key:
|
923
1001
|
The user-provided key-encryption-key. Must implement the following methods:
|
924
|
-
wrap_key(key)
|
925
|
-
|
926
|
-
|
1002
|
+
wrap_key(key)
|
1003
|
+
- Wraps the specified key using an algorithm of the user's choice.
|
1004
|
+
get_key_wrap_algorithm()
|
1005
|
+
- Returns the algorithm used to wrap the specified symmetric key.
|
1006
|
+
get_kid()
|
1007
|
+
- Returns a string key id for this key-encryption-key.
|
927
1008
|
:param str version: The client encryption version to use.
|
928
1009
|
:return: A json-formatted string containing the encrypted message and the encryption metadata.
|
929
1010
|
:rtype: str
|
930
|
-
|
1011
|
+
"""
|
931
1012
|
|
932
1013
|
_validate_not_none('message', message)
|
933
1014
|
_validate_not_none('key_encryption_key', key_encryption_key)
|
@@ -935,7 +1016,7 @@ def encrypt_queue_message(message, key_encryption_key, version):
|
|
935
1016
|
|
936
1017
|
# Queue encoding functions all return unicode strings, and encryption should
|
937
1018
|
# operate on binary strings.
|
938
|
-
|
1019
|
+
message_as_bytes: bytes = message.encode('utf-8')
|
939
1020
|
|
940
1021
|
if version == _ENCRYPTION_PROTOCOL_V1:
|
941
1022
|
# AES256 CBC uses 256 bit (32 byte) keys and always with 16 byte blocks
|
@@ -946,7 +1027,7 @@ def encrypt_queue_message(message, key_encryption_key, version):
|
|
946
1027
|
|
947
1028
|
# PKCS7 with 16 byte blocks ensures compatibility with AES.
|
948
1029
|
padder = PKCS7(128).padder()
|
949
|
-
padded_data = padder.update(
|
1030
|
+
padded_data = padder.update(message_as_bytes) + padder.finalize()
|
950
1031
|
|
951
1032
|
# Encrypt the data.
|
952
1033
|
encryptor = cipher.encryptor()
|
@@ -962,8 +1043,8 @@ def encrypt_queue_message(message, key_encryption_key, version):
|
|
962
1043
|
aesgcm = AESGCM(content_encryption_key)
|
963
1044
|
|
964
1045
|
# Returns ciphertext + tag
|
965
|
-
|
966
|
-
encrypted_data = nonce +
|
1046
|
+
cipertext_with_tag = aesgcm.encrypt(nonce, message_as_bytes, None)
|
1047
|
+
encrypted_data = nonce + cipertext_with_tag
|
967
1048
|
|
968
1049
|
else:
|
969
1050
|
raise ValueError("Invalid encryption version specified.")
|
@@ -978,7 +1059,13 @@ def encrypt_queue_message(message, key_encryption_key, version):
|
|
978
1059
|
return dumps(queue_message)
|
979
1060
|
|
980
1061
|
|
981
|
-
def decrypt_queue_message(
|
1062
|
+
def decrypt_queue_message(
|
1063
|
+
message: str,
|
1064
|
+
response: "PipelineResponse",
|
1065
|
+
require_encryption: bool,
|
1066
|
+
key_encryption_key: Optional[KeyEncryptionKey],
|
1067
|
+
resolver: Optional[Callable[[str], KeyEncryptionKey]]
|
1068
|
+
) -> str:
|
982
1069
|
"""
|
983
1070
|
Returns the decrypted message contents from an EncryptedQueueMessage.
|
984
1071
|
If no encryption metadata is present, will return the unaltered message.
|
@@ -988,13 +1075,15 @@ def decrypt_queue_message(message, response, require_encryption, key_encryption_
|
|
988
1075
|
The pipeline response used to generate an error with.
|
989
1076
|
:param bool require_encryption:
|
990
1077
|
If set, will enforce that the retrieved messages are encrypted and decrypt them.
|
991
|
-
:param
|
1078
|
+
:param Optional[KeyEncryptionKey] key_encryption_key:
|
992
1079
|
The user-provided key-encryption-key. Must implement the following methods:
|
993
|
-
|
994
|
-
-
|
1080
|
+
wrap_key(key)
|
1081
|
+
- Wraps the specified key using an algorithm of the user's choice.
|
1082
|
+
get_key_wrap_algorithm()
|
1083
|
+
- Returns the algorithm used to wrap the specified symmetric key.
|
995
1084
|
get_kid()
|
996
|
-
-
|
997
|
-
:param Callable resolver:
|
1085
|
+
- Returns a string key id for this key-encryption-key.
|
1086
|
+
:param Optional[Callable[[str], KeyEncryptionKey]] resolver:
|
998
1087
|
The user-provided key resolver. Uses the kid string to return a key-encryption-key
|
999
1088
|
implementing the interface defined above.
|
1000
1089
|
:return: The plain text message from the queue message.
|
@@ -1003,10 +1092,10 @@ def decrypt_queue_message(message, response, require_encryption, key_encryption_
|
|
1003
1092
|
response = response.http_response
|
1004
1093
|
|
1005
1094
|
try:
|
1006
|
-
|
1095
|
+
deserialized_message: Dict[str, Any] = loads(message)
|
1007
1096
|
|
1008
|
-
encryption_data = _dict_to_encryption_data(
|
1009
|
-
decoded_data = decode_base64_to_bytes(
|
1097
|
+
encryption_data = _dict_to_encryption_data(deserialized_message['EncryptionData'])
|
1098
|
+
decoded_data = decode_base64_to_bytes(deserialized_message['EncryptedMessageContents'])
|
1010
1099
|
except (KeyError, ValueError) as exc:
|
1011
1100
|
# Message was not json formatted and so was not encrypted
|
1012
1101
|
# or the user provided a json formatted message
|
@@ -1022,5 +1111,5 @@ def decrypt_queue_message(message, response, require_encryption, key_encryption_
|
|
1022
1111
|
except Exception as error:
|
1023
1112
|
raise HttpResponseError(
|
1024
1113
|
message="Decryption failed.",
|
1025
|
-
response=response,
|
1114
|
+
response=response, #type: ignore [arg-type]
|
1026
1115
|
error=error) from error
|