How do I trust dotnet's dev-cert, in Linux?

Viewed 1984

I'm working through an online course on building Microservices in .NET - https://www.dotnetmicroservices.com/.

But while the instructor is running on Windows, I'm working on Linux - Linux Mint 20.1 (Ulyssa).

.NET core and .NET 5.0 are supposed to be cross-platform, and I've had no real issues in getting things to work, until now.

At this point, I have two webapi services, one providing identity services and one providing a catalog service.

The identity service is configured to use IdentityServer4, IdentityServer4.AspNetIdentity, and Microsoft.AspNetCore.Identity.UI to provide OAuth 2.0 and OpenID services.

The catalog service is configured to use Microsoft.AspNetCore.Authentication.JwtBearer, and requires a valid JWT to access the endpoints.

So, in Postman, I have a Request configured to access a simple GET endpoint in the catalog service. In the Authorization tab for the Request I choose OAuth 2.0, and enter the necessary data to make the request, including the Auth URL and Access Token URL (https://localhost:5003/connect/authorize and https://localhost:5003/connect/token).

When I click on Postman's "Get New Access Token" button, I get the login page, I log in, and get the "MANAGE ACCESS TOKENS" dialog. I can copy the Access Token or the id_token from the dialog and paste them into https://jwt.ms/ and they both look fine.

So that much is working.

But when I click on Postman's "Use Token" button, and then do a "Send" on the Request, I get an exception in the catalog service:

fail: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
      Exception occurred while processing message.
      System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'System.String'.
       ---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'System.String'.
       ---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
       ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: PartialChain
         at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
         --- End of inner exception stack trace ---
         at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
         at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
         at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
         --- End of inner exception stack trace ---
         at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
         at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(String address, IDocumentRetriever retriever, CancellationToken cancel)
         at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
         --- End of inner exception stack trace ---
         at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
         at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()

It looks like the problem is that dotnet's developer certificate isn't trusted.

The usual method for doing this is to use the dotnet cli:

dotnet dev-certs https --trust

But on Linux, this returns:

Trusting the HTTPS development certificate was requested. Trusting the certificate on Linux distributions automatically is not supported. For instructions on how to manually trust the certificate on your Linux distribution, go to https://aka.ms/dev-certs-trust

So I went to https://aka.ms/dev-certs-trust, and it contains a lot of stuff, most of which clearly isn't relevant. The section on "Ubuntu trust the certificate for service-to-service communication" seemed apropos:

sudo dotnet dev-certs https -ep /usr/local/share/ca-certificates/aspnet/https.crt --format PEM
sudo update-ca-certificates

And while that ran without issue, it didn't not fix the problem.

As for the rest, the page says "Establishing trust is browser specific. The following sections provide instructions for the Chromium browsers Edge and Chrome and for Firefox."

And I don't see how either is relevant to the problem at hand.

What is dotnet core doing, when it is verifying certificates? What do I need to do to mark the dotnet developer's certificate as trusted?

2 Answers

Either you have to stop using HTTPS and use HTTP instead between the services.

I assume you use containers to host the services? and For HTTPS to work, you need a domain name and mapping that domain name to the containers that match the certificate and using localhost (the dev cert) to map to containers I feel is a bad idea.

Better to get a real certificate that you then using different techniques map to the containers. Like editing the hosts file to map the domain to the IP-addresses of your services.

In dev/production, I usually host my HTTPS certificate in Azure Key Vault and download and add it to ASP.NET Core at startup time.

When I work locally, I have created a certificate for the domain indentityservice.local, using the mkcert tool

mkcert.exe -install

mkcert -pkcs12 -p12-file identityservice.local.pfx identityservice.local

Then I copy the generated identityservice.local.pfx and add it to my project. Then if all works, I can contact my local IdentityServer using https://identityService.local

I also do add this entry to my local hosts file at C:\Windows\System32\drivers\etc

127.0.0.1 identityService.local

The following works on Linux:

  • Generate a local CA.

    mkcert -install

  • Generate the certificate to be used in your service

    mkcert -pkcs12 -p12-file myservice.local.pfx myservice.local

  • Add an entry for myservice.local in /etc/hosts

    127.0.0.1 myservice.local

  • Use the p12 certificate in your dotnet service

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Hosting;
    
    namespace myservice
    {
      public class Program
      {
        public static void Main(string[] args)
        {
          CreateHostBuilder(args).Build().Run();
        }
    
        public static IHostBuilder CreateHostBuilder(string[] args) =>
          Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
    
    #if DEBUG
              webBuilder.UseStartup<Startup>().UseKestrel(options =>
              {
                options.Listen(IPAddress.Loopback, 5001, listenOptions =>
                {
                  listenOptions.UseHttps("./myservice.local.pfx", "changeit");
                });
              }).UseUrls("https://myservice.local:5001");
    
    #else
                        webBuilder.UseStartup<Startup>();
    #endif
            });
      }
    }

Related