Skip to main content

galata_vault/
vault.rs

1//! One vault, opened by its owner key or by a token: every
2//! server operation the token client and the owner API need, with
3//! verification, encryption and decryption on this side, over
4//! [`crate::client::Api`].
5//!
6//! Opening a vault verifies it from a vault id the client pins itself
7//!: the owner derives it from the node
8//! key, a token carries it in its string. The owner signing key must hash to
9//! it, the descriptor must be signed by that key, and the bundle must be
10//! owner-signed for this holder and hold exactly the keys the descriptor
11//! names. From then on the handle encrypts only to the descriptor's keys and
12//! accepts only records signed by its writer keys.
13//!
14//! The verified generation (descriptor and keys) is one [`View`], swapped
15//! whole by [`Handle::refresh`], so a long-lived handle recovers from a
16//! rotation without being rebuilt.
17
18use 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
49/// A response announcing a vault expiry closer than this is reported to the
50/// handle's [`crate::Events`] (once per handle).
51pub const EXPIRY_WARNING_SECS: i64 = 14 * DAY;
52
53/// The descriptor generation a client saw, and its hash.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
55pub struct DescriptorPin {
56    /// The generation.
57    pub generation: u32,
58    /// The descriptor's hash.
59    pub hash: Hash32,
60}
61
62/// What a client remembers to detect rollback: the descriptor it last
63/// verified, and the latest
64/// version it saw of each record. A caller that persists these across runs
65/// gets regression detection across runs.
66#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
67pub struct Pins {
68    /// The descriptor last verified.
69    #[serde(default)]
70    pub descriptor: Option<DescriptorPin>,
71    /// Secret index → the latest version seen.
72    #[serde(default)]
73    pub secrets: BTreeMap<NameHmac, u64>,
74    /// Config index → the latest version seen.
75    #[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            // A handle only ever reads the two kinds it has keys for.
85        }
86    }
87}
88
89pub(crate) enum Holder {
90    Owner(Box<OwnerKeys>),
91    Token(Box<TokenKeys>),
92}
93
94/// A verified, decrypted record name and its latest version's metadata.
95#[derive(Debug, Clone)]
96pub(crate) struct Item {
97    pub name: String,
98    pub hmac: NameHmac,
99    pub version: u64,
100    /// When the latest version was written (the time its writer signed).
101    pub updated_at: i64,
102    /// The latest version's ciphertext size in bytes.
103    pub size: u32,
104    pub tombstone: bool,
105}
106
107/// The verified generation: its descriptor and the keys this holder has for
108/// it.
109pub(crate) struct View {
110    pub descriptor: Descriptor,
111    pub bundle: Bundle,
112    /// Token-opened vaults only.
113    pub scope: Option<Scope>,
114    /// A read token's allow-list.
115    pub allow_list: Option<Vec<NameHmac>>,
116    /// Token-opened vaults only.
117    pub token_expires_at: Option<i64>,
118    /// Owner-opened vaults only: the status the vault was opened with.
119    pub status: Option<VaultStatus>,
120}
121
122/// A verified audit chain.
123pub(crate) struct RawAudit {
124    /// Oldest first: the earlier rows asked for, then every row after the
125    /// known head.
126    pub rows: Vec<AuditRow>,
127    /// How many rows came after the known head.
128    pub new_rows: usize,
129    /// The head now verified.
130    pub head: Option<ChainHead>,
131    /// Record names this credential could decrypt, by index.
132    pub names: HashMap<NameHmac, String>,
133    /// Where the served rows became ones this client cannot verify.
134    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    /// The environment path, or a description in token mode.
156    pub label: String,
157    /// The pinned vault id.
158    pub vault_id: VaultId,
159    /// Verified against the pinned vault id; never changes.
160    pub owner_sign_pub: Key32,
161    view: RwLock<Arc<View>>,
162    warned: AtomicBool,
163    /// `i64::MIN` until a response carries one.
164    last_expires_at: AtomicI64,
165    pins: Mutex<Pins>,
166}
167
168/// An owner's request failed. The server answers `unauthorized` both for a
169/// vault that does not exist and for one that expired.
170fn 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
183/// The server refused a token when opening its vault.
184fn 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
198/// Verify a served descriptor for the pinned vault: the owner signing key
199/// hashes to the pinned id, the owner's signature
200/// over the descriptor verifies, and the descriptor names that vault. Every
201/// public key this SDK encrypts to or verifies a record against comes from a
202/// descriptor that passed here, so this is the check that refuses a key the
203/// server substituted. The one place it happens, so the plant below can
204/// remove it for the adversarial suite's self-test.
205fn 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// The adversarial suite's plant (`scripts/adversary-plant.sh`). It exists
218// so the suite can be shown to fail when a
219// key-substitution check is gone; it must never be in anything shipped.
220// Three latches: the non-default `adversary-plant` feature, which nothing in
221// the workspace enables; debug assertions, which no release profile has
222// (without them the feature refuses to compile); and GV_ADVERSARY_PLANT at
223// run time.
224#[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    /// The variable, and the one weakening it selects.
237    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    /// Everything `SignedDescriptor::verify_for` checks except the owner's
245    /// signature: a descriptor with a substituted public key now passes.
246    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
262/// Design D3 for the owner: the status names this owner's keys, the
263/// descriptor verifies from the derived vault id, and the owner bundle is
264/// owner-signed and holds exactly the descriptor's keys.
265fn 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
300/// Design D3 for a token: the answer is for this token and vault, the
301/// descriptor verifies from the vault id in the token string, and the bundle
302/// is owner-signed for this token and holds exactly the descriptor's keys.
303fn 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    // The bundle's kind is checked against the scope: a scope this client
317    // does not know cannot be checked, so the vault is not opened.
318    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
348/// The head just before the first row served.
349fn 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
359/// Audit rows as served, the server's head, and where the rows stopped
360/// being ones this client can verify, if they did.
361type 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    /// Open a vault as its owner: one signed status request (which also
386    /// keeps the vault alive), then the verification chain from the vault id
387    /// the node key derives.
388    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    /// Open the vault a token belongs to, verifying it from the vault id in
403    /// the token string.
404    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    /// Create the vault for `key` (a proof of work when the server asks for
415    /// one, signed creation, fresh generation keys) with an empty children
416    /// record. `Ok(false)` if it already exists.
417    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    /// As [`Handle::create`], with generation 1's keys given. Idempotent: an
422    /// existing vault is `Ok(false)`.
423    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        // A challenge only for a server that asks for one. One older than the
436        // capabilities document always did.
437        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        // Before the signed creation request: a server that does not speak
442        // protocol 2 is refused (the capabilities are cached by now).
443        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    /// Report a leaked token by proving possession. The token
492    /// string itself is never sent.
493    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    /// The current verified generation.
507    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    /// The token's scope, or `None` when opened by the owner key.
529    pub fn scope(&self) -> Option<Scope> {
530        self.view().scope
531    }
532
533    /// The status an owner-opened vault was opened (or last refreshed) with.
534    pub fn status(&self) -> Option<VaultStatus> {
535        self.view().status.clone()
536    }
537
538    /// The expiry the server reported on the latest response.
539    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    /// When the token expires; `None` when opened by the owner key.
545    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    /// A server refusal, labelled, with its code kept.
563    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    /// Whether this credential may read the value of `name`.
572    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    /// The index of secret `name` (what an allow-list holds).
577    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    /// Every key of the generation: the owner's, or an admin token's.
587    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    /// The key that opens secrets, if this credential holds it.
598    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    /// The key that opens configs, if this credential holds it.
608    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    // ------------------------------------------------------------ pins
632
633    /// What this handle has seen: the current descriptor and every record
634    /// version it has read or written.
635    pub fn pins(&self) -> Pins {
636        lock(&self.pins).clone()
637    }
638
639    /// Adopt pins a caller kept. The descriptor pin is checked against the
640    /// verified descriptor now: an older or different generation is refused,
641    /// and a newer one must follow the pinned one by an unbroken chain of
642    /// owner-signed descriptors.
643    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    /// Refuse a latest version older than one this handle saw; remember it.
688    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    /// Every owner-signed descriptor after generation `after`, verified.
703    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    /// Fetch the vault's current generation again: the owner's status or the
719    /// token's bundle, and the descriptor, verified from the pinned vault id.
720    /// A newer generation must follow the pinned one by an unbroken chain of
721    /// owner-signed descriptors; an older one is a rollback.
722    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    // ------------------------------------------------------------ records
758
759    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            // Empty is a legitimate answer for a name that never existed, and
808            // the wrong answer for one this view can no longer index.
809            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    /// A name index is derived from the generation's name key, so after a
834    /// rotation this view has not adopted, every index it computes is one the
835    /// server has never seen. The answer is then a 404 for a record that
836    /// exists, or an empty history for a record with versions -- which would
837    /// tell a caller a secret is gone when the vault has merely moved on.
838    ///
839    /// So a miss is checked against the vault's generation before it is
840    /// reported: `Some` is what actually happened. One extra request, only on
841    /// a miss. `Handle::refresh` adopts the new generation.
842    fn moved_on(&self, v: &View) -> Option<Error> {
843        // If the generation cannot be confirmed either way, say nothing and
844        // let the miss be reported as it came, rather than guess.
845        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                // Verify before the pin moves: a forged answer must be
897                // refused without leaving its version behind, or one bad
898                // response would lock the genuine record out.
899                crate::seal::verify_version(&v.descriptor, kind, &record).map_err(|e| {
900                    Error::seal(
901                        e,
902                        // By index, never by name: the error names what the
903                        // server served, not what the caller asked for.
904                        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    /// Verify and decrypt a record this handle fetched.
920    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                // Pin the version this client signed, never an unsigned
973                // number from the server; a server that reports another
974                // one is lying about what it stored.
975                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    /// Seal, sign and send a value as the version `pre` implies.
1006    #[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    /// Sign and send a tombstone as the version `pre` implies.
1040    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    // ------------------------------------------------------------ secrets
1078
1079    /// Every secret's latest version, verified, names decrypted. Tombstones
1080    /// included.
1081    pub fn list(&self) -> Result<Vec<Item>, Error> {
1082        self.list_kind(&self.view(), RecordKind::Secret)
1083    }
1084
1085    /// Verified history metadata; empty if the secret never existed.
1086    pub fn versions(&self, name: &str) -> Result<Vec<VersionMeta>, Error> {
1087        self.versions_kind(&self.view(), RecordKind::Secret, name)
1088    }
1089
1090    /// The precondition for writing `name` now.
1091    pub fn current(&self, name: &str) -> Result<Pre, Error> {
1092        Ok(Pre::after(self.versions(name)?.last()))
1093    }
1094
1095    /// Verify and decrypt a secret. Errors name the path, the version and
1096    /// the failure kind, never key or plaintext bytes.
1097    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    /// Delete `name` at live `version`: a signed tombstone as the next version.
1124    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    // ------------------------------------------------------------ configs
1130
1131    /// Every config's latest version, verified, names decrypted. Tombstones
1132    /// included.
1133    pub fn list_configs(&self) -> Result<Vec<Item>, Error> {
1134        self.list_kind(&self.view(), RecordKind::Config)
1135    }
1136
1137    /// A config's verified history metadata; empty if it never existed.
1138    pub fn config_versions(&self, name: &str) -> Result<Vec<VersionMeta>, Error> {
1139        self.versions_kind(&self.view(), RecordKind::Config, name)
1140    }
1141
1142    /// The precondition for writing config `name` now.
1143    pub fn config_current(&self, name: &str) -> Result<Pre, Error> {
1144        Ok(Pre::after(self.config_versions(name)?.last()))
1145    }
1146
1147    /// Verify and decrypt a config document.
1148    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    /// Write a config document, sealed to the descriptor's config key and
1175    /// signed with the config writer key.
1176    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    /// Delete config `name` at live `version`: a signed tombstone.
1188    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    // ------------------------------------------------------------ children
1194
1195    /// The node's owner-only children record, verified, and the
1196    /// precondition to replace it.
1197    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    /// Seal, sign and write the children record as the version `pre` implies.
1215    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    /// Read-modify-write the children record, retrying if another client wins.
1244    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    /// Point this node's children entry for `seg` at `key`, sealed to this
1264    /// vault's owner box key.
1265    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    // ------------------------------------------------------------ vault
1282
1283    /// The vault's status, its descriptor verified against the pinned id.
1284    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    /// Rows after `after` (stopping once `until` is reached, if given), the
1310    /// server's head, and where the rows stopped being ones this client can
1311    /// verify, if they did (no page after that one is fetched).
1312    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    /// Fetch and verify the vault's audit chain against `known`, the head
1331    /// this client verified last time. With `earlier > 0`, up to that many
1332    /// rows at or below `known` are fetched as well, for display, and must
1333    /// lead exactly to it. Every scope may verify.
1334    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        // `docs/spec/audit.md#5`: the server head against `known`, every row
1342        // by position and hash (from the first row served when no head is
1343        // known: older rows may already be in the archive), and the head
1344        // again. A newer row format ends what is verified; an unknown action
1345        // or actor refuses.
1346        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    /// Every retained version of every record of `kind`, for a rotation.
1386    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    /// Move the vault to a new generation, dropping `revoke` in the same
1397    /// transaction. Owner-only. Returns the new generation;
1398    /// [`Handle::refresh`] adopts it.
1399    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            // The guard above and this gather are not one step. A rotation
1415            // that lands in between re-indexes every record under its own
1416            // name key, so generation `v`'s index answers 404 and a record
1417            // this handle just listed vanishes. Rebuild, exactly as for a
1418            // revision conflict: the next turn's guard sees the new
1419            // generation and reports it as stale, instead of letting a 404
1420            // surface as though a secret had been lost. A concurrent write
1421            // that pruned an old version arrives here too, and wants the
1422            // same rebuild. `VaultNotFound` is a different matter and goes
1423            // on through.
1424            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                // Someone wrote meanwhile: rebuild from the new revision.
1449                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    /// Mint a token (owner-only): fresh keys, an owner-signed
1460    /// bundle holding exactly what `scope` allows.
1461    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    /// Revoke without rotating (owner or admin). Forward-only.
1491    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    /// Copy every retained record of `old` into this (new, owner-opened)
1510    /// vault, verified and decrypted with `old`'s keys, re-encrypted and
1511    /// re-signed with this vault's. Write times are kept;
1512    /// versions are renumbered from 1, since a new vault's history starts
1513    /// there, and a leading tombstone has nothing to delete and is skipped.
1514    /// Idempotent: versions already copied are skipped, so an interrupted
1515    /// copy resumes. Returns how many versions it wrote.
1516    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}