coven_keys/keys/
credential_custody.rs1use std::sync::{Arc, Mutex};
2
3use super::{CloudHomeCredentials, KeyError, StoreKeys};
4
5pub trait CloudHomeCredentialCustody: Send + Sync {
12 fn unlock(&self) -> Result<Option<CloudHomeCredentials>, KeyError>;
13
14 fn persist(&self, credentials: &CloudHomeCredentials) -> Result<(), KeyError>;
15}
16
17#[derive(Clone)]
20pub struct CloudHomeCredentialsOwner {
21 destination: StoreKeys,
22 epoch: Arc<Mutex<u64>>,
23}
24
25impl CloudHomeCredentialsOwner {
26 pub fn new(destination: StoreKeys) -> Self {
27 Self {
28 destination,
29 epoch: Arc::new(Mutex::new(0)),
30 }
31 }
32
33 pub fn current(&self) -> Arc<dyn CloudHomeCredentialCustody> {
34 let epoch = *self.epoch.lock().expect("lock cloud credential epoch");
35 Arc::new(CloudHomeCredentialLease {
36 owner: self.clone(),
37 epoch,
38 })
39 }
40
41 pub fn stage(&self, proposed: Option<CloudHomeCredentials>) -> Arc<StagedCloudHomeCredentials> {
42 let base_epoch = *self.epoch.lock().expect("lock cloud credential epoch");
43 Arc::new(StagedCloudHomeCredentials {
44 owner: self.clone(),
45 base_epoch,
46 state: Mutex::new(StagedCredentialState::Proposed(proposed)),
47 })
48 }
49
50 fn read_at(&self, expected_epoch: u64) -> Result<Option<CloudHomeCredentials>, KeyError> {
51 let epoch = self.epoch.lock().expect("lock cloud credential epoch");
52 if *epoch != expected_epoch {
53 return Err(KeyError::CloudCredentialsSuperseded);
54 }
55 self.destination.get_cloud_home_credentials()
56 }
57
58 fn write_at(
59 &self,
60 expected_epoch: u64,
61 credentials: &CloudHomeCredentials,
62 ) -> Result<(), KeyError> {
63 let epoch = self.epoch.lock().expect("lock cloud credential epoch");
64 if *epoch != expected_epoch {
65 return Err(KeyError::CloudCredentialsSuperseded);
66 }
67 self.destination.set_cloud_home_credentials(credentials)
68 }
69}
70
71struct CloudHomeCredentialLease {
72 owner: CloudHomeCredentialsOwner,
73 epoch: u64,
74}
75
76impl CloudHomeCredentialCustody for CloudHomeCredentialLease {
77 fn unlock(&self) -> Result<Option<CloudHomeCredentials>, KeyError> {
78 self.owner.read_at(self.epoch)
79 }
80
81 fn persist(&self, credentials: &CloudHomeCredentials) -> Result<(), KeyError> {
82 self.owner.write_at(self.epoch, credentials)
83 }
84}
85
86enum StagedCredentialState {
87 Proposed(Option<CloudHomeCredentials>),
88 Committed {
89 previous: Option<CloudHomeCredentials>,
90 },
91 RolledBack,
92}
93
94pub struct StagedCloudHomeCredentials {
101 owner: CloudHomeCredentialsOwner,
102 base_epoch: u64,
103 state: Mutex<StagedCredentialState>,
104}
105
106impl StagedCloudHomeCredentials {
107 fn committed_epoch(&self) -> u64 {
108 self.base_epoch
109 .checked_add(1)
110 .expect("cloud credential epoch overflow")
111 }
112
113 pub fn commit(&self) -> Result<(), KeyError> {
116 let mut state = self.state.lock().expect("lock staged cloud credentials");
117 match &*state {
118 StagedCredentialState::Proposed(credentials) => {
119 let credentials = credentials.clone();
120 let mut epoch = self
121 .owner
122 .epoch
123 .lock()
124 .expect("lock cloud credential epoch");
125 if *epoch != self.base_epoch {
126 return Err(KeyError::CloudCredentialsSuperseded);
127 }
128 let previous = self.owner.destination.get_cloud_home_credentials()?;
129 match &credentials {
130 Some(credentials) => self
131 .owner
132 .destination
133 .set_cloud_home_credentials(credentials)?,
134 None => self.owner.destination.delete_cloud_home_credentials()?,
135 }
136 *epoch = self.committed_epoch();
137 *state = StagedCredentialState::Committed { previous };
138 Ok(())
139 }
140 StagedCredentialState::Committed { .. } => Ok(()),
141 StagedCredentialState::RolledBack => Err(KeyError::CloudCredentialsRolledBack {
142 operation: "commit",
143 }),
144 }
145 }
146
147 pub fn rollback(&self) -> Result<(), KeyError> {
150 let mut state = self.state.lock().expect("lock staged cloud credentials");
151 match &*state {
152 StagedCredentialState::Proposed(_) => {
153 let epoch = self
154 .owner
155 .epoch
156 .lock()
157 .expect("lock cloud credential epoch");
158 if *epoch != self.base_epoch {
159 return Err(KeyError::CloudCredentialsSuperseded);
160 }
161 *state = StagedCredentialState::RolledBack;
162 Ok(())
163 }
164 StagedCredentialState::Committed { previous } => {
165 let mut epoch = self
166 .owner
167 .epoch
168 .lock()
169 .expect("lock cloud credential epoch");
170 if *epoch != self.committed_epoch() {
171 return Err(KeyError::CloudCredentialsSuperseded);
172 }
173 match previous {
174 Some(previous) => self
175 .owner
176 .destination
177 .set_cloud_home_credentials(previous)?,
178 None => self.owner.destination.delete_cloud_home_credentials()?,
179 }
180 *epoch = self.base_epoch;
181 *state = StagedCredentialState::RolledBack;
182 Ok(())
183 }
184 StagedCredentialState::RolledBack => Ok(()),
185 }
186 }
187}
188
189impl CloudHomeCredentialCustody for StagedCloudHomeCredentials {
190 fn unlock(&self) -> Result<Option<CloudHomeCredentials>, KeyError> {
191 let state = self.state.lock().expect("lock staged cloud credentials");
192 match &*state {
193 StagedCredentialState::Proposed(credentials) => Ok(credentials.clone()),
194 StagedCredentialState::Committed { .. } => self.owner.read_at(self.committed_epoch()),
195 StagedCredentialState::RolledBack => Err(KeyError::CloudCredentialsRolledBack {
196 operation: "unlock",
197 }),
198 }
199 }
200
201 fn persist(&self, credentials: &CloudHomeCredentials) -> Result<(), KeyError> {
202 let mut state = self.state.lock().expect("lock staged cloud credentials");
203 match &mut *state {
204 StagedCredentialState::Proposed(proposed) => {
205 *proposed = Some(credentials.clone());
206 Ok(())
207 }
208 StagedCredentialState::Committed { .. } => {
209 self.owner.write_at(self.committed_epoch(), credentials)
210 }
211 StagedCredentialState::RolledBack => Err(KeyError::CloudCredentialsRolledBack {
212 operation: "persist refreshed",
213 }),
214 }
215 }
216}
217
218#[cfg(test)]
219#[path = "credential_custody_tests.rs"]
220mod tests;