0

I am testing a self-hosted Asp Net Core Web server (Kestrel), and I am struggling with the client authentication using self-signed certificates. This is my startup code

WebApplicationBuilder webBuilder = WebApplication.CreateBuilder();
var webHostBuilder = builder.WebHost;

X509Certificate2 rootCert = new X509Certificate2(hostCertFilePath, hostCertPassword);

webHostBuilder.ConfigureKestrel(o =>
{
    o.ConfigureHttpsDefaults(o =>
    {
        o.ServerCertificate = rootCert;
        o.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
    });
});

webHostBuilder.UseKestrel(o =>
{
    o.Listen(IPAddress.Parse(myHttpsEndPointIpAddr), myHttpsEndPointPort,
        listenOptions =>
        {
            listenOptions.UseHttps();
        });
    o.Listen(IPAddress.Parse(myHttpEndPointIpAddr), myHttpEndPointPort);
});

var services = webBuilder.Services;

services.AddTransient<MyCustomCertificateValidationService>();
services
    .AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(options =>
    {
        options.AllowedCertificateTypes = CertificateTypes.SelfSigned;
        options.Events = new CertificateAuthenticationEvents
        {
            OnCertificateValidated = context =>
            {
                var validationService = context.HttpContext.RequestServices
                    .GetService<MyCustomCertificateValidationService>();

                if (validationService.ValidateCertificate(context.ClientCertificate))
                {
                    context.Success();
                }
                else
                {
                    context.Fail("invalid cert");
                }

                return Task.CompletedTask;
            },
            OnAuthenticationFailed = context =>
            {
                context.Fail("invalid cert");
                return Task.CompletedTask;
            }
        };
    });

...

var app = webBuilder.Build();

app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

This my custom certification class

public class MyCustomCertificateValidationService
{
    public bool ValidateCertificate(X509Certificate2 clientCertificate)
    {
        // todo: check certificate thumbnail
        return false;
    }
}

But even if MyCustomCertificateValidationService has a method ValidateCertificate() that returns false, the controller method is still called when a client accesses the url with the route to the controller method. This is what is displayed in the log:

...
AspNetCore.Routing.EndpointRoutingMiddleware : Request matched endpoint ‘GetMyData…‘
AspNetCore.Authentication.Certificate.CertificateAuthenticationHandler : Certificate was not authenticated. Failure message: invalid cert
AspNetCore.Routing.EndpointMiddleware : Executing endpoint ‘GetMyData…‘
...

Any clue why the controller method is still called?

RickyTad
  • 281
  • 1
  • 3
  • 15
  • Why are you wanting to use client-side certificates? – Dai Nov 23 '22 at 18:57
  • What do you mean? The server has a root certificate, and the clients get client certificates generated from root. – RickyTad Nov 23 '22 at 19:04
  • I'm curious why you chose that approach considering the relative difficulty involved compared to other approaches for client authentication (e.g. OAuth2's `client_credentials`) or even HTTP Basic Auth... – Dai Nov 23 '22 at 19:23
  • Your `ValidateCertificate` returns false! Of course that leads to: `"Certificate was not authenticated. Failure message: invalid cert"`. – Poul Bak Nov 23 '22 at 20:08
  • Yes, it returns false on purpose. I would expect that the client receives a validation error response without body content, instead of Status Code 200 and the body with the response to the request. I want to test the use-case when the server does not validate the client access. The web-server has to use certificates, it is a customer requirement to use SSL mutual authentication for the application. – RickyTad Nov 23 '22 at 21:24
  • Did you place [Authorize] attribute on your controller/method? – funatparties Nov 23 '22 at 22:51
  • You are right, the [Authorize] attribute was missing, now it works as expected! Is it possible to set the equivalent of the [Authorize] attribute programmatically, for all controllers ? There is a use-case for the application that in some test environment also unauthorized calls (over http://...) should be allowed. I would prefer to use, if possible, a settings parameter to decide dynamically if http access is allowed or not, instead of "hardcode" it as [Authorize] attribute – RickyTad Nov 23 '22 at 22:57
  • This answer will likely help solve your comment question: https://stackoverflow.com/a/59645530/5517088 – Kevin Nov 24 '22 at 12:20

1 Answers1

0

"There is a use-case for the application that in some test environment also unauthorized calls (over http://...) should be allowed. I would prefer to use, if possible, a settings parameter to decide dynamically if http access is allowed or not, instead of "hardcode" it as [Authorize] attribute"

Of course you can do that. There is a handy way to implement your requirement using middleware for sure. Please try the code snippe below:

Http/Https Request Middleare Based On Environment:

public class CustomHttpHttpsRequestMiddleware
    {
        private readonly RequestDelegate next;
        public CustomHttpHttpsRequestMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
                //env = "Production";
            if (env == "Development")
            {
                await next(context);
            }
            else
            {
                if (!context.Request.IsHttps)
                {
                    context.Response.StatusCode = StatusCodes.Status400BadRequest;
                    await context.Response.WriteAsync("HTTPS required!");
                }
            }


        }
    }

Note: In application request context we are checking two important value, first if the request is secure means cIsHttps and the application environment, in Development environment we will allow http request. Therefore, other than, dev or any env based on our requirement we will reject http request.

Register Middleware on Program.cs:

app.UseMiddleware<CustomHttpHttpsRequestMiddleware>();

Note: Make sure you have followed the correct middleware order. In order to avoid short circuiting, you could place this middleware way down of your all current middleware.

Output:

enter image description here

Md Farid Uddin Kiron
  • 16,817
  • 3
  • 17
  • 43