17
17
import contextlib
18
18
import enum
19
19
import socket
20
- import uuid
21
20
import weakref
22
21
from typing import Any , Mapping , Optional , Sequence
23
22
40
39
from bson .raw_bson import DEFAULT_RAW_BSON_OPTIONS , RawBSONDocument , _inflate_bson
41
40
from bson .son import SON
42
41
from pymongo import _csot
42
+ from pymongo .cursor import Cursor
43
43
from pymongo .daemon import _spawn_daemon
44
44
from pymongo .encryption_options import AutoEncryptionOpts
45
45
from pymongo .errors import (
50
50
)
51
51
from pymongo .mongo_client import MongoClient
52
52
from pymongo .network import BLOCKING_IO_ERRORS
53
+ from pymongo .operations import UpdateOne
53
54
from pymongo .pool import PoolOptions , _configured_socket
54
55
from pymongo .read_concern import ReadConcern
56
+ from pymongo .results import BulkWriteResult , DeleteResult
55
57
from pymongo .ssl_support import get_ssl_context
56
58
from pymongo .uri_parser import parse_host
57
59
from pymongo .write_concern import WriteConcern
60
62
_KMS_CONNECT_TIMEOUT = 10 # TODO: CDRIVER-3262 will define this value.
61
63
_MONGOCRYPTD_TIMEOUT_MS = 10000
62
64
65
+
63
66
_DATA_KEY_OPTS : CodecOptions = CodecOptions (document_class = SON , uuid_representation = STANDARD )
64
67
# Use RawBSONDocument codec options to avoid needlessly decoding
65
68
# documents from the key vault.
66
- _KEY_VAULT_OPTS = CodecOptions (document_class = RawBSONDocument , uuid_representation = STANDARD )
69
+ _KEY_VAULT_OPTS = CodecOptions (document_class = RawBSONDocument )
67
70
68
71
69
72
@contextlib .contextmanager
@@ -225,11 +228,11 @@ def insert_data_key(self, data_key):
225
228
"""
226
229
raw_doc = RawBSONDocument (data_key , _KEY_VAULT_OPTS )
227
230
data_key_id = raw_doc .get ("_id" )
228
- if not isinstance (data_key_id , uuid . UUID ) :
229
- raise TypeError ("data_key _id must be a UUID" )
231
+ if not isinstance (data_key_id , Binary ) or data_key_id . subtype != UUID_SUBTYPE :
232
+ raise TypeError ("data_key _id must be Binary with a UUID subtype " )
230
233
231
234
self .key_vault_coll .insert_one (raw_doc )
232
- return Binary ( data_key_id . bytes , subtype = UUID_SUBTYPE )
235
+ return data_key_id
233
236
234
237
def bson_encode (self , doc ):
235
238
"""Encode a document to BSON.
@@ -256,6 +259,30 @@ def close(self):
256
259
self .mongocryptd_client = None
257
260
258
261
262
+ class RewrapManyDataKeyResult (object ):
263
+ def __init__ (self , bulk_write_result : Optional [BulkWriteResult ] = None ) -> None :
264
+ """Result object returned by a ``rewrap_many_data_key`` operation.
265
+
266
+ :Parameters:
267
+ - `bulk_write_result`: The result of the bulk write operation used to
268
+ update the key vault collection with one or more rewrapped data keys.
269
+ If ``rewrap_many_data_key()`` does not find any matching keys to
270
+ rewrap, no bulk write operation will be executed and this field will
271
+ be ``None``.
272
+ """
273
+ self ._bulk_write_result = bulk_write_result
274
+
275
+ @property
276
+ def bulk_write_result (self ) -> Optional [BulkWriteResult ]:
277
+ """The result of the bulk write operation used to update the key vault
278
+ collection with one or more rewrapped data keys. If
279
+ ``rewrap_many_data_key()`` does not find any matching keys to rewrap,
280
+ no bulk write operation will be executed and this field will be
281
+ ``None``.
282
+ """
283
+ return self ._bulk_write_result
284
+
285
+
259
286
class _Encrypter (object ):
260
287
"""Encrypts and decrypts MongoDB commands.
261
288
@@ -514,12 +541,15 @@ def __init__(
514
541
self ._encryption = ExplicitEncrypter (
515
542
self ._io_callbacks , MongoCryptOptions (kms_providers , None )
516
543
)
544
+ # Use the same key vault collection as the callback.
545
+ self ._key_vault_coll = self ._io_callbacks .key_vault_coll
517
546
518
547
def create_data_key (
519
548
self ,
520
549
kms_provider : str ,
521
550
master_key : Optional [Mapping [str , Any ]] = None ,
522
551
key_alt_names : Optional [Sequence [str ]] = None ,
552
+ key_material : Optional [bytes ] = None ,
523
553
) -> Binary :
524
554
"""Create and insert a new data key into the key vault collection.
525
555
@@ -580,16 +610,24 @@ def create_data_key(
580
610
# reference the key with the alternate name
581
611
client_encryption.encrypt("457-55-5462", keyAltName="name1",
582
612
algorithm=Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random)
613
+ - `key_material` (optional): Sets the custom key material to be used
614
+ by the data key for encryption and decryption.
583
615
584
616
:Returns:
585
617
The ``_id`` of the created data key document as a
586
618
:class:`~bson.binary.Binary` with subtype
587
619
:data:`~bson.binary.UUID_SUBTYPE`.
620
+
621
+ .. versionchanged:: 4.2
622
+ Added the `key_material` parameter.
588
623
"""
589
624
self ._check_closed ()
590
625
with _wrap_encryption_errors ():
591
626
return self ._encryption .create_data_key (
592
- kms_provider , master_key = master_key , key_alt_names = key_alt_names
627
+ kms_provider ,
628
+ master_key = master_key ,
629
+ key_alt_names = key_alt_names ,
630
+ key_material = key_material ,
593
631
)
594
632
595
633
def encrypt (
@@ -676,6 +714,145 @@ def decrypt(self, value: Binary) -> Any:
676
714
decrypted_doc = self ._encryption .decrypt (doc )
677
715
return decode (decrypted_doc , codec_options = self ._codec_options )["v" ]
678
716
717
+ def get_key (self , id : Binary ) -> Optional [RawBSONDocument ]:
718
+ """Get a data key by id.
719
+
720
+ :Parameters:
721
+ - `id` (Binary): The UUID of a key a which must be a
722
+ :class:`~bson.binary.Binary` with subtype 4 (
723
+ :attr:`~bson.binary.UUID_SUBTYPE`).
724
+
725
+ :Returns:
726
+ The key document.
727
+ """
728
+ self ._check_closed ()
729
+ return self ._key_vault_coll .find_one ({"_id" : id })
730
+
731
+ def get_keys (self ) -> Cursor [RawBSONDocument ]:
732
+ """Get all of the data keys.
733
+
734
+ :Returns:
735
+ An instance of :class:`~pymongo.cursor.Cursor` over the data key
736
+ documents.
737
+ """
738
+ self ._check_closed ()
739
+ return self ._key_vault_coll .find ({})
740
+
741
+ def delete_key (self , id : Binary ) -> DeleteResult :
742
+ """Delete a key document in the key vault collection that has the given ``key_id``.
743
+
744
+ :Parameters:
745
+ - `id` (Binary): The UUID of a key a which must be a
746
+ :class:`~bson.binary.Binary` with subtype 4 (
747
+ :attr:`~bson.binary.UUID_SUBTYPE`).
748
+
749
+ :Returns:
750
+ The delete result.
751
+ """
752
+ self ._check_closed ()
753
+ return self ._key_vault_coll .delete_one ({"_id" : id })
754
+
755
+ def add_key_alt_name (self , id : Binary , key_alt_name : str ) -> Any :
756
+ """Add ``key_alt_name`` to the set of alternate names in the key document with UUID ``key_id``.
757
+
758
+ :Parameters:
759
+ - ``id``: The UUID of a key a which must be a
760
+ :class:`~bson.binary.Binary` with subtype 4 (
761
+ :attr:`~bson.binary.UUID_SUBTYPE`).
762
+ - ``key_alt_name``: The key alternate name to add.
763
+
764
+ :Returns:
765
+ The previous version of the key document.
766
+ """
767
+ self ._check_closed ()
768
+ update = {"$addToSet" : {"keyAltNames" : key_alt_name }}
769
+ return self ._key_vault_coll .find_one_and_update ({"_id" : id }, update )
770
+
771
+ def get_key_by_alt_name (self , key_alt_name : str ) -> Optional [RawBSONDocument ]:
772
+ """Get a key document in the key vault collection that has the given ``key_alt_name``.
773
+
774
+ :Parameters:
775
+ - `key_alt_name`: (str): The key alternate name of the key to get.
776
+
777
+ :Returns:
778
+ The key document.
779
+ """
780
+ self ._check_closed ()
781
+ return self ._key_vault_coll .find_one ({"keyAltNames" : key_alt_name })
782
+
783
+ def remove_key_alt_name (self , id : Binary , key_alt_name : str ) -> Optional [RawBSONDocument ]:
784
+ """Remove ``key_alt_name`` from the set of keyAltNames in the key document with UUID ``id``.
785
+
786
+ Also removes the ``keyAltNames`` field from the key document if it would otherwise be empty.
787
+
788
+ :Parameters:
789
+ - ``id``: The UUID of a key a which must be a
790
+ :class:`~bson.binary.Binary` with subtype 4 (
791
+ :attr:`~bson.binary.UUID_SUBTYPE`).
792
+ - ``key_alt_name``: The key alternate name to remove.
793
+
794
+ :Returns:
795
+ Returns the previous version of the key document.
796
+ """
797
+ self ._check_closed ()
798
+ pipeline = [
799
+ {
800
+ "$set" : {
801
+ "keyAltNames" : {
802
+ "$cond" : [
803
+ {"$eq" : ["$keyAltNames" , [key_alt_name ]]},
804
+ "$$REMOVE" ,
805
+ {
806
+ "$filter" : {
807
+ "input" : "$keyAltNames" ,
808
+ "cond" : {"$ne" : ["$$this" , key_alt_name ]},
809
+ }
810
+ },
811
+ ]
812
+ }
813
+ }
814
+ }
815
+ ]
816
+ return self ._key_vault_coll .find_one_and_update ({"_id" : id }, pipeline )
817
+
818
+ def rewrap_many_data_key (
819
+ self ,
820
+ filter : Mapping [str , Any ],
821
+ provider : Optional [str ] = None ,
822
+ master_key : Optional [Mapping [str , Any ]] = None ,
823
+ ) -> RewrapManyDataKeyResult :
824
+ """Decrypts and encrypts all matching data keys in the key vault with a possibly new `master_key` value.
825
+
826
+ :Parameters:
827
+ - `filter`: A document used to filter the data keys.
828
+ - `provider`: The new KMS provider to use to encrypt the data keys,
829
+ or ``None`` to use the current KMS provider(s).
830
+ - ``master_key``: The master key fields corresponding to the new KMS
831
+ provider when ``provider`` is not ``None``.
832
+
833
+ :Returns:
834
+ A :class:`RewrapManyDataKeyResult`.
835
+ """
836
+ self ._check_closed ()
837
+ with _wrap_encryption_errors ():
838
+ raw_result = self ._encryption .rewrap_many_data_key (filter , provider , master_key )
839
+ if raw_result is None :
840
+ return RewrapManyDataKeyResult ()
841
+
842
+ raw_doc = RawBSONDocument (raw_result , DEFAULT_RAW_BSON_OPTIONS )
843
+ replacements = []
844
+ for key in raw_doc ["v" ]:
845
+ update_model = {
846
+ "$set" : {"keyMaterial" : key ["keyMaterial" ], "masterKey" : key ["masterKey" ]},
847
+ "$currentDate" : {"updateDate" : True },
848
+ }
849
+ op = UpdateOne ({"_id" : key ["_id" ]}, update_model )
850
+ replacements .append (op )
851
+ if not replacements :
852
+ return RewrapManyDataKeyResult ()
853
+ result = self ._key_vault_coll .bulk_write (replacements )
854
+ return RewrapManyDataKeyResult (result )
855
+
679
856
def __enter__ (self ) -> "ClientEncryption" :
680
857
return self
681
858
0 commit comments