mongocrypt/
lib.rs

1use std::{borrow::Borrow, ffi::CStr, path::Path, ptr, sync::Mutex};
2
3use bson::Document;
4#[cfg(test)]
5use convert::str_bytes_len;
6use convert::{doc_binary, path_cstring};
7use ctx::CtxBuilder;
8use mongocrypt_sys as sys;
9
10mod binary;
11mod convert;
12pub mod ctx;
13pub mod error;
14mod hooks;
15mod native;
16#[cfg(test)]
17mod test;
18
19use error::{HasStatus, Result};
20pub use hooks::*;
21use native::OwnedPtr;
22use once_cell::sync::Lazy;
23
24#[cfg(not(any(feature = "bson-2", feature = "bson-3")))]
25compile_error!("One of the bson-2 and bson-3 features must be enabled.");
26
27#[cfg(all(feature = "bson-2", not(feature = "bson-3")))]
28use bson_2 as bson;
29
30#[cfg(feature = "bson-3")]
31use bson_3 as bson;
32
33/// Returns the version string for libmongocrypt.
34pub fn version() -> &'static str {
35    let c_version = unsafe { CStr::from_ptr(sys::mongocrypt_version(ptr::null_mut())) };
36    // Unwrap safety: the validity of this parse is enforced by unit test in mongocrypt-sys.
37    c_version.to_str().unwrap()
38}
39
40/// Returns true if libmongocrypt was built with native crypto support.
41pub fn is_crypto_available() -> bool {
42    unsafe { sys::mongocrypt_is_crypto_available() }
43}
44
45pub struct CryptBuilder {
46    inner: OwnedPtr<sys::mongocrypt_t>,
47    cleanup: Vec<Box<dyn std::any::Any>>,
48}
49
50impl HasStatus for CryptBuilder {
51    unsafe fn native_status(&self, status: *mut sys::mongocrypt_status_t) {
52        sys::mongocrypt_status(*self.inner.borrow(), status);
53    }
54}
55
56// This works around a possible race condition in mongocrypt [de]initialization; see RUST-1578 for details.
57static CRYPT_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
58
59unsafe extern "C" fn mongocrypt_destroy_locked(crypt: *mut sys::mongocrypt_t) {
60    let _guard = CRYPT_LOCK.lock().unwrap();
61    sys::mongocrypt_destroy(crypt);
62}
63
64impl CryptBuilder {
65    #[allow(clippy::new_without_default)]
66    pub fn new() -> Self {
67        Self {
68            inner: OwnedPtr::steal(unsafe { sys::mongocrypt_new() }, mongocrypt_destroy_locked),
69            cleanup: vec![],
70        }
71    }
72
73    /// Configure an AWS KMS provider.
74    ///
75    /// This has been superseded by the more flexible `kms_providers` method.
76    ///
77    /// * `aws_access_key_id` - The AWS access key ID used to generate KMS messages.
78    /// * `aws_secret_access_key` - The AWS secret access key used to generate KMS messages.
79    #[cfg(test)]
80    pub(crate) fn kms_provider_aws(
81        self,
82        aws_access_key_id: &str,
83        aws_secret_access_key: &str,
84    ) -> Result<Self> {
85        let (key_bytes, key_len) = str_bytes_len(aws_access_key_id)?;
86        let (secret_bytes, secret_len) = str_bytes_len(aws_secret_access_key)?;
87        unsafe {
88            if !sys::mongocrypt_setopt_kms_provider_aws(
89                *self.inner.borrow(),
90                key_bytes,
91                key_len,
92                secret_bytes,
93                secret_len,
94            ) {
95                return Err(self.status().as_error());
96            }
97        }
98        Ok(self)
99    }
100
101    /// Configure KMS providers with a BSON document.
102    ///
103    /// * `kms_providers` - A BSON document mapping the KMS provider names
104    /// to credentials. Set a KMS provider value to an empty document to supply
105    /// credentials on-demand with `Ctx::provide_kms_providers`.
106    pub fn kms_providers(self, kms_providers: &Document) -> Result<Self> {
107        let mut binary = doc_binary(kms_providers)?;
108        unsafe {
109            if !sys::mongocrypt_setopt_kms_providers(*self.inner.borrow(), *binary.native()) {
110                return Err(self.status().as_error());
111            }
112        }
113        Ok(self)
114    }
115
116    /// Set a local schema map for encryption.
117    ///
118    /// * `schema_map` - A BSON document representing the schema map supplied by
119    /// the user. The keys are collection namespaces and values are JSON schemas.
120    pub fn schema_map(self, schema_map: &Document) -> Result<Self> {
121        let mut binary = doc_binary(schema_map)?;
122        unsafe {
123            if !sys::mongocrypt_setopt_schema_map(*self.inner.borrow(), *binary.native()) {
124                return Err(self.status().as_error());
125            }
126        }
127        Ok(self)
128    }
129
130    /// Set a local EncryptedFieldConfigMap for encryption.
131    ///
132    /// * `efc_map` - A BSON document representing the EncryptedFieldConfigMap
133    /// supplied by the user. The keys are collection namespaces and values are
134    /// EncryptedFieldConfigMap documents.
135    pub fn encrypted_field_config_map(self, efc_map: &Document) -> Result<Self> {
136        let mut binary = doc_binary(efc_map)?;
137        unsafe {
138            if !sys::mongocrypt_setopt_encrypted_field_config_map(
139                *self.inner.borrow(),
140                *binary.native(),
141            ) {
142                return Err(self.status().as_error());
143            }
144        }
145        Ok(self)
146    }
147
148    /// Append an additional search directory to the search path for loading
149    /// the crypt_shared dynamic library.
150    ///
151    /// If the leading element of
152    /// the path is the literal string "$ORIGIN", that substring will be replaced
153    /// with the directory path containing the executable libmongocrypt module. If
154    /// the path string is literal "$SYSTEM", then libmongocrypt will defer to the
155    /// system's library resolution mechanism to find the crypt_shared library.
156    ///
157    /// If no crypt_shared dynamic library is found in any of the directories
158    /// specified by the search paths loaded here, `build` will still
159    /// succeed and continue to operate without crypt_shared.
160    ///
161    /// The search paths are searched in the order that they are appended. This
162    /// allows one to provide a precedence in how the library will be discovered. For
163    /// example, appending known directories before appending "$SYSTEM" will allow
164    /// one to supersede the system's installed library, but still fall-back to it if
165    /// the library wasn't found otherwise. If one does not ever append "$SYSTEM",
166    /// then the system's library-search mechanism will never be consulted.
167    ///
168    /// If an absolute path to the library is specified using
169    /// `set_crypt_shared_lib_path_override`, then paths
170    /// appended here will have no effect.
171    pub fn append_crypt_shared_lib_search_path(self, path: &Path) -> Result<Self> {
172        let tmp = path_cstring(path)?;
173        unsafe {
174            sys::mongocrypt_setopt_append_crypt_shared_lib_search_path(
175                *self.inner.borrow(),
176                tmp.as_ptr(),
177            );
178        }
179        Ok(self)
180    }
181
182    /// Set a single override path for loading the crypt_shared dynamic
183    /// library.
184    ///
185    /// If the leading element of the path is the literal string
186    /// `$ORIGIN`, that substring will be replaced with the directory path containing
187    /// the executable libmongocrypt module.
188    ///
189    /// This function will do no IO nor path validation. All validation will
190    /// occur during the call to `build`.
191    ///
192    /// If a crypt_shared library path override is specified here, then no
193    /// paths given to `append_crypt_shared_lib_search_path`
194    /// will be consulted when opening the crypt_shared library.
195    ///
196    /// If a path is provided via this API and `build` fails to
197    /// initialize a valid crypt_shared library instance for the path specified, then
198    /// the initialization will fail with an error.
199    pub fn set_crypt_shared_lib_path_override(self, path: &Path) -> Result<Self> {
200        let tmp = path_cstring(path)?;
201        unsafe {
202            sys::mongocrypt_setopt_set_crypt_shared_lib_path_override(
203                *self.inner.borrow(),
204                tmp.as_ptr(),
205            );
206        }
207        Ok(self)
208    }
209
210    /// Opt-into handling the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state.
211    ///
212    /// If set, before entering the MONGOCRYPT_CTX_NEED_KMS state,
213    /// contexts may enter the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state
214    /// and then wait for credentials to be supplied through
215    /// @ref mongocrypt_ctx_provide_kms_providers.
216    ///
217    /// A context will only enter MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS
218    /// if an empty document was set for a KMS provider in @ref
219    /// mongocrypt_setopt_kms_providers.
220    pub fn use_need_kms_credentials_state(self) -> Self {
221        unsafe {
222            sys::mongocrypt_setopt_use_need_kms_credentials_state(*self.inner.borrow());
223        }
224        self
225    }
226
227    /// Opt-into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
228    ///
229    /// A context enters the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state when
230    /// processing a `bulkWrite` command. The target database of the `bulkWrite` may differ from the command database
231    /// ("admin").
232    pub fn use_need_mongo_collinfo_with_db_state(self) -> Self {
233        unsafe {
234            sys::mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(*self.inner.borrow());
235        }
236        self
237    }
238
239    /// Enable support for multiple collection schemas. Required to support $lookup.
240    pub fn enable_multiple_collinfo(self) -> Result<Self> {
241        let ok = unsafe { sys::mongocrypt_setopt_enable_multiple_collinfo(*self.inner.borrow()) };
242        if !ok {
243            return Err(self.status().as_error());
244        }
245        Ok(self)
246    }
247
248    /// Opt-into skipping query analysis.
249    ///
250    /// If opted in:
251    /// * The crypt_shared library will not attempt to be loaded.
252    /// * A `Ctx` will never enter the `State::NeedMarkings` state.
253    pub fn bypass_query_analysis(self) -> Self {
254        unsafe {
255            sys::mongocrypt_setopt_bypass_query_analysis(*self.inner.borrow());
256        }
257        self
258    }
259
260    /// Opt-into use of Queryable Encryption Range V2 protocol.
261    pub fn use_range_v2(self) -> Result<Self> {
262        let ok = unsafe { sys::mongocrypt_setopt_use_range_v2(*self.inner.borrow()) };
263        if !ok {
264            return Err(self.status().as_error());
265        }
266        Ok(self)
267    }
268
269    /// Enable or disable KMS retry behavior.
270    pub fn retry_kms(self, enable: bool) -> Result<Self> {
271        unsafe {
272            let ok = sys::mongocrypt_setopt_retry_kms(*self.inner.borrow(), enable);
273            if !ok {
274                return Err(self.status().as_error());
275            }
276        }
277        Ok(self)
278    }
279
280    /// Set the expiration time for the data encryption key cache. Defaults to 60 seconds if not set.
281    pub fn key_cache_expiration(self, expiration_ms: u64) -> Result<Self> {
282        unsafe {
283            let ok = sys::mongocrypt_setopt_key_expiration(*self.inner.borrow(), expiration_ms);
284            if !ok {
285                return Err(self.status().as_error());
286            }
287        }
288        Ok(self)
289    }
290
291    pub fn build(mut self) -> Result<Crypt> {
292        let _guard = CRYPT_LOCK.lock().unwrap();
293
294        let ok = unsafe { sys::mongocrypt_init(*self.inner.borrow()) };
295        if !ok {
296            return Err(self.status().as_error());
297        }
298        Ok(Crypt {
299            inner: self.inner,
300            _cleanup: std::mem::take(&mut self.cleanup),
301        })
302    }
303}
304
305/// The top-level handle to libmongocrypt.
306///
307/// Create a `Crypt` handle to perform operations within libmongocrypt:
308/// encryption, decryption, registering log callbacks, etc.
309///
310/// Multiple `Crypt` handles may be created.
311pub struct Crypt {
312    inner: OwnedPtr<sys::mongocrypt_t>,
313    _cleanup: Vec<Box<dyn std::any::Any>>,
314}
315
316unsafe impl Send for Crypt {}
317unsafe impl Sync for Crypt {}
318
319impl Crypt {
320    pub fn builder() -> CryptBuilder {
321        CryptBuilder::new()
322    }
323
324    /// Obtain a version string of the loaded crypt_shared dynamic
325    /// library, if available.
326    ///
327    /// For a numeric value that can be compared against, use `shared_lib_version`.
328    pub fn shared_lib_version_string(&self) -> Option<String> {
329        let s_ptr = unsafe {
330            sys::mongocrypt_crypt_shared_lib_version_string(
331                *self.inner.borrow_const(),
332                ptr::null_mut(),
333            )
334        };
335        if s_ptr.is_null() {
336            return None;
337        }
338        let s = unsafe { CStr::from_ptr(s_ptr) };
339        Some(s.to_string_lossy().to_string())
340    }
341
342    /// Obtain a 64-bit constant encoding the version of the loaded
343    /// crypt_shared library, if available.
344    ///
345    /// The version is encoded as four 16-bit numbers, from high to low:
346    ///
347    /// - Major version
348    /// - Minor version
349    /// - Revision
350    /// - Reserved
351    ///
352    /// For example, version 6.2.1 would be encoded as: 0x0006'0002'0001'0000
353    pub fn shared_lib_version(&self) -> Option<u64> {
354        let out = unsafe { sys::mongocrypt_crypt_shared_lib_version(*self.inner.borrow_const()) };
355        if out == 0 {
356            return None;
357        }
358        Some(out)
359    }
360
361    pub fn ctx_builder(&self) -> CtxBuilder {
362        CtxBuilder::steal(unsafe { sys::mongocrypt_ctx_new(*self.inner.borrow()) })
363    }
364}