Skip to main content

dpp_crypto/keystore/
rotation.rs

1use aes_gcm::{
2    Nonce,
3    aead::{Aead, OsRng},
4};
5use anyhow::Result;
6use rand::RngCore;
7use sha2::{Digest, Sha256};
8use zeroize::Zeroize;
9
10use super::{KeyRecord, KeyStore, default_algorithm};
11use ed25519_dalek::SigningKey;
12
13use crate::keystore::KeyEntry;
14
15impl KeyStore {
16    /// Archive the current key under a timestamped key so it can still be used
17    /// to verify older signatures after rotation.
18    pub fn archive_key(&self, key_id: &str) -> Result<()> {
19        if *self.needs_migration.read().expect("lock") {
20            anyhow::bail!(
21                "key store requires KDF migration before writes are allowed — \
22                 call migrate_if_needed() first"
23            );
24        }
25        let mut map = self.records.write().expect("key store write lock poisoned");
26        if let Some(record) = map.get(key_id) {
27            let ts = std::time::SystemTime::now()
28                .duration_since(std::time::UNIX_EPOCH)
29                .unwrap_or_default()
30                .as_nanos();
31            let archive_key = format!("{key_id}#archived-{ts}");
32            let archived = KeyRecord {
33                encrypted_signing_key: record.encrypted_signing_key.clone(),
34                nonce: record.nonce.clone(),
35                fingerprint: record.fingerprint.clone(),
36                verifying_key_hex: record.verifying_key_hex.clone(),
37                revoked: record.revoked,
38                algorithm: record.algorithm.clone(),
39            };
40            map.insert(archive_key, archived);
41            self.persist_envelope(&map)?;
42        }
43        Ok(())
44    }
45
46    /// Atomically rotate the key: archive the current key (kept valid so older
47    /// signatures still verify) and install a fresh current key, in a **single**
48    /// persisted write. Use for routine/hygiene rotation. Returns the new key.
49    ///
50    /// Unlike calling [`archive_key`](Self::archive_key) then
51    /// [`generate_key`](Self::generate_key), there is no intermediate on-disk
52    /// state where the archive exists but the new key does not (identity I4).
53    pub fn rotate_key(&self, key_id: &str) -> Result<KeyEntry> {
54        self.rotate_inner(key_id, false)
55    }
56
57    /// Atomically **revoke** the current key and install a fresh one. The old key
58    /// is archived but marked revoked, so [`crate::identity::did_builder`] drops it from
59    /// the published DID document and signatures it produced no longer verify.
60    /// Use this on key **compromise** (vs. hygiene rotation, where the old key
61    /// stays valid — see [`rotate_key`](Self::rotate_key)).
62    pub fn revoke_and_rotate(&self, key_id: &str) -> Result<KeyEntry> {
63        self.rotate_inner(key_id, true)
64    }
65
66    fn rotate_inner(&self, key_id: &str, revoke_old: bool) -> Result<KeyEntry> {
67        if *self.needs_migration.read().expect("lock") {
68            anyhow::bail!(
69                "key store requires KDF migration before writes are allowed — \
70                 call migrate_if_needed() first"
71            );
72        }
73
74        // Prepare the new key material up front so the lock-held section is just
75        // the in-memory swap + single persist.
76        let signing_key = SigningKey::generate(&mut OsRng);
77        let verifying_key = signing_key.verifying_key();
78        let fingerprint = hex::encode(Sha256::digest(verifying_key.as_bytes()));
79        let verifying_key_hex = hex::encode(verifying_key.as_bytes());
80        let mut nonce_bytes = [0u8; 12];
81        rand::rngs::OsRng.fill_bytes(&mut nonce_bytes);
82        let nonce = Nonce::from_slice(&nonce_bytes);
83        let mut raw = signing_key.to_bytes();
84        let encrypted = self
85            .cipher
86            .encrypt(nonce, raw.as_ref())
87            .map_err(|_| anyhow::anyhow!("AES-GCM encrypt failed"))?;
88        raw.zeroize();
89        let new_record = KeyRecord {
90            encrypted_signing_key: encrypted,
91            nonce: nonce_bytes.to_vec(),
92            fingerprint: fingerprint.clone(),
93            verifying_key_hex,
94            revoked: false,
95            algorithm: default_algorithm(),
96        };
97
98        {
99            let mut map = self.records.write().expect("key store write lock poisoned");
100            // Archive the existing current key (if any), marking it revoked when
101            // this is a compromise rotation.
102            if let Some(record) = map.get(key_id) {
103                let ts = std::time::SystemTime::now()
104                    .duration_since(std::time::UNIX_EPOCH)
105                    .unwrap_or_default()
106                    .as_nanos();
107                let archive_name = format!("{key_id}#archived-{ts}");
108                let archived = KeyRecord {
109                    encrypted_signing_key: record.encrypted_signing_key.clone(),
110                    nonce: record.nonce.clone(),
111                    fingerprint: record.fingerprint.clone(),
112                    verifying_key_hex: record.verifying_key_hex.clone(),
113                    revoked: revoke_old,
114                    algorithm: record.algorithm.clone(),
115                };
116                map.insert(archive_name, archived);
117            }
118            map.insert(key_id.to_owned(), new_record);
119            self.persist_envelope(&map)?;
120        }
121
122        Ok(KeyEntry {
123            signing_key,
124            verifying_key,
125            fingerprint,
126            revoked: false,
127        })
128    }
129}