I’m not having any issue with issuing and deploying a certificate for the service I host – my problem is related to consuming the service.
My server uses LetsEncrypt certificates for its main domain, dotforward.de. The certificate is used for the web server (HTTPS), the SMTP server and a number of other services. There is absolutely no problem when connecting securely to these services from client machines running Windows or Android.
But when my (ASP).NET Core application, running on that server, tries to connect to that SMTP server with MailKit, an open-source e-mail/smtp library, I get the error: “The remote certificate is invalid according to the validation procedure.” Looking inside the certificate validation callback, the subject of the presented certificate is “CN=dotforward.de” (correct) and the detected errors is RemoteCertificateChainErrors. Now this is fairly general and could mean all sorts of things, also the documentation of that interface isn’t too comprehensive. But it seems that the certificate is the correct one and something’s wrong with the chain. I am connecting to the local SMTP server through its public DNS name, dotforward.de, not localhost, and also on the SSL port 465. So I expect things to work just as from a really remote client.
How can I verify that Ubuntu 16.04 has the proper LetsEncrypt certificates installed so it can actually validate the presented certificate? It seems to be included in Windows/Firefox and Android but maybe it’s missing on that Linux system?
First of all, what version of .net core are you running (on that server)?
It seems like .net is using OpenSSL's trust store to build trust in Ubuntu system. (I Googled it)
In this case, can you try to run openssl version -d on that system and see if there's any CA cert under that directory?
The .NET Core version is 2.2 (will upgrade to 3.1 in the near future). The output of the command is: OPENSSLDIR: “/usr/lib/ssl”
In that directory, there are a few files/dirs, and in the certs subdirectory (points to /etc/ssl/certs) there are 449 files but none start with LetsEncrypt or similar. What name should I look for? But lots of files just have a number as their name.
Updating certificates in /etc/ssl/certs...
0 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
Updating Mono key store
Linux Cert Store Sync - version 4.2.1.0
Synchronize local certs with certs from local Linux trust store.
Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed.
I already trust 149, your new list has 148
Import process completed.
Done
done.
(And old Mono is indeed installed but not used here.)
Doesn't look like it changed anything. I can eliminate time issues (and just checked again) since NTP is active and the logs all show correct times. This is all on the same machine (requesting certs, installing certs, connecting to local service).
When I run this on the .NET Core 3.1 Ubuntu Bionic image, it works fine:
using System;
using MailKit.Net.Smtp;
namespace app {
class Program {
static void Main(string[] args) {
using(var client = new SmtpClient()) {
client.Connect("dotforward.de", 465, true);
client.NoOp();
client.Disconnect(true);
}
}
}
}
When I run the program, I can also see that the .NET Core runtime is reading from /etc/ssl/certs/ca-certificates.crt. Maybe make sure that file exists and is readable.
I used this test app and tried it on that Linux machine. It fails when built with .NET Core 2.2 but succeeds with .NET Core 3.0 and 3.1. Guess it’s a .NET Core thing then. A quick web search didn’t give me anything interesting.
I know you marked this as solved, but I dug into this a little more for you. Here’s what I think is happening.
Before version 3.0, .NET Core (on Linux specifically) did not support OCSP-based revocation checking. Only CRL checking was supported (an alternate, outdated/older way to check for revocation).
Let’s Encrypt does not publish CRLs for end-entity certificates, such as the one for dotforward.de, it only uses OCSP.
In turn, MailKit, by default, requires revocation checking when it connects to a server via TLS or STARTTLS. Notably, this goes against what .NET does by default - which is to not check for revocation.
I think that because there is no way for .NET Core 2.1’s SSL runtime to check for the revocation of a Let’s Encrypt certificate, it decides the certificate is invalid.
I was able to get the above program working on 2.1 by skipping revocation checking: