//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging.Bindings { using System; using System.Collections.Generic; using System.Linq; /// /// A in-memory store of crypto keys. /// internal class MemoryCryptoKeyStore : ICryptoKeyStore { /// /// How frequently to check for and remove expired secrets. /// private static readonly TimeSpan cleaningInterval = TimeSpan.FromMinutes(30); /// /// An in-memory cache of decrypted symmetric keys. /// /// /// The key is the bucket name. The value is a dictionary whose key is the handle and whose value is the cached key. /// private readonly Dictionary> store = new Dictionary>(StringComparer.Ordinal); /// /// The last time the cache had expired keys removed from it. /// private DateTime lastCleaning = DateTime.UtcNow; /// /// Gets the key in a given bucket and handle. /// /// The bucket name. Case sensitive. /// The key handle. Case sensitive. /// /// The cryptographic key, or null if no matching key was found. /// public CryptoKey GetKey(string bucket, string handle) { lock (this.store) { Dictionary cacheBucket; if (this.store.TryGetValue(bucket, out cacheBucket)) { CryptoKey key; if (cacheBucket.TryGetValue(handle, out key)) { return key; } } } return null; } /// /// Gets a sequence of existing keys within a given bucket. /// /// The bucket name. Case sensitive. /// /// A sequence of handles and keys, ordered by descending . /// public IEnumerable> GetKeys(string bucket) { lock (this.store) { Dictionary cacheBucket; if (this.store.TryGetValue(bucket, out cacheBucket)) { return cacheBucket.ToList(); } else { return Enumerable.Empty>(); } } } /// /// Stores a cryptographic key. /// /// The name of the bucket to store the key in. Case sensitive. /// The handle to the key, unique within the bucket. Case sensitive. /// The key to store. /// Thrown in the event of a conflict with an existing key in the same bucket and with the same handle. public void StoreKey(string bucket, string handle, CryptoKey key) { lock (this.store) { Dictionary cacheBucket; if (!this.store.TryGetValue(bucket, out cacheBucket)) { this.store[bucket] = cacheBucket = new Dictionary(StringComparer.Ordinal); } if (cacheBucket.ContainsKey(handle)) { throw new CryptoKeyCollisionException(); } cacheBucket[handle] = key; this.CleanExpiredKeysFromMemoryCacheIfAppropriate(); } } /// /// Removes the key. /// /// The bucket name. Case sensitive. /// The key handle. Case sensitive. public void RemoveKey(string bucket, string handle) { lock (this.store) { Dictionary cacheBucket; if (this.store.TryGetValue(bucket, out cacheBucket)) { cacheBucket.Remove(handle); } } } /// /// Cleans the expired keys from memory cache if the cleaning interval has passed. /// private void CleanExpiredKeysFromMemoryCacheIfAppropriate() { if (DateTime.UtcNow > this.lastCleaning + cleaningInterval) { lock (this.store) { if (DateTime.UtcNow > this.lastCleaning + cleaningInterval) { this.ClearExpiredKeysFromMemoryCache(); } } } } /// /// Weeds out expired keys from the in-memory cache. /// private void ClearExpiredKeysFromMemoryCache() { lock (this.store) { var emptyBuckets = new List(); foreach (var bucketPair in this.store) { var expiredKeys = new List(); foreach (var handlePair in bucketPair.Value) { if (handlePair.Value.ExpiresUtc < DateTime.UtcNow) { expiredKeys.Add(handlePair.Key); } } foreach (var expiredKey in expiredKeys) { bucketPair.Value.Remove(expiredKey); } if (bucketPair.Value.Count == 0) { emptyBuckets.Add(bucketPair.Key); } } foreach (string emptyBucket in emptyBuckets) { this.store.Remove(emptyBucket); } this.lastCleaning = DateTime.UtcNow; } } } }