Skip to main content

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