Rename KeyLifetimeOptions -> KeyManagementOptions

Simplify default key resolution logic
Introduce API for disabling automatic key generation
This commit is contained in:
Levi B 2015-03-11 15:40:15 -07:00
Родитель 20fe4f8d63
Коммит 58c823bc45
11 изменённых файлов: 273 добавлений и 117 удалений

Просмотреть файл

@ -113,6 +113,36 @@ namespace Microsoft.AspNet.DataProtection
return this;
}
/// <summary>
/// Configures the data protection system not to generate new keys automatically.
/// </summary>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// Calling this API corresponds to setting <see cref="KeyManagementOptions.AutoGenerateKeys"/>
/// to 'false'. See that property's documentation for more information.
/// </remarks>
public DataProtectionConfiguration DisableAutomaticKeyGeneration()
{
Services.Configure<KeyManagementOptions>(options =>
{
options.AutoGenerateKeys = false;
});
return this;
}
/// <summary>
/// Configures the data protection system to persist keys in storage as plaintext.
/// </summary>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// Caution: cryptographic key material will not be protected at rest.
/// </remarks>
public DataProtectionConfiguration DisableProtectionOfKeysAtRest()
{
RemoveAllServicesOfType(typeof(IXmlEncryptor));
return this;
}
/// <summary>
/// Configures the data protection system to persist keys to the specified directory.
/// This path may be on the local machine or may point to a UNC share.
@ -241,30 +271,17 @@ namespace Microsoft.AspNet.DataProtection
/// Sets the default lifetime of keys created by the data protection system.
/// </summary>
/// <param name="lifetime">The lifetime (time before expiration) for newly-created keys.
/// See <see cref="KeyLifetimeOptions.NewKeyLifetime"/> for more information and
/// See <see cref="KeyManagementOptions.NewKeyLifetime"/> for more information and
/// usage notes.</param>
/// <returns>The 'this' instance.</returns>
public DataProtectionConfiguration SetDefaultKeyLifetime(TimeSpan lifetime)
{
Services.Configure<KeyLifetimeOptions>(options =>
Services.Configure<KeyManagementOptions>(options =>
{
options.NewKeyLifetime = lifetime;
});
return this;
}
/// <summary>
/// Configures the data protection system to persist keys in storage as plaintext.
/// </summary>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// Caution: cryptographic key material will not be protected at rest.
/// </remarks>
public DataProtectionConfiguration SuppressProtectionOfKeysAtRest()
{
RemoveAllServicesOfType(typeof(IXmlEncryptor));
return this;
}
/// <summary>
/// Configures the data protection system to use the specified cryptographic algorithms

Просмотреть файл

@ -72,7 +72,7 @@ namespace Microsoft.AspNet.DataProtection
{
var keyRingProvider = new KeyRingProvider(
keyManager: services.GetRequiredService<IKeyManager>(),
keyLifetimeOptions: services.GetService<IOptions<KeyLifetimeOptions>>()?.Options, // might be null
keyManagementOptions: services.GetService<IOptions<KeyManagementOptions>>()?.Options, // might be null
services: services);
dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyRingProvider, services);
}

Просмотреть файл

@ -39,14 +39,14 @@ namespace Microsoft.Framework.DependencyInjection
}
/// <summary>
/// An <see cref="IConfigureOptions{KeyLifetimeOptions}"/> where the key lifetime is specified explicitly.
/// An <see cref="IConfigureOptions{KeyManagementOptions}"/> where the key lifetime is specified explicitly.
/// </summary>
public static ServiceDescriptor ConfigureOptions_DefaultKeyLifetime(int numDays)
{
return ServiceDescriptor.Transient<IConfigureOptions<KeyLifetimeOptions>>(services =>
return ServiceDescriptor.Transient<IConfigureOptions<KeyManagementOptions>>(services =>
{
return new ConfigureOptions<KeyLifetimeOptions>(options =>
return new ConfigureOptions<KeyManagementOptions>(options =>
{
options.NewKeyLifetime = TimeSpan.FromDays(numDays);
});

Просмотреть файл

@ -18,10 +18,10 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
/// and persisted to the keyring to ensure uninterrupted service.
/// </summary>
/// <remarks>
/// If the expiration window is 5 days and the current key expires within 5 days,
/// If the propagation time is 5 days and the current key expires within 5 days,
/// a new key will be generated.
/// </remarks>
private readonly TimeSpan _keyGenBeforeExpirationWindow;
private readonly TimeSpan _keyPropagationWindow;
private readonly ILogger _logger;
@ -36,9 +36,9 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
/// </remarks>
private readonly TimeSpan _maxServerToServerClockSkew;
public DefaultKeyResolver(TimeSpan keyGenBeforeExpirationWindow, TimeSpan maxServerToServerClockSkew, IServiceProvider services)
public DefaultKeyResolver(TimeSpan keyPropagationWindow, TimeSpan maxServerToServerClockSkew, IServiceProvider services)
{
_keyGenBeforeExpirationWindow = keyGenBeforeExpirationWindow;
_keyPropagationWindow = keyPropagationWindow;
_maxServerToServerClockSkew = maxServerToServerClockSkew;
_logger = services.GetLogger<DefaultKeyResolver>();
}
@ -52,82 +52,61 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
private IKey FindDefaultKey(DateTimeOffset now, IEnumerable<IKey> allKeys, out bool callerShouldGenerateNewKey)
{
// the key with the most recent activation date where the activation date is in the past
IKey keyMostRecentlyActivated = (from key in allKeys
where key.ActivationDate <= now
orderby key.ActivationDate descending
select key).FirstOrDefault();
// find the preferred default key (allowing for server-to-server clock skew)
var preferredDefaultKey = (from key in allKeys
where key.ActivationDate <= now + _maxServerToServerClockSkew
orderby key.ActivationDate descending, key.KeyId ascending
select key).FirstOrDefault();
if (keyMostRecentlyActivated != null)
if (preferredDefaultKey != null)
{
if (_logger.IsVerboseLevelEnabled())
{
_logger.LogVerbose("Considering key '{0:D}' with expiration date {1:u} as default key candidate.", keyMostRecentlyActivated.KeyId, keyMostRecentlyActivated.ExpirationDate);
_logger.LogVerbose("Considering key '{0:D}' with expiration date {1:u} as default key.", preferredDefaultKey.KeyId, preferredDefaultKey.ExpirationDate);
}
// if the key has been revoked or is expired, it is no longer a candidate
if (keyMostRecentlyActivated.IsExpired(now) || keyMostRecentlyActivated.IsRevoked)
if (preferredDefaultKey.IsExpired(now) || preferredDefaultKey.IsRevoked)
{
if (_logger.IsVerboseLevelEnabled())
{
_logger.LogVerbose("Key '{0:D}' no longer eligible as default key candidate because it is expired or revoked.", keyMostRecentlyActivated.KeyId);
_logger.LogVerbose("Key '{0:D}' is no longer under consideration as default key because it is expired or revoked.", preferredDefaultKey.KeyId);
}
keyMostRecentlyActivated = null;
preferredDefaultKey = null;
}
}
// There's an interesting edge case here. If two keys have an activation date in the past and
// an expiration date in the future, and if the most recently activated of those two keys is
// revoked, we won't consider the older key a valid candidate. This is intentional: generating
// a new key is an implicit signal that we should stop using older keys without explicitly
// revoking them.
// Only the key that has been most recently activated is eligible to be the preferred default,
// and only if it hasn't expired or been revoked. This is intentional: generating a new key is
// an implicit signal that we should stop using older keys (even if they're not revoked), so
// activating a new key should permanently mark all older keys as non-preferred.
// if the key's expiration is beyond our safety window, we can use this key
if (keyMostRecentlyActivated != null && keyMostRecentlyActivated.ExpirationDate - now > _keyGenBeforeExpirationWindow)
if (preferredDefaultKey != null)
{
callerShouldGenerateNewKey = false;
return keyMostRecentlyActivated;
}
// Does *any* key in the key ring fulfill the requirement that its activation date is prior
// to the preferred default key's expiration date (allowing for skew) and that it will
// remain valid one propagation cycle from now? If so, the caller doesn't need to add a
// new key.
callerShouldGenerateNewKey = !allKeys.Any(key =>
key.ActivationDate <= (preferredDefaultKey.ExpirationDate + _maxServerToServerClockSkew)
&& !key.IsExpired(now + _keyPropagationWindow)
&& !key.IsRevoked);
// the key with the nearest activation date where the activation date is in the future
// and the key isn't expired or revoked
IKey keyNextPendingActivation = (from key in allKeys
where key.ActivationDate > now && !key.IsExpired(now) && !key.IsRevoked
orderby key.ActivationDate ascending
select key).FirstOrDefault();
// if we have a valid current key, return it, and signal to the caller that he must perform
// the keygen step only if the next key pending activation won't be activated until *after*
// the current key expires (allowing for server-to-server skew)
if (keyMostRecentlyActivated != null)
{
callerShouldGenerateNewKey = (keyNextPendingActivation == null || (keyNextPendingActivation.ActivationDate - keyMostRecentlyActivated.ExpirationDate > _maxServerToServerClockSkew));
if (callerShouldGenerateNewKey && _logger.IsVerboseLevelEnabled())
{
_logger.LogVerbose("Default key expiration imminent and repository contains no viable successor. Caller should generate a successor.");
}
return keyMostRecentlyActivated;
return preferredDefaultKey;
}
// if there's no valid current key but there is a key pending activation, we can use
// it only if its activation period is within the server-to-server clock skew
if (keyNextPendingActivation != null && keyNextPendingActivation.ActivationDate - now <= _maxServerToServerClockSkew)
{
if (_logger.IsVerboseLevelEnabled())
{
_logger.LogVerbose("Considering key '{0:D}' with expiration date {1:u} as default key candidate.", keyNextPendingActivation.KeyId, keyNextPendingActivation.ExpirationDate);
}
// If we got this far, the caller must generate a key now.
callerShouldGenerateNewKey = false;
return keyNextPendingActivation;
}
// if we got this far, there was no valid default key in the keyring
if (_logger.IsVerboseLevelEnabled())
{
_logger.LogVerbose("Repository contains no viable default key. Caller should generate a key with immediate activation.");
}
callerShouldGenerateNewKey = true;
return null;
}

Просмотреть файл

@ -5,41 +5,58 @@ using System;
namespace Microsoft.AspNet.DataProtection.KeyManagement
{
public class KeyLifetimeOptions
/// <summary>
/// Options that control how an <see cref="IKeyManager"/> should behave.
/// </summary>
public class KeyManagementOptions
{
private readonly TimeSpan _keyExpirationSafetyPeriod = TimeSpan.FromDays(2);
private readonly TimeSpan _keyRingRefreshPeriod = TimeSpan.FromHours(24);
private readonly TimeSpan _maxServerClockSkew = TimeSpan.FromMinutes(5);
private static readonly TimeSpan _keyPropagationWindow = TimeSpan.FromDays(2);
private static readonly TimeSpan _keyRingRefreshPeriod = TimeSpan.FromHours(24);
private static readonly TimeSpan _maxServerClockSkew = TimeSpan.FromMinutes(5);
private TimeSpan _newKeyLifetime = TimeSpan.FromDays(90);
public KeyLifetimeOptions()
public KeyManagementOptions()
{
}
// copy ctor
internal KeyLifetimeOptions(KeyLifetimeOptions other)
internal KeyManagementOptions(KeyManagementOptions other)
{
if (other != null)
{
this.AutoGenerateKeys = other.AutoGenerateKeys;
this._newKeyLifetime = other._newKeyLifetime;
}
}
/// <summary>
/// Specifies the period before key expiration in which a new key should be generated.
/// For example, if this period is 72 hours, then a new key will be created and
/// persisted to storage approximately 72 hours before expiration.
/// Specifies whether the data protection system should auto-generate keys.
/// </summary>
/// <remarks>
/// If this value is 'false', the system will not generate new keys automatically.
/// The key ring must contain at least one active non-revoked key, otherwise calls
/// to <see cref="IDataProtector.Protect(byte[])"/> may fail. The system may end up
/// protecting payloads to expired keys if this property is set to 'false'.
/// The default value is 'true'.
/// </remarks>
public bool AutoGenerateKeys { get; set; } = true;
/// <summary>
/// Specifies the period before key expiration in which a new key should be generated
/// so that it has time to propagate fully throughout the key ring. For example, if this
/// period is 72 hours, then a new key will be created and persisted to storage
/// approximately 72 hours before expiration.
/// </summary>
/// <remarks>
/// This value is currently fixed at 48 hours.
/// </remarks>
internal TimeSpan KeyExpirationSafetyPeriod
internal TimeSpan KeyPropagationWindow
{
get
{
// This value is not settable since there's a complex interaction between
// it and the key ring refresh period.
return _keyExpirationSafetyPeriod;
return _keyPropagationWindow;
}
}
@ -97,7 +114,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
{
if (value < TimeSpan.FromDays(7))
{
throw new ArgumentOutOfRangeException(nameof(value), Resources.KeyLifetimeOptions_MinNewKeyLifetimeViolated);
throw new ArgumentOutOfRangeException(nameof(value), Resources.KeyManagementOptions_MinNewKeyLifetimeViolated);
}
_newKeyLifetime = value;
}

Просмотреть файл

@ -17,20 +17,20 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
private readonly object _cacheableKeyRingLockObj = new object();
private readonly ICacheableKeyRingProvider _cacheableKeyRingProvider;
private readonly IDefaultKeyResolver _defaultKeyResolver;
private readonly KeyLifetimeOptions _keyLifetimeOptions;
private readonly KeyManagementOptions _keyManagementOptions;
private readonly IKeyManager _keyManager;
private readonly ILogger _logger;
public KeyRingProvider(IKeyManager keyManager, KeyLifetimeOptions keyLifetimeOptions, IServiceProvider services)
public KeyRingProvider(IKeyManager keyManager, KeyManagementOptions keyManagementOptions, IServiceProvider services)
{
_keyLifetimeOptions = new KeyLifetimeOptions(keyLifetimeOptions); // clone so new instance is immutable
_keyManagementOptions = new KeyManagementOptions(keyManagementOptions); // clone so new instance is immutable
_keyManager = keyManager;
_cacheableKeyRingProvider = services?.GetService<ICacheableKeyRingProvider>() ?? this;
_logger = services?.GetLogger<KeyRingProvider>();
_defaultKeyResolver = services?.GetService<IDefaultKeyResolver>()
?? new DefaultKeyResolver(_keyLifetimeOptions.KeyExpirationSafetyPeriod, _keyLifetimeOptions.MaxServerClockSkew, services);
?? new DefaultKeyResolver(_keyManagementOptions.KeyPropagationWindow, _keyManagementOptions.MaxServerClockSkew, services);
}
private CacheableKeyRing CreateCacheableKeyRingCore(DateTimeOffset now, bool allowRecursiveCalls = false)
{
// Refresh the list of all keys
@ -67,17 +67,37 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
if (defaultKeyPolicy.DefaultKey == null)
{
// We cannot continue if we have no default key and auto-generation of keys is disabled.
if (!_keyManagementOptions.AutoGenerateKeys)
{
if (_logger.IsErrorLevelEnabled())
{
_logger.LogError("The key ring does not contain a valid default key, and the key manager is configured with auto-generation of keys disabled.");
}
throw new InvalidOperationException(Resources.KeyRingProvider_NoDefaultKey_AutoGenerateDisabled);
}
// 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 + _keyLifetimeOptions.NewKeyLifetime);
_keyManager.CreateNewKey(activationDate: now, expirationDate: now + _keyManagementOptions.NewKeyLifetime);
return CreateCacheableKeyRingCore(now); // recursively call
}
else
{
// If auto-generation of keys is disabled, we cannot call CreateNewKey.
if (!_keyManagementOptions.AutoGenerateKeys)
{
if (_logger.IsWarningLevelEnabled())
{
_logger.LogWarning("Policy resolution states that a new key should be added to the key ring, but automatic generation of keys is disabled.");
}
return CreateCacheableKeyRingCoreStep2(now, cacheExpirationToken, defaultKeyPolicy.DefaultKey, allKeys);
}
// 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 + _keyLifetimeOptions.NewKeyLifetime);
_keyManager.CreateNewKey(activationDate: defaultKeyPolicy.DefaultKey.ExpirationDate, expirationDate: now + _keyManagementOptions.NewKeyLifetime);
return CreateCacheableKeyRingCore(now); // recursively call
}
}
@ -96,7 +116,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
// servers in a cluster from trying to update the key ring simultaneously.
return new CacheableKeyRing(
expirationToken: cacheExpirationToken,
expirationTime: Min(defaultKey.ExpirationDate, now + GetRefreshPeriodWithJitter(_keyLifetimeOptions.KeyRingRefreshPeriod)),
expirationTime: Min(defaultKey.ExpirationDate, now + GetRefreshPeriodWithJitter(_keyManagementOptions.KeyRingRefreshPeriod)),
defaultKey: defaultKey,
allKeys: allKeys);
}

Просмотреть файл

@ -219,19 +219,19 @@ namespace Microsoft.AspNet.DataProtection
}
/// <summary>
/// The default new key lifetime must be at least one week.
/// The new key lifetime must be at least one week.
/// </summary>
internal static string KeyLifetimeOptions_MinNewKeyLifetimeViolated
internal static string KeyManagementOptions_MinNewKeyLifetimeViolated
{
get { return GetString("KeyLifetimeOptions_MinNewKeyLifetimeViolated"); }
get { return GetString("KeyManagementOptions_MinNewKeyLifetimeViolated"); }
}
/// <summary>
/// The default new key lifetime must be at least one week.
/// The new key lifetime must be at least one week.
/// </summary>
internal static string FormatKeyLifetimeOptions_MinNewKeyLifetimeViolated()
internal static string FormatKeyManagementOptions_MinNewKeyLifetimeViolated()
{
return GetString("KeyLifetimeOptions_MinNewKeyLifetimeViolated");
return GetString("KeyManagementOptions_MinNewKeyLifetimeViolated");
}
/// <summary>
@ -378,6 +378,22 @@ namespace Microsoft.AspNet.DataProtection
return string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadKeySize"), p0);
}
/// <summary>
/// The key ring does not contain a valid default protection key. The data protection system cannot create a new key because auto-generation of keys is disabled.
/// </summary>
internal static string KeyRingProvider_NoDefaultKey_AutoGenerateDisabled
{
get { return GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled"); }
}
/// <summary>
/// The key ring does not contain a valid default protection key. The data protection system cannot create a new key because auto-generation of keys is disabled.
/// </summary>
internal static string FormatKeyRingProvider_NoDefaultKey_AutoGenerateDisabled()
{
return GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

Просмотреть файл

@ -156,8 +156,8 @@
<data name="TypeExtensions_BadCast" xml:space="preserve">
<value>The type '{1}' is not assignable to '{0}'.</value>
</data>
<data name="KeyLifetimeOptions_MinNewKeyLifetimeViolated" xml:space="preserve">
<value>The default new key lifetime must be at least one week.</value>
<data name="KeyManagementOptions_MinNewKeyLifetimeViolated" xml:space="preserve">
<value>The new key lifetime must be at least one week.</value>
</data>
<data name="XmlKeyManager_DuplicateKey" xml:space="preserve">
<value>The key '{0:D}' already exists in the keyring.</value>
@ -186,4 +186,7 @@
<data name="AlgorithmAssert_BadKeySize" xml:space="preserve">
<value>The symmetric algorithm key size of {0} bits is invalid. The key size must be between 128 and 2048 bits, inclusive, and it must be a multiple of 8 bits.</value>
</data>
<data name="KeyRingProvider_NoDefaultKey_AutoGenerateDisabled" xml:space="preserve">
<value>The key ring does not contain a valid default protection key. The data protection system cannot create a new key because auto-generation of keys is disabled.</value>
</data>
</root>

Просмотреть файл

@ -31,9 +31,10 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
// Arrange
var resolver = CreateDefaultKeyResolver();
var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z");
var key2 = CreateKey("2016-03-01 00:00:00Z", "2017-03-01 00:00:00Z");
// Act
var resolution = resolver.ResolveDefaultKeyPolicy("2015-04-01 00:00:00Z", key1);
var resolution = resolver.ResolveDefaultKeyPolicy("2016-02-20 23:59:00Z", key1, key2);
// Assert
Assert.Same(key1, resolution.DefaultKey);
@ -41,15 +42,45 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
}
[Fact]
public void ResolveDefaultKeyPolicy_ValidExistingKey_ApproachingSafetyWindow_ReturnsExistingKey_SignalsGenerateNewKey()
public void ResolveDefaultKeyPolicy_ValidExistingKey_AllowsForClockSkew_KeysStraddleSkewLine_ReturnsExistingKey()
{
// Arrange
var resolver = CreateDefaultKeyResolver();
var key1 = CreateKey("2015-03-01 00:00:00Z", "2015-04-01 00:00:00Z");
var key2 = CreateKey("2015-04-01 00:00:00Z", "2015-05-01 00:00:00Z", isRevoked: true);
var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z");
var key2 = CreateKey("2016-03-01 00:00:00Z", "2017-03-01 00:00:00Z");
// Act
var resolution = resolver.ResolveDefaultKeyPolicy("2015-03-30 00:00:00Z", key1, key2);
var resolution = resolver.ResolveDefaultKeyPolicy("2016-02-29 23:59:00Z", key1, key2);
// Assert
Assert.Same(key2, resolution.DefaultKey);
Assert.False(resolution.ShouldGenerateNewKey);
}
[Fact]
public void ResolveDefaultKeyPolicy_ValidExistingKey_AllowsForClockSkew_AllKeysInFuture_ReturnsExistingKey()
{
// Arrange
var resolver = CreateDefaultKeyResolver();
var key1 = CreateKey("2016-03-01 00:00:00Z", "2017-03-01 00:00:00Z");
// Act
var resolution = resolver.ResolveDefaultKeyPolicy("2016-02-29 23:59:00Z", key1);
// Assert
Assert.Same(key1, resolution.DefaultKey);
Assert.False(resolution.ShouldGenerateNewKey);
}
[Fact]
public void ResolveDefaultKeyPolicy_ValidExistingKey_NoSuccessor_ReturnsExistingKey_SignalsGenerateNewKey()
{
// Arrange
var resolver = CreateDefaultKeyResolver();
var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z");
// Act
var resolution = resolver.ResolveDefaultKeyPolicy("2016-02-29 23:59:00Z", key1);
// Assert
Assert.Same(key1, resolution.DefaultKey);
@ -57,20 +88,20 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
}
[Fact]
public void ResolveDefaultKeyPolicy_ValidExistingKey_ApproachingSafetyWindow_FutureKeyIsValidAndWithinSkew_ReturnsExistingKey_NoSignalToGenerateNewKey()
public void ResolveDefaultKeyPolicy_ValidExistingKey_NoLegitimateSuccessor_ReturnsExistingKey_SignalsGenerateNewKey()
{
// Arrange
var resolver = CreateDefaultKeyResolver();
var key1 = CreateKey("2015-03-01 00:00:00Z", "2015-04-01 00:00:00Z");
var key2 = CreateKey("2015-04-01 00:00:00Z", "2015-05-01 00:00:00Z", isRevoked: true);
var key3 = CreateKey("2015-04-01 00:01:00Z", "2015-05-01 00:00:00Z");
var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z");
var key2 = CreateKey("2016-03-01 00:00:00Z", "2017-03-01 00:00:00Z", isRevoked: true);
var key3 = CreateKey("2016-03-01 00:00:00Z", "2016-03-02 00:00:00Z"); // key expires too soon
// Act
var resolution = resolver.ResolveDefaultKeyPolicy("2015-03-31 23:59:00Z", key1, key2, key3);
var resolution = resolver.ResolveDefaultKeyPolicy("2016-02-29 23:50:00Z", key1, key2, key3);
// Assert
Assert.Same(key1, resolution.DefaultKey);
Assert.False(resolution.ShouldGenerateNewKey);
Assert.True(resolution.ShouldGenerateNewKey);
}
[Fact]
@ -139,7 +170,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
private static IDefaultKeyResolver CreateDefaultKeyResolver()
{
return new DefaultKeyResolver(
keyGenBeforeExpirationWindow: TimeSpan.FromDays(2),
keyPropagationWindow: TimeSpan.FromDays(2),
maxServerToServerClockSkew: TimeSpan.FromMinutes(7),
services: null);
}

Просмотреть файл

@ -140,6 +140,40 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy", "CreateNewKey", "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
}
[Fact]
public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_KeyGenerationDisabled_Fails()
{
// Arrange
var callSequence = new List<string>();
var now = StringToDateTime("2015-03-01 00:00:00Z");
var allKeys = new IKey[0];
var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
callSequence: callSequence,
getCacheExpirationTokenReturnValues: new[] { CancellationToken.None },
getAllKeysReturnValues: new[] { allKeys },
createNewKeyCallbacks: new[] {
Tuple.Create((DateTimeOffset)now, (DateTimeOffset)now + TimeSpan.FromDays(90))
},
resolveDefaultKeyPolicyReturnValues: new[]
{
Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys, new DefaultKeyResolution()
{
DefaultKey = null,
ShouldGenerateNewKey = true
})
},
keyManagementOptions: new KeyManagementOptions() { AutoGenerateKeys = false });
// Act
var exception = Assert.Throws<InvalidOperationException>(() => keyRingProvider.GetCacheableKeyRing(now));
// Assert
Assert.Equal(Resources.KeyRingProvider_NoDefaultKey_AutoGenerateDisabled, exception.Message);
Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
}
[Fact]
public void CreateCacheableKeyRing_GenerationRequired_WithDefaultKey_CreatesNewKeyWithDeferredActivationAndExpirationBasedOnCreationTime()
{
@ -190,12 +224,51 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy", "CreateNewKey", "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
}
[Fact]
public void CreateCacheableKeyRing_GenerationRequired_WithDefaultKey_KeyGenerationDisabled_DoesNotCreateDefaultKey()
{
// Arrange
var callSequence = new List<string>();
var expirationCts = new CancellationTokenSource();
var now = StringToDateTime("2016-02-01 00:00:00Z");
var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z");
var allKeys = new[] { key1 };
var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
callSequence: callSequence,
getCacheExpirationTokenReturnValues: new[] { expirationCts.Token },
getAllKeysReturnValues: new[] { allKeys },
createNewKeyCallbacks: null, // empty
resolveDefaultKeyPolicyReturnValues: new[]
{
Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys, new DefaultKeyResolution()
{
DefaultKey = key1,
ShouldGenerateNewKey = true
})
},
keyManagementOptions: new KeyManagementOptions() { AutoGenerateKeys = false });
// Act
var cacheableKeyRing = keyRingProvider.GetCacheableKeyRing(now);
// Assert
Assert.Equal(key1.KeyId, cacheableKeyRing.KeyRing.DefaultKeyId);
AssertWithinJitterRange(cacheableKeyRing.ExpirationTimeUtc, now);
Assert.True(CacheableKeyRing.IsValid(cacheableKeyRing, now));
expirationCts.Cancel();
Assert.False(CacheableKeyRing.IsValid(cacheableKeyRing, now));
Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
}
private static ICacheableKeyRingProvider SetupCreateCacheableKeyRingTestAndCreateKeyManager(
IList<string> callSequence,
IEnumerable<CancellationToken> getCacheExpirationTokenReturnValues,
IEnumerable<IReadOnlyCollection<IKey>> getAllKeysReturnValues,
IEnumerable<Tuple<DateTimeOffset,DateTimeOffset>> createNewKeyCallbacks,
IEnumerable<Tuple<DateTimeOffset, IEnumerable<IKey>, DefaultKeyResolution>> resolveDefaultKeyPolicyReturnValues)
IEnumerable<Tuple<DateTimeOffset, IEnumerable<IKey>, DefaultKeyResolution>> resolveDefaultKeyPolicyReturnValues,
KeyManagementOptions keyManagementOptions = null)
{
var getCacheExpirationTokenReturnValuesEnumerator = getCacheExpirationTokenReturnValues.GetEnumerator();
var mockKeyManager = new Mock<IKeyManager>(MockBehavior.Strict);
@ -242,7 +315,7 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
return resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item3;
});
return CreateKeyRingProvider(mockKeyManager.Object, mockDefaultKeyResolver.Object);
return CreateKeyRingProvider(mockKeyManager.Object, mockDefaultKeyResolver.Object, keyManagementOptions);
}
[Fact]
@ -359,17 +432,17 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
serviceCollection.AddInstance<ICacheableKeyRingProvider>(cacheableKeyRingProvider);
return new KeyRingProvider(
keyManager: null,
keyLifetimeOptions: null,
keyManagementOptions: null,
services: serviceCollection.BuildServiceProvider());
}
private static ICacheableKeyRingProvider CreateKeyRingProvider(IKeyManager keyManager, IDefaultKeyResolver defaultKeyResolver)
private static ICacheableKeyRingProvider CreateKeyRingProvider(IKeyManager keyManager, IDefaultKeyResolver defaultKeyResolver, KeyManagementOptions keyManagementOptions= null)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddInstance<IDefaultKeyResolver>(defaultKeyResolver);
return new KeyRingProvider(
keyManager: keyManager,
keyLifetimeOptions: null,
keyManagementOptions: keyManagementOptions,
services: serviceCollection.BuildServiceProvider());
}

Просмотреть файл

@ -61,8 +61,8 @@ namespace Microsoft.AspNet.DataProtection
});
var services = serviceCollection.BuildServiceProvider();
var keyLifetimeOptions = services.GetService<IOptions<KeyLifetimeOptions>>();
Assert.Equal(TimeSpan.FromDays(1024), keyLifetimeOptions.Options.NewKeyLifetime);
var keyManagementOptions = services.GetService<IOptions<KeyManagementOptions>>();
Assert.Equal(TimeSpan.FromDays(1024), keyManagementOptions.Options.NewKeyLifetime);
}
[ConditionalFact]