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);