diff --git a/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs b/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
index 0bac4f18..346105b4 100644
--- a/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
+++ b/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
@@ -49,11 +49,16 @@ namespace OpenIddict.Client.WebIntegration.Generators
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.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Client;
using OpenIddict.Client.WebIntegration;
+using OpenIddict.Extensions;
using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
namespace Microsoft.Extensions.DependencyInjection;
@@ -272,6 +277,79 @@ public sealed partial class OpenIddictClientWebIntegrationBuilder
return Configure(options => options.{{ setting.property_name }}.UnionWith({{ setting.parameter_name }}));
}
+ {{~ else if setting.clr_type == 'ECDsaSecurityKey' ~}}
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ /// {{ setting.description | string.capitalize }}.
+ /// The instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(ECDsaSecurityKey {{ setting.parameter_name }})
+ {
+ if ({{ setting.parameter_name }} is null)
+ {
+ throw new ArgumentNullException(nameof({{ setting.parameter_name }}));
+ }
+
+ if ({{ setting.parameter_name }}.PrivateKeyStatus is PrivateKeyStatus.DoesNotExist)
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0055), nameof({{ setting.parameter_name }}));
+ }
+
+ return Configure(options => options.{{ setting.property_name }} = {{ setting.parameter_name }});
+ }
+
+#if SUPPORTS_PEM_ENCODED_KEY_IMPORT
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ ///
+ /// The PEM-encoded Elliptic Curve Digital Signature Algorithm (ECDSA) signing key.
+ ///
+ /// The instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(string key)
+ => Set{{ setting.property_name }}(key.AsMemory());
+
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ ///
+ /// The PEM-encoded Elliptic Curve Digital Signature Algorithm (ECDSA) signing key.
+ ///
+ /// The instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(ReadOnlyMemory key)
+ => Set{{ setting.property_name }}(key.Span);
+
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ ///
+ /// The PEM-encoded Elliptic Curve Digital Signature Algorithm (ECDSA) signing key.
+ ///
+ /// The instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(ReadOnlySpan key)
+ {
+ if (key.IsEmpty)
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0346), nameof(key));
+ }
+
+ var algorithm = OpenIddictHelpers.CreateEcdsaKey();
+
+ try
+ {
+ algorithm.ImportFromPem(key);
+ }
+
+ catch
+ {
+ algorithm.Dispose();
+
+ throw;
+ }
+
+ return Set{{ setting.property_name }}(new ECDsaSecurityKey(algorithm));
+ }
+#endif
{{~ else if setting.clr_type == 'Uri' ~}}
///
/// Configures {{ setting.description }}.
@@ -307,6 +385,161 @@ public sealed partial class OpenIddictClientWebIntegrationBuilder
return Set{{ setting.property_name }}(new Uri({{ setting.parameter_name }}, UriKind.RelativeOrAbsolute));
}
+ {{~ else if setting.clr_type == 'X509Certificate2' ~}}
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ /// {{ setting.description | string.capitalize }}.
+ /// The instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(X509Certificate2 {{ setting.parameter_name }})
+ {
+ if ({{ setting.parameter_name }} is null)
+ {
+ throw new ArgumentNullException(nameof({{ setting.parameter_name }}));
+ }
+
+ if (!{{ setting.parameter_name }}.HasPrivateKey)
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0061), nameof({{ setting.parameter_name }}));
+ }
+
+ return Configure(options => options.{{ setting.property_name }} = {{ setting.parameter_name }});
+ }
+
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ /// The assembly containing the certificate.
+ /// The name of the embedded resource.
+ /// The password used to open the certificate.
+ /// The instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(Assembly assembly, string resource, string? password)
+#if SUPPORTS_EPHEMERAL_KEY_SETS
+ // Note: ephemeral key sets are currently not supported on macOS.
+ => Set{{ setting.property_name }}(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
+ X509KeyStorageFlags.MachineKeySet :
+ X509KeyStorageFlags.EphemeralKeySet);
+#else
+ => Set{{ setting.property_name }}(assembly, resource, password, X509KeyStorageFlags.MachineKeySet);
+#endif
+
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ /// 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 instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(
+ Assembly assembly, string resource,
+ string? password, X509KeyStorageFlags flags)
+ {
+ if (assembly is null)
+ {
+ throw new ArgumentNullException(nameof(assembly));
+ }
+
+ if (string.IsNullOrEmpty(resource))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource));
+ }
+
+ using var stream = assembly.GetManifestResourceStream(resource) ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0064));
+
+ return Set{{ setting.property_name }}(stream, password, flags);
+ }
+
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ /// The stream containing the certificate.
+ /// The password used to open the certificate.
+ /// The instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(Stream stream, string? password)
+#if SUPPORTS_EPHEMERAL_KEY_SETS
+ // Note: ephemeral key sets are currently not supported on macOS.
+ => Set{{ setting.property_name }}(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
+ X509KeyStorageFlags.MachineKeySet :
+ X509KeyStorageFlags.EphemeralKeySet);
+#else
+ => Set{{ setting.property_name }}(stream, password, X509KeyStorageFlags.MachineKeySet);
+#endif
+
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ /// 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 instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(Stream stream, string? password, X509KeyStorageFlags flags)
+ {
+ if (stream is null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ using var buffer = new MemoryStream();
+ stream.CopyTo(buffer);
+
+ return Set{{ setting.property_name }}(new X509Certificate2(buffer.ToArray(), password, flags));
+ }
+
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ /// The thumbprint of the certificate used to identify it in the X.509 store.
+ /// The instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(string thumbprint)
+ {
+ if (string.IsNullOrEmpty(thumbprint))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint));
+ }
+
+ return Set{{ setting.property_name }}(
+ GetCertificate(StoreLocation.CurrentUser, thumbprint) ??
+ GetCertificate(StoreLocation.LocalMachine, thumbprint) ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)));
+
+ 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();
+ }
+ }
+
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ /// 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 instance.
+ public {{ provider.name }} Set{{ setting.property_name }}(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);
+
+ return Set{{ setting.property_name }}(
+ store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false)
+ .OfType()
+ .SingleOrDefault() ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)));
+ }
{{~ else ~}}
///
/// Configures {{ setting.description }}.
@@ -375,6 +608,7 @@ public sealed partial class OpenIddictClientWebIntegrationBuilder
is "PS256" or "PS384" or "PS512" or
"RS256" or "RS384" or "RS512" => "RsaSecurityKey",
+ "Certificate" => "X509Certificate2",
"String" => "string",
"StringHashSet" => "HashSet",
"Uri" => "Uri",
@@ -832,6 +1066,7 @@ public static partial class OpenIddictClientWebIntegrationHelpers
{
var template = Template.Parse(@"#nullable enable
+using System.Security.Cryptography.X509Certificates;
using Microsoft.IdentityModel.Tokens;
namespace OpenIddict.Client.WebIntegration;
@@ -915,6 +1150,7 @@ public sealed partial class OpenIddictClientWebIntegrationOptions
is "PS256" or "PS384" or "PS512" or
"RS256" or "RS384" or "RS512" => "RsaSecurityKey",
+ "Certificate" => "X509Certificate2",
"String" => "string",
"StringHashSet" => "HashSet",
"Uri" => "Uri",
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs
index a134e619..cea98c36 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs
@@ -7,12 +7,6 @@
using System.ComponentModel;
using OpenIddict.Client.WebIntegration;
-#if SUPPORTS_PEM_ENCODED_KEY_IMPORT
-using System.Security.Cryptography;
-using Microsoft.IdentityModel.Tokens;
-using OpenIddict.Extensions;
-#endif
-
namespace Microsoft.Extensions.DependencyInjection;
///
@@ -53,69 +47,6 @@ public sealed partial class OpenIddictClientWebIntegrationBuilder
// Note: provider registration methods are automatically generated by the source generator.
- ///
- /// Exposes the necessary methods required to configure the Apple integration.
- ///
- public partial class Apple
- {
-#if SUPPORTS_PEM_ENCODED_KEY_IMPORT
- ///
- /// Configures the Elliptic Curve Digital Signature Algorithm
- /// (ECDSA) signing key associated with the developer account.
- ///
- ///
- /// The PEM-encoded Elliptic Curve Digital Signature Algorithm
- /// (ECDSA) signing key associated with the developer account.
- ///
- /// The instance.
- public Apple SetSigningKey(string key) => SetSigningKey(key.AsMemory());
-
- ///
- /// Configures the Elliptic Curve Digital Signature Algorithm
- /// (ECDSA) signing key associated with the developer account.
- ///
- ///
- /// The PEM-encoded Elliptic Curve Digital Signature Algorithm
- /// (ECDSA) signing key associated with the developer account.
- ///
- /// The instance.
- public Apple SetSigningKey(ReadOnlyMemory key) => SetSigningKey(key.Span);
-
- ///
- /// Configures the Elliptic Curve Digital Signature Algorithm
- /// (ECDSA) signing key associated with the developer account.
- ///
- ///
- /// The PEM-encoded Elliptic Curve Digital Signature Algorithm
- /// (ECDSA) signing key associated with the developer account.
- ///
- /// The instance.
- public Apple SetSigningKey(ReadOnlySpan key)
- {
- if (key.IsEmpty)
- {
- throw new ArgumentException(SR.GetResourceString(SR.ID0346), nameof(key));
- }
-
- var algorithm = OpenIddictHelpers.CreateEcdsaKey();
-
- try
- {
- algorithm.ImportFromPem(key);
- }
-
- catch
- {
- algorithm.Dispose();
-
- throw;
- }
-
- return SetSigningKey(new ECDsaSecurityKey(algorithm));
- }
-#endif
- }
-
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xsd b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xsd
index 14c19ac8..3e06f9fb 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xsd
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xsd
@@ -376,6 +376,7 @@
+