@ -8,7 +8,6 @@ using System.ComponentModel;
using System.Diagnostics ;
using System.Globalization ;
using System.Security.Cryptography.X509Certificates ;
using System.Text ;
using Microsoft.Extensions.DependencyInjection ;
using Microsoft.Extensions.Options ;
using Microsoft.IdentityModel.Tokens ;
@ -19,7 +18,8 @@ namespace OpenIddict.Server;
/// Contains the methods required to ensure that the OpenIddict server configuration is valid.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictServerConfiguration : IPostConfigureOptions < OpenIddictServerOptions >
public sealed class OpenIddictServerConfiguration : IPostConfigureOptions < OpenIddictServerOptions > ,
IValidateOptions < OpenIddictServerOptions >
{
private readonly IServiceProvider _ provider ;
@ -53,15 +53,130 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
options . DisableRollingRefreshTokens = true ;
}
// If token storage was disabled, user codes will be returned as-is by OpenIddict instead of being
// automatically converted to reference identifiers (in this case, custom event handlers must be
// registered to manually store the token payload in a database or cache and return a user code
// that can be used and entered by a human user in a web form). Since the default logic is not
// going be used, disable the formatting logic by setting UserCodeDisplayFormat to null here.
if ( options . DisableTokenStorage )
{
options . UserCodeLength = 0 ;
options . UserCodeCharset . Clear ( ) ;
options . UserCodeDisplayFormat = null ;
}
// Sort the handlers collection using the order associated with each handler.
options . Handlers . Sort ( static ( left , right ) = > left . Order . CompareTo ( right . Order ) ) ;
var now = options . TimeProvider . GetUtcNow ( ) . LocalDateTime ;
// Sort the encryption and signing credentials.
options . EncryptionCredentials . Sort ( ( left , right ) = > Compare ( left . Key , right . Key , now ) ) ;
options . SigningCredentials . Sort ( ( left , right ) = > Compare ( left . Key , right . Key , now ) ) ;
// Generate a key identifier for the encryption/signing keys that don't already have one.
foreach ( var key in options . EncryptionCredentials . Select ( credentials = > credentials . Key )
. Concat ( options . SigningCredentials . Select ( credentials = > credentials . Key ) )
. Where ( key = > string . IsNullOrEmpty ( key . KeyId ) ) )
{
key . KeyId = GetKeyIdentifier ( key ) ;
}
// Attach the signing credentials to the token validation parameters.
options . TokenValidationParameters . IssuerSigningKeys =
from credentials in options . SigningCredentials
select credentials . Key ;
// Attach the encryption credentials to the token validation parameters.
options . TokenValidationParameters . TokenDecryptionKeys =
from credentials in options . EncryptionCredentials
select credentials . Key ;
static int Compare ( SecurityKey left , SecurityKey right , DateTime now ) = > ( left , right ) switch
{
// If the two keys refer to the same instances, return 0.
( SecurityKey first , SecurityKey second ) when ReferenceEquals ( first , second ) = > 0 ,
// If one of the keys is a symmetric key, prefer it to the other one.
( SymmetricSecurityKey , SymmetricSecurityKey ) = > 0 ,
( SymmetricSecurityKey , SecurityKey ) = > - 1 ,
( SecurityKey , SymmetricSecurityKey ) = > 1 ,
// If one of the keys is backed by a X.509 certificate, don't prefer it if it's not valid yet.
( X509SecurityKey first , SecurityKey ) when first . Certificate . NotBefore > now = > 1 ,
( SecurityKey , X509SecurityKey second ) when second . Certificate . NotBefore > now = > - 1 ,
// If the two keys are backed by a X.509 certificate, prefer the one with the furthest expiration date.
( X509SecurityKey first , X509SecurityKey second ) = > - first . Certificate . NotAfter . CompareTo ( second . Certificate . NotAfter ) ,
// If one of the keys is backed by a X.509 certificate, prefer the X.509 security key.
( X509SecurityKey , SecurityKey ) = > - 1 ,
( SecurityKey , X509SecurityKey ) = > 1 ,
// If the two keys are not backed by a X.509 certificate, none should be preferred to the other.
( SecurityKey , SecurityKey ) = > 0
} ;
static string? GetKeyIdentifier ( SecurityKey key )
{
// When no key identifier can be retrieved from the security keys, a value is automatically
// inferred from the hexadecimal representation of the certificate thumbprint (SHA-1)
// when the key is bound to a X.509 certificate or from the public part of the signing key.
if ( key is X509SecurityKey x509SecurityKey )
{
return x509SecurityKey . Certificate . Thumbprint ;
}
if ( key is RsaSecurityKey rsaSecurityKey )
{
// Note: if the RSA parameters are not attached to the signing key,
// extract them by calling ExportParameters on the RSA instance.
var parameters = rsaSecurityKey . Parameters ;
if ( parameters . Modulus is null )
{
parameters = rsaSecurityKey . Rsa . ExportParameters ( includePrivateParameters : false ) ;
Debug . Assert ( parameters . Modulus is not null , SR . GetResourceString ( SR . ID4003 ) ) ;
}
// Only use the 40 first chars of the base64url-encoded modulus.
var identifier = Base64UrlEncoder . Encode ( parameters . Modulus ) ;
return identifier [ . . Math . Min ( identifier . Length , 4 0 ) ] . ToUpperInvariant ( ) ;
}
if ( key is ECDsaSecurityKey ecsdaSecurityKey )
{
// Extract the ECDSA parameters from the signing credentials.
var parameters = ecsdaSecurityKey . ECDsa . ExportParameters ( includePrivateParameters : false ) ;
Debug . Assert ( parameters . Q . X is not null , SR . GetResourceString ( SR . ID4004 ) ) ;
// Only use the 40 first chars of the base64url-encoded X coordinate.
var identifier = Base64UrlEncoder . Encode ( parameters . Q . X ) ;
return identifier [ . . Math . Min ( identifier . Length , 4 0 ) ] . ToUpperInvariant ( ) ;
}
return null ;
}
}
/// <inheritdoc/>
public ValidateOptionsResult Validate ( string? name , OpenIddictServerOptions options )
{
ArgumentNullException . ThrowIfNull ( options ) ;
var builder = new ValidateOptionsResultBuilder ( ) ;
if ( options . JsonWebTokenHandler is null )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0075 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0075 ) ) ;
}
// Ensure at least one flow has been enabled.
if ( options . GrantTypes . Count is 0 & & options . ResponseTypes . Count is 0 )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0076 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0076 ) ) ;
}
var uris = options . AuthorizationEndpointUris . Distinct ( )
@ -80,7 +195,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
// Ensure endpoint URIs are unique across endpoints.
if ( uris . Count ! = uris . Distinct ( ) . Count ( ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0285 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0285 ) ) ;
}
// Ensure the authorization endpoint has been enabled when
@ -88,13 +203,13 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
if ( options . AuthorizationEndpointUris . Count is 0 & & ( options . GrantTypes . Contains ( GrantTypes . AuthorizationCode ) | |
options . GrantTypes . Contains ( GrantTypes . Implicit ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0077 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0077 ) ) ;
}
// Ensure the device authorization endpoint has been enabled when the device grant is supported.
if ( options . DeviceAuthorizationEndpointUris . Count is 0 & & options . GrantTypes . Contains ( GrantTypes . DeviceCode ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0078 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0078 ) ) ;
}
// Ensure the token endpoint has been enabled when the authorization code,
@ -105,19 +220,19 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
options . GrantTypes . Contains ( GrantTypes . Password ) | |
options . GrantTypes . Contains ( GrantTypes . RefreshToken ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0079 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0079 ) ) ;
}
// Ensure the end-user verification endpoint has been enabled when the device grant is supported.
if ( options . EndUserVerificationEndpointUris . Count is 0 & & options . GrantTypes . Contains ( GrantTypes . DeviceCode ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0080 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0080 ) ) ;
}
// Ensure the device grant is allowed when the device authorization endpoint is enabled.
if ( options . DeviceAuthorizationEndpointUris . Count is > 0 & & ! options . GrantTypes . Contains ( GrantTypes . DeviceCode ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0084 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0084 ) ) ;
}
// Ensure the grant types/response types configuration is consistent.
@ -126,17 +241,17 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
var types = type . Split ( Separators . Space , StringSplitOptions . RemoveEmptyEntries ) . ToHashSet ( StringComparer . Ordinal ) ;
if ( types . Contains ( ResponseTypes . Code ) & & ! options . GrantTypes . Contains ( GrantTypes . AuthorizationCode ) )
{
throw new InvalidOperationException ( SR . FormatID0281 ( ResponseTypes . Code ) ) ;
builder . AddError ( SR . FormatID0281 ( ResponseTypes . Code ) ) ;
}
if ( types . Contains ( ResponseTypes . IdToken ) & & ! options . GrantTypes . Contains ( GrantTypes . Implicit ) )
{
throw new InvalidOperationException ( SR . FormatID0282 ( ResponseTypes . IdToken ) ) ;
builder . AddError ( SR . FormatID0282 ( ResponseTypes . IdToken ) ) ;
}
if ( types . Contains ( ResponseTypes . Token ) & & ! options . GrantTypes . Contains ( GrantTypes . Implicit ) )
{
throw new InvalidOperationException ( SR . FormatID0282 ( ResponseTypes . Token ) ) ;
builder . AddError ( SR . FormatID0282 ( ResponseTypes . Token ) ) ;
}
}
@ -147,85 +262,83 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
options . RevocationEndpointUris . Count is not 0 | |
options . TokenEndpointUris . Count is not 0 ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0419 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0419 ) ) ;
}
// Ensure the client authentication methods/client assertion types configuration is consistent.
if ( options . ClientAuthenticationMethods . Contains ( ClientAuthenticationMethods . PrivateKeyJwt ) & &
! options . ClientAssertionTypes . Contains ( ClientAssertionTypes . JwtBearer ) )
{
throw new InvalidOperationException ( SR . FormatID0420 (
ClientAssertionTypes . JwtBearer , ClientAuthenticationMethods . PrivateKeyJwt ) ) ;
builder . AddError ( SR . FormatID0420 ( ClientAssertionTypes . JwtBearer , ClientAuthenticationMethods . PrivateKeyJwt ) ) ;
}
if ( options . ClientAuthenticationMethods . Contains ( ClientAuthenticationMethods . ClientSecretJwt ) & &
! options . ClientAssertionTypes . Contains ( ClientAssertionTypes . JwtBearer ) )
{
throw new InvalidOperationException ( SR . FormatID0420 (
ClientAssertionTypes . JwtBearer , ClientAuthenticationMethods . ClientSecretJwt ) ) ;
builder . AddError ( SR . FormatID0420 ( ClientAssertionTypes . JwtBearer , ClientAuthenticationMethods . ClientSecretJwt ) ) ;
}
// If the tls_client_auth or self_signed_tls_client_auth methods are enabled, ensure a chain policy has been set.
if ( options . ClientAuthenticationMethods . Contains ( ClientAuthenticationMethods . TlsClientAuth ) & &
options . PublicKeyInfrastructureTlsClientAuthenticationPolicy is null )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0505 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0505 ) ) ;
}
if ( options . ClientAuthenticationMethods . Contains ( ClientAuthenticationMethods . SelfSignedTlsClientAuth ) & &
options . SelfSignedTlsClientAuthenticationPolicy is null )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0506 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0506 ) ) ;
}
// Ensure at least one supported subject type is listed.
if ( options . SubjectTypes . Count is 0 )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0421 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0421 ) ) ;
}
// Ensure reference tokens support was not enabled when token storage is disabled.
if ( options . DisableTokenStorage & & ( options . UseReferenceAccessTokens | | options . UseReferenceRefreshTokens ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0083 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0083 ) ) ;
}
// Ensure authorization or end session request caching was not enabled when token storage is disabled.
if ( options . DisableTokenStorage & & ( options . EnableAuthorizationRequestCaching | | options . EnableEndSessionRequestCaching ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0465 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0465 ) ) ;
}
// Prevent the device authorization flow from being used if token storage is disabled, unless the degraded
// mode has been enabled (in this case, additional checks will be enforced later to require custom handlers).
if ( options . DisableTokenStorage & & ! options . EnableDegradedMode & & options . GrantTypes . Contains ( GrantTypes . DeviceCode ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0367 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0367 ) ) ;
}
// Ensure at least one subject token type is configured when the token exchange grant is enabled.
if ( options . SubjectTokenTypes . Count is 0 & & options . GrantTypes . Contains ( GrantTypes . TokenExchange ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0486 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0486 ) ) ;
}
// Prevent internal token types from being used as subject, actor or requested token types.
if ( options . SubjectTokenTypes . Any ( static type = > type . StartsWith (
TokenTypeIdentifiers . Prefixes . OpenIddict , StringComparison . OrdinalIgnoreCase ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0487 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0487 ) ) ;
}
if ( options . ActorTokenTypes . Any ( static type = > type . StartsWith (
TokenTypeIdentifiers . Prefixes . OpenIddict , StringComparison . OrdinalIgnoreCase ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0488 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0488 ) ) ;
}
if ( options . RequestedTokenTypes . Any ( static type = > type . StartsWith (
TokenTypeIdentifiers . Prefixes . OpenIddict , StringComparison . OrdinalIgnoreCase ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0489 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0489 ) ) ;
}
// Ensure that the default requested token type (used when the caller doesn't specify a
@ -233,28 +346,28 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
// and that the configured value is also present in the list of allowed token types.
if ( string . IsNullOrEmpty ( options . DefaultRequestedTokenType ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0490 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0490 ) ) ;
}
if ( options . DefaultRequestedTokenType . StartsWith (
TokenTypeIdentifiers . Prefixes . OpenIddict , StringComparison . OrdinalIgnoreCase ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0491 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0491 ) ) ;
}
if ( ! options . RequestedTokenTypes . Contains ( options . DefaultRequestedTokenType ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0492 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0492 ) ) ;
}
if ( options . EncryptionCredentials . Count is 0 )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0085 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0085 ) ) ;
}
if ( ! options . SigningCredentials . Exists ( static credentials = > credentials . Key is AsymmetricSecurityKey ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0086 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0086 ) ) ;
}
var now = options . TimeProvider . GetUtcNow ( ) . LocalDateTime ;
@ -264,7 +377,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
credentials . Key is X509SecurityKey { Certificate : X509Certificate2 certificate } & &
( certificate . NotBefore > now | | certificate . NotAfter < now ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0087 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0087 ) ) ;
}
// If all the registered signing credentials are backed by a X.509 certificate, at least one of them must be valid.
@ -272,7 +385,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
credentials . Key is X509SecurityKey { Certificate : X509Certificate2 certificate } & &
( certificate . NotBefore > now | | certificate . NotAfter < now ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0088 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0088 ) ) ;
}
// When set, the mTLS endpoint aliases MUST represent absolute HTTPS URLs.
@ -283,7 +396,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
! TryValidateMtlsEndpointAlias ( options . MtlsTokenEndpointAliasUri ) | |
! TryValidateMtlsEndpointAlias ( options . MtlsUserInfoEndpointAliasUri ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0499 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0499 ) ) ;
}
// Prevent the mTLS aliases from being configured if the corresponding endpoints haven't been enabled.
@ -294,7 +407,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
( options . MtlsTokenEndpointAliasUri is not null & & options . TokenEndpointUris . Count is 0 ) | |
( options . MtlsUserInfoEndpointAliasUri is not null & & options . UserInfoEndpointUris . Count is 0 ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0510 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0510 ) ) ;
}
// If at least one mTLS endpoint alias was configured, require that the issuer be explicitly set
@ -307,7 +420,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
options . MtlsTokenEndpointAliasUri is not null | |
options . MtlsUserInfoEndpointAliasUri is not null ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0500 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0500 ) ) ;
}
// Ensure no end certificate was included in the PKI TLS client authentication
@ -319,13 +432,13 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
! OpenIddictHelpers . IsCertificateAuthority ( certificate ) | |
! OpenIddictHelpers . HasKeyUsage ( certificate , X509KeyUsageFlags . KeyCertSign ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0501 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0501 ) ) ;
}
if ( options . PublicKeyInfrastructureTlsClientAuthenticationPolicy . ExtraStore . Cast < X509Certificate2 > ( )
. Any ( static certificate = > certificate . HasPrivateKey ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0511 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0511 ) ) ;
}
#if NET
@ -334,13 +447,13 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
! OpenIddictHelpers . IsCertificateAuthority ( certificate ) | |
! OpenIddictHelpers . HasKeyUsage ( certificate , X509KeyUsageFlags . KeyCertSign ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0501 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0501 ) ) ;
}
if ( options . PublicKeyInfrastructureTlsClientAuthenticationPolicy . CustomTrustStore . Cast < X509Certificate2 > ( )
. Any ( static certificate = > certificate . HasPrivateKey ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0511 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0511 ) ) ;
}
#endif
}
@ -350,13 +463,13 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
{
if ( options . SelfSignedTlsClientAuthenticationPolicy . ExtraStore . Count is not 0 )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0502 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0502 ) ) ;
}
#if NET
if ( options . SelfSignedTlsClientAuthenticationPolicy . CustomTrustStore . Count is not 0 )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0502 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0502 ) ) ;
}
#endif
}
@ -371,7 +484,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0089 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0089 ) ) ;
}
if ( options . DeviceAuthorizationEndpointUris . Count is not 0 & & ! options . Handlers . Exists ( static descriptor = >
@ -380,7 +493,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0090 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0090 ) ) ;
}
if ( options . IntrospectionEndpointUris . Count is not 0 & & ! options . Handlers . Exists ( static descriptor = >
@ -389,7 +502,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0091 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0091 ) ) ;
}
if ( options . EndSessionEndpointUris . Count is not 0 & & ! options . Handlers . Exists ( static descriptor = >
@ -397,7 +510,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0092 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0092 ) ) ;
}
if ( options . PushedAuthorizationEndpointUris . Count is not 0 & & ! options . Handlers . Exists ( static descriptor = >
@ -406,7 +519,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0466 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0466 ) ) ;
}
if ( options . RevocationEndpointUris . Count is not 0 & & ! options . Handlers . Exists ( static descriptor = >
@ -415,7 +528,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0093 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0093 ) ) ;
}
if ( options . TokenEndpointUris . Count is not 0 & & ! options . Handlers . Exists ( static descriptor = >
@ -424,7 +537,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0094 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0094 ) ) ;
}
if ( options . EndUserVerificationEndpointUris . Count is not 0 & & ! options . Handlers . Exists ( static descriptor = >
@ -432,7 +545,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0095 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0095 ) ) ;
}
// If the degraded mode was enabled, ensure custom validation/generation handlers
@ -445,7 +558,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( static type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0096 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0096 ) ) ;
}
if ( ! options . Handlers . Exists ( static descriptor = >
@ -453,183 +566,61 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor . Type is OpenIddictServerHandlerType . Custom & &
descriptor . FilterTypes . All ( static type = > ! typeof ( RequireDegradedModeDisabled ) . IsAssignableFrom ( type ) ) ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0097 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0097 ) ) ;
}
}
}
// If token storage was disabled, user codes will be returned as-is by OpenIddict instead of being
// automatically converted to reference identifiers (in this case, custom event handlers must be
// registered to manually store the token payload in a database or cache and return a user code
// that can be used and entered by a human user in a web form). Since the default logic is not
// going be used, disable the formatting logic by setting UserCodeDisplayFormat to null here.
if ( options . DisableTokenStorage )
{
options . UserCodeLength = 0 ;
options . UserCodeCharset . Clear ( ) ;
options . UserCodeDisplayFormat = null ;
}
else
if ( ! options . DisableTokenStorage )
{
if ( options . UserCodeLength is < 6 )
{
throw new InvalidOperationException ( SR . FormatID0439 ( 6 ) ) ;
builder . AddError ( SR . FormatID0439 ( 6 ) ) ;
}
if ( options . UserCodeCharset . Count is < 9 )
{
throw new InvalidOperationException ( SR . FormatID0440 ( 9 ) ) ;
builder . AddError ( SR . FormatID0440 ( 9 ) ) ;
}
if ( options . UserCodeCharset . Count ! = options . UserCodeCharset . Distinct ( StringComparer . Ordinal ) . Count ( ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0436 ) ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0436 ) ) ;
}
foreach ( var character in options . UserCodeCharset )
{
#if NET
// On supported platforms, ensure each character added to the
// charset represents exactly one grapheme cluster/text element.
var enumerator = StringInfo . GetTextElementEnumerator ( character ) ;
if ( ! enumerator . MoveNext ( ) | | enumerator . MoveNext ( ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0437 ) ) ;
}
#else
// On unsupported platforms, prevent non-ASCII characters from being used.
if ( character . Any ( static character = > ( uint ) character > ' \ x007f ' ) )
{
throw new InvalidOperationException ( SR . GetResourceString ( SR . ID0438 ) ) ;
}
#endif
}
if ( string . IsNullOrEmpty ( options . UserCodeDisplayFormat ) )
// On supported platforms, ensure each character added to the
// charset represents exactly one grapheme cluster/text element.
if ( options . UserCodeCharset . Any ( static character = > ! ValidateUserCodeCharacter ( character ) ) )
{
var builder = new StringBuilder ( ) ;
var count = options . UserCodeLength % 5 is 0 ? 5 :
options . UserCodeLength % 4 is 0 ? 4 :
options . UserCodeLength % 3 is 0 ? 3 :
options . UserCodeLength % 2 is 0 ? 2 : 1 ;
for ( var index = 0 ; index < options . UserCodeLength ; index + + )
{
if ( index is > 0 & & index % count is 0 )
{
builder . Append ( Separators . Dash [ 0 ] ) ;
}
builder . Append ( '{' ) ;
builder . Append ( index ) ;
builder . Append ( '}' ) ;
}
options . UserCodeDisplayFormat = builder . ToString ( ) ;
builder . AddError ( SR . GetResourceString ( SR . ID0437 ) ) ;
}
if ( options . UserCodeCharset . Contains ( "-" , StringComparer . Ordinal ) & &
options . UserCodeDisplayFormat . Any ( static character = > character is '-' ) )
{
throw new InvalidOperationException ( SR . FormatID0441 ( '-' ) ) ;
}
}
// Sort the handlers collection using the order associated with each handler.
options . Handlers . Sort ( static ( left , right ) = > left . Order . CompareTo ( right . Order ) ) ;
// Sort the encryption and signing credentials.
options . EncryptionCredentials . Sort ( ( left , right ) = > Compare ( left . Key , right . Key , now ) ) ;
options . SigningCredentials . Sort ( ( left , right ) = > Compare ( left . Key , right . Key , now ) ) ;
// Generate a key identifier for the encryption/signing keys that don't already have one.
foreach ( var key in options . EncryptionCredentials . Select ( credentials = > credentials . Key )
. Concat ( options . SigningCredentials . Select ( credentials = > credentials . Key ) )
. Where ( key = > string . IsNullOrEmpty ( key . KeyId ) ) )
{
key . KeyId = GetKeyIdentifier ( key ) ;
}
// Attach the signing credentials to the token validation parameters.
options . TokenValidationParameters . IssuerSigningKeys =
from credentials in options . SigningCredentials
select credentials . Key ;
// Attach the encryption credentials to the token validation parameters.
options . TokenValidationParameters . TokenDecryptionKeys =
from credentials in options . EncryptionCredentials
select credentials . Key ;
static int Compare ( SecurityKey left , SecurityKey right , DateTime now ) = > ( left , right ) switch
{
// If the two keys refer to the same instances, return 0.
( SecurityKey first , SecurityKey second ) when ReferenceEquals ( first , second ) = > 0 ,
// If one of the keys is a symmetric key, prefer it to the other one.
( SymmetricSecurityKey , SymmetricSecurityKey ) = > 0 ,
( SymmetricSecurityKey , SecurityKey ) = > - 1 ,
( SecurityKey , SymmetricSecurityKey ) = > 1 ,
// If one of the keys is backed by a X.509 certificate, don't prefer it if it's not valid yet.
( X509SecurityKey first , SecurityKey ) when first . Certificate . NotBefore > now = > 1 ,
( SecurityKey , X509SecurityKey second ) when second . Certificate . NotBefore > now = > - 1 ,
// If the two keys are backed by a X.509 certificate, prefer the one with the furthest expiration date.
( X509SecurityKey first , X509SecurityKey second ) = > - first . Certificate . NotAfter . CompareTo ( second . Certificate . NotAfter ) ,
// If one of the keys is backed by a X.509 certificate, prefer the X.509 security key.
( X509SecurityKey , SecurityKey ) = > - 1 ,
( SecurityKey , X509SecurityKey ) = > 1 ,
// If the two keys are not backed by a X.509 certificate, none should be preferred to the other.
( SecurityKey , SecurityKey ) = > 0
} ;
static string? GetKeyIdentifier ( SecurityKey key )
{
// When no key identifier can be retrieved from the security keys, a value is automatically
// inferred from the hexadecimal representation of the certificate thumbprint (SHA-1)
// when the key is bound to a X.509 certificate or from the public part of the signing key.
if ( key is X509SecurityKey x509SecurityKey )
#else
// On unsupported platforms, prevent non-ASCII characters from being used.
if ( options . UserCodeCharset . Any ( static character = > character . Any ( static character = > ( uint ) character > ' \ x007f ' ) ) )
{
return x509SecurityKey . Certificate . Thumbprint ;
builder . AddError ( SR . GetResourceString ( SR . ID0438 ) ) ;
}
#endif
if ( key is RsaSecurityKey rsaSecurityKey )
if ( ! string . IsNullOrEmpty ( options . UserCodeDisplayFormat ) & &
options . UserCodeCharset . Contains ( "-" , StringComparer . Ordinal ) & &
options . UserCodeDisplayFormat . Any ( static character = > character is '-' ) )
{
// Note: if the RSA parameters are not attached to the signing key,
// extract them by calling ExportParameters on the RSA instance.
var parameters = rsaSecurityKey . Parameters ;
if ( parameters . Modulus is null )
{
parameters = rsaSecurityKey . Rsa . ExportParameters ( includePrivateParameters : false ) ;
Debug . Assert ( parameters . Modulus is not null , SR . GetResourceString ( SR . ID4003 ) ) ;
}
// Only use the 40 first chars of the base64url-encoded modulus.
var identifier = Base64UrlEncoder . Encode ( parameters . Modulus ) ;
return identifier [ . . Math . Min ( identifier . Length , 4 0 ) ] . ToUpperInvariant ( ) ;
builder . AddError ( SR . FormatID0441 ( '-' ) ) ;
}
if ( key is ECDsaSecurityKey ecsdaSecurityKey )
#if NET
static bool ValidateUserCodeCharacter ( string character )
{
// Extract the ECDSA parameters from the signing credentials.
var parameters = ecsdaSecurityKey . ECDsa . ExportParameters ( includePrivateParameters : false ) ;
Debug . Assert ( parameters . Q . X is not null , SR . GetResourceString ( SR . ID4004 ) ) ;
// Only use the 40 first chars of the base64url-encoded X coordinate.
var identifier = Base64UrlEncoder . Encode ( parameters . Q . X ) ;
return identifier [ . . Math . Min ( identifier . Length , 4 0 ) ] . ToUpperInvariant ( ) ;
var enumerator = StringInfo . GetTextElementEnumerator ( character ) ;
return enumerator . MoveNext ( ) & & ! enumerator . MoveNext ( ) ;
}
return null ;
#endif
}
return builder . Build ( ) ;
static bool TryValidateMtlsEndpointAlias ( Uri ? uri ) = > uri is null | |
( uri . IsAbsoluteUri & & string . Equals ( uri . Scheme , Uri . UriSchemeHttps , StringComparison . OrdinalIgnoreCase ) ) ;
}