Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a new client authentication method negotiation logic and introduce mTLS support in the client stack #2187

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,23 @@ public sealed partial class {{ provider.name }}
[EditorBrowsable(EditorBrowsableState.Never)]
public OpenIddictClientRegistration Registration { get; }

/// <summary>
/// Adds one or more client authentication methods to the list of client authentication methods that can be negotiated for this provider.
/// </summary>
/// <param name=""methods"">The client authentication methods.</param>
/// <remarks>Note: explicitly configuring the allowed client authentication methods is NOT recommended in most cases.</remarks>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/> instance.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public {{ provider.name }} AddClientAuthenticationMethods(params string[] methods)
{
if (methods is null)
{
throw new ArgumentNullException(nameof(methods));
}

return Set(registration => registration.ClientAuthenticationMethods.UnionWith(methods));
}

/// <summary>
/// Adds one or more code challenge methods to the list of code challenge methods that can be negotiated for this provider.
/// </summary>
Expand Down Expand Up @@ -791,17 +808,23 @@ public sealed partial class {{ provider.name }}
ClrType = (string) setting.Attribute("Type") switch
{
"Boolean" => "bool",
"EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value")
is "RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"EncryptionKey" => (string?) setting.Element("EncryptionAlgorithm")?.Attribute("Value") switch
{
"RS256" or "RS384" or "RS512" => "RsaSecurityKey",

"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey",
_ => "SecurityKey"
},

"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "PS256" or "PS384" or "PS512" or
"RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"SigningCertificate" => "X509Certificate2",

"SigningKey" => (string?) setting.Element("SigningAlgorithm")?.Attribute("Value") switch
{
"ES256" or "ES384" or "ES512" => "ECDsaSecurityKey",
"PS256" or "PS384" or "PS512" or "RS256" or "RS384" or "RS512" => "RsaSecurityKey",

_ => "SecurityKey"
},

"Certificate" => "X509Certificate2",
"String" => "string",
"StringHashSet" => "HashSet<string>",
"Uri" => "Uri",
Expand Down Expand Up @@ -1121,13 +1144,111 @@ public static partial void ConfigureProvider(OpenIddictClientRegistration regist

{{~ for setting in provider.settings ~}}
{{~ if setting.type == 'EncryptionKey' ~}}
registration.EncryptionCredentials.Add(new EncryptingCredentials(settings.{{ setting.property_name }}, ""{{ setting.encryption_algorithm }}"", SecurityAlgorithms.Aes256CbcHmacSha512));
if (settings.{{ setting.property_name }} is not null)
{
registration.EncryptionCredentials.Add(new EncryptingCredentials(settings.{{ setting.property_name }}, ""{{ setting.encryption_algorithm }}"", SecurityAlgorithms.Aes256CbcHmacSha512));
}
{{~ end ~}}
{{~ end ~}}

{{~ for setting in provider.settings ~}}
{{~ if setting.type == 'SigningCertificate' ~}}
if (settings.{{ setting.property_name }} is not null)
{
var key = new X509SecurityKey(settings.{{ setting.property_name }});
if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256))
{
registration.SigningCredentials.Add(new SigningCredentials(key, SecurityAlgorithms.RsaSha256));
}

else if (key.IsSupportedAlgorithm(SecurityAlgorithms.HmacSha256))
{
registration.SigningCredentials.Add(new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
}

#if SUPPORTS_ECDSA
// Note: ECDSA algorithms are bound to specific curves and must be treated separately.
else if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256))
{
registration.SigningCredentials.Add(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256));
}

else if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384))
{
registration.SigningCredentials.Add(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha384));
}

else if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
{
registration.SigningCredentials.Add(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha512));
}
#else
else if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) ||
key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) ||
key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069));
}
#endif
else
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0068));
}
}
{{~ end ~}}
{{~ if setting.type == 'SigningKey' ~}}
registration.SigningCredentials.Add(new SigningCredentials(settings.{{ setting.property_name }}, ""{{ setting.signing_algorithm }}""));
if (settings.{{ setting.property_name }} is not null)
{
// If the signing key is an asymmetric security key, ensure it has a private key.
if (settings.{{ setting.property_name }} is AsymmetricSecurityKey asymmetricSecurityKey &&
asymmetricSecurityKey.PrivateKeyStatus is PrivateKeyStatus.DoesNotExist)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0067));
}

{{~ if setting.signing_algorithm ~}}
registration.SigningCredentials.Add(new SigningCredentials(settings.{{ setting.property_name }}, ""{{ setting.signing_algorithm }}""));
{{~ else ~}}
if (settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256))
{
registration.SigningCredentials.Add(new SigningCredentials(settings.{{ setting.property_name }}, SecurityAlgorithms.RsaSha256));
}

else if (settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.HmacSha256))
{
registration.SigningCredentials.Add(new SigningCredentials(settings.{{ setting.property_name }}, SecurityAlgorithms.HmacSha256));
}

#if SUPPORTS_ECDSA
// Note: ECDSA algorithms are bound to specific curves and must be treated separately.
else if (settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256))
{
registration.SigningCredentials.Add(new SigningCredentials(settings.{{ setting.property_name }}, SecurityAlgorithms.EcdsaSha256));
}

else if (settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384))
{
registration.SigningCredentials.Add(new SigningCredentials(settings.{{ setting.property_name }}, SecurityAlgorithms.EcdsaSha384));
}

else if (settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
{
registration.SigningCredentials.Add(new SigningCredentials(settings.{{ setting.property_name }}, SecurityAlgorithms.EcdsaSha512));
}
#else
else if (settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) ||
settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) ||
settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069));
}
#endif
else
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0068));
}
{{~ end ~}}
}
{{~ end ~}}
{{~ end ~}}
}
Expand Down Expand Up @@ -1379,17 +1500,23 @@ public sealed class {{ provider.name }}
ClrType = (string) setting.Attribute("Type") switch
{
"Boolean" => "bool",
"EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value")
is "RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"EncryptionKey" => (string?) setting.Element("EncryptionAlgorithm")?.Attribute("Value") switch
{
"RS256" or "RS384" or "RS512" => "RsaSecurityKey",

"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey",
_ => "SecurityKey"
},

"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "PS256" or "PS384" or "PS512" or
"RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"SigningCertificate" => "X509Certificate2",

"SigningKey" => (string?) setting.Element("SigningAlgorithm")?.Attribute("Value") switch
{
"ES256" or "ES384" or "ES512" => "ECDsaSecurityKey",
"PS256" or "PS384" or "PS512" or "RS256" or "RS384" or "RS512" => "RsaSecurityKey",

_ => "SecurityKey"
},

"Certificate" => "X509Certificate2",
"String" => "string",
"StringHashSet" => "HashSet<string>",
"Uri" => "Uri",
Expand Down
10 changes: 10 additions & 0 deletions src/OpenIddict.Abstractions/OpenIddictConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ public static class ClientAuthenticationMethods
public const string ClientSecretPost = "client_secret_post";
public const string None = "none";
public const string PrivateKeyJwt = "private_key_jwt";
public const string SelfSignedTlsClientAuth = "self_signed_tls_client_auth";
public const string TlsClientAuth = "tls_client_auth";
}

public static class ClientTypes
Expand Down Expand Up @@ -290,6 +292,7 @@ public static class Metadata
public const string IntrospectionEndpointAuthSigningAlgValuesSupported = "introspection_endpoint_auth_signing_alg_values_supported";
public const string Issuer = "issuer";
public const string JwksUri = "jwks_uri";
public const string MtlsEndpointAliases = "mtls_endpoint_aliases";
public const string OpPolicyUri = "op_policy_uri";
public const string OpTosUri = "op_tos_uri";
public const string RequestObjectEncryptionAlgValuesSupported = "request_object_encryption_alg_values_supported";
Expand All @@ -306,6 +309,7 @@ public static class Metadata
public const string ScopesSupported = "scopes_supported";
public const string ServiceDocumentation = "service_documentation";
public const string SubjectTypesSupported = "subject_types_supported";
public const string TlsClientCertificateBoundAccessTokens = "tls_client_certificate_bound_access_tokens";
public const string TokenEndpoint = "token_endpoint";
public const string TokenEndpointAuthMethodsSupported = "token_endpoint_auth_methods_supported";
public const string TokenEndpointAuthSigningAlgValuesSupported = "token_endpoint_auth_signing_alg_values_supported";
Expand Down Expand Up @@ -531,6 +535,12 @@ public static class SubjectTypes
public const string Public = "public";
}

public static class TokenBindingMethods
{
public const string SelfSignedTlsClientCertificate = "self_signed_tls_client_certificate";
public const string TlsClientCertificate = "tls_client_certificate";
}

public static class TokenFormats
{
public const string Jwt = "urn:ietf:params:oauth:token-type:jwt";
Expand Down
6 changes: 6 additions & 0 deletions src/OpenIddict.Abstractions/OpenIddictResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1698,6 +1698,12 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<data name="ID0454" xml:space="preserve">
<value>The format of the specified certificate is not supported.</value>
</data>
<data name="ID0455" xml:space="preserve">
<value>Registration identifiers cannot contain U+001E or U+001F characters.</value>
</data>
<data name="ID0456" xml:space="preserve">
<value>The specified client authentication method/token binding methods combination is not valid.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>
Expand Down
31 changes: 31 additions & 0 deletions src/OpenIddict.Abstractions/Primitives/OpenIddictConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,31 @@ public sealed class OpenIddictConfiguration
/// </summary>
public Uri? JwksUri { get; set; }

/// <summary>
/// Gets or sets the URI of the mTLS-enabled device authorization endpoint.
/// </summary>
public Uri? MtlsDeviceAuthorizationEndpoint { get; set; }

/// <summary>
/// Gets or sets the URI of the mTLS-enabled introspection endpoint.
/// </summary>
public Uri? MtlsIntrospectionEndpoint { get; set; }

/// <summary>
/// Gets or sets the URI of the mTLS-enabled revocation endpoint.
/// </summary>
public Uri? MtlsRevocationEndpoint { get; set; }

/// <summary>
/// Gets or sets the URI of the mTLS-enabled token endpoint.
/// </summary>
public Uri? MtlsTokenEndpoint { get; set; }

/// <summary>
/// Gets or sets the URI of the mTLS-enabled userinfo endpoint.
/// </summary>
public Uri? MtlsUserInfoEndpoint { get; set; }

/// <summary>
/// Gets the additional properties.
/// </summary>
Expand Down Expand Up @@ -111,6 +136,12 @@ public sealed class OpenIddictConfiguration
/// </summary>
public List<SecurityKey> SigningKeys { get; } = [];

/// <summary>
/// Gets or sets a boolean indicating whether access tokens issued by the
/// authorization server are bound to the client certificate when using mTLS.
/// </summary>
public bool? TlsClientCertificateBoundAccessTokens { get; set; }

/// <summary>
/// Gets or sets the URI of the token endpoint.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public void Configure(OpenIddictClientOptions options)

// Register the built-in event handlers used by the OpenIddict client system integration components.
options.Handlers.AddRange(OpenIddictClientSystemIntegrationHandlers.DefaultHandlers);

// Enable response_mode=fragment support by default.
options.ResponseModes.Add(ResponseModes.Fragment);
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Net.Http.Headers;
using System.Net.Mail;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using OpenIddict.Client;
using OpenIddict.Client.SystemNetHttp;
using Polly;
Expand Down Expand Up @@ -339,6 +340,54 @@ public OpenIddictClientSystemNetHttpBuilder SetProductInformation(Assembly assem
productVersion: assembly.GetName().Version!.ToString()));
}

/// <summary>
/// Sets the delegate called by OpenIddict when trying to resolve the self-signed
/// TLS client authentication certificate that will be used for OAuth 2.0
/// mTLS-based client authentication (self_signed_tls_client_auth), if applicable.
/// </summary>
/// <param name="selector">The selector delegate.</param>
/// <remarks>
/// If no value is explicitly set, OpenIddict automatically tries to resolve the
/// X.509 certificate from the signing credentials attached to the client registration
/// (in this case, the X.509 certificate MUST include the digital signature and
/// client authentication key usages to be automatically selected by OpenIddict).
/// </remarks>
/// <returns>The <see cref="OpenIddictClientSystemNetHttpBuilder"/> instance.</returns>
public OpenIddictClientSystemNetHttpBuilder SetSelfSignedTlsClientAuthenticationCertificateSelector(
Func<OpenIddictClientRegistration, X509Certificate2?> selector)
{
if (selector is null)
{
throw new ArgumentNullException(nameof(selector));
}

return Configure(options => options.SelfSignedTlsClientAuthenticationCertificateSelector = selector);
}

/// <summary>
/// Sets the delegate called by OpenIddict when trying to resolve the
/// TLS client authentication certificate that will be used for OAuth 2.0
/// mTLS-based client authentication (tls_client_auth), if applicable.
/// </summary>
/// <param name="selector">The selector delegate.</param>
/// <remarks>
/// If no value is explicitly set, OpenIddict automatically tries to resolve the
/// X.509 certificate from the signing credentials attached to the client registration
/// (in this case, the X.509 certificate MUST include the digital signature and
/// client authentication key usages to be automatically selected by OpenIddict).
/// </remarks>
/// <returns>The <see cref="OpenIddictClientSystemNetHttpBuilder"/> instance.</returns>
public OpenIddictClientSystemNetHttpBuilder SetTlsClientAuthenticationCertificateSelector(
Func<OpenIddictClientRegistration, X509Certificate2?> selector)
{
if (selector is null)
{
throw new ArgumentNullException(nameof(selector));
}

return Configure(options => options.TlsClientAuthenticationCertificateSelector = selector);
}

/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
Expand Down
Loading