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;
24pub 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#[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#[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#[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#[derive(Default, Debug, Serialize, Deserialize)]
98pub struct EmptyResponse {}
99
100impl ViturResponse for EmptyResponse {}
101
102#[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#[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#[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#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
154#[serde(untagged)]
155pub enum ClientKeysetId {
156 Single(Uuid),
157 Multiple(Vec<Uuid>),
158}
159
160impl 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#[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#[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#[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#[derive(Debug, Serialize, Deserialize)]
255pub struct GeneratedKey {
256 pub key_material: ViturKeyMaterial,
257 #[serde(with = "base64_vec")]
259 pub tag: Vec<u8>,
260}
261
262#[derive(Debug, Serialize, Deserialize)]
264pub struct GenerateKeyResponse {
265 pub keys: Vec<GeneratedKey>,
266}
267
268impl ViturResponse for GenerateKeyResponse {}
269
270#[derive(Debug, Serialize, Deserialize, Clone)]
272pub struct GenerateKeySpec<'a> {
273 #[serde(alias = "id")]
275 pub iv: KeyId,
276 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#[derive(Debug, Serialize, Deserialize, Clone)]
304pub enum Context {
306 Tag(String),
308
309 Value(String, String),
312
313 #[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#[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#[derive(Debug, Serialize, Deserialize)]
361pub struct RetrievedKey {
362 pub key_material: ViturKeyMaterial,
363}
364
365#[derive(Debug, Serialize, Deserialize)]
368pub struct RetrieveKeyResponse {
369 pub keys: Vec<RetrievedKey>,
370}
371
372impl ViturResponse for RetrieveKeyResponse {}
373
374#[derive(Debug, Serialize, Deserialize, Clone)]
376pub struct RetrieveKeySpec<'a> {
377 #[serde(alias = "id")]
378 pub iv: KeyId,
379 pub descriptor: Cow<'a, str>,
381 pub tag: Cow<'a, [u8]>,
382
383 #[serde(default = "Vec::new")]
384 pub context: Vec<Context>,
385
386 #[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#[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#[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#[derive(Debug, Serialize, Deserialize)]
457pub struct RetrieveKeyResponseFallible {
458 pub keys: Vec<Result<RetrievedKey, String>>, }
460
461impl ViturResponse for RetrieveKeyResponseFallible {}
462
463#[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#[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#[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#[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#[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#[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 const SCOPE: Scope = Scope::with_permission(Permission::DataKey(DataKeyPermission::Retrieve));
575}
576
577#[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}