Skip to main content

vcon_server/store/
rekey.rs

1//! A tenant key's rotation, as the Store sees it: every version's data
2//! key, wrapped under the old tenant key, is rewrapped under the new one.
3//! Bodies are never re-encrypted.
4
5use super::{Store, StoreError, commit, key_failed};
6use crate::envelope::{self, Context, Key, WrapLimit};
7
8impl Store {
9    /// Rewraps up to `limit` of the data keys still wrapped under this
10    /// Store's tenant key so they are wrapped under `new`, in one
11    /// transaction, and returns how many it rewrapped; 0 means none is left.
12    /// Each rewrap is counted against `new`, and refused at its wrap limit.
13    pub fn rewrap(&self, new: &Key, limit: usize) -> Result<usize, StoreError> {
14        let old = self.tenant_key.id();
15        let mut tx = self.catalog.immediate()?;
16        let rows = tx.wrapped_under(old, limit)?;
17        let after = tx.wraps(new.id())?.saturating_add(rows.len() as u64);
18        if !rows.is_empty() && envelope::wrap_limit(after - 1) == WrapLimit::Refuse {
19            return Err(StoreError::LimitReached);
20        }
21        for row in &rows {
22            let ctx = Context::Version {
23                tenant: self.tenant,
24                uuid: row.uuid,
25                version: row.version,
26            };
27            let rewrapped =
28                envelope::rewrap(&self.tenant_key, new, &ctx, &row.wrapped).map_err(key_failed)?;
29            tx.set_wrapped(row.version, new.id(), &rewrapped)?;
30            tx.add_wrap(new.id())?;
31        }
32        commit(tx)?;
33        Ok(rows.len())
34    }
35
36    /// How many version rows name a key other than this Store's tenant key:
37    /// a rotation stopped part way leaves some.
38    pub fn rows_under_other_keys(&self) -> Result<u64, StoreError> {
39        self.catalog
40            .snapshot()?
41            .count_not_under(self.tenant_key.id())
42    }
43}
44
45#[cfg(test)]
46mod rekey_tests {
47    use super::super::testing::vcon;
48    use super::super::tests::{Fixture, fixture};
49    use super::*;
50    use crate::envelope::{self, Context};
51    use crate::ids::VersionId;
52    use crate::store::Created;
53
54    /// Every version row: its id, uuid, wrapping key id and wrapped key.
55    fn rows(f: &Fixture) -> Vec<(VersionId, uuid::Uuid, Vec<u8>, Vec<u8>)> {
56        f.store
57            .conn()
58            .prepare(
59                "SELECT version, uuid, tenant_key_id, wrapped_key FROM version ORDER BY version",
60            )
61            .expect("prepare")
62            .query_map([], |r| {
63                Ok((
64                    VersionId(r.get(0)?),
65                    uuid::Uuid::from_bytes(r.get(1)?),
66                    r.get(2)?,
67                    r.get(3)?,
68                ))
69            })
70            .expect("query")
71            .collect::<Result<_, _>>()
72            .expect("rows")
73    }
74
75    fn store_five(f: &Fixture) {
76        for n in 0..5 {
77            let uuid = format!("01a07da8-c2bb-83e5-b9a2-279e0d16bc4{n}");
78            let (found, body) = vcon(&uuid, "kept");
79            assert!(matches!(
80                f.store.create(&f.caller, &found, &body),
81                Ok(Created::New(_))
82            ));
83        }
84    }
85
86    fn wraps_of(f: &Fixture, key: &Key) -> i64 {
87        f.store
88            .conn()
89            .query_row(
90                "SELECT coalesce(sum(wraps), 0) FROM key_use WHERE tenant_key_id = ?1",
91                [key.id().bytes()],
92                |r| r.get(0),
93            )
94            .expect("wraps")
95    }
96
97    #[test]
98    fn rewrap_moves_every_data_key_to_the_new_key_in_batches() {
99        let f = fixture();
100        store_five(&f);
101        let (old, new) = (f.tenant_key(), Key::generate().expect("key"));
102        assert_eq!(f.store.rewrap(&new, 2), Ok(2));
103        assert_eq!(f.store.rewrap(&new, 2), Ok(2));
104        assert_eq!(f.store.rewrap(&new, 2), Ok(1));
105        assert_eq!(f.store.rewrap(&new, 2), Ok(0), "none left");
106        let rows = rows(&f);
107        assert_eq!(rows.len(), 5);
108        for (version, uuid, key_id, wrapped) in rows {
109            let ctx = Context::Version {
110                tenant: f.tenant(),
111                uuid,
112                version,
113            };
114            assert_eq!(
115                key_id,
116                new.id().bytes().to_vec(),
117                "the row names the new key"
118            );
119            assert!(
120                envelope::unwrap(&new, &ctx, &wrapped).is_ok(),
121                "opens under the new key"
122            );
123            assert!(
124                envelope::unwrap(&old, &ctx, &wrapped).is_err(),
125                "not under the old one"
126            );
127        }
128        assert_eq!(
129            wraps_of(&f, &new),
130            5,
131            "each rewrap is counted against the new key"
132        );
133    }
134
135    #[test]
136    fn a_rewrap_cut_short_leaves_every_row_opening_under_one_key_or_the_other() {
137        let f = fixture();
138        store_five(&f);
139        let (old, new) = (f.tenant_key(), Key::generate().expect("key"));
140        assert_eq!(f.store.rewrap(&new, 3), Ok(3));
141        let (mut under_old, mut under_new) = (0, 0);
142        for (version, uuid, key_id, wrapped) in rows(&f) {
143            let ctx = Context::Version {
144                tenant: f.tenant(),
145                uuid,
146                version,
147            };
148            let (key, count) = if key_id == new.id().bytes().to_vec() {
149                (&new, &mut under_new)
150            } else {
151                (&old, &mut under_old)
152            };
153            assert!(envelope::unwrap(key, &ctx, &wrapped).is_ok(), "{version}");
154            *count += 1;
155        }
156        assert_eq!((under_old, under_new), (2, 3));
157    }
158
159    #[test]
160    fn a_new_key_at_its_wrap_limit_is_refused_and_nothing_changes() {
161        let f = fixture();
162        store_five(&f);
163        let new = Key::generate().expect("key");
164        f.store
165            .conn()
166            .execute(
167                "INSERT INTO key_use (tenant_key_id, wraps) VALUES (?1, ?2)",
168                rusqlite::params![new.id().bytes(), 1_i64 << 32],
169            )
170            .expect("at the limit");
171        let before = rows(&f);
172        assert_eq!(f.store.rewrap(&new, 10), Err(StoreError::LimitReached));
173        assert_eq!(rows(&f), before);
174    }
175}