Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

don't expose a localhost https endpoint when there is no developer certificate #32361

Closed
tmds opened this issue May 3, 2021 · 37 comments
Closed
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved

Comments

@tmds
Copy link
Member

tmds commented May 3, 2021

By default kestrel exposes an HTTPS endpoint for localhost using a development certificate.

On Linux, the dotnet dev-certs https --trust doesn't work well leading to a bricked development experience.

It would be nice if there was a global way to opt-out of the HTTPS localhost bind.

One option may be to not bind localhost HTTPS when there is no development certificate.

I think the reason for having the HTTPS endpoint is to be secure by default, though not having it on localhost does not make things insecure by default.

@Tratcher @halter73 @jkotalik what do you think?

@Tratcher
Copy link
Member

Tratcher commented May 3, 2021

cc: @javiercn

leading to a bricked development experience.

Can you say more about the failures you hit?

One option may be to not bind localhost HTTPS when there is no development certificate.

This should already be the default behavior. However, there's a gap between providing a dev cert and having anything trust it. Can you confirm there is no default https endpoint if you remove the dev cert?

I think the reason for having the HTTPS endpoint is to be secure by default, though not having it on localhost does not make things insecure by default.

The main goal is to make the dev environment closely match the production environments where HTTPS should be enabled. Many features behave differently between http and https.

You can suppress the default https endpoint by providing an http endpoint when running them app.

@BrennanConroy
Copy link
Member

However, there's a gap between providing a dev cert and having anything trust it. Can you confirm there is no default https endpoint if you remove the dev cert?

Triage: Maybe we should avoid installing the dev cert by default on Linux. If you want the dev cert it is easy to install (although harder to get it actually working).

cc @javiercn @blowdart @halter73

@tmds
Copy link
Member Author

tmds commented May 3, 2021

Can you say more about the failures you hit?

I've been clicking the ignore certificate in Firefox as long as I remember.

I'm now playing around with tye and there are some manual steps in the repo that are based on some stackoverflow: https://github.com/dotnet/tye/blob/main/docs/tutorials/hello-tye/00_run_locally.md#certificate-is-invalid-exception-on-linux.

Like with the ASP.NET apps, this breaks UX. The steps here are distro specific. Changes to the config files can't be checked into source control. And root is required to make things work.

This should already be the default behavior. However, there's a gap between providing a dev cert and having anything trust it. Can you confirm there is no default https endpoint if you remove the dev cert?

If the dev cert is removed, the server fails to start:

crit: Microsoft.AspNetCore.Server.Kestrel[0]
      Unable to start Kestrel.
      System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.
      To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'.
      For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
         at Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(ListenOptions listenOptions, Action`1 configureOptions)
         at Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(ListenOptions listenOptions)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(IEnumerable`1 listenOptions, AddressBindContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)

You can suppress the default https endpoint by providing an http endpoint when running them app.

I'm looking for a way to opt out for all my apps at once, rather than have to change it per app.
It could be through removing the dev cert, but that doesn't currently work.

Maybe we should avoid installing the dev cert by default on Linux.

Yes, I think that makes sense.

@Tratcher
Copy link
Member

Tratcher commented May 3, 2021

I've been clicking the ignore certificate in Firefox as long as I remember.

Ok, that's a trust issue. We know that story needs work.

AddressBinder.AddressesStrategy means that an https address is being passed in manually, it's not using the defaults. Is it in the launchsettings.json?

@tmds
Copy link
Member Author

tmds commented May 4, 2021

Is it in the launchsettings.json?

Yes: "applicationUrl": "https://localhost:5001;http://localhost:5000".
So this is part of the template?
Is this file for Visual Studio to find out the url to launch?
What part of dotnet run ends up reading this file and configuring ASP.NET?

@Tratcher
Copy link
Member

Tratcher commented May 4, 2021

Yes it's in the template, and only includes https if that checkbox is marked.

//#if(NoHttps)
"applicationUrl": "http://localhost:5000",
//#else
"applicationUrl": "https://localhost:5001;http://localhost:5000",

VS and dotnet run read this and set environment variables when starting the app. You only get the server default behavior if you remove this line.

@tmds
Copy link
Member Author

tmds commented May 4, 2021

@Tratcher can we special case this in Kestrel?

Something like: ignore the "https" binding for "localhost" when the default development certificate is missing and the environment is "Development"?

@javiercn
Copy link
Member

javiercn commented May 4, 2021

@BrennanConroy we are not changing the HTTPS experience for Linux to make it different from other platforms. We already provide options to people who don't want to install the https dev cert or use https within their templates.

The certificate installation can be skipped on first run by setting the following environment variable DOTNET_GENERATE_ASPNET_CERTIFICATE=0, the templates all support the --no-https flag and we offer documentation on how to setup trust on Linux here for all browsers and for the system trust root.

@javiercn
Copy link
Member

javiercn commented May 4, 2021

On Linux, the dotnet dev-certs https --trust doesn't work well leading to a bricked development experience.

You can still browse to the site, and you can add a permanent exception if Firefox for localhost:5001 if you decide not to trust the certificate using the instructions provided in the docs.

@tmds
Copy link
Member Author

tmds commented May 4, 2021

we are not changing the HTTPS experience for Linux to make it different from other platforms.

The development experience for Linux for HTTPS is not the same as for other platforms: on Windows and macOS it works out-of-the box, on Linux it does not.

You can still browse to the site, and you can add a permanent exception if Firefox for localhost:5001 if you decide not to trust the certificate using the instructions provided in the docs.

I created this issue when confronted with the steps in https://github.com/dotnet/tye/blob/main/docs/tutorials/hello-tye/00_run_locally.md#certificate-is-invalid-exception-on-linux. These steps describe a workaround and not a real solution. I've made a suggestion to improve the situation for tye here: dotnet/tye#1025.

the certificate installation can be skipped

For general ASP.NET I think it would be an interesting option to not perform the https bind when there is no developer cert. This now gives an error, which could become a warning.

That makes it possible for the individual developer to opt-out. So when he checks out some repo that used the template with the default https option, that code runs for him too.

@javiercn
Copy link
Member

javiercn commented May 4, 2021

The development experience for Linux for HTTPS is not the same as for other platforms: on Windows and macOS it works out-of-the box, on Linux it does not.

That is not the case, you are complaining about the certificate not being trusted and what happens is:

  • On all cases you need to run dotnet dev-certs https --trust
    • On windows and Mac OS we take the steps to add them to the machine trust store.
    • On Linux we point you to the docs article with instructions on how to make those changes.

dotnet uses the machine trust root for determining whether a certificate is trusted or not. On windows we use the machine store, on Mac OS we use the system keychain. On Linux there is no centralized trust root and the different trust roots change across distros and browsers.

Browsers on Mac and Windows rely on the system trust root for determining if the certificate is valid, Firefox does too if you provide the appropriate setting (which we cover in docs).

On Linux we provide instructions on how to trust the certificate in all three major browsers (Edge, Chrome, Firefox).

In all cases unless the user runs dotnet dev-certs https --trust the experience is the same, the app launches and runs, and the browser will prompt a warning pointing out that the certificate is not trusted and the developer can choose to bypass the warning or go to the command line, and run the command.

When they run the command we provide all that is necessary on all OS to work. On Windows and Mac we rely on the system trust roots, on Linux we point you to the steps you need to follow to achieve the same result. In all cases we tell you the manual steps to achieve a trusted dev-cert across the different browsers and OSs. The only difference is that some of the steps are automatic on Windows and Mac and require a few steps that you perform as a one time gesture on Linux to make it happen.

While I understand your frustration about the experience on Linux, I don't think that's a good enough reason to change the default experience. The experience is not as good as on Windows or Mac because the HTTPS experience is in general "harder" on Linux, however even in the Linux case you can be up and running in 5 minutes by following the instructions we provide.

As for this issue on tye, this is caused by a "long standing bug" that was fixed on Open SSL at least 6 month ago. Recent versions of most popular distros contain newer versions of Open SSL that contain this fix.

HTTPS by default is a fundamental development default for several years and is not something we have plans to change. While I understand it might make things more difficult in some more advanced scenarios and require manual steps to solve, that doesn't mean we should switch defaults at the cost of offering a different experience across OS flavors.

If the issue is with the certificate not being trusted in the container image, we can instead change how we build the image to add the certificate to the trust store for the distro as part of building the image, we don't have to change the experience for everyone not using containers on Linux.

If we ever see a convergence on Linux to provide a common system trust root, we will likely consider adopting that within the dev-certs tool itself. However, given that it is not the case today we provide docs for how you can do it yourself.

Our "limit" with the dev-certs tool for now is that we support the system trust root, and offer docs for how to setup trust in other circumstances.

I hope this helps bring some clarity about our reasoning.

@Tratcher
Copy link
Member

Tratcher commented May 4, 2021

@Tratcher can we special case this in Kestrel?

Something like: ignore the "https" binding for "localhost" when the default development certificate is missing and the environment is "Development"?

We want to avoid special cases like that in the runtime, they're hard to distinguish from a real issue that you need to know about. In this case the app is explicitly telling us to bind to https but we can't and tell you accordingly.

It works as expected when you remove the https address or the launchsettings file, right?

@tmds
Copy link
Member Author

tmds commented May 4, 2021

As for this issue on tye, this is caused by a "long standing bug" that was fixed on Open SSL at least 6 month ago. Recent versions of most popular distros contain newer versions of Open SSL that contain this fix.

Are you suggesting there is a better way to do this now?

The experience is not as good as on Windows or Mac because the HTTPS experience is in general "harder" on Linux, however even in the Linux case you can be up and running in 5 minutes by following the instructions we provide.

I spent more than 5 minutes and I wasn't able to make it work. I use Fedora, not Ubuntu:

The path in the preceding command is specific for Ubuntu. For other distributions, select an appropriate path or use the path for the Certificate Authorities (CAs).

It works as expected when you remove the https address or the launchsettings file, right?

Yes. Though I don't think I should push that as a change in version control if the HTTPS dev cert works fine for other collaborators.

HTTPS on localhost isn't providing any real security. Instead of causing me issues, I wish there was a way to opt-out.

@blowdart
Copy link
Contributor

blowdart commented May 4, 2021

HTTPS on localhost isn't providing any real security. Instead of causing me issues, I wish there was a way to opt-out.

As @javiercn points out there is an opt-out, --no-https when create the project or unticking the box in Visual Studio

HTTPS on localhost is not there for security, it's there to emulate what a real deployment looks like, to allow you to use oauth and other federated identities, to enable samesite cookies to work in the same way they would in production. Removing it, even if it's "just" on linux makes that experience impossible, and gives you surprises on deployment, something we want to avoid.

@Tratcher
Copy link
Member

Tratcher commented May 4, 2021

I wish there was a way to opt-out.

launchsettings.json is the opt-in/out mechanism. If your team has opted in for a project then the only way for you to opt out is by providing a higher priority config like at the command line:

dotnet run --urls http://localhost:5000

@blowdart blowdart added the ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. label May 4, 2021
@ghost ghost added the Status: Resolved label May 4, 2021
@tmds
Copy link
Member Author

tmds commented May 4, 2021

Having the HTTPS has some advantages. Having no method to trust the certificate brings disadvantages.

Because the certificate trusting is an issue for Linux devs, the request here is to opt-out at the machine developer level.

Based on your feedback, I'll close the issue.

@tmds tmds closed this as completed May 4, 2021
@tmds
Copy link
Member Author

tmds commented May 7, 2021

@blowdart @Tratcher @javiercn I investigated further what is needed to trust the ASP.NET developer certificate on Fedora.

Fedora does not want to accept a self-signed certificate in the system store. It needs a CA certificate.

I wrote a console app that generates a self-signed CA certificate, and uses that to sign the ASP.NET HTTPS development certificate: https://github.com/tmds/linux-dev-certs/.

I'll look into adding this to the dotnet dev-certs tool.

@javiercn
Copy link
Member

javiercn commented May 7, 2021

@tmds thanks for the research.

Unfortunately that's not something we are willing to accept. Creating a CA certificate on the user machine opens up the machine to way more liability and is not something that we want to manage and maintain.

Can you post the fedora version you are using as well as the openssl version with openssl -v

@tmds
Copy link
Member Author

tmds commented May 7, 2021

Unfortunately that's not something we are willing to accept. Creating a CA certificate on the user machine opens up the machine to way more liability and is not something that we want to manage and maintain.

There is no need to store the CA key, so I think that mitigates the security issues?

Can you post the fedora version you are using as well as the openssl version with openssl -v

I'm on Fedora 33. Fedora releases are only supported for 1 year, so I'll be moving to F34 in the next half year or so.

@javiercn
Copy link
Member

javiercn commented May 7, 2021

@tmds what's the openssl version

@tmds
Copy link
Member Author

tmds commented May 7, 2021

$ openssl version
OpenSSL 1.1.1k  FIPS 25 Mar 2021

@tmds
Copy link
Member Author

tmds commented May 10, 2021

@javiercn @blowdart you are not a fan of using CA certs. I would prefer to do without also. If you can make it work without them, that would be great. If you cannot, consider using them instead of having something broken.

I can make a PR to add the using a CA cert, or try out some things if you have suggestions.

@javiercn
Copy link
Member

@tmds I'm not sure what's happening in fedora, this works on Ubuntu following the instructions in the docs.

I want to clarify some things:

  • We don't plan to have some different setup for certs in Linux compared to other platforms.
  • We don't plan to install a CA authority on the machine and deal with the added complexity of keeping a two certificate setup working.
  • We've provided details on how to trust the dev cert to work in dotnet-to-dotnet communications as well as with how to setup the certificate trust across all browsers.
  • We produced a sample for Ubuntu/Debian and we expect steps to be similar in other distros.
    • Configuring the trust in Chrome/Edge/Firefox should be similar across distros since they use their own store.
    • Configuring the trust for dotnet-to-dotnet communications depends on Open SSL, the instructions for setting the list of trusted certs I believe are distro specific.
  • If a distro is not able for some reason to support a self-issued, self-signed certificate we consider that a limitation/bug on the distro and not something we need to fix/solve.
  • If our default setup doesn't work in a specific Linux distro we suggest going the manual way and setting up your own self-signed cert with your own development CA at your own risk.

I understand this is not what you are expecting to hear, however the change that you are suggesting increases the complexity for us in either having to support a more complex setup across all OSses or having to support a special setup for Linux machines. In addition to that, we don't have the resources to take on the challenge of trusting certs across al Linux distributions and to keep it working over time given the large variability in this space.

We have provided fairly generic instructions on how to do this in popular distros like Debian/Ubuntu, it should be possible to adapt those instructions for other distros. If our instructions are not enough, we suggest looking for help on the specific distro channels about how to diagnose what is not working correctly and how it can be made to work, we simply don't have the capacity nor the expertise for such task.

We believe that the cert we generate is completely valid and should be usable in all the platforms (in fact, it works fine on Ubuntu as per the image below) and that if it can't be successfully trusted in a given platform it means there is a bug on the platform.

image

@tmds
Copy link
Member Author

tmds commented May 10, 2021

if it can't be successfully trusted in a given platform it means there is a bug on the platform.

Have you considered it may not be a bug, but a decision to not accept non-CA certificates in the system CA store?

popular distros like Debian/Ubuntu

It doesn't work for the popular Fedora distribution.

I just tried to add the self-signed certificate to the system CA store on the Microsoft Alpine image, and it did not work either.

@javiercn
Copy link
Member

@tmds I downloaded the latest Fedora and followed instructions similar to the ones that we provide. I was able to make it work in all scenarios (as per the image below). I essentially created two APIs, front end and backend and used HttpClient to call from frontend to backend and accessed frontend from all browsers.

image

@javiercn
Copy link
Member

javiercn commented May 10, 2021

It required a bit of trial and error and adjustment to fedora specific stuff, but in the end it worked. I don't have a "validated" script or set of steps, however its clearly possible to setup, and the instructions are mostly the same.

For chrome/edge I did the exact same thing as for Ubuntu. Export the certificate and use the commands below to trust it.

certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n localhost -i ./localhost.crt
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n localhost -i ./localhost.crt

This has not always work correctly and I ended up removing $HOME/.pki/nssdb before I got it to work (AFAIK thats chrome cert store and it will recreate it when its not there, so do it at your own risk).

For Firefox I had to follow the steps described here https://shivering-isles.com/Manage-Firefox-on-Fedora to setup the policy because this is distro specific for Fedora.

For open SSL I copied the cert to the locations described here

  • /etc/pki/ca-trust/
  • /usr/share/pki/ca-trust-source/

And ran update-ca-trust

I closed the console by accident so I don't know the exact steps I took, It might have taken some trial and error before it. I think its possible to do this the trust command, but I haven't tried it out.

@tmds
Copy link
Member Author

tmds commented May 10, 2021

@javiercn awesome you got it to work! I'm interested in particular in how you make the dotnet-to-dotnet work.

This is what happens for me (on my Fedora 32 desktop):

dotnet dev-certs https --clean
dotnet dev-certs https -ep /tmp/aspnet.crt --format PEM
sudo cp /tmp/aspnet.crt /etc/pki/ca-trust/
sudo cp /tmp/aspnet.crt /usr/share/pki/ca-trust-source/
sudo update-ca-trust 

(edit: added missing --format PEM to avoid copy-paste of something broken)

Run a web app:

dotnet new web -o web
cd web
dotnet run

Create a console app that uses HttpClient:

dotnet new console -o console

Program.cs:

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace console
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var client = new HttpClient();
            await client.GetAsync("https://localhost:5001");
        }
    }
}

And now run:

$ dotnet run
Unhandled exception. 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) in /_/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 592
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm) in /_/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 421
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs:line 111
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs:line 135
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs:line 1271
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs:line 1332
   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs:line 535
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs:line 858
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs:line 30
   at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs:line 682
   at console.Program.Main(String[] args) in /tmp/console/Program.cs:line 12
   at console.Program.<Main>(String[] args)

Also:

$ curl https://localhost:5001
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.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.

@javiercn
Copy link
Member

@tmds I believe your issue is either with the certificate format or with the openssl version.

I believe you need to use PEM format (look at the updated command below). You shouldn't need to clean the certificate either (that will force a new one to be created)

dotnet dev-certs https --clean
dotnet dev-certs https -ep /tmp/aspnet.crt --format PEM
sudo cp /tmp/aspnet.crt /etc/pki/ca-trust/
sudo cp /tmp/aspnet.crt /usr/share/pki/ca-trust-source/
sudo update-ca-trust 

In addition to that, if those changes don't work I would try the sudo trust anchor /tmp/aspnet.crt since that seems to be fedora specific.

There is a long-standing issue in openssl that was solved on 1.1.1h and onwards and our docs reflect that. I suspect you might be running into that, however its not fully clear since you mentioned you are using 1.1.1k, so you might have updated manually.

Other than that, I can only think of the openssl installation not having the certificate in the CA bundle.

I want to be very clear that I'm not an expert on this area (Linux tooling around certs) and that it is incredibly frustrating because it is trial-and-error every time I have to do it. That said, the things that have worked for me to troubleshoot things are:

  • Identify what openssl is using for determining if the cert is trusted.
    • openssl version -d
  • Use openssl to check if the cert is present there
    • openssl crl2pkcs7 -nocrl -certfile /etc/pki/tls/certs/ca-bundle.trust.crt | openssl pkcs7 -print_certs -text -noout | grep localhost --context 10
  • Trial and error with different distro commands up until you get the cert there :)
    • I've always done this through the "system" trust root for the distro, but there might be alternative ways.
    • I found out that the certificate format matters, tools don't give you meaningful visible errors when they fail here, they just silently do nothing.
    • I believe you need to use PEM format for it to work though (--format PEM on dotnet dev-certs)
    • I don't know if the extension when running some commands matters, I've had success with .crt on the cert name, although again this is just based on trial and error evidence.
    • If its there and dotnet doesn't like it, I'm not sure what the right way to go about it is. I would ask the runtime folks at that point.

@tmds
Copy link
Member Author

tmds commented May 10, 2021

I believe your issue is either with the certificate format

I tried again, adding the missing --format PEM didn't make things work.

sudo trust anchor /tmp/aspnet.crt

This didn't have an effect either.

There is a long-standing issue in openssl that was solved on 1.1.1h

My Fedora 32 machine doesn't meet this version requirement.

openssl version -d

On my Fedora 33 machine:

$ openssl version -d
OPENSSLDIR: "/etc/pki/tls"
/tmp/console$ openssl version
OpenSSL 1.1.1k  FIPS 25 Mar 2021

The error message is different:

 ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot

Use openssl to check if the cert is present there

For some reason I have two:

$ openssl crl2pkcs7 -nocrl -certfile /etc/pki/tls/certs/ca-bundle.trust.crt | openssl pkcs7 -print_certs -text -noout | grep localhost --context 10
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            94:04:7c:82:87:03:0e:f1
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=localhost
        Validity
            Not Before: May 10 20:53:11 2021 GMT
            Not After : May 10 20:53:11 2022 GMT
        Subject: CN=localhost
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:da:d3:8e:0e:88:e3:e6:a7:43:5b:36:99:b7:76:
                    ee:c9:50:5e:94:85:f7:69:4c:2e:a8:e2:32:62:c8:
                    ab:53:2c:ca:ac:24:57:3d:07:2c:82:cd:32:86:db:
                    9e:31:4e:74:e8:8d:a0:15:02:2f:ca:dd:06:82:7f:
                    8f:54:db:07:e9:12:82:9e:99:0a:70:01:bb:af:e6:
                    94:4a:31:ab:45:bb:82:ce:4b:d2:23:bd:d3:bc:ef:
--
                    4f:75
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: critical
                TLS Web Server Authentication
            X509v3 Subject Alternative Name: critical
                DNS:localhost
            1.3.6.1.4.1.311.84.1.1: 
                .
    Signature Algorithm: sha256WithRSAEncryption
         6d:5e:1c:77:5f:f5:77:8a:c6:22:38:bb:2c:af:cf:61:1a:bc:
         5d:5a:53:c5:b8:1d:6b:b7:c9:0a:67:82:c9:05:ff:8a:ac:8c:
         d5:18:bf:f0:e8:10:51:7c:d2:a7:98:e0:8a:a6:96:03:b7:e1:
         2e:a1:e5:27:cb:9a:17:92:15:9e:07:e0:3c:70:f3:ea:de:b0:
         9c:30:8b:c4:8e:e9:55:b9:09:a7:0c:49:a1:b8:41:1b:92:b4:
         8d:de:a2:52:75:48:55:12:92:a5:a6:c6:3d:46:82:10:f6:3a:
         15:6f:66:12:4c:13:9f:fc:7d:7c:ef:b0:96:9e:46:f8:12:61:
--
         52:2c:62:f7:70:63:d9:39:bc:6f:1c:c2:79:dc:76:29:af:ce:
         c5:2c:64:04:5e:88:36:6e:31:d4:40:1a:62:34:36:3f:35:01:
         ae:ac:63:a0

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            8b:c6:cd:12:ed:a5:3b:88
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=localhost
        Validity
            Not Before: May  4 14:21:57 2021 GMT
            Not After : May  4 14:21:57 2022 GMT
        Subject: CN=localhost
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:b0:a2:8c:a4:98:6b:ee:ea:eb:63:c1:a3:66:01:
                    06:6d:8a:bb:bf:ae:bf:54:02:2b:97:3a:8b:b0:24:
                    ed:05:6f:37:d1:0f:2b:e2:10:56:fc:37:0f:41:da:
                    8a:b3:e5:c5:ec:5e:e1:c0:c0:f9:60:f7:11:16:16:
                    a5:e5:57:47:b3:00:d8:0d:c5:de:2c:51:e3:35:fb:
                    b7:67:18:3c:fb:4b:ab:83:06:5d:90:ee:da:6c:5f:
--
                    dc:ff
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: critical
                TLS Web Server Authentication
            X509v3 Subject Alternative Name: critical
                DNS:localhost
            1.3.6.1.4.1.311.84.1.1: 
                .
    Signature Algorithm: sha256WithRSAEncryption
         3d:10:62:46:e9:ce:8e:64:6f:69:b5:cc:7c:f6:87:6c:31:b1:
         d6:49:0a:f7:04:4f:63:fb:60:87:2c:bb:a4:2f:62:c7:68:6e:
         0c:d1:61:61:41:6d:0e:fb:13:41:75:31:43:38:87:19:87:fb:
         0d:db:7a:67:2f:20:b4:f0:0e:13:10:4c:71:63:b5:92:d1:34:
         b4:70:1d:a1:1f:19:04:88:84:5b:a5:bd:5b:c1:30:c6:1f:57:
         26:20:6f:4d:ec:30:9c:7d:95:44:b0:0f:66:81:96:0d:ba:48:
         07:db:30:e1:40:bb:cc:8c:d4:cc:f7:e9:1a:69:ce:67:d9:84:

I tried these steps in a Fedora 34 container image, this is what I get there:

sh-5.1# openssl crl2pkcs7 -nocrl -certfile /etc/pki/tls/certs/ca-bundle.trust.crt | openssl pkcs7 -print_certs -text -noout | grep localhost --context 10
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 7327724817460649868 (0x65b14ee5c48e5f8c)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=localhost
        Validity
            Not Before: May 10 21:01:42 2021 GMT
            Not After : May 10 21:01:42 2022 GMT
        Subject: CN=localhost
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:b7:13:d9:e9:1b:f9:30:3a:b3:29:02:3b:8a:e4:
                    43:95:ea:80:21:7f:f4:96:f7:c2:ca:50:10:b2:06:
                    d1:44:65:0f:fd:9a:cc:34:4b:74:51:66:51:f2:0e:
                    9c:e7:d8:99:6a:4f:f3:53:72:91:cc:ab:c3:fc:ec:
                    a0:fc:6c:d4:ca:95:0c:6c:8e:cf:24:fc:0d:85:d3:
                    a0:a5:ac:b5:0b:26:0a:8e:30:1a:81:e4:73:de:2d:
--
                    6c:b5
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: critical
                TLS Web Server Authentication
            X509v3 Subject Alternative Name: critical
                DNS:localhost
            1.3.6.1.4.1.311.84.1.1: 
                .
    Signature Algorithm: sha256WithRSAEncryption
         34:a2:ac:06:df:1a:41:8a:8d:52:ea:42:40:20:56:7f:1b:1a:
         12:4e:35:1e:60:26:58:b1:3c:14:f6:bf:98:5f:ed:74:1f:a8:
         67:90:33:5b:64:30:13:6b:a5:ef:3e:6e:cd:d9:0f:bb:ac:22:
         30:d2:45:27:e8:43:d2:21:f9:68:70:b4:f2:84:11:92:e7:73:
         90:ed:0d:b1:5f:b2:73:2c:ed:27:cd:65:33:75:2c:63:4b:50:
         01:26:a6:b7:0b:d5:ab:4d:e7:62:c6:0e:ff:19:3e:d2:30:f3:
         99:77:e0:c5:7f:b7:3d:34:41:62:b3:6d:05:49:f8:41:65:e7:
sh-5.1# curl -v https://localhost:5001
*   Trying ::1:5001...
* Connected to localhost (::1) port 5001 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: self signed certificate
* Closing connection 0
curl: (60) SSL certificate problem: self signed certificate
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.
sh-5.1# dotnet run
Unhandled exception. 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: UntrustedRoot
   at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception) in /_/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 592
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm) in /_/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 421
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs:line 111
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs:line 135
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs:line 1271
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs:line 1332
   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs:line 535
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs:line 858
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs:line 30
   at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs:line 682
   at console.Program.Main(String[] args) in /tmp/console/Program.cs:line 12
   at console.Program.<Main>(String[] args)

@javiercn can you try and figure out again how you made it work?

@tmds
Copy link
Member Author

tmds commented May 11, 2021

@omajid can you please try the steps outlined in #32361 (comment)? In particular the ones that configure OpenSSL. Those should make HttpClient and curl trust the developer certificate used by ASP.NET Core.

You need a recent version of Fedora (33+) so you have openssl 1.1.1h+. I am not able to make it work, but maybe pebkac, or maybe I messed up my machine.

@javiercn
Copy link
Member

I just found the missing piece and put everything in script form

echo 'pref("general.config.filename", "firefox.cfg");
pref("general.config.obscure_value", 0);' > ./autoconfig.js

echo '//Enable policies.json
lockPref("browser.policies.perUserDir", false);' > firefox.cfg

echo "{
    \"policies\": {
        \"Certificates\": {
            \"Install\": [
            	\"aspnetcore-localhost-https.crt\"
            ]
        }
    }
}" > policies.json

dotnet dev-certs https -ep "${HOME}/work/linuxhttps/localhost.crt" --format PEM

sudo mv autoconfig.js /usr/lib64/firefox/
sudo mv firefox.cfg /usr/lib64/firefox/
sudo mv policies.json /usr/lib64/firefox/distribution/
mkdir -p ~/.mozilla/certificates
cp ./localhost.crt ~/.mozilla/certificates/aspnetcore-localhost-https.crt

# Trust Edge/Chrome
certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n localhost -i ./localhost.crt
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n localhost -i ./localhost.crt

# Trust dotnet-to-dotnet (.pem extension is important here)
sudo cp localhost.crt /etc/pki/tls/certs/localhost.pem
sudo update-ca-trust

# Cleanup
rm localhost.crt

The trick was that the cert needs to go into the openssl folder and not on the system ones.

@tmds
Copy link
Member Author

tmds commented May 11, 2021

I just found the missing piece and put everything in script form

@javiercn great! I'll give these instructions a try.

@javiercn
Copy link
Member

javiercn commented May 11, 2021

@tmds this time around I've done it on a VM with checkpoints, so I just ran the script. It should at the very least, work for you on a clean VM using fedora 34

There is also https://github.com/javiercn/linuxhttps which I've used to test it, run the two projects and go to https://localhost:5001/weatherforecast

@tmds
Copy link
Member Author

tmds commented May 12, 2021

The steps work for me on Fedora.

The system tools (like curl and wget) don't trust the site though. They are less permissive than .NET.

Some suggestions to improve the script:

  • It would be better if the Firefox trusting doesn't need sudo and doesn't make configuration changes that affect all users.
  • The "-ep" path needs an update.
  • Include the username in the certificate name to avoid conflict with other users at the system location.
  • As far as I can tell, the "C,," overwrites the "P,," so only "C,," is needed.

I've applied these suggestions in the script below. Feel free to take the bits you like.

Besides updating the docs, it would be even better if we can make the --trust option work when this tool is used on Fedora. I can look into that.

RHEL7 and RHEL8 don't meet the OpenSSL version requirement. Do you know what bug was fixed in OpenSSL 1.1.1h+?

dnf list installed nss-tools >/dev/null 2>&1 ||
  (echo "Installing dependencies." && \
  sudo dnf install -y nss-tools)

echo "Exporting developer certificate."
DEV_CERT="$HOME/aspnet-$USER.pem"
dotnet dev-certs https -ep "$DEV_CERT" --format PEM

CERT_DB=$(echo "$HOME/.mozilla/firefox/*.default-release")
[ -d "$CERT_DB" ] && echo "Adding certificate to Firefox default profile certificates." && \
  certutil -d "$CERT_DB" -A -t "C,," -n localhost -i "$DEV_CERT"

CERT_DB="$HOME/.pki/nssdb"
[ -d "$CERT_DB" ] && echo "Adding certificate to Edge/Chrome certificates." && \
  certutil -d "$CERT_DB" -A -t "C,," -n localhost -i "$DEV_CERT"

echo "Adding certificate to System certificates."
sudo cp "$DEV_CERT" /etc/pki/tls/certs
sudo update-ca-trust

rm "$DEV_CERT"

@javiercn
Copy link
Member

@tmds I'm glad that we sorted this out

The openssl issue that was fixed is this one:
openssl/openssl#1418

WRT to Firefox the problem with not setting it up at the system level is that it might impact automation tools. We could do it at the user profile level, however it will have to be "smarter" than looking at the *.default-release. There is a profiles.ini file or something like that and we would need to find the default profile in there.

Include the username in the certificate name to avoid conflict with other users at the system location.

This is a good idea, though not sure how many devs use the same machine with multiple users for development.

As far as I can tell, the "C,," overwrites the "P,," so only "C,," is needed.

This might be the case, we would need to test this out, since I read something about a bug requiring this when I did this the first time.

With regards to supporting this on .NET Core I think we would be open to it, however I want to set expectations here, since it's not just about making --trust work. We would need to put in the work to make sure that --trust, --clean and --check options work within the expectations as well as do all the necessary work to ensure that we can maintain support and prevent regressions moving forward without this being a constant source of issues.

In addition to that, we wouldn't support --trust on a single distro. Our support matrix for 6.0 looks as follows:

Alpine Linux 3.13+ x64, Arm64, Arm32 Alpine
CentOS 7+ x64 CentOS
Debian 10+ x64, Arm64, Arm32 Debian
Fedora 32+ x64 Fedora
openSUSE 15+ x64 OpenSUSE
Red Hat Enterprise Linux 7+ x64, Arm64 Red Hat
SUSE Enterprise Linux (SLES) 12 SP2+ x64 SUSE
Ubuntu 16.04, 18.04, 20.04+ x64, Arm64, Arm32 Ubuntu

If we add support for this, we would need to try and cover as many distros as possible.

We would need to make a list of the following per distro:

  • Steps to validate the tool works manually from a clean install
    • Steps to install chrome, edge (Firefox comes by default in all?)
    • Steps to install dotnet
    • Steps to install all prerequisites
      • openssl (a recent enough version if not present)
      • libnss3-tools (if edge/chrome are installed?).
    • A reference app we can use for validation that exercises browser trust and dotnet to dotnet trust. Something like this
    • A list of commands to run and facts to validate after each command.
      • We do have a list of things we validate on Mac and Windows and we would use that same list.
      • We check that you can run the app from a new install, that cleaning the certs removes all the state and trust.
      • That commands are idempotent and don't fail if you repeat them.
      • Different sequences of --trust, --clean, etc.
  • We would need to update --clean to make sure we undo everything we do on a per-distro basis.
    • Remove the cert from the places we copied the cert.
    • Delete the cert from the chrome/edge db.
  • We would need to update --check to support determining if the certificate is already trusted
    • We can check dotnet-to-dotnet trust loading the cert and validating it or potentially looking into the Trusted Roots Store for it.
    • We will need to figure out how to validate it is trusted in Firefox and Chrome/Edge via nss-tools or other means.
  • We would need to include checks for installed tools on a per-distro basis:
    • Check openssl is recent enough.
    • Check if chrome and edge are installed.
    • Check if certutil is available.
  • We would need to make sure we don't override things that a user has explicitly set, like a custom policy using policies.json in Firefox or an existing certificate in any of the locations on disk where we need them to be.

Implementation wise, we could potentially divide this into 3 areas:

  • Firefox trust
    • This seems to vary across distros.
  • Chrome/Edge trust
    • This seems to be the same across distros
  • dotnet-to-dotnet trust
    • This varies across distros.

I would avoid us having distro specific code where possible. It would be neat if we could embed distro/version specific scripts/metadata to drive the process so that there's only one path available for Linux that is largely driven by distro specific resources. That way, when something changes we just need to update a resource for a specific distro/version which greatly limits the chances of regressing other distros (and creates an implicit boundary of the scenarios to revalidate).

All this is to say that this is a LOT of work and nothing that is planned for 6.0.

We would be open to getting this done in 6.0 provided that this goes through the right feature design/process if someone wants to tackle it, however its not something we will ourselves tackle.

The first thing we would require would be manual steps to setup and validate HTTPS on all distros and once we have that we could look at supporting it in dotnet dev-certs.

There is a big chance that this will bring in tons of new issues and maintenance work to the repo, and if we can't ensure that we have a low cost story for validating that it works on each specifically supported distro and that we can make changes easily, we won't want to sign up for owning this.

@tmds If this is something you are interested in contributing we would be open to accept it provided is based on the expectations set above. Let us know if this is the case and we can open a new issue for it.

Given the size of it, there might be some things that could be "parallelized" or split between folks to enable other people from the community to contribute. I wouldn't expect you to tackle on all this work, since as you can tell this is a really large effort in terms of cost and maintenance for us.

@tmds
Copy link
Member Author

tmds commented May 18, 2021

I hope I'll find some time for this in June.

note: most of the distros in the list won't meet the OpenSSL version requirement.

@javiercn
Copy link
Member

@tmds great!

I have filed an issue with more details here and I'll see if I can rally folks to do this mob style to get support across distros.

I tried several distros with unsupported open ssl versions and I was able to get things to work after I installed openssl one way or another. I've covered all that in the issue.

@ghost ghost locked as resolved and limited conversation to collaborators Jun 18, 2021
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Aug 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved
Projects
None yet
Development

No branches or pull requests

6 participants