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 @@ +