Files
mytheclipse/crates/mytheclipse-crypto/src/key_ring.rs
T
asepharyana db4f277336 feat: add corex-storage crate for unified storage abstraction
- Introduced corex-storage crate with support for local disk, S3-compatible, and Google Cloud Storage backends.
- Implemented StorageDriver trait for various storage backends.
- Added LocalFileStorage for local disk operations.
- Added S3Storage for S3-compatible object storage with multipart upload support.
- Added GcsStorage for Google Cloud Storage operations.
- Included error handling for storage operations.
- Added tests for each storage backend to ensure functionality.
- Created README.md for documentation and usage examples.
- Added Apache and MIT licenses for open-source compliance.
2026-08-28 22:24:18 +07:00

97 lines
2.9 KiB
Rust

//! Key rotation support.
//!
//! [`KeyRing`] holds a "current" key plus a list of "previous" keys. Operations
//! that need to *decrypt* or *verify* (as opposed to sign/encrypt) try the
//! current key first and then fall back to the previous keys, which is exactly
//! what you want during a rotation window: new writes use the current key, old
//! data can still be read with the previous key.
/// A generic ring of keys: one current plus any number of previous.
///
/// `T` is typically `&[u8]` or a key handle. The type is `Clone`; rotation just
/// swaps the current key into the previous list.
#[derive(Debug, Clone)]
pub struct KeyRing<T> {
current: T,
previous: Vec<T>,
}
impl<T> KeyRing<T> {
/// Builds a ring with a single current key.
pub fn new(current: T) -> Self {
Self {
current,
previous: Vec::new(),
}
}
/// Returns the current key.
pub fn current(&self) -> &T {
&self.current
}
/// Returns all keys, current first, then previous oldest-first-in-insert
/// order.
pub fn all_keys(&self) -> impl Iterator<Item = &T> {
std::iter::once(&self.current).chain(self.previous.iter())
}
/// Rotates in a new key, demoting the old current key to `previous`.
///
/// Usually call this with the *new* key as `new_key`; the old current key
/// remains usable for reads during the rotation window.
pub fn rotate(&mut self, new_key: T) {
let old = std::mem::replace(&mut self.current, new_key);
self.previous.push(old);
}
/// The number of keys being tracked (current + previous).
pub fn size(&self) -> usize {
1 + self.previous.len()
}
}
impl<T> KeyRing<T>
where
T: PartialEq,
{
/// Whether `key` is currently in the ring (current or previous).
pub fn contains(&self, key: &T) -> bool {
self.all_keys().any(|k| k == key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rotate_preserves_previous() {
let mut ring = KeyRing::new([1u8; 32]);
assert_eq!(ring.current(), &[1u8; 32]);
assert_eq!(ring.size(), 1);
ring.rotate([2u8; 32]);
assert_eq!(ring.current(), &[2u8; 32]);
assert_eq!(ring.size(), 2);
assert!(ring.contains(&[1u8; 32]));
assert!(ring.contains(&[2u8; 32]));
ring.rotate([3u8; 32]);
assert_eq!(ring.size(), 3);
assert!(ring.contains(&[1u8; 32]));
assert!(ring.contains(&[2u8; 32]));
assert!(ring.contains(&[3u8; 32]));
}
#[test]
fn all_keys_iterates_current_first() {
let mut ring = KeyRing::new("current");
ring.rotate("prev1");
ring.rotate("prev2");
// `previous` is push-ordered, so iteration is current, then newest-old.
let keys: Vec<&str> = ring.all_keys().copied().collect();
assert_eq!(keys, vec!["prev2", "current", "prev1"]);
}
}