Skip to content

tls: mbedtls: append PEM chain certs to leaf so full chain is sent on the wire#3588

Open
precla wants to merge 32 commits into
warmcat:mainfrom
precla:cert_chain_fix
Open

tls: mbedtls: append PEM chain certs to leaf so full chain is sent on the wire#3588
precla wants to merge 32 commits into
warmcat:mainfrom
precla:cert_chain_fix

Conversation

@precla
Copy link
Copy Markdown
Contributor

@precla precla commented May 13, 2026

issue

Certificate chain within one PEM file is not handled properly when using mbedtls as backend

steps to reproduce:

Link example main.c against static library 'build/lib/libwebsockets.a.':

main.c
#include <libwebsockets.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>

static int interrupted;
static int callback_count;

static int
callback_http(struct lws *wsi, enum lws_callback_reasons reason,
	      void *user, void *in, size_t len)
{
	switch (reason) {
	case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
		lwsl_notice("Connection filter\n");
		break;
	case LWS_CALLBACK_ESTABLISHED:
		lwsl_notice("Client connected\n");
		callback_count++;
		break;
	case LWS_CALLBACK_CLOSED:
		lwsl_notice("Client disconnected\n");
		break;
	case LWS_CALLBACK_HTTP:
		lwsl_notice("HTTP request: %s\n", in ? (char *)in : "(null)");
		lws_return_http_status(wsi, 200, NULL);
		return -1;
	default:
		break;
	}

	return 0;
}

static const struct lws_protocols protocols[] = {
	{ "http-only", callback_http, 0, 0, 0, NULL, 0 },
	LWS_PROTOCOL_LIST_TERM
};

#if !defined(WIN32)
static void
sigint_handler(int sig)
{
	(void)sig;
	interrupted = 1;
}
#endif

int main(int argc, const char **argv)
{
	struct lws_context_creation_info info;
	struct lws_context *context;
	const char *p;
	int n = 0;

#if !defined(WIN32)
	signal(SIGINT, sigint_handler);
#endif

	memset(&info, 0, sizeof info);

	info.port = 7681;
	if ((p = lws_cmdline_option(argc, argv, "--port")))
		info.port = atoi(p);

	info.protocols = protocols;
	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;

	info.ssl_cert_filepath = lws_cmdline_option(argc, argv, "--cert");
	info.ssl_private_key_filepath = lws_cmdline_option(argc, argv, "--key");

	if (!info.ssl_cert_filepath)
		info.ssl_cert_filepath = "full_chain.pem";
	if (!info.ssl_private_key_filepath)
		info.ssl_private_key_filepath = "full_chain.key";

	lwsl_user("TLS chain cert test server | https://localhost:%d\n", info.port);
	lwsl_user("Run: openssl s_client -connect localhost:%d -showcerts\n", info.port);
	lws_set_log_level(LLL_NOTICE | LLL_INFO | LLL_ERR, NULL);

	context = lws_create_context(&info);
	if (!context) {
		lwsl_err("lws_create_context failed\n");
		return 1;
	}

	while (n >= 0 && !interrupted)
		n = lws_service(context, 100);

	lws_context_destroy(context);

	return 0;
}
gcc -o test-chain main.c -I../include -I../build/include -L../build/lib -lwebsockets -lpthread -Wl,-rpath,../build/lib

save the following certificates:

full_chain.key
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDE8LeuJ6p/wZyl
PXvfHVmnixuUOLKBXzuTaE/crcQKTxyVJR+EXgp5r47zsxh2MIOjrLWXKJyWE72Q
Cgdw++2hEhrZODtNQNqx0uDWmetaQDebrDfWKHCfXW6SVStpRM07n9CxBkUwfEaM
8TQ+ZNuyvVqszYJPYeUDZqwkujejSfP4ZmDGXenR/ACZOkTXXes+tVZCVXWo6qHS
5KFU67mPgtlkeE7LMNwyMlJvvQVx/gSz5q5IZ3YLLe1NoLtrwaPefmBTVY2iI5gY
UuVb6ohXLuEe+m8oi24Y5h9v+jDPnNRXs0cEclQoHhLgI2q6av3U3yn/6WUQhJGh
KkGBkmVRAgMBAAECggEABktFK4kz/0JCTduhrldDSsH83ydSuSTAhC0ExGI/Ti8K
FFAr6jCROCNet7YlFX3mbyAxa4POQVOfLs/LPhanGQs5lXChII5ujtlU+zbPGULN
JCoEXNFNDCLRylZzfTlIL9wjMM0o/+ww2wdIaqgRh2xazgQBYqUXyxyIauISriK8
CBO9aLZY8edjRIyvnkTlPZ0Jpc4cPcvItV8T8guSHzMEdQioYfaGyomu9quOtyZM
nox3FWK3+ZbEPcnD6e8E4Qh4+DYwvM2otEHH1obcgdlcq/eD03J+dKk7OfN1MZI4
86QNmnG5oVKcDMMWNxF0ljMbtfM8LQoc6HPBOi2TWQKBgQDxX6t+BIgBfPEwWpLF
F4OfI1oAd0KqIt0RkZ4OY7tpM5C/pG37B+PSX4d0H3gbemtlUwMfUGYRE1BIZoLl
Si8uB26q9gnC/OnfdHA0Sq3r0m0l06jDkZiErxGHr/Kv8WFBSP0D5eIu5YggnSZa
yKLtTv+dncLehs0sUXFKPI3pPQKBgQDQ38VGzdfnsLs5XaTZxgX90hwsj8NINFex
PJib+MPB/ssXe5SS4JW7KdEf57QHw79ilxXWfRJHSoV6yiYANppd4KyVcPRoMays
Pp/DDtQ6KbcYc1MpjAghMKrd4okLSMMO060/somJS5nfhA57bBuWGITt+QsxX/D4
Vwk+5s5lpQKBgBYII4EnLLrmCqXdPpRa5xq5gVeVizI4aHFYF1M86zhZCpdTjO5i
8/qZ2aVR0gbAD9l50Eklb4uTdbqGEOCY+uF6sDFOr+lqaHaLDErZnjJuTKQtQ7RG
L+O8jx8Rgldo8vWgkeevLgwVy20eqweSVLzQfyiF4+mn6EZpUKv0BwgJAoGBAMeI
XJ6M2Wm9jLz6VharcuHMK5nuI/D//52Sc37cH7Vcv4pJRd8hqCfJhLrjzzlzp4p5
JiVmCWYNLIHmRnMBtmljzbpc0z4N5hQNJvVhXx/I5d33nUQdP07lfPTGCYC8G4o4
cFJfLb/4dp/tOSByX8/80E+9DxI1wq6aBn0OY7D5AoGBAOZMzFuUrkH47/Ms1A2C
p4uzo+gBph1Yz8TH05vOO/qVK2ltBkQOCK7MnAW9Ql9cR71VsAgta/we86FcWGN+
afo9yT3Dp4fChbCjIJJBRSduWHzPA6eVAhQc7m/lYgDEGC23z4FOARFLMdkWfWuP
qRjsKnd9LjlUxzQpP2BDBjn0
-----END PRIVATE KEY-----
full_chain.pem
-----BEGIN CERTIFICATE-----
MIIDUTCCAjmgAwIBAgIUaZ4eiKuiZZz2reArMFCzr2cTZmowDQYJKoZIhvcNAQEL
BQAwJDEiMCAGA1UEAwwZTXkgQ3VzdG9tIEludGVybWVkaWF0ZSBDQTAeFw0yNjA1
MTMxMTEyMDJaFw0yNzA1MTMxMTEyMDJaMBYxFDASBgNVBAMMC215YXBwLmxvY2Fs
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxPC3rieqf8GcpT173x1Z
p4sblDiygV87k2hP3K3ECk8clSUfhF4Kea+O87MYdjCDo6y1lyiclhO9kAoHcPvt
oRIa2Tg7TUDasdLg1pnrWkA3m6w31ihwn11uklUraUTNO5/QsQZFMHxGjPE0PmTb
sr1arM2CT2HlA2asJLo3o0nz+GZgxl3p0fwAmTpE113rPrVWQlV1qOqh0uShVOu5
j4LZZHhOyzDcMjJSb70Fcf4Es+auSGd2Cy3tTaC7a8Gj3n5gU1WNoiOYGFLlW+qI
Vy7hHvpvKItuGOYfb/owz5zUV7NHBHJUKB4S4CNqumr91N8p/+llEISRoSpBgZJl
UQIDAQABo4GIMIGFMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoG
CCsGAQUFBwMBMBYGA1UdEQQPMA2CC215YXBwLmxvY2FsMB0GA1UdDgQWBBSZ5jeW
mD2ntRpC4sk1rcWD3xM9wTAfBgNVHSMEGDAWgBT6GaHOv4S/mm1Eyxz6fRjDrq3q
AjANBgkqhkiG9w0BAQsFAAOCAQEAitXijaCqWZ4VFXnZCfMpFR8xipAU8edlJ72+
/0Wl78vjCsM6lTjlbEBYzvkK/kANYQBvn+YUgeI2md3RS1As2esG5RGe03gbKj/A
MppJFDFz5mCqVu3NHWxAUnu86JXtjBNUo0F12BAsfTplPG7QaIEQDeTiEv6+Y9y6
05LOpzEI4ZBvOhReFvF0JM358bP1+4w+y5myTnyIjBRqNKqFjAawxFMLMBE2nIAZ
fhMd4Bz4CR1hgW0Zay5vGsfMXfFGdMd9/lAL+YESjQ9sUrqfbX/2DlZiIRBrMlrW
J/0WRv7DgUEP0o+cpp1q5wqY0jmja8F5wdcKN1zJth+x4zIHEg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDMTCCAhmgAwIBAgIUUoE69XW5ptD3sWrgsm36e/u0grEwDQYJKoZIhvcNAQEL
BQAwHDEaMBgGA1UEAwwRTXkgQ3VzdG9tIFJvb3QgQ0EwHhcNMjYwNTEzMTExMTM4
WhcNMzEwNTEyMTExMTM4WjAkMSIwIAYDVQQDDBlNeSBDdXN0b20gSW50ZXJtZWRp
YXRlIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqPuXkICVQU2D
ZPuK8Tueln3SKCvYF/oGx7A3GuhPz14V+PkYYm221TuDfHRxvG7VBhREHO422k+J
rATNYdAhhAFdbDJetwG/BFmAUGyv8HqaskkdDlBMyn0eH5Tae7K3P0n/iGcfl9QZ
Pu9+Rgh40PNe94YgUWLV0K6yVWY1aDmJ9zAzlpsOPFYuo4nw8lc7FcHIlJyb6jhr
GBmI2REiozxhUulx7UvXiwOuOEDOkAofyGDzJyvbo13FBgae0f96sMToXUJIz+28
3vLHFzsnf1fNJTaH+2cIyq/66BKOw4cG+hzWDBpCzy+OMAFeVxOndYdRysX9AFcr
giph8J2vZQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQU+hmhzr+Ev5ptRMsc+n0Yw66t6gIwHwYDVR0jBBgwFoAUoqdZ
mqmFYRUVB/NfiVTmBrRBQXgwDQYJKoZIhvcNAQELBQADggEBAFJTjN8jOuGu8z3O
BKuJZRuQIofnBI9Z4on/zEJL59LIXGOT2gPnEpdJg1kfuMMRcWM9FlzZK6UV3m+Y
1EwjtniidL1c9YQKBF3OeFSgE5gdR+vl2N7KUJjPGFDd4RFB1vN3lGi6VTFHOR58
lShrWPXr2qVeGouonO3ToDYpTmk0fnkVtw/SA4KwsHbcYy5lQeTqHn1b+QlnXAGT
GDcX42YWVy4AFJOSx+EJ7Quiz+rLxvRRaJD5zvGQFtUr0ukRoBOraod3qsvIED7S
fA//ltIlNGQEl95juyOT//YyyjydjDmh5n3gPVecKr93fpMucA0BWdlS1X6/J3bO
9JOL4f8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDKTCCAhGgAwIBAgIUXoaeBWMy88if21XUk4tYny1dMGgwDQYJKoZIhvcNAQEL
BQAwHDEaMBgGA1UEAwwRTXkgQ3VzdG9tIFJvb3QgQ0EwHhcNMjYwNTEzMTExMDQ1
WhcNMzYwNTEwMTExMDQ1WjAcMRowGAYDVQQDDBFNeSBDdXN0b20gUm9vdCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJyCesSb6yW8mWxMf6nn6TPK
TWoolHZb8niph+FOI8BgLJfH2W1rwXZz3xB7dS+5grwW3GSBJHJOko1poO0FMNvr
sPMCjMcylu7KBabQJGQnjeSJk0SQn9auiB5eO43Pz8fM/OBYz7AlBHNixLbBJb6t
GVx8s3aGzMA+UgfbLv2+lXdcqYtuwMVQsbiE3E1+/PYlloxu1uwWlsNAP0lCJbT7
ZkCD1NEPXzP23asa1xMf4hZGtsHZzfqLXe4gXelxsQf1TvdXOvOgQ0tJubmndEkB
Ql0U6Gw47fryuiLjO6SBkmZG2bZ6YHiENsXlDlzvrES9eym6ODV40Ngi/ngRmD0C
AwEAAaNjMGEwHQYDVR0OBBYEFKKnWZqphWEVFQfzX4lU5ga0QUF4MB8GA1UdIwQY
MBaAFKKnWZqphWEVFQfzX4lU5ga0QUF4MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQAT/szFJpTU9ZrovpxhOZCQws7V
g7YahmRrIS9PqY3/693mqayhRWaVqJHQuVlJoFJbs1DolGoKvOhapgDDU5VrUepO
/yReoiFwxtcmk7GWdXX+mcToaN4wwug4R1RDr6l1PE1YU/jDJWao2elNXEs1jNiR
YREPwO3CMLy6dpV9xND19WLNhGzJARiUKNGJ4mh+NSeG3VgPvpmT/i4bmwtYuHa5
5U413DoLiGRqxq2cfuqZ9bT/ner8CVWLwGMiJpl3GKXcIawcB3MZwhjH6pjpKDpZ
vgBcGhyuSYW2hmdDShwALOevvHeR+bJKLDsckkM9DW/Qaz6wBZovk3rtYOmy
-----END CERTIFICATE-----

run the binary and test certificate:

test-chain can be run with following arguments:
"--cert" for the .pem file, default file is full_chain.pem
"--key" for the .key file, default file is full_chain.key
"--port" for a custom port, default port is 7681

libwebsocket with mbedtls <= 4.3.10:

openssl s_client -connect localhost:7681 -servername localhost:7681 -showcerts </dev/null 2>/dev/null | grep -E "^( [0-9]|s:|i:)"
 0 s:CN=myapp.local

^ certificate chain is not loaded.

libwebsocket with mbedtls == 4.3.10 + this patch:

openssl s_client -connect localhost:7681 -servername localhost:7681 -showcerts </dev/null 2>/dev/null | grep -E "^( [0-9]|s:|i:)"
 0 s:CN=myapp.local
 1 s:CN=My Custom Intermediate CA
 2 s:CN=My Custom Root CA

^ certificate chain is loaded.

NOTE

i couldn't get the libwebsocket from main branch to load the certificate when mbedtls is used as the backend. so i tested only with v4.3.10 + patch.

I get the following errors when using main branch and the provided .pem + .key file with mbedtls as backend. (same if i use this patch or if i don't use the patch):

E: lws_b64_decode_stateful: non = after =
E: lws_tls_alloc_pem_to_der_file: base64 pem decode failed
E: couldn't load cert file full_chain.pem
E: [vh|2|default||7681]: lws_create_vhost: lws_context_init_server_ssl failed
I: [vh|2|default||7681]: lws_vhost_destroy1: 
I: [vh|2|default||7681]: lws_vhost_destroy: count_bound_wsi 0
I: __lws_lc_untag:  -- [vh|2|default||7681] (2) 2.204ms
E: lws_create_context: Failed to create default vhost

with main + openssl it works fine, same for 4.3.10 + openssl.

Co-developed-by: Opus 4.7

@lws-team lws-team force-pushed the main branch 2 times, most recently from 36b13c7 to a54bc50 Compare May 14, 2026 06:25
@precla
Copy link
Copy Markdown
Contributor Author

precla commented May 14, 2026

@lws-team i see that this has been merged in main. shall we close this PR now?

bdd3b9f

@lws-team lws-team force-pushed the main branch 18 times, most recently from 8e57b3d to a686094 Compare May 20, 2026 15:08
@lws-team lws-team force-pushed the main branch 9 times, most recently from a2e93a1 to eb5ee93 Compare May 30, 2026 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants