SSL/TLS
TLS for servers
In this section, we give a very short and quick description on how to enable SSL/TLS for a server (listening) connection - for example, HTTPS server.
- Obtain TLS certificate and private key files (or create your own self-signed certificate, see below)
- In your initialization section, after
mg_mgr_init()
, callmg_tls_ctx_init()
:struct mg_tls_opts opts = { .server_cert = mg_unpacked("/certs/server_cert.pem"), .server_key = mg_unpacked("/certs/server_key.pem")}; mg_tls_ctx_init(&mgr, &opts);
- Rebuild the application, see build instructions below
You can see example code in
TLS for clients
In order to TLS-enable client connections,
- Obtain the CA (Certificate Authority) certificate file
- In your initialization section, after
mg_mgr_init()
, callmg_tls_ctx_init()
:struct mg_tls_opts opts = {.client_ca = mg_unpacked("/certs/client_ca.pem")}; mg_tls_ctx_init(&mgr, &opts);
- If you need to do something once the TLS handshake is finished, handle the
MG_EV_TLS_HS
event: - Rebuild the application, see build instructions below
You can see example code in
- The MQTT client example
- The HTTP client example
- The TCP client and server example
- The SMTP client example
Certificates overview
TLS provides two major benefits:
- traffic encryption, which makes it impossible to sniff and look inside the traffic, and
- authentication, which makes it possible to one side of the TLS connection to verify the identity of the other side.
Here we're talking about authentication. Authentication is implemented via certificates. A certificate has two parts - public and private. Talking in practical terms, three files are required to implement TLS authentication:
- TLS certificate. This is a "public" part. For example, a TLS-enabled server sends it to the client during the TLS handshake
- TLS private key. This is a "private" part
- TLS Certificate Authority (CA) file. It is used for verification of the "public" certificate sent by the server
TLS certificates are obtained from services like Let's Encrypt. The other possibility is self-signed certificates, which are mainly used for development.
Self-signed certificates
It is possible to generate certificate files. Note that servers using those files will not be trusted by browsers, because browsers use pre-installed CA files and know nothing about your generated certificates.
The command below uses the openssl
command line tool to generate a self-signed
server certificate and a key file:
$ openssl req -nodes -new -x509 -keyout key.pem -out cert.pem
When generating keys for embedded hardware, you may want to use shorter elliptic-curve cryptography (ECC) options:
$ openssl ecparam -name prime256v1 -genkey -noout -out key.pem
$ openssl req -new -key key.pem -x509 -nodes -out cert.pem
Two-way TLS
Normally, when a client makes a connection to a TLS-enabled server, the server sends its certificate to the client and the client verifies it using its own CA (Certificate Authority) file. This way the client authenticates the server.
In the most common situation, the client verifies the server, but the server does not verify the client. For example, browsers (clients) use a big CA file (or many CA files) to verify HTTPS servers.
Clients can also provide certificates during the TLS handshake, and the server can
verify it using a CA file. When both client and server use certificates,
and verify the other side using a CA file, it is called two-way TLS.
In order to implement two-way TLS, now both client and server must have their own
_cert
, _certkey
and _ca
specified in mg_tls_opts
:
For a server:
struct mg_tls_opts opts = {.server_ca = mg_unpacked("/certs/client_ca.pem"),
.server_cert = mg_unpacked("/certs/server_cert.pem"),
.server_key = mg_unpacked("/certs/server_key.pem")};
mg_tls_ctx_init(&mgr, &opts);
For a client:
struct mg_tls_opts opts = {.client_ca = mg_unpacked("/certs/client_ca.pem"),
.client_cert = mg_unpacked("/certs/client_cert.pem"),
.client_key = mg_unpacked("/certs/client_key.pem")};
mg_tls_ctx_init(&mgr, &opts);
Two-way TLS certificates
It is possible to generate self-signed ca.pem
, and cert + key pairs for
both client and server for two-way (mutual) authentication. This is an
example on how to do it using the openssl
command line tool:
## Common parameters
$ SUBJ="/C=IE/ST=Dublin/L=Docks/O=MyCompany/CN=howdy"
## Generate CA
$ openssl genrsa -out ca.key 2048
$ openssl req -new -x509 -days 365 -key ca.key -out ca.crt \
-subj /C=IE/ST=Dublin/L=Docks/O=mos/CN=me
## Generate client cert
$ openssl genrsa -out client.key 2048
$ openssl req -new -key client.key -out client.csr -subj $SUBJ
$ openssl x509 -req -days 365 -in client.csr -CA ca.crt \
-CAkey ca.key -set_serial 01 -out client.crt
## Generate server cert
$ openssl genrsa -out server.key 2048
$ openssl req -new -key server.key -out server.csr -subj $SUBJ
$ openssl x509 -req -days 365 -in server.csr -CA ca.crt \
-CAkey ca.key -set_serial 01 -out server.crt
How to load credentials
Certificates and keys can be loaded from a file in a supported filesystem using
mg_file_read
:size_t size = 0; // read CA certs from plain file char *data = mg_file_read(&mg_fs_posix, "test/data/ca.pem", &size); struct mg_tls_opts opts = {.client_ca = mg_str_n(data, size)}; ... free(data);
This copies the file in memory that has to be freed later when the program exits.
They can also be loaded from a file in an embedded filesystem, using
mg_unpacked()
as shown above:struct mg_tls_opts opts = {.client_ca = mg_unpacked("/certs/client_ca.pem")};
This accesses the file directly from the code, where it has been embedded.
Finally, certificates and keys can be defined as C strings in source code:
How to build
Use the MG_TLS
macro in your compile options:
MG_TLS=MG_TLS_MBED
to build using MbedTLSMG_TLS=MG_TLS_OPENSSL
to build using OpenSSL
Most examples can pull and build a known and tested version of MbedTLS. Then, depending on your operating system, you may have additional options
The following instructions assume you've already followed the Build Tools tutorial to setup your development environment.
Embedded hardware
Some platforms include support for MbedTLS. In CMake platforms, like Espressif SDK, it is just a matter of setting the proper Mongoose compile option:
component_compile_options(-DMG_TLS=MG_TLS_MBED)
See for example the device-dashboard tutorial for the ESP32.
For bare metal examples, some include support to clone MbedTLS and build with it. Just run:
make TLS=mbedtls
See for example the device-dashboard tutorial for the STM32 series.
Linux
Assuming Ubuntu Linux, start a terminal and install the library of your choice:
sudo apt -y update
sudo apt -y install libmbedtls-dev
sudo apt -y install libssl-dev
Then use the CFLAGS_EXTRA
argument to pass the necessary additional compile options, as follows:
make CFLAGS_EXTRA="-DMG_TLS=MBED -lmbedtls -lmbedcrypto -lmbedx509"
to build for MbedTLSmake CFLAGS_EXTRA="-DMG_TLS=OPENSSL -lssl -lcrypto"
to build for OpenSSL
For other similar distributions and operating systems, check pkg-config below
MacOS
Start a terminal, and install the library of your choice:
brew install mbedtls
brew install openssl
Then use the CFLAGS_EXTRA
argument to pass the necessary additional compile options, as follows:
make CFLAGS_EXTRA="-DMG_TLS=MBED -lmbedtls -lmbedcrypto -lmbedx509 -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib"
to build for MbedTLSmake CFLAGS_EXTRA="-DMG_TLS=OPENSSL -lssl -lcrypto -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib"
to build for OpenSSL
If you see compiler errors messages, see pkg-config below
Windows
Only what the example provides in its Makefile is supported
pkg-config
If, when using your system libraries, you see a compiler error message like "Cannot find .... file", your system needs extra paths for includes and/or libs, and/or link options; usually the former. This means adding -I path/to/include
to tell where installed TLS headers are, and/or -L path/to/lib
to tell where installed TLS shared libs are.
These paths and link options are usually determined in GNU/Linux-like systems using pkg-config
. Sometimes the package uses the standard paths and no extra paths are needed; others they are installed in a different path to avoid conflicts and flags are required:
$ pkg-config --cflags --libs openssl
-lssl -lcrypto
$ pkg-config --cflags --libs openssl11
-I/usr/include/openssl11 -L/usr/lib64/openssl11 -lssl -lcrypto
$ pkg-config --cflags --libs-only-L openssl11
-I/usr/include/openssl11 -L/usr/lib64/openssl11
$ pkg-config --libs-only-l openssl11
-lssl -lcrypto
This requires that the relevant packages have installed their packagename.pc files in the default path where pkg-config
will search for them; otherwise you have to set the PKG_CONFIG_PATH
environment variable to where they reside.
To pass these extra options to the make command, extend the CFLAGS_EXTRA
argument as, for example, we did for MacOS above.
The link options are mostly the same among systems and usually there is no need to add to them.
Check your system for relevant paths and compiler flags