#3903: Rework OpenSSL certificate verification to support alternative chains -------------------------+---------------------- Reporter: kempniu | Owner: mutt-dev Type: enhancement | Status: new Priority: minor | Milestone: Component: crypto | Version: Keywords: | -------------------------+---------------------- The way Mutt currently verifies SSL certificates using OpenSSL does not support alternative chains, which may cause confusion when some popular mail providers (e.g. Gmail) are used with specific sets of trusted CA certificates.
I am using Mutt 1.7.2 on Arch Linux, which currently ships with CA certificates distributed as part of Mozilla's NSS 3.27.1. One particularly interesting thing about that release is that [https://bugzilla.mozilla.org/show_bug.cgi?id=1296689 some CAs were removed in it], specifically the Equifax Secure Certificate Authority. As of today, Gmail IMAP servers are using a certificate chain which ends with a certificate (attached) that was issued to !GeoTrust by Equifax. When trying to use Gmail with the trusted CA store from NSS 3.27.1, things get awkward, because the signer of the !GeoTrust certificate is not present in the trusted CA store. However, the latter contains another certificate (also attached) issued to !GeoTrust that is self-signed (both the removed Equifax-signed certificate and the self-signed one have the same subject). This is where alternative chains step in. Default verification routines implemented in newer OpenSSL versions (1.0.2b+) have them enabled by default, which in this case causes the Equifax-signed !GeoTrust certificate to be replaced with its self-signed version during verification. This in turn prevents any untrusted certificate warnings from appearing. However, for an alternative chain to be found, the `X509_STORE_CTX` structure supplied to `X509_verify_cert()` must have a non-`NULL` `untrusted` member. Meanwhile, Mutt tries to build a trust chain on its own, starting from the root and adding certificates to `SslSessionCerts` as it goes, which means that when `X509_verify_cert()` is first called from `check_certificate_by_signer()`, `SslSessionCerts` will be `NULL`, causing the `untrusted` member of `X509_STORE_CTX` to be `NULL` and thus making it impossible for OpenSSL to look for an alternative chain. This in turn causes the certificate to be displayed to the user as untrusted. In the case of Gmail, things get even more confusing if the user chooses to reject the Equifax-signed !GeoTrust certificate, because the `for` loop moves on to the next certificate in the chain, i.e. the one issued to Google by !GeoTrust and this time verification succeeds, because the signer of this certificate is in the trust store (due to the self-signed !GeoTrust certificate being present). The end result is that the user chooses to reject a certificate and the connection succeeds anyway. The confusing prompt can obviously be prevented from appearing by adding the Equifax-signed !GeoTrust certificate (or the Equifax self-signed certificate removed in NSS 3.27.1) to `~/.mutt_certificates`, but I decided to attempt a proper fix by reworking the way Mutt verifies certificates using OpenSSL, implementing a solution which I believe is a bit closer to how OpenSSL is typically used. I removed `ssl_check_certificate()` altogether, instead enabling peer certificate verification (which causes `X509_verify_cert()` to be called automatically when `SSL_connect()` is called) and setting the verify callback to a modified version of `ssl_check_preauth()`. It seemed reasonable as `ssl_check_certificate()` duplicates much of the work that `X509_verify_cert()` does automatically when called with a complete certificate chain supplied by the peer (it walks the chain starting from the root and tries to validate each certificate along the path based on what it has learned so far). Using this approach together with some `SSL_CTX_*` calls enabled me to also get rid of `check_certificate_by_signer()`, making the code a bit shorter and hopefully cleaner. The patch also causes the connection to be immediately interrupted at the SSL handshake level if the user rejects any certificate in the chain, which I feel is the intuitive thing to do. This patch likely needs further work and definitely needs a thorough review and testing. Hopefully the code itself is easier to understand than the convoluted explanation above. If you think this is a move in the right direction, I will be happy to further polish the patch to your liking. One issue I can already think of is that the user might want to have the possibility to turn alternative chains off through the configuration file to retain old behavior. I can implement that as well, but I think it deserves a separate patch for clarity. -- Ticket URL: <https://dev.mutt.org/trac/ticket/3903> Mutt <http://www.mutt.org/> The Mutt mail user agent