Defer processing <key> descriptors until necessary

- Make CreateNewKey more robust against bad key repositories
- Don't hide key deserialization errors
This commit is contained in:
Levi B 2015-03-16 11:38:11 -07:00
parent 4365b531d8
commit 612a81d9ce
18 changed files with 526 additions and 148 deletions

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
private readonly CancellationToken _expirationToken;
internal CacheableKeyRing(CancellationToken expirationToken, DateTimeOffset expirationTime, IKey defaultKey, IEnumerable<IKey> allKeys)
: this(expirationToken, expirationTime, keyRing: new KeyRing(defaultKey.KeyId, allKeys))
: this(expirationToken, expirationTime, keyRing: new KeyRing(defaultKey, allKeys))
{
}

View File

@ -10,6 +10,10 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
/// <summary>
/// The default key, may be null if no key is a good default candidate.
/// </summary>
/// <remarks>
/// If this property is non-null, its <see cref="IKey.CreateEncryptorInstance"/> method will succeed
/// so is appropriate for use with deferred keys.
/// </remarks>
public IKey DefaultKey;
/// <summary>
@ -17,6 +21,10 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
/// honor the <see cref="ShouldGenerateNewKey"/> property. This property may
/// be null if there is no viable fallback key.
/// </summary>
/// <remarks>
/// If this property is non-null, its <see cref="IKey.CreateEncryptorInstance"/> method will succeed
/// so is appropriate for use with deferred keys.
/// </remarks>
public IKey FallbackKey;
/// <summary>

View File

@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.DataProtection.KeyManagement
@ -43,11 +45,21 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
_logger = services.GetLogger<DefaultKeyResolver>();
}
public DefaultKeyResolution ResolveDefaultKeyPolicy(DateTimeOffset now, IEnumerable<IKey> allKeys)
private bool CanCreateAuthenticatedEncryptor(IKey key)
{
DefaultKeyResolution retVal = default(DefaultKeyResolution);
retVal.DefaultKey = FindDefaultKey(now, allKeys, out retVal.FallbackKey, out retVal.ShouldGenerateNewKey);
return retVal;
try
{
var encryptorInstance = key.CreateEncryptorInstance() ?? CryptoUtil.Fail<IAuthenticatedEncryptor>("CreateEncryptorInstance returned null.");
return true;
}
catch (Exception ex)
{
if (_logger.IsWarningLevelEnabled())
{
_logger.LogWarningF(ex, $"Key {key.KeyId:B} is ineligible to be the default key because its {nameof(IKey.CreateEncryptorInstance)} method failed.");
}
return false;
}
}
private IKey FindDefaultKey(DateTimeOffset now, IEnumerable<IKey> allKeys, out IKey fallbackKey, out bool callerShouldGenerateNewKey)
@ -66,11 +78,11 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
}
// if the key has been revoked or is expired, it is no longer a candidate
if (preferredDefaultKey.IsExpired(now) || preferredDefaultKey.IsRevoked)
if (preferredDefaultKey.IsRevoked || preferredDefaultKey.IsExpired(now) || !CanCreateAuthenticatedEncryptor(preferredDefaultKey))
{
if (_logger.IsVerboseLevelEnabled())
{
_logger.LogVerboseF($"Key {preferredDefaultKey.KeyId:B} is no longer under consideration as default key because it is expired or revoked.");
_logger.LogVerboseF($"Key {preferredDefaultKey.KeyId:B} is no longer under consideration as default key because it is expired, revoked, or cannot be deciphered.");
}
preferredDefaultKey = null;
}
@ -112,7 +124,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
select key).Concat(from key in allKeys
orderby key.CreationDate ascending
select key)
where !key.IsRevoked
where !key.IsRevoked && CanCreateAuthenticatedEncryptor(key)
select key).FirstOrDefault();
if (_logger.IsVerboseLevelEnabled())
@ -123,5 +135,12 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
callerShouldGenerateNewKey = true;
return null;
}
public DefaultKeyResolution ResolveDefaultKeyPolicy(DateTimeOffset now, IEnumerable<IKey> allKeys)
{
DefaultKeyResolution retVal = default(DefaultKeyResolution);
retVal.DefaultKey = FindDefaultKey(now, allKeys, out retVal.FallbackKey, out retVal.ShouldGenerateNewKey);
return retVal;
}
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Xml.Linq;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNet.DataProtection.XmlEncryption;
namespace Microsoft.AspNet.DataProtection.KeyManagement
{
/// <summary>
/// The basic implementation of <see cref="IKey"/>, where the incoming XML element
/// hasn't yet been fully processed.
/// </summary>
internal sealed class DeferredKey : KeyBase
{
public DeferredKey(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate, IInternalXmlKeyManager keyManager, XElement keyElement)
: base(keyId, creationDate, activationDate, expirationDate, new Lazy<IAuthenticatedEncryptor>(GetLazyEncryptorDelegate(keyManager, keyElement)))
{
}
private static Func<IAuthenticatedEncryptor> GetLazyEncryptorDelegate(IInternalXmlKeyManager keyManager, XElement keyElement)
{
// The <key> element will be held around in memory for a potentially lengthy period
// of time. Since it might contain sensitive information, we should protect it.
var encryptedKeyElement = keyElement.ToSecret();
try
{
return () => keyManager.DeserializeDescriptorFromKeyElement(encryptedKeyElement.ToXElement()).CreateEncryptorInstance();
}
finally
{
// It's important that the lambda above doesn't capture 'descriptorElement'. Clearing the reference here
// helps us detect if we've done this by causing a null ref at runtime.
keyElement = null;
}
}
}
}

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Xml.Linq;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel;
namespace Microsoft.AspNet.DataProtection.KeyManagement
{
@ -9,6 +11,9 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
internal interface IInternalXmlKeyManager
{
IKey CreateNewKey(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate);
IAuthenticatedEncryptorDescriptor DeserializeDescriptorFromKeyElement(XElement keyElement);
void RevokeSingleKey(Guid keyId, DateTimeOffset revocationDate, string reason);
}
}

View File

@ -8,40 +8,14 @@ using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
namespace Microsoft.AspNet.DataProtection.KeyManagement
{
/// <summary>
/// The basic implementation of <see cref="IKey"/>.
/// The basic implementation of <see cref="IKey"/>, where the <see cref="IAuthenticatedEncryptorDescriptor"/>
/// has already been created.
/// </summary>
internal sealed class Key : IKey
internal sealed class Key : KeyBase
{
private readonly IAuthenticatedEncryptorDescriptor _descriptor;
public Key(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate, IAuthenticatedEncryptorDescriptor descriptor)
: base(keyId, creationDate, activationDate, expirationDate, new Lazy<IAuthenticatedEncryptor>(descriptor.CreateEncryptorInstance))
{
KeyId = keyId;
CreationDate = creationDate;
ActivationDate = activationDate;
ExpirationDate = expirationDate;
_descriptor = descriptor;
}
public DateTimeOffset ActivationDate { get; }
public DateTimeOffset CreationDate { get; }
public DateTimeOffset ExpirationDate { get; }
public bool IsRevoked { get; private set; }
public Guid KeyId { get; }
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return _descriptor.CreateEncryptorInstance();
}
internal void SetRevoked()
{
IsRevoked = true;
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
namespace Microsoft.AspNet.DataProtection.KeyManagement
{
/// <summary>
/// The basic implementation of <see cref="IKey"/>.
/// </summary>
internal abstract class KeyBase : IKey
{
private readonly Lazy<IAuthenticatedEncryptor> _lazyEncryptor;
public KeyBase(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate, Lazy<IAuthenticatedEncryptor> lazyEncryptor)
{
KeyId = keyId;
CreationDate = creationDate;
ActivationDate = activationDate;
ExpirationDate = expirationDate;
_lazyEncryptor = lazyEncryptor;
}
public DateTimeOffset ActivationDate { get; }
public DateTimeOffset CreationDate { get; }
public DateTimeOffset ExpirationDate { get; }
public bool IsRevoked { get; private set; }
public Guid KeyId { get; }
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return _lazyEncryptor.Value;
}
internal void SetRevoked()
{
IsRevoked = true;
}
}
}

View File

@ -16,16 +16,24 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
private readonly KeyHolder _defaultKeyHolder;
private readonly Dictionary<Guid, KeyHolder> _keyIdToKeyHolderMap;
public KeyRing(Guid defaultKeyId, IEnumerable<IKey> keys)
public KeyRing(IKey defaultKey, IEnumerable<IKey> allKeys)
{
_keyIdToKeyHolderMap = new Dictionary<Guid, KeyHolder>();
foreach (IKey key in keys)
foreach (IKey key in allKeys)
{
_keyIdToKeyHolderMap.Add(key.KeyId, new KeyHolder(key));
}
DefaultKeyId = defaultKeyId;
_defaultKeyHolder = _keyIdToKeyHolderMap[defaultKeyId];
// It's possible under some circumstances that the default key won't be part of 'allKeys',
// such as if the key manager is forced to use the key it just generated even if such key
// wasn't in the underlying repository. In this case, we just add it now.
if (!_keyIdToKeyHolderMap.ContainsKey(defaultKey.KeyId))
{
_keyIdToKeyHolderMap.Add(defaultKey.KeyId, new KeyHolder(defaultKey));
}
DefaultKeyId = defaultKey.KeyId;
_defaultKeyHolder = _keyIdToKeyHolderMap[DefaultKeyId];
}
public IAuthenticatedEncryptor DefaultAuthenticatedEncryptor

View File

@ -31,7 +31,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
?? new DefaultKeyResolver(_keyManagementOptions.KeyPropagationWindow, _keyManagementOptions.MaxServerClockSkew, services);
}
private CacheableKeyRing CreateCacheableKeyRingCore(DateTimeOffset now, bool allowRecursiveCalls = false)
private CacheableKeyRing CreateCacheableKeyRingCore(DateTimeOffset now, IKey keyJustAdded)
{
// Refresh the list of all keys
var cacheExpirationToken = _keyManager.GetCacheExpirationToken();
@ -50,21 +50,18 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
_logger.LogVerbose("Policy resolution states that a new key should be added to the key ring.");
}
// At this point, we know we need to generate a new key.
// This should only occur if a call to CreateNewKey immediately followed by a call to
// GetAllKeys returned 'you need to add a key to the key ring'. This should never happen
// in practice unless there's corruption in the backing store. Regardless, we can't recurse
// forever, so we have to bail now.
if (!allowRecursiveCalls)
// We shouldn't call CreateKey more than once, else we risk stack diving. This code path shouldn't
// get hit unless there was an ineligible key with an activation date slightly later than the one we
// just added. If this does happen, then we'll just use whatever key we can instead of creating
// new keys endlessly, eventually falling back to the one we just added if all else fails.
if (keyJustAdded != null)
{
if (_logger.IsErrorLevelEnabled())
{
_logger.LogError("Policy resolution states that a new key should be added to the key ring, even after a call to CreateNewKey.");
}
throw CryptoUtil.Fail("Policy resolution states that a new key should be added to the key ring, even after a call to CreateNewKey.");
var keyToUse = defaultKeyPolicy.DefaultKey ?? defaultKeyPolicy.FallbackKey ?? keyJustAdded;
return CreateCacheableKeyRingCoreStep2(now, cacheExpirationToken, keyToUse, allKeys);
}
// At this point, we know we need to generate a new key.
// We have been asked to generate a new key, but auto-generation of keys has been disabled.
// We need to use the fallback key or fail.
if (!_keyManagementOptions.AutoGenerateKeys)
@ -92,16 +89,16 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
{
// The case where there's no default key is the easiest scenario, since it
// means that we need to create a new key with immediate activation.
_keyManager.CreateNewKey(activationDate: now, expirationDate: now + _keyManagementOptions.NewKeyLifetime);
return CreateCacheableKeyRingCore(now); // recursively call
var newKey = _keyManager.CreateNewKey(activationDate: now, expirationDate: now + _keyManagementOptions.NewKeyLifetime);
return CreateCacheableKeyRingCore(now, keyJustAdded: newKey); // recursively call
}
else
{
// If there is a default key, then the new key we generate should become active upon
// expiration of the default key. The new key lifetime is measured from the creation
// date (now), not the activation date.
_keyManager.CreateNewKey(activationDate: defaultKeyPolicy.DefaultKey.ExpirationDate, expirationDate: now + _keyManagementOptions.NewKeyLifetime);
return CreateCacheableKeyRingCore(now); // recursively call
var newKey = _keyManager.CreateNewKey(activationDate: defaultKeyPolicy.DefaultKey.ExpirationDate, expirationDate: now + _keyManagementOptions.NewKeyLifetime);
return CreateCacheableKeyRingCore(now, keyJustAdded: newKey); // recursively call
}
}
@ -109,6 +106,9 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
{
Debug.Assert(defaultKey != null);
// Invariant: our caller ensures that CreateEncryptorInstance succeeded at least once
Debug.Assert(defaultKey.CreateEncryptorInstance() != null);
if (_logger.IsVerboseLevelEnabled())
{
_logger.LogVerboseF($"Using key {defaultKey.KeyId:B} as the default key.");
@ -186,7 +186,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
CacheableKeyRing ICacheableKeyRingProvider.GetCacheableKeyRing(DateTimeOffset now)
{
// the entry point allows one recursive call
return CreateCacheableKeyRingCore(now, allowRecursiveCalls: true);
return CreateCacheableKeyRingCore(now, keyJustAdded: null);
}
}
}

View File

@ -122,7 +122,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
var allElements = KeyRepository.GetAllElements();
// We aggregate all the information we read into three buckets
Dictionary<Guid, Key> keyIdToKeyMap = new Dictionary<Guid, Key>();
Dictionary<Guid, KeyBase> keyIdToKeyMap = new Dictionary<Guid, KeyBase>();
HashSet<Guid> revokedKeyIds = null;
DateTimeOffset? mostRecentMassRevocationDate = null;
@ -132,7 +132,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
{
// ProcessKeyElement can return null in the case of failure, and if this happens we'll move on.
// Still need to throw if we see duplicate keys with the same id.
Key key = ProcessKeyElement(element);
KeyBase key = ProcessKeyElement(element);
if (key != null)
{
if (keyIdToKeyMap.ContainsKey(key.KeyId))
@ -179,7 +179,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
{
foreach (Guid revokedKeyId in revokedKeyIds)
{
Key key;
KeyBase key;
keyIdToKeyMap.TryGetValue(revokedKeyId, out key);
if (key != null)
{
@ -224,60 +224,36 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
return Interlocked.CompareExchange(ref _cacheExpirationTokenSource, null, null).Token;
}
private Key ProcessKeyElement(XElement keyElement)
private KeyBase ProcessKeyElement(XElement keyElement)
{
Debug.Assert(keyElement.Name == KeyElementName);
try
{
// Read metadata
// Read metadata and prepare the key for deferred instantiation
Guid keyId = (Guid)keyElement.Attribute(IdAttributeName);
DateTimeOffset creationDate = (DateTimeOffset)keyElement.Element(CreationDateElementName);
DateTimeOffset activationDate = (DateTimeOffset)keyElement.Element(ActivationDateElementName);
DateTimeOffset expirationDate = (DateTimeOffset)keyElement.Element(ExpirationDateElementName);
// Figure out who will be deserializing this
XElement descriptorElement = keyElement.Element(DescriptorElementName);
string descriptorDeserializerTypeName = (string)descriptorElement.Attribute(DeserializerTypeAttributeName);
// Decrypt the descriptor element and pass it to the descriptor for consumption
XElement unencryptedInputToDeserializer = descriptorElement.Elements().Single().DecryptElement(_activator);
var deserializerInstance = _activator.CreateInstance<IAuthenticatedEncryptorDescriptorDeserializer>(descriptorDeserializerTypeName);
var descriptorInstance = deserializerInstance.ImportFromXml(unencryptedInputToDeserializer);
// Finally, create the Key instance
if (_logger.IsVerboseLevelEnabled())
{
_logger.LogVerboseF($"Found key {keyId:B}.");
}
return new Key(
return new DeferredKey(
keyId: keyId,
creationDate: creationDate,
activationDate: activationDate,
expirationDate: expirationDate,
descriptor: descriptorInstance);
keyManager: _internalKeyManager,
keyElement: keyElement);
}
catch (Exception ex)
{
// We only write the exception out to the 'debug' log since it could contain sensitive
// information and we don't want to leak it.
if (_logger.IsDebugLevelEnabled())
{
if (_logger.IsWarningLevelEnabled())
{
_logger.LogWarningF($"An exception of type '{ex.GetType().FullName}' occurred while processing the key element '{keyElement.WithoutChildNodes()}', so the key will not be included in the keyring. Full details of the exception will be written to the 'Debug' log.");
}
_logger.LogDebugF(ex, $"An exception occurred while processing the key element '{keyElement}'.");
}
else
{
if (_logger.IsWarningLevelEnabled())
{
_logger.LogWarningF($"An exception of type '{ex.GetType().FullName}' occurred while processing the key element '{keyElement.WithoutChildNodes()}', so the key will not be included in the keyring. To prevent accidental disclosure of sensitive information the full exception details are not being logged. To enable logging full exception details, enable 'Debug' level logging for this provider.");
}
}
WriteKeyDeserializationErrorToLog(ex, keyElement);
// If an error occurs, we just skip this key.
// Don't include this key in the key ring
return null;
}
}
@ -369,6 +345,26 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
Interlocked.Exchange(ref _cacheExpirationTokenSource, new CancellationTokenSource())?.Cancel();
}
private void WriteKeyDeserializationErrorToLog(Exception error, XElement keyElement)
{
// Ideally we'd suppress the error since it might contain sensitive information, but it would be too difficult for
// an administrator to diagnose the issue if we hide this information. Instead we'll log the error to the error
// log and the raw <key> element to the debug log. This works for our out-of-box XML decryptors since they don't
// include sensitive information in the exception message.
if (_logger.IsErrorLevelEnabled())
{
// write sanitized <key> element
_logger.LogErrorF(error, $"An exception occurred while processing the key element '{keyElement.WithoutChildNodes()}'.");
}
if (_logger.IsDebugLevelEnabled())
{
// write full <key> element
_logger.LogDebugF(error, $"An exception occurred while processing the key element '{keyElement}'.");
}
}
IKey IInternalXmlKeyManager.CreateNewKey(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate)
{
// <key id="{guid}" version="1">
@ -440,6 +436,28 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
descriptor: newDescriptor);
}
IAuthenticatedEncryptorDescriptor IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(XElement keyElement)
{
try
{
// Figure out who will be deserializing this
XElement descriptorElement = keyElement.Element(DescriptorElementName);
string descriptorDeserializerTypeName = (string)descriptorElement.Attribute(DeserializerTypeAttributeName);
// Decrypt the descriptor element and pass it to the descriptor for consumption
XElement unencryptedInputToDeserializer = descriptorElement.Elements().Single().DecryptElement(_activator);
var deserializerInstance = _activator.CreateInstance<IAuthenticatedEncryptorDescriptorDeserializer>(descriptorDeserializerTypeName);
var descriptorInstance = deserializerInstance.ImportFromXml(unencryptedInputToDeserializer);
return descriptorInstance ?? CryptoUtil.Fail<IAuthenticatedEncryptorDescriptor>("ImportFromXml returned null.");
}
catch (Exception ex)
{
WriteKeyDeserializationErrorToLog(ex, keyElement);
throw;
}
}
void IInternalXmlKeyManager.RevokeSingleKey(Guid keyId, DateTimeOffset revocationDate, string reason)
{
// <revocation version="1">

View File

@ -123,8 +123,8 @@ namespace Microsoft.AspNet.DataProtection.XmlEncryption
}
/// <summary>
/// Converts an <see cref="XElement"/> to a <see cref="Secret"/> so that it can be run through
/// the DPAPI routines.
/// Converts an <see cref="XElement"/> to a <see cref="Secret"/> so that it can be kept in memory
/// securely or run through the DPAPI routines.
/// </summary>
public static Secret ToSecret(this XElement element)
{
@ -163,7 +163,7 @@ namespace Microsoft.AspNet.DataProtection.XmlEncryption
}
/// <summary>
/// Converts a <see cref="Secret"/> provided by the DPAPI routines back into an <see cref="XElement"/>.
/// Converts a <see cref="Secret"/> back into an <see cref="XElement"/>.
/// </summary>
public static XElement ToXElement(this Secret secret)
{

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Moq;
using Xunit;
@ -105,7 +106,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
}
[Fact]
public void ResolveDefaultKeyPolicy_MostRecentKeyIsInvalid_ReturnsNull()
public void ResolveDefaultKeyPolicy_MostRecentKeyIsInvalid_BecauseOfRevocation_ReturnsNull()
{
// Arrange
var resolver = CreateDefaultKeyResolver();
@ -120,6 +121,22 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
Assert.True(resolution.ShouldGenerateNewKey);
}
[Fact]
public void ResolveDefaultKeyPolicy_MostRecentKeyIsInvalid_BecauseOfFailureToDecipher_ReturnsNull()
{
// Arrange
var resolver = CreateDefaultKeyResolver();
var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z");
var key2 = CreateKey("2015-03-02 00:00:00Z", "2016-03-01 00:00:00Z", createEncryptorInstanceThrows: true);
// Act
var resolution = resolver.ResolveDefaultKeyPolicy("2015-04-01 00:00:00Z", key1, key2);
// Assert
Assert.Null(resolution.DefaultKey);
Assert.True(resolution.ShouldGenerateNewKey);
}
[Fact]
public void ResolveDefaultKeyPolicy_FutureKeyIsValidAndWithinClockSkew_ReturnsFutureKey()
{
@ -168,7 +185,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
}
[Fact]
public void ResolveDefaultKeyPolicy_FallbackKey_SelectsLatestBeforePriorPropagationWindow()
public void ResolveDefaultKeyPolicy_FallbackKey_SelectsLatestBeforePriorPropagationWindow_IgnoresRevokedKeys()
{
// Arrange
var resolver = CreateDefaultKeyResolver();
@ -185,6 +202,24 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
Assert.True(resolution.ShouldGenerateNewKey);
}
[Fact]
public void ResolveDefaultKeyPolicy_FallbackKey_SelectsLatestBeforePriorPropagationWindow_IgnoresFailures()
{
// Arrange
var resolver = CreateDefaultKeyResolver();
var key1 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-01 00:00:00Z");
var key2 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-02 00:00:00Z");
var key3 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-03 00:00:00Z", createEncryptorInstanceThrows: true);
var key4 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-04 00:00:00Z");
// Act
var resolution = resolver.ResolveDefaultKeyPolicy("2000-01-05 00:00:00Z", key1, key2, key3, key4);
// Assert
Assert.Same(key2, resolution.FallbackKey);
Assert.True(resolution.ShouldGenerateNewKey);
}
[Fact]
public void ResolveDefaultKeyPolicy_FallbackKey_NoNonRevokedKeysBeforePriorPropagationWindow_SelectsEarliestNonRevokedKey()
{
@ -210,7 +245,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
services: null);
}
private static IKey CreateKey(string activationDate, string expirationDate, string creationDate = null, bool isRevoked = false)
private static IKey CreateKey(string activationDate, string expirationDate, string creationDate = null, bool isRevoked = false, bool createEncryptorInstanceThrows = false)
{
var mockKey = new Mock<IKey>();
mockKey.Setup(o => o.KeyId).Returns(Guid.NewGuid());
@ -218,6 +253,14 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
mockKey.Setup(o => o.ActivationDate).Returns(DateTimeOffset.ParseExact(activationDate, "u", CultureInfo.InvariantCulture));
mockKey.Setup(o => o.ExpirationDate).Returns(DateTimeOffset.ParseExact(expirationDate, "u", CultureInfo.InvariantCulture));
mockKey.Setup(o => o.IsRevoked).Returns(isRevoked);
if (createEncryptorInstanceThrows)
{
mockKey.Setup(o => o.CreateEncryptorInstance()).Throws(new Exception("This method fails."));
}
else
{
mockKey.Setup(o => o.CreateEncryptorInstance()).Returns(new Mock<IAuthenticatedEncryptor>().Object);
}
return mockKey.Object;
}
}

View File

@ -0,0 +1,95 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Xml.Linq;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNet.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNet.DataProtection.KeyManagement
{
public class DeferredKeyTests
{
[Fact]
public void Ctor_Properties()
{
// Arrange
var keyId = Guid.NewGuid();
var creationDate = DateTimeOffset.Now;
var activationDate = creationDate.AddDays(2);
var expirationDate = creationDate.AddDays(90);
// Act
var key = new DeferredKey(keyId, creationDate, activationDate, expirationDate, new Mock<IInternalXmlKeyManager>().Object, XElement.Parse(@"<node />"));
// Assert
Assert.Equal(keyId, key.KeyId);
Assert.Equal(creationDate, key.CreationDate);
Assert.Equal(activationDate, key.ActivationDate);
Assert.Equal(expirationDate, key.ExpirationDate);
}
[Fact]
public void SetRevoked_Respected()
{
// Arrange
var now = DateTimeOffset.UtcNow;
var key = new DeferredKey(Guid.Empty, now, now, now, new Mock<IInternalXmlKeyManager>().Object, XElement.Parse(@"<node />"));
// Act & assert
Assert.False(key.IsRevoked);
key.SetRevoked();
Assert.True(key.IsRevoked);
}
[Fact]
public void CreateEncryptorInstance_Success()
{
// Arrange
var expectedEncryptor = new Mock<IAuthenticatedEncryptor>().Object;
var mockDescriptor = new Mock<IAuthenticatedEncryptorDescriptor>();
mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(expectedEncryptor);
var mockKeyManager = new Mock<IInternalXmlKeyManager>();
mockKeyManager.Setup(o => o.DeserializeDescriptorFromKeyElement(It.IsAny<XElement>()))
.Returns<XElement>(element =>
{
XmlAssert.Equal(@"<node />", element);
return mockDescriptor.Object;
});
var now = DateTimeOffset.UtcNow;
var key = new DeferredKey(Guid.Empty, now, now, now, mockKeyManager.Object, XElement.Parse(@"<node />"));
// Act
var actual = key.CreateEncryptorInstance();
// Assert
Assert.Same(expectedEncryptor, actual);
}
[Fact]
public void CreateEncryptorInstance_CachesFailures()
{
// Arrange
int numTimesCalled = 0;
var mockKeyManager = new Mock<IInternalXmlKeyManager>();
mockKeyManager.Setup(o => o.DeserializeDescriptorFromKeyElement(It.IsAny<XElement>()))
.Returns<XElement>(element =>
{
numTimesCalled++;
throw new Exception("How exceptional.");
});
var now = DateTimeOffset.UtcNow;
var key = new DeferredKey(Guid.Empty, now, now, now, mockKeyManager.Object, XElement.Parse(@"<node />"));
// Act & assert
ExceptionAssert.Throws<Exception>(() => key.CreateEncryptorInstance(), "How exceptional.");
ExceptionAssert.Throws<Exception>(() => key.CreateEncryptorInstance(), "How exceptional.");
Assert.Equal(1, numTimesCalled);
}
}
}

View File

@ -203,7 +203,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
// the keyring has only one key
Key key = new Key(Guid.Empty, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object);
var keyRing = new KeyRing(Guid.Empty, new[] { key });
var keyRing = new KeyRing(key, new[] { key });
var mockKeyRingProvider = new Mock<IKeyRingProvider>();
mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
@ -233,7 +233,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
// the keyring has only one key
Key key = new Key(keyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object);
key.SetRevoked();
var keyRing = new KeyRing(keyId, new[] { key });
var keyRing = new KeyRing(key, new[] { key });
var mockKeyRingProvider = new Mock<IKeyRingProvider>();
mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
@ -272,7 +272,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
Key defaultKey = new Key(defaultKeyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object);
defaultKey.SetRevoked();
var keyRing = new KeyRing(defaultKeyId, new[] { defaultKey });
var keyRing = new KeyRing(defaultKey, new[] { defaultKey });
var mockKeyRingProvider = new Mock<IKeyRingProvider>();
mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
@ -318,7 +318,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(mockEncryptor.Object);
Key defaultKey = new Key(defaultKeyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object);
var keyRing = new KeyRing(defaultKeyId, new[] { defaultKey });
var keyRing = new KeyRing(defaultKey, new[] { defaultKey });
var mockKeyRingProvider = new Mock<IKeyRingProvider>();
mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
@ -368,7 +368,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
Key defaultKey = new Key(defaultKeyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, new Mock<IAuthenticatedEncryptorDescriptor>().Object);
Key embeddedKey = new Key(embeddedKeyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object);
var keyRing = new KeyRing(defaultKeyId, new[] { defaultKey, embeddedKey });
var keyRing = new KeyRing(defaultKey, new[] { defaultKey, embeddedKey });
var mockKeyRingProvider = new Mock<IKeyRingProvider>();
mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
@ -399,7 +399,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
// Arrange
byte[] plaintext = new byte[] { 0x10, 0x20, 0x30, 0x40, 0x50 };
Key key = new Key(Guid.NewGuid(), DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, new AuthenticatedEncryptorConfiguration(new AuthenticatedEncryptionOptions()).CreateNewDescriptor());
var keyRing = new KeyRing(key.KeyId, new[] { key });
var keyRing = new KeyRing(key, new[] { key });
var mockKeyRingProvider = new Mock<IKeyRingProvider>();
mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);

View File

@ -6,10 +6,13 @@ using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Microsoft.Framework.DependencyInjection;
using Moq;
using Xunit;
using static System.FormattableString;
namespace Microsoft.AspNet.DataProtection.KeyManagement
{
public class KeyRingProviderTests
@ -110,7 +113,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
getCacheExpirationTokenReturnValues: new[] { expirationCts1.Token, expirationCts2.Token },
getAllKeysReturnValues: new[] { allKeys1, allKeys2 },
createNewKeyCallbacks: new[] {
Tuple.Create((DateTimeOffset)now, (DateTimeOffset)now + TimeSpan.FromDays(90))
Tuple.Create((DateTimeOffset)now, (DateTimeOffset)now + TimeSpan.FromDays(90), CreateKey())
},
resolveDefaultKeyPolicyReturnValues: new[]
{
@ -140,6 +143,54 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy", "CreateNewKey", "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
}
[Fact]
public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_CreatesNewKeyWithImmediateActivation_StillNoDefaultKey_ReturnsNewlyCreatedKey()
{
// Arrange
var callSequence = new List<string>();
var expirationCts1 = new CancellationTokenSource();
var expirationCts2 = new CancellationTokenSource();
var now = StringToDateTime("2015-03-01 00:00:00Z");
var allKeys = new IKey[0];
var newlyCreatedKey = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z");
var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
callSequence: callSequence,
getCacheExpirationTokenReturnValues: new[] { expirationCts1.Token, expirationCts2.Token },
getAllKeysReturnValues: new[] { allKeys, allKeys },
createNewKeyCallbacks: new[] {
Tuple.Create((DateTimeOffset)now, (DateTimeOffset)now + TimeSpan.FromDays(90), newlyCreatedKey)
},
resolveDefaultKeyPolicyReturnValues: new[]
{
Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys, new DefaultKeyResolution()
{
DefaultKey = null,
ShouldGenerateNewKey = true
}),
Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys, new DefaultKeyResolution()
{
DefaultKey = null,
ShouldGenerateNewKey = true
})
});
// Act
var cacheableKeyRing = keyRingProvider.GetCacheableKeyRing(now);
// Assert
Assert.Equal(newlyCreatedKey.KeyId, cacheableKeyRing.KeyRing.DefaultKeyId);
AssertWithinJitterRange(cacheableKeyRing.ExpirationTimeUtc, now);
Assert.True(CacheableKeyRing.IsValid(cacheableKeyRing, now));
expirationCts1.Cancel();
Assert.True(CacheableKeyRing.IsValid(cacheableKeyRing, now));
expirationCts2.Cancel();
Assert.False(CacheableKeyRing.IsValid(cacheableKeyRing, now));
Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy", "CreateNewKey", "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
}
[Fact]
public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_KeyGenerationDisabled_Fails()
{
@ -154,7 +205,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
getCacheExpirationTokenReturnValues: new[] { CancellationToken.None },
getAllKeysReturnValues: new[] { allKeys },
createNewKeyCallbacks: new[] {
Tuple.Create((DateTimeOffset)now, (DateTimeOffset)now + TimeSpan.FromDays(90))
Tuple.Create((DateTimeOffset)now, (DateTimeOffset)now + TimeSpan.FromDays(90), CreateKey())
},
resolveDefaultKeyPolicyReturnValues: new[]
{
@ -194,7 +245,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
getCacheExpirationTokenReturnValues: new[] { expirationCts1.Token, expirationCts2.Token },
getAllKeysReturnValues: new[] { allKeys1, allKeys2 },
createNewKeyCallbacks: new[] {
Tuple.Create(key1.ExpirationDate, (DateTimeOffset)now + TimeSpan.FromDays(90))
Tuple.Create(key1.ExpirationDate, (DateTimeOffset)now + TimeSpan.FromDays(90), CreateKey())
},
resolveDefaultKeyPolicyReturnValues: new[]
{
@ -304,7 +355,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
IList<string> callSequence,
IEnumerable<CancellationToken> getCacheExpirationTokenReturnValues,
IEnumerable<IReadOnlyCollection<IKey>> getAllKeysReturnValues,
IEnumerable<Tuple<DateTimeOffset,DateTimeOffset>> createNewKeyCallbacks,
IEnumerable<Tuple<DateTimeOffset, DateTimeOffset, IKey>> createNewKeyCallbacks,
IEnumerable<Tuple<DateTimeOffset, IEnumerable<IKey>, DefaultKeyResolution>> resolveDefaultKeyPolicyReturnValues,
KeyManagementOptions keyManagementOptions = null)
{
@ -337,21 +388,21 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
createNewKeyCallbacksEnumerator.MoveNext();
Assert.Equal(createNewKeyCallbacksEnumerator.Current.Item1, activationDate);
Assert.Equal(createNewKeyCallbacksEnumerator.Current.Item2, expirationDate);
return null; // nobody uses this return value
});
return createNewKeyCallbacksEnumerator.Current.Item3;
});
}
var resolveDefaultKeyPolicyReturnValuesEnumerator = resolveDefaultKeyPolicyReturnValues.GetEnumerator();
var mockDefaultKeyResolver = new Mock<IDefaultKeyResolver>(MockBehavior.Strict);
mockDefaultKeyResolver.Setup(o => o.ResolveDefaultKeyPolicy(It.IsAny<DateTimeOffset>(), It.IsAny<IEnumerable<IKey>>()))
.Returns<DateTimeOffset,IEnumerable<IKey>>((now, allKeys) =>
{
callSequence.Add("ResolveDefaultKeyPolicy");
resolveDefaultKeyPolicyReturnValuesEnumerator.MoveNext();
Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item1, now);
Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item2, allKeys);
return resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item3;
});
.Returns<DateTimeOffset, IEnumerable<IKey>>((now, allKeys) =>
{
callSequence.Add("ResolveDefaultKeyPolicy");
resolveDefaultKeyPolicyReturnValuesEnumerator.MoveNext();
Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item1, now);
Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item2, allKeys);
return resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item3;
});
return CreateKeyRingProvider(mockKeyManager.Object, mockDefaultKeyResolver.Object, keyManagementOptions);
}
@ -495,6 +546,12 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
return DateTimeOffset.ParseExact(input, "u", CultureInfo.InvariantCulture).UtcDateTime;
}
private static IKey CreateKey()
{
var now = DateTimeOffset.Now;
return CreateKey(Invariant($"{now:u}"), Invariant($"{now.AddDays(90):u}"));
}
private static IKey CreateKey(string activationDate, string expirationDate, bool isRevoked = false)
{
var mockKey = new Mock<IKey>();
@ -502,6 +559,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
mockKey.Setup(o => o.ActivationDate).Returns(DateTimeOffset.ParseExact(activationDate, "u", CultureInfo.InvariantCulture));
mockKey.Setup(o => o.ExpirationDate).Returns(DateTimeOffset.ParseExact(expirationDate, "u", CultureInfo.InvariantCulture));
mockKey.Setup(o => o.IsRevoked).Returns(isRevoked);
mockKey.Setup(o => o.CreateEncryptorInstance()).Returns(new Mock<IAuthenticatedEncryptor>().Object);
return mockKey.Object;
}
}

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
var key2 = new MyKey();
// Act
var keyRing = new KeyRing(key1.KeyId, new[] { key1, key2 });
var keyRing = new KeyRing(key1, new[] { key1, key2 });
// Assert
Assert.Equal(0, key1.NumTimesCreateEncryptorInstanceCalled);
@ -38,12 +38,29 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
var key2 = new MyKey();
// Act
var keyRing = new KeyRing(key2.KeyId, new[] { key1, key2 });
var keyRing = new KeyRing(key2, new[] { key1, key2 });
// Assert
Assert.Equal(key2.KeyId, keyRing.DefaultKeyId);
}
[Fact]
public void DefaultKeyIdAndEncryptor_IfDefaultKeyNotPresentInAllKeys()
{
// Arrange
var key1 = new MyKey();
var key2 = new MyKey();
var key3 = new MyKey(expectedEncryptorInstance: new Mock<IAuthenticatedEncryptor>().Object);
// Act
var keyRing = new KeyRing(key3, new[] { key1, key2 });
// Assert
bool unused;
Assert.Equal(key3.KeyId, keyRing.DefaultKeyId);
Assert.Equal(key3.CreateEncryptorInstance(), keyRing.GetAuthenticatedEncryptorByKeyId(key3.KeyId, out unused));
}
[Fact]
public void GetAuthenticatedEncryptorByKeyId_DefersInstantiation_AndReturnsRevocationInfo()
{
@ -55,7 +72,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
var key2 = new MyKey(expectedEncryptorInstance: expectedEncryptorInstance2);
// Act
var keyRing = new KeyRing(key2.KeyId, new[] { key1, key2 });
var keyRing = new KeyRing(key2, new[] { key1, key2 });
// Assert
bool isRevoked;

View File

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Moq;
using Xunit;
namespace Microsoft.AspNet.DataProtection.KeyManagement
{
public class KeyTests
{
[Fact]
public void Ctor_Properties()
{
// Arrange
var keyId = Guid.NewGuid();
var creationDate = DateTimeOffset.Now;
var activationDate = creationDate.AddDays(2);
var expirationDate = creationDate.AddDays(90);
// Act
var key = new Key(keyId, creationDate, activationDate, expirationDate, new Mock<IAuthenticatedEncryptorDescriptor>().Object);
// Assert
Assert.Equal(keyId, key.KeyId);
Assert.Equal(creationDate, key.CreationDate);
Assert.Equal(activationDate, key.ActivationDate);
Assert.Equal(expirationDate, key.ExpirationDate);
}
[Fact]
public void SetRevoked_Respected()
{
// Arrange
var now = DateTimeOffset.UtcNow;
var key = new Key(Guid.Empty, now, now, now, new Mock<IAuthenticatedEncryptorDescriptor>().Object);
// Act & assert
Assert.False(key.IsRevoked);
key.SetRevoked();
Assert.True(key.IsRevoked);
}
[Fact]
public void CreateEncryptorInstance()
{
// Arrange
var expected = new Mock<IAuthenticatedEncryptor>().Object;
var mockDescriptor = new Mock<IAuthenticatedEncryptorDescriptor>();
mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(expected);
var now = DateTimeOffset.UtcNow;
var key = new Key(Guid.Empty, now, now, now, mockDescriptor.Object);
// Act
var actual = key.CreateEncryptorInstance();
// Assert
Assert.Same(expected, actual);
}
}
}

View File

@ -473,7 +473,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
<key id='78cd498e-9375-4e55-ac0d-d79527ecd09d' version='1'>
<creationDate>2015-01-01T00:00:00Z</creationDate>
<activationDate>2015-02-01T00:00:00Z</activationDate>
<expirationDate>2015-03-01T00:00:00Z</expirationDate>
<expirationDate>NOT A VALID DATE</expirationDate>
<descriptor deserializerType='badDeserializer'>
<node />
</descriptor>
@ -492,7 +492,6 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
var expectedEncryptor = new Mock<IAuthenticatedEncryptor>().Object;
var mockActivator = new Mock<IActivator>();
mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("goodDeserializer", "<node xmlns='private' />", expectedEncryptor);
mockActivator.Setup(o => o.CreateInstance(It.IsAny<Type>(), "badDeserializer")).Throws(new Exception("How exceptional!"));
// Act
var keys = RunGetAllKeysCore(xml, mockActivator.Object).ToArray();
@ -513,26 +512,18 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
<key id='78cd498e-9375-4e55-ac0d-d79527ecd09d' version='1'>
<creationDate>2015-01-01T00:00:00Z</creationDate>
<activationDate>2015-02-01T00:00:00Z</activationDate>
<expirationDate>2015-03-01T00:00:00Z</expirationDate>
<descriptor deserializerType='badDeserializer'>
<node>
<!-- Secret information: 1A2B3C4D -->
</node>
</descriptor>
<expirationDate>NOT A VALID DATE</expirationDate>
<!-- Secret information: 1A2B3C4D -->
</key>
</root>";
var mockActivator = new Mock<IActivator>();
mockActivator.Setup(o => o.CreateInstance(It.IsAny<Type>(), "badDeserializer")).Throws(new Exception("Secret information: 9Z8Y7X6W"));
var loggerFactory = new StringLoggerFactory(LogLevel.Verbose);
// Act
RunGetAllKeysCore(xml, mockActivator.Object, loggerFactory).ToArray();
RunGetAllKeysCore(xml, new Mock<IActivator>().Object, loggerFactory).ToArray();
// Assert
Assert.False(loggerFactory.ToString().Contains("1A2B3C4D"), "The secret '1A2B3C4D' should not have been logged.");
Assert.False(loggerFactory.ToString().Contains("9Z8Y7X6W"), "The secret '1A2B3C4D' should not have been logged.");
}
[Fact]
@ -545,26 +536,18 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
<key id='78cd498e-9375-4e55-ac0d-d79527ecd09d' version='1'>
<creationDate>2015-01-01T00:00:00Z</creationDate>
<activationDate>2015-02-01T00:00:00Z</activationDate>
<expirationDate>2015-03-01T00:00:00Z</expirationDate>
<descriptor deserializerType='badDeserializer'>
<node>
<!-- Secret information: 1A2B3C4D -->
</node>
</descriptor>
<expirationDate>NOT A VALID DATE</expirationDate>
<!-- Secret information: 1A2B3C4D -->
</key>
</root>";
var mockActivator = new Mock<IActivator>();
mockActivator.Setup(o => o.CreateInstance(It.IsAny<Type>(), "badDeserializer")).Throws(new Exception("Secret information: 9Z8Y7X6W"));
var loggerFactory = new StringLoggerFactory(LogLevel.Debug);
// Act
RunGetAllKeysCore(xml, mockActivator.Object, loggerFactory).ToArray();
RunGetAllKeysCore(xml, new Mock<IActivator>().Object, loggerFactory).ToArray();
// Assert
Assert.True(loggerFactory.ToString().Contains("1A2B3C4D"), "The secret '1A2B3C4D' should have been logged.");
Assert.True(loggerFactory.ToString().Contains("9Z8Y7X6W"), "The secret '9Z8Y7X6W' should have been logged.");
}
[Fact]