private async Task <bool> InvokeCryptographyEndpointAsync()
        {
            // Metadata requests must be made via GET.
            // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
            if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                Logger.LogError("The cryptography request was rejected because an invalid " +
                                "HTTP method was specified: {Method}.", Request.Method);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The specified HTTP method is not valid."
                }));
            }

            var request = new OpenIdConnectRequest(Request.Query);

            // Note: set the message type before invoking the ExtractCryptographyRequest event.
            request.SetProperty(OpenIdConnectConstants.Properties.MessageType,
                                OpenIdConnectConstants.MessageTypes.CryptographyRequest);

            // Store the cryptography request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractCryptographyRequestContext(Context, Options, request);
            await Options.Provider.ExtractCryptographyRequest(@event);

            if (@event.HandledResponse)
            {
                Logger.LogDebug("The cryptography request was handled in user code.");

                return(true);
            }

            else if (@event.Skipped)
            {
                Logger.LogDebug("The default cryptography request handling was skipped from user code.");

                return(false);
            }

            else if (@event.IsRejected)
            {
                Logger.LogError("The cryptography request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ @event.ErrorDescription);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse
                {
                    Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = @event.ErrorDescription,
                    ErrorUri = @event.ErrorUri
                }));
            }

            Logger.LogInformation("The cryptography request was successfully extracted " +
                                  "from the HTTP request: {Request}.", request);

            var context = new ValidateCryptographyRequestContext(Context, Options, request);
            await Options.Provider.ValidateCryptographyRequest(context);

            if (context.HandledResponse)
            {
                Logger.LogDebug("The cryptography request was handled in user code.");

                return(true);
            }

            else if (context.Skipped)
            {
                Logger.LogDebug("The default cryptography request handling was skipped from user code.");

                return(false);
            }

            else if (context.IsRejected)
            {
                Logger.LogError("The cryptography request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ context.ErrorDescription);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse
                {
                    Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = context.ErrorDescription,
                    ErrorUri = context.ErrorUri
                }));
            }

            var notification = new HandleCryptographyRequestContext(Context, Options, request);

            foreach (var credentials in Options.SigningCredentials)
            {
                // If the signing key is not an asymmetric key, ignore it.
                if (!(credentials.Key is AsymmetricSecurityKey))
                {
                    Logger.LogDebug("A non-asymmetric signing key of type '{Type}' was excluded " +
                                    "from the key set.", credentials.Key.GetType().FullName);

                    continue;
                }

#if SUPPORTS_ECDSA
                if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) &&
                    !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) &&
                    !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) &&
                    !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
                {
                    Logger.LogInformation("An unsupported signing key of type '{Type}' was ignored and excluded " +
                                          "from the key set. Only RSA and ECDSA asymmetric security keys can be " +
                                          "exposed via the JWKS endpoint.", credentials.Key.GetType().Name);

                    continue;
                }
#else
                if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256))
                {
                    Logger.LogInformation("An unsupported signing key of type '{Type}' was ignored and excluded " +
                                          "from the key set. Only RSA asymmetric security keys can be exposed " +
                                          "via the JWKS endpoint.", credentials.Key.GetType().Name);

                    continue;
                }
#endif

                var key = new JsonWebKey
                {
                    Use = JsonWebKeyUseNames.Sig,

                    // Resolve the JWA identifier from the algorithm specified in the credentials.
                    Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm),

                    // Use the key identifier specified in the signing credentials.
                    Kid = credentials.Kid,
                };

                if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256))
                {
                    RSA algorithm = null;

                    // Note: IdentityModel 5 doesn't expose a method allowing to retrieve the underlying algorithm
                    // from a generic asymmetric security key. To work around this limitation, try to cast
                    // the security key to the built-in IdentityModel types to extract the required RSA instance.
                    // See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/395
                    if (credentials.Key is X509SecurityKey x509SecurityKey)
                    {
                        algorithm = x509SecurityKey.PublicKey as RSA;
                    }

                    else if (credentials.Key is RsaSecurityKey rsaSecurityKey)
                    {
                        algorithm = rsaSecurityKey.Rsa;

                        // If no RSA instance can be found, create one using
                        // the RSA parameters attached to the security key.
                        if (algorithm == null)
                        {
                            var rsa = RSA.Create();
                            rsa.ImportParameters(rsaSecurityKey.Parameters);
                            algorithm = rsa;
                        }
                    }

                    // Skip the key if an algorithm instance cannot be extracted.
                    if (algorithm == null)
                    {
                        Logger.LogWarning("A signing key was ignored because it was unable " +
                                          "to provide the requested algorithm instance.");

                        continue;
                    }

                    // Export the RSA public key to create a new JSON Web Key
                    // exposing the exponent and the modulus parameters.
                    var parameters = algorithm.ExportParameters(includePrivateParameters: false);

                    Debug.Assert(parameters.Exponent != null &&
                                 parameters.Modulus != null,
                                 "RSA.ExportParameters() shouldn't return null parameters.");

                    key.Kty = JsonWebAlgorithmsKeyTypes.RSA;

                    // Note: both E and N must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7518#section-6.3.1.1
                    key.E = Base64UrlEncoder.Encode(parameters.Exponent);
                    key.N = Base64UrlEncoder.Encode(parameters.Modulus);
                }

#if SUPPORTS_ECDSA
                else if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) ||
                         credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) ||
                         credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
                {
                    ECDsa algorithm = null;

                    if (credentials.Key is X509SecurityKey x509SecurityKey)
                    {
                        algorithm = x509SecurityKey.PublicKey as ECDsa;
                    }

                    else if (credentials.Key is ECDsaSecurityKey ecdsaSecurityKey)
                    {
                        algorithm = ecdsaSecurityKey.ECDsa;
                    }

                    // Skip the key if an algorithm instance cannot be extracted.
                    if (algorithm == null)
                    {
                        Logger.LogWarning("A signing key was ignored because it was unable " +
                                          "to provide the requested algorithm instance.");

                        continue;
                    }

                    // Export the ECDsa public key to create a new JSON Web Key
                    // exposing the coordinates of the point on the curve.
                    var parameters = algorithm.ExportParameters(includePrivateParameters: false);

                    Debug.Assert(parameters.Q.X != null &&
                                 parameters.Q.Y != null,
                                 "ECDsa.ExportParameters() shouldn't return null coordinates.");

                    key.Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve;
                    key.Crv = OpenIdConnectServerHelpers.GetJwtAlgorithmCurve(parameters.Curve);

                    // Note: both X and Y must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2
                    key.X = Base64UrlEncoder.Encode(parameters.Q.X);
                    key.Y = Base64UrlEncoder.Encode(parameters.Q.Y);
                }
#endif

                // If the signing key is embedded in a X.509 certificate, set
                // the x5t and x5c parameters using the certificate details.
                var certificate = (credentials.Key as X509SecurityKey)?.Certificate;
                if (certificate != null)
                {
                    // x5t must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.8
                    key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash());

                    // Unlike E or N, the certificates contained in x5c
                    // must be base64-encoded and not base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.7
                    key.X5c.Add(Convert.ToBase64String(certificate.RawData));
                }

                notification.Keys.Add(key);
            }

            await Options.Provider.HandleCryptographyRequest(notification);

            if (notification.HandledResponse)
            {
                Logger.LogDebug("The cryptography request was handled in user code.");

                return(true);
            }

            else if (notification.Skipped)
            {
                Logger.LogDebug("The default cryptography request handling was skipped from user code.");

                return(false);
            }

            else if (notification.IsRejected)
            {
                Logger.LogError("The cryptography request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ notification.ErrorDescription);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse
                {
                    Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = notification.ErrorDescription,
                    ErrorUri = notification.ErrorUri
                }));
            }

            var keys = new JArray();

            foreach (var key in notification.Keys)
            {
                var item = new JObject();

                // Ensure a key type has been provided.
                // See https://tools.ietf.org/html/rfc7517#section-4.1
                if (string.IsNullOrEmpty(key.Kty))
                {
                    Logger.LogError("A JSON Web Key was excluded from the key set because " +
                                    "it didn't contain the mandatory 'kid' parameter.");

                    continue;
                }

                // Create a dictionary associating the
                // JsonWebKey components with their values.
                var parameters = new Dictionary <string, string>
                {
                    [JsonWebKeyParameterNames.Kid] = key.Kid,
                    [JsonWebKeyParameterNames.Use] = key.Use,
                    [JsonWebKeyParameterNames.Kty] = key.Kty,
                    [JsonWebKeyParameterNames.Alg] = key.Alg,
                    [JsonWebKeyParameterNames.Crv] = key.Crv,
                    [JsonWebKeyParameterNames.E]   = key.E,
                    [JsonWebKeyParameterNames.N]   = key.N,
                    [JsonWebKeyParameterNames.X]   = key.X,
                    [JsonWebKeyParameterNames.Y]   = key.Y,
                    [JsonWebKeyParameterNames.X5t] = key.X5t,
                    [JsonWebKeyParameterNames.X5u] = key.X5u
                };

                foreach (var parameter in parameters)
                {
                    if (!string.IsNullOrEmpty(parameter.Value))
                    {
                        item.Add(parameter.Key, parameter.Value);
                    }
                }

                if (key.KeyOps.Count != 0)
                {
                    item.Add(JsonWebKeyParameterNames.KeyOps, new JArray(key.KeyOps));
                }

                if (key.X5c.Count != 0)
                {
                    item.Add(JsonWebKeyParameterNames.X5c, new JArray(key.X5c));
                }

                keys.Add(item);
            }

            // Note: AddParameter() is used here to ensure the mandatory "keys" node
            // is returned to the caller, even if the key set doesn't expose any key.
            // See https://tools.ietf.org/html/rfc7517#section-5 for more information.
            var response = new OpenIdConnectResponse();
            response.AddParameter(OpenIdConnectConstants.Parameters.Keys, keys);

            return(await SendCryptographyResponseAsync(response));
        }
 /// <summary>
 /// Represents an event called for each request to the cryptography endpoint to give the user code
 /// a chance to manually extract the cryptography request from the ambient HTTP context.
 /// </summary>
 /// <param name="context">The context instance associated with this event.</param>
 /// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
 public virtual Task ExtractCryptographyRequest(ExtractCryptographyRequestContext context)
 => OnExtractCryptographyRequest(context);
        private async Task <bool> InvokeCryptographyEndpointAsync()
        {
            // Metadata requests must be made via GET.
            // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
            if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                Options.Logger.LogError("The discovery request was rejected because an invalid " +
                                        "HTTP method was used: {Method}.", Request.Method);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "Invalid HTTP method: make sure to use GET."
                }));
            }

            var request = new OpenIdConnectRequest(Request.Query);

            // Note: set the message type before invoking the ExtractCryptographyRequest event.
            request.SetProperty(OpenIdConnectConstants.Properties.MessageType,
                                OpenIdConnectConstants.MessageTypes.Cryptography);

            // Store the discovery request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractCryptographyRequestContext(Context, Options, request);
            await Options.Provider.ExtractCryptographyRequest(@event);

            if (@event.HandledResponse)
            {
                return(true);
            }

            else if (@event.Skipped)
            {
                return(false);
            }

            else if (@event.IsRejected)
            {
                Options.Logger.LogError("The discovery request was rejected with the following error: {Error} ; {Description}",
                                        /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                        /* Description: */ @event.ErrorDescription);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse {
                    Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = @event.ErrorDescription,
                    ErrorUri = @event.ErrorUri
                }));
            }

            var context = new ValidateCryptographyRequestContext(Context, Options, request);
            await Options.Provider.ValidateCryptographyRequest(context);

            if (context.HandledResponse)
            {
                return(true);
            }

            else if (context.Skipped)
            {
                return(false);
            }

            else if (!context.IsValidated)
            {
                Options.Logger.LogError("The discovery request was rejected with the following error: {Error} ; {Description}",
                                        /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                        /* Description: */ context.ErrorDescription);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse {
                    Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = context.ErrorDescription,
                    ErrorUri = context.ErrorUri
                }));
            }

            var notification = new HandleCryptographyRequestContext(Context, Options, request);

            foreach (var credentials in Options.EncryptingCredentials)
            {
                // Ignore the key if it's not supported.
                if (!credentials.SecurityKey.IsSupportedAlgorithm(SecurityAlgorithms.RsaOaepKeyWrap) &&
                    !credentials.SecurityKey.IsSupportedAlgorithm(SecurityAlgorithms.RsaV15KeyWrap))
                {
                    Options.Logger.LogInformation("An unsupported encryption key was ignored and excluded from the " +
                                                  "key set: {Type}. Only RSA asymmetric security keys can be exposed " +
                                                  "via the JWKS endpoint.", credentials.SecurityKey.GetType().Name);

                    continue;
                }

                // Try to extract a key identifier from the credentials.
                LocalIdKeyIdentifierClause identifier = null;
                credentials.SecurityKeyIdentifier?.TryFind(out identifier);

                // Resolve the underlying algorithm from the security key.
                var algorithm = ((AsymmetricSecurityKey)credentials.SecurityKey)
                                .GetAsymmetricAlgorithm(
                    algorithm: SecurityAlgorithms.RsaOaepKeyWrap,
                    privateKey: false) as RSA;

                // Skip the key if an algorithm instance cannot be extracted.
                if (algorithm == null)
                {
                    Options.Logger.LogWarning("An encryption key was ignored because it was unable " +
                                              "to provide the requested algorithm instance.");

                    continue;
                }

                // Export the RSA public key to create a new JSON Web Key
                // exposing the exponent and the modulus parameters.
                var parameters = algorithm.ExportParameters(includePrivateParameters: false);

                Debug.Assert(parameters.Exponent != null &&
                             parameters.Modulus != null,
                             "RSA.ExportParameters() shouldn't return null parameters.");

                var key = new JsonWebKey {
                    Use = JsonWebKeyUseNames.Enc,
                    Kty = JsonWebAlgorithmsKeyTypes.RSA,

                    // Resolve the JWA identifier from the algorithm specified in the credentials.
                    Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm),

                    // Use the key identifier specified
                    // in the signing credentials.
                    Kid = identifier.LocalId,

                    // Note: both E and N must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2
                    E = Base64UrlEncoder.Encode(parameters.Exponent),
                    N = Base64UrlEncoder.Encode(parameters.Modulus)
                };

                X509Certificate2 certificate = null;

                // Determine whether the encrypting credentials are directly based on a X.509 certificate.
                var x509EncryptingCredentials = credentials as X509EncryptingCredentials;
                if (x509EncryptingCredentials != null)
                {
                    certificate = x509EncryptingCredentials.Certificate;
                }

                // Skip looking for a X509SecurityKey in EncryptingCredentials.SecurityKey
                // if a certificate has been found in the EncryptingCredentials instance.
                if (certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509SecurityKey = credentials.SecurityKey as X509SecurityKey;
                    if (x509SecurityKey != null)
                    {
                        certificate = x509SecurityKey.Certificate;
                    }
                }

                // Skip looking for a X509AsymmetricSecurityKey in EncryptingCredentials.SecurityKey
                // if a certificate has been found in EncryptingCredentials or EncryptingCredentials.SecurityKey.
                if (certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509AsymmetricSecurityKey = credentials.SecurityKey as X509AsymmetricSecurityKey;
                    if (x509AsymmetricSecurityKey != null)
                    {
                        // The X.509 certificate is not directly accessible when using X509AsymmetricSecurityKey.
                        // Reflection is the only way to get the certificate used to create the security key.
                        var field = typeof(X509AsymmetricSecurityKey).GetField(
                            name: "certificate",
                            bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic);
                        Debug.Assert(field != null);

                        certificate = (X509Certificate2)field.GetValue(x509AsymmetricSecurityKey);
                    }
                }

                // If the encryption key is embedded in a X.509 certificate, set
                // the x5t and x5c parameters using the certificate details.
                if (certificate != null)
                {
                    // x5t must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.8
                    key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash());

                    // Unlike E or N, the certificates contained in x5c
                    // must be base64-encoded and not base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.7
                    key.X5c.Add(Convert.ToBase64String(certificate.RawData));
                }

                notification.Keys.Add(key);
            }

            foreach (var credentials in Options.SigningCredentials)
            {
                // Ignore the key if it's not supported.
                if (!credentials.SigningKey.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256Signature))
                {
                    Options.Logger.LogInformation("An unsupported signing key was ignored and excluded from the " +
                                                  "key set: {Type}. Only RSA asymmetric security keys can be exposed " +
                                                  "via the JWKS endpoint.", credentials.SigningKey.GetType().Name);

                    continue;
                }

                // Try to extract a key identifier from the credentials.
                LocalIdKeyIdentifierClause identifier = null;
                credentials.SigningKeyIdentifier?.TryFind(out identifier);

                // Resolve the underlying algorithm from the security key.
                var algorithm = ((AsymmetricSecurityKey)credentials.SigningKey)
                                .GetAsymmetricAlgorithm(
                    algorithm: SecurityAlgorithms.RsaSha256Signature,
                    privateKey: false) as RSA;

                // Skip the key if an algorithm instance cannot be extracted.
                if (algorithm == null)
                {
                    Options.Logger.LogWarning("A signing key was ignored because it was unable " +
                                              "to provide the requested algorithm instance.");

                    continue;
                }

                // Export the RSA public key to create a new JSON Web Key
                // exposing the exponent and the modulus parameters.
                var parameters = algorithm.ExportParameters(includePrivateParameters: false);

                Debug.Assert(parameters.Exponent != null &&
                             parameters.Modulus != null,
                             "RSA.ExportParameters() shouldn't return null parameters.");

                var key = new JsonWebKey {
                    Use = JsonWebKeyUseNames.Sig,
                    Kty = JsonWebAlgorithmsKeyTypes.RSA,

                    // Resolve the JWA identifier from the algorithm specified in the credentials.
                    Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.SignatureAlgorithm),

                    // Use the key identifier specified
                    // in the signing credentials.
                    Kid = identifier?.LocalId,

                    // Note: both E and N must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2
                    E = Base64UrlEncoder.Encode(parameters.Exponent),
                    N = Base64UrlEncoder.Encode(parameters.Modulus)
                };

                X509Certificate2 certificate = null;

                // Determine whether the signing credentials are directly based on a X.509 certificate.
                var x509SigningCredentials = credentials as X509SigningCredentials;
                if (x509SigningCredentials != null)
                {
                    certificate = x509SigningCredentials.Certificate;
                }

                // Skip looking for a X509SecurityKey in SigningCredentials.SigningKey
                // if a certificate has been found in the SigningCredentials instance.
                if (certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509SecurityKey = credentials.SigningKey as X509SecurityKey;
                    if (x509SecurityKey != null)
                    {
                        certificate = x509SecurityKey.Certificate;
                    }
                }

                // Skip looking for a X509AsymmetricSecurityKey in SigningCredentials.SigningKey
                // if a certificate has been found in SigningCredentials or SigningCredentials.SigningKey.
                if (certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509AsymmetricSecurityKey = credentials.SigningKey as X509AsymmetricSecurityKey;
                    if (x509AsymmetricSecurityKey != null)
                    {
                        // The X.509 certificate is not directly accessible when using X509AsymmetricSecurityKey.
                        // Reflection is the only way to get the certificate used to create the security key.
                        var field = typeof(X509AsymmetricSecurityKey).GetField(
                            name: "certificate",
                            bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic);
                        Debug.Assert(field != null);

                        certificate = (X509Certificate2)field.GetValue(x509AsymmetricSecurityKey);
                    }
                }

                // If the signing key is embedded in a X.509 certificate, set
                // the x5t and x5c parameters using the certificate details.
                if (certificate != null)
                {
                    // x5t must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.8
                    key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash());

                    // Unlike E or N, the certificates contained in x5c
                    // must be base64-encoded and not base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.7
                    key.X5c.Add(Convert.ToBase64String(certificate.RawData));
                }

                notification.Keys.Add(key);
            }

            await Options.Provider.HandleCryptographyRequest(notification);

            if (notification.HandledResponse)
            {
                return(true);
            }

            else if (notification.Skipped)
            {
                return(false);
            }

            else if (notification.IsRejected)
            {
                Options.Logger.LogError("The discovery request was rejected with the following error: {Error} ; {Description}",
                                        /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                        /* Description: */ notification.ErrorDescription);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse {
                    Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = notification.ErrorDescription,
                    ErrorUri = notification.ErrorUri
                }));
            }

            var keys = new JArray();

            foreach (var key in notification.Keys)
            {
                var item = new JObject();

                // Ensure a key type has been provided.
                // See https://tools.ietf.org/html/rfc7517#section-4.1
                if (string.IsNullOrEmpty(key.Kty))
                {
                    Options.Logger.LogError("A JSON Web Key was excluded from the key set because " +
                                            "it didn't contain the mandatory 'kid' parameter.");

                    continue;
                }

                // Create a dictionary associating the
                // JsonWebKey components with their values.
                var parameters = new Dictionary <string, string> {
                    { JsonWebKeyParameterNames.Kid, key.Kid },
                    { JsonWebKeyParameterNames.Use, key.Use },
                    { JsonWebKeyParameterNames.Kty, key.Kty },
                    { JsonWebKeyParameterNames.KeyOps, key.KeyOps },
                    { JsonWebKeyParameterNames.Alg, key.Alg },
                    { JsonWebKeyParameterNames.E, key.E },
                    { JsonWebKeyParameterNames.N, key.N },
                    { JsonWebKeyParameterNames.X5t, key.X5t },
                    { JsonWebKeyParameterNames.X5u, key.X5u }
                };

                foreach (var parameter in parameters)
                {
                    if (!string.IsNullOrEmpty(parameter.Value))
                    {
                        item.Add(parameter.Key, parameter.Value);
                    }
                }

                if (key.X5c.Count != 0)
                {
                    item.Add(JsonWebKeyParameterNames.X5c, JArray.FromObject(key.X5c));
                }

                keys.Add(item);
            }

            return(await SendCryptographyResponseAsync(new OpenIdConnectResponse {
                [OpenIdConnectConstants.Parameters.Keys] = keys
            }));
        }