14
14
15
15
"""Client side encryption."""
16
16
17
+ import contextlib
17
18
import functools
18
19
import subprocess
19
20
import uuid
32
33
33
34
from bson import _bson_to_dict , _dict_to_bson , decode , encode
34
35
from bson .codec_options import CodecOptions
35
- from bson .binary import STANDARD , Binary
36
+ from bson .binary import (Binary ,
37
+ STANDARD ,
38
+ UUID_SUBTYPE )
36
39
from bson .errors import BSONError
37
40
from bson .raw_bson import (DEFAULT_RAW_BSON_OPTIONS ,
38
41
RawBSONDocument ,
58
61
uuid_representation = STANDARD )
59
62
60
63
64
+ @contextlib .contextmanager
65
+ def _wrap_encryption_errors_ctx ():
66
+ """Context manager to wrap encryption related errors."""
67
+ try :
68
+ yield
69
+ except BSONError :
70
+ # BSON encoding/decoding errors are unrelated to encryption so
71
+ # we should propagate them unchanged.
72
+ raise
73
+ except Exception as exc :
74
+ raise EncryptionError (exc )
75
+
76
+
61
77
def _wrap_encryption_errors (encryption_func = None ):
62
- """Decorator to wrap encryption related errors with EncryptionError."""
63
- @functools .wraps (encryption_func )
64
- def wrap_encryption_errors (* args , ** kwargs ):
65
- try :
66
- return encryption_func (* args , ** kwargs )
67
- except BSONError :
68
- # BSON encoding/decoding errors are unrelated to encryption so
69
- # we should propagate them unchanged.
70
- raise
71
- except Exception as exc :
72
- raise EncryptionError (exc )
78
+ """Decorator or context manager to wrap encryption related errors."""
79
+ if encryption_func :
80
+ @functools .wraps (encryption_func )
81
+ def wrap_encryption_errors (* args , ** kwargs ):
82
+ with _wrap_encryption_errors_ctx ():
83
+ return encryption_func (* args , ** kwargs )
73
84
74
- return wrap_encryption_errors
85
+ return wrap_encryption_errors
86
+ else :
87
+ return _wrap_encryption_errors_ctx ()
75
88
76
89
77
90
class _EncryptionIO (MongoCryptCallback ):
@@ -190,8 +203,11 @@ def insert_data_key(self, data_key):
190
203
"""
191
204
# insert does not return the inserted _id when given a RawBSONDocument.
192
205
doc = _bson_to_dict (data_key , _DATA_KEY_OPTS )
206
+ if not isinstance (doc .get ('_id' ), uuid .UUID ):
207
+ raise TypeError (
208
+ 'data_key _id must be a bson.binary.Binary with subtype 4' )
193
209
res = self .key_vault_coll .insert_one (doc )
194
- return res .inserted_id
210
+ return Binary ( res .inserted_id . bytes , subtype = UUID_SUBTYPE )
195
211
196
212
def bson_encode (self , doc ):
197
213
"""Encode a document to BSON.
@@ -406,7 +422,6 @@ def create_data_key(self, kms_provider, master_key=None,
406
422
return self ._encryption .create_data_key (
407
423
kms_provider , master_key = master_key , key_alt_names = key_alt_names )
408
424
409
- @_wrap_encryption_errors
410
425
def encrypt (self , value , algorithm , key_id = None , key_alt_name = None ):
411
426
"""Encrypt a BSON value with a given key and algorithm.
412
427
@@ -417,28 +432,25 @@ def encrypt(self, value, algorithm, key_id=None, key_alt_name=None):
417
432
- `value`: The BSON value to encrypt.
418
433
- `algorithm` (string): The encryption algorithm to use. See
419
434
:class:`Algorithm` for some valid options.
420
- - `key_id`: Identifies a data key by ``_id`` which must be a UUID
421
- or a :class:`~bson.binary.Binary` with subtype 4.
435
+ - `key_id`: Identifies a data key by ``_id`` which must be a
436
+ :class:`~bson.binary.Binary` with subtype 4 (
437
+ :attr:`~bson.binary.UUID_SUBTYPE`).
422
438
- `key_alt_name`: Identifies a key vault document by 'keyAltName'.
423
439
424
440
:Returns:
425
441
The encrypted value, a :class:`~bson.binary.Binary` with subtype 6.
426
442
"""
427
- doc = encode ({'v' : value }, codec_options = self ._codec_options )
428
- if isinstance (key_id , uuid .UUID ):
429
- raw_key_id = key_id .bytes
430
- else :
431
- raw_key_id = key_id
432
- encrypted_doc = self ._encryption .encrypt (
433
- doc , algorithm , key_id = raw_key_id , key_alt_name = key_alt_name )
434
- return decode (encrypted_doc )['v' ]
443
+ if (key_id is not None and not (
444
+ isinstance (key_id , Binary ) and
445
+ key_id .subtype == UUID_SUBTYPE )):
446
+ raise TypeError (
447
+ 'key_id must be a bson.binary.Binary with subtype 4' )
435
448
436
- @_wrap_encryption_errors
437
- def _decrypt (self , value ):
438
- """Internal decrypt helper."""
439
- doc = encode ({'v' : value })
440
- decrypted_doc = self ._encryption .decrypt (doc )
441
- return decode (decrypted_doc , codec_options = self ._codec_options )['v' ]
449
+ doc = encode ({'v' : value }, codec_options = self ._codec_options )
450
+ with _wrap_encryption_errors_ctx ():
451
+ encrypted_doc = self ._encryption .encrypt (
452
+ doc , algorithm , key_id = key_id , key_alt_name = key_alt_name )
453
+ return decode (encrypted_doc )['v' ]
442
454
443
455
def decrypt (self , value ):
444
456
"""Decrypt an encrypted value.
@@ -454,7 +466,11 @@ def decrypt(self, value):
454
466
raise TypeError (
455
467
'value to decrypt must be a bson.binary.Binary with subtype 6' )
456
468
457
- return self ._decrypt (value )
469
+ with _wrap_encryption_errors_ctx ():
470
+ doc = encode ({'v' : value })
471
+ decrypted_doc = self ._encryption .decrypt (doc )
472
+ return decode (decrypted_doc ,
473
+ codec_options = self ._codec_options )['v' ]
458
474
459
475
def close (self ):
460
476
"""Release resources."""
0 commit comments