Rename KeyLifetimeOptions -> KeyManagementOptions
Simplify default key resolution logic Introduce API for disabling automatic key generation
This commit is contained in:
Родитель
20fe4f8d63
Коммит
58c823bc45
|
@ -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]
|
||||
|
|
Загрузка…
Ссылка в новой задаче