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:
parent
4365b531d8
commit
612a81d9ce
|
|
@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue