asp.net-coresslkestrel-http-server

Kestrel ignores certificate configuration in code


Problem Statement

Kestrel should listen to http://localhost:50008 and https://localhost:50009. The certificate should be loaded from an external source at startup (in this example it's just loaded from files). Unfortunately, it only works if loaded from appsettings.json (as described in Built in way to configure HTTPS with certificate in kestrel server) and not if configured in code (as described in ASP.NET Core application setup production SSL certificate).

Experiments

Setup

The certificate is issued by Letsencrypt (production, not staging) for *.mydomain.xyz. The A record for www.mydomain.xyz points to 127.0.0.1.

Experiment 1: Configure Kestrel in appsettings.json

Program.cs

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => Results.Ok("hello"));
app.Run();

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Debug"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://*:50009",
        "Certificate": {
          "Path": "C:\\Users\\myusername\\certs\\acme_certificate_pem.crt",
          "KeyPath": "C:\\Users\\myusername\\certs\\acme_certificate_private_key_pem.key"
        }
      },
      "Http": {
        "Url": "http://*:50008"
      }
    }
  }
}

debug output

info: Microsoft.Hosting.Lifetime[14]     
      Now listening on: http://[::]:50008
info: Microsoft.Hosting.Lifetime[14]                 
      Now listening on: https://[::]:50009

curl (browser also shows correct cert for *.mydomain.xyz)

$ curl http://www.mydomain.xyz:50008
"hello"
$ curl https://www.mydomain.xyz:50009
"hello"

Experiment 2: Configure Kestrel in Program.cs

Program.cs

var builder = WebApplication.CreateBuilder(args);

var certPath = "C:\\Users\\myusername\\certs\\acme_certificate_pem.crt";
var keyPath = "C:\\Users\\myusername\\certs\\acme_certificate_private_key_pem.key";
var certificate = X509Certificate2.CreateFromPemFile(certPath, keyPath);

builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
    serverOptions.Listen(IPAddress.Any, 50008);
    serverOptions.Listen(IPAddress.Any, 50009, listenOptions =>
    {
        listenOptions.UseHttps(certificate);
    });
});

var app = builder.Build();
app.MapGet("/", () => Results.Ok("hello"));
app.Run();

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Debug"
    }
  }
}

debug output

info: Microsoft.Hosting.Lifetime[14]        
      Now listening on: http://0.0.0.0:50008
info: Microsoft.Hosting.Lifetime[14]                 
      Now listening on: https://0.0.0.0:50009 

curl

$ curl http://www.mydomain.xyz:50008
"hello"
$ curl https://www.mydomain.xyz:50009
curl: (35) schannel: failed to receive handshake, SSL/TLS connection failed

Experiment 3: Configure Kestrel in Program.cs without Cert

Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
    serverOptions.Listen(IPAddress.Any, 50008);
    serverOptions.Listen(IPAddress.Any, 50009, listenOptions =>
    {
        listenOptions.UseHttps();
    });
});

var app = builder.Build();
app.MapGet("/", () => Results.Ok("hello"));
app.Run();

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Debug"
    }
  }
}

debug output

info: Microsoft.Hosting.Lifetime[14]        
      Now listening on: http://0.0.0.0:50008
info: Microsoft.Hosting.Lifetime[14]                 
      Now listening on: https://0.0.0.0:50009 

curl (uses development certificate, issued to localhost)

$ curl http://www.mydomain.xyz:50008
"hello"
$ curl https://www.mydomain.xyz::50009 -k
"hello"
$ curl https://www.mydomain.xyz::50009
curl: (60) schannel: SNI or certificate check failed: SEC_E_WRONG_PRINCIPAL (0x80090322) - Der Zielprinzipalname ist falsch.
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Solution

  • Use .pfx and it will work. The reason is a technical limitation of Windows.