libmongocrypt-helper 1.20.1.0.1001 → 1.20.5.0.1001
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/libmongocrypt/libmongocrypt/CHANGELOG.md +21 -0
- data/ext/libmongocrypt/libmongocrypt/Earthfile +4 -3
- data/ext/libmongocrypt/libmongocrypt/bindings/python/CONTRIBUTING.md +12 -0
- data/ext/libmongocrypt/libmongocrypt/bindings/python/scripts/update-version.sh +1 -1
- data/ext/libmongocrypt/libmongocrypt/doc/releasing.md +10 -0
- data/ext/libmongocrypt/libmongocrypt/etc/cyclonedx.sbom.json +2 -2
- data/ext/libmongocrypt/libmongocrypt/kms-message/src/kms_gcp_request.c +42 -0
- data/ext/libmongocrypt/libmongocrypt/src/mc-fle2-insert-update-payload-v2.c +6 -1
- data/ext/libmongocrypt/libmongocrypt/src/mc-fle2-insert-update-payload.c +6 -1
- data/ext/libmongocrypt/libmongocrypt/src/mc-fle2-payload-iev.c +10 -2
- data/ext/libmongocrypt/libmongocrypt/src/mc-fle2-payload-uev-common.c +1 -1
- data/ext/libmongocrypt/libmongocrypt/src/mc-schema-broker.c +10 -2
- data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-crypto.c +7 -3
- data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-ctx-decrypt.c +28 -1
- data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-ctx-encrypt.c +25 -4
- data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-endpoint.c +122 -10
- data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-kek.c +19 -7
- data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-key.c +4 -0
- data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-private.h +5 -0
- data/ext/libmongocrypt/libmongocrypt/src/mongocrypt.c +11 -0
- data/ext/libmongocrypt/libmongocrypt/test/data/kek-tests.json +3 -3
- data/ext/libmongocrypt/libmongocrypt/test/test-gcp-auth.c +51 -0
- data/ext/libmongocrypt/libmongocrypt/test/test-mc-fle2-payload-iup-v2.c +64 -0
- data/ext/libmongocrypt/libmongocrypt/test/test-mc-fle2-payload-iup.c +67 -0
- data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-ctx-decrypt.c +46 -20
- data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-ctx-encrypt.c +114 -0
- data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-endpoint.c +170 -0
- data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-kek.c +36 -0
- data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-key.c +26 -0
- data/lib/libmongocrypt_helper/version.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b483fc7b479e693858209265fa305b4bd51e34592b452bbbd36c32c13f262802
|
|
4
|
+
data.tar.gz: 352d1052b07f14081865b5c02fe8b33ce2b0a739550db43a6e57d6c26cd1a08b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 945910ccf2515b64cbd9dd1d97a9de2822562a2544533006e3aa5fa7837107697f6b38e5f43d0da1ce3218fa98d36362a9f883362d9e1efb16bb90fbee7d5236
|
|
7
|
+
data.tar.gz: ef89918bdf09d579c60cce8aa5ecdd779a655edffc7223fef5a97fc00fa63ef787c3d5b360ce880812dc3d96562c1ebda7ca158ba4968377e1fd95fcb9c9a81c
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# ChangeLog
|
|
2
2
|
|
|
3
|
+
## 1.20.5
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Fix possible abort parsing endpoints.
|
|
7
|
+
- Reject more payloads that cannot be decrypted in explicit decryption.
|
|
8
|
+
- Fix more degenerate cases of masterKey parsing.
|
|
9
|
+
|
|
10
|
+
## 1.20.4
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Reject undersized decryption payloads.
|
|
14
|
+
|
|
15
|
+
## 1.20.3
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Validate database and collection name arguments against "." and NUL bytes
|
|
19
|
+
## 1.20.2
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Add checks when creating GCP requests.
|
|
23
|
+
|
|
3
24
|
## 1.20.1
|
|
4
25
|
|
|
5
26
|
## Changed
|
|
@@ -438,12 +438,12 @@ test-deb-packages-from-ppa:
|
|
|
438
438
|
RUN ./test.out
|
|
439
439
|
|
|
440
440
|
# `sign` uses Garasign to sign a file with the libmongocrypt key.
|
|
441
|
-
# Requires prior authentication with
|
|
441
|
+
# Requires prior authentication with the DevProd Platforms ECR registry (see doc/releasing.md).
|
|
442
442
|
# See: https://docs.devprod.prod.corp.mongodb.com/release-tools-container-images/garasign/garasign_signing/.
|
|
443
443
|
sign:
|
|
444
444
|
ARG --required file_to_sign
|
|
445
445
|
ARG --required output_file
|
|
446
|
-
FROM
|
|
446
|
+
FROM 901841024863.dkr.ecr.us-east-1.amazonaws.com/release-infrastructure/garasign-gpg
|
|
447
447
|
WORKDIR /s
|
|
448
448
|
COPY ${file_to_sign} /s/file
|
|
449
449
|
RUN --secret garasign_username --secret garasign_password \
|
|
@@ -460,8 +460,9 @@ sign:
|
|
|
460
460
|
# An environment with the `silkbomb` command.
|
|
461
461
|
#
|
|
462
462
|
# See https://docs.devprod.prod.corp.mongodb.com/mms/python/src/sbom/silkbomb/ for documentation of silkbomb.
|
|
463
|
+
# Requires prior authentication with the DevProd Platforms ECR registry (see doc/releasing.md).
|
|
463
464
|
silkbomb:
|
|
464
|
-
FROM
|
|
465
|
+
FROM 901841024863.dkr.ecr.us-east-1.amazonaws.com/release-infrastructure/silkbomb:2.0
|
|
465
466
|
# Alias the silkbomb executable to a simpler name:
|
|
466
467
|
RUN ln -s /python/src/sbom/silkbomb/bin /usr/local/bin/silkbomb
|
|
467
468
|
|
|
@@ -28,6 +28,18 @@ bash scripts/update-version.sh <new-version>
|
|
|
28
28
|
This will set the version in `scripts/libmongocrypt-version.sh` and update `sbom.json` to reflect
|
|
29
29
|
the new vendored version of `libmongocrypt`.
|
|
30
30
|
|
|
31
|
+
This pulls the `silkbomb` image from the DevProd Platforms ECR registry. Authenticate to the ECR
|
|
32
|
+
registry with the container launcher (`docker` or `podman`) before running the script:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
aws sso login --profile ECRScopedAccess-901841024863
|
|
36
|
+
|
|
37
|
+
# Authenticate "podman" or "docker"
|
|
38
|
+
CONTAINER_LAUNCHER="podman"
|
|
39
|
+
|
|
40
|
+
aws ecr get-login-password --region us-east-1 --profile ECRScopedAccess-901841024863 | $CONTAINER_LAUNCHER login --username AWS --password-stdin 901841024863.dkr.ecr.us-east-1.amazonaws.com
|
|
41
|
+
```
|
|
42
|
+
|
|
31
43
|
## Building wheels
|
|
32
44
|
|
|
33
45
|
To build wheels, run `scripts/release.sh`. It will build the appropriate wheel for the current system
|
|
@@ -21,7 +21,7 @@ else
|
|
|
21
21
|
fi
|
|
22
22
|
|
|
23
23
|
echo "pkg:github/mongodb/libmongocrypt@$LIBMONGOCRYPT_VERSION" > purls.txt
|
|
24
|
-
$DOCKER run --platform="linux/amd64" -it --rm -v $(pwd):$(pwd)
|
|
24
|
+
$DOCKER run --platform="linux/amd64" -it --rm -v $(pwd):$(pwd) 901841024863.dkr.ecr.us-east-1.amazonaws.com/release-infrastructure/silkbomb:2.0 update --purls=$(pwd)/purls.txt -o $(pwd)/sbom.json
|
|
25
25
|
rm purls.txt
|
|
26
26
|
|
|
27
27
|
popd
|
|
@@ -93,6 +93,15 @@ Do the following when releasing:
|
|
|
93
93
|
```bash
|
|
94
94
|
./.evergreen/earthly.sh +sbom-generate-new-serial-number
|
|
95
95
|
```
|
|
96
|
+
This pulls the `silkbomb` image from the DevProd Platforms ECR registry. Authenticate to the ECR registry with the container launcher (`docker` or `podman`):
|
|
97
|
+
```bash
|
|
98
|
+
aws sso login --profile ECRScopedAccess-901841024863
|
|
99
|
+
|
|
100
|
+
# Authenticate "podman" or "docker"
|
|
101
|
+
CONTAINER_LAUNCHER="podman"
|
|
102
|
+
|
|
103
|
+
aws ecr get-login-password --region us-east-1 --profile ECRScopedAccess-901841024863 | $CONTAINER_LAUNCHER login --username AWS --password-stdin 901841024863.dkr.ecr.us-east-1.amazonaws.com
|
|
104
|
+
```
|
|
96
105
|
Commit resulting `etc/cyclonedx.sbom.json` and push to `rx.y`.
|
|
97
106
|
- Remove yourself from the [releases team](https://github.com/orgs/mongodb/teams/dbx-c-cxx-releases) on GitHub via [MANA](https://mana.corp.mongodb.com/resources/68029673d39aa9f7de6399f9).
|
|
98
107
|
- If this is a new non-patch release (e.g. `x.y.0`):
|
|
@@ -130,6 +139,7 @@ Do the following when releasing:
|
|
|
130
139
|
```bash
|
|
131
140
|
./.evergreen/earthly.sh +sbom-generate-new-serial-number
|
|
132
141
|
```
|
|
142
|
+
Requires AWS SSO access to the `ECRScopedAccess-901841024863` profile (see note above).
|
|
133
143
|
Commit resulting `etc/cyclonedx.sbom.json`.
|
|
134
144
|
- Update the release on the [Jira releases page](https://jira.mongodb.org/projects/MONGOCRYPT/versions).
|
|
135
145
|
- Record the release on [C/C++ Release Info](https://docs.google.com/spreadsheets/d/1yHfGmDnbA5-Qt8FX4tKWC5xk9AhzYZx1SKF4AD36ecY/edit?usp=sharing). This is done to leave commentary about the process.
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
}
|
|
58
58
|
],
|
|
59
59
|
"metadata": {
|
|
60
|
-
"timestamp": "2026-
|
|
60
|
+
"timestamp": "2026-09-03T12:34:15.836217+00:00",
|
|
61
61
|
"tools": [
|
|
62
62
|
{
|
|
63
63
|
"externalReferences": [
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
}
|
|
101
101
|
]
|
|
102
102
|
},
|
|
103
|
-
"serialNumber": "urn:uuid:
|
|
103
|
+
"serialNumber": "urn:uuid:f38989f9-86cb-4a6e-94b5-bbcbf0a8fc1f",
|
|
104
104
|
"version": 1,
|
|
105
105
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
106
106
|
"bomFormat": "CycloneDX",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
#include "kms_message_private.h"
|
|
21
21
|
#include "kms_request_opt_private.h"
|
|
22
22
|
|
|
23
|
+
#include <ctype.h>
|
|
24
|
+
|
|
23
25
|
/* Set a default expiration of 5 minutes for JSON Web Tokens (GCP allows up to
|
|
24
26
|
* one hour) */
|
|
25
27
|
#define JWT_EXPIRATION_SECS 5 * 60
|
|
@@ -145,6 +147,38 @@ done:
|
|
|
145
147
|
return req;
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
/* _check_key_identifier returns true if `value` is safe to use as a URL path segment.
|
|
151
|
+
*
|
|
152
|
+
* Key identifiers may originate from key vault documents or caller-supplied options. They are
|
|
153
|
+
* formatted into the request path, so an unescaped '/', '?', '#', space, or CR/LF would rewrite
|
|
154
|
+
* the request target or inject header lines into the request.
|
|
155
|
+
*
|
|
156
|
+
* Only the RFC 3986 unreserved characters are permitted. This is a superset of what GCP itself
|
|
157
|
+
* accepts, so it does not reject any identifier that would otherwise work:
|
|
158
|
+
* - keyRing and keyName must match `[a-zA-Z0-9_-]{1,63}`. See
|
|
159
|
+
* https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings/create and
|
|
160
|
+
* https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys/create
|
|
161
|
+
* - projectId is 6-30 characters of lowercase letters, numbers, and hyphens. See
|
|
162
|
+
* https://cloud.google.com/resource-manager/docs/creating-managing-projects
|
|
163
|
+
* - location is a Cloud location name (e.g. "global", "us-east1").
|
|
164
|
+
* - keyVersion is a positive integer.
|
|
165
|
+
*
|
|
166
|
+
* `value` is not included in the error message: it is untrusted, and the error may be logged. */
|
|
167
|
+
static bool
|
|
168
|
+
_check_key_identifier (kms_request_t *req, const char *name, const char *value)
|
|
169
|
+
{
|
|
170
|
+
const char *c;
|
|
171
|
+
|
|
172
|
+
for (c = value; *c != '\0'; c++) {
|
|
173
|
+
if (!isalnum ((unsigned char) *c) && *c != '-' && *c != '.' && *c != '_' &&
|
|
174
|
+
*c != '~') {
|
|
175
|
+
KMS_ERROR (req, "Invalid character in GCP KMS key identifier: %s", name);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
148
182
|
static kms_request_t *
|
|
149
183
|
_encrypt_decrypt_common (const char *encrypt_decrypt,
|
|
150
184
|
const char *host,
|
|
@@ -192,6 +226,14 @@ _encrypt_decrypt_common (const char *encrypt_decrypt,
|
|
|
192
226
|
goto done;
|
|
193
227
|
}
|
|
194
228
|
|
|
229
|
+
if (!_check_key_identifier (req, "projectId", project_id) ||
|
|
230
|
+
!_check_key_identifier (req, "location", location) ||
|
|
231
|
+
!_check_key_identifier (req, "keyRing", key_ring_name) ||
|
|
232
|
+
!_check_key_identifier (req, "keyName", key_name) ||
|
|
233
|
+
(key_version && !_check_key_identifier (req, "keyVersion", key_version))) {
|
|
234
|
+
goto done;
|
|
235
|
+
}
|
|
236
|
+
|
|
195
237
|
value_base64 = kms_message_raw_to_b64 (value, value_len);
|
|
196
238
|
if (!value_base64) {
|
|
197
239
|
KMS_ERROR (req, "Could not bases64-encode plaintext");
|
|
@@ -523,7 +523,12 @@ const _mongocrypt_buffer_t *mc_FLE2InsertUpdatePayloadV2_decrypt(_mongocrypt_cry
|
|
|
523
523
|
return NULL;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
|
|
526
|
+
const uint32_t plaintext_len = fle2v2->get_plaintext_len(ciphertext.len, status);
|
|
527
|
+
if (!mongocrypt_status_ok(status)) {
|
|
528
|
+
return NULL;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
_mongocrypt_buffer_resize(&iup->plaintext, plaintext_len);
|
|
527
532
|
uint32_t bytes_written;
|
|
528
533
|
|
|
529
534
|
if (!fle2v2->do_decrypt(crypto, &iup->userKeyId, user_key, &ciphertext, &iup->plaintext, &bytes_written, status)) {
|
|
@@ -252,7 +252,12 @@ const _mongocrypt_buffer_t *mc_FLE2InsertUpdatePayload_decrypt(_mongocrypt_crypt
|
|
|
252
252
|
return NULL;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
const uint32_t plaintext_len = fle2aead->get_plaintext_len(ciphertext.len, status);
|
|
256
|
+
if (!mongocrypt_status_ok(status)) {
|
|
257
|
+
return NULL;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_mongocrypt_buffer_resize(&iup->plaintext, plaintext_len);
|
|
256
261
|
uint32_t bytes_written; /* ignored */
|
|
257
262
|
|
|
258
263
|
if (!fle2aead
|
|
@@ -353,7 +353,11 @@ static bool mc_FLE2IndexedEncryptedValue_decrypt(_mongocrypt_crypto_t *crypto,
|
|
|
353
353
|
const _mongocrypt_buffer_t *token_buf = mc_ServerDataEncryptionLevel1Token_get(token);
|
|
354
354
|
uint32_t bytes_written;
|
|
355
355
|
|
|
356
|
-
|
|
356
|
+
const uint32_t inner_len = fle2alg->get_plaintext_len(iev->InnerEncrypted.len, status);
|
|
357
|
+
if (!mongocrypt_status_ok(status)) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
_mongocrypt_buffer_resize(&iev->Inner, inner_len);
|
|
357
361
|
|
|
358
362
|
/* Decrypt InnerEncrypted. */
|
|
359
363
|
if (!fle2alg->do_decrypt(crypto,
|
|
@@ -445,7 +449,11 @@ bool mc_FLE2IndexedEncryptedValue_add_K_Key(_mongocrypt_crypto_t *crypto,
|
|
|
445
449
|
return false;
|
|
446
450
|
}
|
|
447
451
|
/* Attempt to decrypt ClientEncryptedValue */
|
|
448
|
-
|
|
452
|
+
const uint32_t client_value_len = fle2aead->get_plaintext_len(iev->ClientEncryptedValue.len, status);
|
|
453
|
+
if (!mongocrypt_status_ok(status)) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
_mongocrypt_buffer_resize(&iev->ClientValue, client_value_len);
|
|
449
457
|
uint32_t bytes_written;
|
|
450
458
|
if (!fle2aead->do_decrypt(crypto,
|
|
451
459
|
&iev->K_KeyId,
|
|
@@ -93,7 +93,7 @@ const _mongocrypt_buffer_t *_mc_FLE2UnindexedEncryptedValueCommon_decrypt(_mongo
|
|
|
93
93
|
memcpy(AD.data + 1, key_uuid->data, key_uuid->len);
|
|
94
94
|
AD.data[1 + key_uuid->len] = (uint8_t)original_bson_type;
|
|
95
95
|
const uint32_t plaintext_len = fle2aead->get_plaintext_len(ciphertext->len, status);
|
|
96
|
-
if (
|
|
96
|
+
if (!mongocrypt_status_ok(status)) {
|
|
97
97
|
_mongocrypt_buffer_cleanup(&AD);
|
|
98
98
|
return NULL;
|
|
99
99
|
}
|
|
@@ -323,7 +323,11 @@ bool mc_schema_broker_satisfy_from_collinfo(mc_schema_broker_t *sb,
|
|
|
323
323
|
CLIENT_ERR("failed to find 'name' in collinfo in database: %s", sb->db);
|
|
324
324
|
return false;
|
|
325
325
|
}
|
|
326
|
-
|
|
326
|
+
uint32_t coll_len;
|
|
327
|
+
coll = bson_iter_utf8(&name_iter, &coll_len);
|
|
328
|
+
if (!_mongocrypt_check_no_embedded_nul(coll, coll_len, "collection name in collinfo", status)) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
327
331
|
}
|
|
328
332
|
|
|
329
333
|
// Cache the received collinfo.
|
|
@@ -508,7 +512,11 @@ bool mc_schema_broker_satisfy_from_create_or_collMod(mc_schema_broker_t *sb,
|
|
|
508
512
|
CLIENT_ERR("Failed to get collection name from command");
|
|
509
513
|
return false;
|
|
510
514
|
}
|
|
511
|
-
|
|
515
|
+
uint32_t coll_len;
|
|
516
|
+
const char *coll = bson_iter_utf8(&iter, &coll_len);
|
|
517
|
+
if (!_mongocrypt_check_no_embedded_nul(coll, coll_len, "collection name", status)) {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
512
520
|
|
|
513
521
|
// Check if schema was requested.
|
|
514
522
|
mc_schema_entry_t *found = NULL;
|
|
@@ -443,9 +443,9 @@ static uint32_t _mongocrypt_calculate_plaintext_len(uint32_t inlen,
|
|
|
443
443
|
_mongocrypt_hmac_type_t hmac,
|
|
444
444
|
mongocrypt_status_t *status) {
|
|
445
445
|
const uint32_t hmaclen = (hmac == HMAC_NONE) ? 0 : MONGOCRYPT_HMAC_LEN;
|
|
446
|
-
const uint32_t mincipher = (mode == MODE_CTR) ?
|
|
446
|
+
const uint32_t mincipher = (mode == MODE_CTR) ? 1 : MONGOCRYPT_BLOCK_SIZE;
|
|
447
447
|
if (inlen < (MONGOCRYPT_IV_LEN + mincipher + hmaclen)) {
|
|
448
|
-
CLIENT_ERR("input ciphertext too small. Must be
|
|
448
|
+
CLIENT_ERR("input ciphertext too small. Must be more than %" PRIu32 " bytes",
|
|
449
449
|
MONGOCRYPT_IV_LEN + mincipher + hmaclen);
|
|
450
450
|
return 0;
|
|
451
451
|
}
|
|
@@ -1346,7 +1346,11 @@ bool _mongocrypt_unwrap_key(_mongocrypt_crypto_t *crypto,
|
|
|
1346
1346
|
|
|
1347
1347
|
// _mongocrypt_wrap_key() uses FLE1 algorithm parameters.
|
|
1348
1348
|
_mongocrypt_buffer_init(dek);
|
|
1349
|
-
|
|
1349
|
+
const uint32_t plaintext_len = fle1alg->get_plaintext_len(encrypted_dek->len, status);
|
|
1350
|
+
if (!mongocrypt_status_ok(status)) {
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
_mongocrypt_buffer_resize(dek, plaintext_len);
|
|
1350
1354
|
|
|
1351
1355
|
if (!fle1alg->do_decrypt(crypto, NULL /* associated data. */, kek, encrypted_dek, dek, &bytes_written, status)) {
|
|
1352
1356
|
return false;
|
|
@@ -344,7 +344,7 @@ static bool _replace_FLE1Payload_with_plaintext(void *ctx,
|
|
|
344
344
|
|
|
345
345
|
const _mongocrypt_value_encryption_algorithm_t *fle1alg = _mcFLE1Algorithm();
|
|
346
346
|
plaintext.len = fle1alg->get_plaintext_len(ciphertext.data.len, status);
|
|
347
|
-
CHECK_AND_RETURN(
|
|
347
|
+
CHECK_AND_RETURN(mongocrypt_status_ok(status));
|
|
348
348
|
plaintext.data = bson_malloc0(plaintext.len);
|
|
349
349
|
BSON_ASSERT(plaintext.data);
|
|
350
350
|
|
|
@@ -766,6 +766,25 @@ static void _cleanup(mongocrypt_ctx_t *ctx) {
|
|
|
766
766
|
_mongocrypt_buffer_cleanup(&dctx->decrypted_doc);
|
|
767
767
|
}
|
|
768
768
|
|
|
769
|
+
/* Returns true if a subtype-6 payload's first byte names a recognized FLE blob
|
|
770
|
+
* subtype. */
|
|
771
|
+
static bool _is_decryptable_fle_blob_subtype(uint8_t byte) {
|
|
772
|
+
switch (byte) {
|
|
773
|
+
case MC_SUBTYPE_FLE1DeterministicEncryptedValue:
|
|
774
|
+
case MC_SUBTYPE_FLE1RandomEncryptedValue:
|
|
775
|
+
case MC_SUBTYPE_FLE2InsertUpdatePayload:
|
|
776
|
+
case MC_SUBTYPE_FLE2UnindexedEncryptedValue:
|
|
777
|
+
case MC_SUBTYPE_FLE2IndexedEqualityEncryptedValue:
|
|
778
|
+
case MC_SUBTYPE_FLE2IndexedRangeEncryptedValue:
|
|
779
|
+
case MC_SUBTYPE_FLE2InsertUpdatePayloadV2:
|
|
780
|
+
case MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2:
|
|
781
|
+
case MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2:
|
|
782
|
+
case MC_SUBTYPE_FLE2UnindexedEncryptedValueV2:
|
|
783
|
+
case MC_SUBTYPE_FLE2IndexedTextEncryptedValue: return true;
|
|
784
|
+
default: return false;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
769
788
|
bool mongocrypt_ctx_explicit_decrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *msg) {
|
|
770
789
|
bson_iter_t iter;
|
|
771
790
|
bson_t as_bson;
|
|
@@ -804,6 +823,14 @@ bool mongocrypt_ctx_explicit_decrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_bina
|
|
|
804
823
|
CLIENT_ERR("decryption expected BSON binary subtype %d, got %d", (int)BSON_SUBTYPE_ENCRYPTED, (int)subtype);
|
|
805
824
|
return _mongocrypt_ctx_fail(ctx);
|
|
806
825
|
}
|
|
826
|
+
if (binary_len == 0) {
|
|
827
|
+
CLIENT_ERR("unsupported FLE blob subtype: expected non-empty payload");
|
|
828
|
+
return _mongocrypt_ctx_fail(ctx);
|
|
829
|
+
}
|
|
830
|
+
if (!_is_decryptable_fle_blob_subtype(binary[0])) {
|
|
831
|
+
CLIENT_ERR("unsupported FLE blob subtype: %hhu", binary[0]);
|
|
832
|
+
return _mongocrypt_ctx_fail(ctx);
|
|
833
|
+
}
|
|
807
834
|
}
|
|
808
835
|
|
|
809
836
|
if (!mongocrypt_ctx_decrypt_init(ctx, msg)) {
|
|
@@ -2303,7 +2303,11 @@ static bool _check_cmd_for_auto_encrypt_bulkWrite(mongocrypt_binary_t *cmd,
|
|
|
2303
2303
|
return false;
|
|
2304
2304
|
}
|
|
2305
2305
|
|
|
2306
|
-
|
|
2306
|
+
uint32_t target_ns_len;
|
|
2307
|
+
const char *target_ns = bson_iter_utf8(&ns_iter, &target_ns_len);
|
|
2308
|
+
if (!_mongocrypt_check_no_embedded_nul(target_ns, target_ns_len, "namespace in `bulkWrite` command", status)) {
|
|
2309
|
+
return false;
|
|
2310
|
+
}
|
|
2307
2311
|
// Parse `target_ns` into "<db>.<coll>"
|
|
2308
2312
|
const char *dot = strstr(target_ns, ".");
|
|
2309
2313
|
if (!dot) {
|
|
@@ -2375,7 +2379,12 @@ _check_cmd_for_auto_encrypt(mongocrypt_binary_t *cmd, bool *bypass, char **targe
|
|
|
2375
2379
|
}
|
|
2376
2380
|
|
|
2377
2381
|
if (BSON_ITER_HOLDS_UTF8(&target_coll_iter)) {
|
|
2378
|
-
|
|
2382
|
+
uint32_t target_coll_len;
|
|
2383
|
+
const char *target_coll_str = bson_iter_utf8(&target_coll_iter, &target_coll_len);
|
|
2384
|
+
if (!_mongocrypt_check_no_embedded_nul(target_coll_str, target_coll_len, "collection name", status)) {
|
|
2385
|
+
return false;
|
|
2386
|
+
}
|
|
2387
|
+
*target_coll = bson_strdup(target_coll_str);
|
|
2379
2388
|
} else {
|
|
2380
2389
|
*target_coll = NULL;
|
|
2381
2390
|
}
|
|
@@ -2572,7 +2581,11 @@ static bool find_collections_in_pipeline(mc_schema_broker_t *sb,
|
|
|
2572
2581
|
stage_key);
|
|
2573
2582
|
return false;
|
|
2574
2583
|
}
|
|
2575
|
-
|
|
2584
|
+
uint32_t from_len;
|
|
2585
|
+
const char *from = bson_iter_utf8(&lookup_iter, &from_len);
|
|
2586
|
+
if (!_mongocrypt_check_no_embedded_nul(from, from_len, "'from' collection name", status)) {
|
|
2587
|
+
return false;
|
|
2588
|
+
}
|
|
2576
2589
|
if (!mc_schema_broker_request(sb, db, from, status)) {
|
|
2577
2590
|
return false;
|
|
2578
2591
|
}
|
|
@@ -2631,7 +2644,11 @@ static bool find_collections_in_pipeline(mc_schema_broker_t *sb,
|
|
|
2631
2644
|
stage_key);
|
|
2632
2645
|
return false;
|
|
2633
2646
|
}
|
|
2634
|
-
|
|
2647
|
+
uint32_t coll_len;
|
|
2648
|
+
const char *coll = bson_iter_utf8(&unionWith_iter, &coll_len);
|
|
2649
|
+
if (!_mongocrypt_check_no_embedded_nul(coll, coll_len, "'coll' collection name", status)) {
|
|
2650
|
+
return false;
|
|
2651
|
+
}
|
|
2635
2652
|
if (!mc_schema_broker_request(sb, db, coll, status)) {
|
|
2636
2653
|
return false;
|
|
2637
2654
|
}
|
|
@@ -2722,6 +2739,10 @@ bool mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t *ctx, const char *db, int32_t
|
|
|
2722
2739
|
return _mongocrypt_ctx_fail_w_msg(ctx, "invalid db");
|
|
2723
2740
|
}
|
|
2724
2741
|
|
|
2742
|
+
if (strchr(ectx->cmd_db, '.')) {
|
|
2743
|
+
return _mongocrypt_ctx_fail_w_msg(ctx, "invalid db: must not contain a dot");
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2725
2746
|
if (0 == strcmp(ectx->cmd_name, "bulkWrite")) {
|
|
2726
2747
|
// Handle `bulkWrite` as a special case.
|
|
2727
2748
|
// `bulkWrite` includes the target namespaces in an `nsInfo` field.
|
|
@@ -34,6 +34,104 @@ void _mongocrypt_endpoint_destroy(_mongocrypt_endpoint_t *endpoint) {
|
|
|
34
34
|
bson_free(endpoint);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/* RFC 3986 section 2.3: unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" */
|
|
38
|
+
static bool _is_unreserved(char c) {
|
|
39
|
+
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.'
|
|
40
|
+
|| c == '_' || c == '~';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* RFC 3986 section 2.2: reserved = gen-delims / sub-delims */
|
|
44
|
+
static bool _is_reserved(char c) {
|
|
45
|
+
return NULL
|
|
46
|
+
!= strchr(":/?#[]@" /* gen-delims */
|
|
47
|
+
"!$&'()*+,;=" /* sub-delims */,
|
|
48
|
+
c);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static bool _is_hexdig(char c) {
|
|
52
|
+
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* _endpoint_chars_are_valid returns true if `endpoint_raw` contains only characters permitted in a URI
|
|
56
|
+
* by RFC 3986. Useful to prevent malformed endpoints being used to construct HTTP requests. */
|
|
57
|
+
static bool _endpoint_chars_are_valid(const char *endpoint_raw, mongocrypt_status_t *status) {
|
|
58
|
+
for (const char *c = endpoint_raw; *c != '\0'; c++) {
|
|
59
|
+
if (_is_unreserved(*c) || _is_reserved(*c)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (*c == '%') {
|
|
63
|
+
/* Returns false if either character is the NUL terminator. */
|
|
64
|
+
if (!_is_hexdig(c[1])) {
|
|
65
|
+
CLIENT_ERR("Invalid percent-encoding in endpoint at offset %zu: 0x%02x",
|
|
66
|
+
(size_t)(c - endpoint_raw) + 1,
|
|
67
|
+
(unsigned char)c[1]);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (!_is_hexdig(c[2])) {
|
|
71
|
+
CLIENT_ERR("Invalid percent-encoding in endpoint at offset %zu: 0x%02x",
|
|
72
|
+
(size_t)(c - endpoint_raw) + 2,
|
|
73
|
+
(unsigned char)c[2]);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
c += 2;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
CLIENT_ERR("Invalid character in endpoint at offset %zu: 0x%02x",
|
|
80
|
+
(size_t)(c - endpoint_raw),
|
|
81
|
+
(unsigned char)*c);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* RFC 3986 section 2.2: sub-delims. Permitted in a reg-name; gen-delims are not. */
|
|
88
|
+
static bool _is_sub_delim(char c) {
|
|
89
|
+
return NULL != strchr("!$&'()*+,;=", c);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* _host_chars_are_valid returns true if `host` is an RFC 3986 section 3.2.2 `reg-name`:
|
|
93
|
+
*
|
|
94
|
+
* reg-name = *( unreserved / pct-encoded / sub-delims )
|
|
95
|
+
*
|
|
96
|
+
* The gen-delims (":/?#[]@") are not permitted. The parse above already ends the host at ':', '/', and '?',
|
|
97
|
+
* so in practice this rejects '#', '@', and the brackets of an IP-literal.
|
|
98
|
+
*
|
|
99
|
+
* This is defense in depth. `host` reaches the Host header, where CR and LF are the only characters that can
|
|
100
|
+
* alter the message and kms-message rejects them, and `host_and_port` is passed to the consumer as the
|
|
101
|
+
* address to connect to, where a gen-delim yields a name that does not resolve rather than a structural
|
|
102
|
+
* problem. An `IP-literal` (RFC 3986 section 3.2.2) is not supported: the parse splits the host at the first
|
|
103
|
+
* colon with no bracket handling, so "[2001:db8::1]" yielded a host of "[2001" and a port of "db8::1]". */
|
|
104
|
+
static bool _host_chars_are_valid(const char *host, mongocrypt_status_t *status) {
|
|
105
|
+
for (const char *c = host; *c != '\0'; c++) {
|
|
106
|
+
if (_is_unreserved(*c) || _is_sub_delim(*c)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (*c == '%') {
|
|
110
|
+
/* Already validated by _endpoint_chars_are_valid. */
|
|
111
|
+
c += 2;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
CLIENT_ERR("Invalid character in endpoint host: 0x%02x", (unsigned char)*c);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* _port_chars_are_valid returns true if `port` is a non-empty run of digits. */
|
|
121
|
+
static bool _port_chars_are_valid(const char *port, mongocrypt_status_t *status) {
|
|
122
|
+
if (*port == '\0') {
|
|
123
|
+
CLIENT_ERR("Invalid endpoint, expected a port after the colon");
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
for (const char *c = port; *c != '\0'; c++) {
|
|
127
|
+
if (*c < '0' || *c > '9') {
|
|
128
|
+
CLIENT_ERR("Invalid character in endpoint port: 0x%02x", (unsigned char)*c);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
37
135
|
/* Parses a subset of URIs of the form:
|
|
38
136
|
* [protocol://][host[:port]][path][?query]
|
|
39
137
|
*/
|
|
@@ -61,6 +159,11 @@ _mongocrypt_endpoint_t *_mongocrypt_endpoint_new(const char *endpoint_raw,
|
|
|
61
159
|
goto fail;
|
|
62
160
|
}
|
|
63
161
|
|
|
162
|
+
/* Validate before parsing: every field parsed below is a substring of `original`. */
|
|
163
|
+
if (!_endpoint_chars_are_valid(endpoint->original, status)) {
|
|
164
|
+
goto fail;
|
|
165
|
+
}
|
|
166
|
+
|
|
64
167
|
/* Parse optional protocol. */
|
|
65
168
|
pos = strstr(endpoint->original, "://");
|
|
66
169
|
if (pos) {
|
|
@@ -92,14 +195,15 @@ _mongocrypt_endpoint_t *_mongocrypt_endpoint_new(const char *endpoint_raw,
|
|
|
92
195
|
colon = strstr(pos, ":");
|
|
93
196
|
qmark = strstr(pos, "?");
|
|
94
197
|
slash = strstr(pos, "/");
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
198
|
+
/* The host ends at the first delimiter. A colon delimits a port only if it
|
|
199
|
+
* precedes the path and query: in "example.com/path:8080" the colon is part
|
|
200
|
+
* of the path, not a port separator. */
|
|
201
|
+
host_end = colon;
|
|
202
|
+
if (slash && (!host_end || slash < host_end)) {
|
|
98
203
|
host_end = slash;
|
|
99
|
-
}
|
|
204
|
+
}
|
|
205
|
+
if (qmark && (!host_end || qmark < host_end)) {
|
|
100
206
|
host_end = qmark;
|
|
101
|
-
} else {
|
|
102
|
-
host_end = NULL;
|
|
103
207
|
}
|
|
104
208
|
|
|
105
209
|
if (host_end) {
|
|
@@ -112,12 +216,16 @@ _mongocrypt_endpoint_t *_mongocrypt_endpoint_new(const char *endpoint_raw,
|
|
|
112
216
|
endpoint->host = bson_strdup(host_start);
|
|
113
217
|
}
|
|
114
218
|
|
|
219
|
+
if (!_host_chars_are_valid(endpoint->host, status)) {
|
|
220
|
+
goto fail;
|
|
221
|
+
}
|
|
222
|
+
|
|
115
223
|
/* Parse optional port */
|
|
116
|
-
if (colon) {
|
|
224
|
+
if (colon && colon == host_end) {
|
|
117
225
|
prev = colon + 1;
|
|
118
|
-
qmark = strstr(
|
|
119
|
-
slash = strstr(
|
|
120
|
-
if (slash) {
|
|
226
|
+
qmark = strstr(prev, "?");
|
|
227
|
+
slash = strstr(prev, "/");
|
|
228
|
+
if (slash && (!qmark || slash < qmark)) {
|
|
121
229
|
endpoint->port = bson_strndup(prev, (size_t)(slash - prev));
|
|
122
230
|
} else if (qmark) {
|
|
123
231
|
BSON_ASSERT(qmark >= prev);
|
|
@@ -125,6 +233,10 @@ _mongocrypt_endpoint_t *_mongocrypt_endpoint_new(const char *endpoint_raw,
|
|
|
125
233
|
} else {
|
|
126
234
|
endpoint->port = bson_strdup(prev);
|
|
127
235
|
}
|
|
236
|
+
|
|
237
|
+
if (!_port_chars_are_valid(endpoint->port, status)) {
|
|
238
|
+
goto fail;
|
|
239
|
+
}
|
|
128
240
|
}
|
|
129
241
|
|
|
130
242
|
/* Parse optional path */
|