1use crate::{signing, Did, Error, Result, RootKey};
4use chrono::{DateTime, Duration, Utc};
5use ed25519_dalek::{Signature, Verifier};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum RotationType {
12 Root,
14 Session,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub enum RotationReason {
22 Scheduled,
24 Compromise,
26 AlgorithmUpgrade,
28 Operational,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct NewKey {
36 pub id: String,
38 #[serde(rename = "type")]
40 pub key_type: String,
41 pub public_key_multibase: String,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct KeyRotation {
49 #[serde(rename = "type")]
51 pub type_: String,
52
53 pub version: String,
55
56 pub did: String,
58
59 pub rotation_type: RotationType,
61
62 pub new_key: NewKey,
64
65 pub previous_key: String,
67
68 pub effective_at: i64,
70
71 pub overlap_until: i64,
73
74 pub reason: RotationReason,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub signature: Option<String>,
80}
81
82impl KeyRotation {
83 pub const DEFAULT_OVERLAP: Duration = Duration::hours(24);
85
86 pub fn new(
88 did: Did,
89 rotation_type: RotationType,
90 new_key: NewKey,
91 previous_key: String,
92 reason: RotationReason,
93 ) -> Self {
94 let now = Utc::now();
95 Self {
96 type_: "KeyRotation".to_string(),
97 version: "1.0".to_string(),
98 did: did.to_string(),
99 rotation_type,
100 new_key,
101 previous_key,
102 effective_at: now.timestamp_millis(),
103 overlap_until: (now + Self::DEFAULT_OVERLAP).timestamp_millis(),
104 reason,
105 signature: None,
106 }
107 }
108
109 pub fn effective_at(mut self, time: DateTime<Utc>) -> Self {
111 self.effective_at = time.timestamp_millis();
112 self
113 }
114
115 pub fn overlap_duration(mut self, duration: Duration) -> Self {
117 let effective = DateTime::from_timestamp_millis(self.effective_at).unwrap_or_else(Utc::now);
118 self.overlap_until = (effective + duration).timestamp_millis();
119 self
120 }
121
122 pub fn sign(mut self, old_key: &RootKey) -> Result<Self> {
124 self.signature = None;
125 let canonical = signing::canonicalize(&self)?;
126 let sig = old_key.sign(&canonical);
127 self.signature = Some(base64::Engine::encode(
128 &base64::engine::general_purpose::STANDARD,
129 sig.to_bytes(),
130 ));
131 Ok(self)
132 }
133
134 pub fn verify(&self) -> Result<()> {
136 let sig_b64 = self.signature.as_ref().ok_or(Error::InvalidSignature)?;
137 let sig_bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, sig_b64)
138 .map_err(|_| Error::InvalidSignature)?;
139
140 let signature =
141 Signature::from_bytes(&sig_bytes.try_into().map_err(|_| Error::InvalidSignature)?);
142
143 let did: Did = self.did.parse()?;
144 let public_key = did.public_key()?;
145
146 let mut unsigned = self.clone();
147 unsigned.signature = None;
148 let canonical = signing::canonicalize(&unsigned)?;
149
150 public_key
151 .verify(&canonical, &signature)
152 .map_err(|_| Error::InvalidSignature)
153 }
154
155 pub fn is_old_key_valid_at(&self, time: DateTime<Utc>) -> bool {
157 time.timestamp_millis() <= self.overlap_until
158 }
159
160 pub fn is_new_key_active_at(&self, time: DateTime<Utc>) -> bool {
162 time.timestamp_millis() >= self.effective_at
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(rename_all = "snake_case")]
169pub enum RevocationReason {
170 Compromised,
172 Superseded,
174 Expired,
176 Administrative,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct Revocation {
184 #[serde(rename = "type")]
186 pub type_: String,
187
188 pub version: String,
190
191 pub did: String,
193
194 pub revoked_key: String,
196
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub revocation_id: Option<String>,
200
201 pub reason: RevocationReason,
203
204 pub effective_at: i64,
206
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub signature: Option<String>,
210}
211
212impl Revocation {
213 pub fn new(did: Did, revoked_key: String, reason: RevocationReason) -> Self {
215 Self {
216 type_: "Revocation".to_string(),
217 version: "1.0".to_string(),
218 did: did.to_string(),
219 revoked_key,
220 revocation_id: None,
221 reason,
222 effective_at: Utc::now().timestamp_millis(),
223 signature: None,
224 }
225 }
226
227 pub fn with_revocation_id(mut self, id: String) -> Self {
229 self.revocation_id = Some(id);
230 self
231 }
232
233 pub fn sign(mut self, signing_key: &RootKey) -> Result<Self> {
235 self.signature = None;
236 let canonical = signing::canonicalize(&self)?;
237 let sig = signing_key.sign(&canonical);
238 self.signature = Some(base64::Engine::encode(
239 &base64::engine::general_purpose::STANDARD,
240 sig.to_bytes(),
241 ));
242 Ok(self)
243 }
244
245 pub fn verify(&self, verifying_did: &Did) -> Result<()> {
247 let sig_b64 = self.signature.as_ref().ok_or(Error::InvalidSignature)?;
248 let sig_bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, sig_b64)
249 .map_err(|_| Error::InvalidSignature)?;
250
251 let signature =
252 Signature::from_bytes(&sig_bytes.try_into().map_err(|_| Error::InvalidSignature)?);
253
254 let public_key = verifying_did.public_key()?;
255
256 let mut unsigned = self.clone();
257 unsigned.signature = None;
258 let canonical = signing::canonicalize(&unsigned)?;
259
260 public_key
261 .verify(&canonical, &signature)
262 .map_err(|_| Error::InvalidSignature)
263 }
264
265 pub fn is_effective_at(&self, time: DateTime<Utc>) -> bool {
267 time.timestamp_millis() >= self.effective_at
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 fn make_new_key(root: &RootKey, suffix: &str) -> NewKey {
276 NewKey {
277 id: format!("{}#{}", root.did(), suffix),
278 key_type: "Ed25519VerificationKey2020".to_string(),
279 public_key_multibase: format!(
280 "z{}",
281 root.did().to_string().split(':').next_back().unwrap()
282 ),
283 }
284 }
285
286 #[test]
287 fn test_key_rotation() {
288 let old_key = RootKey::generate();
289 let new_key = RootKey::generate();
290
291 let rotation = KeyRotation::new(
292 old_key.did(),
293 RotationType::Root,
294 make_new_key(&new_key, "root-2"),
295 format!("{}#root", old_key.did()),
296 RotationReason::Scheduled,
297 );
298
299 let signed = rotation.sign(&old_key).unwrap();
300 assert!(signed.signature.is_some());
301 signed.verify().unwrap();
302 }
303
304 #[test]
305 fn test_key_rotation_overlap() {
306 let key = RootKey::generate();
307 let new_key = RootKey::generate();
308
309 let rotation = KeyRotation::new(
310 key.did(),
311 RotationType::Root,
312 make_new_key(&new_key, "root-2"),
313 format!("{}#root", key.did()),
314 RotationReason::Scheduled,
315 );
316
317 assert!(rotation.is_old_key_valid_at(Utc::now()));
319 assert!(rotation.is_new_key_active_at(Utc::now()));
320
321 let after_overlap = Utc::now() + Duration::hours(25);
323 assert!(!rotation.is_old_key_valid_at(after_overlap));
324 }
325
326 #[test]
327 fn test_revocation() {
328 let root = RootKey::generate();
329
330 let revocation = Revocation::new(
331 root.did(),
332 format!("{}#session-1", root.did()),
333 RevocationReason::Compromised,
334 );
335
336 let signed = revocation.sign(&root).unwrap();
337 assert!(signed.signature.is_some());
338 signed.verify(&root.did()).unwrap();
339 }
340
341 #[test]
342 fn test_revocation_with_id() {
343 let root = RootKey::generate();
344
345 let revocation = Revocation::new(
346 root.did(),
347 format!("{}#session-1", root.did()),
348 RevocationReason::Administrative,
349 )
350 .with_revocation_id("delegation-uuid-123".to_string());
351
352 assert_eq!(
353 revocation.revocation_id,
354 Some("delegation-uuid-123".to_string())
355 );
356 }
357}