Coverage Report

Created: 2026-10-28 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libmongocrypt/src/mongocrypt-key.c
Line
Count
Source
1
/*
2
 * Copyright 2019-present MongoDB, Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *   http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include "mongocrypt-key-private.h"
18
#include "mongocrypt-private.h"
19
#include "mongocrypt-util-private.h" // mc_iter_document_as_bson
20
21
/* Check if two single entries are equal (i.e. ignore the 'next' pointer). */
22
0
static bool _one_key_alt_name_equal(_mongocrypt_key_alt_name_t *ptr_a, _mongocrypt_key_alt_name_t *ptr_b) {
23
0
    BSON_ASSERT_PARAM(ptr_a);
24
0
    BSON_ASSERT_PARAM(ptr_b);
25
0
    BSON_ASSERT(ptr_a->value.value_type == BSON_TYPE_UTF8);
26
0
    BSON_ASSERT(ptr_b->value.value_type == BSON_TYPE_UTF8);
27
0
    return 0 == strcmp(_mongocrypt_key_alt_name_get_string(ptr_a), _mongocrypt_key_alt_name_get_string(ptr_b));
28
0
}
29
30
0
static bool _find(_mongocrypt_key_alt_name_t *list, _mongocrypt_key_alt_name_t *entry) {
31
0
    BSON_ASSERT_PARAM(entry);
32
33
0
    for (; NULL != list; list = list->next) {
34
0
        if (_one_key_alt_name_equal(list, entry)) {
35
0
            return true;
36
0
        }
37
0
    }
38
0
    return false;
39
0
}
40
41
0
static uint32_t _list_len(_mongocrypt_key_alt_name_t *list) {
42
0
    uint32_t count = 0;
43
44
0
    while (NULL != list && count < UINT32_MAX) {
45
0
        count++;
46
0
        list = list->next;
47
0
    }
48
0
    return count;
49
0
}
50
51
0
static bool _check_unique(_mongocrypt_key_alt_name_t *list) {
52
0
    for (; NULL != list; list = list->next) {
53
        /* Check if we can find the current entry in the remaining. */
54
0
        if (_find(list->next, list)) {
55
0
            return false;
56
0
        }
57
0
    }
58
0
    return true;
59
0
}
60
61
4
static bool _parse_masterkey(bson_iter_t *iter, _mongocrypt_key_doc_t *out, mongocrypt_status_t *status) {
62
4
    bson_t kek_doc;
63
64
4
    BSON_ASSERT_PARAM(iter);
65
4
    BSON_ASSERT_PARAM(out);
66
67
4
    if (!BSON_ITER_HOLDS_DOCUMENT(iter)) {
68
4
        CLIENT_ERR("invalid 'masterKey', expected document");
69
4
        return false;
70
4
    }
71
72
0
    if (!mc_iter_document_as_bson(iter, &kek_doc, status)) {
73
0
        return false;
74
0
    }
75
76
0
    if (!_mongocrypt_kek_parse_owned(&kek_doc, &out->kek, status)) {
77
0
        return false;
78
0
    }
79
0
    return true;
80
0
}
81
82
bool _mongocrypt_key_alt_name_from_iter(const bson_iter_t *iter_in,
83
                                        _mongocrypt_key_alt_name_t **out,
84
6
                                        mongocrypt_status_t *status) {
85
6
    _mongocrypt_key_alt_name_t *key_alt_names = NULL, *tmp;
86
6
    bson_iter_t iter;
87
88
6
    BSON_ASSERT_PARAM(iter_in);
89
6
    BSON_ASSERT_PARAM(out);
90
91
6
    memcpy(&iter, iter_in, sizeof(iter));
92
6
    *out = NULL;
93
94
    /* A key parsed with no keyAltNames will have a zero'ed out bson value. Not
95
     * an error. */
96
6
    if (!BSON_ITER_HOLDS_ARRAY(&iter)) {
97
6
        CLIENT_ERR("malformed keyAltNames, expected array");
98
6
        return false;
99
6
    }
100
101
0
    if (!bson_iter_recurse(&iter, &iter)) {
102
0
        CLIENT_ERR("malformed keyAltNames, could not recurse into array");
103
0
        return false;
104
0
    }
105
106
0
    while (bson_iter_next(&iter)) {
107
0
        if (!BSON_ITER_HOLDS_UTF8(&iter)) {
108
0
            _mongocrypt_key_alt_name_destroy_all(key_alt_names);
109
0
            CLIENT_ERR("unexpected non-UTF8 keyAltName");
110
0
            return false;
111
0
        }
112
113
0
        tmp = _mongocrypt_key_alt_name_new(bson_iter_value(&iter));
114
0
        tmp->next = key_alt_names;
115
0
        key_alt_names = tmp;
116
0
    }
117
118
0
    if (!_check_unique(key_alt_names)) {
119
0
        _mongocrypt_key_alt_name_destroy_all(key_alt_names);
120
0
        CLIENT_ERR("unexpected duplicate keyAltNames");
121
0
        return false;
122
0
    }
123
124
0
    *out = key_alt_names;
125
0
    return true;
126
0
}
127
128
/* Takes ownership of all fields. */
129
928
bool _mongocrypt_key_parse_owned(const bson_t *bson, _mongocrypt_key_doc_t *out, mongocrypt_status_t *status) {
130
928
    bson_iter_t iter = {0};
131
928
    bool has_id = false, has_key_material = false, has_status = false, has_creation_date = false,
132
928
         has_update_date = false, has_master_key = false;
133
134
928
    BSON_ASSERT_PARAM(bson);
135
928
    BSON_ASSERT_PARAM(out);
136
137
928
    if (!bson_validate(bson, BSON_VALIDATE_NONE, NULL) || !bson_iter_init(&iter, bson)) {
138
166
        CLIENT_ERR("invalid BSON");
139
166
        return false;
140
166
    }
141
142
762
    bson_destroy(&out->bson);
143
762
    bson_copy_to(bson, &out->bson);
144
145
774
    while (bson_iter_next(&iter)) {
146
770
        const char *field;
147
148
770
        field = bson_iter_key(&iter);
149
770
        if (!field) {
150
0
            CLIENT_ERR("invalid BSON, could not retrieve field name");
151
0
            return false;
152
0
        }
153
770
        if (0 == strcmp("_id", field)) {
154
10
            has_id = true;
155
10
            if (!_mongocrypt_buffer_copy_from_uuid_iter(&out->id, &iter)) {
156
10
                CLIENT_ERR("invalid key, '_id' is not a UUID");
157
10
                return false;
158
10
            }
159
0
            continue;
160
10
        }
161
162
        /* keyAltNames (optional) */
163
760
        if (0 == strcmp("keyAltNames", field)) {
164
6
            if (!_mongocrypt_key_alt_name_from_iter(&iter, &out->key_alt_names, status)) {
165
6
                return false;
166
6
            }
167
0
            continue;
168
6
        }
169
170
754
        if (0 == strcmp("keyMaterial", field)) {
171
7
            has_key_material = true;
172
7
            if (!_mongocrypt_buffer_copy_from_binary_iter(&out->key_material, &iter)) {
173
7
                CLIENT_ERR("invalid 'keyMaterial', expected binary");
174
7
                return false;
175
7
            }
176
0
            if (out->key_material.subtype != BSON_SUBTYPE_BINARY) {
177
0
                CLIENT_ERR("invalid 'keyMaterial', expected subtype 0");
178
0
                return false;
179
0
            }
180
0
            continue;
181
0
        }
182
183
747
        if (0 == strcmp("masterKey", field)) {
184
4
            if (has_master_key) {
185
0
                CLIENT_ERR("duplicate field 'masterKey' in key document");
186
0
                return false;
187
0
            }
188
4
            has_master_key = true;
189
4
            if (!_parse_masterkey(&iter, out, status)) {
190
4
                return false;
191
4
            }
192
0
            continue;
193
4
        }
194
195
743
        if (0 == strcmp("version", field)) {
196
105
            if (!BSON_ITER_HOLDS_INT(&iter)) {
197
8
                CLIENT_ERR("invalid 'version', expect int");
198
8
                return false;
199
8
            }
200
97
            if (bson_iter_as_int64(&iter) != 0) {
201
93
                CLIENT_ERR("unsupported key document version, only supports version=0");
202
93
                return false;
203
93
            }
204
4
            continue;
205
97
        }
206
207
638
        if (0 == strcmp("status", field)) {
208
            /* Don't need status. Check that it's present and ignore it. */
209
7
            has_status = true;
210
7
            continue;
211
7
        }
212
213
631
        if (0 == strcmp("creationDate", field)) {
214
2
            has_creation_date = true;
215
216
2
            if (!BSON_ITER_HOLDS_DATE_TIME(&iter)) {
217
2
                CLIENT_ERR("invalid 'creationDate', expect datetime");
218
2
                return false;
219
2
            }
220
221
0
            out->creation_date = bson_iter_date_time(&iter);
222
0
            continue;
223
2
        }
224
225
629
        if (0 == strcmp("updateDate", field)) {
226
5
            has_update_date = true;
227
228
5
            if (!BSON_ITER_HOLDS_DATE_TIME(&iter)) {
229
4
                CLIENT_ERR("invalid 'updateDate', expect datetime");
230
4
                return false;
231
4
            }
232
233
1
            out->update_date = bson_iter_date_time(&iter);
234
1
            continue;
235
5
        }
236
237
624
        CLIENT_ERR("unrecognized field '%s'", field);
238
624
        return false;
239
629
    }
240
241
    /* Check that required fields were set. */
242
4
    if (!has_id) {
243
4
        CLIENT_ERR("invalid key, no '_id'");
244
4
        return false;
245
4
    }
246
247
0
    if (!has_master_key) {
248
0
        CLIENT_ERR("invalid key, no 'masterKey'");
249
0
        return false;
250
0
    }
251
252
0
    if (!has_key_material) {
253
0
        CLIENT_ERR("invalid key, no 'keyMaterial'");
254
0
        return false;
255
0
    }
256
257
0
    if (!has_status) {
258
0
        CLIENT_ERR("invalid key, no 'status'");
259
0
        return false;
260
0
    }
261
262
0
    if (!has_creation_date) {
263
0
        CLIENT_ERR("invalid key, no 'creationDate'");
264
0
        return false;
265
0
    }
266
267
0
    if (!has_update_date) {
268
0
        CLIENT_ERR("invalid key, no 'updateDate'");
269
0
        return false;
270
0
    }
271
272
0
    return true;
273
0
}
274
275
1.21k
_mongocrypt_key_doc_t *_mongocrypt_key_new(void) {
276
1.21k
    _mongocrypt_key_doc_t *key_doc;
277
278
1.21k
    key_doc = BSON_ALIGNED_ALLOC(_mongocrypt_key_doc_t);
279
    // Use two sets of braces to avoid erroneous missing-braces warning in GCC. Refer:
280
    // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119
281
1.21k
    *key_doc = (_mongocrypt_key_doc_t){{0}};
282
1.21k
    bson_init(&key_doc->bson);
283
284
1.21k
    return key_doc;
285
1.21k
}
286
287
1.21k
void _mongocrypt_key_destroy(_mongocrypt_key_doc_t *key) {
288
1.21k
    if (!key) {
289
0
        return;
290
0
    }
291
292
1.21k
    _mongocrypt_buffer_cleanup(&key->id);
293
1.21k
    _mongocrypt_key_alt_name_destroy_all(key->key_alt_names);
294
1.21k
    _mongocrypt_buffer_cleanup(&key->key_material);
295
1.21k
    _mongocrypt_kek_cleanup(&key->kek);
296
297
1.21k
    bson_destroy(&key->bson);
298
1.21k
    bson_free(key);
299
1.21k
}
300
301
0
void _mongocrypt_key_doc_copy_to(_mongocrypt_key_doc_t *src, _mongocrypt_key_doc_t *dst) {
302
0
    BSON_ASSERT_PARAM(src);
303
0
    BSON_ASSERT_PARAM(dst);
304
305
0
    _mongocrypt_buffer_copy_to(&src->id, &dst->id);
306
0
    _mongocrypt_buffer_copy_to(&src->key_material, &dst->key_material);
307
0
    dst->key_alt_names = _mongocrypt_key_alt_name_copy_all(src->key_alt_names);
308
0
    bson_destroy(&dst->bson);
309
0
    bson_copy_to(&src->bson, &dst->bson);
310
0
    _mongocrypt_kek_copy_to(&src->kek, &dst->kek);
311
0
    dst->creation_date = src->creation_date;
312
0
    dst->update_date = src->update_date;
313
0
}
314
315
556
_mongocrypt_key_alt_name_t *_mongocrypt_key_alt_name_copy_all(_mongocrypt_key_alt_name_t *ptr) {
316
556
    _mongocrypt_key_alt_name_t *ptr_copy = NULL, *head = NULL;
317
318
556
    while (ptr) {
319
0
        _mongocrypt_key_alt_name_t *copied;
320
0
        copied = bson_malloc0(sizeof(*copied));
321
0
        BSON_ASSERT(copied);
322
323
0
        bson_value_copy(&ptr->value, &copied->value);
324
325
0
        if (!ptr_copy) {
326
0
            ptr_copy = copied;
327
0
            head = ptr_copy;
328
0
        } else {
329
0
            ptr_copy->next = copied;
330
0
            ptr_copy = ptr_copy->next;
331
0
        }
332
0
        ptr = ptr->next;
333
0
    }
334
556
    return head;
335
556
}
336
337
12.6k
void _mongocrypt_key_alt_name_destroy_all(_mongocrypt_key_alt_name_t *ptr) {
338
12.6k
    _mongocrypt_key_alt_name_t *next;
339
12.6k
    while (ptr) {
340
0
        next = ptr->next;
341
0
        bson_value_destroy(&ptr->value);
342
0
        bson_free(ptr);
343
0
        ptr = next;
344
0
    }
345
12.6k
}
346
347
0
bool _mongocrypt_key_alt_name_intersects(_mongocrypt_key_alt_name_t *ptr_a, _mongocrypt_key_alt_name_t *ptr_b) {
348
0
    _mongocrypt_key_alt_name_t *orig_ptr_b = ptr_b;
349
350
0
    if (!ptr_a || !ptr_b) {
351
0
        return false;
352
0
    }
353
354
0
    for (; ptr_a; ptr_a = ptr_a->next) {
355
0
        for (ptr_b = orig_ptr_b; ptr_b; ptr_b = ptr_b->next) {
356
0
            if (_one_key_alt_name_equal(ptr_a, ptr_b)) {
357
0
                return true;
358
0
            }
359
0
        }
360
0
    }
361
0
    return false;
362
0
}
363
364
0
_mongocrypt_key_alt_name_t *_mongocrypt_key_alt_name_create(const char *name, ...) {
365
0
    va_list args;
366
0
    const char *arg_ptr;
367
0
    _mongocrypt_key_alt_name_t *head, *prev;
368
369
0
    head = NULL;
370
0
    prev = NULL;
371
0
    va_start(args, name);
372
0
    arg_ptr = name;
373
0
    while (arg_ptr) {
374
0
        _mongocrypt_key_alt_name_t *curr;
375
376
0
        curr = bson_malloc0(sizeof(*curr));
377
0
        BSON_ASSERT(curr);
378
379
0
        curr->value.value_type = BSON_TYPE_UTF8;
380
0
        curr->value.value.v_utf8.str = bson_strdup(arg_ptr);
381
0
        curr->value.value.v_utf8.len = (uint32_t)strlen(arg_ptr);
382
0
        if (!prev) {
383
0
            head = curr;
384
0
        } else {
385
0
            prev->next = curr;
386
0
        }
387
388
0
        arg_ptr = va_arg(args, const char *);
389
0
        prev = curr;
390
0
    }
391
0
    va_end(args);
392
393
0
    return head;
394
0
}
395
396
0
_mongocrypt_key_alt_name_t *_mongocrypt_key_alt_name_new(const bson_value_t *value) {
397
0
    BSON_ASSERT_PARAM(value);
398
399
0
    _mongocrypt_key_alt_name_t *name = BSON_ALIGNED_ALLOC(_mongocrypt_key_alt_name_t);
400
0
    *name = (_mongocrypt_key_alt_name_t){0};
401
0
    BSON_ASSERT(name);
402
403
0
    bson_value_copy(value, &name->value);
404
0
    return name;
405
0
}
406
407
bool _mongocrypt_key_alt_name_unique_list_equal(_mongocrypt_key_alt_name_t *list_a,
408
0
                                                _mongocrypt_key_alt_name_t *list_b) {
409
0
    _mongocrypt_key_alt_name_t *ptr;
410
411
0
    BSON_ASSERT(_check_unique(list_a));
412
0
    BSON_ASSERT(_check_unique(list_b));
413
0
    if (_list_len(list_a) != _list_len(list_b)) {
414
0
        return false;
415
0
    }
416
0
    for (ptr = list_a; NULL != ptr; ptr = ptr->next) {
417
0
        if (!_find(list_b, ptr)) {
418
0
            return false;
419
0
        }
420
0
    }
421
0
    return true;
422
0
}
423
424
0
const char *_mongocrypt_key_alt_name_get_string(_mongocrypt_key_alt_name_t *key_alt_name) {
425
0
    BSON_ASSERT_PARAM(key_alt_name);
426
427
0
    return key_alt_name->value.value.v_utf8.str;
428
0
}