Commit d9b43b500c for strongswan.org

commit d9b43b500c205e42452766c07a68560e5c382817
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Mon Sep 14 12:17:58 2026 +0200

    pem: Add support to parse PEM-encoded certificate bundles

    We currently only support concatenated certificates (hard-coded matching
    for BEGIN/END CERTIFICATE boundaries), such as certificate chains.

diff --git a/src/libstrongswan/plugins/pem/Makefile.am b/src/libstrongswan/plugins/pem/Makefile.am
index 55e52511e5..bf718c4017 100644
--- a/src/libstrongswan/plugins/pem/Makefile.am
+++ b/src/libstrongswan/plugins/pem/Makefile.am
@@ -12,6 +12,7 @@ endif

 libstrongswan_pem_la_SOURCES = \
 	pem_plugin.h pem_plugin.c \
+	pem_bundle.c pem_bundle.h \
 	pem_builder.c pem_builder.h \
 	pem_encoder.c pem_encoder.h

diff --git a/src/libstrongswan/plugins/pem/pem_builder.c b/src/libstrongswan/plugins/pem/pem_builder.c
index 217849e89f..5bbca12304 100644
--- a/src/libstrongswan/plugins/pem/pem_builder.c
+++ b/src/libstrongswan/plugins/pem/pem_builder.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2013-2026 Tobias Brunner
  * Copyright (C) 2009 Martin Willi
  * Copyright (C) 2001-2008 Andreas Steffen
  *
@@ -17,6 +17,7 @@
  */

 #include "pem_builder.h"
+#include "pem_bundle.h"

 #include <stdio.h>
 #include <stdlib.h>
@@ -38,6 +39,18 @@

 #define PKCS5_SALT_LEN	8	/* bytes */

+/**
+ * Parser state
+ */
+typedef enum {
+	PEM_PRE    = 0,
+	PEM_MSG    = 1,
+	PEM_HEADER = 2,
+	PEM_BODY   = 3,
+	PEM_POST   = 4,
+	PEM_ABORT  = 5
+} state_t;
+
 /**
  * check the presence of a pattern in a character string, skip if found
  */
@@ -190,15 +203,6 @@ static status_t pem_decrypt(chunk_t *blob, encryption_algorithm_t alg,
  */
 static status_t pem_to_bin(chunk_t *blob, bool *pgp)
 {
-	typedef enum {
-		PEM_PRE    = 0,
-		PEM_MSG    = 1,
-		PEM_HEADER = 2,
-		PEM_BODY   = 3,
-		PEM_POST   = 4,
-		PEM_ABORT  = 5
-	} state_t;
-
 	encryption_algorithm_t alg = ENCR_UNDEFINED;
 	size_t key_size = 0;
 	bool encrypted = FALSE;
@@ -547,3 +551,117 @@ container_t *pem_container_load(container_type_t type, va_list args)
 {
 	return pem_load(CRED_CONTAINER, type, args);
 }
+
+/**
+ * Load a PEM certificate bundle container.
+ */
+container_t *pem_container_load_bundle(container_type_t type, va_list args)
+{
+	char *file = NULL;
+	chunk_t pem = chunk_empty, *mapped = NULL, src, dst, line, data, der;
+	pem_bundle_t *bundle;
+	certificate_t *cert;
+	state_t state = PEM_PRE;
+	bool found = FALSE;
+
+	while (TRUE)
+	{
+		switch (va_arg(args, builder_part_t))
+		{
+			case BUILD_FROM_FILE:
+				file = va_arg(args, char*);
+				continue;
+			case BUILD_BLOB:
+			case BUILD_BLOB_PEM:
+				pem = va_arg(args, chunk_t);
+				continue;
+			case BUILD_END:
+				break;
+			default:
+				return NULL;
+		}
+		break;
+	}
+
+	if (!pem.len && file)
+	{
+		mapped = chunk_map(file, FALSE);
+		if (!mapped)
+		{
+			DBG1(DBG_LIB, "  opening '%s' failed: %s", file, strerror(errno));
+			return NULL;
+		}
+		pem = *mapped;
+	}
+
+	if (is_ber_indefinite_length(pem) || is_asn1(pem))
+	{
+		if (mapped)
+		{
+			chunk_unmap(mapped);
+		}
+		return NULL;
+	}
+
+	pem = chunk_clone(pem);
+	if (mapped)
+	{
+		chunk_unmap(mapped);
+	}
+
+	bundle = pem_bundle_create();
+	src = pem;
+	while (fetchline(&src, &line))
+	{
+		if (state != PEM_BODY)
+		{
+			if (!present("-----BEGIN CERTIFICATE-----", &line))
+			{
+				continue;
+			}
+			state = PEM_BODY;
+			/* decode every certificate inline at the start of the buffer */
+			der = dst = pem;
+			der.len = 0;
+		}
+		else
+		{
+			if (present("-----END CERTIFICATE-----", &line))
+			{
+				cert = lib->creds->create(lib->creds, CRED_CERTIFICATE,
+										  CERT_X509, BUILD_BLOB_ASN1_DER, der,
+										  BUILD_END);
+				if (cert)
+				{
+					bundle->add_cert(bundle, cert);
+					found = TRUE;
+				}
+				state = PEM_PRE;
+				continue;
+			}
+
+			if (!extract_token(&data, ' ', &line))
+			{
+				data = line;
+			}
+			if (dst.len < data.len / 4 * 3)
+			{
+				state = PEM_PRE;
+				continue;
+			}
+			data = chunk_from_base64(data, dst.ptr);
+			dst.ptr += data.len;
+			dst.len -= data.len;
+			der.len += data.len;
+		}
+	}
+
+	chunk_free(&pem);
+
+	if (!found)
+	{
+		bundle->pem.container.destroy(&bundle->pem.container);
+		return NULL;
+	}
+	return &bundle->pem.container;
+}
diff --git a/src/libstrongswan/plugins/pem/pem_builder.h b/src/libstrongswan/plugins/pem/pem_builder.h
index 80dac8ac4e..6b4950eb4b 100644
--- a/src/libstrongswan/plugins/pem/pem_builder.h
+++ b/src/libstrongswan/plugins/pem/pem_builder.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2013-2026 Tobias Brunner
  * Copyright (C) 2009 Martin Willi
  *
  * Copyright (C) secunet Security Networks AG
@@ -65,5 +65,13 @@ certificate_t *pem_certificate_load(certificate_type_t type, va_list args);
  */
 container_t *pem_container_load(container_type_t type, va_list args);

-#endif /** PEM_BUILDER_H_ @}*/
+/**
+ * Load PEM-encoded certificate bundle.
+ *
+ * @param type		type of the container (CONTAINER_PEM)
+ * @param args		builder_part_t argument list
+ * @return 			container, NULL if failed
+ */
+container_t *pem_container_load_bundle(container_type_t type, va_list args);

+#endif /** PEM_BUILDER_H_ @}*/
diff --git a/src/libstrongswan/plugins/pem/pem_bundle.c b/src/libstrongswan/plugins/pem/pem_bundle.c
new file mode 100644
index 0000000000..92ecfbc02a
--- /dev/null
+++ b/src/libstrongswan/plugins/pem/pem_bundle.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2026 Tobias Brunner
+ *
+ * Copyright (C) secunet Security Networks AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+#include "pem_bundle.h"
+
+#include <collections/array.h>
+
+typedef struct private_pem_bundle_t private_pem_bundle_t;
+
+/**
+ * Private data of a pem_t object.
+ */
+struct private_pem_bundle_t {
+
+	/**
+	 * Public interface.
+	 */
+	pem_bundle_t public;
+
+	/**
+	 * Contained certificates.
+	 */
+	array_t *certs;
+};
+
+METHOD(container_t, get_type, container_type_t,
+	private_pem_bundle_t *this)
+{
+	return CONTAINER_PEM;
+}
+
+METHOD(container_t, create_signature_enumerator, enumerator_t*,
+	private_pem_bundle_t *this)
+{
+	return enumerator_create_empty();
+}
+
+METHOD(container_t, get_data, bool,
+	private_pem_bundle_t *this, chunk_t *data)
+{
+	return FALSE;
+}
+
+METHOD(container_t, get_encoding, bool,
+	private_pem_bundle_t *this, chunk_t *data)
+{
+	/* we currently don't have any use for this */
+	return FALSE;
+}
+
+METHOD(pem_t, create_cert_enumerator, enumerator_t*,
+	private_pem_bundle_t *this)
+{
+	return array_create_enumerator(this->certs);
+}
+
+METHOD(pem_bundle_t, add_cert, void,
+	private_pem_bundle_t *this, certificate_t *cert)
+{
+	array_insert_create(&this->certs, ARRAY_TAIL, cert);
+}
+
+METHOD(container_t, destroy, void,
+	private_pem_bundle_t *this)
+{
+	array_destroy_offset(this->certs, offsetof(certificate_t, destroy));
+	free(this);
+}
+
+/*
+ * Described in header
+ */
+pem_bundle_t *pem_bundle_create()
+{
+	private_pem_bundle_t *this;
+
+	INIT(this,
+		.public = {
+			.pem = {
+				.container = {
+					.get_type = _get_type,
+					.create_signature_enumerator = _create_signature_enumerator,
+					.get_data = _get_data,
+					.get_encoding = _get_encoding,
+					.destroy = _destroy,
+				},
+				.create_cert_enumerator = _create_cert_enumerator,
+			},
+			.add_cert = _add_cert,
+		},
+	);
+
+	return &this->public;
+}
diff --git a/src/libstrongswan/plugins/pem/pem_bundle.h b/src/libstrongswan/plugins/pem/pem_bundle.h
new file mode 100644
index 0000000000..7fc7d7a29e
--- /dev/null
+++ b/src/libstrongswan/plugins/pem/pem_bundle.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2026 Tobias Brunner
+ *
+ * Copyright (C) secunet Security Networks AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+/**
+ * @defgroup pem_bundle pem_bundle
+ * @{ @ingroup pem_p
+ */
+
+#ifndef PEM_BUNDLE_H_
+#define PEM_BUNDLE_H_
+
+#include <credentials/certificates/certificate.h>
+#include <credentials/containers/pem.h>
+
+typedef struct pem_bundle_t pem_bundle_t;
+
+/**
+ * PEM certificate bundle container type.
+ */
+struct pem_bundle_t {
+
+	/**
+	 * Implements pem_t.
+	 */
+	pem_t pem;
+
+	/**
+	 * Add a certificate to the bundle.
+	 *
+	 * @param cert		certificate to add (adopted)
+	 */
+	void (*add_cert)(pem_bundle_t *this, certificate_t *cert);
+};
+
+/**
+ * Create an empty PEM certificate bundle container.
+ */
+pem_bundle_t *pem_bundle_create();
+
+#endif /** PEM_BUNDLE_H_ @}*/
diff --git a/src/libstrongswan/plugins/pem/pem_plugin.c b/src/libstrongswan/plugins/pem/pem_plugin.c
index d46906168e..db0fefb14b 100644
--- a/src/libstrongswan/plugins/pem/pem_plugin.c
+++ b/src/libstrongswan/plugins/pem/pem_plugin.c
@@ -124,6 +124,11 @@ METHOD(plugin_t, get_features, int,
 				PLUGIN_DEPENDS(CONTAINER_DECODE, CONTAINER_PKCS12),
 			PLUGIN_PROVIDE(CONTAINER_DECODE, CONTAINER_PKCS7),
 				PLUGIN_DEPENDS(CONTAINER_DECODE, CONTAINER_PKCS7),
+
+		/* PEM certificate bundle decoding */
+		PLUGIN_REGISTER(CONTAINER_DECODE, pem_container_load_bundle, FALSE),
+			PLUGIN_PROVIDE(CONTAINER_DECODE, CONTAINER_PEM),
+				PLUGIN_DEPENDS(CERT_DECODE, CERT_X509),
 	};
 	*features = f;
 	return countof(f);