33
33
#include <openssl/hmac.h>
34
34
#include <openssl/rand.h>
35
35
36
- #if OPENSSL_VERSION_NUMBER < 0x10100000L || (defined(LIBRESSL_VERSION_NUMBER ) && LIBRESSL_VERSION_NUMBER < 0x20700000L )
37
-
38
- static HMAC_CTX * HMAC_CTX_new (void ) {
39
- return bson_malloc0 (sizeof (HMAC_CTX ));
40
- }
41
-
42
- static void HMAC_CTX_free (HMAC_CTX * ctx ) {
43
- HMAC_CTX_cleanup (ctx );
44
- bson_free (ctx );
45
- }
36
+ #if OPENSSL_VERSION_NUMBER >= 0x30000000L
37
+ #include <openssl/core_names.h>
38
+ #include <openssl/params.h>
46
39
#endif
47
40
48
41
bool _native_crypto_initialized = false;
49
42
50
- void _native_crypto_init (void ) {
51
- _native_crypto_initialized = true;
52
- }
53
-
54
- /* _encrypt_with_cipher encrypts @in with the OpenSSL cipher specified by
55
- * @cipher.
43
+ /* _encrypt_with_cipher encrypts @in with the specified OpenSSL cipher.
44
+ * @cipher is a usable EVP_CIPHER, or NULL if early initialization failed.
45
+ * @cipher_description is a human-readable description used when reporting deferred errors from initialization, required
46
+ * if @cipher might be NULL.
56
47
* @key is the input key. @iv is the input IV.
57
48
* @out is the output ciphertext. @out must be allocated by the caller with
58
49
* enough room for the ciphertext.
59
50
* @bytes_written is the number of bytes that were written to @out.
60
51
* Returns false and sets @status on error. @status is required. */
61
- static bool _encrypt_with_cipher (const EVP_CIPHER * cipher , aes_256_args_t args ) {
62
- EVP_CIPHER_CTX * ctx ;
63
- bool ret = false;
64
- int intermediate_bytes_written = 0 ;
65
- mongocrypt_status_t * status = args .status ;
66
-
67
- ctx = EVP_CIPHER_CTX_new ();
68
-
52
+ static bool _encrypt_with_cipher (const EVP_CIPHER * cipher , const char * cipher_description , aes_256_args_t args ) {
69
53
BSON_ASSERT (args .key );
70
54
BSON_ASSERT (args .in );
71
55
BSON_ASSERT (args .out );
72
- BSON_ASSERT (ctx );
73
- BSON_ASSERT (cipher );
56
+ BSON_ASSERT (args .in -> len <= INT_MAX );
57
+
58
+ mongocrypt_status_t * status = args .status ;
59
+ if (!cipher ) {
60
+ BSON_ASSERT (cipher_description );
61
+ CLIENT_ERR ("failed to initialize cipher %s" , cipher_description );
62
+ return false;
63
+ }
64
+
74
65
BSON_ASSERT (NULL == args .iv || (uint32_t )EVP_CIPHER_iv_length (cipher ) == args .iv -> len );
75
66
BSON_ASSERT ((uint32_t )EVP_CIPHER_key_length (cipher ) == args .key -> len );
76
- BSON_ASSERT (args .in -> len <= INT_MAX );
67
+
68
+ bool ret = false;
69
+ EVP_CIPHER_CTX * ctx = EVP_CIPHER_CTX_new ();
70
+ BSON_ASSERT (ctx );
77
71
78
72
if (!EVP_EncryptInit_ex (ctx , cipher , NULL /* engine */ , args .key -> data , NULL == args .iv ? NULL : args .iv -> data )) {
79
73
CLIENT_ERR ("error in EVP_EncryptInit_ex: %s" , ERR_error_string (ERR_get_error (), NULL ));
@@ -84,6 +78,8 @@ static bool _encrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args)
84
78
EVP_CIPHER_CTX_set_padding (ctx , 0 );
85
79
86
80
* args .bytes_written = 0 ;
81
+
82
+ int intermediate_bytes_written = 0 ;
87
83
if (!EVP_EncryptUpdate (ctx , args .out -> data , & intermediate_bytes_written , args .in -> data , (int )args .in -> len )) {
88
84
CLIENT_ERR ("error in EVP_EncryptUpdate: %s" , ERR_error_string (ERR_get_error (), NULL ));
89
85
goto done ;
@@ -107,30 +103,35 @@ static bool _encrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args)
107
103
return ret ;
108
104
}
109
105
110
- /* _decrypt_with_cipher decrypts @in with the OpenSSL cipher specified by
111
- * @cipher.
106
+ /* _decrypt_with_cipher decrypts @in with the specified OpenSSL cipher.
107
+ * @cipher is a usable EVP_CIPHER, or NULL if early initialization failed.
108
+ * @cipher_description is a human-readable description used when reporting deferred errors from initialization, required
109
+ * if @cipher might be NULL.
112
110
* @key is the input key. @iv is the input IV.
113
111
* @out is the output plaintext. @out must be allocated by the caller with
114
112
* enough room for the plaintext.
115
113
* @bytes_written is the number of bytes that were written to @out.
116
114
* Returns false and sets @status on error. @status is required. */
117
- static bool _decrypt_with_cipher (const EVP_CIPHER * cipher , aes_256_args_t args ) {
118
- EVP_CIPHER_CTX * ctx ;
119
- bool ret = false;
120
- int intermediate_bytes_written = 0 ;
121
- mongocrypt_status_t * status = args .status ;
122
-
123
- ctx = EVP_CIPHER_CTX_new ();
124
- BSON_ASSERT (ctx );
125
-
126
- BSON_ASSERT_PARAM (cipher );
115
+ static bool _decrypt_with_cipher (const EVP_CIPHER * cipher , const char * cipher_description , aes_256_args_t args ) {
127
116
BSON_ASSERT (args .iv );
128
117
BSON_ASSERT (args .key );
129
118
BSON_ASSERT (args .in );
130
119
BSON_ASSERT (args .out );
120
+ BSON_ASSERT (args .in -> len <= INT_MAX );
121
+
122
+ mongocrypt_status_t * status = args .status ;
123
+ if (!cipher ) {
124
+ BSON_ASSERT_PARAM (cipher_description );
125
+ CLIENT_ERR ("failed to initialize cipher %s" , cipher_description );
126
+ return false;
127
+ }
128
+
131
129
BSON_ASSERT ((uint32_t )EVP_CIPHER_iv_length (cipher ) == args .iv -> len );
132
130
BSON_ASSERT ((uint32_t )EVP_CIPHER_key_length (cipher ) == args .key -> len );
133
- BSON_ASSERT (args .in -> len <= INT_MAX );
131
+
132
+ bool ret = false;
133
+ EVP_CIPHER_CTX * ctx = EVP_CIPHER_CTX_new ();
134
+ BSON_ASSERT (ctx );
134
135
135
136
if (!EVP_DecryptInit_ex (ctx , cipher , NULL /* engine */ , args .key -> data , args .iv -> data )) {
136
137
CLIENT_ERR ("error in EVP_DecryptInit_ex: %s" , ERR_error_string (ERR_get_error (), NULL ));
@@ -142,6 +143,7 @@ static bool _decrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args)
142
143
143
144
* args .bytes_written = 0 ;
144
145
146
+ int intermediate_bytes_written = 0 ;
145
147
if (!EVP_DecryptUpdate (ctx , args .out -> data , & intermediate_bytes_written , args .in -> data , (int )args .in -> len )) {
146
148
CLIENT_ERR ("error in EVP_DecryptUpdate: %s" , ERR_error_string (ERR_get_error (), NULL ));
147
149
goto done ;
@@ -165,18 +167,186 @@ static bool _decrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args)
165
167
return ret ;
166
168
}
167
169
170
+ bool _native_crypto_random (_mongocrypt_buffer_t * out , uint32_t count , mongocrypt_status_t * status ) {
171
+ BSON_ASSERT_PARAM (out );
172
+ BSON_ASSERT (count <= INT_MAX );
173
+
174
+ int ret = RAND_bytes (out -> data , (int )count );
175
+ /* From man page: "RAND_bytes() and RAND_priv_bytes() return 1 on success, -1
176
+ * if not supported by the current RAND method, or 0 on other failure. The
177
+ * error code can be obtained by ERR_get_error(3)" */
178
+ if (ret == -1 ) {
179
+ CLIENT_ERR ("secure random IV not supported: %s" , ERR_error_string (ERR_get_error (), NULL ));
180
+ return false;
181
+ } else if (ret == 0 ) {
182
+ CLIENT_ERR ("failed to generate random IV: %s" , ERR_error_string (ERR_get_error (), NULL ));
183
+ return false;
184
+ }
185
+ return true;
186
+ }
187
+
188
+ #if OPENSSL_VERSION_NUMBER >= 0x30000000L
189
+ // Newest libcrypto support: requires EVP_MAC_CTX_dup and EVP_CIPHER_fetch added in OpenSSL 3.0.0
190
+
191
+ static struct {
192
+ EVP_MAC_CTX * hmac_sha2_256 ;
193
+ EVP_MAC_CTX * hmac_sha2_512 ;
194
+ EVP_CIPHER * aes_256_cbc ;
195
+ EVP_CIPHER * aes_256_ctr ;
196
+ EVP_CIPHER * aes_256_ecb ; // For testing only
197
+ } _mongocrypt_libcrypto ;
198
+
199
+ EVP_MAC_CTX * _build_hmac_ctx_prototype (const char * digest_name ) {
200
+ EVP_MAC * hmac = EVP_MAC_fetch (NULL , OSSL_MAC_NAME_HMAC , NULL );
201
+ if (!hmac ) {
202
+ return NULL ;
203
+ }
204
+
205
+ EVP_MAC_CTX * ctx = EVP_MAC_CTX_new (hmac );
206
+ EVP_MAC_free (hmac );
207
+ if (!ctx ) {
208
+ return NULL ;
209
+ }
210
+
211
+ OSSL_PARAM params [] = {OSSL_PARAM_construct_utf8_string (OSSL_MAC_PARAM_DIGEST , (char * )digest_name , 0 ),
212
+ OSSL_PARAM_construct_end ()};
213
+
214
+ if (EVP_MAC_CTX_set_params (ctx , params )) {
215
+ return ctx ;
216
+ } else {
217
+ EVP_MAC_CTX_free (ctx );
218
+ return NULL ;
219
+ }
220
+ }
221
+
222
+ /* _hmac_with_ctx_prototype computes an HMAC of @in using an OpenSSL context duplicated from @ctx_prototype.
223
+ * @ctx_description is a human-readable description used when reporting deferred errors from initialization, required
224
+ * if @ctx_prototype might be NULL.
225
+ * @key is the input key.
226
+ * @out is the output. @out must be allocated by the caller with
227
+ * the exact length for the output. E.g. for HMAC 256, @out->len must be 32.
228
+ * Returns false and sets @status on error. @status is required. */
229
+ static bool _hmac_with_ctx_prototype (const EVP_MAC_CTX * ctx_prototype ,
230
+ const char * ctx_description ,
231
+ const _mongocrypt_buffer_t * key ,
232
+ const _mongocrypt_buffer_t * in ,
233
+ _mongocrypt_buffer_t * out ,
234
+ mongocrypt_status_t * status ) {
235
+ BSON_ASSERT_PARAM (key );
236
+ BSON_ASSERT_PARAM (in );
237
+ BSON_ASSERT_PARAM (out );
238
+ BSON_ASSERT (key -> len <= INT_MAX );
239
+
240
+ if (!ctx_prototype ) {
241
+ BSON_ASSERT_PARAM (ctx_description );
242
+ CLIENT_ERR ("failed to initialize algorithm %s" , ctx_description );
243
+ return false;
244
+ }
245
+
246
+ EVP_MAC_CTX * ctx = EVP_MAC_CTX_dup (ctx_prototype );
247
+ if (ctx ) {
248
+ bool ok = EVP_MAC_init (ctx , key -> data , key -> len , NULL ) && EVP_MAC_update (ctx , in -> data , in -> len )
249
+ && EVP_MAC_final (ctx , out -> data , NULL , out -> len );
250
+ EVP_MAC_CTX_free (ctx );
251
+ if (ok ) {
252
+ return true;
253
+ }
254
+ }
255
+ CLIENT_ERR ("HMAC error: %s" , ERR_error_string (ERR_get_error (), NULL ));
256
+ return false;
257
+ }
258
+
259
+ void _native_crypto_init (void ) {
260
+ // Early lookup of digest and cipher algorithms avoids both the lookup overhead itself and the overhead of lock
261
+ // contention in the default OSSL_LIB_CTX.
262
+ //
263
+ // Failures now will store NULL, reporting a client error later.
264
+ //
265
+ // On HMAC fetching:
266
+ //
267
+ // Note that libcrypto sets an additional trap for us regarding MAC algorithms. An early fetch of the HMAC itself
268
+ // won't actually pre-fetch the subalgorithm. The name of the inner digest gets stored as a string, and re-fetched
269
+ // when setting up MAC context parameters. To fetch both the outer and inner algorithms ahead of time, we construct
270
+ // a prototype EVP_MAC_CTX that can be duplicated before each use.
271
+ //
272
+ // On thread safety:
273
+ //
274
+ // This creates objects that are intended to be immutable shared data after initialization. To understand whether
275
+ // this is safe we could consult the OpenSSL documentation but currently it's lacking in specifics about the
276
+ // individual API functions and types. It offers some general guidelines: "Objects are thread-safe as long as the
277
+ // API's being invoked don't modify the object; in this case the parameter is usually marked in the API as C<const>.
278
+ // Not all parameters are marked this way." By inspection, we can see that pre-fetched ciphers and MACs are designed
279
+ // with atomic reference counting support and appear to be intended for safe immutable use. Contexts are normally
280
+ // not safe to share, but these used only as a source for EVP_MAC_CTX_dup() can be treated as immutable.
281
+ //
282
+ // TODO: This could be refactored to live in mongocrypt_t rather than in global data. Currently there's no way to
283
+ // avoid leaking this set of one-time allocations.
284
+ //
285
+ // TODO: Higher performance yet could be achieved by re-using thread local EVP_MAC_CTX, but this requires careful
286
+ // lifecycle management to avoid leaking data. Alternatively, the libmongocrypt API could be modified to include
287
+ // some non-shared but long-lived context suitable for keeping these crypto objects. Alternatively still, it may be
288
+ // worth using a self contained SHA2 HMAC with favorable performance and portability characteristics.
289
+
290
+ _mongocrypt_libcrypto .aes_256_cbc = EVP_CIPHER_fetch (NULL , "AES-256-CBC" , NULL );
291
+ _mongocrypt_libcrypto .aes_256_ctr = EVP_CIPHER_fetch (NULL , "AES-256-CTR" , NULL );
292
+ _mongocrypt_libcrypto .aes_256_ecb = EVP_CIPHER_fetch (NULL , "AES-256-ECB" , NULL );
293
+ _mongocrypt_libcrypto .hmac_sha2_256 = _build_hmac_ctx_prototype (OSSL_DIGEST_NAME_SHA2_256 );
294
+ _mongocrypt_libcrypto .hmac_sha2_512 = _build_hmac_ctx_prototype (OSSL_DIGEST_NAME_SHA2_512 );
295
+ _native_crypto_initialized = true;
296
+ }
297
+
168
298
bool _native_crypto_aes_256_cbc_encrypt (aes_256_args_t args ) {
169
- return _encrypt_with_cipher (EVP_aes_256_cbc () , args );
299
+ return _encrypt_with_cipher (_mongocrypt_libcrypto . aes_256_cbc , "AES-256-CBC" , args );
170
300
}
171
301
172
302
bool _native_crypto_aes_256_cbc_decrypt (aes_256_args_t args ) {
173
- return _decrypt_with_cipher (EVP_aes_256_cbc () , args );
303
+ return _decrypt_with_cipher (_mongocrypt_libcrypto . aes_256_cbc , "AES-256-CBC" , args );
174
304
}
175
305
176
306
bool _native_crypto_aes_256_ecb_encrypt (aes_256_args_t args ); // -Wmissing-prototypes: for testing only.
177
307
178
308
bool _native_crypto_aes_256_ecb_encrypt (aes_256_args_t args ) {
179
- return _encrypt_with_cipher (EVP_aes_256_ecb (), args );
309
+ return _encrypt_with_cipher (_mongocrypt_libcrypto .aes_256_ecb , "AES-256-ECB" , args );
310
+ }
311
+
312
+ bool _native_crypto_aes_256_ctr_encrypt (aes_256_args_t args ) {
313
+ return _encrypt_with_cipher (_mongocrypt_libcrypto .aes_256_ctr , "AES-256-CTR" , args );
314
+ }
315
+
316
+ bool _native_crypto_aes_256_ctr_decrypt (aes_256_args_t args ) {
317
+ return _decrypt_with_cipher (_mongocrypt_libcrypto .aes_256_ctr , "AES-256-CTR" , args );
318
+ }
319
+
320
+ bool _native_crypto_hmac_sha_256 (const _mongocrypt_buffer_t * key ,
321
+ const _mongocrypt_buffer_t * in ,
322
+ _mongocrypt_buffer_t * out ,
323
+ mongocrypt_status_t * status ) {
324
+ return _hmac_with_ctx_prototype (_mongocrypt_libcrypto .hmac_sha2_256 , "HMAC-SHA2-256" , key , in , out , status );
325
+ }
326
+
327
+ bool _native_crypto_hmac_sha_512 (const _mongocrypt_buffer_t * key ,
328
+ const _mongocrypt_buffer_t * in ,
329
+ _mongocrypt_buffer_t * out ,
330
+ mongocrypt_status_t * status ) {
331
+ return _hmac_with_ctx_prototype (_mongocrypt_libcrypto .hmac_sha2_512 , "HMAC-SHA2-512" , key , in , out , status );
332
+ }
333
+
334
+ #else /* OPENSSL_VERSION_NUMBER < 0x30000000L */
335
+ // Support for previous libcrypto versions, without early fetch optimization.
336
+
337
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L || (defined(LIBRESSL_VERSION_NUMBER ) && LIBRESSL_VERSION_NUMBER < 0x20700000L )
338
+ static HMAC_CTX * HMAC_CTX_new (void ) {
339
+ return bson_malloc0 (sizeof (HMAC_CTX ));
340
+ }
341
+
342
+ static void HMAC_CTX_free (HMAC_CTX * ctx ) {
343
+ HMAC_CTX_cleanup (ctx );
344
+ bson_free (ctx );
345
+ }
346
+ #endif
347
+
348
+ void _native_crypto_init (void ) {
349
+ _native_crypto_initialized = true;
180
350
}
181
351
182
352
/* _hmac_with_hash computes an HMAC of @in with the OpenSSL hash specified by
@@ -235,37 +405,26 @@ static bool _hmac_with_hash(const EVP_MD *hash,
235
405
#endif
236
406
}
237
407
238
- bool _native_crypto_hmac_sha_512 (const _mongocrypt_buffer_t * key ,
239
- const _mongocrypt_buffer_t * in ,
240
- _mongocrypt_buffer_t * out ,
241
- mongocrypt_status_t * status ) {
242
- return _hmac_with_hash (EVP_sha512 (), key , in , out , status );
408
+ bool _native_crypto_aes_256_cbc_encrypt (aes_256_args_t args ) {
409
+ return _encrypt_with_cipher (EVP_aes_256_cbc (), NULL , args );
243
410
}
244
411
245
- bool _native_crypto_random ( _mongocrypt_buffer_t * out , uint32_t count , mongocrypt_status_t * status ) {
246
- BSON_ASSERT_PARAM ( out );
247
- BSON_ASSERT ( count <= INT_MAX );
412
+ bool _native_crypto_aes_256_cbc_decrypt ( aes_256_args_t args ) {
413
+ return _decrypt_with_cipher ( EVP_aes_256_cbc (), NULL , args );
414
+ }
248
415
249
- int ret = RAND_bytes (out -> data , (int )count );
250
- /* From man page: "RAND_bytes() and RAND_priv_bytes() return 1 on success, -1
251
- * if not supported by the current RAND method, or 0 on other failure. The
252
- * error code can be obtained by ERR_get_error(3)" */
253
- if (ret == -1 ) {
254
- CLIENT_ERR ("secure random IV not supported: %s" , ERR_error_string (ERR_get_error (), NULL ));
255
- return false;
256
- } else if (ret == 0 ) {
257
- CLIENT_ERR ("failed to generate random IV: %s" , ERR_error_string (ERR_get_error (), NULL ));
258
- return false;
259
- }
260
- return true;
416
+ bool _native_crypto_aes_256_ecb_encrypt (aes_256_args_t args ); // -Wmissing-prototypes: for testing only.
417
+
418
+ bool _native_crypto_aes_256_ecb_encrypt (aes_256_args_t args ) {
419
+ return _encrypt_with_cipher (EVP_aes_256_ecb (), NULL , args );
261
420
}
262
421
263
422
bool _native_crypto_aes_256_ctr_encrypt (aes_256_args_t args ) {
264
- return _encrypt_with_cipher (EVP_aes_256_ctr (), args );
423
+ return _encrypt_with_cipher (EVP_aes_256_ctr (), NULL , args );
265
424
}
266
425
267
426
bool _native_crypto_aes_256_ctr_decrypt (aes_256_args_t args ) {
268
- return _decrypt_with_cipher (EVP_aes_256_ctr (), args );
427
+ return _decrypt_with_cipher (EVP_aes_256_ctr (), NULL , args );
269
428
}
270
429
271
430
bool _native_crypto_hmac_sha_256 (const _mongocrypt_buffer_t * key ,
@@ -275,4 +434,13 @@ bool _native_crypto_hmac_sha_256(const _mongocrypt_buffer_t *key,
275
434
return _hmac_with_hash (EVP_sha256 (), key , in , out , status );
276
435
}
277
436
437
+ bool _native_crypto_hmac_sha_512 (const _mongocrypt_buffer_t * key ,
438
+ const _mongocrypt_buffer_t * in ,
439
+ _mongocrypt_buffer_t * out ,
440
+ mongocrypt_status_t * status ) {
441
+ return _hmac_with_hash (EVP_sha512 (), key , in , out , status );
442
+ }
443
+
444
+ #endif /* OPENSSL_VERSION_NUMBER */
445
+
278
446
#endif /* MONGOCRYPT_ENABLE_CRYPTO_LIBCRYPTO */
0 commit comments