Skip to content

Commit 154d878

Browse files
authored
PYTHON-3245 Support explicit queryable encryption (#959)
1 parent 09385be commit 154d878

File tree

10 files changed

+298
-18
lines changed

10 files changed

+298
-18
lines changed

.evergreen/resync-specs.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ do
9393
cpjson client-side-encryption/corpus/ client-side-encryption/corpus
9494
cpjson client-side-encryption/external/ client-side-encryption/external
9595
cpjson client-side-encryption/limits/ client-side-encryption/limits
96+
cpjson client-side-encryption/etc/data client-side-encryption/etc/data
9697
;;
9798
cmap|CMAP|connection-monitoring-and-pooling)
9899
cpjson connection-monitoring-and-pooling/tests cmap

.evergreen/run-tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ if [ -n "$TEST_ENCRYPTION" ]; then
139139
export PYMONGOCRYPT_LIB
140140

141141
# TODO: Test with 'pip install pymongocrypt'
142-
git clone --branch master https://github.com/mongodb/libmongocrypt.git libmongocrypt_git
142+
git clone https://github.com/mongodb/libmongocrypt.git libmongocrypt_git
143143
python -m pip install --prefer-binary -r .evergreen/test-encryption-requirements.txt
144144
python -m pip install ./libmongocrypt_git/bindings/python
145145
python -c "import pymongocrypt; print('pymongocrypt version: '+pymongocrypt.__version__)"

pymongo/encryption.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""Support for explicit client-side field level encryption."""
1616

1717
import contextlib
18+
import enum
1819
import uuid
1920
import weakref
2021
from typing import Any, Mapping, Optional, Sequence
@@ -303,6 +304,7 @@ def _get_internal_client(encrypter, mongo_client):
303304
crypt_shared_lib_path=opts._crypt_shared_lib_path,
304305
crypt_shared_lib_required=opts._crypt_shared_lib_required,
305306
bypass_encryption=opts._bypass_auto_encryption,
307+
bypass_query_analysis=opts._bypass_query_analysis,
306308
),
307309
)
308310
self._closed = False
@@ -352,11 +354,33 @@ def close(self):
352354
self._internal_client = None
353355

354356

355-
class Algorithm(object):
357+
class Algorithm(str, enum.Enum):
356358
"""An enum that defines the supported encryption algorithms."""
357359

358360
AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
361+
"""AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic."""
359362
AEAD_AES_256_CBC_HMAC_SHA_512_Random = "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
363+
"""AEAD_AES_256_CBC_HMAC_SHA_512_Random."""
364+
INDEXED = "Indexed"
365+
"""Indexed.
366+
367+
.. versionadded:: 4.2
368+
"""
369+
UNINDEXED = "Unindexed"
370+
"""Unindexed.
371+
372+
.. versionadded:: 4.2
373+
"""
374+
375+
376+
class QueryType(enum.IntEnum):
377+
"""An enum that defines the supported values for explicit encryption query_type.
378+
379+
.. versionadded:: 4.2
380+
"""
381+
382+
EQUALITY = 1
383+
"""Used to encrypt a value for an equality query."""
360384

361385

362386
class ClientEncryption(object):
@@ -550,6 +574,9 @@ def encrypt(
550574
algorithm: str,
551575
key_id: Optional[Binary] = None,
552576
key_alt_name: Optional[str] = None,
577+
index_key_id: Optional[Binary] = None,
578+
query_type: Optional[int] = None,
579+
contention_factor: Optional[int] = None,
553580
) -> Binary:
554581
"""Encrypt a BSON value with a given key and algorithm.
555582
@@ -564,20 +591,38 @@ def encrypt(
564591
:class:`~bson.binary.Binary` with subtype 4 (
565592
:attr:`~bson.binary.UUID_SUBTYPE`).
566593
- `key_alt_name`: Identifies a key vault document by 'keyAltName'.
594+
- `index_key_id` (bytes): the index key id to use for Queryable Encryption.
595+
- `query_type` (int): The query type to execute. See
596+
:class:`QueryType` for valid options.
597+
- `contention_factor` (int): The contention factor to use
598+
when the algorithm is "Indexed".
567599
568600
:Returns:
569601
The encrypted value, a :class:`~bson.binary.Binary` with subtype 6.
602+
603+
.. versionchanged:: 4.2
604+
Added the `index_key_id`, `query_type`, and `contention_factor` parameters.
570605
"""
571606
self._check_closed()
572607
if key_id is not None and not (
573608
isinstance(key_id, Binary) and key_id.subtype == UUID_SUBTYPE
574609
):
575610
raise TypeError("key_id must be a bson.binary.Binary with subtype 4")
611+
if index_key_id is not None and not (
612+
isinstance(index_key_id, Binary) and index_key_id.subtype == UUID_SUBTYPE
613+
):
614+
raise TypeError("index_key_id must be a bson.binary.Binary with subtype 4")
576615

577616
doc = encode({"v": value}, codec_options=self._codec_options)
578617
with _wrap_encryption_errors():
579618
encrypted_doc = self._encryption.encrypt(
580-
doc, algorithm, key_id=key_id, key_alt_name=key_alt_name
619+
doc,
620+
algorithm,
621+
key_id=key_id,
622+
key_alt_name=key_alt_name,
623+
index_key_id=index_key_id,
624+
query_type=query_type,
625+
contention_factor=contention_factor,
581626
)
582627
return decode(encrypted_doc)["v"] # type: ignore[index]
583628

pymongo/encryption_options.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __init__(
4747
kms_tls_options: Optional[Mapping[str, Any]] = None,
4848
crypt_shared_lib_path: Optional[str] = None,
4949
crypt_shared_lib_required: bool = False,
50+
bypass_query_analysis: bool = False,
5051
) -> None:
5152
"""Options to configure automatic client-side field level encryption.
5253
@@ -145,9 +146,14 @@ def __init__(
145146
- `crypt_shared_lib_path` (optional): Override the path to load the crypt_shared library.
146147
- `crypt_shared_lib_required` (optional): If True, raise an error if libmongocrypt is
147148
unable to load the crypt_shared library.
149+
- `bypass_query_analysis` (optional): If ``True``, disable automatic analysis of
150+
outgoing commands. Set `bypass_query_analysis` to use explicit
151+
encryption on indexed fields without the MongoDB Enterprise Advanced
152+
licensed crypt_shared library.
148153
149154
.. versionchanged:: 4.2
150-
Added `crypt_shared_lib_path` and `crypt_shared_lib_required` parameters
155+
Added `crypt_shared_lib_path`, `crypt_shared_lib_required`, and `bypass_query_analysis`
156+
parameters.
151157
152158
.. versionchanged:: 4.0
153159
Added the `kms_tls_options` parameter and the "kmip" KMS provider.
@@ -179,3 +185,4 @@ def __init__(
179185
self._mongocryptd_spawn_args.append("--idleShutdownTimeoutSecs=60")
180186
# Maps KMS provider name to a SSLContext.
181187
self._kms_ssl_contexts = _parse_kms_tls_options(kms_tls_options)
188+
self._bypass_query_analysis = bypass_query_analysis
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"escCollection": "enxcol_.default.esc",
3+
"eccCollection": "enxcol_.default.ecc",
4+
"ecocCollection": "enxcol_.default.ecoc",
5+
"fields": [
6+
{
7+
"keyId": {
8+
"$binary": {
9+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
10+
"subType": "04"
11+
}
12+
},
13+
"path": "encryptedIndexed",
14+
"bsonType": "string",
15+
"queries": {
16+
"queryType": "equality",
17+
"contention": {
18+
"$numberLong": "0"
19+
}
20+
}
21+
},
22+
{
23+
"keyId": {
24+
"$binary": {
25+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
26+
"subType": "04"
27+
}
28+
},
29+
"path": "encryptedUnindexed",
30+
"bsonType": "string"
31+
}
32+
]
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"_id": {
3+
"$binary": {
4+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
5+
"subType": "04"
6+
}
7+
},
8+
"keyMaterial": {
9+
"$binary": {
10+
"base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==",
11+
"subType": "00"
12+
}
13+
},
14+
"creationDate": {
15+
"$date": {
16+
"$numberLong": "1648914851981"
17+
}
18+
},
19+
"updateDate": {
20+
"$date": {
21+
"$numberLong": "1648914851981"
22+
}
23+
},
24+
"status": {
25+
"$numberInt": "0"
26+
},
27+
"masterKey": {
28+
"provider": "local"
29+
}
30+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"$binary": {
3+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
4+
"subType": "04"
5+
}
6+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"_id": {
3+
"$binary": {
4+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
5+
"subType": "04"
6+
}
7+
},
8+
"keyMaterial": {
9+
"$binary": {
10+
"base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==",
11+
"subType": "00"
12+
}
13+
},
14+
"creationDate": {
15+
"$date": {
16+
"$numberLong": "1648914851981"
17+
}
18+
},
19+
"updateDate": {
20+
"$date": {
21+
"$numberLong": "1648914851981"
22+
}
23+
},
24+
"status": {
25+
"$numberInt": "0"
26+
},
27+
"masterKey": {
28+
"provider": "local"
29+
}
30+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"$binary": {
3+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
4+
"subType": "04"
5+
}
6+
}

0 commit comments

Comments
 (0)