Fixing UNABLE_TO_VERIFY_LEAF_SIGNATURE...

...and what are intermediate certificates anyway?

Just last week my colleague Andi from MDN Web Docs (one of the best websites ever), asked me about a issue he had encountered in the MDN Observatory backend. For those who don't know the Observatory yet, it's a web page that allows you to get a quick web security check for your web page. (My web page gets an A+. This is no coincidence. And yours?) The issue occured when checking a third-party site and the error message was UNABLE_TO_VERIFY_LEAF_SIGNATURE. Clearly, this is is something about HTTPS / TLS. But the odd thing is that the web pag worked just fine in Firefox. Why?

Intermediate Certificates

The core issue is easier explained when looking at how certificates are typically created (and valideted). To get a trusted certificate for your website, you need to work with a Certificate Authority (CA). This is to ensure that the domain in the certificate is only usable by the owner of the domain. Most of this is easily automated thanks to Let's Encrypt and the ACME protol (not in scope for this blog post :-)). In essence, you can easily create a certificate for your domain, prove that it is actually yours and have it signed such that every browser can verify your certificate as trustworthy. This works because your browser (or really any TLS/HTTPS client) has a list of CAs (like Let's Encrypt) pre-insalled and can therefore validate a certificate by verifying a cryptographic signature. What's mostly hidden from the end user is that CAs need to keep their cryptographiy key material super secure to ensure it is not exposed to the internet. So, how do they sign a load of certificates while keeping their keys secured? The solution are so-called Intermediate certificates (or Intermediates). A CA typically creates a set of certificates which are also marked as CA ceriticates that then do the actual signing of certificates for the web. In case of Let's Encrypt, there are currently two Intermediates that are signed by two roots (both are signing both). This essentially means that the steps to validate a certificate is now a bit longer. In terms of tree metaphors, the path goes from the root CA to the Intermediate to the so-called leaf certificate (which is for your domain). For your client, that means it needs to know of the intermediate as a trusted entity signed by the root to verify that a web site's certificate is valid. The usual way to solve it is that a web server should not present its own certificate but a full chain that goes up to a trusted root. Tools like cerbot for Let's Encrypt usually do that by default. But what if it doesn't?

Intermediate Preloading

While it was long believed as a misconfiguration when a web site can not show a full proof of validity, browsers have stepped in to fix this. Even though Intermediates can change more often than Root CAs, they can still be loaded into the browser. A typical fix is to load them dynamically as part of browser settings rather than making them part of the browser release. Firefox calls this Intermediate Preloading. While preloading fixes this for browsers and their end users, the missing intermediate is still an issue for other clients out there. And that is why node is sometimes reporting an error to verify a leaf signature.

Fixing this in nodejs

There are various posts on the internet on how to fix this, some suggest to just not validate certificates at all. (This is where you realize that the motivation for this very blog post may have been "Someone is wrong on the internet". But it's also because I replied to Andi's question with a very long monologue that I found worth capturing here).

The best fix is to supply node with the extra intermediates. As mentioned above, Intermediates can change more often than roots, so they should be maintained independently and more often. The good thing is that all Certificate Authorities need to disclose their Intermediates and the great Common CA Database (CCADB) project has a list.

In nodejs, you can set the environment variable NODE_EXTRA_CA_CERTS to a file path that has an additional certificate bundle.


If you find a mistake in this article, you can submit a pull request on GitHub.

Other posts

  1. Fixing UNABLE_TO_VERIFY_LEAF_SIGNATURE... (Sun 01 December 2024)
  2. Modern solutions against cross-site attacks (Tue 26 November 2024)
  3. Prompt Injections and a demo (Wed 18 September 2024)
  4. The Mozilla Monument in San Francisco (Fri 05 July 2024)
  5. What is mixed content? (Sat 15 June 2024)
  6. How I got a new domain name (Sat 15 June 2024)
  7. How Firefox gives special permissions to some domains (Fri 02 February 2024)
  8. Examine Firefox Inter-Process Communication using JavaScript in 2023 (Mon 17 April 2023)
  9. Origins, Sites and other Terminologies (Sat 14 January 2023)
  10. Finding and Fixing DOM-based XSS with Static Analysis (Mon 02 January 2023)
  11. DOM Clobbering (Mon 12 December 2022)
  12. Neue Methoden für Cross-Origin Isolation: Resource, Opener & Embedding Policies mit COOP, COEP, CORP und CORB (Thu 10 November 2022)
  13. Reference Sheet for Principals in Mozilla Code (Mon 03 August 2020)
  14. Hardening Firefox against Injection Attacks – The Technical Details (Tue 07 July 2020)
  15. Understanding Web Security Checks in Firefox (Part 1) (Wed 10 June 2020)
  16. Help Test Firefox's built-in HTML Sanitizer to protect against UXSS bugs (Fri 06 December 2019)
  17. Remote Code Execution in Firefox beyond memory corruptions (Sun 29 September 2019)
  18. XSS in The Digital #ClimateStrike Widget (Mon 23 September 2019)
  19. Chrome switching the XSSAuditor to filter mode re-enables old attack (Fri 10 May 2019)
  20. Challenge Write-up: Subresource Integrity in Service Workers (Sat 25 March 2017)
  21. Finding the SqueezeBox Radio Default SSH Password (Fri 02 September 2016)
  22. New CSP directive to make Subresource Integrity mandatory (`require-sri-for`) (Thu 02 June 2016)
  23. Firefox OS apps and beyond (Tue 12 April 2016)
  24. Teacher's Pinboard Write-up (Wed 02 December 2015)
  25. A CDN that can not XSS you: Using Subresource Integrity (Sun 19 July 2015)
  26. The Twitter Gazebo (Sat 18 July 2015)
  27. German Firefox 1.0 ad (OCR) (Sun 09 November 2014)
  28. My thoughts on Tor appliances (Tue 14 October 2014)
  29. Subresource Integrity (Sun 05 October 2014)
  30. Revoke App Permissions on Firefox OS (Sun 24 August 2014)
  31. (Self) XSS at Mozilla's internal Phonebook (Fri 23 May 2014)
  32. Tales of Python's Encoding (Mon 17 March 2014)
  33. On the X-Frame-Options Security Header (Thu 12 December 2013)
  34. html2dom (Tue 24 September 2013)
  35. Security Review: HTML sanitizer in Thunderbird (Mon 22 July 2013)
  36. Week 29 2013 (Sun 21 July 2013)
  37. The First Post (Tue 16 July 2013)
π