quentin.dufour.io/_posts/2021-09-10-dev-with-tls.md

178 lines
7.9 KiB
Markdown
Raw Permalink Normal View History

2021-09-10 13:18:07 +00:00
---
layout: post
slug: dev-with-tls
status: published
sitemap: true
title: Develop with TLS
category: developpement
tags:
- security
---
In this article we focus on openssl as it is generally available and,
hopefully, it is versatile enough to adapt all our uses cases.
2021-09-10 13:35:26 +00:00
Note that more straightforward tools exist such as `mkcert`.
They simplify many steps presented here.
2021-09-10 13:18:07 +00:00
This article is not exhaustive to keep it readable but do not hesitate to complete it with
2021-09-10 13:35:26 +00:00
the corresponding openssl manpage.
You can already start with `man openssl` but keep in mind that openssl
2021-09-10 13:18:07 +00:00
works with subcommands that have dedicated manpages (eg. the manpage for `openssl x509` is `man x509` on my Fedora).
In this guide, our target is to create a simple CA for **local development purposes only**.
Do not reproduce this practises in production or you will put yourself, your organization and your users at risk.
2021-09-10 13:35:26 +00:00
In our commands, we do not specify an output format even if multiple formats exist.
To name only two, we have PEM or DER.
By default, openssl always use PEM encoding (which stands for Privacy-Enhanced Mail) and is defined in [RFC 7468](https://datatracker.ietf.org/doc/html/rfc7468). It is basically a way to store base64 encoded data in files. In my experience, this format is supported in many places. Often we use the `.pem` extension to denote a PEM encoded file but in practise, like in this article, we also create files with the `.crt` or `.key` extension that are also storing PEM encoded data.
2021-09-10 13:18:07 +00:00
2021-09-10 13:35:26 +00:00
I am done with the preamble, let's start generating our certificates by choosing a working folder (I will assume that your `$D` environment variable is set for all following commands):
2021-09-10 13:18:07 +00:00
```bash
export D=$HOME/.certs/localhost/
mkdir -p $D
```
## The Certificate Authority
2021-09-10 13:35:26 +00:00
It is mandatory to create a CA certificate that is independant from your End-entity Certificate,
2021-09-10 13:18:07 +00:00
otherwise you will have an error such as `CA_CERT_USED_AS_END_ENTITY` in Firefox.
For this article, I arbitrarily chose to generate an Elliptic Curve Key (and not a RSA one) that will be our CA private key.
```bash
openssl ecparam \
-genkey \
-name prime256v1 \
-out $D/ca.key
```
For more information about this command, run `man ecparam`, read [Bortzmeyer post (FR)](https://www.bortzmeyer.org/8422.html) or directly the [RFC 8422](https://www.rfc-editor.org/rfc/rfc8422.html).
2021-09-10 13:35:26 +00:00
Now, we want to generate a self-signed [X.509 certificate](https://en.wikipedia.org/wiki/X.509) for our Certificate Authority from the previously generated private key. Know that an expiration date is mandatory for a certificate. We set it to 10 years (3650 days) to not be annoyed in the near future by the expiration of our certificate but be sure to set it to a shorter time in production.
2021-09-10 13:18:07 +00:00
```bash
openssl req \
-x509 \
-new \
-key $D/ca.key \
-days 3650 \
-out $D/ca.pem \
-subj "/C=XX/ST=XX/L=XX/O=XX/OU=XX/CN=LOCAL_CA/emailAddress=X@X.XX"
```
For more information on this command, run `man req`.
2021-09-10 13:35:26 +00:00
Now that our authority (CA) is ready, we can add it to the CA store of our system and/or applications.
Each software, OS and distribution as its own procedure.
For Fedora, run:
2021-09-10 13:18:07 +00:00
```bash
sudo cp $D/ca.pem /etc/pki/ca-trust/source/anchors/localhost.crt
sudo update-ca-trust
```
For Windows, Mac OS, Debian/Ubuntu, Firefox or Chrome, you can refer to [BounCA's guide](https://www.bounca.org/tutorials/install_root_certificate.html).
2021-09-10 13:35:26 +00:00
And that's all, we have added our certificate authority to our system!
2021-09-10 13:18:07 +00:00
## End-entity Certificate
2021-09-10 13:35:26 +00:00
Now, we will generate our end-entity certificate, the one that will be used by our application. We start with the private key:
2021-09-10 13:18:07 +00:00
```bash
openssl ecparam \
-genkey \
-name prime256v1 \
-out $D/localhost.key
```
Then we generate a Certificate Signing Request.
The `CN` field is important as it will be checked against your domain name in many cases.
Here, we want a certificate for our development needs so we set it to `localhost`.
2021-09-10 13:35:26 +00:00
But we also want a valid certificate when we access our service through our loopback IP address, `127.0.0.1`.
2021-09-10 13:18:07 +00:00
Additionnaly, we want to support an infinite number of subdomains to test multiple services at the same time.
At this point, we need to use an extension to set the `subjectAltName` key.
Before going further, let digress a little bit on how to choose a domain for development that will not overlap with a (possibly) existing internet service. Know that [RFC 6761](https://datatracker.ietf.org/doc/html/rfc6761#section-6.3) says that `.localhost` is a reserved TLD, so we are sure it will never be advertised by root DNS servers and limited to our machine.
To summarize, we want a single certificate that is valid for (1) `localhost`, (2) all subdomains of `localhost` and (3) for the IP address `127.0.0.1` (and we could add a (4) for `::1`, the IPv6 loopback address). We simply must set the `subjectAltName` key to `DNS:localhost, DNS:*.localhost, IP:127.0.0.1`.
The full command to generate a certificate signature request looks like this:
```bash
openssl req \
-new \
-key $D/localhost.key \
-out $D/localhost.csr \
-subj "/C=XX/ST=XX/L=XX/O=XX/OU=XX/CN=localhost/emailAddress=X@X.XX" \
-addext "subjectAltName = DNS:localhost, DNS:*.localhost, IP:127.0.0.1"
```
And finally we sign the request (CSR) with our own authority (CA).
This command is a bit more tricky as we have to set again some fields.
It seems to be for security reasons: as this operation is thought to be done by a third party,
2021-09-10 13:35:26 +00:00
it should not trust your parameters and set its owns. In our case, we need to re-specify the number of days and our `subjectAltName`.
2021-09-10 13:18:07 +00:00
2021-09-10 13:35:26 +00:00
openssl has a more advanced/high level tool than the one we will use, namely `openssl ca` (doc: `man ca`). `openssl ca` is able to copy some or all fields of a signing request but this tool has, in return, some other caveats. If you are interested, please read its manual and especially the section entitled `WARNINGS`.
2021-09-10 13:18:07 +00:00
Our final command is:
```bash
openssl x509 \
-req \
-in $D/localhost.csr \
-CA $D/ca.pem \
-CAkey $D/ca.key \
-CAcreateserial \
-out $D/localhost.crt \
-days 3650 \
-extensions v3_ext \
-extfile <(printf "[ v3_ext ]\nsubjectAltName = DNS:localhost, DNS:*.localhost, IP:127.0.0.1\n")
```
You can run `man x509` to know more about this command.
## With socat
2021-09-10 13:35:26 +00:00
socat is the swiss army knife of the network operator. In this example, we will use it as a simple TLS
2021-09-10 13:18:07 +00:00
proxy in front of a plain text application.
2021-09-10 13:35:26 +00:00
First, we need to concatenate our certificate in a bundle for socat. The key must comes first, then its X.509 certificates, and finally the whole chain of X.509 certificates up your root certificates.
2021-09-10 13:18:07 +00:00
For us:
```bash
cat \
$D/localhost.key \
$D/localhost.crt \
$D/ca.pem \
> $D/localhost-bundle.pem
```
*For nginx, you need to concatenate X.509 certificates in the same order but you must not put the private key file. Instead, the private key file is specified independently in your configuration*.
To run socat on port `4443` with TLS and forward requests in plain text to `localhost:3900`, run:
```bash
socat \
"openssl-listen:4443,\
reuseaddr,\
fork,\
verify=0,\
cert=$D/localhost-bundle.pem" \
tcp4-connect:localhost:3900
```
2021-09-10 13:35:26 +00:00
You may ask yourself why we put parameters like `reuseaddr` or `fork`. By using `reuseaddr`, we can reuse a port without waiting for an internal timeout in the kernel which is required to quickly restart socat, the article [Bind: Address already in use](https://hea-www.harvard.edu/~fine/Tech/addrinuse.html) explains on details why. `fork` allows us to handle multiple connections in parallel. `verify` allows us to activate or deactivate mutual authentication, here we do not want to authenticate the client so we set it to zero.
2021-09-10 13:18:07 +00:00
## Other resources
Some other resources that could help you with TLS/X.509 certificates:
* [How to Create Your Own SSL Certificate Authority for Local HTTPS Development](https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/)
* [Certificates on Kubernetes](https://kubernetes.io/docs/tasks/administer-cluster/certificates/#openssl)