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