Skip to content

Commit 919618d

Browse files
authored
Throw if the default key is revoked (#54278)
Under somewhat contrived circumstances, it's possible that the keyring, on determining that no suitable default key is available, will generate a new, immediately-activated key and that that key will also be immediately-revoked. For example, it's possible to revoke all keys created before a given date and clock skew between servers could result in that being in the future for some servers. It's also possible that a third-party `IDefaultKeyResolver` could select a revoked key, since the contract doesn't state that it should not. Having said that, we haven't really hardened against other misbehavior by resolvers, so this isn't terribly compelling. Still, no harm in throwing - better than using a revoked key to encrypt data.
1 parent 42e715d commit 919618d

File tree

5 files changed

+60
-0
lines changed

5 files changed

+60
-0
lines changed

src/DataProtection/DataProtection/src/Error.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,10 @@ public static InvalidOperationException XmlKeyManager_DuplicateKey(Guid keyId)
9191
var message = string.Format(CultureInfo.CurrentCulture, Resources.XmlKeyManager_DuplicateKey, keyId);
9292
return new InvalidOperationException(message);
9393
}
94+
95+
public static InvalidOperationException KeyRingProvider_DefaultKeyRevoked(Guid id)
96+
{
97+
var message = string.Format(CultureInfo.CurrentCulture, Resources.KeyRingProvider_DefaultKeyRevoked, id);
98+
return new InvalidOperationException(message);
99+
}
94100
}

src/DataProtection/DataProtection/src/KeyManagement/KeyRingProvider.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ private CacheableKeyRing CreateCacheableKeyRingCoreStep2(DateTimeOffset now, Can
155155
// Invariant: our caller ensures that CreateEncryptorInstance succeeded at least once
156156
Debug.Assert(defaultKey.CreateEncryptor() != null);
157157

158+
// This can happen if there's a date-based revocation that's in the future (e.g. because of clock skew)
159+
if (defaultKey.IsRevoked)
160+
{
161+
_logger.KeyRingDefaultKeyIsRevoked(defaultKey.KeyId);
162+
throw Error.KeyRingProvider_DefaultKeyRevoked(defaultKey.KeyId);
163+
}
164+
158165
_logger.UsingKeyAsDefaultKey(defaultKey.KeyId);
159166

160167
var nextAutoRefreshTime = now + GetRefreshPeriodWithJitter(KeyManagementOptions.KeyRingRefreshPeriod);

src/DataProtection/DataProtection/src/LoggingExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,7 @@ private static bool IsLogLevelEnabledCore([NotNullWhen(true)] ILogger? logger, L
249249

250250
[LoggerMessage(64, LogLevel.Debug, "Not enabling read-only key access because an XML encryptor has been specified", EventName = "NotUsingReadOnlyKeyConfigurationBecauseOfEncryptor")]
251251
public static partial void NotUsingReadOnlyKeyConfigurationBecauseOfEncryptor(this ILogger logger);
252+
253+
[LoggerMessage(72, LogLevel.Error, "The key ring's default data protection key {KeyId:B} has been revoked.", EventName = "KeyRingDefaultKeyIsRevoked")]
254+
public static partial void KeyRingDefaultKeyIsRevoked(this ILogger logger, Guid keyId);
252255
}

src/DataProtection/DataProtection/src/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@
187187
<data name="KeyRingProvider_NoDefaultKey_AutoGenerateDisabled" xml:space="preserve">
188188
<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. For more information go to https://aka.ms/aspnet/dataprotectionwarning</value>
189189
</data>
190+
<data name="KeyRingProvider_DefaultKeyRevoked" xml:space="preserve">
191+
<value>The key ring's default data protection key {0:B} has been revoked.</value>
192+
</data>
190193
<data name="LifetimeMustNotBeNegative" xml:space="preserve">
191194
<value>{0} must not be negative.</value>
192195
</data>

src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/KeyManagement/KeyRingProviderTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,47 @@ public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_CreatesNewKey
145145
Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy", "CreateNewKey", "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
146146
}
147147

148+
[Fact]
149+
public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_CreatesNewKeyWithImmediateActivation_NewKeyIsRevoked()
150+
{
151+
// Arrange
152+
var callSequence = new List<string>();
153+
154+
var now = (DateTimeOffset)StringToDateTime("2015-03-01 00:00:00Z");
155+
var allKeys1 = Array.Empty<IKey>();
156+
157+
// This could happen if there were a date-based revocation newer than 2015-03-01
158+
var newKey = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z", isRevoked: true);
159+
var allKeys2 = new[] { newKey };
160+
161+
var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
162+
callSequence: callSequence,
163+
getCacheExpirationTokenReturnValues: new[] { CancellationToken.None, CancellationToken.None },
164+
getAllKeysReturnValues: new[] { allKeys1, allKeys2 },
165+
createNewKeyCallbacks: new[] {
166+
Tuple.Create(now, now + TimeSpan.FromDays(90), newKey)
167+
},
168+
resolveDefaultKeyPolicyReturnValues: new[]
169+
{
170+
Tuple.Create(now, (IEnumerable<IKey>)allKeys1, new DefaultKeyResolution()
171+
{
172+
DefaultKey = null, // Since there are no keys
173+
ShouldGenerateNewKey = true
174+
}),
175+
Tuple.Create(now, (IEnumerable<IKey>)allKeys2, new DefaultKeyResolution()
176+
{
177+
DefaultKey = null, // Since all keys are revoked
178+
ShouldGenerateNewKey = true
179+
})
180+
});
181+
182+
// Act/Assert
183+
Assert.Throws<InvalidOperationException>(() => keyRingProvider.GetCacheableKeyRing(now)); // The would-be default key is revoked
184+
185+
// Still make the usual calls - just throw before creating a keyring
186+
Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy", "CreateNewKey", "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
187+
}
188+
148189
[Fact]
149190
public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_CreatesNewKeyWithImmediateActivation_StillNoDefaultKey_ReturnsNewlyCreatedKey()
150191
{

0 commit comments

Comments
 (0)