use mongocrypt::{
ctx::{Algorithm, Ctx, CtxBuilder, KmsProvider},
Crypt,
};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use typed_builder::TypedBuilder;
use crate::{
bson::{
doc,
spec::BinarySubtype,
Binary,
Bson,
Document,
RawBinaryRef,
RawBson,
RawDocumentBuf,
},
client::options::TlsOptions,
coll::options::CollectionOptions,
db::options::CreateCollectionOptions,
error::{Error, Result},
options::{ReadConcern, WriteConcern},
results::DeleteResult,
Client,
Collection,
Cursor,
Database,
Namespace,
};
use super::{options::KmsProviders, state_machine::CryptExecutor};
pub struct ClientEncryption {
crypt: Crypt,
exec: CryptExecutor,
key_vault: Collection<RawDocumentBuf>,
}
impl ClientEncryption {
pub fn new(
key_vault_client: Client,
key_vault_namespace: Namespace,
kms_providers: impl IntoIterator<Item = (KmsProvider, bson::Document, Option<TlsOptions>)>,
) -> Result<Self> {
let kms_providers = KmsProviders::new(kms_providers)?;
let crypt = Crypt::builder()
.kms_providers(&kms_providers.credentials_doc()?)?
.use_need_kms_credentials_state()
.build()?;
let exec = CryptExecutor::new_explicit(
key_vault_client.weak(),
key_vault_namespace.clone(),
kms_providers,
)?;
let key_vault = key_vault_client
.database(&key_vault_namespace.db)
.collection_with_options(
&key_vault_namespace.coll,
CollectionOptions::builder()
.write_concern(WriteConcern::MAJORITY)
.read_concern(ReadConcern::MAJORITY)
.build(),
);
Ok(ClientEncryption {
crypt,
exec,
key_vault,
})
}
#[must_use]
pub fn create_data_key(&self, master_key: MasterKey) -> CreateDataKeyAction {
CreateDataKeyAction {
client_enc: self,
opts: DataKeyOptions {
master_key,
key_alt_names: None,
key_material: None,
},
}
}
pub(crate) async fn create_data_key_final(
&self,
kms_provider: &KmsProvider,
opts: impl Into<Option<DataKeyOptions>>,
) -> Result<Binary> {
let ctx = self.create_data_key_ctx(kms_provider, opts.into().as_ref())?;
let data_key = self.exec.run_ctx(ctx, None).await?;
self.key_vault.insert_one(&data_key, None).await?;
let bin_ref = data_key
.get_binary("_id")
.map_err(|e| Error::internal(format!("invalid data key id: {}", e)))?;
Ok(bin_ref.to_binary())
}
fn create_data_key_ctx(
&self,
kms_provider: &KmsProvider,
opts: Option<&DataKeyOptions>,
) -> Result<Ctx> {
let mut builder = self.crypt.ctx_builder();
let mut key_doc = doc! { "provider": kms_provider.name() };
if let Some(opts) = opts {
if !matches!(opts.master_key, MasterKey::Local) {
let master_doc = bson::to_document(&opts.master_key)?;
key_doc.extend(master_doc);
}
if let Some(alt_names) = &opts.key_alt_names {
for name in alt_names {
builder = builder.key_alt_name(name)?;
}
}
if let Some(material) = &opts.key_material {
builder = builder.key_material(material)?;
}
}
builder = builder.key_encryption_key(&key_doc)?;
Ok(builder.build_datakey()?)
}
pub async fn delete_key(&self, id: &Binary) -> Result<DeleteResult> {
self.key_vault.delete_one(doc! { "_id": id }, None).await
}
pub async fn get_key(&self, id: &Binary) -> Result<Option<RawDocumentBuf>> {
self.key_vault.find_one(doc! { "_id": id }, None).await
}
pub async fn get_keys(&self) -> Result<Cursor<RawDocumentBuf>> {
self.key_vault.find(doc! {}, None).await
}
pub async fn add_key_alt_name(
&self,
id: &Binary,
key_alt_name: &str,
) -> Result<Option<RawDocumentBuf>> {
self.key_vault
.find_one_and_update(
doc! { "_id": id },
doc! { "$addToSet": { "keyAltNames": key_alt_name } },
None,
)
.await
}
pub async fn remove_key_alt_name(
&self,
id: &Binary,
key_alt_name: &str,
) -> Result<Option<RawDocumentBuf>> {
let update = doc! {
"$set": {
"keyAltNames": {
"$cond": [
{ "$eq": ["$keyAltNames", [key_alt_name]] },
"$$REMOVE",
{
"$filter": {
"input": "$keyAltNames",
"cond": { "$ne": ["$$this", key_alt_name] },
}
}
]
}
}
};
self.key_vault
.find_one_and_update(doc! { "_id": id }, vec![update], None)
.await
}
pub async fn get_key_by_alt_name(
&self,
key_alt_name: impl AsRef<str>,
) -> Result<Option<RawDocumentBuf>> {
self.key_vault
.find_one(doc! { "keyAltNames": key_alt_name.as_ref() }, None)
.await
}
#[must_use]
pub fn encrypt(
&self,
value: impl Into<bson::RawBson>,
key: impl Into<EncryptKey>,
algorithm: Algorithm,
) -> EncryptAction {
EncryptAction {
client_enc: self,
value: value.into(),
opts: EncryptOptions {
key: key.into(),
algorithm,
contention_factor: None,
query_type: None,
range_options: None,
},
}
}
#[must_use]
pub fn encrypt_expression(
&self,
expression: RawDocumentBuf,
key: impl Into<EncryptKey>,
) -> EncryptExpressionAction {
EncryptExpressionAction {
client_enc: self,
value: expression,
opts: EncryptOptions {
key: key.into(),
algorithm: Algorithm::RangePreview,
contention_factor: None,
query_type: Some("rangePreview".into()),
range_options: None,
},
}
}
async fn encrypt_final(&self, value: RawBson, opts: EncryptOptions) -> Result<Binary> {
let builder = self.get_ctx_builder(&opts)?;
let ctx = builder.build_explicit_encrypt(value)?;
let result = self.exec.run_ctx(ctx, None).await?;
let bin_ref = result
.get_binary("v")
.map_err(|e| Error::internal(format!("invalid encryption result: {}", e)))?;
Ok(bin_ref.to_binary())
}
async fn encrypt_expression_final(
&self,
value: RawDocumentBuf,
opts: EncryptOptions,
) -> Result<Document> {
let builder = self.get_ctx_builder(&opts)?;
let ctx = builder.build_explicit_encrypt_expression(value)?;
let result = self.exec.run_ctx(ctx, None).await?;
let doc_ref = result
.get_document("v")
.map_err(|e| Error::internal(format!("invalid encryption result: {}", e)))?;
let doc = doc_ref
.to_owned()
.to_document()
.map_err(|e| Error::internal(format!("invalid encryption result: {}", e)))?;
Ok(doc)
}
fn get_ctx_builder(&self, opts: &EncryptOptions) -> Result<CtxBuilder> {
let mut builder = self.crypt.ctx_builder();
match &opts.key {
EncryptKey::Id(id) => {
builder = builder.key_id(&id.bytes)?;
}
EncryptKey::AltName(name) => {
builder = builder.key_alt_name(name)?;
}
}
builder = builder.algorithm(opts.algorithm)?;
if let Some(factor) = opts.contention_factor {
builder = builder.contention_factor(factor)?;
}
if let Some(qtype) = &opts.query_type {
builder = builder.query_type(qtype)?;
}
if let Some(range_options) = &opts.range_options {
let options_doc = bson::to_document(range_options)?;
builder = builder.range_options(options_doc)?;
}
Ok(builder)
}
pub async fn decrypt<'a>(&self, value: RawBinaryRef<'a>) -> Result<bson::RawBson> {
if value.subtype != BinarySubtype::Encrypted {
return Err(Error::invalid_argument(format!(
"Invalid binary subtype for decrypt: expected {:?}, got {:?}",
BinarySubtype::Encrypted,
value.subtype
)));
}
let ctx = self
.crypt
.ctx_builder()
.build_explicit_decrypt(value.bytes)?;
let result = self.exec.run_ctx(ctx, None).await?;
Ok(result
.get("v")?
.ok_or_else(|| Error::internal("invalid decryption result"))?
.to_raw_bson())
}
pub async fn create_encrypted_collection(
&self,
db: &Database,
name: impl AsRef<str>,
master_key: MasterKey,
options: CreateCollectionOptions,
) -> (Document, Result<()>) {
let ef = match options.encrypted_fields.as_ref() {
Some(ef) => ef,
None => {
return (
doc! {},
Err(Error::invalid_argument(
"no encrypted_fields defined for collection",
)),
);
}
};
let mut ef_prime = ef.clone();
if let Ok(fields) = ef_prime.get_array_mut("fields") {
for f in fields {
let f_doc = if let Some(d) = f.as_document_mut() {
d
} else {
continue;
};
if f_doc.get("keyId") == Some(&Bson::Null) {
let d = match self.create_data_key(master_key.clone()).run().await {
Ok(v) => v,
Err(e) => return (ef_prime, Err(e)),
};
f_doc.insert("keyId", d);
}
}
}
let mut opts_prime = options.clone();
opts_prime.encrypted_fields = Some(ef_prime.clone());
(
ef_prime,
db.create_collection(name.as_ref(), opts_prime).await,
)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub(crate) struct DataKeyOptions {
pub(crate) master_key: MasterKey,
pub(crate) key_alt_names: Option<Vec<String>>,
pub(crate) key_material: Option<Vec<u8>>,
}
pub struct CreateDataKeyAction<'a> {
client_enc: &'a ClientEncryption,
opts: DataKeyOptions,
}
impl<'a> CreateDataKeyAction<'a> {
pub async fn run(self) -> Result<Binary> {
self.client_enc
.create_data_key_final(&self.opts.master_key.provider(), self.opts)
.await
}
pub fn key_alt_names(mut self, names: impl IntoIterator<Item = String>) -> Self {
self.opts.key_alt_names = Some(names.into_iter().collect());
self
}
pub fn key_material(mut self, material: impl IntoIterator<Item = u8>) -> Self {
self.opts.key_material = Some(material.into_iter().collect());
self
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum MasterKey {
#[serde(rename_all = "camelCase")]
Aws {
region: String,
key: String,
endpoint: Option<String>,
},
#[serde(rename_all = "camelCase")]
Azure {
key_vault_endpoint: String,
key_name: String,
key_version: Option<String>,
},
#[serde(rename_all = "camelCase")]
Gcp {
project_id: String,
location: String,
key_ring: String,
key_name: String,
key_version: Option<String>,
endpoint: Option<String>,
},
Local,
#[serde(rename_all = "camelCase")]
Kmip {
key_id: Option<String>,
endpoint: Option<String>,
},
}
impl MasterKey {
pub fn provider(&self) -> KmsProvider {
match self {
MasterKey::Aws { .. } => KmsProvider::Aws,
MasterKey::Azure { .. } => KmsProvider::Azure,
MasterKey::Gcp { .. } => KmsProvider::Gcp,
MasterKey::Kmip { .. } => KmsProvider::Kmip,
MasterKey::Local => KmsProvider::Local,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub(crate) struct EncryptOptions {
pub(crate) key: EncryptKey,
pub(crate) algorithm: Algorithm,
pub(crate) contention_factor: Option<i64>,
pub(crate) query_type: Option<String>,
pub(crate) range_options: Option<RangeOptions>,
}
#[skip_serializing_none]
#[derive(Clone, Default, Debug, Serialize, TypedBuilder)]
#[builder(field_defaults(default, setter(into)))]
#[non_exhaustive]
pub struct RangeOptions {
pub min: Option<Bson>,
pub max: Option<Bson>,
pub sparsity: i64,
pub precision: Option<i32>,
}
pub struct EncryptAction<'a> {
client_enc: &'a ClientEncryption,
value: bson::RawBson,
opts: EncryptOptions,
}
impl<'a> EncryptAction<'a> {
pub async fn run(self) -> Result<Binary> {
self.client_enc.encrypt_final(self.value, self.opts).await
}
pub fn contention_factor(mut self, factor: impl Into<Option<i64>>) -> Self {
self.opts.contention_factor = factor.into();
self
}
pub fn query_type(mut self, qtype: impl Into<String>) -> Self {
self.opts.query_type = Some(qtype.into());
self
}
pub fn range_options(mut self, range_options: impl Into<Option<RangeOptions>>) -> Self {
self.opts.range_options = range_options.into();
self
}
}
pub struct EncryptExpressionAction<'a> {
client_enc: &'a ClientEncryption,
value: RawDocumentBuf,
opts: EncryptOptions,
}
impl<'a> EncryptExpressionAction<'a> {
pub async fn run(self) -> Result<Document> {
self.client_enc
.encrypt_expression_final(self.value, self.opts)
.await
}
pub fn contention_factor(mut self, factor: impl Into<Option<i64>>) -> Self {
self.opts.contention_factor = factor.into();
self
}
pub fn range_options(mut self, range_options: impl Into<Option<RangeOptions>>) -> Self {
self.opts.range_options = range_options.into();
self
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum EncryptKey {
Id(Binary),
AltName(String),
}
impl From<Binary> for EncryptKey {
fn from(bin: Binary) -> Self {
Self::Id(bin)
}
}
impl From<String> for EncryptKey {
fn from(s: String) -> Self {
Self::AltName(s)
}
}