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}