| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 |
- // Copyright (c) .NET Foundation. All rights reserved.
- // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.Threading;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
- using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
- using Microsoft.AspNetCore.Testing;
- using Microsoft.Extensions.DependencyInjection;
- using Moq;
- using Xunit;
- using static System.FormattableString;
- namespace Microsoft.AspNetCore.DataProtection.KeyManagement
- {
- public class KeyRingProviderTests
- {
- [Fact]
- public void CreateCacheableKeyRing_NoGenerationRequired_DefaultKeyExpiresAfterRefreshPeriod()
- {
- // Arrange
- var callSequence = new List<string>();
- var expirationCts = new CancellationTokenSource();
- var now = StringToDateTime("2015-03-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");
- var allKeys = new[] { key1, key2 };
- var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
- callSequence: callSequence,
- getCacheExpirationTokenReturnValues: new[] { expirationCts.Token },
- getAllKeysReturnValues: new[] { allKeys },
- createNewKeyCallbacks: null,
- resolveDefaultKeyPolicyReturnValues: new[]
- {
- Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys, new DefaultKeyResolution()
- {
- DefaultKey = key1,
- ShouldGenerateNewKey = 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);
- }
- [Fact]
- public void CreateCacheableKeyRing_NoGenerationRequired_DefaultKeyExpiresBeforeRefreshPeriod()
- {
- // Arrange
- var callSequence = new List<string>();
- var expirationCts = new CancellationTokenSource();
- var now = StringToDateTime("2016-02-29 20: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");
- var allKeys = new[] { key1, key2 };
- var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
- callSequence: callSequence,
- getCacheExpirationTokenReturnValues: new[] { expirationCts.Token },
- getAllKeysReturnValues: new[] { allKeys },
- createNewKeyCallbacks: null,
- resolveDefaultKeyPolicyReturnValues: new[]
- {
- Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys, new DefaultKeyResolution()
- {
- DefaultKey = key1,
- ShouldGenerateNewKey = false
- })
- });
- // Act
- var cacheableKeyRing = keyRingProvider.GetCacheableKeyRing(now);
- // Assert
- Assert.Equal(key1.KeyId, cacheableKeyRing.KeyRing.DefaultKeyId);
- Assert.Equal(StringToDateTime("2016-03-01 00:00:00Z"), cacheableKeyRing.ExpirationTimeUtc);
- Assert.True(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- expirationCts.Cancel();
- Assert.False(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
- }
- [Fact]
- public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_CreatesNewKeyWithImmediateActivation()
- {
- // Arrange
- var callSequence = new List<string>();
- var expirationCts1 = new CancellationTokenSource();
- var expirationCts2 = new CancellationTokenSource();
- var now = StringToDateTime("2015-03-01 00:00:00Z");
- var allKeys1 = new IKey[0];
- 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");
- var allKeys2 = new[] { key1, key2 };
- var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
- callSequence: callSequence,
- getCacheExpirationTokenReturnValues: new[] { expirationCts1.Token, expirationCts2.Token },
- getAllKeysReturnValues: new[] { allKeys1, allKeys2 },
- createNewKeyCallbacks: new[] {
- Tuple.Create((DateTimeOffset)now, (DateTimeOffset)now + TimeSpan.FromDays(90), CreateKey())
- },
- resolveDefaultKeyPolicyReturnValues: new[]
- {
- Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys1, new DefaultKeyResolution()
- {
- DefaultKey = null,
- ShouldGenerateNewKey = true
- }),
- Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys2, new DefaultKeyResolution()
- {
- DefaultKey = key1,
- ShouldGenerateNewKey = 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));
- expirationCts1.Cancel();
- Assert.True(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- expirationCts2.Cancel();
- Assert.False(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy", "CreateNewKey", "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence);
- }
- [Fact]
- public void CreateCacheableKeyRing_GenerationRequired_NoDefaultKey_CreatesNewKeyWithImmediateActivation_StillNoDefaultKey_ReturnsNewlyCreatedKey()
- {
- // Arrange
- var callSequence = new List<string>();
- var expirationCts1 = new CancellationTokenSource();
- var expirationCts2 = new CancellationTokenSource();
- var now = StringToDateTime("2015-03-01 00:00:00Z");
- var allKeys = new IKey[0];
- var newlyCreatedKey = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z");
- var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
- callSequence: callSequence,
- getCacheExpirationTokenReturnValues: new[] { expirationCts1.Token, expirationCts2.Token },
- getAllKeysReturnValues: new[] { allKeys, allKeys },
- createNewKeyCallbacks: new[] {
- Tuple.Create((DateTimeOffset)now, (DateTimeOffset)now + TimeSpan.FromDays(90), newlyCreatedKey)
- },
- resolveDefaultKeyPolicyReturnValues: new[]
- {
- Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys, new DefaultKeyResolution()
- {
- DefaultKey = null,
- ShouldGenerateNewKey = true
- }),
- Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys, new DefaultKeyResolution()
- {
- DefaultKey = null,
- ShouldGenerateNewKey = true
- })
- });
- // Act
- var cacheableKeyRing = keyRingProvider.GetCacheableKeyRing(now);
- // Assert
- Assert.Equal(newlyCreatedKey.KeyId, cacheableKeyRing.KeyRing.DefaultKeyId);
- AssertWithinJitterRange(cacheableKeyRing.ExpirationTimeUtc, now);
- Assert.True(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- expirationCts1.Cancel();
- Assert.True(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- expirationCts2.Cancel();
- Assert.False(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- 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), CreateKey())
- },
- 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()
- {
- // Arrange
- var callSequence = new List<string>();
- var expirationCts1 = new CancellationTokenSource();
- var expirationCts2 = 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 allKeys1 = new[] { key1 };
- var key2 = CreateKey("2016-03-01 00:00:00Z", "2017-03-01 00:00:00Z");
- var allKeys2 = new[] { key1, key2 };
- var keyRingProvider = SetupCreateCacheableKeyRingTestAndCreateKeyManager(
- callSequence: callSequence,
- getCacheExpirationTokenReturnValues: new[] { expirationCts1.Token, expirationCts2.Token },
- getAllKeysReturnValues: new[] { allKeys1, allKeys2 },
- createNewKeyCallbacks: new[] {
- Tuple.Create(key1.ExpirationDate, (DateTimeOffset)now + TimeSpan.FromDays(90), CreateKey())
- },
- resolveDefaultKeyPolicyReturnValues: new[]
- {
- Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys1, new DefaultKeyResolution()
- {
- DefaultKey = key1,
- ShouldGenerateNewKey = true
- }),
- Tuple.Create((DateTimeOffset)now, (IEnumerable<IKey>)allKeys2, new DefaultKeyResolution()
- {
- DefaultKey = key2,
- ShouldGenerateNewKey = false
- })
- });
- // Act
- var cacheableKeyRing = keyRingProvider.GetCacheableKeyRing(now);
- // Assert
- Assert.Equal(key2.KeyId, cacheableKeyRing.KeyRing.DefaultKeyId);
- AssertWithinJitterRange(cacheableKeyRing.ExpirationTimeUtc, now);
- Assert.True(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- expirationCts1.Cancel();
- Assert.True(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- expirationCts2.Cancel();
- Assert.False(CacheableKeyRing.IsValid(cacheableKeyRing, now));
- 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);
- }
- [Fact]
- public void CreateCacheableKeyRing_GenerationRequired_WithFallbackKey_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", "2015-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()
- {
- FallbackKey = 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, IKey>> createNewKeyCallbacks,
- IEnumerable<Tuple<DateTimeOffset, IEnumerable<IKey>, DefaultKeyResolution>> resolveDefaultKeyPolicyReturnValues,
- KeyManagementOptions keyManagementOptions = null)
- {
- var getCacheExpirationTokenReturnValuesEnumerator = getCacheExpirationTokenReturnValues.GetEnumerator();
- var mockKeyManager = new Mock<IKeyManager>(MockBehavior.Strict);
- mockKeyManager.Setup(o => o.GetCacheExpirationToken())
- .Returns(() =>
- {
- callSequence.Add("GetCacheExpirationToken");
- getCacheExpirationTokenReturnValuesEnumerator.MoveNext();
- return getCacheExpirationTokenReturnValuesEnumerator.Current;
- });
- var getAllKeysReturnValuesEnumerator = getAllKeysReturnValues.GetEnumerator();
- mockKeyManager.Setup(o => o.GetAllKeys())
- .Returns(() =>
- {
- callSequence.Add("GetAllKeys");
- getAllKeysReturnValuesEnumerator.MoveNext();
- return getAllKeysReturnValuesEnumerator.Current;
- });
- if (createNewKeyCallbacks != null)
- {
- var createNewKeyCallbacksEnumerator = createNewKeyCallbacks.GetEnumerator();
- mockKeyManager.Setup(o => o.CreateNewKey(It.IsAny<DateTimeOffset>(), It.IsAny<DateTimeOffset>()))
- .Returns<DateTimeOffset, DateTimeOffset>((activationDate, expirationDate) =>
- {
- callSequence.Add("CreateNewKey");
- createNewKeyCallbacksEnumerator.MoveNext();
- Assert.Equal(createNewKeyCallbacksEnumerator.Current.Item1, activationDate);
- Assert.Equal(createNewKeyCallbacksEnumerator.Current.Item2, expirationDate);
- return createNewKeyCallbacksEnumerator.Current.Item3;
- });
- }
- var resolveDefaultKeyPolicyReturnValuesEnumerator = resolveDefaultKeyPolicyReturnValues.GetEnumerator();
- var mockDefaultKeyResolver = new Mock<IDefaultKeyResolver>(MockBehavior.Strict);
- mockDefaultKeyResolver.Setup(o => o.ResolveDefaultKeyPolicy(It.IsAny<DateTimeOffset>(), It.IsAny<IEnumerable<IKey>>()))
- .Returns<DateTimeOffset, IEnumerable<IKey>>((now, allKeys) =>
- {
- callSequence.Add("ResolveDefaultKeyPolicy");
- resolveDefaultKeyPolicyReturnValuesEnumerator.MoveNext();
- Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item1, now);
- Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item2, allKeys);
- return resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item3;
- });
- return CreateKeyRingProvider(mockKeyManager.Object, mockDefaultKeyResolver.Object, keyManagementOptions);
- }
- [Fact]
- public void GetCurrentKeyRing_NoKeyRingCached_CachesAndReturns()
- {
- // Arrange
- var now = StringToDateTime("2015-03-01 00:00:00Z");
- var expectedKeyRing = new Mock<IKeyRing>().Object;
- var mockCacheableKeyRingProvider = new Mock<ICacheableKeyRingProvider>();
- mockCacheableKeyRingProvider
- .Setup(o => o.GetCacheableKeyRing(now))
- .Returns(new CacheableKeyRing(
- expirationToken: CancellationToken.None,
- expirationTime: StringToDateTime("2015-03-02 00:00:00Z"),
- keyRing: expectedKeyRing));
- var keyRingProvider = CreateKeyRingProvider(mockCacheableKeyRingProvider.Object);
- // Act
- var retVal1 = keyRingProvider.GetCurrentKeyRingCore(now);
- var retVal2 = keyRingProvider.GetCurrentKeyRingCore(now + TimeSpan.FromHours(1));
- // Assert - underlying provider only should have been called once
- Assert.Same(expectedKeyRing, retVal1);
- Assert.Same(expectedKeyRing, retVal2);
- mockCacheableKeyRingProvider.Verify(o => o.GetCacheableKeyRing(It.IsAny<DateTimeOffset>()), Times.Once);
- }
- [Fact]
- public void GetCurrentKeyRing_KeyRingCached_AfterExpiration_ClearsCache()
- {
- // Arrange
- var now = StringToDateTime("2015-03-01 00:00:00Z");
- var expectedKeyRing1 = new Mock<IKeyRing>().Object;
- var expectedKeyRing2 = new Mock<IKeyRing>().Object;
- var mockCacheableKeyRingProvider = new Mock<ICacheableKeyRingProvider>();
- mockCacheableKeyRingProvider
- .Setup(o => o.GetCacheableKeyRing(now))
- .Returns(new CacheableKeyRing(
- expirationToken: CancellationToken.None,
- expirationTime: StringToDateTime("2015-03-01 00:30:00Z"), // expire in half an hour
- keyRing: expectedKeyRing1));
- mockCacheableKeyRingProvider
- .Setup(o => o.GetCacheableKeyRing(now + TimeSpan.FromHours(1)))
- .Returns(new CacheableKeyRing(
- expirationToken: CancellationToken.None,
- expirationTime: StringToDateTime("2015-03-02 00:00:00Z"),
- keyRing: expectedKeyRing2));
- var keyRingProvider = CreateKeyRingProvider(mockCacheableKeyRingProvider.Object);
- // Act
- var retVal1 = keyRingProvider.GetCurrentKeyRingCore(now);
- var retVal2 = keyRingProvider.GetCurrentKeyRingCore(now + TimeSpan.FromHours(1));
- // Assert - underlying provider only should have been called once
- Assert.Same(expectedKeyRing1, retVal1);
- Assert.Same(expectedKeyRing2, retVal2);
- mockCacheableKeyRingProvider.Verify(o => o.GetCacheableKeyRing(It.IsAny<DateTimeOffset>()), Times.Exactly(2));
- }
- [Fact]
- public void GetCurrentKeyRing_NoExistingKeyRing_HoldsAllThreadsUntilKeyRingCreated()
- {
- // Arrange
- var now = StringToDateTime("2015-03-01 00:00:00Z");
- var expectedKeyRing = new Mock<IKeyRing>().Object;
- var mockCacheableKeyRingProvider = new Mock<ICacheableKeyRingProvider>();
- var keyRingProvider = CreateKeyRingProvider(mockCacheableKeyRingProvider.Object);
- // This test spawns a background thread which calls GetCurrentKeyRing then waits
- // for the foreground thread to call GetCurrentKeyRing. When the foreground thread
- // blocks (inside the lock), the background thread will return the cached keyring
- // object, and the foreground thread should consume that same object instance.
- TimeSpan testTimeout = TimeSpan.FromSeconds(10);
- Thread foregroundThread = Thread.CurrentThread;
- ManualResetEventSlim mreBackgroundThreadHasCalledGetCurrentKeyRing = new ManualResetEventSlim();
- ManualResetEventSlim mreForegroundThreadIsCallingGetCurrentKeyRing = new ManualResetEventSlim();
- var backgroundGetKeyRingTask = Task.Run(() =>
- {
- mockCacheableKeyRingProvider
- .Setup(o => o.GetCacheableKeyRing(now))
- .Returns(() =>
- {
- mreBackgroundThreadHasCalledGetCurrentKeyRing.Set();
- Assert.True(mreForegroundThreadIsCallingGetCurrentKeyRing.Wait(testTimeout), "Test timed out.");
- SpinWait.SpinUntil(() => (foregroundThread.ThreadState & ThreadState.WaitSleepJoin) != 0, testTimeout);
- return new CacheableKeyRing(
- expirationToken: CancellationToken.None,
- expirationTime: StringToDateTime("2015-03-02 00:00:00Z"),
- keyRing: expectedKeyRing);
- });
- return keyRingProvider.GetCurrentKeyRingCore(now);
- });
- Assert.True(mreBackgroundThreadHasCalledGetCurrentKeyRing.Wait(testTimeout), "Test timed out.");
- mreForegroundThreadIsCallingGetCurrentKeyRing.Set();
- var foregroundRetVal = keyRingProvider.GetCurrentKeyRingCore(now);
- backgroundGetKeyRingTask.Wait(testTimeout);
- var backgroundRetVal = backgroundGetKeyRingTask.GetAwaiter().GetResult();
- // Assert - underlying provider only should have been called once
- Assert.Same(expectedKeyRing, foregroundRetVal);
- Assert.Same(expectedKeyRing, backgroundRetVal);
- mockCacheableKeyRingProvider.Verify(o => o.GetCacheableKeyRing(It.IsAny<DateTimeOffset>()), Times.Once);
- }
- [Fact]
- public void GetCurrentKeyRing_WithExpiredExistingKeyRing_AllowsOneThreadToUpdate_ReturnsExistingKeyRingToOtherCallersWithoutBlocking()
- {
- // Arrange
- var originalKeyRing = new Mock<IKeyRing>().Object;
- var originalKeyRingTime = StringToDateTime("2015-03-01 00:00:00Z");
- var updatedKeyRing = new Mock<IKeyRing>().Object;
- var updatedKeyRingTime = StringToDateTime("2015-03-02 00:00:00Z");
- var mockCacheableKeyRingProvider = new Mock<ICacheableKeyRingProvider>();
- var keyRingProvider = CreateKeyRingProvider(mockCacheableKeyRingProvider.Object);
- // In this test, the foreground thread acquires the critial section in GetCurrentKeyRing,
- // and the background thread returns the original key ring rather than blocking while
- // waiting for the foreground thread to update the key ring.
- TimeSpan testTimeout = TimeSpan.FromSeconds(10);
- IKeyRing keyRingReturnedToBackgroundThread = null;
- mockCacheableKeyRingProvider.Setup(o => o.GetCacheableKeyRing(originalKeyRingTime))
- .Returns(new CacheableKeyRing(CancellationToken.None, StringToDateTime("2015-03-02 00:00:00Z"), originalKeyRing));
- mockCacheableKeyRingProvider.Setup(o => o.GetCacheableKeyRing(updatedKeyRingTime))
- .Returns<DateTimeOffset>(dto =>
- {
- // at this point we're inside the critical section - spawn the background thread now
- var backgroundGetKeyRingTask = Task.Run(() =>
- {
- keyRingReturnedToBackgroundThread = keyRingProvider.GetCurrentKeyRingCore(updatedKeyRingTime);
- });
- Assert.True(backgroundGetKeyRingTask.Wait(testTimeout), "Test timed out.");
- return new CacheableKeyRing(CancellationToken.None, StringToDateTime("2015-03-03 00:00:00Z"), updatedKeyRing);
- });
- // Assert - underlying provider only should have been called once with the updated time (by the foreground thread)
- Assert.Same(originalKeyRing, keyRingProvider.GetCurrentKeyRingCore(originalKeyRingTime));
- Assert.Same(updatedKeyRing, keyRingProvider.GetCurrentKeyRingCore(updatedKeyRingTime));
- Assert.Same(originalKeyRing, keyRingReturnedToBackgroundThread);
- mockCacheableKeyRingProvider.Verify(o => o.GetCacheableKeyRing(updatedKeyRingTime), Times.Once);
- }
- [Fact]
- public void GetCurrentKeyRing_WithExpiredExistingKeyRing_UpdateFails_ThrowsButCachesOldKeyRing()
- {
- // Arrange
- var cts = new CancellationTokenSource();
- var mockCacheableKeyRingProvider = new Mock<ICacheableKeyRingProvider>();
- var originalKeyRing = new Mock<IKeyRing>().Object;
- var originalKeyRingTime = StringToDateTime("2015-03-01 00:00:00Z");
- mockCacheableKeyRingProvider.Setup(o => o.GetCacheableKeyRing(originalKeyRingTime))
- .Returns(new CacheableKeyRing(cts.Token, StringToDateTime("2015-03-02 00:00:00Z"), originalKeyRing));
- var throwKeyRingTime = StringToDateTime("2015-03-01 12:00:00Z");
- mockCacheableKeyRingProvider.Setup(o => o.GetCacheableKeyRing(throwKeyRingTime)).Throws(new Exception("How exceptional."));
- var updatedKeyRing = new Mock<IKeyRing>().Object;
- var updatedKeyRingTime = StringToDateTime("2015-03-01 12:02:00Z");
- mockCacheableKeyRingProvider.Setup(o => o.GetCacheableKeyRing(updatedKeyRingTime))
- .Returns(new CacheableKeyRing(CancellationToken.None, StringToDateTime("2015-03-02 00:00:00Z"), updatedKeyRing));
- var keyRingProvider = CreateKeyRingProvider(mockCacheableKeyRingProvider.Object);
- // Act & assert
- Assert.Same(originalKeyRing, keyRingProvider.GetCurrentKeyRingCore(originalKeyRingTime));
- cts.Cancel(); // invalidate the key ring
- ExceptionAssert.Throws<Exception>(() => keyRingProvider.GetCurrentKeyRingCore(throwKeyRingTime), "How exceptional.");
- Assert.Same(originalKeyRing, keyRingProvider.GetCurrentKeyRingCore(throwKeyRingTime));
- Assert.Same(updatedKeyRing, keyRingProvider.GetCurrentKeyRingCore(updatedKeyRingTime));
- mockCacheableKeyRingProvider.Verify(o => o.GetCacheableKeyRing(originalKeyRingTime), Times.Once);
- mockCacheableKeyRingProvider.Verify(o => o.GetCacheableKeyRing(throwKeyRingTime), Times.Once);
- mockCacheableKeyRingProvider.Verify(o => o.GetCacheableKeyRing(updatedKeyRingTime), Times.Once);
- }
- private static KeyRingProvider CreateKeyRingProvider(ICacheableKeyRingProvider cacheableKeyRingProvider)
- {
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton<ICacheableKeyRingProvider>(cacheableKeyRingProvider);
- return new KeyRingProvider(
- keyManager: null,
- keyManagementOptions: null,
- services: serviceCollection.BuildServiceProvider());
- }
- private static ICacheableKeyRingProvider CreateKeyRingProvider(IKeyManager keyManager, IDefaultKeyResolver defaultKeyResolver, KeyManagementOptions keyManagementOptions= null)
- {
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton<IDefaultKeyResolver>(defaultKeyResolver);
- return new KeyRingProvider(
- keyManager: keyManager,
- keyManagementOptions: keyManagementOptions,
- services: serviceCollection.BuildServiceProvider());
- }
- private static void AssertWithinJitterRange(DateTimeOffset actual, DateTimeOffset now)
- {
- // The jitter can cause the actual value to fall in the range [now + 80% of refresh period, now + 100% of refresh period)
- Assert.InRange(actual, now + TimeSpan.FromHours(24 * 0.8), now + TimeSpan.FromHours(24));
- }
- private static DateTime StringToDateTime(string input)
- {
- return DateTimeOffset.ParseExact(input, "u", CultureInfo.InvariantCulture).UtcDateTime;
- }
- private static IKey CreateKey()
- {
- var now = DateTimeOffset.Now;
- return CreateKey(Invariant($"{now:u}"), Invariant($"{now.AddDays(90):u}"));
- }
- private static IKey CreateKey(string activationDate, string expirationDate, bool isRevoked = false)
- {
- var mockKey = new Mock<IKey>();
- mockKey.Setup(o => o.KeyId).Returns(Guid.NewGuid());
- mockKey.Setup(o => o.ActivationDate).Returns(DateTimeOffset.ParseExact(activationDate, "u", CultureInfo.InvariantCulture));
- mockKey.Setup(o => o.ExpirationDate).Returns(DateTimeOffset.ParseExact(expirationDate, "u", CultureInfo.InvariantCulture));
- mockKey.Setup(o => o.IsRevoked).Returns(isRevoked);
- mockKey.Setup(o => o.CreateEncryptorInstance()).Returns(new Mock<IAuthenticatedEncryptor>().Object);
- return mockKey.Object;
- }
- }
- }
|