Skip to content

Commit

Permalink
Merge branch 'main' into features/net5
Browse files Browse the repository at this point in the history
* main:
  remove flaky time test
  typos/comments
  make automatic key mgmt first class in key material service
  fix code comments
  add flag to indicate if seralized key is data protected
  remove common base class
  add key management as default
  update license headers
  add key mgmt unit tests
  move key management files in
  add storage models for keymgmt
  • Loading branch information
leastprivilege committed Oct 23, 2020
2 parents 4e68669 + dc20fde commit c473821
Show file tree
Hide file tree
Showing 44 changed files with 3,141 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,5 @@ samples/KeyManagement/FileSystem/signingkeys/
workspace.xml

src/IdentityServer4/host/identityserver.db
tempkey.jwk
tempkey.jwk
src/IdentityServer/host/keys/is-signing-key-*.json
7 changes: 7 additions & 0 deletions src/IdentityServer/host/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ public void ConfigureServices(IServiceCollection services)
options.MutualTls.Enabled = true;
options.MutualTls.DomainName = "mtls";
//options.MutualTls.AlwaysEmitConfirmationClaim = true;
options.KeyManagement.InitializationDuration = TimeSpan.Zero;
//options.KeyManagement.Enabled = false;
//options.KeyManagement.KeyActivationDelay = TimeSpan.FromSeconds(10);
//options.KeyManagement.KeyExpiration = options.KeyManagement.KeyActivationDelay * 2;
//options.KeyManagement.KeyRetirement = options.KeyManagement.KeyActivationDelay * 3;
})
.AddInMemoryClients(Clients.Get())
.AddInMemoryIdentityResources(Resources.IdentityResources)
Expand Down
2 changes: 1 addition & 1 deletion src/IdentityServer/src/Configuration/CryptoHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Duende Software. All rights reserved.
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
using Duende.IdentityServer.Extensions;
using Duende.IdentityServer.Hosting.FederatedSignOut;
using Duende.IdentityServer.Services.Default;
using Duende.IdentityServer.Services.KeyManagement;
using System.IO;
using Microsoft.Extensions.Logging;

namespace Microsoft.Extensions.DependencyInjection
{
Expand Down Expand Up @@ -174,7 +177,7 @@ public static IIdentityServerBuilder AddPluggableServices(this IIdentityServerBu
builder.Services.TryAddTransient<IBackChannelLogoutService, DefaultBackChannelLogoutService>();
builder.Services.TryAddTransient<IResourceValidator, DefaultResourceValidator>();
builder.Services.TryAddTransient<IScopeParser, DefaultScopeParser>();

builder.AddJwtRequestUriHttpClient();
builder.AddBackChannelLogoutHttpClient();

Expand All @@ -187,6 +190,26 @@ public static IIdentityServerBuilder AddPluggableServices(this IIdentityServerBu
return builder;
}

/// <summary>
/// Adds key management services.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns></returns>
public static IIdentityServerBuilder AddKeyManagement(this IIdentityServerBuilder builder)
{
builder.Services.TryAddTransient<IAutomaticKeyManagerKeyStore, AutomaticKeyManagerKeyStore>();
builder.Services.TryAddTransient<IKeyManager, KeyManager>();
builder.Services.TryAddTransient<INewKeyLock, DefaultKeyLock>();
builder.Services.TryAddTransient<ISigningKeyProtector, DataProtectionKeyProtector>();
builder.Services.TryAddTransient<ISigningKeyStoreCache, NopKeyStoreCache>();
builder.Services.TryAddTransient(provider => provider.GetRequiredService<IdentityServerOptions>().KeyManagement);

var path = Path.Combine(Directory.GetCurrentDirectory(), "keys");
builder.Services.TryAddSingleton<ISigningKeyStore>(x => new FileSystemKeyStore(path, x.GetRequiredService<ILogger<FileSystemKeyStore>>()));

return builder;
}

/// <summary>
/// Adds the validators.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Duende Software. All rights reserved.
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


Expand Down Expand Up @@ -41,6 +41,7 @@ public static IIdentityServerBuilder AddIdentityServer(this IServiceCollection s
.AddCoreServices()
.AddDefaultEndpoints()
.AddPluggableServices()
.AddKeyManagement()
.AddValidators()
.AddResponseGenerators()
.AddDefaultSecretParsers()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,10 @@ public class IdentityServerOptions
/// Gets or sets the mutual TLS options.
/// </summary>
public MutualTlsOptions MutualTls { get; set; } = new MutualTlsOptions();

/// <summary>
/// Gets or sets the signing key management options.
/// </summary>
public KeyManagementOptions KeyManagement { get; set; } = new KeyManagementOptions();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Duende.IdentityServer.Models;
using System;
using static Duende.IdentityServer.IdentityServerConstants;

namespace Duende.IdentityServer.Configuration
{
/// <summary>
/// Options to configure behavior of KeyManager.
/// </summary>
public class KeyManagementOptions
{
/// <summary>
/// Specifies if key management should be enabled. Defaults to true.
/// </summary>
public bool Enabled { get; set; } = true;

/// <summary>
/// The signing algorithm to use.
/// Defaults to RS256.
/// </summary>
public RsaSigningAlgorithm SigningAlgorithm { get; set; } = RsaSigningAlgorithm.RS256;

/// <summary>
/// Key size (in bits) of SigningAlgorithm.
/// </summary>
public int KeySize => SigningAlgorithm switch
{
RsaSigningAlgorithm.RS256 => 256 * 8,
RsaSigningAlgorithm.RS384 => 384 * 8,
RsaSigningAlgorithm.RS512 => 512 * 8,

RsaSigningAlgorithm.PS256 => 256 * 8,
RsaSigningAlgorithm.PS384 => 384 * 8,
RsaSigningAlgorithm.PS512 => 512 * 8,
_ => throw new ArgumentException("Invalid signing algorithm value", nameof(SigningAlgorithm)),
};

/// <summary>
/// Type of key to use. Defaults to RSA.
/// </summary>
public KeyType KeyType { get; set; } = KeyType.RSA;

/// <summary>
/// When no keys have been created yet, this is the window of time considered to be an initialization
/// period to allow all servers to synchronize if the keys are being created for the first time.
/// Defaults to 5 minutes.
/// </summary>
public TimeSpan InitializationDuration { get; set; } = TimeSpan.FromMinutes(5);
/// <summary>
/// Delay used when re-loading from the store when the initialization period. It allows
/// other servers more time to write new keys so other servers can include them.
/// Defaults to 5 seconds.
/// </summary>
public TimeSpan InitializationSynchronizationDelay { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// Cache duration when within the initialization period.
/// Defaults to 1 minute.
/// </summary>
public TimeSpan InitializationKeyCacheDuration { get; set; } = TimeSpan.FromMinutes(1);

/// <summary>
/// When in normal operation, duration to cache keys from store.
/// Defaults to 24 hours.
/// </summary>
public TimeSpan KeyCacheDuration { get; set; } = TimeSpan.FromHours(24);

/// <summary>
/// Time expected to propigate new keys to all servers, and time expected all clients to refresh discovery.
/// Defaults to 14 days.
/// </summary>
public TimeSpan KeyActivationDelay { get; set; } = TimeSpan.FromDays(14);

/// <summary>
/// Age at which keys will no longer be used for signing, but will still be used in discovery for validation.
/// Defaults to 90 days.
/// </summary>
public TimeSpan KeyExpiration { get; set; } = TimeSpan.FromDays(90);

/// <summary>
/// Age at which keys will no longer be used in discovery. they can be deleted at this point.
/// Defaults to 180 days.
/// </summary>
public TimeSpan KeyRetirement { get; set; } = TimeSpan.FromDays(180);

/// <summary>
/// Automatically delete retired keys.
/// Defaults to true.
/// </summary>
public bool DeleteRetiredKeys { get; set; } = true;

/// <summary>
/// Automatically protect keys in the storage using data protection.
/// Defaults to true.
/// </summary>
public bool DataProtectKeys { get; set; } = true;

internal void Validate()
{
if (InitializationDuration < TimeSpan.Zero) throw new Exception("InitializationDuration must be greater than or equal zero.");
if (InitializationSynchronizationDelay < TimeSpan.Zero) throw new Exception("InitializationSynchronizationDelay must be greater than or equal zero.");

if (InitializationKeyCacheDuration < TimeSpan.Zero) throw new Exception("InitializationKeyCacheDuration must be greater than or equal zero.");
if (KeyCacheDuration < TimeSpan.Zero) throw new Exception("KeyCacheDuration must be greater than or equal zero.");

if (KeyActivationDelay <= TimeSpan.Zero) throw new Exception("KeyActivationDelay must be greater than zero.");
if (KeyExpiration <= TimeSpan.Zero) throw new Exception("KeyExpiration must be greater than zero.");
if (KeyRetirement <= TimeSpan.Zero) throw new Exception("KeyRetirement must be greater than zero.");

if (KeyExpiration <= KeyActivationDelay) throw new Exception("KeyExpiration must be longer than KeyActivationDelay.");
if (KeyRetirement <= KeyExpiration) throw new Exception("KeyRetirement must be longer than KeyExpiration.");
}
}
}
2 changes: 1 addition & 1 deletion src/IdentityServer/src/Duende.IdentityServer.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>Duende.IdentityServer</PackageId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


using Duende.IdentityServer.Configuration;
using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Tokens;
using System;

namespace Duende.IdentityServer.Extensions
{
/// <summary>
/// Extensions for Key Management
/// </summary>
public static class KeyManagementExtensionsExtensions
{
internal static RsaSecurityKey CreateRsaSecurityKey(this KeyManagementOptions options)
{
return CryptoHelper.CreateRsaSecurityKey(options.KeySize);
}

internal static bool IsRetired(this KeyManagementOptions options, TimeSpan diff)
{
return (diff >= options.KeyRetirement);
}

internal static bool IsExpired(this KeyManagementOptions options, TimeSpan diff)
{
return (diff >= options.KeyExpiration);
}

internal static bool IsWithinInitializationDuration(this KeyManagementOptions options, TimeSpan diff)
{
return (diff <= options.InitializationDuration);
}

internal static TimeSpan GetAge(this ISystemClock clock, DateTime date)
{
var now = clock.UtcNow.DateTime;
if (date > now) now = date;
return now.Subtract(date);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Duende Software. All rights reserved.
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


Expand All @@ -10,6 +10,7 @@
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Stores;
using Duende.IdentityServer.Extensions;
using Duende.IdentityServer.Services.KeyManagement;

namespace Duende.IdentityServer.Services
{
Expand All @@ -21,16 +22,22 @@ public class DefaultKeyMaterialService : IKeyMaterialService
{
private readonly IEnumerable<ISigningCredentialStore> _signingCredentialStores;
private readonly IEnumerable<IValidationKeysStore> _validationKeysStores;
private readonly IAutomaticKeyManagerKeyStore _keyManagerKeyStore;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultKeyMaterialService"/> class.
/// </summary>
/// <param name="validationKeysStores">The validation keys stores.</param>
/// <param name="signingCredentialStores">The signing credential store.</param>
public DefaultKeyMaterialService(IEnumerable<IValidationKeysStore> validationKeysStores, IEnumerable<ISigningCredentialStore> signingCredentialStores)
/// <param name="keyManagerKeyStore">The store for automatic key management.</param>
public DefaultKeyMaterialService(
IEnumerable<IValidationKeysStore> validationKeysStores,
IEnumerable<ISigningCredentialStore> signingCredentialStores,
IAutomaticKeyManagerKeyStore keyManagerKeyStore)
{
_signingCredentialStores = signingCredentialStores;
_validationKeysStores = validationKeysStores;
_keyManagerKeyStore = keyManagerKeyStore;
}

/// <inheritdoc/>
Expand All @@ -40,7 +47,23 @@ public async Task<SigningCredentials> GetSigningCredentialsAsync(IEnumerable<str
{
if (allowedAlgorithms.IsNullOrEmpty())
{
return await _signingCredentialStores.First().GetSigningCredentialsAsync();
var automaticKey = await _keyManagerKeyStore.GetSigningCredentialsAsync();
if (automaticKey != null)
{
return automaticKey;
}

var list = _signingCredentialStores.ToList();
for (var i = 0; i < list.Count; i++)
{
var key = await list[i].GetSigningCredentialsAsync();
if (key != null)
{
return key;
}
}

throw new InvalidOperationException($"No signing credential registered.");
}

var credential = (await GetAllSigningCredentialsAsync()).FirstOrDefault(c => allowedAlgorithms.Contains(c.Algorithm));
Expand All @@ -60,9 +83,19 @@ public async Task<IEnumerable<SigningCredentials>> GetAllSigningCredentialsAsync
{
var credentials = new List<SigningCredentials>();

var automaticSigningKey = await _keyManagerKeyStore.GetSigningCredentialsAsync();
if (automaticSigningKey != null)
{
credentials.Add(automaticSigningKey);
}

foreach (var store in _signingCredentialStores)
{
credentials.Add(await store.GetSigningCredentialsAsync());
var signingKey = await store.GetSigningCredentialsAsync();
if (signingKey != null)
{
credentials.Add(signingKey);
}
}

return credentials;
Expand All @@ -73,9 +106,19 @@ public async Task<IEnumerable<SecurityKeyInfo>> GetValidationKeysAsync()
{
var keys = new List<SecurityKeyInfo>();

var automaticSigningKeys = await _keyManagerKeyStore.GetValidationKeysAsync();
if (automaticSigningKeys?.Any() == true)
{
keys.AddRange(automaticSigningKeys);
}

foreach (var store in _validationKeysStores)
{
keys.AddRange(await store.GetValidationKeysAsync());
var validationKeys = await store.GetValidationKeysAsync();
if (validationKeys.Any())
{
keys.AddRange(validationKeys);
}
}

return keys;
Expand Down
Loading

0 comments on commit c473821

Please sign in to comment.