zerokms_protocol/
lib.rs

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