diff --git a/src/D2L.Security.OAuth2/Keys/KeyManagementService.cs b/src/D2L.Security.OAuth2/Keys/KeyManagementService.cs index 0b8bea9e..70583f20 100644 --- a/src/D2L.Security.OAuth2/Keys/KeyManagementService.cs +++ b/src/D2L.Security.OAuth2/Keys/KeyManagementService.cs @@ -17,6 +17,20 @@ public sealed partial class KeyManagementService : IKeyManagementService, IPriva private D2LSecurityToken m_current = null; + // This controls how long the background refresh is going to wait for a + // new key to be generated + private static readonly TimeSpan BackgroundRefreshDelay = TimeSpan.FromMinutes( 1 ); + + // This controls how frequently background refresh will retry if it + // doesn't find a key + private static readonly TimeSpan BackgroundRefreshRetryDelay = TimeSpan.FromMinutes( 1 ); + + // Wait for the delay and some number of retries before making GetSigningCredentials + // do a foreground Refresh + private static readonly TimeSpan GetSigningCredentialsRefreshGracePeriod + = BackgroundRefreshDelay + + BackgroundRefreshRetryDelay + BackgroundRefreshRetryDelay; + internal KeyManagementService( IPublicKeyDataProvider publicKeys, IPrivateKeyDataProvider privateKeys, @@ -51,7 +65,9 @@ async Task IPrivateKeyProvider.GetSigningCredentialsAsync() { var now = m_clock.UtcNow; - if ( current == null || current.ValidTo <= now ) { + if ( current == null + || ExpectedTimeOfNewUsableKey( current ) + GetSigningCredentialsRefreshGracePeriod < now + ) { // Slow path: RefreshKeyAsync() wasn't called on boot and/or it // isn't being called in a background job. await RefreshKeyAsync( now ) @@ -67,6 +83,13 @@ await RefreshKeyAsync( now ) return current.Ref(); } + private DateTimeOffset ExpectedTimeOfNewUsableKey( D2LSecurityToken current ) + // A new key will get generated some time before the current key + // expires, but will only become usable some time after that. + => current.ValidTo + - m_config.KeyRotationBuffer + + m_config.KeyTimeUntilUse; + [GenerateSync] async Task IKeyManagementService.RefreshKeyAsync() { var now = m_clock.UtcNow; @@ -81,20 +104,16 @@ await RefreshKeyAsync( now ) return TimeSpan.FromSeconds( 10 ); } - // A new key will get generated some time before the current key - // expires, but will only become usable some time after that. - var expectedTimeOfNewUsableKey = current.ValidTo - - m_config.KeyRotationBuffer - + m_config.KeyTimeUntilUse; + var expectedTimeOfNewUsableKey = ExpectedTimeOfNewUsableKey( current ); if( now > expectedTimeOfNewUsableKey ) { // If we would have expected a new key by now, retry again in a // bit. This code branch supports configuration changes mostly. - return TimeSpan.FromMinutes( 1 ); + return BackgroundRefreshRetryDelay; } else { // Otherwise use that but with a little buffer for key // generation time/imprecisely scheduled cron jobs. - return expectedTimeOfNewUsableKey.AddMinutes( 1 ) - now; + return expectedTimeOfNewUsableKey + BackgroundRefreshDelay - now; } }