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