zerokms_protocol/
lib.rs

1mod base64_array;
2mod base64_vec;
3mod error;
4
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::{borrow::Cow, ops::Deref};
8use uuid::Uuid;
9use zeroize::{Zeroize, ZeroizeOnDrop};
10
11pub use cipherstash_config;
12/// Re-exports
13pub use error::*;
14pub mod testing;
15
16#[async_trait]
17pub trait ViturConnection {
18    async fn send<Request: ViturRequest>(
19        &self,
20        request: Request,
21        access_token: &str,
22    ) -> Result<Request::Response, ViturRequestError>;
23}
24
25pub trait ViturResponse: Serialize + for<'de> Deserialize<'de> + Send {}
26
27#[async_trait]
28pub trait ViturRequest: Serialize + for<'de> Deserialize<'de> + Sized + Send {
29    type Response: ViturResponse;
30
31    const SCOPE: &'static str;
32    const ENDPOINT: &'static str;
33}
34
35/// Request message to create a new [Dataset] with the given name and description.
36///
37/// Requies the `dataset:create` scope.
38#[derive(Debug, Serialize, Deserialize)]
39pub struct CreateDatasetRequest<'a> {
40    pub name: Cow<'a, str>,
41    pub description: Cow<'a, str>,
42}
43
44impl ViturRequest for CreateDatasetRequest<'_> {
45    type Response = Dataset;
46
47    const ENDPOINT: &'static str = "create-dataset";
48    const SCOPE: &'static str = "dataset:create";
49}
50
51/// Request message to list all [Dataset]s.
52///
53/// Requires the `dataset:list` scope.
54/// Response is a vector of [Dataset]s.
55#[derive(Default, Debug, Serialize, Deserialize)]
56pub struct ListDatasetRequest {
57    #[serde(default)]
58    pub show_disabled: bool,
59}
60
61impl ViturRequest for ListDatasetRequest {
62    type Response = Vec<Dataset>;
63
64    const ENDPOINT: &'static str = "list-datasets";
65    const SCOPE: &'static str = "dataset:list";
66}
67
68/// Struct representing a dataset.
69/// This is the response to a [CreateDatasetRequest] and a in a vector in the response to a [ListDatasetRequest].
70#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
71pub struct Dataset {
72    pub id: Uuid,
73    pub name: String,
74    pub description: String,
75}
76
77impl ViturResponse for Dataset {}
78impl ViturResponse for Vec<Dataset> {}
79
80/// Represents an empty response for requests that don't return any data.
81#[derive(Default, Debug, Serialize, Deserialize)]
82pub struct EmptyResponse {}
83
84impl ViturResponse for EmptyResponse {}
85
86/// Request message to create a new client with the given name, description and dataset_id.
87///
88/// Requires the `client:create` scope.
89/// Response is a [CreateClientResponse].
90#[derive(Debug, Serialize, Deserialize)]
91pub struct CreateClientRequest<'a> {
92    pub dataset_id: Uuid,
93    pub name: Cow<'a, str>,
94    pub description: Cow<'a, str>,
95}
96
97impl ViturRequest for CreateClientRequest<'_> {
98    type Response = CreateClientResponse;
99
100    const ENDPOINT: &'static str = "create-client";
101    const SCOPE: &'static str = "client:create";
102}
103
104/// Response message to a [CreateClientRequest].
105///
106/// Contains the `client_id`` and the `client_key, the latter being a base64 encoded 32 byte key.
107/// The `client_key` should be considered sensitive and should be stored securely.
108#[derive(Debug, Serialize, Deserialize)]
109pub struct CreateClientResponse {
110    pub id: Uuid,
111    pub dataset_id: Uuid,
112    pub name: String,
113    pub description: String,
114    pub client_key: ViturKeyMaterial,
115}
116
117impl ViturResponse for CreateClientResponse {}
118
119/// Request message to list all clients.
120///
121/// Requires the `client:list` scope.
122/// Response is a vector of [DatasetClient]s.
123#[derive(Debug, Serialize, Deserialize)]
124pub struct ListClientRequest;
125
126impl ViturRequest for ListClientRequest {
127    type Response = Vec<DatasetClient>;
128
129    const ENDPOINT: &'static str = "list-clients";
130    const SCOPE: &'static str = "client:list";
131}
132
133/// Struct representing the dataset ids associated with a client
134/// which could be a single dataset or multiple datasets.
135#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
136#[serde(untagged)]
137pub enum ClientDatasetId {
138    Single(Uuid),
139    Multiple(Vec<Uuid>),
140}
141
142/// A `Uuid` is comparable with `ClientDatasetId` if the `ClientDatasetId` is a `Single` variant.
143impl PartialEq<Uuid> for ClientDatasetId {
144    fn eq(&self, other: &Uuid) -> bool {
145        if let ClientDatasetId::Single(id) = self {
146            id == other
147        } else {
148            false
149        }
150    }
151}
152
153/// Response type for a [ListClientRequest].
154#[derive(Debug, Serialize, Deserialize)]
155pub struct DatasetClient {
156    pub id: Uuid,
157    pub dataset_id: ClientDatasetId,
158    pub name: String,
159    pub description: String,
160}
161
162impl ViturResponse for Vec<DatasetClient> {}
163
164/// Request message to delete a client and all associated authority keys.
165///
166/// Requires the `client:revoke` scope.
167/// Response is an [DeleteClientResponse].
168#[derive(Debug, Serialize, Deserialize)]
169pub struct DeleteClientRequest {
170    pub client_id: Uuid,
171}
172
173impl ViturRequest for DeleteClientRequest {
174    type Response = DeleteClientResponse;
175
176    const ENDPOINT: &'static str = "delete-client";
177    const SCOPE: &'static str = "client:delete";
178}
179
180#[derive(Default, Debug, Serialize, Deserialize)]
181pub struct DeleteClientResponse {}
182
183impl ViturResponse for DeleteClientResponse {}
184
185/// Key material type used in [GenerateKeyRequest] and [RetrieveKeyRequest] as well as [CreateClientResponse].
186#[derive(Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
187pub struct ViturKeyMaterial(#[serde(with = "base64_vec")] Vec<u8>);
188opaque_debug::implement!(ViturKeyMaterial);
189
190impl From<Vec<u8>> for ViturKeyMaterial {
191    fn from(inner: Vec<u8>) -> Self {
192        Self(inner)
193    }
194}
195
196impl Deref for ViturKeyMaterial {
197    type Target = [u8];
198
199    fn deref(&self) -> &Self::Target {
200        &self.0
201    }
202}
203
204/// Represents generated data key material which is used by the client to derive data keys with its own key material.
205///
206/// Returned in the response to a [GenerateKeyRequest].
207#[derive(Debug, Serialize, Deserialize)]
208pub struct GeneratedKey {
209    pub key_material: ViturKeyMaterial,
210    // FIXME: Use Vitamin C Equatable type
211    #[serde(with = "base64_vec")]
212    pub tag: Vec<u8>,
213}
214
215/// Response to a [GenerateKeyRequest].
216#[derive(Debug, Serialize, Deserialize)]
217pub struct GenerateKeyResponse {
218    pub keys: Vec<GeneratedKey>,
219}
220
221impl ViturResponse for GenerateKeyResponse {}
222
223/// A specification for generating a data key used in a [GenerateKeyRequest].
224#[derive(Debug, Serialize, Deserialize, Clone)]
225pub struct GenerateKeySpec<'a> {
226    // FIXME: Remove IV and have the server generate it instead
227    #[serde(with = "base64_array")]
228    pub iv: [u8; 16],
229    // TODO: Deprecate descriptor in favor of context
230    pub descriptor: Cow<'a, str>,
231
232    #[serde(default = "Vec::new")]
233    pub context: Vec<Context>,
234}
235
236impl<'a> GenerateKeySpec<'a> {
237    pub fn new(iv: [u8; 16], descriptor: &'a str) -> Self {
238        Self {
239            iv,
240            descriptor: Cow::from(descriptor),
241            context: Vec::new(),
242        }
243    }
244
245    pub fn new_with_context(iv: [u8; 16], descriptor: &'a str, context: Vec<Context>) -> Self {
246        Self {
247            iv,
248            descriptor: Cow::from(descriptor),
249            context,
250        }
251    }
252}
253/// Represents a contextual attribute for a data key which is used to "lock" the key to a specific context.
254/// Context attributes are included key tag generation which is in turn used as AAD in the final encryption step in the client.
255/// Context attributes should _never_ include any sensitive information.
256#[derive(Debug, Serialize, Deserialize, Clone)]
257// TODO: Use Cow?
258pub enum Context {
259    /// A tag that can be used to identify the key.
260    Tag(String),
261
262    /// A key-value pair that can be used to identify the key.
263    /// For example, a key-value pair could be `("user_id", "1234")`.
264    Value(String, String),
265
266    /// A claim from the identity of the principal that is requesting the key.
267    /// The claim value is read from the claims list after token verification and prior to key generation.
268    ///
269    /// For example, a claim could be `"sub"`.
270    IdentityClaim(String),
271}
272
273impl Context {
274    pub fn new_tag(tag: impl Into<String>) -> Self {
275        Self::Tag(tag.into())
276    }
277
278    pub fn new_value(key: impl Into<String>, value: impl Into<String>) -> Self {
279        Self::Value(key.into(), value.into())
280    }
281
282    pub fn new_identity_claim(claim: &str) -> Self {
283        Self::IdentityClaim(claim.to_string())
284    }
285}
286
287/// A request message to generate a data key made on behalf of a client
288/// in the given dataset.
289///
290/// Requires the `data_key:generate` scope.
291/// Response is a [GenerateKeyResponse].
292///
293/// See also [GenerateKeySpec].
294#[derive(Debug, Serialize, Deserialize)]
295pub struct GenerateKeyRequest<'a> {
296    pub client_id: Uuid,
297    pub dataset_id: Option<Uuid>,
298    pub keys: Cow<'a, [GenerateKeySpec<'a>]>,
299}
300
301impl ViturRequest for GenerateKeyRequest<'_> {
302    type Response = GenerateKeyResponse;
303
304    const ENDPOINT: &'static str = "generate-data-key";
305    const SCOPE: &'static str = "data_key:generate";
306}
307
308/// Returned type from a [RetrieveKeyRequest].
309#[derive(Debug, Serialize, Deserialize)]
310pub struct RetrievedKey {
311    pub key_material: ViturKeyMaterial,
312}
313
314/// Response to a [RetrieveKeyRequest].
315/// Contains a list of [RetrievedKey]s.
316#[derive(Debug, Serialize, Deserialize)]
317pub struct RetrieveKeyResponse {
318    pub keys: Vec<RetrievedKey>,
319}
320
321impl ViturResponse for RetrieveKeyResponse {}
322
323/// A specification for retrieving a data key used in a [RetrieveKeyRequest].
324#[derive(Debug, Serialize, Deserialize, Clone)]
325pub struct RetrieveKeySpec<'a> {
326    #[serde(with = "base64_array")]
327    pub iv: [u8; 16],
328    // TODO: Make Descriptor Optional
329    pub descriptor: Cow<'a, str>,
330    pub tag: Cow<'a, [u8]>,
331
332    #[serde(default = "Vec::new")]
333    pub context: Vec<Context>,
334
335    // Since this field will be removed in the future allow older versions of Vitur to be able to
336    // parse a RetrieveKeySpec that doesn't include the tag_version.
337    #[serde(default)]
338    pub tag_version: usize,
339}
340
341impl<'a> RetrieveKeySpec<'a> {
342    const DEFAULT_TAG_VERSION: usize = 0;
343
344    pub fn new(iv: [u8; 16], tag: &'a [u8], descriptor: &'a str) -> Self {
345        Self {
346            iv,
347            descriptor: Cow::from(descriptor),
348            tag: Cow::from(tag),
349            context: Vec::new(),
350            tag_version: Self::DEFAULT_TAG_VERSION,
351        }
352    }
353
354    pub fn with_context(mut self, context: Vec<Context>) -> Self {
355        self.context = context;
356        self
357    }
358}
359
360/// Request to retrieve a data key on behalf of a client in the given dataset.
361/// Requires the `data_key:retrieve` scope.
362/// Response is a [RetrieveKeyResponse].
363///
364/// See also [RetrieveKeySpec].
365#[derive(Debug, Serialize, Deserialize)]
366pub struct RetrieveKeyRequest<'a> {
367    pub client_id: Uuid,
368    pub dataset_id: Option<Uuid>,
369    pub keys: Cow<'a, [RetrieveKeySpec<'a>]>,
370}
371
372impl ViturRequest for RetrieveKeyRequest<'_> {
373    type Response = RetrieveKeyResponse;
374
375    const ENDPOINT: &'static str = "retrieve-data-key";
376    const SCOPE: &'static str = "data_key:retrieve";
377}
378
379/// Request message to disable a dataset.
380/// Requires the `dataset:disable` scope.
381/// Response is an [EmptyResponse].
382#[derive(Debug, Serialize, Deserialize)]
383pub struct DisableDatasetRequest {
384    pub dataset_id: Uuid,
385}
386
387impl ViturRequest for DisableDatasetRequest {
388    type Response = EmptyResponse;
389
390    const ENDPOINT: &'static str = "disable-dataset";
391    const SCOPE: &'static str = "dataset:disable";
392}
393
394/// Request message to enable a dataset that has was previously disabled.
395/// Requires the `dataset:enable` scope.
396/// Response is an [EmptyResponse].
397#[derive(Debug, Serialize, Deserialize)]
398pub struct EnableDatasetRequest {
399    pub dataset_id: Uuid,
400}
401
402impl ViturRequest for EnableDatasetRequest {
403    type Response = EmptyResponse;
404
405    const ENDPOINT: &'static str = "enable-dataset";
406    const SCOPE: &'static str = "dataset:enable";
407}
408
409/// Request message to modify a dataset with the given dataset_id.
410/// `name` and `description` are optional and will be updated if provided.
411///
412/// Requires the `dataset:modify` scope.
413/// Response is an [EmptyResponse].
414#[derive(Debug, Serialize, Deserialize)]
415pub struct ModifyDatasetRequest<'a> {
416    pub dataset_id: Uuid,
417
418    pub name: Option<Cow<'a, str>>,
419    pub description: Option<Cow<'a, str>>,
420}
421
422impl ViturRequest for ModifyDatasetRequest<'_> {
423    type Response = EmptyResponse;
424
425    const ENDPOINT: &'static str = "modify-dataset";
426    const SCOPE: &'static str = "dataset:modify";
427}
428
429/// Request message to grant a client access to a dataset.
430/// Requires the `dataset:grant` scope.
431///
432/// Response is an [EmptyResponse].
433#[derive(Debug, Serialize, Deserialize)]
434pub struct GrantDatasetRequest {
435    pub client_id: Uuid,
436    pub dataset_id: Uuid,
437}
438
439impl ViturRequest for GrantDatasetRequest {
440    type Response = EmptyResponse;
441
442    const ENDPOINT: &'static str = "grant-dataset";
443    const SCOPE: &'static str = "dataset:grant";
444}
445
446/// Request message to revoke a client's access to a dataset.
447/// Requires the `dataset:revoke` scope.
448/// Response is an [EmptyResponse].
449#[derive(Debug, Serialize, Deserialize)]
450pub struct RevokeDatasetRequest {
451    pub client_id: Uuid,
452    pub dataset_id: Uuid,
453}
454
455impl ViturRequest for RevokeDatasetRequest {
456    type Response = EmptyResponse;
457
458    const ENDPOINT: &'static str = "revoke-dataset";
459    const SCOPE: &'static str = "dataset:revoke";
460}
461
462/// Request to load a dataset on behalf of a client.
463/// This is used by clients before indexing or querying data and includes
464/// key material which can be derived by the client to generate encrypted index terms.
465///
466/// If a dataset_id is not provided the client's default dataset will be loaded.
467///
468/// Requires the `data_key:retrieve` scope (though this may change in the future).
469/// Response is a [LoadDatasetResponse].
470#[derive(Debug, Serialize, Deserialize)]
471pub struct LoadDatasetRequest {
472    pub client_id: Uuid,
473    pub dataset_id: Option<Uuid>,
474}
475
476impl ViturRequest for LoadDatasetRequest {
477    type Response = LoadDatasetResponse;
478
479    const ENDPOINT: &'static str = "load-dataset";
480    // NOTE: We don't currently support the ability to allow an operation
481    // based on any one of several possible scopes so we'll just use `data_key:retrieve` for now.
482    // This should probably be allowed for any operation that requires indexing or querying.
483    const SCOPE: &'static str = "data_key:retrieve";
484}
485
486/// Response to a [LoadDatasetRequest].
487/// The response includes the key material required to derive data keys.
488/// It is analogous to a [RetrieveKeyResponse] but where the server generated the key.
489#[derive(Debug, Serialize, Deserialize)]
490pub struct LoadDatasetResponse {
491    pub partial_index_key: RetrievedKey,
492    pub dataset: Dataset,
493}
494
495impl ViturResponse for LoadDatasetResponse {}