zerokms_protocol/
lib.rs

1mod base64_array;
2mod base64_vec;
3mod error;
4mod identified_by;
5
6pub use identified_by::*;
7
8mod unverified_context;
9
10use async_trait::async_trait;
11use serde::{Deserialize, Serialize};
12use std::{
13    borrow::Cow,
14    fmt::{self, Debug, Display, Formatter},
15    ops::Deref,
16};
17use uuid::Uuid;
18use zeroize::{Zeroize, ZeroizeOnDrop};
19
20pub use cipherstash_config;
21/// Re-exports
22pub use error::*;
23
24pub use crate::unverified_context::{UnverifiedContext, UnverifiedContextValue};
25pub use crate::{IdentifiedBy, Name};
26pub mod testing;
27
28#[async_trait]
29pub trait ViturConnection {
30    async fn send<Request: ViturRequest>(
31        &self,
32        request: Request,
33        access_token: &str,
34    ) -> Result<Request::Response, ViturRequestError>;
35}
36
37pub trait ViturResponse: Serialize + for<'de> Deserialize<'de> + Send {}
38
39#[async_trait]
40pub trait ViturRequest: Serialize + for<'de> Deserialize<'de> + Sized + Send {
41    type Response: ViturResponse;
42
43    const SCOPE: &'static str;
44    const ENDPOINT: &'static str;
45}
46
47/// Request message to create a new [Keyset] with the given name and description.
48///
49/// Requies the `dataset:create` scope.
50#[derive(Debug, Serialize, Deserialize)]
51pub struct CreateKeysetRequest<'a> {
52    pub name: Cow<'a, str>,
53    pub description: Cow<'a, str>,
54}
55
56impl ViturRequest for CreateKeysetRequest<'_> {
57    type Response = Keyset;
58
59    const ENDPOINT: &'static str = "create-keyset";
60    const SCOPE: &'static str = "dataset:create";
61}
62
63/// Request message to list all [Keyset]s.
64///
65/// Requires the `dataset:list` scope.
66/// Response is a vector of [Keyset]s.
67#[derive(Default, Debug, Serialize, Deserialize)]
68pub struct ListKeysetRequest {
69    #[serde(default)]
70    pub show_disabled: bool,
71}
72
73impl ViturRequest for ListKeysetRequest {
74    type Response = Vec<Keyset>;
75
76    const ENDPOINT: &'static str = "list-keysets";
77    const SCOPE: &'static str = "dataset:list";
78}
79
80/// Struct representing a keyset.
81/// This is the response to a [CreateKeysetRequest] and a in a vector in the response to a [ListKeysetRequest].
82#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
83pub struct Keyset {
84    pub id: Uuid,
85    pub name: String,
86    pub description: String,
87    pub is_disabled: bool,
88}
89
90impl ViturResponse for Keyset {}
91impl ViturResponse for Vec<Keyset> {}
92
93/// Represents an empty response for requests that don't return any data.
94#[derive(Default, Debug, Serialize, Deserialize)]
95pub struct EmptyResponse {}
96
97impl ViturResponse for EmptyResponse {}
98
99/// Request message to create a new client with the given name, description and keyset_id.
100///
101/// Requires the `client:create` scope.
102/// Response is a [CreateClientResponse].
103#[derive(Debug, Serialize, Deserialize)]
104pub struct CreateClientRequest<'a> {
105    #[serde(alias = "dataset_id")]
106    pub keyset_id: IdentifiedBy,
107    pub name: Cow<'a, str>,
108    pub description: Cow<'a, str>,
109}
110
111impl ViturRequest for CreateClientRequest<'_> {
112    type Response = CreateClientResponse;
113
114    const ENDPOINT: &'static str = "create-client";
115    const SCOPE: &'static str = "client:create";
116}
117
118/// Response message to a [CreateClientRequest].
119///
120/// Contains the `client_id` and the `client_key`, the latter being a base64 encoded 32 byte key.
121/// The `client_key` should be considered sensitive and should be stored securely.
122#[derive(Debug, Serialize, Deserialize)]
123pub struct CreateClientResponse {
124    pub id: Uuid,
125    #[serde(rename = "dataset_id")]
126    pub keyset_id: Uuid,
127    pub name: String,
128    pub description: String,
129    pub client_key: ViturKeyMaterial,
130}
131
132impl ViturResponse for CreateClientResponse {}
133
134/// Request message to list all clients.
135///
136/// Requires the `client:list` scope.
137/// Response is a vector of [KeysetClient]s.
138#[derive(Debug, Serialize, Deserialize)]
139pub struct ListClientRequest;
140
141impl ViturRequest for ListClientRequest {
142    type Response = Vec<KeysetClient>;
143
144    const ENDPOINT: &'static str = "list-clients";
145    const SCOPE: &'static str = "client:list";
146}
147
148/// Struct representing the keyset ids associated with a client
149/// which could be a single keyset or multiple keysets.
150#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
151#[serde(untagged)]
152pub enum ClientKeysetId {
153    Single(Uuid),
154    Multiple(Vec<Uuid>),
155}
156
157/// A `Uuid` is comparable with `ClientKeysetId` if the `ClientKeysetId` is a `Single` variant.
158impl PartialEq<Uuid> for ClientKeysetId {
159    fn eq(&self, other: &Uuid) -> bool {
160        if let ClientKeysetId::Single(id) = self {
161            id == other
162        } else {
163            false
164        }
165    }
166}
167
168/// Response type for a [ListClientRequest].
169#[derive(Debug, Serialize, Deserialize)]
170pub struct KeysetClient {
171    pub id: Uuid,
172    #[serde(alias = "dataset_id")]
173    pub keyset_id: ClientKeysetId,
174    pub name: String,
175    pub description: String,
176}
177
178impl ViturResponse for Vec<KeysetClient> {}
179
180/// Request message to delete a client and all associated authority keys.
181///
182/// Requires the `client:revoke` scope.
183/// Response is an [DeleteClientResponse].
184#[derive(Debug, Serialize, Deserialize)]
185pub struct DeleteClientRequest {
186    pub client_id: Uuid,
187}
188
189impl ViturRequest for DeleteClientRequest {
190    type Response = DeleteClientResponse;
191
192    const ENDPOINT: &'static str = "delete-client";
193    const SCOPE: &'static str = "client:delete";
194}
195
196#[derive(Default, Debug, Serialize, Deserialize)]
197pub struct DeleteClientResponse {}
198
199impl ViturResponse for DeleteClientResponse {}
200
201/// Key material type used in [GenerateKeyRequest] and [RetrieveKeyRequest] as well as [CreateClientResponse].
202#[derive(Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
203pub struct ViturKeyMaterial(#[serde(with = "base64_vec")] Vec<u8>);
204opaque_debug::implement!(ViturKeyMaterial);
205
206impl From<Vec<u8>> for ViturKeyMaterial {
207    fn from(inner: Vec<u8>) -> Self {
208        Self(inner)
209    }
210}
211
212impl Deref for ViturKeyMaterial {
213    type Target = [u8];
214
215    fn deref(&self) -> &Self::Target {
216        &self.0
217    }
218}
219
220#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Zeroize)]
221#[serde(transparent)]
222pub struct KeyId(#[serde(with = "base64_array")] [u8; 16]);
223
224impl KeyId {
225    pub fn into_inner(self) -> [u8; 16] {
226        self.0
227    }
228}
229
230impl Display for KeyId {
231    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
232        write!(f, "{}", const_hex::encode(self.0))
233    }
234}
235
236impl From<[u8; 16]> for KeyId {
237    fn from(inner: [u8; 16]) -> Self {
238        Self(inner)
239    }
240}
241
242impl AsRef<[u8; 16]> for KeyId {
243    fn as_ref(&self) -> &[u8; 16] {
244        &self.0
245    }
246}
247
248/// Represents generated data key material which is used by the client to derive data keys with its own key material.
249///
250/// Returned in the response to a [GenerateKeyRequest].
251#[derive(Debug, Serialize, Deserialize)]
252pub struct GeneratedKey {
253    pub key_material: ViturKeyMaterial,
254    // FIXME: Use Vitamin C Equatable type
255    #[serde(with = "base64_vec")]
256    pub tag: Vec<u8>,
257}
258
259/// Response to a [GenerateKeyRequest].
260#[derive(Debug, Serialize, Deserialize)]
261pub struct GenerateKeyResponse {
262    pub keys: Vec<GeneratedKey>,
263}
264
265impl ViturResponse for GenerateKeyResponse {}
266
267/// A specification for generating a data key used in a [GenerateKeyRequest].
268#[derive(Debug, Serialize, Deserialize, Clone)]
269pub struct GenerateKeySpec<'a> {
270    // FIXME: Remove ID and have the server generate it instead
271    #[serde(alias = "id")]
272    pub iv: KeyId,
273    // TODO: Deprecate descriptor in favor of context
274    pub descriptor: Cow<'a, str>,
275
276    #[serde(default = "Vec::new")]
277    pub context: Vec<Context>,
278}
279
280impl<'a> GenerateKeySpec<'a> {
281    pub fn new(iv: [u8; 16], descriptor: &'a str) -> Self {
282        Self {
283            iv: KeyId(iv),
284            descriptor: Cow::from(descriptor),
285            context: Vec::new(),
286        }
287    }
288
289    pub fn new_with_context(iv: [u8; 16], descriptor: &'a str, context: Vec<Context>) -> Self {
290        Self {
291            iv: KeyId(iv),
292            descriptor: Cow::from(descriptor),
293            context,
294        }
295    }
296}
297/// Represents a contextual attribute for a data key which is used to "lock" the key to a specific context.
298/// Context attributes are included key tag generation which is in turn used as AAD in the final encryption step in the client.
299/// Context attributes should _never_ include any sensitive information.
300#[derive(Debug, Serialize, Deserialize, Clone)]
301// TODO: Use Cow?
302pub enum Context {
303    /// A tag that can be used to identify the key.
304    Tag(String),
305
306    /// A key-value pair that can be used to identify the key.
307    /// For example, a key-value pair could be `("user_id", "1234")`.
308    Value(String, String),
309
310    /// A claim from the identity of the principal that is requesting the key.
311    /// The claim value is read from the claims list after token verification and prior to key generation.
312    ///
313    /// For example, a claim could be `"sub"`.
314    IdentityClaim(String),
315}
316
317impl Context {
318    pub fn new_tag(tag: impl Into<String>) -> Self {
319        Self::Tag(tag.into())
320    }
321
322    pub fn new_value(key: impl Into<String>, value: impl Into<String>) -> Self {
323        Self::Value(key.into(), value.into())
324    }
325
326    pub fn new_identity_claim(claim: &str) -> Self {
327        Self::IdentityClaim(claim.to_string())
328    }
329}
330
331/// A request message to generate a data key made on behalf of a client
332/// in the given keyset.
333///
334/// Requires the `data_key:generate` scope.
335/// Response is a [GenerateKeyResponse].
336///
337/// See also [GenerateKeySpec].
338#[derive(Debug, Serialize, Deserialize)]
339pub struct GenerateKeyRequest<'a> {
340    pub client_id: Uuid,
341    #[serde(alias = "dataset_id")]
342    pub keyset_id: Option<IdentifiedBy>,
343    pub keys: Cow<'a, [GenerateKeySpec<'a>]>,
344    #[serde(default)]
345    pub unverified_context: UnverifiedContext,
346}
347
348impl ViturRequest for GenerateKeyRequest<'_> {
349    type Response = GenerateKeyResponse;
350
351    const ENDPOINT: &'static str = "generate-data-key";
352    const SCOPE: &'static str = "data_key:generate";
353}
354
355/// Returned type from a [RetrieveKeyRequest].
356#[derive(Debug, Serialize, Deserialize)]
357pub struct RetrievedKey {
358    pub key_material: ViturKeyMaterial,
359}
360
361/// Response to a [RetrieveKeyRequest].
362/// Contains a list of [RetrievedKey]s.
363#[derive(Debug, Serialize, Deserialize)]
364pub struct RetrieveKeyResponse {
365    pub keys: Vec<RetrievedKey>,
366}
367
368impl ViturResponse for RetrieveKeyResponse {}
369
370/// A specification for retrieving a data key used in a [RetrieveKeyRequest].
371#[derive(Debug, Serialize, Deserialize, Clone)]
372pub struct RetrieveKeySpec<'a> {
373    #[serde(alias = "id")]
374    pub iv: KeyId,
375    // TODO: Make Descriptor Optional
376    pub descriptor: Cow<'a, str>,
377    pub tag: Cow<'a, [u8]>,
378
379    #[serde(default = "Vec::new")]
380    pub context: Vec<Context>,
381
382    // Since this field will be removed in the future allow older versions of Vitur to be able to
383    // parse a RetrieveKeySpec that doesn't include the tag_version.
384    #[serde(default)]
385    pub tag_version: usize,
386}
387
388impl<'a> RetrieveKeySpec<'a> {
389    const DEFAULT_TAG_VERSION: usize = 0;
390
391    pub fn new(id: KeyId, tag: &'a [u8], descriptor: &'a str) -> Self {
392        Self {
393            iv: id,
394            descriptor: Cow::from(descriptor),
395            tag: Cow::from(tag),
396            context: Vec::new(),
397            tag_version: Self::DEFAULT_TAG_VERSION,
398        }
399    }
400
401    pub fn with_context(mut self, context: Vec<Context>) -> Self {
402        self.context = context;
403        self
404    }
405}
406
407/// Request to retrieve a data key on behalf of a client in the given keyset.
408/// Requires the `data_key:retrieve` scope.
409/// Response is a [RetrieveKeyResponse].
410///
411/// See also [RetrieveKeySpec].
412#[derive(Debug, Serialize, Deserialize)]
413pub struct RetrieveKeyRequest<'a> {
414    pub client_id: Uuid,
415    #[serde(alias = "dataset_id")]
416    pub keyset_id: Option<IdentifiedBy>,
417    pub keys: Cow<'a, [RetrieveKeySpec<'a>]>,
418    #[serde(default)]
419    pub unverified_context: UnverifiedContext,
420}
421
422impl ViturRequest for RetrieveKeyRequest<'_> {
423    type Response = RetrieveKeyResponse;
424
425    const ENDPOINT: &'static str = "retrieve-data-key";
426    const SCOPE: &'static str = "data_key:retrieve";
427}
428
429/// Request to retrieve a data key on behalf of a client in the given keyset.
430/// Requires the `data_key:retrieve` scope.
431/// Response is a [RetrieveKeyResponse].
432///
433/// See also [RetrieveKeySpec].
434#[derive(Debug, Serialize, Deserialize)]
435pub struct RetrieveKeyRequestFallible<'a> {
436    pub client_id: Uuid,
437    #[serde(alias = "dataset_id")]
438    pub keyset_id: Option<IdentifiedBy>,
439    pub keys: Cow<'a, [RetrieveKeySpec<'a>]>,
440    #[serde(default)]
441    pub unverified_context: UnverifiedContext,
442}
443
444impl ViturRequest for RetrieveKeyRequestFallible<'_> {
445    type Response = RetrieveKeyResponseFallible;
446
447    const ENDPOINT: &'static str = "retrieve-data-key-fallible";
448    const SCOPE: &'static str = "data_key:retrieve";
449}
450
451/// Response to a [RetrieveKeyRequest] with per-key error handling
452#[derive(Debug, Serialize, Deserialize)]
453pub struct RetrieveKeyResponseFallible {
454    pub keys: Vec<Result<RetrievedKey, String>>, // TODO: Error?
455}
456
457impl ViturResponse for RetrieveKeyResponseFallible {}
458
459/// Request message to disable a keyset.
460/// Requires the `dataset:disable` scope.
461/// Response is an [EmptyResponse].
462#[derive(Debug, Serialize, Deserialize)]
463pub struct DisableKeysetRequest {
464    #[serde(alias = "dataset_id")]
465    pub keyset_id: IdentifiedBy,
466}
467
468impl ViturRequest for DisableKeysetRequest {
469    type Response = EmptyResponse;
470
471    const ENDPOINT: &'static str = "disable-keyset";
472    const SCOPE: &'static str = "dataset:disable";
473}
474
475/// Request message to enable a keyset that has was previously disabled.
476/// Requires the `dataset:enable` scope.
477/// Response is an [EmptyResponse].
478#[derive(Debug, Serialize, Deserialize)]
479pub struct EnableKeysetRequest {
480    #[serde(alias = "dataset_id")]
481    pub keyset_id: IdentifiedBy,
482}
483
484impl ViturRequest for EnableKeysetRequest {
485    type Response = EmptyResponse;
486
487    const ENDPOINT: &'static str = "enable-keyset";
488    const SCOPE: &'static str = "dataset:enable";
489}
490
491/// Request message to modify a keyset with the given keyset_id.
492/// `name` and `description` are optional and will be updated if provided.
493///
494/// Requires the `dataset:modify` scope.
495/// Response is an [EmptyResponse].
496#[derive(Debug, Serialize, Deserialize)]
497pub struct ModifyKeysetRequest<'a> {
498    #[serde(alias = "dataset_id")]
499    pub keyset_id: IdentifiedBy,
500
501    pub name: Option<Cow<'a, str>>,
502    pub description: Option<Cow<'a, str>>,
503}
504
505impl ViturRequest for ModifyKeysetRequest<'_> {
506    type Response = EmptyResponse;
507
508    const ENDPOINT: &'static str = "modify-keyset";
509    const SCOPE: &'static str = "dataset:modify";
510}
511
512/// Request message to grant a client access to a keyset.
513/// Requires the `dataset:grant` scope.
514///
515/// Response is an [EmptyResponse].
516#[derive(Debug, Serialize, Deserialize)]
517pub struct GrantKeysetRequest {
518    pub client_id: Uuid,
519    #[serde(alias = "dataset_id")]
520    pub keyset_id: IdentifiedBy,
521}
522
523impl ViturRequest for GrantKeysetRequest {
524    type Response = EmptyResponse;
525
526    const ENDPOINT: &'static str = "grant-keyset";
527    const SCOPE: &'static str = "dataset:grant";
528}
529
530/// Request message to revoke a client's access to a keyset.
531/// Requires the `dataset:revoke` scope.
532/// Response is an [EmptyResponse].
533#[derive(Debug, Serialize, Deserialize)]
534pub struct RevokeKeysetRequest {
535    pub client_id: Uuid,
536    #[serde(alias = "dataset_id")]
537    pub keyset_id: IdentifiedBy,
538}
539
540impl ViturRequest for RevokeKeysetRequest {
541    type Response = EmptyResponse;
542
543    const ENDPOINT: &'static str = "revoke-keyset";
544    const SCOPE: &'static str = "dataset:revoke";
545}
546
547/// Request to load a keyset on behalf of a client.
548/// This is used by clients before indexing or querying data and includes
549/// key material which can be derived by the client to generate encrypted index terms.
550///
551/// If a keyset_id is not provided the client's default keyset will be loaded.
552///
553/// Requires the `data_key:retrieve` scope (though this may change in the future).
554/// Response is a [LoadKeysetResponse].
555#[derive(Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
556pub struct LoadKeysetRequest {
557    pub client_id: Uuid,
558    #[serde(alias = "dataset_id")]
559    pub keyset_id: Option<IdentifiedBy>,
560}
561
562impl ViturRequest for LoadKeysetRequest {
563    type Response = LoadKeysetResponse;
564
565    const ENDPOINT: &'static str = "load-keyset";
566
567    // NOTE: We don't currently support the ability to allow an operation
568    // based on any one of several possible scopes so we'll just use `data_key:retrieve` for now.
569    // This should probably be allowed for any operation that requires indexing or querying.
570    const SCOPE: &'static str = "data_key:retrieve";
571}
572
573/// Response to a [LoadKeysetRequest].
574/// The response includes the key material required to derive data keys.
575/// It is analogous to a [RetrieveKeyResponse] but where the server generated the key.
576#[derive(Debug, Serialize, Deserialize)]
577pub struct LoadKeysetResponse {
578    pub partial_index_key: RetrievedKey,
579    #[serde(rename = "dataset")]
580    pub keyset: Keyset,
581}
582
583impl ViturResponse for LoadKeysetResponse {}
584
585#[cfg(test)]
586mod test {
587    use serde_json::json;
588    use uuid::Uuid;
589
590    use crate::{IdentifiedBy, LoadKeysetRequest, Name};
591
592    mod backwards_compatible_deserialisation {
593        use super::*;
594
595        #[test]
596        fn when_dataset_id_is_uuid() {
597            let client_id = Uuid::new_v4();
598            let dataset_id = Uuid::new_v4();
599
600            let json = json!({
601                "client_id": client_id,
602                "dataset_id": dataset_id,
603            });
604
605            let req: LoadKeysetRequest = serde_json::from_value(json).unwrap();
606
607            assert_eq!(
608                req,
609                LoadKeysetRequest {
610                    client_id,
611                    keyset_id: Some(IdentifiedBy::Uuid(dataset_id))
612                }
613            );
614        }
615
616        #[test]
617        fn when_keyset_id_is_uuid() {
618            let client_id = Uuid::new_v4();
619            let keyset_id = Uuid::new_v4();
620
621            let json = json!({
622                "client_id": client_id,
623                "keyset_id": keyset_id,
624            });
625
626            let req: LoadKeysetRequest = serde_json::from_value(json).unwrap();
627
628            assert_eq!(
629                req,
630                LoadKeysetRequest {
631                    client_id,
632                    keyset_id: Some(IdentifiedBy::Uuid(keyset_id))
633                }
634            );
635        }
636
637        #[test]
638        fn when_dataset_id_is_id_name() {
639            let client_id = Uuid::new_v4();
640            let dataset_id = IdentifiedBy::Name(Name(String::from("some-dataset-name")));
641
642            let json = json!({
643                "client_id": client_id,
644                "dataset_id": dataset_id,
645            });
646
647            let req: LoadKeysetRequest = serde_json::from_value(json).unwrap();
648
649            assert_eq!(
650                req,
651                LoadKeysetRequest {
652                    client_id,
653                    keyset_id: Some(dataset_id)
654                }
655            );
656        }
657    }
658}