diff --git a/src/Microsoft.AspNet.DataProtection/DataProtectionConfiguration.cs b/src/Microsoft.AspNet.DataProtection/DataProtectionConfiguration.cs
index 2fa1164d04..e2350cd642 100644
--- a/src/Microsoft.AspNet.DataProtection/DataProtectionConfiguration.cs
+++ b/src/Microsoft.AspNet.DataProtection/DataProtectionConfiguration.cs
@@ -113,6 +113,36 @@ namespace Microsoft.AspNet.DataProtection
return this;
}
+ ///
+ /// Configures the data protection system not to generate new keys automatically.
+ ///
+ /// The 'this' instance.
+ ///
+ /// Calling this API corresponds to setting
+ /// to 'false'. See that property's documentation for more information.
+ ///
+ public DataProtectionConfiguration DisableAutomaticKeyGeneration()
+ {
+ Services.Configure(options =>
+ {
+ options.AutoGenerateKeys = false;
+ });
+ return this;
+ }
+
+ ///
+ /// Configures the data protection system to persist keys in storage as plaintext.
+ ///
+ /// The 'this' instance.
+ ///
+ /// Caution: cryptographic key material will not be protected at rest.
+ ///
+ public DataProtectionConfiguration DisableProtectionOfKeysAtRest()
+ {
+ RemoveAllServicesOfType(typeof(IXmlEncryptor));
+ return this;
+ }
+
///
/// 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.
///
/// The lifetime (time before expiration) for newly-created keys.
- /// See for more information and
+ /// See for more information and
/// usage notes.
/// The 'this' instance.
public DataProtectionConfiguration SetDefaultKeyLifetime(TimeSpan lifetime)
{
- Services.Configure(options =>
+ Services.Configure(options =>
{
options.NewKeyLifetime = lifetime;
});
return this;
}
-
- ///
- /// Configures the data protection system to persist keys in storage as plaintext.
- ///
- /// The 'this' instance.
- ///
- /// Caution: cryptographic key material will not be protected at rest.
- ///
- public DataProtectionConfiguration SuppressProtectionOfKeysAtRest()
- {
- RemoveAllServicesOfType(typeof(IXmlEncryptor));
- return this;
- }
///
/// Configures the data protection system to use the specified cryptographic algorithms
diff --git a/src/Microsoft.AspNet.DataProtection/DataProtectionProvider.cs b/src/Microsoft.AspNet.DataProtection/DataProtectionProvider.cs
index 20d42ee09e..de61cdd9f7 100644
--- a/src/Microsoft.AspNet.DataProtection/DataProtectionProvider.cs
+++ b/src/Microsoft.AspNet.DataProtection/DataProtectionProvider.cs
@@ -72,7 +72,7 @@ namespace Microsoft.AspNet.DataProtection
{
var keyRingProvider = new KeyRingProvider(
keyManager: services.GetRequiredService(),
- keyLifetimeOptions: services.GetService>()?.Options, // might be null
+ keyManagementOptions: services.GetService>()?.Options, // might be null
services: services);
dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyRingProvider, services);
}
diff --git a/src/Microsoft.AspNet.DataProtection/DataProtectionServiceDescriptors.cs b/src/Microsoft.AspNet.DataProtection/DataProtectionServiceDescriptors.cs
index 5a1c08ca29..43b94d65a5 100644
--- a/src/Microsoft.AspNet.DataProtection/DataProtectionServiceDescriptors.cs
+++ b/src/Microsoft.AspNet.DataProtection/DataProtectionServiceDescriptors.cs
@@ -39,14 +39,14 @@ namespace Microsoft.Framework.DependencyInjection
}
///
- /// An where the key lifetime is specified explicitly.
+ /// An where the key lifetime is specified explicitly.
///
public static ServiceDescriptor ConfigureOptions_DefaultKeyLifetime(int numDays)
{
- return ServiceDescriptor.Transient>(services =>
+ return ServiceDescriptor.Transient>(services =>
{
- return new ConfigureOptions(options =>
+ return new ConfigureOptions(options =>
{
options.NewKeyLifetime = TimeSpan.FromDays(numDays);
});
diff --git a/src/Microsoft.AspNet.DataProtection/KeyManagement/DefaultKeyResolver.cs b/src/Microsoft.AspNet.DataProtection/KeyManagement/DefaultKeyResolver.cs
index 624b23e53f..66beedb862 100644
--- a/src/Microsoft.AspNet.DataProtection/KeyManagement/DefaultKeyResolver.cs
+++ b/src/Microsoft.AspNet.DataProtection/KeyManagement/DefaultKeyResolver.cs
@@ -18,10 +18,10 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
/// and persisted to the keyring to ensure uninterrupted service.
///
///
- /// 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.
///
- private readonly TimeSpan _keyGenBeforeExpirationWindow;
+ private readonly TimeSpan _keyPropagationWindow;
private readonly ILogger _logger;
@@ -36,9 +36,9 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
///
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();
}
@@ -52,82 +52,61 @@ namespace Microsoft.AspNet.DataProtection.KeyManagement
private IKey FindDefaultKey(DateTimeOffset now, IEnumerable 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;
}
diff --git a/src/Microsoft.AspNet.DataProtection/KeyManagement/KeyLifetimeOptions.cs b/src/Microsoft.AspNet.DataProtection/KeyManagement/KeyManagementOptions.cs
similarity index 64%
rename from src/Microsoft.AspNet.DataProtection/KeyManagement/KeyLifetimeOptions.cs
rename to src/Microsoft.AspNet.DataProtection/KeyManagement/KeyManagementOptions.cs
index 7316cdb3f7..ae4d7479b5 100644
--- a/src/Microsoft.AspNet.DataProtection/KeyManagement/KeyLifetimeOptions.cs
+++ b/src/Microsoft.AspNet.DataProtection/KeyManagement/KeyManagementOptions.cs
@@ -5,41 +5,58 @@ using System;
namespace Microsoft.AspNet.DataProtection.KeyManagement
{
- public class KeyLifetimeOptions
+ ///
+ /// Options that control how an should behave.
+ ///
+ 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;
}
}
///
- /// 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.
+ ///
+ ///
+ /// 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 may fail. The system may end up
+ /// protecting payloads to expired keys if this property is set to 'false'.
+ /// The default value is 'true'.
+ ///
+ public bool AutoGenerateKeys { get; set; } = true;
+
+ ///
+ /// 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.
///
///
/// This value is currently fixed at 48 hours.
///
- 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;
}
diff --git a/src/Microsoft.AspNet.DataProtection/KeyManagement/KeyRingProvider.cs b/src/Microsoft.AspNet.DataProtection/KeyManagement/KeyRingProvider.cs
index ec8c878c04..475ffda929 100644
--- a/src/Microsoft.AspNet.DataProtection/KeyManagement/KeyRingProvider.cs
+++ b/src/Microsoft.AspNet.DataProtection/KeyManagement/KeyRingProvider.cs
@@ -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() ?? this;
_logger = services?.GetLogger();
_defaultKeyResolver = services?.GetService()
- ?? 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);
}
diff --git a/src/Microsoft.AspNet.DataProtection/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.DataProtection/Properties/Resources.Designer.cs
index fad1928f16..9edb8c1b05 100644
--- a/src/Microsoft.AspNet.DataProtection/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.DataProtection/Properties/Resources.Designer.cs
@@ -219,19 +219,19 @@ namespace Microsoft.AspNet.DataProtection
}
///
- /// The default new key lifetime must be at least one week.
+ /// The new key lifetime must be at least one week.
///
- internal static string KeyLifetimeOptions_MinNewKeyLifetimeViolated
+ internal static string KeyManagementOptions_MinNewKeyLifetimeViolated
{
- get { return GetString("KeyLifetimeOptions_MinNewKeyLifetimeViolated"); }
+ get { return GetString("KeyManagementOptions_MinNewKeyLifetimeViolated"); }
}
///
- /// The default new key lifetime must be at least one week.
+ /// The new key lifetime must be at least one week.
///
- internal static string FormatKeyLifetimeOptions_MinNewKeyLifetimeViolated()
+ internal static string FormatKeyManagementOptions_MinNewKeyLifetimeViolated()
{
- return GetString("KeyLifetimeOptions_MinNewKeyLifetimeViolated");
+ return GetString("KeyManagementOptions_MinNewKeyLifetimeViolated");
}
///
@@ -378,6 +378,22 @@ namespace Microsoft.AspNet.DataProtection
return string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadKeySize"), p0);
}
+ ///
+ /// 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.
+ ///
+ internal static string KeyRingProvider_NoDefaultKey_AutoGenerateDisabled
+ {
+ get { return GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled"); }
+ }
+
+ ///
+ /// 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.
+ ///
+ 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);
diff --git a/src/Microsoft.AspNet.DataProtection/Resources.resx b/src/Microsoft.AspNet.DataProtection/Resources.resx
index ad1f4512df..3562a6a959 100644
--- a/src/Microsoft.AspNet.DataProtection/Resources.resx
+++ b/src/Microsoft.AspNet.DataProtection/Resources.resx
@@ -156,8 +156,8 @@
The type '{1}' is not assignable to '{0}'.
-
- The default new key lifetime must be at least one week.
+
+ The new key lifetime must be at least one week.
The key '{0:D}' already exists in the keyring.
@@ -186,4 +186,7 @@
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.
+
+ 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.
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.DataProtection.Test/KeyManagement/DefaultKeyResolverTests.cs b/test/Microsoft.AspNet.DataProtection.Test/KeyManagement/DefaultKeyResolverTests.cs
index 7c66fdc3e0..3eed6faa19 100644
--- a/test/Microsoft.AspNet.DataProtection.Test/KeyManagement/DefaultKeyResolverTests.cs
+++ b/test/Microsoft.AspNet.DataProtection.Test/KeyManagement/DefaultKeyResolverTests.cs
@@ -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);
}
diff --git a/test/Microsoft.AspNet.DataProtection.Test/KeyManagement/KeyRingProviderTests.cs b/test/Microsoft.AspNet.DataProtection.Test/KeyManagement/KeyRingProviderTests.cs
index b117c9e215..6bb7ab2c0c 100644
--- a/test/Microsoft.AspNet.DataProtection.Test/KeyManagement/KeyRingProviderTests.cs
+++ b/test/Microsoft.AspNet.DataProtection.Test/KeyManagement/KeyRingProviderTests.cs
@@ -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();
+
+ 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)allKeys, new DefaultKeyResolution()
+ {
+ DefaultKey = null,
+ ShouldGenerateNewKey = true
+ })
+ },
+ keyManagementOptions: new KeyManagementOptions() { AutoGenerateKeys = false });
+
+ // Act
+ var exception = Assert.Throws(() => 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();
+ 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)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 callSequence,
IEnumerable getCacheExpirationTokenReturnValues,
IEnumerable> getAllKeysReturnValues,
IEnumerable> createNewKeyCallbacks,
- IEnumerable, DefaultKeyResolution>> resolveDefaultKeyPolicyReturnValues)
+ IEnumerable, DefaultKeyResolution>> resolveDefaultKeyPolicyReturnValues,
+ KeyManagementOptions keyManagementOptions = null)
{
var getCacheExpirationTokenReturnValuesEnumerator = getCacheExpirationTokenReturnValues.GetEnumerator();
var mockKeyManager = new Mock(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(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(defaultKeyResolver);
return new KeyRingProvider(
keyManager: keyManager,
- keyLifetimeOptions: null,
+ keyManagementOptions: keyManagementOptions,
services: serviceCollection.BuildServiceProvider());
}
diff --git a/test/Microsoft.AspNet.DataProtection.Test/RegistryPolicyResolverTests.cs b/test/Microsoft.AspNet.DataProtection.Test/RegistryPolicyResolverTests.cs
index 12f2818957..8dcb0424eb 100644
--- a/test/Microsoft.AspNet.DataProtection.Test/RegistryPolicyResolverTests.cs
+++ b/test/Microsoft.AspNet.DataProtection.Test/RegistryPolicyResolverTests.cs
@@ -61,8 +61,8 @@ namespace Microsoft.AspNet.DataProtection
});
var services = serviceCollection.BuildServiceProvider();
- var keyLifetimeOptions = services.GetService>();
- Assert.Equal(TimeSpan.FromDays(1024), keyLifetimeOptions.Options.NewKeyLifetime);
+ var keyManagementOptions = services.GetService>();
+ Assert.Equal(TimeSpan.FromDays(1024), keyManagementOptions.Options.NewKeyLifetime);
}
[ConditionalFact]