Skip to content
1 change: 1 addition & 0 deletions .evergreen/resync-specs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ do
cpjson client-side-encryption/corpus/ client-side-encryption/corpus
cpjson client-side-encryption/external/ client-side-encryption/external
cpjson client-side-encryption/limits/ client-side-encryption/limits
cpjson client-side-encryption/etc/data client-side-encryption/etc/data
;;
cmap|CMAP|connection-monitoring-and-pooling)
cpjson connection-monitoring-and-pooling/tests cmap
Expand Down
2 changes: 1 addition & 1 deletion .evergreen/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ if [ -n "$TEST_ENCRYPTION" ]; then
export PYMONGOCRYPT_LIB

# TODO: Test with 'pip install pymongocrypt'
git clone --branch master https://github.com/mongodb/libmongocrypt.git libmongocrypt_git
git clone https://github.com/mongodb/libmongocrypt.git libmongocrypt_git
python -m pip install --prefer-binary -r .evergreen/test-encryption-requirements.txt
python -m pip install ./libmongocrypt_git/bindings/python
python -c "import pymongocrypt; print('pymongocrypt version: '+pymongocrypt.__version__)"
Expand Down
49 changes: 47 additions & 2 deletions pymongo/encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""Support for explicit client-side field level encryption."""

import contextlib
import enum
import uuid
import weakref
from typing import Any, Mapping, Optional, Sequence
Expand Down Expand Up @@ -303,6 +304,7 @@ def _get_internal_client(encrypter, mongo_client):
crypt_shared_lib_path=opts._crypt_shared_lib_path,
crypt_shared_lib_required=opts._crypt_shared_lib_required,
bypass_encryption=opts._bypass_auto_encryption,
bypass_query_analysis=opts._bypass_query_analysis,
),
)
self._closed = False
Expand Down Expand Up @@ -352,11 +354,33 @@ def close(self):
self._internal_client = None


class Algorithm(object):
class Algorithm(str, enum.Enum):
"""An enum that defines the supported encryption algorithms."""

AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
"""AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic."""
AEAD_AES_256_CBC_HMAC_SHA_512_Random = "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
"""AEAD_AES_256_CBC_HMAC_SHA_512_Random."""
INDEXED = "Indexed"
"""Indexed.

.. versionadded:: 4.2
"""
UNINDEXED = "Unindexed"
"""Unindexed.

.. versionadded:: 4.2
"""


class QueryType(enum.IntEnum):
"""An enum that defines the supported values for explicit encryption query_type.

.. versionadded:: 4.2
"""

EQUALITY = 1
"""Used to encrypt a value for an equality query."""


class ClientEncryption(object):
Expand Down Expand Up @@ -550,6 +574,9 @@ def encrypt(
algorithm: str,
key_id: Optional[Binary] = None,
key_alt_name: Optional[str] = None,
index_key_id: Optional[Binary] = None,
query_type: Optional[int] = None,
contention_factor: Optional[int] = None,
) -> Binary:
"""Encrypt a BSON value with a given key and algorithm.

Expand All @@ -564,20 +591,38 @@ def encrypt(
:class:`~bson.binary.Binary` with subtype 4 (
:attr:`~bson.binary.UUID_SUBTYPE`).
- `key_alt_name`: Identifies a key vault document by 'keyAltName'.
- `index_key_id` (bytes): the index key id to use for Queryable Encryption.
- `query_type` (int): The query type to execute. See
:class:`QueryType` for valid options.
- `contention_factor` (int): The contention factor to use
when the algorithm is "Indexed".

:Returns:
The encrypted value, a :class:`~bson.binary.Binary` with subtype 6.

.. versionchanged:: 4.2
Added the `index_key_id`, `query_type`, and `contention_factor` parameters.
"""
self._check_closed()
if key_id is not None and not (
isinstance(key_id, Binary) and key_id.subtype == UUID_SUBTYPE
):
raise TypeError("key_id must be a bson.binary.Binary with subtype 4")
if index_key_id is not None and not (
isinstance(index_key_id, Binary) and index_key_id.subtype == UUID_SUBTYPE
):
raise TypeError("index_key_id must be a bson.binary.Binary with subtype 4")

doc = encode({"v": value}, codec_options=self._codec_options)
with _wrap_encryption_errors():
encrypted_doc = self._encryption.encrypt(
doc, algorithm, key_id=key_id, key_alt_name=key_alt_name
doc,
algorithm,
key_id=key_id,
key_alt_name=key_alt_name,
index_key_id=index_key_id,
query_type=query_type,
contention_factor=contention_factor,
)
return decode(encrypted_doc)["v"] # type: ignore[index]

Expand Down
9 changes: 8 additions & 1 deletion pymongo/encryption_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def __init__(
kms_tls_options: Optional[Mapping[str, Any]] = None,
crypt_shared_lib_path: Optional[str] = None,
crypt_shared_lib_required: bool = False,
bypass_query_analysis: bool = False,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will conflict with Julius' auto encryption PR but it was required for the tests.

) -> None:
"""Options to configure automatic client-side field level encryption.

Expand Down Expand Up @@ -145,9 +146,14 @@ def __init__(
- `crypt_shared_lib_path` (optional): Override the path to load the crypt_shared library.
- `crypt_shared_lib_required` (optional): If True, raise an error if libmongocrypt is
unable to load the crypt_shared library.
- `bypass_query_analysis` (optional): If ``True``, disable automatic analysis of
outgoing commands. Set `bypass_query_analysis` to use explicit
encryption on indexed fields without the MongoDB Enterprise Advanced
licensed crypt_shared library.

.. versionchanged:: 4.2
Added `crypt_shared_lib_path` and `crypt_shared_lib_required` parameters
Added `crypt_shared_lib_path`, `crypt_shared_lib_required`, and `bypass_query_analysis`
parameters.

.. versionchanged:: 4.0
Added the `kms_tls_options` parameter and the "kmip" KMS provider.
Expand Down Expand Up @@ -179,3 +185,4 @@ def __init__(
self._mongocryptd_spawn_args.append("--idleShutdownTimeoutSecs=60")
# Maps KMS provider name to a SSLContext.
self._kms_ssl_contexts = _parse_kms_tls_options(kms_tls_options)
self._bypass_query_analysis = bypass_query_analysis
33 changes: 33 additions & 0 deletions test/client-side-encryption/etc/data/encryptedFields.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"escCollection": "enxcol_.default.esc",
"eccCollection": "enxcol_.default.ecc",
"ecocCollection": "enxcol_.default.ecoc",
"fields": [
{
"keyId": {
"$binary": {
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"path": "encryptedIndexed",
"bsonType": "string",
"queries": {
"queryType": "equality",
"contention": {
"$numberLong": "0"
}
}
},
{
"keyId": {
"$binary": {
"base64": "q83vqxI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"path": "encryptedUnindexed",
"bsonType": "string"
}
]
}
30 changes: 30 additions & 0 deletions test/client-side-encryption/etc/data/keys/key1-document.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"_id": {
"$binary": {
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"keyMaterial": {
"$binary": {
"base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==",
"subType": "00"
}
},
"creationDate": {
"$date": {
"$numberLong": "1648914851981"
}
},
"updateDate": {
"$date": {
"$numberLong": "1648914851981"
}
},
"status": {
"$numberInt": "0"
},
"masterKey": {
"provider": "local"
}
}
6 changes: 6 additions & 0 deletions test/client-side-encryption/etc/data/keys/key1-id.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$binary": {
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
"subType": "04"
}
}
30 changes: 30 additions & 0 deletions test/client-side-encryption/etc/data/keys/key2-document.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"_id": {
"$binary": {
"base64": "q83vqxI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"keyMaterial": {
"$binary": {
"base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==",
"subType": "00"
}
},
"creationDate": {
"$date": {
"$numberLong": "1648914851981"
}
},
"updateDate": {
"$date": {
"$numberLong": "1648914851981"
}
},
"status": {
"$numberInt": "0"
},
"masterKey": {
"provider": "local"
}
}
6 changes: 6 additions & 0 deletions test/client-side-encryption/etc/data/keys/key2-id.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$binary": {
"base64": "q83vqxI0mHYSNBI0VniQEg==",
"subType": "04"
}
}
Loading