/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Client;
namespace Microsoft.Extensions.DependencyInjection;
///
/// Exposes the necessary methods required to configure the OpenIddict client services.
///
public class OpenIddictClientBuilder
{
///
/// Initializes a new instance of .
///
/// The services collection.
public OpenIddictClientBuilder(IServiceCollection services!!)
=> Services = services;
///
/// Gets the services collection.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
///
/// Registers an event handler using the specified configuration delegate.
///
/// The event context type.
/// The configuration delegate.
/// The .
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictClientBuilder AddEventHandler(
Action> configuration!!)
where TContext : OpenIddictClientEvents.BaseContext
{
// Note: handlers registered using this API are assumed to be custom handlers by default.
var builder = OpenIddictClientHandlerDescriptor.CreateBuilder()
.SetType(OpenIddictClientHandlerType.Custom);
configuration(builder);
return AddEventHandler(builder.Build());
}
///
/// Registers an event handler using the specified descriptor.
///
/// The handler descriptor.
/// The .
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictClientBuilder AddEventHandler(OpenIddictClientHandlerDescriptor descriptor!!)
{
// Register the handler in the services collection.
Services.Add(descriptor.ServiceDescriptor);
return Configure(options => options.Handlers.Add(descriptor));
}
///
/// Removes the event handler that matches the specified descriptor.
///
/// The descriptor corresponding to the handler to remove.
/// The .
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictClientBuilder RemoveEventHandler(OpenIddictClientHandlerDescriptor descriptor!!)
{
Services.RemoveAll(descriptor.ServiceDescriptor.ServiceType);
Services.PostConfigure(options =>
{
for (var index = options.Handlers.Count - 1; index >= 0; index--)
{
if (options.Handlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType)
{
options.Handlers.RemoveAt(index);
}
}
});
return this;
}
///
/// Amends the default OpenIddict client configuration.
///
/// The delegate used to configure the OpenIddict options.
/// This extension can be safely called multiple times.
/// The .
public OpenIddictClientBuilder Configure(Action configuration!!)
{
Services.Configure(configuration);
return this;
}
///
/// Registers encryption credentials.
///
/// The encrypting credentials.
/// The .
public OpenIddictClientBuilder AddEncryptionCredentials(EncryptingCredentials credentials!!)
=> Configure(options => options.EncryptionCredentials.Add(credentials));
///
/// Registers an encryption key.
///
/// The security key.
/// The .
public OpenIddictClientBuilder AddEncryptionKey(SecurityKey key!!)
{
// If the encryption key is an asymmetric security key, ensure it has a private key.
if (key is AsymmetricSecurityKey asymmetricSecurityKey &&
asymmetricSecurityKey.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0055));
}
if (key.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW))
{
if (key.KeySize != 256)
{
throw new InvalidOperationException(SR.FormatID0283(256, key.KeySize));
}
return AddEncryptionCredentials(new EncryptingCredentials(key,
SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512));
}
if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaOAEP))
{
return AddEncryptionCredentials(new EncryptingCredentials(key,
SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512));
}
throw new InvalidOperationException(SR.GetResourceString(SR.ID0056));
}
///
/// Registers (and generates if necessary) a user-specific development encryption certificate.
///
/// The .
public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate()
=> AddDevelopmentEncryptionCertificate(new X500DistinguishedName("CN=OpenIddict Client Encryption Certificate"));
///
/// Registers (and generates if necessary) a user-specific development encryption certificate.
///
/// The subject name associated with the certificate.
/// The .
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the client options.")]
public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject!!)
{
using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
// Try to retrieve the existing development certificates from the specified store.
// If no valid existing certificate was found, create a new encryption certificate.
var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false)
.OfType()
.ToList();
if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now))
{
#if SUPPORTS_CERTIFICATE_GENERATION
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));
var 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 Client 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;
}
certificates.Insert(0, certificate = new X509Certificate2(data, string.Empty, flags));
}
finally
{
Array.Clear(data, 0, data.Length);
}
store.Add(certificate);
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264));
#endif
}
return Configure(options => options.EncryptionCredentials.AddRange(
from certificate in certificates
let key = new X509SecurityKey(certificate)
select new EncryptingCredentials(key, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512)));
}
///
/// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically
/// discarded when the application shuts down and payloads 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.
///
/// The .
public OpenIddictClientBuilder AddEphemeralEncryptionKey()
=> AddEphemeralEncryptionKey(SecurityAlgorithms.RsaOAEP);
///
/// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically
/// discarded when the application shuts down and payloads 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.
///
/// The algorithm associated with the encryption key.
/// The .
public OpenIddictClientBuilder AddEphemeralEncryptionKey(string algorithm)
{
if (string.IsNullOrEmpty(algorithm))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0057), nameof(algorithm));
}
return algorithm switch
{
SecurityAlgorithms.Aes256KW
=> AddEncryptionCredentials(new EncryptingCredentials(CreateSymmetricSecurityKey(256),
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)),
SecurityAlgorithms.RsaOAEP or
SecurityAlgorithms.RsaOaepKeyWrap
=> AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048),
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)),
};
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);
}
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The generated RSA key is attached to the client options.")]
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(SR.GetResourceString(SR.ID0059));
}
return new RsaSecurityKey(algorithm);
#endif
}
}
///
/// Registers an encryption certificate.
///
/// The encryption certificate.
/// The .
public OpenIddictClientBuilder AddEncryptionCertificate(X509Certificate2 certificate!!)
{
// If the certificate is a X.509v3 certificate that specifies at least one
// key usage, ensure that the certificate key can be used for key encryption.
if (certificate.Version >= 3)
{
var extensions = certificate.Extensions.OfType().ToList();
if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.KeyEncipherment)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0060));
}
}
if (!certificate.HasPrivateKey)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0061));
}
return AddEncryptionKey(new X509SecurityKey(certificate));
}
///
/// Registers an encryption certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
/// The password used to open the certificate.
/// The .
public OpenIddictClientBuilder AddEncryptionCertificate(Assembly assembly, string resource, string? password)
#if SUPPORTS_EPHEMERAL_KEY_SETS
// Note: ephemeral key sets are currently not supported on macOS.
=> AddEncryptionCertificate(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
X509KeyStorageFlags.MachineKeySet :
X509KeyStorageFlags.EphemeralKeySet);
#else
=> AddEncryptionCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet);
#endif
///
/// Registers an encryption certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
/// The password used to open the certificate.
/// An enumeration of flags indicating how and where to store the private key of the certificate.
/// The .
public OpenIddictClientBuilder AddEncryptionCertificate(
Assembly assembly!!, string resource,
string? password, X509KeyStorageFlags flags)
{
if (string.IsNullOrEmpty(resource))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource));
}
using var stream = assembly.GetManifestResourceStream(resource);
if (stream is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0064));
}
return AddEncryptionCertificate(stream, password, flags);
}
///
/// Registers an encryption certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
/// The .
public OpenIddictClientBuilder AddEncryptionCertificate(Stream stream, string? password)
#if SUPPORTS_EPHEMERAL_KEY_SETS
// Note: ephemeral key sets are currently not supported on macOS.
=> AddEncryptionCertificate(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
X509KeyStorageFlags.MachineKeySet :
X509KeyStorageFlags.EphemeralKeySet);
#else
=> AddEncryptionCertificate(stream, password, X509KeyStorageFlags.MachineKeySet);
#endif
///
/// Registers an encryption certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
///
/// An enumeration of flags indicating how and where
/// to store the private key of the certificate.
///
/// The .
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the client options.")]
public OpenIddictClientBuilder AddEncryptionCertificate(Stream stream!!, string? password, X509KeyStorageFlags flags)
{
using var buffer = new MemoryStream();
stream.CopyTo(buffer);
return AddEncryptionCertificate(new X509Certificate2(buffer.ToArray(), password, flags));
}
///
/// Registers an encryption certificate retrieved from the X.509 user or machine store.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The .
public OpenIddictClientBuilder AddEncryptionCertificate(string thumbprint)
{
if (string.IsNullOrEmpty(thumbprint))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint));
}
var certificate = GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint);
if (certificate is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0066));
}
return AddEncryptionCertificate(certificate);
static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint)
{
using var store = new X509Store(StoreName.My, location);
store.Open(OpenFlags.ReadOnly);
return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false)
.OfType()
.SingleOrDefault();
}
}
///
/// Registers an encryption certificate retrieved from the specified X.509 store.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The name of the X.509 store.
/// The location of the X.509 store.
/// The .
public OpenIddictClientBuilder AddEncryptionCertificate(string thumbprint, StoreName name, StoreLocation location)
{
if (string.IsNullOrEmpty(thumbprint))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint));
}
using var store = new X509Store(name, location);
store.Open(OpenFlags.ReadOnly);
var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false)
.OfType()
.SingleOrDefault();
if (certificate is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0066));
}
return AddEncryptionCertificate(certificate);
}
///
/// Registers signing credentials.
///
/// The signing credentials.
/// The .
public OpenIddictClientBuilder AddSigningCredentials(SigningCredentials credentials!!)
=> Configure(options => options.SigningCredentials.Add(credentials));
///
/// Registers a signing key.
///
/// The security key.
/// The .
public OpenIddictClientBuilder AddSigningKey(SecurityKey key!!)
{
// If the signing key is an asymmetric security key, ensure it has a private key.
if (key is AsymmetricSecurityKey asymmetricSecurityKey &&
asymmetricSecurityKey.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0067));
}
if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256))
{
return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.RsaSha256));
}
if (key.IsSupportedAlgorithm(SecurityAlgorithms.HmacSha256))
{
return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
}
#if SUPPORTS_ECDSA
// Note: ECDSA algorithms are bound to specific curves and must be treated separately.
if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256))
{
return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256));
}
if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384))
{
return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha384));
}
if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
{
return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha512));
}
#else
if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) ||
key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) ||
key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069));
}
#endif
throw new InvalidOperationException(SR.GetResourceString(SR.ID0068));
}
///
/// Registers (and generates if necessary) a user-specific development signing certificate.
///
/// The .
public OpenIddictClientBuilder AddDevelopmentSigningCertificate()
=> AddDevelopmentSigningCertificate(new X500DistinguishedName("CN=OpenIddict Client Signing Certificate"));
///
/// Registers (and generates if necessary) a user-specific development signing certificate.
///
/// The subject name associated with the certificate.
/// The .
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the client options.")]
public OpenIddictClientBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject!!)
{
using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
// Try to retrieve the existing development certificates from the specified store.
// If no valid existing certificate was found, create a new signing certificate.
var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false)
.OfType()
.ToList();
if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now))
{
#if SUPPORTS_CERTIFICATE_GENERATION
using var algorithm = RSA.Create(keySizeInBits: 2048);
var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true));
var 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 Client Development Signing 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;
}
certificates.Insert(0, certificate = new X509Certificate2(data, string.Empty, flags));
}
finally
{
Array.Clear(data, 0, data.Length);
}
store.Add(certificate);
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264));
#endif
}
return Configure(options => options.SigningCredentials.AddRange(
from certificate in certificates
let key = new X509SecurityKey(certificate)
select new SigningCredentials(key, SecurityAlgorithms.RsaSha256)));
}
///
/// Registers a new ephemeral signing key. Ephemeral signing keys are automatically
/// discarded when the application shuts down and payloads 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 OpenIddictClientBuilder AddEphemeralSigningKey()
=> AddEphemeralSigningKey(SecurityAlgorithms.RsaSha256);
///
/// Registers a new ephemeral signing key. Ephemeral signing keys are automatically
/// discarded when the application shuts down and payloads 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 algorithm associated with the signing key.
/// The .
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the client options.")]
public OpenIddictClientBuilder AddEphemeralSigningKey(string algorithm)
{
if (string.IsNullOrEmpty(algorithm))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0057), nameof(algorithm));
}
return algorithm switch
{
SecurityAlgorithms.RsaSha256 or
SecurityAlgorithms.RsaSha384 or
SecurityAlgorithms.RsaSha512 or
SecurityAlgorithms.RsaSha256Signature or
SecurityAlgorithms.RsaSha384Signature or
SecurityAlgorithms.RsaSha512Signature or
SecurityAlgorithms.RsaSsaPssSha256 or
SecurityAlgorithms.RsaSsaPssSha384 or
SecurityAlgorithms.RsaSsaPssSha512 or
SecurityAlgorithms.RsaSsaPssSha256Signature or
SecurityAlgorithms.RsaSsaPssSha384Signature or
SecurityAlgorithms.RsaSsaPssSha512Signature
=> AddSigningCredentials(new SigningCredentials(CreateRsaSecurityKey(2048), algorithm)),
#if SUPPORTS_ECDSA
SecurityAlgorithms.EcdsaSha256 or
SecurityAlgorithms.EcdsaSha256Signature
=> AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey(
ECDsa.Create(ECCurve.NamedCurves.nistP256)), algorithm)),
SecurityAlgorithms.EcdsaSha384 or
SecurityAlgorithms.EcdsaSha384Signature
=> AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey(
ECDsa.Create(ECCurve.NamedCurves.nistP384)), algorithm)),
SecurityAlgorithms.EcdsaSha512 or
SecurityAlgorithms.EcdsaSha512Signature
=> AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey(
ECDsa.Create(ECCurve.NamedCurves.nistP521)), algorithm)),
#else
SecurityAlgorithms.EcdsaSha256 or
SecurityAlgorithms.EcdsaSha384 or
SecurityAlgorithms.EcdsaSha512 or
SecurityAlgorithms.EcdsaSha256Signature or
SecurityAlgorithms.EcdsaSha384Signature or
SecurityAlgorithms.EcdsaSha512Signature
=> throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)),
};
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The generated RSA key is attached to the client options.")]
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(SR.GetResourceString(SR.ID0059));
}
return new RsaSecurityKey(algorithm);
#endif
}
}
///
/// Registers a signing certificate.
///
/// The signing certificate.
/// The .
public OpenIddictClientBuilder AddSigningCertificate(X509Certificate2 certificate!!)
{
// If the certificate is a X.509v3 certificate that specifies at least
// one key usage, ensure that the certificate key can be used for signing.
if (certificate.Version >= 3)
{
var extensions = certificate.Extensions.OfType().ToList();
if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.DigitalSignature)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0070));
}
}
if (!certificate.HasPrivateKey)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0061));
}
return AddSigningKey(new X509SecurityKey(certificate));
}
///
/// Registers a signing certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
/// The password used to open the certificate.
/// The .
public OpenIddictClientBuilder AddSigningCertificate(Assembly assembly, string resource, string? password)
#if SUPPORTS_EPHEMERAL_KEY_SETS
// Note: ephemeral key sets are currently not supported on macOS.
=> AddSigningCertificate(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
X509KeyStorageFlags.MachineKeySet :
X509KeyStorageFlags.EphemeralKeySet);
#else
=> AddSigningCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet);
#endif
///
/// Registers a signing certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
/// The password used to open the certificate.
/// An enumeration of flags indicating how and where to store the private key of the certificate.
/// The .
public OpenIddictClientBuilder AddSigningCertificate(
Assembly assembly!!, string resource,
string? password, X509KeyStorageFlags flags)
{
if (string.IsNullOrEmpty(resource))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource));
}
using var stream = assembly.GetManifestResourceStream(resource);
if (stream is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0064));
}
return AddSigningCertificate(stream, password, flags);
}
///
/// Registers a signing certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
/// The .
public OpenIddictClientBuilder AddSigningCertificate(Stream stream, string? password)
#if SUPPORTS_EPHEMERAL_KEY_SETS
// Note: ephemeral key sets are currently not supported on macOS.
=> AddSigningCertificate(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
X509KeyStorageFlags.MachineKeySet :
X509KeyStorageFlags.EphemeralKeySet);
#else
=> AddSigningCertificate(stream, password, X509KeyStorageFlags.MachineKeySet);
#endif
///
/// Registers a signing certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
///
/// An enumeration of flags indicating how and where
/// to store the private key of the certificate.
///
/// The .
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the client options.")]
public OpenIddictClientBuilder AddSigningCertificate(Stream stream!!, string? password, X509KeyStorageFlags flags)
{
using var buffer = new MemoryStream();
stream.CopyTo(buffer);
return AddSigningCertificate(new X509Certificate2(buffer.ToArray(), password, flags));
}
///
/// Registers a signing certificate retrieved from the X.509 user or machine store.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The .
public OpenIddictClientBuilder AddSigningCertificate(string thumbprint)
{
if (string.IsNullOrEmpty(thumbprint))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint));
}
var certificate = GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint);
if (certificate is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0066));
}
return AddSigningCertificate(certificate);
static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint)
{
using var store = new X509Store(StoreName.My, location);
store.Open(OpenFlags.ReadOnly);
return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false)
.OfType()
.SingleOrDefault();
}
}
///
/// Registers a signing certificate retrieved from the specified X.509 store.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The name of the X.509 store.
/// The location of the X.509 store.
/// The .
public OpenIddictClientBuilder AddSigningCertificate(string thumbprint, StoreName name, StoreLocation location)
{
if (string.IsNullOrEmpty(thumbprint))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint));
}
using var store = new X509Store(name, location);
store.Open(OpenFlags.ReadOnly);
var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false)
.OfType()
.SingleOrDefault();
if (certificate is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0066));
}
return AddSigningCertificate(certificate);
}
///
/// Adds a new client registration.
///
/// The client registration.
/// The .
public OpenIddictClientBuilder AddRegistration(OpenIddictClientRegistration registration!!)
=> Configure(options => options.Registrations.Add(registration));
///
/// Sets the relative or absolute URLs associated to the redirection endpoint.
/// If an empty array is specified, the endpoint will be considered disabled.
/// Note: only the first address will be returned as part of the discovery document.
///
/// The addresses associated to the endpoint.
/// The .
public OpenIddictClientBuilder SetRedirectionEndpointUris(params string[] addresses!!)
=> SetRedirectionEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray());
///
/// Sets the relative or absolute URLs associated to the redirection endpoint.
/// If an empty array is specified, the endpoint will be considered disabled.
/// Note: only the first address will be returned as part of the discovery document.
///
/// The addresses associated to the endpoint.
/// The .
public OpenIddictClientBuilder SetRedirectionEndpointUris(params Uri[] addresses!!)
{
if (addresses.Any(address => !address.IsWellFormedOriginalString()))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses));
}
if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase)))
{
throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses));
}
return Configure(options =>
{
options.RedirectionEndpointUris.Clear();
options.RedirectionEndpointUris.AddRange(addresses);
});
}
///
/// Sets the state token lifetime, after which authorization callbacks
/// using an expired state token will be automatically rejected by OpenIddict.
/// Using long-lived state tokens or tokens that never expire is not recommended.
/// While discouraged, null can be specified to issue tokens that never expire.
///
/// The access token lifetime.
/// The .
public OpenIddictClientBuilder SetStateTokenLifetime(TimeSpan? lifetime)
=> Configure(options => options.StateTokenLifetime = lifetime);
///
/// Determines whether the specified object is equal to the current object.
///
/// The object to compare with the current object.
/// true if the specified object is equal to the current object; otherwise, false.
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
///
/// Serves as the default hash function.
///
/// A hash code for the current object.
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
///
/// Returns a string that represents the current object.
///
/// A string that represents the current object.
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}