Commit 06ef6c6d00 for strongswan.org

commit 06ef6c6d00b7b05dd83bf05b1c1d9a40b2f0653b
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Wed Sep 16 09:39:39 2026 +0200

    charon-nm: Add support to load server/CA certificates from a PEM-encoded bundle

    From a bundle, the first non-CA certificate is returned as server
    certificate, which includes files that only contain a single PEM-encoded
    server certificate.

    It also fixes some locking issues in nm-creds.

diff --git a/src/charon-nm/nm/nm_creds.c b/src/charon-nm/nm/nm_creds.c
index 1cc1434859..fa590e8f3c 100644
--- a/src/charon-nm/nm/nm_creds.c
+++ b/src/charon-nm/nm/nm_creds.c
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2011-2026 Tobias Brunner
  * Copyright (C) 2008 Martin Willi
  *
  * This program is free software; you can redistribute it and/or modify it
@@ -163,25 +164,29 @@ METHOD(credential_set_t, create_cert_enumerator, enumerator_t*,
 METHOD(credential_set_t, create_private_enumerator, enumerator_t*,
 	private_nm_creds_t *this, key_type_t type, identification_t *id)
 {
+	this->lock->read_lock(this->lock);
 	if (this->key == NULL)
 	{
-		return NULL;
+		goto no_key;
 	}
 	if (type != KEY_ANY && type != this->key->get_type(this->key))
 	{
-		return NULL;
+		goto no_key;
 	}
 	if (id && id->get_type(id) != ID_ANY)
 	{
 		if (id->get_type(id) != ID_KEY_ID ||
 			!this->key->has_fingerprint(this->key, id->get_encoding(id)))
 		{
-			return NULL;
+			goto no_key;
 		}
 	}
-	this->lock->read_lock(this->lock);
 	return enumerator_create_cleaner(enumerator_create_single(this->key, NULL),
 									 (void*)this->lock->unlock, this->lock);
+
+no_key:
+	this->lock->unlock(this->lock);
+	return NULL;
 }

 /**
@@ -288,6 +293,15 @@ no_secret:
 METHOD(nm_creds_t, add_certificate, void,
 	private_nm_creds_t *this, certificate_t *cert)
 {
+	x509_t *x509 = (x509_t*)cert;
+	x509_flag_t flags DBG_UNUSED = x509->get_flags(x509);
+	bool is_ca DBG_UNUSED = flags & X509_CA;
+	bool is_im DBG_UNUSED = is_ca && !(flags & X509_SELF_SIGNED);
+
+	DBG2(DBG_CFG, "loaded %s%scertificate '%Y'",
+		 is_im ? "intermediate " : "", is_ca ? "CA " : "",
+		 cert->get_subject(cert));
+
 	this->lock->write_lock(this->lock);
 	this->certs->insert_last(this->certs, cert);
 	this->lock->unlock(this->lock);
@@ -300,7 +314,6 @@ static void load_ca_file(private_nm_creds_t *this, char *file)
 {
 	certificate_t *cert;

-	/* We add the CA constraint, as many CAs miss it */
 	cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
 							  BUILD_FROM_FILE, file, BUILD_END);
 	if (!cert)
@@ -309,13 +322,7 @@ static void load_ca_file(private_nm_creds_t *this, char *file)
 	}
 	else
 	{
-		DBG2(DBG_CFG, "loaded CA certificate '%Y'", cert->get_subject(cert));
-		x509_t *x509 = (x509_t*)cert;
-		if (!(x509->get_flags(x509) & X509_SELF_SIGNED))
-		{
-			DBG1(DBG_CFG, "%Y is not self signed", cert->get_subject(cert));
-		}
-		this->certs->insert_last(this->certs, cert);
+		add_certificate(this, cert);
 	}
 }

@@ -395,6 +402,7 @@ METHOD(nm_creds_t, clear, void,
 {
 	certificate_t *cert;

+	this->lock->write_lock(this->lock);
 	while (this->certs->remove_last(this->certs, (void**)&cert) == SUCCESS)
 	{
 		cert->destroy(cert);
@@ -411,6 +419,7 @@ METHOD(nm_creds_t, clear, void,
 	this->user = NULL;
 	this->keypass = NULL;
 	this->keyid = chunk_empty;
+	this->lock->unlock(this->lock);
 }

 METHOD(nm_creds_t, destroy, void,
diff --git a/src/charon-nm/nm/nm_creds.h b/src/charon-nm/nm/nm_creds.h
index 15b0f3068c..9669c79463 100644
--- a/src/charon-nm/nm/nm_creds.h
+++ b/src/charon-nm/nm/nm_creds.h
@@ -38,7 +38,7 @@ struct nm_creds_t {
 	/**
 	 * Add a trusted gateway certificate to serve by this set.
 	 *
-	 * @param cert		certificate to serve
+	 * @param cert		X.509 certificate to serve (adopted)
 	 */
 	void (*add_certificate)(nm_creds_t *this, certificate_t *cert);

diff --git a/src/charon-nm/nm/nm_service.c b/src/charon-nm/nm/nm_service.c
index 43504cd9cc..81b5d7983b 100644
--- a/src/charon-nm/nm/nm_service.c
+++ b/src/charon-nm/nm/nm_service.c
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2017 Lubomir Rintel
- * Copyright (C) 2013-2023 Tobias Brunner
+ * Copyright (C) 2013-2026 Tobias Brunner
  * Copyright (C) 2008-2009 Martin Willi
  *
  * This program is free software; you can redistribute it and/or modify it
@@ -26,6 +26,7 @@
 #include <utils/identification.h>
 #include <config/peer_cfg.h>
 #include <credentials/certificates/x509.h>
+#include <credentials/containers/pem.h>
 #include <networking/tun_device.h>
 #include <plugins/kernel_netlink/kernel_netlink_xfrmi.h>

@@ -857,6 +858,66 @@ static bool add_traffic_selectors(child_cfg_t *child_cfg, bool local,
 	return TRUE;
 }

+/**
+ * Add the given certificate to the credential set (adopted) and return a
+ * reference if it's the first end entity certificate.
+ */
+static void add_certificate(NMStrongswanPluginPrivate *priv,
+							certificate_t *cert, certificate_t **ee_cert)
+{
+	x509_t *x509;
+
+	x509 = (x509_t*)cert;
+	if (!*ee_cert && !(x509->get_flags(x509) & X509_CA))
+	{
+		*ee_cert = cert->get_ref(cert);
+	}
+	priv->creds->add_certificate(priv->creds, cert);
+}
+
+/**
+ * Try to load the given blob as bundle first, otherwise as a single
+ * certificate.  Returns the loaded end entity certificate (first non-CA cert).
+ */
+static bool load_trusted_certificates(NMStrongswanPluginPrivate *priv,
+									  chunk_t file, certificate_t **ee_cert)
+{
+	enumerator_t *enumerator;
+	certificate_t *cert;
+	pem_t *bundle = NULL;
+	int added = 0;
+
+	bundle = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PEM,
+								BUILD_BLOB, file, BUILD_END);
+	if (bundle)
+	{
+		enumerator = bundle->create_cert_enumerator(bundle);
+		while (enumerator->enumerate(enumerator, &cert))
+		{
+			if (cert->get_type(cert) == CERT_X509)
+			{
+				add_certificate(priv, cert->get_ref(cert), ee_cert);
+				added++;
+			}
+		}
+		enumerator->destroy(enumerator);
+		bundle->container.destroy(&bundle->container);
+
+		if (added)
+		{
+			return TRUE;
+		}
+	}
+	cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
+							  BUILD_BLOB, file, BUILD_END);
+	if (cert)
+	{
+		add_certificate(priv, cert, ee_cert);
+		return TRUE;
+	}
+	return FALSE;
+}
+
 /**
  * Connect function called from NM via DBUS
  */
@@ -879,7 +940,6 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection,
 	ike_sa_t *ike_sa;
 	auth_cfg_t *auth;
 	certificate_t *cert = NULL;
-	x509_t *x509;
 	bool loose_gateway_id = FALSE;
 	ike_cfg_create_t ike = {
 		.version = IKEV2,
@@ -960,17 +1020,15 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection,
 		{
 			return FALSE;
 		}
-		cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
-								  BUILD_BLOB, safe_file, BUILD_END);
-		chunk_clear(&safe_file);
-		if (!cert)
+		if (!load_trusted_certificates(priv, safe_file, &cert))
 		{
+			chunk_clear(&safe_file);
 			g_set_error(err, NM_VPN_PLUGIN_ERROR,
 						NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
 						"Loading gateway certificate failed.");
 			return FALSE;
 		}
-		priv->creds->add_certificate(priv->creds, cert);
+		chunk_clear(&safe_file);
 	}
 	else
 	{
@@ -986,13 +1044,11 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection,
 	}
 	else if (cert)
 	{
-		x509 = (x509_t*)cert;
-		if (!(x509->get_flags(x509) & X509_CA))
-		{	/* for server certificates, we use the subject as identity */
-			gateway = cert->get_subject(cert);
-			gateway = gateway->clone(gateway);
-		}
+		/* for server certificates, we use the subject as identity */
+		gateway = cert->get_subject(cert);
+		gateway = gateway->clone(gateway);
 	}
+	DESTROY_IF(cert);
 	if (!gateway || gateway->get_type(gateway) == ID_ANY)
 	{
 		/* if the user configured a CA certificate (or an invalid identity),