|
|
@ -9,8 +9,6 @@ using System.ComponentModel; |
|
|
using System.IO; |
|
|
using System.IO; |
|
|
using System.Linq; |
|
|
using System.Linq; |
|
|
using System.Reflection; |
|
|
using System.Reflection; |
|
|
using System.Runtime.InteropServices; |
|
|
|
|
|
using System.Security.Cryptography; |
|
|
|
|
|
using System.Security.Cryptography.X509Certificates; |
|
|
using System.Security.Cryptography.X509Certificates; |
|
|
using System.Text; |
|
|
using System.Text; |
|
|
using JetBrains.Annotations; |
|
|
using JetBrains.Annotations; |
|
|
@ -189,179 +187,6 @@ namespace Microsoft.Extensions.DependencyInjection |
|
|
key.CryptoProviderFactory.IsSupportedAlgorithm(algorithm, key); |
|
|
key.CryptoProviderFactory.IsSupportedAlgorithm(algorithm, key); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Registers (and generates if necessary) a user-specific development
|
|
|
|
|
|
/// certificate used to decrypt the tokens issued by OpenIddict.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|
|
|
|
|
public OpenIddictValidationBuilder AddDevelopmentEncryptionCertificate() |
|
|
|
|
|
=> AddDevelopmentEncryptionCertificate(new X500DistinguishedName("CN=OpenIddict Validation Encryption Certificate")); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Registers (and generates if necessary) a user-specific development
|
|
|
|
|
|
/// certificate used to decrypt the tokens issued by OpenIddict.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="subject">The subject name associated with the certificate.</param>
|
|
|
|
|
|
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|
|
|
|
|
public OpenIddictValidationBuilder AddDevelopmentEncryptionCertificate([NotNull] X500DistinguishedName subject) |
|
|
|
|
|
{ |
|
|
|
|
|
if (subject == null) |
|
|
|
|
|
{ |
|
|
|
|
|
throw new ArgumentNullException(nameof(subject)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); |
|
|
|
|
|
store.Open(OpenFlags.ReadWrite); |
|
|
|
|
|
|
|
|
|
|
|
// Try to retrieve the development certificate from the specified store.
|
|
|
|
|
|
// If a certificate was found but is not yet or no longer valid, remove it
|
|
|
|
|
|
// from the store before creating and persisting a new encryption certificate.
|
|
|
|
|
|
var certificate = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) |
|
|
|
|
|
.OfType<X509Certificate2>() |
|
|
|
|
|
.SingleOrDefault(); |
|
|
|
|
|
|
|
|
|
|
|
if (certificate != null && (certificate.NotBefore > DateTime.Now || certificate.NotAfter < DateTime.Now)) |
|
|
|
|
|
{ |
|
|
|
|
|
store.Remove(certificate); |
|
|
|
|
|
certificate = null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#if SUPPORTS_CERTIFICATE_GENERATION
|
|
|
|
|
|
// If no appropriate certificate can be found, generate and persist a new certificate in the specified store.
|
|
|
|
|
|
if (certificate == null) |
|
|
|
|
|
{ |
|
|
|
|
|
using var algorithm = RSA.Create(keySizeInBits: 2048); |
|
|
|
|
|
|
|
|
|
|
|
var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); |
|
|
|
|
|
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); |
|
|
|
|
|
|
|
|
|
|
|
certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); |
|
|
|
|
|
|
|
|
|
|
|
// Note: setting the friendly name is not supported on Unix machines (including Linux and macOS).
|
|
|
|
|
|
// To ensure an exception is not thrown by the property setter, an OS runtime check is used here.
|
|
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|
|
|
|
|
{ |
|
|
|
|
|
certificate.FriendlyName = "OpenIddict Validation Development Encryption Certificate"; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate
|
|
|
|
|
|
// as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key.
|
|
|
|
|
|
// To work around this issue, the certificate payload is manually exported and imported back
|
|
|
|
|
|
// into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag.
|
|
|
|
|
|
var data = certificate.Export(X509ContentType.Pfx, string.Empty); |
|
|
|
|
|
|
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
var flags = X509KeyStorageFlags.PersistKeySet; |
|
|
|
|
|
|
|
|
|
|
|
// Note: macOS requires marking the certificate private key as exportable.
|
|
|
|
|
|
// If this flag is not set, a CryptographicException is thrown at runtime.
|
|
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|
|
|
|
|
{ |
|
|
|
|
|
flags |= X509KeyStorageFlags.Exportable; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
certificate = new X509Certificate2(data, string.Empty, flags); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
finally |
|
|
|
|
|
{ |
|
|
|
|
|
Array.Clear(data, 0, data.Length); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
store.Add(certificate); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return AddEncryptionCertificate(certificate); |
|
|
|
|
|
#else
|
|
|
|
|
|
throw new PlatformNotSupportedException("X.509 certificate generation is not supported on this platform."); |
|
|
|
|
|
#endif
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Registers a new ephemeral key used to decrypt the tokens issued by OpenIddict: the key
|
|
|
|
|
|
/// is discarded when the application shuts down and tokens encrypted 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.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|
|
|
|
|
public OpenIddictValidationBuilder AddEphemeralEncryptionKey() |
|
|
|
|
|
=> AddEphemeralEncryptionKey(SecurityAlgorithms.RsaOAEP); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Registers a new ephemeral key used to decrypt the tokens issued by OpenIddict: the key
|
|
|
|
|
|
/// is discarded when the application shuts down and tokens encrypted 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.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="algorithm">The algorithm associated with the encryption key.</param>
|
|
|
|
|
|
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|
|
|
|
|
public OpenIddictValidationBuilder AddEphemeralEncryptionKey([NotNull] string algorithm) |
|
|
|
|
|
{ |
|
|
|
|
|
if (string.IsNullOrEmpty(algorithm)) |
|
|
|
|
|
{ |
|
|
|
|
|
throw new ArgumentException("The algorithm cannot be null or empty.", nameof(algorithm)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
switch (algorithm) |
|
|
|
|
|
{ |
|
|
|
|
|
case SecurityAlgorithms.Aes256KW: |
|
|
|
|
|
return AddEncryptionCredentials(new EncryptingCredentials(CreateSymmetricSecurityKey(256), |
|
|
|
|
|
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)); |
|
|
|
|
|
|
|
|
|
|
|
case SecurityAlgorithms.RsaOAEP: |
|
|
|
|
|
case SecurityAlgorithms.RsaOaepKeyWrap: |
|
|
|
|
|
return AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), |
|
|
|
|
|
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)); |
|
|
|
|
|
|
|
|
|
|
|
default: throw new InvalidOperationException("The specified algorithm is not supported."); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static SymmetricSecurityKey CreateSymmetricSecurityKey(int size) |
|
|
|
|
|
{ |
|
|
|
|
|
var data = new byte[size / 8]; |
|
|
|
|
|
|
|
|
|
|
|
#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS
|
|
|
|
|
|
RandomNumberGenerator.Fill(data); |
|
|
|
|
|
#else
|
|
|
|
|
|
using var generator = RandomNumberGenerator.Create(); |
|
|
|
|
|
generator.GetBytes(data); |
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
return new SymmetricSecurityKey(data); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static RsaSecurityKey CreateRsaSecurityKey(int size) |
|
|
|
|
|
{ |
|
|
|
|
|
#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE
|
|
|
|
|
|
return new RsaSecurityKey(RSA.Create(size)); |
|
|
|
|
|
#else
|
|
|
|
|
|
// 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 < size) |
|
|
|
|
|
{ |
|
|
|
|
|
algorithm.KeySize = size; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) |
|
|
|
|
|
{ |
|
|
|
|
|
algorithm.Dispose(); |
|
|
|
|
|
algorithm = new RSACryptoServiceProvider(size); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (algorithm.KeySize < size) |
|
|
|
|
|
{ |
|
|
|
|
|
throw new InvalidOperationException("RSA key generation failed."); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return new RsaSecurityKey(algorithm); |
|
|
|
|
|
#endif
|
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Registers a <see cref="X509Certificate2"/> that is used to decrypt the tokens issued by OpenIddict.
|
|
|
/// Registers a <see cref="X509Certificate2"/> that is used to decrypt the tokens issued by OpenIddict.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
|