NOTE: Exported from this Notion page
This article is about adding your own root CA certificate to your local root trust stores. And by doing that all the certificates (intermediate or leaf) signed by that is automatically trusted because of the “chain of trust”. I.e., when you have created one root certificate with mkcert you only have to add it once to the trust stores. When you need to create new certificates you can do so successfully as long as those are signed by your root certificate. The alternative would be to add every certificate you create to every trust store.
What I want to achieve looks something like this:
Diagram rendered by the amazing plantuml ☝️
There’s a lot of different CA (Certificate Authority) root trust stores, not all applications uses the system’s, e.g., macOS Trust Store. So if you want some applications to trust your certificate you need to add it to those particular store. E.g., if you’re using Google Chrome, you’re good as of now, because it uses the system’s store. But they are planning on creating their own in the future. Read about their “Root Program” here.
Out of scope of this article
I present multiple ways of installing certificates:
- Install certificates using
- Install certificates manually
On all the commands below I use the
$CERT variable which point to my root CA certificate I want to trust:
Install certificates using
Smallstep supports adding root certs to multiple trust stores. This is a lot easier than doing it manually. As of now it supports the following stores:
- System store (In my case: macOS Trust Store)
brew install step step certificate install --all $CERT
Even though I got an error when installing cert to Firefox… But if it works it will save you a lot of time 🙂
Install certificates manually
macOS Trust Store
MacOS store is managed through “Keychain Access”. Install a cert with a command like this:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain $CERT
You add extra certs with an environment variable included in the node process:
Curl uses openssl as its trust store. And by default (I think) openssl is using the system’s store. But if you only want to update openssl’s store follow along.
You do that by copying the cert to this directory:
/usr/local/etc/[email protected]/certs/. I take for granted
openssl is installed with version
1.1 with homebrew on it’s default location.
cp $CERT /usr/local/etc/[email protected]/certs/
And then run this to update openssl:
And that’s it. That was easy ☺️.
The above is explained by homebrew:
brew info openssl.
Optional approach to trust certificate per request (without adding it to store):
curl --cacert $CERT https://localhost
Firefox is using Mozillas NSS root store. Firefox recommends installing custom certificates this way: https://support.mozilla.org/en-US/kb/setting-certificate-authorities-firefox#w_using-policies-to-import-ca-certificates-recommended. I haven’t tried that as I wanted a fully automated way and went for using
Install with certutil
certutil is a cli-tool and it’s dependent on
nss. Here is a resource with all the commands and arguments to
brew install nss
Locate certutil (optional if it’s not added to your PATH)
When I installedd
nsswith homebrew it was symlinked into
/usr/local/binand therefore in my
PATHalready. So I could skip this step. If this command gives output you are fine as well:
$ which certutil /usr/local/bin/certutil
If not let’s find
brew --prefix nss
That should output the location of
bin/certutilto the above output and you have
/usr/local/opt/nss/bin/certutilon a macOS.
Locate your Firefox profile
You also need to find you Firefox profile path. It usually is here:
~/Library/Application Support/Firefox/Profiles/xxxxxxxx.defaultwith the
.defaultsuffix on it. But maybe you use another profile. My profile was called
xxxxxxxx.dev-edition-default-1579772843598/as I use Firefox Developer Edition.
Ok. Now we can add the certificate when we have the path to our certificate (
$PROFILEand can execute
certutil. To identify the certificate it’s also required to set a “nickname” on it with
-t. I set it with
PROFILE="~/Library/Application\ Support/Firefox/Profiles/df25fipq.dev-edition-default-1579772843598/" NAME=MyUniqueTestCAName1 certutil -A -d $PROFILE -t "C,," -n $NAME -i $CERT
You can now list the certificates in the profile to see if it got added. You should recognize it by the name you specified with
certutil -L -d $PROFILE
Extra: Remove certificate
It’s useful to be able to remove certificates. Do it by targetting the name of it:
certutil -D -d $PROFILE -n $NAME
Apple seems to recommend using Apple Configurator 2 from the app store. See https://support.apple.com/en-us/HT204477 and https://developer.apple.com/library/archive/qa/qa1948/_index.html. But I’m not sure if it’s still applicable for iOS13
I’ll refer to IBM’s knowledge center: https://www.ibm.com/support/knowledgecenter/en/SSEP7J_10.2.2/com.ibm.swg.ba.cognos.adm_ba_pattern.1.2.0.doc/t_biblu_add_cacert.html or Oracles: https://docs.oracle.com/cd/E19906-01/820-4916/geygn/index.html
Container to Container communication
I’ve came across scenarios when I make HTTPS requests between containers, e.g., when I want to mock an external service and can’t change the protocol to HTTP. So if I do a HTTPS requests from my Nodejs-container app to https://stripe.com/api-call which is my mock-service I need to present a valid certificate for Nodejs from my mock service.
A good thing to know about is that you can redirect network domains in docker-compose with network aliasas. This is great for mocking external services, when you, e.g., want to handle all external traffic in a an internal service.
This example make all calls to
[api.stripe.com](http://api.stripe.com) getting routed to
my-app for example:
my-app: image: my-app ports: - 80:80 - 443:443 networks: default: aliases: - api.stripe.com