From 7f646c85545453db21b196b2ad0434770ad75170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 20 Jul 2016 17:10:46 +0200 Subject: [PATCH] Throw an exception when no signing key is explicitly registered and introduce OpenIddictBuilder.AddEphemeralSigningKey() --- samples/Mvc.Server/Startup.cs | 10 ++-- src/OpenIddict.Core/OpenIddictBuilder.cs | 55 +++++++++++++++++++++ src/OpenIddict.Core/OpenIddictExtensions.cs | 6 +++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index aae19c60..f979a8d7 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -44,11 +44,13 @@ namespace Mvc.Server { .AllowRefreshTokenFlow() // During development, you can disable the HTTPS requirement. - .DisableHttpsRequirement(); + .DisableHttpsRequirement() + + // Register a new ephemeral key, that is discarded when the application + // shuts down. Tokens signed using this key are automatically invalidated. + // This method should only be used during development. + .AddEphemeralSigningKey(); - // Note: if you don't explicitly register a signing key, one is automatically generated and - // persisted on the disk. If the key cannot be persisted, an exception is thrown. - // // On production, using a X.509 certificate stored in the machine store is recommended. // You can generate a self-signed certificate using Pluralsight's self-cert utility: // https://s3.amazonaws.com/pluralsight-free/keith-brown/samples/SelfCert.zip diff --git a/src/OpenIddict.Core/OpenIddictBuilder.cs b/src/OpenIddict.Core/OpenIddictBuilder.cs index fe962912..f21bd78f 100644 --- a/src/OpenIddict.Core/OpenIddictBuilder.cs +++ b/src/OpenIddict.Core/OpenIddictBuilder.cs @@ -10,11 +10,13 @@ using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Linq; using System.Reflection; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using AspNet.Security.OpenIdConnect.Extensions; using JetBrains.Annotations; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; using OpenIddict; namespace Microsoft.AspNetCore.Builder { @@ -276,6 +278,59 @@ namespace Microsoft.AspNetCore.Builder { }); } + /// + /// Registers a new ephemeral key used to sign the tokens issued by OpenIddict: the key + /// is discarded when the application shuts down and tokens signed using this key are + /// automatically invalidated. This method should only be used during development: + /// on production, using a X.509 certificate stored in the machine store is recommended. + /// + /// The . + public virtual OpenIddictBuilder AddEphemeralSigningKey() { + // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, + // where RSACryptoServiceProvider is still the default implementation and + // where custom implementations can be registered via CryptoConfig. + // To ensure the key size is always acceptable, replace it if necessary. + var algorithm = RSA.Create(); + + if (algorithm.KeySize < 2048) { + algorithm.KeySize = 2048; + } + +#if NET451 + // Note: RSACng cannot be used as it's not available on Mono. + if (algorithm.KeySize < 2048 && algorithm is RSACryptoServiceProvider) { + algorithm.Dispose(); + algorithm = new RSACryptoServiceProvider(2048); + } +#endif + + if (algorithm.KeySize < 2048) { + throw new InvalidOperationException("The ephemeral key generation failed."); + } + + // Note: the RSA instance cannot be flowed as-is due to a bug in IdentityModel that disposes + // the underlying algorithm when it can be cast to RSACryptoServiceProvider. To work around + // this bug, the RSA public/private parameters are manually exported and re-imported when needed. + SecurityKey key; +#if NET451 + if (algorithm is RSACryptoServiceProvider) { + var parameters = algorithm.ExportParameters(includePrivateParameters: true); + key = new RsaSecurityKey(parameters); + + // Dispose the algorithm instance. + algorithm.Dispose(); + } + + else { +#endif + key = new RsaSecurityKey(algorithm); +#if NET451 + } +#endif + + return Configure(options => options.SigningCredentials.AddKey(key)); + } + /// /// Registers a used to sign the tokens issued by OpenIddict. /// diff --git a/src/OpenIddict.Core/OpenIddictExtensions.cs b/src/OpenIddict.Core/OpenIddictExtensions.cs index 8581232b..0a84da88 100644 --- a/src/OpenIddict.Core/OpenIddictExtensions.cs +++ b/src/OpenIddict.Core/OpenIddictExtensions.cs @@ -93,6 +93,12 @@ namespace Microsoft.AspNetCore.Builder { options.Cache = app.ApplicationServices.GetRequiredService(); } + if (options.SigningCredentials.Count == 0) { + throw new InvalidOperationException("At least one signing key must be registered. Consider registering a X.509 " + + "certificate using 'services.AddOpenIddict().AddSigningCertificate()' or call " + + "'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key."); + } + // Ensure at least one flow has been enabled. if (options.GrantTypes.Count == 0) { throw new InvalidOperationException("At least one OAuth2/OpenID Connect flow must be enabled.");