Skip to main content

sigstore_cache/
lib.rs

1//! Flexible caching support for Sigstore clients
2//!
3//! This crate provides a pluggable caching mechanism for Sigstore operations.
4//! It allows users to choose between different caching strategies:
5//!
6//! - [`FileSystemCache`]: Persistent cache stored on disk (default location or custom)
7//! - [`InMemoryCache`]: Fast in-process cache with TTL support
8//! - [`NoCache`]: Disabled caching (for testing or when caching is not desired)
9//!
10//! # Example
11//!
12//! ```no_run
13//! use sigstore_cache::{FileSystemCache, CacheAdapter, CacheKey};
14//! use std::time::Duration;
15//!
16//! # async fn example() -> Result<(), sigstore_cache::Error> {
17//! // Use default cache location (~/.cache/sigstore-rust/)
18//! let cache = FileSystemCache::default_location()?;
19//!
20//! // Or specify a custom directory
21//! let cache = FileSystemCache::new("/tmp/my-cache")?;
22//!
23//! // Store a value with TTL
24//! cache.set(CacheKey::RekorPublicKey, b"public-key-data", Duration::from_secs(86400)).await?;
25//!
26//! // Retrieve the value
27//! if let Some(data) = cache.get(CacheKey::RekorPublicKey).await? {
28//!     println!("Got cached data: {} bytes", data.len());
29//! }
30//! # Ok(())
31//! # }
32//! ```
33
34mod error;
35mod filesystem;
36mod memory;
37mod noop;
38
39pub use error::{Error, Result};
40pub use filesystem::{FileSystemCache, SIGSTORE_PRODUCTION_URL, SIGSTORE_STAGING_URL};
41pub use memory::InMemoryCache;
42pub use noop::NoCache;
43
44use std::future::Future;
45use std::pin::Pin;
46use std::sync::Arc;
47use std::time::Duration;
48
49/// Future type for cache get operations
50pub type CacheGetFuture<'a> = Pin<Box<dyn Future<Output = Result<Option<Vec<u8>>>> + Send + 'a>>;
51
52/// Future type for cache set/remove/clear operations
53pub type CacheOpFuture<'a> = Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
54
55/// Cache keys for different Sigstore resources
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub enum CacheKey {
58    /// Rekor transparency log public key
59    RekorPublicKey,
60    /// Rekor log info (tree size, root hash)
61    RekorLogInfo,
62    /// Fulcio trust bundle (CA certificates)
63    FulcioTrustBundle,
64    /// Fulcio OIDC configuration
65    FulcioConfiguration,
66    /// Trusted root from TUF
67    TrustedRoot,
68}
69
70impl CacheKey {
71    /// Get the string representation used for file names
72    pub fn as_str(&self) -> &'static str {
73        match self {
74            CacheKey::RekorPublicKey => "rekor_public_key",
75            CacheKey::RekorLogInfo => "rekor_log_info",
76            CacheKey::FulcioTrustBundle => "fulcio_trust_bundle",
77            CacheKey::FulcioConfiguration => "fulcio_configuration",
78            CacheKey::TrustedRoot => "trusted_root",
79        }
80    }
81
82    /// Get the recommended TTL for this cache key
83    pub fn default_ttl(&self) -> Duration {
84        match self {
85            // Keys/certs rotate infrequently
86            CacheKey::RekorPublicKey => Duration::from_secs(24 * 60 * 60), // 24 hours
87            CacheKey::FulcioTrustBundle => Duration::from_secs(24 * 60 * 60), // 24 hours
88            CacheKey::TrustedRoot => Duration::from_secs(24 * 60 * 60),    // 24 hours
89            // OIDC config is very stable
90            CacheKey::FulcioConfiguration => Duration::from_secs(7 * 24 * 60 * 60), // 7 days
91            // Log info changes more frequently
92            CacheKey::RekorLogInfo => Duration::from_secs(60 * 60), // 1 hour
93        }
94    }
95}
96
97/// Trait for cache adapters
98///
99/// This trait defines the interface for caching operations. Implementations
100/// can provide different storage backends (filesystem, memory, etc.) while
101/// maintaining the same API.
102pub trait CacheAdapter: Send + Sync {
103    /// Get a cached value by key
104    ///
105    /// Returns `Ok(Some(data))` if the key exists and hasn't expired,
106    /// `Ok(None)` if the key doesn't exist or has expired,
107    /// or `Err(...)` on I/O or other errors.
108    fn get(&self, key: CacheKey) -> CacheGetFuture<'_>;
109
110    /// Set a cached value with a TTL
111    ///
112    /// The value will be considered expired after `ttl` has elapsed.
113    fn set(&self, key: CacheKey, value: &[u8], ttl: Duration) -> CacheOpFuture<'_>;
114
115    /// Remove a cached value
116    fn remove(&self, key: CacheKey) -> CacheOpFuture<'_>;
117
118    /// Clear all cached values
119    fn clear(&self) -> CacheOpFuture<'_>;
120}
121
122/// Extension trait providing convenience methods for caching
123pub trait CacheAdapterExt: CacheAdapter {
124    /// Get a cached value, or compute and cache it if not present
125    fn get_or_set<'a, F, Fut>(
126        &'a self,
127        key: CacheKey,
128        ttl: Duration,
129        compute: F,
130    ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>>> + Send + 'a>>
131    where
132        F: FnOnce() -> Fut + Send + 'a,
133        Fut: Future<Output = Result<Vec<u8>>> + Send + 'a,
134    {
135        Box::pin(async move {
136            // Try to get from cache first
137            if let Some(cached) = self.get(key).await? {
138                return Ok(cached);
139            }
140
141            // Compute the value
142            let value = compute().await?;
143
144            // Store in cache (ignore errors - caching is best-effort)
145            let _ = self.set(key, &value, ttl).await;
146
147            Ok(value)
148        })
149    }
150
151    /// Get a cached value using the key's default TTL for caching
152    fn get_or_set_default<'a, F, Fut>(
153        &'a self,
154        key: CacheKey,
155        compute: F,
156    ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>>> + Send + 'a>>
157    where
158        F: FnOnce() -> Fut + Send + 'a,
159        Fut: Future<Output = Result<Vec<u8>>> + Send + 'a,
160    {
161        self.get_or_set(key, key.default_ttl(), compute)
162    }
163}
164
165// Implement CacheAdapterExt for all CacheAdapter implementations
166impl<T: CacheAdapter + ?Sized> CacheAdapterExt for T {}
167
168// Also implement CacheAdapter for Arc<T> where T: CacheAdapter
169impl<T: CacheAdapter + ?Sized> CacheAdapter for Arc<T> {
170    fn get(&self, key: CacheKey) -> CacheGetFuture<'_> {
171        (**self).get(key)
172    }
173
174    fn set(&self, key: CacheKey, value: &[u8], ttl: Duration) -> CacheOpFuture<'_> {
175        (**self).set(key, value, ttl)
176    }
177
178    fn remove(&self, key: CacheKey) -> CacheOpFuture<'_> {
179        (**self).remove(key)
180    }
181
182    fn clear(&self) -> CacheOpFuture<'_> {
183        (**self).clear()
184    }
185}
186
187// Implement CacheAdapter for Box<dyn CacheAdapter>
188impl CacheAdapter for Box<dyn CacheAdapter> {
189    fn get(&self, key: CacheKey) -> CacheGetFuture<'_> {
190        (**self).get(key)
191    }
192
193    fn set(&self, key: CacheKey, value: &[u8], ttl: Duration) -> CacheOpFuture<'_> {
194        (**self).set(key, value, ttl)
195    }
196
197    fn remove(&self, key: CacheKey) -> CacheOpFuture<'_> {
198        (**self).remove(key)
199    }
200
201    fn clear(&self) -> CacheOpFuture<'_> {
202        (**self).clear()
203    }
204}
205
206/// Get the default cache directory for sigstore-rust
207///
208/// This returns the platform-specific cache directory:
209/// - Linux: `~/.cache/sigstore-rust/`
210/// - macOS: `~/Library/Caches/dev.sigstore.sigstore-rust/`
211/// - Windows: `C:\Users\<User>\AppData\Local\sigstore\sigstore-rust\cache\`
212pub fn default_cache_dir() -> Result<std::path::PathBuf> {
213    let project_dirs = directories::ProjectDirs::from("dev", "sigstore", "sigstore-rust")
214        .ok_or_else(|| Error::Io("Could not determine cache directory".into()))?;
215    Ok(project_dirs.cache_dir().to_path_buf())
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_cache_key_as_str() {
224        assert_eq!(CacheKey::RekorPublicKey.as_str(), "rekor_public_key");
225        assert_eq!(CacheKey::FulcioTrustBundle.as_str(), "fulcio_trust_bundle");
226    }
227
228    #[test]
229    fn test_cache_key_default_ttl() {
230        // Just verify they return reasonable values
231        assert!(CacheKey::RekorPublicKey.default_ttl().as_secs() > 0);
232        assert!(CacheKey::FulcioConfiguration.default_ttl().as_secs() > 0);
233    }
234}