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 = "Vec::new")]
280    pub context: Vec<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: Vec::new(),
289        }
290    }
291
292    pub fn new_with_context(iv: [u8; 16], descriptor: &'a str, context: Vec<Context>) -> Self {
293        Self {
294            iv: KeyId(iv),
295            descriptor: Cow::from(descriptor),
296            context,
297        }
298    }
299}
300/// Represents a contextual attribute for a data key which is used to "lock" the key to a specific context.
301/// Context attributes are included key tag generation which is in turn used as AAD in the final encryption step in the client.
302/// Context attributes should _never_ include any sensitive information.
303#[derive(Debug, Serialize, Deserialize, Clone)]
304// TODO: Use Cow?
305pub enum Context {
306    /// A tag that can be used to identify the key.
307    Tag(String),
308
309    /// A key-value pair that can be used to identify the key.
310    /// For example, a key-value pair could be `("user_id", "1234")`.
311    Value(String, String),
312
313    /// A claim from the identity of the principal that is requesting the key.
314    /// The claim value is read from the claims list after token verification and prior to key generation.
315    ///
316    /// For example, a claim could be `"sub"`.
317    #[serde(alias = "identityClaim")]
318    IdentityClaim(String),
319}
320
321impl Context {
322    pub fn new_tag(tag: impl Into<String>) -> Self {
323        Self::Tag(tag.into())
324    }
325
326    pub fn new_value(key: impl Into<String>, value: impl Into<String>) -> Self {
327        Self::Value(key.into(), value.into())
328    }
329
330    pub fn new_identity_claim(claim: &str) -> Self {
331        Self::IdentityClaim(claim.to_string())
332    }
333}
334
335/// A request message to generate a data key made on behalf of a client
336/// in the given keyset.
337///
338/// Requires the `data_key:generate` scope.
339/// Response is a [GenerateKeyResponse].
340///
341/// See also [GenerateKeySpec].
342#[derive(Debug, Serialize, Deserialize)]
343pub struct GenerateKeyRequest<'a> {
344    pub client_id: Uuid,
345    #[serde(alias = "dataset_id")]
346    pub keyset_id: Option<IdentifiedBy>,
347    pub keys: Cow<'a, [GenerateKeySpec<'a>]>,
348    #[serde(default)]
349    pub unverified_context: UnverifiedContext,
350}
351
352impl ViturRequest for GenerateKeyRequest<'_> {
353    type Response = GenerateKeyResponse;
354
355    const ENDPOINT: &'static str = "generate-data-key";
356    const SCOPE: Scope = Scope::with_permission(Permission::DataKey(DataKeyPermission::Generate));
357}
358
359/// Returned type from a [RetrieveKeyRequest].
360#[derive(Debug, Serialize, Deserialize)]
361pub struct RetrievedKey {
362    pub key_material: ViturKeyMaterial,
363}
364
365/// Response to a [RetrieveKeyRequest].
366/// Contains a list of [RetrievedKey]s.
367#[derive(Debug, Serialize, Deserialize)]
368pub struct RetrieveKeyResponse {
369    pub keys: Vec<RetrievedKey>,
370}
371
372impl ViturResponse for RetrieveKeyResponse {}
373
374/// A specification for retrieving a data key used in a [RetrieveKeyRequest].
375#[derive(Debug, Serialize, Deserialize, Clone)]
376pub struct RetrieveKeySpec<'a> {
377    #[serde(alias = "id")]
378    pub iv: KeyId,
379    // TODO: Make Descriptor Optional
380    pub descriptor: Cow<'a, str>,
381    pub tag: Cow<'a, [u8]>,
382
383    #[serde(default = "Vec::new")]
384    pub context: Vec<Context>,
385
386    // Since this field will be removed in the future allow older versions of Vitur to be able to
387    // parse a RetrieveKeySpec that doesn't include the tag_version.
388    #[serde(default)]
389    pub tag_version: usize,
390}
391
392impl<'a> RetrieveKeySpec<'a> {
393    const DEFAULT_TAG_VERSION: usize = 0;
394
395    pub fn new(id: KeyId, tag: &'a [u8], descriptor: &'a str) -> Self {
396        Self {
397            iv: id,
398            descriptor: Cow::from(descriptor),
399            tag: Cow::from(tag),
400            context: Vec::new(),
401            tag_version: Self::DEFAULT_TAG_VERSION,
402        }
403    }
404
405    pub fn with_context(mut self, context: Vec<Context>) -> Self {
406        self.context = context;
407        self
408    }
409}
410
411/// Request to retrieve a data key on behalf of a client in the given keyset.
412/// Requires the `data_key:retrieve` scope.
413/// Response is a [RetrieveKeyResponse].
414///
415/// See also [RetrieveKeySpec].
416#[derive(Debug, Serialize, Deserialize)]
417pub struct RetrieveKeyRequest<'a> {
418    pub client_id: Uuid,
419    #[serde(alias = "dataset_id")]
420    pub keyset_id: Option<IdentifiedBy>,
421    pub keys: Cow<'a, [RetrieveKeySpec<'a>]>,
422    #[serde(default)]
423    pub unverified_context: UnverifiedContext,
424}
425
426impl ViturRequest for RetrieveKeyRequest<'_> {
427    type Response = RetrieveKeyResponse;
428
429    const ENDPOINT: &'static str = "retrieve-data-key";
430    const SCOPE: Scope = Scope::with_permission(Permission::DataKey(DataKeyPermission::Retrieve));
431}
432
433/// Request to retrieve a data key on behalf of a client in the given keyset.
434/// Requires the `data_key:retrieve` scope.
435/// Response is a [RetrieveKeyResponse].
436///
437/// See also [RetrieveKeySpec].
438#[derive(Debug, Serialize, Deserialize)]
439pub struct RetrieveKeyRequestFallible<'a> {
440    pub client_id: Uuid,
441    #[serde(alias = "dataset_id")]
442    pub keyset_id: Option<IdentifiedBy>,
443    pub keys: Cow<'a, [RetrieveKeySpec<'a>]>,
444    #[serde(default)]
445    pub unverified_context: UnverifiedContext,
446}
447
448impl ViturRequest for RetrieveKeyRequestFallible<'_> {
449    type Response = RetrieveKeyResponseFallible;
450
451    const ENDPOINT: &'static str = "retrieve-data-key-fallible";
452    const SCOPE: Scope = Scope::with_permission(Permission::DataKey(DataKeyPermission::Retrieve));
453}
454
455/// Response to a [RetrieveKeyRequest] with per-key error handling
456#[derive(Debug, Serialize, Deserialize)]
457pub struct RetrieveKeyResponseFallible {
458    pub keys: Vec<Result<RetrievedKey, String>>, // TODO: Error?
459}
460
461impl ViturResponse for RetrieveKeyResponseFallible {}
462
463/// Request message to disable a keyset.
464/// Requires the `dataset:disable` scope.
465/// Response is an [EmptyResponse].
466#[derive(Debug, Serialize, Deserialize)]
467pub struct DisableKeysetRequest {
468    #[serde(alias = "dataset_id")]
469    pub keyset_id: IdentifiedBy,
470}
471
472impl ViturRequest for DisableKeysetRequest {
473    type Response = EmptyResponse;
474
475    const ENDPOINT: &'static str = "disable-keyset";
476    const SCOPE: Scope = Scope::with_permission(Permission::Keyset(KeysetPermission::Disable));
477}
478
479/// Request message to enable a keyset that has was previously disabled.
480/// Requires the `dataset:enable` scope.
481/// Response is an [EmptyResponse].
482#[derive(Debug, Serialize, Deserialize)]
483pub struct EnableKeysetRequest {
484    #[serde(alias = "dataset_id")]
485    pub keyset_id: IdentifiedBy,
486}
487
488impl ViturRequest for EnableKeysetRequest {
489    type Response = EmptyResponse;
490
491    const ENDPOINT: &'static str = "enable-keyset";
492    const SCOPE: Scope = Scope::with_permission(Permission::Keyset(KeysetPermission::Enable));
493}
494
495/// Request message to modify a keyset with the given keyset_id.
496/// `name` and `description` are optional and will be updated if provided.
497///
498/// Requires the `dataset:modify` scope.
499/// Response is an [EmptyResponse].
500#[derive(Debug, Serialize, Deserialize)]
501pub struct ModifyKeysetRequest<'a> {
502    #[serde(alias = "dataset_id")]
503    pub keyset_id: IdentifiedBy,
504
505    pub name: Option<Cow<'a, str>>,
506    pub description: Option<Cow<'a, str>>,
507}
508
509impl ViturRequest for ModifyKeysetRequest<'_> {
510    type Response = EmptyResponse;
511
512    const ENDPOINT: &'static str = "modify-keyset";
513    const SCOPE: Scope = Scope::with_permission(Permission::Keyset(KeysetPermission::Modify));
514}
515
516/// Request message to grant a client access to a keyset.
517/// Requires the `dataset:grant` scope.
518///
519/// Response is an [EmptyResponse].
520#[derive(Debug, Serialize, Deserialize)]
521pub struct GrantKeysetRequest {
522    pub client_id: Uuid,
523    #[serde(alias = "dataset_id")]
524    pub keyset_id: IdentifiedBy,
525}
526
527impl ViturRequest for GrantKeysetRequest {
528    type Response = EmptyResponse;
529
530    const ENDPOINT: &'static str = "grant-keyset";
531    const SCOPE: Scope = Scope::with_permission(Permission::Keyset(KeysetPermission::Grant));
532}
533
534/// Request message to revoke a client's access to a keyset.
535/// Requires the `dataset:revoke` scope.
536/// Response is an [EmptyResponse].
537#[derive(Debug, Serialize, Deserialize)]
538pub struct RevokeKeysetRequest {
539    pub client_id: Uuid,
540    #[serde(alias = "dataset_id")]
541    pub keyset_id: IdentifiedBy,
542}
543
544impl ViturRequest for RevokeKeysetRequest {
545    type Response = EmptyResponse;
546
547    const ENDPOINT: &'static str = "revoke-keyset";
548    const SCOPE: Scope = Scope::with_permission(Permission::Keyset(KeysetPermission::Revoke));
549}
550
551/// Request to load a keyset on behalf of a client.
552/// This is used by clients before indexing or querying data and includes
553/// key material which can be derived by the client to generate encrypted index terms.
554///
555/// If a keyset_id is not provided the client's default keyset will be loaded.
556///
557/// Requires the `data_key:retrieve` scope (though this may change in the future).
558/// Response is a [LoadKeysetResponse].
559#[derive(Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
560pub struct LoadKeysetRequest {
561    pub client_id: Uuid,
562    #[serde(alias = "dataset_id")]
563    pub keyset_id: Option<IdentifiedBy>,
564}
565
566impl ViturRequest for LoadKeysetRequest {
567    type Response = LoadKeysetResponse;
568
569    const ENDPOINT: &'static str = "load-keyset";
570
571    // NOTE: We don't currently support the ability to allow an operation
572    // based on any one of several possible scopes so we'll just use `data_key:retrieve` for now.
573    // This should probably be allowed for any operation that requires indexing or querying.
574    const SCOPE: Scope = Scope::with_permission(Permission::DataKey(DataKeyPermission::Retrieve));
575}
576
577/// Response to a [LoadKeysetRequest].
578/// The response includes the key material required to derive data keys.
579/// It is analogous to a [RetrieveKeyResponse] but where the server generated the key.
580#[derive(Debug, Serialize, Deserialize)]
581pub struct LoadKeysetResponse {
582    pub partial_index_key: RetrievedKey,
583    #[serde(rename = "dataset")]
584    pub keyset: Keyset,
585}
586
587impl ViturResponse for LoadKeysetResponse {}
588
589#[cfg(test)]
590mod test {
591    use serde_json::json;
592    use uuid::Uuid;
593
594    use crate::{IdentifiedBy, LoadKeysetRequest, Name};
595
596    mod backwards_compatible_deserialisation {
597        use super::*;
598
599        #[test]
600        fn when_dataset_id_is_uuid() {
601            let client_id = Uuid::new_v4();
602            let dataset_id = Uuid::new_v4();
603
604            let json = json!({
605                "client_id": client_id,
606                "dataset_id": dataset_id,
607            });
608
609            let req: LoadKeysetRequest = serde_json::from_value(json).unwrap();
610
611            assert_eq!(
612                req,
613                LoadKeysetRequest {
614                    client_id,
615                    keyset_id: Some(IdentifiedBy::Uuid(dataset_id))
616                }
617            );
618        }
619
620        #[test]
621        fn when_keyset_id_is_uuid() {
622            let client_id = Uuid::new_v4();
623            let keyset_id = Uuid::new_v4();
624
625            let json = json!({
626                "client_id": client_id,
627                "keyset_id": keyset_id,
628            });
629
630            let req: LoadKeysetRequest = serde_json::from_value(json).unwrap();
631
632            assert_eq!(
633                req,
634                LoadKeysetRequest {
635                    client_id,
636                    keyset_id: Some(IdentifiedBy::Uuid(keyset_id))
637                }
638            );
639        }
640
641        #[test]
642        fn when_dataset_id_is_id_name() {
643            let client_id = Uuid::new_v4();
644            let dataset_id = IdentifiedBy::Name(Name::new_untrusted("some-dataset-name"));
645
646            let json = json!({
647                "client_id": client_id,
648                "dataset_id": dataset_id,
649            });
650
651            let req: LoadKeysetRequest = serde_json::from_value(json).unwrap();
652
653            assert_eq!(
654                req,
655                LoadKeysetRequest {
656                    client_id,
657                    keyset_id: Some(dataset_id)
658                }
659            );
660        }
661    }
662}