diff --git a/src/DataProtection/DataProtection/src/Error.cs b/src/DataProtection/DataProtection/src/Error.cs
index 839e8f1a2e71..6de1d7aa3bd6 100644
--- a/src/DataProtection/DataProtection/src/Error.cs
+++ b/src/DataProtection/DataProtection/src/Error.cs
@@ -91,4 +91,10 @@ public static InvalidOperationException XmlKeyManager_DuplicateKey(Guid keyId)
var message = string.Format(CultureInfo.CurrentCulture, Resources.XmlKeyManager_DuplicateKey, keyId);
return new InvalidOperationException(message);
}
+
+ public static InvalidOperationException KeyRingProvider_DefaultKeyRevoked(Guid id)
+ {
+ var message = string.Format(CultureInfo.CurrentCulture, Resources.KeyRingProvider_DefaultKeyRevoked, id);
+ return new InvalidOperationException(message);
+ }
}
diff --git a/src/DataProtection/DataProtection/src/KeyManagement/KeyRingProvider.cs b/src/DataProtection/DataProtection/src/KeyManagement/KeyRingProvider.cs
index 0cd7c668831a..c3da92f22661 100644
--- a/src/DataProtection/DataProtection/src/KeyManagement/KeyRingProvider.cs
+++ b/src/DataProtection/DataProtection/src/KeyManagement/KeyRingProvider.cs
@@ -127,6 +127,13 @@ private CacheableKeyRing CreateCacheableKeyRingCoreStep2(DateTimeOffset now, Can
// Invariant: our caller ensures that CreateEncryptorInstance succeeded at least once
Debug.Assert(defaultKey.CreateEncryptor() != null);
+ // This can happen if there's a date-based revocation that's in the future (e.g. because of clock skew)
+ if (defaultKey.IsRevoked)
+ {
+ _logger.KeyRingDefaultKeyIsRevoked(defaultKey.KeyId);
+ throw Error.KeyRingProvider_DefaultKeyRevoked(defaultKey.KeyId);
+ }
+
_logger.UsingKeyAsDefaultKey(defaultKey.KeyId);
var nextAutoRefreshTime = now + GetRefreshPeriodWithJitter(KeyManagementOptions.KeyRingRefreshPeriod);
diff --git a/src/DataProtection/DataProtection/src/LoggingExtensions.cs b/src/DataProtection/DataProtection/src/LoggingExtensions.cs
index 3522725b837b..cd4e38918b6d 100644
--- a/src/DataProtection/DataProtection/src/LoggingExtensions.cs
+++ b/src/DataProtection/DataProtection/src/LoggingExtensions.cs
@@ -249,4 +249,7 @@ private static bool IsLogLevelEnabledCore([NotNullWhen(true)] ILogger? logger, L
[LoggerMessage(64, LogLevel.Debug, "Not enabling read-only key access because an XML encryptor has been specified", EventName = "NotUsingReadOnlyKeyConfigurationBecauseOfEncryptor")]
public static partial void NotUsingReadOnlyKeyConfigurationBecauseOfEncryptor(this ILogger logger);
+
+ [LoggerMessage(72, LogLevel.Error, "The key ring's default data protection key {KeyId:B} has been revoked.", EventName = "KeyRingDefaultKeyIsRevoked")]
+ public static partial void KeyRingDefaultKeyIsRevoked(this ILogger logger, Guid keyId);
}
diff --git a/src/DataProtection/DataProtection/src/Resources.resx b/src/DataProtection/DataProtection/src/Resources.resx
index c69efbb97bc2..aa1eb24fea14 100644
--- a/src/DataProtection/DataProtection/src/Resources.resx
+++ b/src/DataProtection/DataProtection/src/Resources.resx
@@ -187,6 +187,9 @@
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. For more information go to https://aka.ms/aspnet/dataprotectionwarning
+
+ The key ring's default data protection key {0:B} has been revoked.
+
{0} must not be negative.
diff --git a/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/KeyManagement/KeyRingProviderTests.cs b/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/KeyManagement/KeyRingProviderTests.cs
index 55afbb4cefff..169e7633e5db 100644
--- a/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/KeyManagement/KeyRingProviderTests.cs
+++ b/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/KeyManagement/KeyRingProviderTests.cs
@@ -140,6 +140,47 @@ public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_CreatesNewKey
Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy", "CreateNewKey", "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
}
+ [Fact]
+ public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_CreatesNewKeyWithImmediateActivation_NewKeyIsRevoked()
+ {
+ // Arrange
+ var callSequence = new List();
+
+ var now = (DateTimeOffset)StringToDateTime("2015-03-01 00:00:00Z");
+ var allKeys1 = Array.Empty();
+
+ // This could happen if there were a date-based revocation newer than 2015-03-01
+ var newKey = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z", isRevoked: true);
+ var allKeys2 = new[] { newKey };
+
+ var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
+ callSequence: callSequence,
+ getCacheExpirationTokenReturnValues: new[] { CancellationToken.None, CancellationToken.None },
+ getAllKeysReturnValues: new[] { allKeys1, allKeys2 },
+ createNewKeyCallbacks: new[] {
+ Tuple.Create(now, now + TimeSpan.FromDays(90), newKey)
+ },
+ resolveDefaultKeyPolicyReturnValues: new[]
+ {
+ Tuple.Create(now, (IEnumerable)allKeys1, new DefaultKeyResolution()
+ {
+ DefaultKey = null, // Since there are no keys
+ ShouldGenerateNewKey = true
+ }),
+ Tuple.Create(now, (IEnumerable)allKeys2, new DefaultKeyResolution()
+ {
+ DefaultKey = null, // Since all keys are revoked
+ ShouldGenerateNewKey = true
+ })
+ });
+
+ // Act/Assert
+ Assert.Throws(() => keyRingProvider.GetCacheableKeyRing(now)); // The would-be default key is revoked
+
+ // Still make the usual calls - just throw before creating a keyring
+ Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy", "CreateNewKey", "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
+ }
+
[Fact]
public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_CreatesNewKeyWithImmediateActivation_StillNoDefaultKey_ReturnsNewlyCreatedKey()
{