1use std::collections::{BTreeMap, HashMap};
19use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
20use std::sync::{Arc, Mutex, MutexGuard, RwLock};
21
22use crate::client::{Answer, Api, ApiError, Auth, Pre, Progress, now};
23use crate::keys::{
24 Bundle, ConfigSecret, FullBundle, NodeKey, OwnerKeys, TokenKeys, VaultSecret, WriterKey,
25 seal_child_key, seal_for_scope, seal_owner_bundle,
26};
27use crate::proto::api::{
28 ChallengePurpose, CreateVaultRequest, ErrorCode, RegisterTokenRequest, RegisterTokenResponse,
29 ReportTokenRequest, RevokeResponse, Scope, SecretVersion, TokenSelf, VaultStatus, VersionMeta,
30};
31use crate::proto::audit::{AuditRow, ChainHead, RowStop, verify_chain, verify_rows};
32use crate::proto::children::{ChildEntry, ChildMode, ChildrenRecord};
33use crate::proto::descriptor::{Descriptor, SignedDescriptor};
34use crate::proto::ids::{B64, Hash32, Key32, NameHmac, TokenId, VaultId};
35use crate::proto::integrity::IntegrityError;
36use crate::proto::path::Segment;
37use crate::proto::pow;
38use crate::proto::record::RecordKind;
39use crate::seal::{
40 ConfigFormat, Opened, OpenedConfig, OpenedRecord, Writer, WrittenRecord, build_rotation,
41 open_config_record, open_name, open_secret, verify_listed, verify_meta,
42};
43use serde::{Deserialize, Serialize};
44
45use crate::error::Error;
46
47pub(crate) const DAY: i64 = 86_400;
48
49pub const EXPIRY_WARNING_SECS: i64 = 14 * DAY;
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
55pub struct DescriptorPin {
56 pub generation: u32,
58 pub hash: Hash32,
60}
61
62#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
67pub struct Pins {
68 #[serde(default)]
70 pub descriptor: Option<DescriptorPin>,
71 #[serde(default)]
73 pub secrets: BTreeMap<NameHmac, u64>,
74 #[serde(default)]
76 pub configs: BTreeMap<NameHmac, u64>,
77}
78
79impl Pins {
80 fn versions(&mut self, kind: RecordKind) -> &mut BTreeMap<NameHmac, u64> {
81 match kind {
82 RecordKind::Secret => &mut self.secrets,
83 RecordKind::Config => &mut self.configs,
84 }
86 }
87}
88
89pub(crate) enum Holder {
90 Owner(Box<OwnerKeys>),
91 Token(Box<TokenKeys>),
92}
93
94#[derive(Debug, Clone)]
96pub(crate) struct Item {
97 pub name: String,
98 pub hmac: NameHmac,
99 pub version: u64,
100 pub updated_at: i64,
102 pub size: u32,
104 pub tombstone: bool,
105}
106
107pub(crate) struct View {
110 pub descriptor: Descriptor,
111 pub bundle: Bundle,
112 pub scope: Option<Scope>,
114 pub allow_list: Option<Vec<NameHmac>>,
116 pub token_expires_at: Option<i64>,
118 pub status: Option<VaultStatus>,
120}
121
122pub(crate) struct RawAudit {
124 pub rows: Vec<AuditRow>,
127 pub new_rows: usize,
129 pub head: Option<ChainHead>,
131 pub names: HashMap<NameHmac, String>,
133 pub unverifiable: Option<RowStop>,
135}
136
137fn noun(kind: RecordKind) -> &'static str {
138 match kind {
139 RecordKind::Secret => "secret",
140 RecordKind::Config => "config",
141 }
142}
143
144fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
145 m.lock().unwrap_or_else(|p| p.into_inner())
146}
147
148pub(crate) fn short(h: &NameHmac) -> String {
149 h.to_hex()[..8].to_owned()
150}
151
152pub(crate) struct Handle {
153 api: Api,
154 holder: Holder,
155 pub label: String,
157 pub vault_id: VaultId,
159 pub owner_sign_pub: Key32,
161 view: RwLock<Arc<View>>,
162 warned: AtomicBool,
163 last_expires_at: AtomicI64,
165 pins: Mutex<Pins>,
166}
167
168fn owner_error(e: ApiError, label: &str) -> Error {
171 if e.code() == Some(ErrorCode::Unauthorized) {
172 Error::VaultNotFound {
173 label: label.to_owned(),
174 message: format!(
175 "{label}: the server does not know this vault (it expired, was deleted, or was re-rooted)"
176 ),
177 }
178 } else {
179 Error::api(e, Some(label))
180 }
181}
182
183fn token_refused(e: ApiError) -> Error {
185 let message = match e.code() {
186 Some(ErrorCode::Unauthorized) => {
187 "the token was not accepted: it is unknown, revoked, or its vault has expired"
188 }
189 Some(ErrorCode::TokenExpired) => "the token has expired",
190 _ => return Error::api(e, None),
191 };
192 Error::Auth {
193 code: e.stable_code(),
194 message: message.to_owned(),
195 }
196}
197
198fn verify_descriptor(
206 signed: &SignedDescriptor,
207 vault_id: &VaultId,
208 owner_sign_pub: &Key32,
209) -> Result<Descriptor, IntegrityError> {
210 #[cfg(all(feature = "adversary-plant", debug_assertions))]
211 if plant::skips_descriptor_signature() {
212 return plant::descriptor_without_signature(signed, vault_id, owner_sign_pub);
213 }
214 signed.verify_for(vault_id, owner_sign_pub)
215}
216
217#[cfg(all(feature = "adversary-plant", not(debug_assertions)))]
225compile_error!(
226 "galata-vault's `adversary-plant` feature weakens verification for the adversarial \
227 suite's self-test and cannot be built without debug assertions (a release build)"
228);
229
230#[cfg(all(feature = "adversary-plant", debug_assertions))]
231mod plant {
232 use crate::proto::descriptor::{Descriptor, SignedDescriptor};
233 use crate::proto::ids::{Key32, VaultId};
234 use crate::proto::integrity::IntegrityError;
235
236 pub(super) const VAR: &str = "GV_ADVERSARY_PLANT";
238 pub(super) const SKIP_DESCRIPTOR_SIGNATURE: &str = "skip-descriptor-signature";
239
240 pub(super) fn skips_descriptor_signature() -> bool {
241 std::env::var(VAR).is_ok_and(|v| v == SKIP_DESCRIPTOR_SIGNATURE)
242 }
243
244 pub(super) fn descriptor_without_signature(
247 signed: &SignedDescriptor,
248 vault_id: &VaultId,
249 owner_sign_pub: &Key32,
250 ) -> Result<Descriptor, IntegrityError> {
251 if VaultId::from_owner_sign_pub(owner_sign_pub) != *vault_id {
252 return Err(IntegrityError::KeyMismatch("owner signing key"));
253 }
254 let descriptor = signed.decode_unverified()?;
255 if descriptor.vault_id != *vault_id {
256 return Err(IntegrityError::KeyMismatch("descriptor's vault id"));
257 }
258 Ok(descriptor)
259 }
260}
261
262fn owner_view(owner: &OwnerKeys, status: VaultStatus, label: &str) -> Result<View, Error> {
266 let fail = |e| Error::integrity(label, e);
267 if status.vault_id != owner.vault_id() {
268 return Err(fail(IntegrityError::KeyMismatch("vault id")));
269 }
270 if status.owner_sign_pub != owner.sign_pub() {
271 return Err(fail(IntegrityError::KeyMismatch("owner signing key")));
272 }
273 if status.owner_box_pub != owner.box_pub() {
274 return Err(fail(IntegrityError::KeyMismatch("owner box key")));
275 }
276 let descriptor = verify_descriptor(&status.descriptor, &owner.vault_id(), &owner.sign_pub())
277 .map_err(fail)?;
278 if descriptor.generation != status.generation {
279 return Err(fail(IntegrityError::BindingMismatch("generation")));
280 }
281 let Some(signed) = status.owner_bundle.as_ref() else {
282 return Err(Error::other("the server sent no owner bundle"));
283 };
284 let full = owner
285 .open_own_bundle(signed, descriptor.generation)
286 .map_err(|e| Error::key(e, format!("{label}: the owner bundle")))?;
287 if !full.matches(&descriptor) {
288 return Err(fail(IntegrityError::KeyMismatch("owner bundle")));
289 }
290 Ok(View {
291 descriptor,
292 bundle: Bundle::Full(full),
293 scope: None,
294 allow_list: None,
295 token_expires_at: None,
296 status: Some(status),
297 })
298}
299
300fn token_view(token: &TokenKeys, me: TokenSelf, label: &str) -> Result<(View, Key32), Error> {
304 if me.vault_id != token.vault_id() {
305 return Err(Error::integrity(
306 label,
307 IntegrityError::KeyMismatch("vault id"),
308 ));
309 }
310 if me.token_id != token.id() {
311 return Err(Error::integrity(
312 label,
313 IntegrityError::KeyMismatch("token id"),
314 ));
315 }
316 let scope = me.scope.get().ok_or_else(|| {
319 Error::unsupported(format!(
320 "{label}: this token has scope {:?}, which this client does not know",
321 me.scope.unknown().unwrap_or("?")
322 ))
323 })?;
324 let descriptor = verify_descriptor(&me.descriptor, &token.vault_id(), &me.owner_sign_pub)
325 .map_err(|e| Error::integrity(label, e))?;
326 let bundle = token
327 .open_bundle(&me.bundle, &me.owner_sign_pub, scope, descriptor.generation)
328 .map_err(|e| Error::key(e, format!("{label}: the token's bundle")))?;
329 if !bundle.matches(&descriptor) {
330 return Err(Error::integrity(
331 label,
332 IntegrityError::KeyMismatch("token bundle"),
333 ));
334 }
335 Ok((
336 View {
337 descriptor,
338 bundle,
339 scope: Some(scope),
340 allow_list: me.allow_list,
341 token_expires_at: Some(me.expires_at),
342 status: None,
343 },
344 me.owner_sign_pub,
345 ))
346}
347
348fn head_before(rows: &[AuditRow]) -> Option<ChainHead> {
350 rows.first()
351 .filter(|r| r.seq > 1)
352 .map(|r| ChainHead::new(r.seq - 1, r.prev))
353}
354
355fn children_error(e: impl std::fmt::Display) -> Error {
356 Error::other(e.to_string())
357}
358
359type AuditRows = (Vec<AuditRow>, Option<ChainHead>, Option<RowStop>);
362
363impl Handle {
364 fn new(api: &Api, holder: Holder, label: &str, view: View, owner_sign_pub: Key32) -> Handle {
365 let pins = Pins {
366 descriptor: Some(DescriptorPin {
367 generation: view.descriptor.generation,
368 hash: view.descriptor.hash(),
369 }),
370 ..Pins::default()
371 };
372 Handle {
373 api: api.clone(),
374 holder,
375 label: label.to_owned(),
376 vault_id: view.descriptor.vault_id,
377 owner_sign_pub,
378 view: RwLock::new(Arc::new(view)),
379 warned: AtomicBool::new(false),
380 last_expires_at: AtomicI64::new(i64::MIN),
381 pins: Mutex::new(pins),
382 }
383 }
384
385 pub fn open_owner(api: &Api, key: &NodeKey, label: &str) -> Result<Handle, Error> {
389 api.require_protocol()
390 .map_err(|e| Error::api(e, Some(label)))?;
391 let owner = key.owner();
392 let answer = api
393 .status(Auth::Owner(&owner))
394 .map_err(|e| owner_error(e, label))?;
395 let view = owner_view(&owner, answer.value, label)?;
396 let sign_pub = owner.sign_pub();
397 let handle = Handle::new(api, Holder::Owner(Box::new(owner)), label, view, sign_pub);
398 handle.note_expiry(answer.expires_at);
399 Ok(handle)
400 }
401
402 pub fn open_token(api: &Api, token: TokenKeys, label: &str) -> Result<Handle, Error> {
405 api.require_protocol()
406 .map_err(|e| Error::api(e, Some(label)))?;
407 let answer = api.token_self(&token).map_err(token_refused)?;
408 let (view, sign_pub) = token_view(&token, answer.value, label)?;
409 let handle = Handle::new(api, Holder::Token(Box::new(token)), label, view, sign_pub);
410 handle.note_expiry(answer.expires_at);
411 Ok(handle)
412 }
413
414 pub fn create(api: &Api, key: &NodeKey, label: &str) -> Result<bool, Error> {
418 Handle::create_with_keys(api, key, label, &FullBundle::generate(1))
419 }
420
421 pub fn create_with_keys(
424 api: &Api,
425 key: &NodeKey,
426 label: &str,
427 full: &FullBundle,
428 ) -> Result<bool, Error> {
429 if full.generation != 1 {
430 return Err(Error::other(format!(
431 "{label}: a new vault starts at generation 1"
432 )));
433 }
434 let owner = key.owner();
435 let wants_proof = api
438 .capabilities()
439 .map_err(|e| Error::api(e, None))?
440 .is_none_or(|c| c.proof_of_work.is_some());
441 api.require_protocol()
444 .map_err(|e| Error::api(e, Some(label)))?;
445 let (challenge, nonce) = if wants_proof {
446 let c = api
447 .challenge(ChallengePurpose::CreateVault)
448 .map_err(|e| Error::api(e, None))?
449 .value;
450 if c.difficulty > 16 {
451 api.events().progress(&Progress::ProofOfWork {
452 label: label.to_owned(),
453 difficulty: c.difficulty,
454 });
455 }
456 let nonce = pow::solve(&c.challenge, c.difficulty).map_err(|e| {
457 Error::other(format!(
458 "{label}: the server's proof-of-work challenge (difficulty {}): {e}",
459 c.difficulty
460 ))
461 })?;
462 (Some(c.challenge), Some(nonce))
463 } else {
464 (None, None)
465 };
466 let descriptor = full.descriptor(owner.vault_id(), Hash32([0; 32]), now());
467 let owner_bundle = seal_owner_bundle(&owner, full)
468 .map_err(|e| Error::key(e, format!("{label}: the owner bundle")))?;
469 let request = CreateVaultRequest::new(
470 owner.vault_id(),
471 owner.sign_pub(),
472 owner.box_pub(),
473 owner.sign_descriptor(&descriptor),
474 owner_bundle,
475 )
476 .with_proof(challenge, nonce);
477 match api.create_vault(&owner, &request) {
478 Ok(_) => {}
479 Err(e) if e.code() == Some(ErrorCode::Conflict) => return Ok(false),
480 Err(e) => {
481 return Err(Error::api(e, None).context(format!("creating the vault for {label}")));
482 }
483 }
484 let vault = Handle::open_owner(api, key, label)?;
485 match vault.put_children(&ChildrenRecord::new(), Pre::Create) {
486 Ok(_) | Err(Error::Conflict { .. }) => Ok(true),
487 Err(e) => Err(e.context(format!("{label}: the children record"))),
488 }
489 }
490
491 pub fn report_token(api: &Api, token: &TokenKeys) -> Result<RevokeResponse, Error> {
494 let ts = now();
495 let request = ReportTokenRequest::new(token.id(), ts, token.sign_report(ts));
496 Ok(api
497 .report_token(&request)
498 .map_err(|e| Error::api(e, None))?
499 .value)
500 }
501
502 pub fn api(&self) -> &Api {
503 &self.api
504 }
505
506 pub fn view(&self) -> Arc<View> {
508 self.view.read().unwrap_or_else(|p| p.into_inner()).clone()
509 }
510
511 fn auth(&self) -> Auth<'_> {
512 match &self.holder {
513 Holder::Owner(o) => Auth::Owner(o),
514 Holder::Token(t) => Auth::Token(t),
515 }
516 }
517
518 pub fn owner(&self) -> Result<&OwnerKeys, Error> {
519 match &self.holder {
520 Holder::Owner(o) => Ok(o.as_ref()),
521 Holder::Token(_) => Err(Error::forbidden(format!(
522 "{}: this needs the owner key, not a token",
523 self.label
524 ))),
525 }
526 }
527
528 pub fn scope(&self) -> Option<Scope> {
530 self.view().scope
531 }
532
533 pub fn status(&self) -> Option<VaultStatus> {
535 self.view().status.clone()
536 }
537
538 pub fn last_expires_at(&self) -> Option<i64> {
540 let at = self.last_expires_at.load(Ordering::Relaxed);
541 (at != i64::MIN).then_some(at)
542 }
543
544 pub fn token_expires_at(&self) -> Option<i64> {
546 self.view().token_expires_at
547 }
548
549 fn note_expiry(&self, expires_at: Option<i64>) {
550 let Some(at) = expires_at else { return };
551 self.last_expires_at.store(at, Ordering::Relaxed);
552 if at - now() < EXPIRY_WARNING_SECS && !self.warned.swap(true, Ordering::Relaxed) {
553 self.api.events().expiry(&self.label, at);
554 }
555 }
556
557 fn take<T>(&self, answer: Answer<T>) -> T {
558 self.note_expiry(answer.expires_at);
559 answer.value
560 }
561
562 fn server_err(&self, e: ApiError) -> Error {
564 Error::api(e, Some(&self.label))
565 }
566
567 fn integrity(&self, e: IntegrityError) -> Error {
568 Error::integrity(&self.label, e)
569 }
570
571 pub fn may_read(&self, v: &View, name: &NameHmac) -> bool {
573 v.allow_list.as_ref().is_none_or(|l| l.contains(name))
574 }
575
576 pub fn hmac(&self, name: &str) -> NameHmac {
578 self.view().bundle.name_key().hmac(name)
579 }
580
581 #[cfg(feature = "test-util")]
582 pub fn name_key(&self) -> crate::keys::NameKey {
583 self.view().bundle.name_key().clone()
584 }
585
586 pub fn full<'v>(&self, v: &'v View) -> Result<&'v FullBundle, Error> {
588 match &v.bundle {
589 Bundle::Full(f) => Ok(f),
590 _ => Err(Error::forbidden(format!(
591 "{}: this needs every key of the vault (the owner, or an admin token)",
592 self.label
593 ))),
594 }
595 }
596
597 pub fn vault_secret<'v>(&self, v: &'v View) -> Result<&'v VaultSecret, Error> {
599 v.bundle.vault_secret().ok_or_else(|| {
600 Error::forbidden(format!(
601 "{}: this credential can list names but cannot decrypt secrets",
602 self.label
603 ))
604 })
605 }
606
607 pub fn config_secret<'v>(&self, v: &'v View) -> Result<&'v ConfigSecret, Error> {
609 v.bundle.config_secret().ok_or_else(|| {
610 Error::forbidden(format!(
611 "{}: this credential cannot read configs",
612 self.label
613 ))
614 })
615 }
616
617 fn writer<'v>(&self, v: &'v View, kind: RecordKind) -> Result<&'v WriterKey, Error> {
618 match kind {
619 RecordKind::Secret => v.bundle.secret_writer(),
620 RecordKind::Config => v.bundle.config_writer(),
621 }
622 .ok_or_else(|| {
623 Error::forbidden(format!(
624 "{}: this credential may not write {}s",
625 self.label,
626 noun(kind)
627 ))
628 })
629 }
630
631 pub fn pins(&self) -> Pins {
636 lock(&self.pins).clone()
637 }
638
639 pub fn set_pins(&self, pins: Pins) -> Result<(), Error> {
644 if let Some(pin) = &pins.descriptor {
645 self.check_descriptor_pin(pin, &self.view().descriptor)?;
646 }
647 let mut mine = lock(&self.pins);
648 let current = mine.descriptor;
649 *mine = pins;
650 mine.descriptor = current;
651 Ok(())
652 }
653
654 fn check_descriptor_pin(&self, pin: &DescriptorPin, d: &Descriptor) -> Result<(), Error> {
655 let rollback = || {
656 self.integrity(IntegrityError::GenerationRollback {
657 known: pin.generation,
658 got: d.generation,
659 })
660 };
661 if d.generation < pin.generation {
662 return Err(rollback());
663 }
664 if d.generation == pin.generation {
665 return if d.hash() == pin.hash {
666 Ok(())
667 } else {
668 Err(rollback())
669 };
670 }
671 let (mut generation, mut hash) = (pin.generation, pin.hash);
672 for next in self.descriptors_after(pin.generation)? {
673 if next.generation > d.generation {
674 break;
675 }
676 if next.generation != generation + 1 || next.prev_hash != hash {
677 return Err(self.integrity(IntegrityError::BindingMismatch("descriptor chain")));
678 }
679 (generation, hash) = (next.generation, next.hash());
680 }
681 if generation != d.generation || hash != d.hash() {
682 return Err(self.integrity(IntegrityError::BindingMismatch("descriptor chain")));
683 }
684 Ok(())
685 }
686
687 fn check_version(&self, kind: RecordKind, index: &NameHmac, got: u64) -> Result<(), Error> {
689 let mut pins = lock(&self.pins);
690 let map = pins.versions(kind);
691 match map.get(index) {
692 Some(&known) if got < known => {
693 Err(self.integrity(IntegrityError::VersionRollback { known, got }))
694 }
695 _ => {
696 map.insert(*index, got);
697 Ok(())
698 }
699 }
700 }
701
702 pub fn descriptors_after(&self, after: u32) -> Result<Vec<Descriptor>, Error> {
704 let list = self
705 .api
706 .descriptors(self.auth(), after)
707 .map_err(|e| self.server_err(e))?;
708 self.take(list)
709 .descriptors
710 .iter()
711 .map(|s| {
712 verify_descriptor(s, &self.vault_id, &self.owner_sign_pub)
713 .map_err(|e| self.integrity(e))
714 })
715 .collect()
716 }
717
718 pub fn refresh(&self) -> Result<(), Error> {
723 let (view, expires_at) = match &self.holder {
724 Holder::Owner(owner) => {
725 let answer = self
726 .api
727 .status(Auth::Owner(owner))
728 .map_err(|e| owner_error(e, &self.label))?;
729 (
730 owner_view(owner, answer.value, &self.label)?,
731 answer.expires_at,
732 )
733 }
734 Holder::Token(token) => {
735 let answer = self.api.token_self(token).map_err(token_refused)?;
736 let (view, sign_pub) = token_view(token, answer.value, &self.label)?;
737 if sign_pub != self.owner_sign_pub {
738 return Err(self.integrity(IntegrityError::KeyMismatch("owner signing key")));
739 }
740 (view, answer.expires_at)
741 }
742 };
743 let pinned = lock(&self.pins).descriptor;
744 if let Some(pin) = pinned {
745 self.check_descriptor_pin(&pin, &view.descriptor)?;
746 }
747 let pin = DescriptorPin {
748 generation: view.descriptor.generation,
749 hash: view.descriptor.hash(),
750 };
751 *self.view.write().unwrap_or_else(|p| p.into_inner()) = Arc::new(view);
752 lock(&self.pins).descriptor = Some(pin);
753 self.note_expiry(expires_at);
754 Ok(())
755 }
756
757 fn list_kind(&self, v: &View, kind: RecordKind) -> Result<Vec<Item>, Error> {
760 let mut out = Vec::new();
761 let mut after: Option<NameHmac> = None;
762 loop {
763 let page = self
764 .api
765 .list(self.auth(), kind, after.as_ref())
766 .map_err(|e| self.server_err(e))?;
767 let page = self.take(page);
768 for it in page.items {
769 let what = || format!("{}: {} #{}", self.label, noun(kind), short(&it.name_hmac));
770 verify_listed(&v.descriptor, kind, &it).map_err(|e| Error::seal(e, what()))?;
771 let name = open_name(
772 &v.descriptor,
773 v.bundle.name_key(),
774 kind,
775 &it.name_hmac,
776 &it.name_ct.0,
777 )
778 .map_err(|e| Error::seal(e, what()))?;
779 self.check_version(kind, &it.name_hmac, it.version)?;
780 out.push(Item {
781 name,
782 hmac: it.name_hmac,
783 version: it.version,
784 updated_at: it.written_at,
785 size: it.size,
786 tombstone: it.tombstone,
787 });
788 }
789 match page.next_cursor {
790 Some(c) => after = Some(c),
791 None => break,
792 }
793 }
794 out.sort_by(|a, b| a.name.cmp(&b.name));
795 Ok(out)
796 }
797
798 fn versions_kind(
799 &self,
800 v: &View,
801 kind: RecordKind,
802 name: &str,
803 ) -> Result<Vec<VersionMeta>, Error> {
804 let index = v.bundle.name_key().index(kind, name);
805 let list = match self.api.versions(self.auth(), kind, &index) {
806 Ok(a) => self.take(a),
807 Err(e) if e.code() == Some(ErrorCode::NotFound) => {
810 return match self.moved_on(v) {
811 Some(moved) => Err(moved),
812 None => Ok(Vec::new()),
813 };
814 }
815 Err(e) => return Err(self.server_err(e)),
816 };
817 for m in &list.versions {
818 verify_meta(&v.descriptor, kind, &index, m).map_err(|e| {
819 Error::seal(
820 e,
821 format!(
822 "{}: version {} of {} {name}",
823 self.label,
824 m.version,
825 noun(kind)
826 ),
827 )
828 })?;
829 }
830 Ok(list.versions)
831 }
832
833 fn moved_on(&self, v: &View) -> Option<Error> {
843 let status = self.refresh_status().ok()?;
846 if status.generation == v.descriptor.generation {
847 return None;
848 }
849 Some(Error::stale(
850 &self.label,
851 format!(
852 "{}: the vault moved to generation {} since this handle was opened, so its \
853 records are under new keys; refresh it",
854 self.label, status.generation
855 ),
856 ))
857 }
858
859 fn fetch(
860 &self,
861 v: &View,
862 kind: RecordKind,
863 name: &str,
864 version: Option<u64>,
865 ) -> Result<SecretVersion, Error> {
866 let index = v.bundle.name_key().index(kind, name);
867 let record = match self.api.record(self.auth(), kind, &index, version) {
868 Ok(a) => self.take(a),
869 Err(e) if e.code() == Some(ErrorCode::NotFound) => {
870 if let Some(moved) = self.moved_on(v) {
871 return Err(moved);
872 }
873 return Err(Error::not_found(match version {
874 Some(n) => format!("{}: {} {name} has no version {n}", self.label, noun(kind)),
875 None => format!("{}: no {} named {name}", self.label, noun(kind)),
876 }));
877 }
878 Err(e) if e.code() == Some(ErrorCode::Forbidden) => {
879 return Err(Error::forbidden(format!(
880 "{}: this credential may not read {} {name}",
881 self.label,
882 noun(kind)
883 )));
884 }
885 Err(e) => return Err(self.server_err(e)),
886 };
887 if record.name_hmac != index {
888 return Err(self.integrity(IntegrityError::BindingMismatch("record index")));
889 }
890 match version {
891 Some(want) if record.version != want => {
892 return Err(self.integrity(IntegrityError::BindingMismatch("record version")));
893 }
894 Some(_) => {}
895 None => {
896 crate::seal::verify_version(&v.descriptor, kind, &record).map_err(|e| {
900 Error::seal(
901 e,
902 format!(
905 "{}: version {} of {} #{}",
906 self.label,
907 record.version,
908 noun(kind),
909 short(&record.name_hmac)
910 ),
911 )
912 })?;
913 self.check_version(kind, &index, record.version)?;
914 }
915 }
916 Ok(record)
917 }
918
919 fn open_record(
921 &self,
922 v: &View,
923 kind: RecordKind,
924 record: &SecretVersion,
925 ) -> Result<OpenedRecord, Error> {
926 let what = || {
927 format!(
928 "{}: version {} of {} #{}",
929 self.label,
930 record.version,
931 noun(kind),
932 short(&record.name_hmac)
933 )
934 };
935 match kind {
936 RecordKind::Secret => open_secret(
937 &v.descriptor,
938 v.bundle.name_key(),
939 self.vault_secret(v)?,
940 record,
941 )
942 .map_err(|e| Error::seal(e, what())),
943 RecordKind::Config => open_config_record(
944 &v.descriptor,
945 v.bundle.name_key(),
946 self.config_secret(v)?,
947 record,
948 )
949 .map_err(|e| Error::seal(e, what())),
950 }
951 }
952
953 fn send_write(&self, v: &View, name: &str, rec: WrittenRecord, pre: Pre) -> Result<u64, Error> {
954 let kind = rec.kind;
955 let deleting = rec.value_ct.is_none();
956 let answer = match (rec.put_request(), rec.delete_request()) {
957 (Some(put), _) => self.api.put(self.auth(), kind, &rec.name_hmac, &put, pre),
958 (None, Some(delete)) => {
959 self.api
960 .delete(self.auth(), kind, &rec.name_hmac, &delete, pre)
961 }
962 (None, None) => {
963 return Err(Error::other(format!(
964 "{}: {name} has neither a value nor a tombstone",
965 self.label
966 )));
967 }
968 };
969 match answer {
970 Ok(a) => {
971 let version = self.take(a).version;
972 if version != rec.version {
976 return Err(self.integrity(IntegrityError::BindingMismatch("written version")));
977 }
978 self.check_version(kind, &rec.name_hmac, rec.version)?;
979 Ok(rec.version)
980 }
981 Err(e)
982 if matches!(
983 e.code(),
984 Some(ErrorCode::PreconditionFailed | ErrorCode::VersionMismatch)
985 ) =>
986 {
987 Err(self.conflict(v, kind, name, pre, deleting))
988 }
989 Err(e) if e.code() == Some(ErrorCode::Forbidden) => Err(Error::forbidden(format!(
990 "{}: this credential may not write {} {name}",
991 self.label,
992 noun(kind)
993 ))),
994 Err(e) if e.code() == Some(ErrorCode::StaleGeneration) => Err(Error::stale(
995 &self.label,
996 format!(
997 "{}: the vault was rotated while writing {name}; open it again (or refresh the handle) and retry",
998 self.label
999 ),
1000 )),
1001 Err(e) => Err(self.server_err(e)),
1002 }
1003 }
1004
1005 #[allow(clippy::too_many_arguments)]
1007 fn write_value(
1008 &self,
1009 v: &View,
1010 kind: RecordKind,
1011 name: &str,
1012 format: Option<ConfigFormat>,
1013 body: &[u8],
1014 pre: Pre,
1015 written_at: i64,
1016 ) -> Result<u64, Error> {
1017 let w = Writer {
1018 descriptor: &v.descriptor,
1019 name_key: v.bundle.name_key(),
1020 writer: self.writer(v, kind)?,
1021 };
1022 let version = pre.next_version();
1023 let rec = match (kind, format) {
1024 (RecordKind::Secret, _) => w.secret(name, body, version, written_at),
1025 (RecordKind::Config, Some(f)) => w.config(name, f, body, version, written_at),
1026 (RecordKind::Config, None) => {
1027 w.config(name, ConfigFormat::Text, body, version, written_at)
1028 }
1029 }
1030 .map_err(|e| {
1031 Error::seal(
1032 e,
1033 format!("{}: encrypting {} {name}", self.label, noun(kind)),
1034 )
1035 })?;
1036 self.send_write(v, name, rec, pre)
1037 }
1038
1039 fn write_tombstone(
1041 &self,
1042 v: &View,
1043 kind: RecordKind,
1044 name: &str,
1045 pre: Pre,
1046 written_at: i64,
1047 ) -> Result<u64, Error> {
1048 let w = Writer {
1049 descriptor: &v.descriptor,
1050 name_key: v.bundle.name_key(),
1051 writer: self.writer(v, kind)?,
1052 };
1053 let rec = w
1054 .tombstone(kind, name, pre.next_version(), written_at)
1055 .map_err(|e| {
1056 Error::seal(e, format!("{}: deleting {} {name}", self.label, noun(kind)))
1057 })?;
1058 self.send_write(v, name, rec, pre)
1059 }
1060
1061 fn conflict(
1062 &self,
1063 v: &View,
1064 kind: RecordKind,
1065 name: &str,
1066 expected: Pre,
1067 deleting: bool,
1068 ) -> Error {
1069 match self.versions_kind(v, kind, name) {
1070 Ok(versions) => {
1071 Error::conflict(name, expected, versions.last().map(|m| m.version), deleting)
1072 }
1073 Err(e) => e,
1074 }
1075 }
1076
1077 pub fn list(&self) -> Result<Vec<Item>, Error> {
1082 self.list_kind(&self.view(), RecordKind::Secret)
1083 }
1084
1085 pub fn versions(&self, name: &str) -> Result<Vec<VersionMeta>, Error> {
1087 self.versions_kind(&self.view(), RecordKind::Secret, name)
1088 }
1089
1090 pub fn current(&self, name: &str) -> Result<Pre, Error> {
1092 Ok(Pre::after(self.versions(name)?.last()))
1093 }
1094
1095 pub fn get(&self, name: &str, version: Option<u64>) -> Result<(u64, Opened), Error> {
1098 let v = self.view();
1099 self.vault_secret(&v)?;
1100 let record = self.fetch(&v, RecordKind::Secret, name, version)?;
1101 let rec = self.open_record(&v, RecordKind::Secret, &record)?;
1102 let Some(value) = rec.value else {
1103 return Err(Error::not_found(format!(
1104 "{}: {name} was deleted at version {}",
1105 self.label, record.version
1106 )));
1107 };
1108 Ok((
1109 record.version,
1110 Opened {
1111 name: rec.name,
1112 value,
1113 written_at: rec.written_at,
1114 },
1115 ))
1116 }
1117
1118 pub fn put(&self, name: &str, value: &[u8], pre: Pre) -> Result<u64, Error> {
1119 let v = self.view();
1120 self.write_value(&v, RecordKind::Secret, name, None, value, pre, now())
1121 }
1122
1123 pub fn delete(&self, name: &str, version: u64) -> Result<u64, Error> {
1125 let v = self.view();
1126 self.write_tombstone(&v, RecordKind::Secret, name, Pre::Update(version), now())
1127 }
1128
1129 pub fn list_configs(&self) -> Result<Vec<Item>, Error> {
1134 self.list_kind(&self.view(), RecordKind::Config)
1135 }
1136
1137 pub fn config_versions(&self, name: &str) -> Result<Vec<VersionMeta>, Error> {
1139 self.versions_kind(&self.view(), RecordKind::Config, name)
1140 }
1141
1142 pub fn config_current(&self, name: &str) -> Result<Pre, Error> {
1144 Ok(Pre::after(self.config_versions(name)?.last()))
1145 }
1146
1147 pub fn get_config(
1149 &self,
1150 name: &str,
1151 version: Option<u64>,
1152 ) -> Result<(u64, OpenedConfig), Error> {
1153 let v = self.view();
1154 self.config_secret(&v)?;
1155 let record = self.fetch(&v, RecordKind::Config, name, version)?;
1156 let rec = self.open_record(&v, RecordKind::Config, &record)?;
1157 let (Some(body), Some(format)) = (rec.value, rec.format) else {
1158 return Err(Error::not_found(format!(
1159 "{}: config {name} was deleted at version {}",
1160 self.label, record.version
1161 )));
1162 };
1163 Ok((
1164 record.version,
1165 OpenedConfig {
1166 name: rec.name,
1167 format,
1168 body,
1169 written_at: rec.written_at,
1170 },
1171 ))
1172 }
1173
1174 pub fn put_config(
1177 &self,
1178 name: &str,
1179 format: ConfigFormat,
1180 body: &[u8],
1181 pre: Pre,
1182 ) -> Result<u64, Error> {
1183 let v = self.view();
1184 self.write_value(&v, RecordKind::Config, name, Some(format), body, pre, now())
1185 }
1186
1187 pub fn delete_config(&self, name: &str, version: u64) -> Result<u64, Error> {
1189 let v = self.view();
1190 self.write_tombstone(&v, RecordKind::Config, name, Pre::Update(version), now())
1191 }
1192
1193 pub fn children(&self) -> Result<(ChildrenRecord, Pre), Error> {
1198 let owner = self.owner()?;
1199 match self.api.children_get(owner) {
1200 Ok(a) => {
1201 let blob = self.take(a);
1202 let record = owner
1203 .open_children(&blob)
1204 .map_err(|e| Error::key(e, format!("{}: the children record", self.label)))?;
1205 Ok((record, Pre::Update(blob.version)))
1206 }
1207 Err(e) if e.code() == Some(ErrorCode::NotFound) => {
1208 Ok((ChildrenRecord::new(), Pre::Create))
1209 }
1210 Err(e) => Err(self.server_err(e)),
1211 }
1212 }
1213
1214 pub fn put_children(&self, record: &ChildrenRecord, pre: Pre) -> Result<u64, Error> {
1216 let owner = self.owner()?;
1217 let version = pre.next_version();
1218 let blob = owner
1219 .seal_children(version, record)
1220 .map_err(|e| Error::key(e, "the children record".to_owned()))?;
1221 match self.api.children_put(owner, &blob, pre) {
1222 Ok(a) => {
1223 self.take(a);
1224 Ok(version)
1225 }
1226 Err(e)
1227 if matches!(
1228 e.code(),
1229 Some(ErrorCode::PreconditionFailed | ErrorCode::VersionMismatch)
1230 ) =>
1231 {
1232 Err(Error::conflict(
1233 &format!("{}'s children record", self.label),
1234 pre,
1235 None,
1236 false,
1237 ))
1238 }
1239 Err(e) => Err(self.server_err(e)),
1240 }
1241 }
1242
1243 pub fn update_children(
1245 &self,
1246 mut change: impl FnMut(&mut ChildrenRecord) -> Result<(), Error>,
1247 ) -> Result<(), Error> {
1248 for _ in 0..5 {
1249 let (mut record, pre) = self.children()?;
1250 change(&mut record)?;
1251 match self.put_children(&record, pre) {
1252 Ok(_) => return Ok(()),
1253 Err(Error::Conflict { .. }) => continue,
1254 Err(e) => return Err(e),
1255 }
1256 }
1257 Err(Error::other(format!(
1258 "{}: the children record kept changing; try again",
1259 self.label
1260 )))
1261 }
1262
1263 pub fn seal_child(&self, seg: &Segment, key: &NodeKey) -> Result<(), Error> {
1266 let to = self.owner()?.box_pub();
1267 let key_ct = seal_child_key(&to, &self.vault_id, key)
1268 .map_err(|e| Error::key(e, format!("the sealed key for {seg}")))?;
1269 self.update_children(|r| {
1270 let mode = ChildMode::Sealed {
1271 key_ct: B64(key_ct.clone()),
1272 };
1273 if r.set_mode(seg, mode.clone()).is_err() {
1274 r.insert(ChildEntry::new(seg.clone(), now(), mode))
1275 .map_err(children_error)?;
1276 }
1277 Ok(())
1278 })
1279 }
1280
1281 pub fn refresh_status(&self) -> Result<VaultStatus, Error> {
1285 let answer = self
1286 .api
1287 .status(self.auth())
1288 .map_err(|e| self.server_err(e))?;
1289 let status = self.take(answer);
1290 if status.owner_sign_pub != self.owner_sign_pub || status.vault_id != self.vault_id {
1291 return Err(self.integrity(IntegrityError::KeyMismatch("owner signing key")));
1292 }
1293 verify_descriptor(&status.descriptor, &self.vault_id, &self.owner_sign_pub)
1294 .map_err(|e| self.integrity(e))?;
1295 Ok(status)
1296 }
1297
1298 fn audit_page(&self, after: u64) -> Result<crate::proto::api::AuditPage, Error> {
1299 match self.api.audit(self.auth(), after) {
1300 Ok(a) => Ok(self.take(a)),
1301 Err(e) if e.code() == Some(ErrorCode::Forbidden) => Err(Error::forbidden(format!(
1302 "{}: this credential may not read the audit log",
1303 self.label
1304 ))),
1305 Err(e) => Err(self.server_err(e)),
1306 }
1307 }
1308
1309 fn audit_rows(&self, mut after: u64, until: Option<u64>) -> Result<AuditRows, Error> {
1313 let mut rows = Vec::new();
1314 loop {
1315 let page = self.audit_page(after)?;
1316 let n = page.rows.len();
1317 if let Some(last) = page.rows.last() {
1318 after = last.seq;
1319 }
1320 rows.extend(page.rows);
1321 if page.stop.is_some() {
1322 return Ok((rows, page.head, page.stop));
1323 }
1324 if n < crate::client::AUDIT_PAGE || until.is_some_and(|u| after >= u) {
1325 return Ok((rows, page.head, None));
1326 }
1327 }
1328 }
1329
1330 pub fn verify_audit(
1335 &self,
1336 known: Option<ChainHead>,
1337 earlier: usize,
1338 ) -> Result<RawAudit, Error> {
1339 let failed = format!("{}: audit verification FAILED", self.label);
1340 let (rows, server_head, stop) = self.audit_rows(known.map_or(0, |h| h.seq), None)?;
1341 let verified = verify_rows(known.as_ref(), &rows, server_head.as_ref(), stop.as_ref())
1347 .map_err(|e| Error::audit_chain(e, &failed))?;
1348 let head = verified.head;
1349
1350 let mut shown = Vec::new();
1351 if let Some(k) = known.filter(|k| earlier > 0 && k.seq > 0) {
1352 let (mut before, _, _) =
1353 self.audit_rows(k.seq.saturating_sub(earlier as u64), Some(k.seq))?;
1354 before.retain(|r| r.seq <= k.seq);
1355 if !before.is_empty() {
1356 let reached = verify_chain(head_before(&before).as_ref(), &before)
1357 .map_err(|e| Error::audit_chain(e, &failed))?;
1358 if reached != Some(k) {
1359 return Err(self
1360 .integrity(IntegrityError::BindingMismatch("audit rows"))
1361 .context(&failed));
1362 }
1363 shown = before;
1364 }
1365 }
1366 let new_rows = rows.len();
1367 shown.extend(rows);
1368
1369 let mut names = HashMap::new();
1370 for item in self.list().unwrap_or_default() {
1371 names.insert(item.hmac, item.name);
1372 }
1373 for item in self.list_configs().unwrap_or_default() {
1374 names.insert(item.hmac, item.name);
1375 }
1376 Ok(RawAudit {
1377 rows: shown,
1378 new_rows,
1379 head,
1380 names,
1381 unverifiable: verified.unverifiable,
1382 })
1383 }
1384
1385 fn all_versions(&self, v: &View, kind: RecordKind) -> Result<Vec<SecretVersion>, Error> {
1387 let mut out = Vec::new();
1388 for item in self.list_kind(v, kind)? {
1389 for meta in self.versions_kind(v, kind, &item.name)? {
1390 out.push(self.fetch(v, kind, &item.name, Some(meta.version))?);
1391 }
1392 }
1393 Ok(out)
1394 }
1395
1396 pub fn rotate(&self, revoke: &[TokenId]) -> Result<u32, Error> {
1400 let owner = self.owner()?;
1401 let v = self.view();
1402 let full = self.full(&v)?;
1403 for _ in 0..3 {
1404 let status = self.refresh_status()?;
1405 if status.generation != v.descriptor.generation {
1406 return Err(Error::stale(
1407 &self.label,
1408 format!(
1409 "{}: the vault moved to generation {} since it was opened; open it again",
1410 self.label, status.generation
1411 ),
1412 ));
1413 }
1414 let gathered = self
1425 .all_versions(&v, RecordKind::Secret)
1426 .and_then(|secrets| {
1427 self.all_versions(&v, RecordKind::Config)
1428 .map(|configs| (secrets, configs))
1429 });
1430 let (secrets, configs) = match gathered {
1431 Ok(both) => both,
1432 Err(Error::NotFound { .. }) => continue,
1433 Err(e) => return Err(e),
1434 };
1435 let rotation = build_rotation(
1436 owner,
1437 &status,
1438 &v.descriptor,
1439 full,
1440 &secrets,
1441 &configs,
1442 revoke,
1443 now(),
1444 )
1445 .map_err(|e| Error::seal(e, format!("{}: preparing the rotation", self.label)))?;
1446 match self.api.rotate(owner, &rotation.request) {
1447 Ok(a) => return Ok(self.take(a).generation),
1448 Err(e) if e.code() == Some(ErrorCode::Conflict) => continue,
1450 Err(e) => return Err(self.server_err(e)),
1451 }
1452 }
1453 Err(Error::other(format!(
1454 "{}: the vault kept changing during rotation; try again",
1455 self.label
1456 )))
1457 }
1458
1459 pub fn mint(
1462 &self,
1463 scope: Scope,
1464 ttl_secs: u64,
1465 allow_list: Option<Vec<NameHmac>>,
1466 ) -> Result<(TokenKeys, RegisterTokenResponse), Error> {
1467 let owner = self.owner()?;
1468 let v = self.view();
1469 let full = self.full(&v)?;
1470 let token = TokenKeys::generate(self.vault_id);
1471 let bundle = seal_for_scope(owner, &token.box_pub(), &token.id(), scope, full)
1472 .map_err(|e| Error::key(e, format!("{}: minting a {scope} token", self.label)))?;
1473 let request = RegisterTokenRequest::new(
1474 token.id(),
1475 token.auth_pub(),
1476 token.box_pub(),
1477 scope,
1478 ttl_secs,
1479 allow_list,
1480 full.generation,
1481 bundle,
1482 );
1483 let answer = self
1484 .api
1485 .register_token(owner, &request)
1486 .map_err(|e| self.server_err(e))?;
1487 Ok((token, self.take(answer)))
1488 }
1489
1490 pub fn revoke(&self, id: &TokenId) -> Result<RevokeResponse, Error> {
1492 let answer = self
1493 .api
1494 .revoke(self.auth(), id)
1495 .map_err(|e| self.server_err(e))?;
1496 Ok(self.take(answer))
1497 }
1498
1499 pub fn delete_vault(&self) -> Result<(), Error> {
1500 let owner = self.owner()?;
1501 let answer = self
1502 .api
1503 .delete_vault(owner)
1504 .map_err(|e| self.server_err(e))?;
1505 self.take(answer);
1506 Ok(())
1507 }
1508
1509 pub fn copy_from(&self, old: &Handle) -> Result<usize, Error> {
1517 let v = self.view();
1518 let ov = old.view();
1519 self.full(&v)?;
1520 old.full(&ov)?;
1521 let mut copied = 0;
1522 for kind in [RecordKind::Secret, RecordKind::Config] {
1523 for item in old.list_kind(&ov, kind)? {
1524 let mut source = Vec::new();
1525 for meta in old.versions_kind(&ov, kind, &item.name)? {
1526 if source.is_empty() && meta.tombstone {
1527 continue;
1528 }
1529 source.push(meta.version);
1530 }
1531 let target = self.versions_kind(&v, kind, &item.name)?;
1532 let mut pre = Pre::after(target.last());
1533 for &n in source.iter().skip(target.len()) {
1534 let raw = old.fetch(&ov, kind, &item.name, Some(n))?;
1535 let rec = old.open_record(&ov, kind, &raw)?;
1536 let written = match &rec.value {
1537 Some(body) => self.write_value(
1538 &v,
1539 kind,
1540 &item.name,
1541 rec.format,
1542 body,
1543 pre,
1544 rec.written_at,
1545 )?,
1546 None => self.write_tombstone(&v, kind, &item.name, pre, rec.written_at)?,
1547 };
1548 pre = if rec.value.is_some() {
1549 Pre::Update(written)
1550 } else {
1551 Pre::Revive(written)
1552 };
1553 copied += 1;
1554 }
1555 }
1556 }
1557 Ok(copied)
1558 }
1559}
1560
1561#[cfg(test)]
1562mod tests {
1563 use super::*;
1564
1565 fn meta(version: u64, tombstone: bool) -> VersionMeta {
1566 VersionMeta::new(
1567 version,
1568 0,
1569 crate::proto::audit::Actor::Owner,
1570 0,
1571 tombstone,
1572 1,
1573 Hash32([0; 32]),
1574 Hash32([0; 32]),
1575 crate::proto::ids::Sig64([0; 64]),
1576 )
1577 }
1578
1579 #[test]
1580 fn preconditions_follow_the_latest_version() {
1581 assert_eq!(Pre::after(None), Pre::Create);
1582 assert_eq!(Pre::after(Some(&meta(3, true))), Pre::Revive(3));
1583 assert_eq!(Pre::after(Some(&meta(3, false))), Pre::Update(3));
1584 }
1585
1586 #[test]
1587 fn pins_roundtrip_through_json_and_toml() {
1588 let mut pins = Pins {
1589 descriptor: Some(DescriptorPin {
1590 generation: 2,
1591 hash: Hash32([7; 32]),
1592 }),
1593 ..Pins::default()
1594 };
1595 pins.secrets.insert(NameHmac([1; 32]), 4);
1596 pins.configs.insert(NameHmac([2; 32]), 9);
1597 let json = serde_json::to_string(&pins).unwrap();
1598 assert_eq!(serde_json::from_str::<Pins>(&json).unwrap(), pins);
1599 let text = toml::to_string(&pins).unwrap();
1600 assert_eq!(toml::from_str::<Pins>(&text).unwrap(), pins);
1601 }
1602}