1717import contextlib
1818import enum
1919import socket
20- import uuid
2120import weakref
2221from typing import Any , Mapping , Optional , Sequence
2322
4039from bson .raw_bson import DEFAULT_RAW_BSON_OPTIONS , RawBSONDocument , _inflate_bson
4140from bson .son import SON
4241from pymongo import _csot
42+ from pymongo .cursor import Cursor
4343from pymongo .daemon import _spawn_daemon
4444from pymongo .encryption_options import AutoEncryptionOpts
4545from pymongo .errors import (
5050)
5151from pymongo .mongo_client import MongoClient
5252from pymongo .network import BLOCKING_IO_ERRORS
53+ from pymongo .operations import UpdateOne
5354from pymongo .pool import PoolOptions , _configured_socket
5455from pymongo .read_concern import ReadConcern
56+ from pymongo .results import BulkWriteResult , DeleteResult
5557from pymongo .ssl_support import get_ssl_context
5658from pymongo .uri_parser import parse_host
5759from pymongo .write_concern import WriteConcern
6062_KMS_CONNECT_TIMEOUT = 10 # TODO: CDRIVER-3262 will define this value.
6163_MONGOCRYPTD_TIMEOUT_MS = 10000
6264
65+
6366_DATA_KEY_OPTS : CodecOptions = CodecOptions (document_class = SON , uuid_representation = STANDARD )
6467# Use RawBSONDocument codec options to avoid needlessly decoding
6568# documents from the key vault.
66- _KEY_VAULT_OPTS = CodecOptions (document_class = RawBSONDocument , uuid_representation = STANDARD )
69+ _KEY_VAULT_OPTS = CodecOptions (document_class = RawBSONDocument )
6770
6871
6972@contextlib .contextmanager
@@ -225,11 +228,11 @@ def insert_data_key(self, data_key):
225228 """
226229 raw_doc = RawBSONDocument (data_key , _KEY_VAULT_OPTS )
227230 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 " )
230233
231234 self .key_vault_coll .insert_one (raw_doc )
232- return Binary ( data_key_id . bytes , subtype = UUID_SUBTYPE )
235+ return data_key_id
233236
234237 def bson_encode (self , doc ):
235238 """Encode a document to BSON.
@@ -256,6 +259,30 @@ def close(self):
256259 self .mongocryptd_client = None
257260
258261
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+
259286class _Encrypter (object ):
260287 """Encrypts and decrypts MongoDB commands.
261288
@@ -514,12 +541,15 @@ def __init__(
514541 self ._encryption = ExplicitEncrypter (
515542 self ._io_callbacks , MongoCryptOptions (kms_providers , None )
516543 )
544+ # Use the same key vault collection as the callback.
545+ self ._key_vault_coll = self ._io_callbacks .key_vault_coll
517546
518547 def create_data_key (
519548 self ,
520549 kms_provider : str ,
521550 master_key : Optional [Mapping [str , Any ]] = None ,
522551 key_alt_names : Optional [Sequence [str ]] = None ,
552+ key_material : Optional [bytes ] = None ,
523553 ) -> Binary :
524554 """Create and insert a new data key into the key vault collection.
525555
@@ -580,16 +610,24 @@ def create_data_key(
580610 # reference the key with the alternate name
581611 client_encryption.encrypt("457-55-5462", keyAltName="name1",
582612 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.
583615
584616 :Returns:
585617 The ``_id`` of the created data key document as a
586618 :class:`~bson.binary.Binary` with subtype
587619 :data:`~bson.binary.UUID_SUBTYPE`.
620+
621+ .. versionchanged:: 4.2
622+ Added the `key_material` parameter.
588623 """
589624 self ._check_closed ()
590625 with _wrap_encryption_errors ():
591626 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 ,
593631 )
594632
595633 def encrypt (
@@ -676,6 +714,145 @@ def decrypt(self, value: Binary) -> Any:
676714 decrypted_doc = self ._encryption .decrypt (doc )
677715 return decode (decrypted_doc , codec_options = self ._codec_options )["v" ]
678716
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+
679856 def __enter__ (self ) -> "ClientEncryption" :
680857 return self
681858
0 commit comments