//-----------------------------------------------------------------------
//
// 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;
}
}
}
}