SETTING UP MUTUAL TLS (mTLS) BETWEEN A TIBBO DEVICE AND A SERVER
Applies to: Tibbo G3 devices (e.g. TPP2W(G3))
Sample project: HTTPS_MTLS [LINK: HTTPS_MTLS download]
OVERVIEW
In ordinary TLS only the client checks the server: the server presents a certificate and the client verifies it. In mutual TLS (mTLS) the server also asks the client for a certificate and refuses the connection unless the client presents one it trusts. Cloud IoT services such as AWS IoT Core use mTLS to make sure that only registered devices can connect.
When the Tibbo device is the mTLS client, it needs three things, stored together in one file (the mTLS bundle) that is added to the project and loaded with sock.tlsinit():
- the certificate used to check the server (as in ordinary outbound TLS);
- the device's own client certificate, which it presents to the server;
- the device's private key, with which it proves that the certificate is its own.
The server, in turn, must be configured to require a client certificate and to trust the device's certificate.
This article uses XAMPP (Apache) on a PC as the server and self-signed certificates on both sides. It shows how to create the certificates and the bundle with OpenSSL, configure Apache to require a client certificate, test from the PC, and run the HTTPS_MTLS sample. The sample sends one HTTPS GET request when you press the MD button and prints the response, which shows the client certificate the server received.
The steps are given for both ECDSA P-256 and RSA 2048 keys; we suggest using ECDSA P-256 for higher security and connection speed versus RSA 2048.
- ECDSA P-256 : Smaller and faster. The socket buffers in the sample (6 pages RX and TX) are sized for this key type.
- RSA 2048 : Widest compatibility. Needs larger buffers: set TLS_RX_BUFF and TLS_TX_BUFF to 8 pages.
WHAT YOU NEED
- OpenSSL 3.x on your PC.
- TIDE with the HTTPS_MTLS sample project.
- XAMPP on the PC, with Apache (mod_ssl is included and loaded by default) and PHP.
- The IP addresses. The examples use 192.168.1.70 for the PC and 192.168.1.218 for the device; replace them with your own addresses.
All commands below are written on a single line so they work the same in Command Prompt, PowerShell and Git Bash.
IMPORTANT: THE DEVICE CERTIFICATE MUST ALLOW CLIENT USE
A certificate can state what it may be used for. A certificate that allows only server use (extendedKeyUsage = serverAuth) is rejected by Apache when it is presented by a client. The device's client certificate in Part 2 is created with extendedKeyUsage = clientAuth for this reason; do not reuse a device server certificate for mTLS.
PART 1 - CREATE THE SERVER CERTIFICATE (XAMPP)
The examples use a PC at 192.168.1.70. Replace it everywhere with the address of your server.
Option A: ECDSA P-256
Step 1. Create a configuration file named server_ec.cnf with this content:
[req]
prompt = no
distinguished_name = dn
x509_extensions = v3_req
[dn]
CN = 192.168.1.70
[v3_req]
basicConstraints = critical,CA:TRUE
subjectAltName = @alt_names
[alt_names]
IP.1 = 192.168.1.70
Step 2. Generate the private key:
openssl ecparam -name prime256v1 -genkey -noout -out server_ec.key
Step 3. Create the self-signed certificate, valid for 10 years:
openssl req -new -x509 -key server_ec.key -out server_ec.crt -days 3650 -config server_ec.cnf
Option B: RSA 2048
Step 1. Create a configuration file named server_rsa.cnf with this content:
[req]
default_bits = 2048
prompt = no
distinguished_name = dn
x509_extensions = v3_req
[dn]
CN = 192.168.1.70
[v3_req]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
IP.1 = 192.168.1.70
Step 2. Generate the private key:
openssl genrsa -out server_rsa.key 2048
Step 3. Create the self-signed certificate, valid for 10 years:
openssl req -new -x509 -key server_rsa.key -out server_rsa.crt -days 3650 -config server_rsa.cnf
Check the certificate and confirm the SAN contains the server's address (use server_rsa.crt for RSA):
openssl x509 -in server_ec.crt -noout -ext subjectAltNameExpected output:
X509v3 Subject Alternative Name: IP Address:192.168.1.70The .crt and .key files are used by the server on the PC. The .key file is secret and stays on the PC; it never goes to the Tibbo device.
Convert the server certificate to DER; this is the first part of the mTLS bundle (use server_rsa.crt for RSA):
openssl x509 -in server_ec.crt -outform DER -out server_ec_cert.der
PART 2 - CREATE THE DEVICE CLIENT CERTIFICATE
Option A: ECDSA P-256
Step 1. Create a configuration file named client_ec.cnf with this content. CN is the name the server will see; it does not need to be an IP address:
[req]
prompt = no
distinguished_name = dn
x509_extensions = v3_req
[dn]
CN = tibbo-device-218
O = Tibbo Test
[v3_req]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature
extendedKeyUsage = clientAuth
Step 2. Generate the private key:
openssl ecparam -name prime256v1 -genkey -noout -out client_ec.key
Step 3. Create the self-signed certificate, valid for 10 years:
openssl req -new -x509 -key client_ec.key -out client_ec.crt -days 3650 -config client_ec.cnf
Step 4. Convert the certificate and the key to DER (binary) format:
openssl x509 -in client_ec.crt -outform DER -out client_ec_cert.der
openssl ec -in client_ec.key -outform DER -out client_ec_key.der
Option B: RSA 2048
Use the same configuration file with default_bits = 2048 added under [req], save it as client_rsa.cnf, and run:
openssl genrsa -out client_rsa.key 2048
openssl req -new -x509 -key client_rsa.key -out client_rsa.crt -days 3650 -config client_rsa.cnf
openssl x509 -in client_rsa.crt -outform DER -out client_rsa_cert.der
openssl pkcs8 -topk8 -nocrypt -in client_rsa.key -outform DER -out client_rsa_key.derThe RSA key is converted to PKCS#8. RSA client certificates also need 8-page socket buffers on the device.
Check that the certificate allows client use (use client_rsa.crt for RSA):
openssl x509 -in client_ec.crt -noout -ext extendedKeyUsageExpected output:
X509v3 Extended Key Usage: TLS Web Client Authentication
PART 3 - BUILD THE mTLS BUNDLE
The bundle is three DER files joined in this order: the server certificate, the device certificate, the device private key.
In Command Prompt (the /b switch is required, it copies the files as binary):
copy /b server_ec_cert.der + client_ec_cert.der + client_ec_key.der mtls_bundle.der
In PowerShell, run the same command through Command Prompt:
cmd /c copy /b server_ec_cert.der + client_ec_cert.der + client_ec_key.der mtls_bundle.der
In Git Bash, Linux or macOS:
cat server_ec_cert.der client_ec_cert.der client_ec_key.der > mtls_bundle.derFor RSA, use the RSA files instead.
Check the result: the size of mtls_bundle.der must equal the three input files added together. As a guide, an all-ECDSA bundle is roughly 940 bytes and a bundle with an RSA client certificate roughly 2.4 KB.
For the negative test in Part 6, also keep a copy of the server certificate on its own as ca_only.der:
copy /b server_ec_cert.der ca_only.derKeep the key private
mtls_bundle.der contains the device's private key and is compiled into the firmware. Do not publish the bundle, the .key files or the *_key.der files. Anyone who has the device's key can connect to the server as that device.
PART 4 - CONFIGURE XAMPP TO REQUIRE A CLIENT CERTIFICATE
The configuration below adds a separate HTTPS site on port 4443, so the standard XAMPP site on port 443 is not affected.
Step 1. Create the folder C:\xampp\apache\conf\mtls\ and copy into it: server_ec.crt, server_ec.key and client_ec.crt (or the RSA files).
Step 2. Save the following as C:\xampp\apache\conf\extra\httpd-mtls.conf:
# mTLS test site for the HTTPS_MTLS sample.
# Save as C:\xampp\apache\conf\extra\httpd-mtls.conf and add this line to the end of
# C:\xampp\apache\conf\httpd.conf:
# Include conf/extra/httpd-mtls.conf
Listen 4443
<VirtualHost _default_:4443>
DocumentRoot "C:/xampp/htdocs/mtls"
<Directory "C:/xampp/htdocs/mtls">
Require all granted
</Directory>
SSLEngine on
SSLProtocol -all +TLSv1.3
# The server's own certificate and key
SSLCertificateFile "conf/mtls/server_ec.crt"
SSLCertificateKeyFile "conf/mtls/server_ec.key"
# Client certificates Apache accepts. With self-signed certificates this is the
# device certificate itself; add one certificate per device to the same file.
SSLCACertificateFile "conf/mtls/client_ec.crt"
# Require a client certificate for the whole site. Keep this at VirtualHost level:
# requesting it per directory needs post-handshake authentication in TLS 1.3.
SSLVerifyClient require
SSLVerifyDepth 1
# Make the client certificate details available to PHP
SSLOptions +StdEnvVars
ErrorLog "logs/mtls_error.log"
CustomLog "logs/mtls_access.log" common
</VirtualHost>Step 3. Add this line to the end of C:\xampp\apache\conf\httpd.conf:
Include conf/extra/httpd-mtls.confStep 4. Create the folder C:\xampp\htdocs\mtls\ and save the following in it as mtls.php. It reports the client certificate Apache received:
<?php
// Reports what Apache learned about the TLS client. Used to confirm mTLS from the device.
header('Content-Type: text/plain');
echo "mTLS OK\n";
echo "Client verify: " . ($_SERVER['SSL_CLIENT_VERIFY'] ?? 'none') . "\n";
echo "Client CN: " . ($_SERVER['SSL_CLIENT_S_DN_CN'] ?? 'none') . "\n";
echo "Protocol: " . ($_SERVER['SSL_PROTOCOL'] ?? 'none') . "\n";
echo "Cipher: " . ($_SERVER['SSL_CIPHER'] ?? 'none') . "\n";Step 5. Restart Apache from the XAMPP Control Panel and allow port 4443 through Windows Firewall.
The Include line in Step 3 is required. Without it Apache never reads httpd-mtls.conf and does not listen on port 4443; the device then reports "Connection closed" right after "Connecting...". Note that the XAMPP Control Panel lists only the ports from its own settings (80, 443), not 4443. To check that Apache is listening, run this in Command Prompt and look for 0.0.0.0:4443 ... LISTENING:
netstat -ano | findstr :4443Apache may log "server certificate is a CA certificate" for the self-signed ECDSA server certificate. The warning is harmless.
Why SSLVerifyClient is set for the whole site
Apache can also require a client certificate for a single folder only. In TLS 1.3 that needs a second certificate request after the handshake (post-handshake authentication), which many embedded clients, including the device, are not designed for. Keep SSLVerifyClient require at VirtualHost level, as above.
More than one device
With self-signed client certificates, Apache trusts exactly the certificates listed in SSLCACertificateFile. To admit more devices, give each its own client certificate and append each .crt file to that file (one after another, as text), then restart Apache.
PART 5 - TEST FROM THE PC
Before involving the device, check the server with OpenSSL using the same certificates:
openssl s_client -connect 192.168.1.70:4443 -CAfile server_ec.crt -cert client_ec.crt -key client_ec.key -quietThen type these lines, pressing Enter after each one and once more at the end:
GET /mtls.php HTTP/1.1
Host: 192.168.1.70
Connection: closeExpected output, after the HTTP headers:
mTLS OK
Client verify: SUCCESS
Client CN: tibbo-device-218
Protocol: TLSv1.3
Cipher: TLS_AES_256_GCM_SHA384Run the command again without -cert and -key. The server must now refuse the connection with "tlsv13 alert certificate required" (alert number 116). This confirms that the server really requires a client certificate.
PART 6 - CONFIGURE AND RUN HTTPS_MTLS
Step 1. Copy mtls_bundle.der and ca_only.der into the HTTPS_MTLS project folder, replacing the existing files. In TIDE, make sure both are part of the project.
Step 2. Check the settings in global.tbh:
const DEVICE_IP = "192.168.1.218"
const DEVICE_MASK = "255.255.255.0"
const DEVICE_GW = "192.168.1.1"
const SERVER_IP = "192.168.1.70" must match the IP in the server certificate
const SERVER_PORT = 4443
const SERVER_PATH = "/mtls.php"
#define CERT_FILE "mtls_bundle.der"
#define TLS_RX_BUFF 6 use 8 for RSA 2048
#define TLS_TX_BUFF 6 use 8 for RSA 2048Step 3. Build the project and upload it to the device. Wait for "HTTPS_MTLS ready" and press MD.
How the sample uses the bundle: when the TCP connection to the server is established (PL_SST_EST_AOPENED), the socket loads the bundle and starts the handshake, exactly as for ordinary outbound TLS:
romfile.open(CERT_FILE)
tls_res = sock.tlsinit(romfile.offset)
tls_res = sock.tlshandshake(SERVER_IP)Because the bundle also contains the device's certificate and key, the device can answer the server's certificate request. The debug output then shows the request and the server's answer:
Connecting to 192.168.1.70:4443...
TCP connected. Starting TLS with client certificate...
TLS handshake started...
mTLS established. Sending GET /mtls.php
HTTP/1.1 200 OK
...
mTLS OK
Client verify: SUCCESS
Client CN: tibbo-device-218
Protocol: TLSv1.3
Cipher: TLS_AES_256_GCM_SHA384
Response complete (server closed the connection)"Client CN: tibbo-device-218" is the proof that the server received and accepted the device's certificate.
Negative test: a device without a client certificate
Set CERT_FILE to "ca_only.der", rebuild and press MD. The device can still check the server, but has no certificate to present, so the server refuses the handshake and the device reports "TLS handshake FAILED - resetting". Set CERT_FILE back to "mtls_bundle.der" afterwards.
USING A CLOUD SERVICE
Cloud services that use mTLS, such as AWS IoT Core, follow the same structure. The difference is where the three parts come from: the first part is the root certificate of the service's certificate authority, and the device certificate and key are issued by the service when you register the device. Build the bundle in the same order and convert each part to DER as shown above.
TROUBLESHOOTING
"tlsinit FAILED" in the device's debug output
The bundle could not be loaded. Check that mtls_bundle.der is in the project, that its name matches CERT_FILE, and that it was built in the right order: server certificate, device certificate, device key.
"TLS handshake FAILED - resetting" with mtls_bundle.der
Run the PC test in Part 5 first. If it fails too, the problem is in the XAMPP configuration. If it works, check that the bundle was built from the same files that Apache uses, that SERVER_IP matches the server certificate, and, for RSA, that the buffers are 8 pages.
Apache error log shows "unsupported certificate purpose"
The device certificate does not allow client use. Create it with extendedKeyUsage = clientAuth (Part 2).
Apache error log shows "self-signed certificate" or "unknown ca"
The device certificate is not listed in SSLCACertificateFile, or Apache was not restarted after adding it.
"Connection closed" right after "Connecting..."
Nothing is listening on port 4443. Check that httpd.conf contains the Include line from Part 4, Step 3, restart Apache, and check with netstat as shown in Part 4.
Apache does not start after adding the configuration
Check the file paths in httpd-mtls.conf, and that port 4443 is not in use by another program. The XAMPP Control Panel shows the Apache error log.
Comments
0 comments
Please sign in to leave a comment.