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