mod errors;
mod local_log;
mod vitur_client;
use crate::credentials::{service_credentials::ServiceToken, Credentials};
use local_log::log_decryptions;
use log::debug;
use std::path::{Path, PathBuf};
use url::Url;
use uuid::Uuid;
pub use errors::Error;
pub use vitur_client::{ClientKey, EncryptPayload, EncryptedRecord};
pub use zerokms_protocol::cipherstash_config::{DatasetConfig, DatasetConfigWithIndexRootKey};
pub use zerokms_protocol::{
ClientDatasetId, CreateClientResponse, Dataset, DatasetClient, RevokeClientResponse,
};
type ViturClient = vitur_client::Client<vitur_client::HttpConnection>;
pub struct ZeroKMS<C: Credentials<Token = ServiceToken>, ClientKeyState = ()> {
client: ViturClient,
credentials: C,
decryption_log_path: Option<PathBuf>,
client_key: ClientKeyState,
}
pub type ZeroKMSWithClientKey<C> = ZeroKMS<C, ClientKey>;
impl<C: Credentials<Token = ServiceToken>> ZeroKMS<C> {
pub fn new(base_url: &Url, credentials: C, decryption_log_path: Option<&Path>) -> Self {
let mut host = base_url.to_string();
if host.ends_with('/') {
host.pop();
}
let client = ViturClient::init(host);
Self {
client,
credentials,
decryption_log_path: decryption_log_path.map(|p| p.to_path_buf()),
client_key: (),
}
}
pub fn new_with_client_key(
base_url: &Url,
credentials: C,
decryption_log_path: Option<&Path>,
client_key: ClientKey,
) -> ZeroKMSWithClientKey<C> {
let mut host = base_url.to_string();
if host.ends_with('/') {
host.pop();
}
let client = ViturClient::init(host);
ZeroKMSWithClientKey {
client,
credentials,
decryption_log_path: decryption_log_path.map(|p| p.to_path_buf()),
client_key,
}
}
}
impl<C: Credentials<Token = ServiceToken>, K> ZeroKMS<C, K> {
pub fn log_decryptions(&self, records: &[EncryptedRecord], access_token: &str) {
if let Some(log_path) = &self.decryption_log_path {
_ = log_decryptions(records, access_token, log_path);
}
}
pub async fn create_dataset(&self, name: &str, description: &str) -> Result<Dataset, Error> {
let access_token = self.credentials.get_token().await?.access_token();
self.client
.create_dataset(name, description, &access_token)
.await
.map_err(Error::from)
}
pub async fn grant_dataset(&self, client_id: Uuid, dataset_id: Uuid) -> Result<(), Error> {
let access_token = self.credentials.get_token().await?.access_token();
self.client
.grant_dataset(client_id, dataset_id, &access_token)
.await
.map_err(Error::from)
}
pub async fn revoke_dataset(&self, client_id: Uuid, dataset_id: Uuid) -> Result<(), Error> {
let access_token = self.credentials.get_token().await?.access_token();
self.client
.revoke_dataset(client_id, dataset_id, &access_token)
.await
.map_err(Error::from)
}
pub async fn list_datasets(&self) -> Result<Vec<Dataset>, Error> {
let access_token = self.credentials.get_token().await?.access_token();
let show_disabled = false;
self.client
.list_datasets(&access_token, show_disabled)
.await
.map_err(Error::from)
}
pub async fn enable_dataset(&self, dataset_id: Uuid) -> Result<(), Error> {
let access_token = self.credentials.get_token().await?.access_token();
self.client
.enable_dataset(dataset_id, &access_token)
.await
.map_err(Error::from)
}
pub async fn disable_dataset(&self, dataset_id: Uuid) -> Result<(), Error> {
let access_token = self.credentials.get_token().await?.access_token();
self.client
.disable_dataset(dataset_id, &access_token)
.await
.map_err(Error::from)
}
pub async fn modify_dataset(
&self,
dataset_id: Uuid,
name: Option<&str>,
description: Option<&str>,
) -> Result<(), Error> {
let access_token = self.credentials.get_token().await?.access_token();
self.client
.modify_dataset(dataset_id, name, description, &access_token)
.await
.map_err(Error::from)
}
pub async fn create_client(
&self,
name: &str,
description: &str,
dataset_id: Uuid,
) -> Result<CreateClientResponse, Error> {
let access_token = self.credentials.get_token().await?.access_token();
self.client
.create_client(name, description, dataset_id, &access_token)
.await
.map_err(Error::from)
}
pub async fn list_clients(&self) -> Result<Vec<DatasetClient>, Error> {
let access_token = self.credentials.get_token().await?.access_token();
self.client
.list_clients(&access_token)
.await
.map_err(Error::from)
}
pub async fn revoke_client(&self, client_id: Uuid) -> Result<RevokeClientResponse, Error> {
let access_token = self.credentials.get_token().await?.access_token();
self.client
.revoke_client(client_id, &access_token)
.await
.map_err(Error::from)
}
}
impl<C: Credentials<Token = ServiceToken>> ZeroKMSWithClientKey<C> {
pub async fn save_dataset_config(
&self,
config: DatasetConfig,
) -> Result<DatasetConfigWithIndexRootKey, Error> {
let access_token = self.credentials.get_token().await?.access_token();
let dataset_id = None;
self.client
.save_config(config, &self.client_key, &access_token, dataset_id)
.await
.map_err(Error::from)
}
pub async fn load_dataset_config(&self) -> Result<DatasetConfigWithIndexRootKey, Error> {
let access_token = self.credentials.get_token().await?.access_token();
let dataset_id = None;
self.client
.load_config(&self.client_key, &access_token, dataset_id)
.await
.map_err(Error::from)
}
pub async fn encrypt(
&self,
payloads: impl IntoIterator<Item = EncryptPayload<'_>>,
dataset_id: Option<Uuid>,
) -> Result<Vec<EncryptedRecord>, Error> {
debug!(target: "zero_kms::encrypt", "encrypting records");
let payloads: Vec<_> = payloads.into_iter().collect();
if payloads.is_empty() {
debug!(target: "zero_kms::encrypt", "no records to encrypt");
return Ok(vec![]);
}
debug!(target: "zero_kms::encrypt", "waiting for access token");
let access_token = self.credentials.get_token().await?.access_token();
debug!(target: "zero_kms::encrypt", "got token, encrypting");
let res = self
.client
.encrypt(payloads, &self.client_key, dataset_id, &access_token)
.await?;
debug!(target: "zero_kms::encrypt", "success, encrypted {} records", res.len());
Ok(res)
}
pub async fn encrypt_single(
&self,
payload: EncryptPayload<'_>,
dataset_id: Option<Uuid>,
) -> Result<EncryptedRecord, Error> {
debug!(target: "zero_kms::encrypt_single", "encrypting record - waiting for access token");
let access_token = self.credentials.get_token().await?.access_token();
debug!(target: "zero_kms::encrypt_single", "got token, encrypting");
let res = self
.client
.encrypt_single(payload, &self.client_key, dataset_id, &access_token)
.await?;
debug!(target: "zero_kms::encrypt_single", "success");
Ok(res)
}
pub async fn decrypt(
&self,
payloads: impl IntoIterator<Item = EncryptedRecord>,
) -> Result<Vec<Vec<u8>>, Error> {
debug!(target: "zero_kms::decrypt", "decrypting records");
let payloads: Vec<_> = payloads.into_iter().collect();
if payloads.is_empty() {
debug!(target: "zero_kms::decrypt", "no records to decrypt");
return Ok(vec![]);
}
debug!(target: "zero_kms::decrypt", "waiting for access token");
let access_token = self.credentials.get_token().await?.access_token();
self.log_decryptions(&payloads[..], &access_token);
debug!(target: "zero_kms::decrypt", "got token, decrypting {} records", payloads.len());
let res = self
.client
.decrypt(payloads, &self.client_key, &access_token)
.await?;
debug!(target: "zero_kms::decrypt", "success, decrypted {} records", res.len());
Ok(res)
}
pub async fn decrypt_single(&self, payload: EncryptedRecord) -> Result<Vec<u8>, Error> {
debug!(target: "zero_kms::decrypt_single", "decrypting record - waiting for access token");
let access_token = self.credentials.get_token().await?.access_token();
self.log_decryptions(&[payload.clone()], &access_token);
debug!(target: "zero_kms::decrypt_single", "got token, decrypting record");
let res = self
.client
.decrypt_single(payload, &self.client_key, &access_token)
.await?;
debug!(target: "zero_kms::decrypt_single", "success");
Ok(res)
}
}