TLS (formerly known as SSL) applies an encryption layer to TCP communications. This consists of two parts
There are many key exchange algorithms which can be used; there are many secret key algorithms which can be used. See for example Cipher Suites: Ciphers, Algorithms and Negotiating Security Settings for more details.
Be warned: this part of the chapter covers eye-glazing territory. You may wish to skip to the summary section and come back to the rest only when feeling like getting your head messed around.
The most commonly used public/private key system used for TLS is RSA.
(An alternative is ECDSA - see
Comparing ECDSA vs RSA: Everything You Need to Know
).
There are many ways of generating a public/private key pair, some of which
are preferred by the programming language environment (see Java later).
The most commonly used method for RSA keys on Posix systems is
openssl
.
I will use it in this section as a language-independent way of managing TLS
certificates.
It is also available on Windows.
(Other Windows alternatives are given by
Generating self-signed certificates on Windows
).
To generate a 2048-bit RSA key pair and store it in the
file key.pem
,
execute
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
The command genpkey
replaces the older genrsa
command:
openssl genrsa -out key.pem 2048
.
Amongst other things, it uses the more flexible PKCS #8 format instead of
PKCS #1.
See
PKCS#1 and PKCS#8 format for RSA private key [closed]
for more details.
You keep this file to yourself - giving it to anyone else defeats the purpose of a private key.
The key file is in PEM
format.
The PKCS #8 format is as ASCII text and can be
viewed.
The key file will look like
-----BEGIN PRIVATE KEY-----
<Base 64 encoded data>
-----END PRIVATE KEY-----
and the certificate file will look like
-----BEGIN CERTIFICATE-----
<Base 64 encoded data>
-----END CERTIFICATE-----
The public key can be extracted by
openssl rsa -in key.pem -pubout
and the private key can be viewed in a textual representation by
openssl rsa -in key.pem -text -noout
A more recent file format is PKCS #12, which is a binary format and cannot be 'cut and pasted' like PEM files can.
We all know that CPUs are getting faster, and that GPUs and FPGAs can perform some calculations even faster than most CPUs. This makes it progressively easier to crack any keys. So if you want to keep a key for a long time, you must ensure its key length is long enough.
Mozilla reports that a 2048-bit RSA private key is considered secure for the next 4 years, a 3072-bit RSA key for the next 8 years, while a 4096-bit RSA key will only be okay for the next 15 years.
An X.509 (more accurately, PKIX) certificate associates extra information with an RSA key pair. The primary piece is an identity/subject - usually of a person or a host name.
X.509 certificates are an old format, and some of the elements of that
format are in the process of being replaced. The most significant of these
is the 'Common Name' (CN). For a server, this is usually the DNS hostname, but
the CN is now judged to have an ambiguous meaning.
It should be replaced (or at least augmented) by a 'subjectAltName' (SAN), which has typed
entries such as subjectAltName=DNS:example.com
.
Typically, a certificate will have both.
There are three ways of creating X.509 certificates with subject information
using openssl
:
-subj
parameters on the command line
Browser vendors have a large say in what certificates they will accept - if they don't
like a site's certificate they will make it hard for you to get to that site.
The current version of X.509 is version 3, PKIX, specified by the IETF
RFC 5280
.
Mozilla (creators of Firefox
) spell out in
A Web PKI x509 certificate primer
their expectations of X.509 certificates according to the IETF
specifications and give examples
of creating them using openssl
.
In this chapter I follow those guidelines as some of the programming languages
(particularly Rust) also do so.
The command
openssl req -new -x509 -sha256 -key key.pem -out certificate.crt -days 3650
will create a new X.509 certificate certificate.crt
using interactive
prompting, valid for 10 years.
It will prompt for many tags, the most important being the
CN (Common Name).
The interaction to generate keys for the host name desktop.home.arpa
is
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:VIC
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Home
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:desktop.home.arpa
Email Address []:jan@newmarch.name
The contents of the certificate file can be seen by
openssl x509 -in certificate.crt -text -noout
and are
Certificate:
Data:
...
Issuer: C = AU, ST = VIC, O = Home, CN = desktop.home.arpa, emailAddress = jan@newmarch.name
Validity
Not Before: Jun 21 05:50:58 2020 GMT
Not After : Jun 19 05:50:58 2030 GMT
Subject: C = AU, ST = VIC, O = Home, CN = desktop.home.arpa, emailAddress = jan@newmarch.name
...
X509v3 extensions:
...
The certificate file is also in PEM
format.
It is an ASCII text file and can be
viewed directly (but not informatively).
A key part of PKIX certificates is that they must be able to demonstrate their authenticity. That is, they must be signed by someone and the certificate can be linked to that signature. The certificate generated above is only self-signed. No-one has verified that it is a genuine certificate (apart from me of course, and that is no guarantee :-).
Certificates should be signed by a Certificate Authority (CA) to be trusted.
A recipient of a certificate may or may not trust the CA. Lists of CAs trusted by browsers are pointed to by List of certificate authorities in browsers and mobile platforms [duplicate] although these change all the time.
Wikipedia includes a list of 14 CAs at Certificate authority
Some languages recognise all CAs that the Operating System recognises.
The Go file root_linux.go
tells which files contain the
information for Linux systems:
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
For Debian, see List all available ssl ca certificates The answer from Stéphane Chazelas is to run
awk -v cmd='openssl x509 -noout -subject' '
/BEGIN/{close(cmd)};{print | cmd}' < /etc/ssl/certs/ca-certificates.crt
This gives
subject=CN = ACCVRAIZ1, OU = PKIACCV, O = ACCV, C = ES
subject=C = IT, L = Milan, O = Actalis S.p.A./03358520967, CN = Actalis Authenti
cation Root CA
subject=C = US, O = AffirmTrust, CN = AffirmTrust Commercial
subject=C = US, O = AffirmTrust, CN = AffirmTrust Networking
subject=C = US, O = AffirmTrust, CN = AffirmTrust Premium
subject=C = US, O = AffirmTrust, CN = AffirmTrust Premium ECC
subject=CN = Atos TrustedRoot 2011, O = Atos, C = DE
subject=C = ES, CN = Autoridad de Certificacion Firmaprofesional CIF A62634068
subject=C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
subject=C = NO, O = Buypass AS-983163327, CN = Buypass Class 2 Root CA
subject=C = NO, O = Buypass AS-983163327, CN = Buypass Class 3 Root CA
subject=C = SK, L = Bratislava, O = Disig a.s., CN = CA Disig Root R2
subject=C = CN, O = China Financial Certification Authority, CN = CFCA EV ROOT
subject=C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN
= COMODO Certification Authority
...
which is probably more information than you need!
Looking at the CN field, you can see CAs
Actalis, AffirmTrust, Atos, ... .
Digging up information for other systems doesn't seem quite as easy.
To get a certificate signed by a recognised CA a certificate signing request (not a certificate) must be submitted to them, a validation process must be done by them, maybe some money changes hands (I use ZeroSSL to avoid that), and if successful a certificate is returned.
To prepare for a CA to sign a certificate, a Certificate Signing Request must be
created. This typically ends in .csr
instead of a signed
certificate .crt
. The changes to the earlier certificate
creation request of earlier are minor: drop the -x509
and -days
options and change the filename extension, as in
openssl req -new -sha256 -key key.pem -out certificate.csr -subj '/CN=localhost'
The command to view such a file changes slightly, using the command req
instead of x509
:
openssl req -in certificate.csr -text -noout
One of the features of PKIX is that it considers the possibility that a certificate might somehow become invalid. Events such as the certificate life expiring can be caught from the information in the certificate. But sometimes other events might cause one to become invalid - for example, the private key has been leaked or the indentity of an entity has been found to be false.
In such cases, the certificate needs to be revoked
.
Formerly, this was done using a CRL (Certificate Revocation List).
Now the preferred mechanism (and also required by the Mozilla guidelines)
is to use OCSP (Online Certificate Status Protocol).
This will usually run as a server, with address specified in the
certificate.
The certificate will need to contain entries like
Authority Information Access:
OCSP - URI:http://localhost:8888
and will be generated by openssl
by configuration entries
(see later) such as
authorityInfoAccess = OCSP;URI:http://localhost:8888
We won't need to actually create an OCSP server (it is done by
openssl ocsp ...
) and have it verify certificates.
If you do need to, see
Online Certificate Status Protocol
by Jamie Nguyen.
The .crt
certificate initially created above is self-signed.
Such a certificate will not be trusted by anyone (but me)
but has uses in testing and in setting up local
environments.
It does however require some tweaking to conform to the Mozilla guidelines. These guidelines require the use of X.509 v3 extensions, above the standard X.509 fields. The certificate generated above contains the extension fields
X509v3 extensions:
X509v3 Subject Key Identifier:
...
X509v3 Authority Key Identifier:
...
X509v3 Basic Constraints: critical
CA:TRUE
What is primarily missing is the subjectAltName
. There are other
fields that may be useful, such as extendedKeyUsage
which can be set to values such as serverAuth
to show the
role expected for this certificate. Also, the generated self-signed certificate
assumes that it is for a CA, and this may or may not be true.
There are several routes to creating a satisfactory self-signed certificate.
Really, they are all about playing games with openssl
to get it to add
the X509v3 extensions in the way you want.
-addext
as in
openssl req -new -x509 -sha256 -key key.pem -out certificate.crt
-days 3650 -subj '/CN=localhost'
-addext 'subjectAltName=DNS:localhost'
The only drawback to this method is that it cannot turn off
the CA:TRUE
extension in the certificate -
it should be FALSE
for a leaf certificate
openssl
as described by man 5 x509v3_config
.
Create a file e.g. self.cfg
of
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = localhost
[v3_req]
basicConstraints = CA:FALSE
subjectAltName = DNS:localhost
extendedKeyUsage = serverAuth
authorityInfoAccess = OCSP;URI:http://localhost:8888
Then create the certificate just picking out the v3_req
section:
openssl req -new -x509 -sha256 -key key.pem -out certificate.crt
-days 3650 -subj '/CN=localhost' -config self.cnf
-extensions v3_req
basicConstraints = CA:FALSE
subjectAltName = DNS:localhost
extendedKeyUsage = serverAuth
authorityInfoAccess = OCSP;URI:http://localhost:8888
Then the commands are
# create the CSR file
openssl req -new -key key.pem -days 1096 -extensions v3_ca -batch \
-out certificate.csr -utf8 -subj '/CN=localhost'
# and sign it
openssl x509 -req -sha256 -days 3650 -in certificate.csr \
-signkey key.pem -extfile self.cnf \
-out certificate.pem
These all result in a certificate file with an extensions section such as
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Alternative Name:
DNS:localhost
X509v3 Extended Key Usage:
TLS Web Server Authentication
Authority Information Access:
OCSP - URI:http://localhost:8888
Particularly during development, you may not want to go through the pain of getting a certificate signed by a recognised CA. In that case, you can make your own Certificate Authority. There are three issues involved:
The first is done by first generating a key and
then a certificate. The CN is set to localhost
, but could
be any host name
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=localhost" -days 5000 -out ca.crt
Instead of generating a finished server certificate directly, now we generate a Certificate Signing Request (CSR) file for localhost by
openssl req -new -key server.key -subj "/CN=localhost" -out server.csr
This can then be signed by our new CA
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 1000
The final step, of getting this CA recognised by each client depends on the client's environment. This may be done by adding to the O/S CA list, which we won't discuss, or programmatically, which we will discuss for each language.
In summary, there are four ways of getting certificates representing
localhost. Each of them will use the same private.key
generated by
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
self-signed-cert.pem
self-signed.cnf
with contents
basicConstraints = CA:FALSE
subjectAltName = DNS:localhost
extendedKeyUsage = serverAuth
authorityInfoAccess = OCSP;URI:http://localhost:8888
Then create a CSR by
openssl req -new -key key.pem -extensions v3_ca -batch \
-out self-signed-cert.csr -utf8 -subj '/CN=localhost'
Sign this by
openssl x509 -req -sha256 -days 3650 -in self-signed-cert.csr \
-signkey key.pem -extfile self-signed.cnf \
-out self-signed-cert.pem
CA-cert.pem
CA.cnf
with contents
basicConstraints = critical, CA:TRUE
keyUsage = keyCertSign, cRLSign
subjectAltName = DNS:localhost
extendedKeyUsage = serverAuth
authorityInfoAccess = OCSP;URI:http://localhost:8888
Then create a CSR by
openssl req -new -key key.pem -extensions v3_ca -batch \
-out CA.csr -utf8 -subj '/O=NewmarchCA'
Sign this by
openssl x509 -req -sha256 -days 3650 -in CA.csr \
-signkey key.pem -extfile CA.cnf \
-out CA-cert.pem
CA-signed-cert.pem
leaf.cnf
with contents
basicConstraints = critical, CA:FALSE
subjectAltName = DNS:localhost
extendedKeyUsage = serverAuth
authorityInfoAccess = OCSP;URI:http://localhost:8888
openssl req -new -key key.pem -extensions v3_ca \
-batch -out localhost.csr -utf8 -subj '/CN=localhost'
Sign the certificate
openssl x509 -req -sha256 -days 1096 -in localhost.csr \
-CAkey key.pem -CA CA-cert.pem -out CA-signed-cert.pem \
-extfile leaf.cnf -CAcreateserial
The certificates created above
(self-signed-cert.pem
,
CA-cert.pem
and CA-signed-cert.pem
)
will be used in the programs following.
Their relative path will be ../certs/
.
Every TLS client will have a list of root CAs that it trusts. If a certificate is signed by a root CA, then it is trusted too. The root CAs are long-lived. So they typically create a number of shorter-lived non-root CAs and sign them. These non-root CAs can then sign further certificates, possibly forming a chain of signed certificates, with top of the chain being one of the root CAs. To trust any particular certificate, you need to trace from the certificate itself back through the chain until you get to the trusted root CA.
Typically, when you ask a CA to sign a certificate, it will return the certificate signed by an intermediary CA, and also a chain of intermediate certificates back to the root CA. For any client to be able to validate a certificate, it must have both the leaf certificate and also the intermediate certificate chain.
How each system manages these varies. For example, the Apache
TLS server configuration file
(such as sites-enabled/default-ssl.conf
) has an entry for each:
SSLCertificateFile ...
SSLCertificateChainFile ...
To see a certificate chain, run e.g.
openssl s_client -showcerts -connect <host>:443
For example, on my site jan.newmarch.name
,
the chain consists of (at present)
USERTrust RSA Certification Authority
ZeroSSL RSA Domain Secure Site CA
jan.newmarch.name
where the first is the root CA and the second is the
CA I got the certificate signed by.
Nearly all web sites supporting HTTPS have valid certificates. The site Bad SSL has a range of pages with bad certificates such as expired, self-signed, revoked, etc. Good for debugging!
Copyright © Jan Newmarch, jan@newmarch.name
" Network Programming using Java, Go, Python, Rust, JavaScript and Julia"
by
Jan Newmarch
is licensed under a
Creative Commons Attribution-ShareAlike 4.0 International License
.
Based on a work at
https://jan.newmarch.name/NetworkProgramming/
.