//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Messaging.Bindings {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using DotNetOpenAuth.Messaging;
using Validation;
///
/// Provides RSA encryption of symmetric keys to protect them from a theft of
/// the persistent store.
///
public class AsymmetricCryptoKeyStoreWrapper : ICryptoKeyStore {
///
/// The persistent store for asymmetrically encrypted symmetric keys.
///
private readonly ICryptoKeyStore dataStore;
///
/// The memory cache of decrypted keys.
///
private readonly MemoryCryptoKeyStore cache = new MemoryCryptoKeyStore();
///
/// The asymmetric algorithm to use encrypting/decrypting the symmetric keys.
///
private readonly RSACryptoServiceProvider asymmetricCrypto;
///
/// Initializes a new instance of the class.
///
/// The data store.
/// The asymmetric protection to apply to symmetric keys. Must include the private key.
public AsymmetricCryptoKeyStoreWrapper(ICryptoKeyStore dataStore, RSACryptoServiceProvider asymmetricCrypto) {
Requires.NotNull(dataStore, "dataStore");
Requires.NotNull(asymmetricCrypto, "asymmetricCrypto");
Requires.That(!asymmetricCrypto.PublicOnly, "asymmetricCrypto", "Private key required.");
this.dataStore = dataStore;
this.asymmetricCrypto = asymmetricCrypto;
}
///
/// 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) {
var key = this.dataStore.GetKey(bucket, handle);
return this.Decrypt(bucket, handle, key);
}
///
/// 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) {
return this.dataStore.GetKeys(bucket)
.Select(pair => new KeyValuePair(pair.Key, this.Decrypt(bucket, pair.Key, pair.Value)));
}
///
/// 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.
[SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Helps readability because multiple keys are involved.")]
public void StoreKey(string bucket, string handle, CryptoKey decryptedCryptoKey) {
byte[] encryptedKey = this.asymmetricCrypto.Encrypt(decryptedCryptoKey.Key, true);
var encryptedCryptoKey = new CryptoKey(encryptedKey, decryptedCryptoKey.ExpiresUtc);
this.dataStore.StoreKey(bucket, handle, encryptedCryptoKey);
this.cache.StoreKey(bucket, handle, new CachedCryptoKey(encryptedCryptoKey, decryptedCryptoKey));
}
///
/// Removes the key.
///
/// The bucket name. Case sensitive.
/// The key handle. Case sensitive.
public void RemoveKey(string bucket, string handle) {
this.dataStore.RemoveKey(bucket, handle);
this.cache.RemoveKey(bucket, handle);
}
///
/// Decrypts the specified key.
///
/// The bucket.
/// The handle.
/// The encrypted key.
///
/// The decrypted key.
///
[SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Helps readability because multiple keys are involved.")]
private CryptoKey Decrypt(string bucket, string handle, CryptoKey encryptedCryptoKey) {
if (encryptedCryptoKey == null) {
return null;
}
// Avoid the asymmetric decryption if possible by looking up whether we have that in our cache.
CachedCryptoKey cached = (CachedCryptoKey)this.cache.GetKey(bucket, handle);
if (cached != null && MessagingUtilities.AreEquivalent(cached.EncryptedKey, encryptedCryptoKey.Key)) {
return cached;
}
byte[] decryptedKey = this.asymmetricCrypto.Decrypt(encryptedCryptoKey.Key, true);
var decryptedCryptoKey = new CryptoKey(decryptedKey, encryptedCryptoKey.ExpiresUtc);
// Store the decrypted version in the cache to save time next time.
this.cache.StoreKey(bucket, handle, new CachedCryptoKey(encryptedCryptoKey, decryptedCryptoKey));
return decryptedCryptoKey;
}
///
/// An encrypted key and its decrypted equivalent.
///
private class CachedCryptoKey : CryptoKey {
///
/// Initializes a new instance of the class.
///
/// The encrypted key.
/// The decrypted key.
internal CachedCryptoKey(CryptoKey encrypted, CryptoKey decrypted)
: base(decrypted.Key, decrypted.ExpiresUtc) {
Requires.NotNull(encrypted, "encrypted");
Requires.NotNull(decrypted, "decrypted");
Requires.That(encrypted.ExpiresUtc == decrypted.ExpiresUtc, "encrypted", "encrypted and decrypted expirations must equal.");
this.EncryptedKey = encrypted.Key;
}
///
/// Gets the encrypted key.
///
internal byte[] EncryptedKey { get; private set; }
}
}
}