Commit 0d759391cc for strongswan.org

commit 0d759391cc9aa5552c59fc35daa2dc186a133311
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Mon Sep 14 16:22:32 2026 +0200

    pki: Add option to create a PEM-encoded bundle of certificates

    Compared to just using `cat` this has the advantage of supporting
    DER-encoded certificates and only adding successfully parsed certificates
    to the output.

diff --git a/src/pki/commands/bundle.c b/src/pki/commands/bundle.c
index a0e5c71fdb..3d1ecd78eb 100644
--- a/src/pki/commands/bundle.c
+++ b/src/pki/commands/bundle.c
@@ -18,6 +18,8 @@

 #include "pki.h"

+#include <asn1/asn1.h>
+#include <collections/array.h>
 #include <credentials/certificates/x509.h>
 #include <credentials/containers/pem.h>

@@ -91,6 +93,115 @@ static int export(pem_t *bundle, int index, char *outform)
 	return 1;
 }

+/**
+ * Create a bundle file from a list of certificates
+ */
+static int create(array_t *certs)
+{
+	enumerator_t *enumerator;
+	certificate_t *cert;
+	chunk_t encoding;
+
+	if (!array_count(certs))
+	{
+		return command_usage("missing certificates to create bundle");
+	}
+
+	enumerator = array_create_enumerator(certs);
+	while (enumerator->enumerate(enumerator, &cert))
+	{
+		if (cert->get_encoding(cert, CERT_PEM, &encoding))
+		{
+			if (fwrite(encoding.ptr, encoding.len, 1, stdout) != 1)
+			{
+				fprintf(stderr, "failed to write certificate to stdout\n");
+				free(encoding.ptr);
+				enumerator->destroy(enumerator);
+				return 1;
+			}
+			free(encoding.ptr);
+		}
+		else
+		{
+			fprintf(stderr, "unable to encode certificate in PEM format\n");
+			enumerator->destroy(enumerator);
+			return 1;
+		}
+	}
+	enumerator->destroy(enumerator);
+	return 0;
+}
+
+/**
+ * Add the parsed certificate/bundle to the list
+ */
+static bool add_certs(array_t **certs, char *path)
+{
+	certificate_t *cert;
+	enumerator_t *enumerator;
+	pem_t *pem = NULL;
+	chunk_t *mapped = NULL, chunk = chunk_empty;
+	bool success = FALSE;
+
+	if (streq(path, "-"))
+	{
+		set_file_mode(stdin, CERT_ASN1_DER);
+		if (!chunk_from_fd(0, &chunk))
+		{
+			fprintf(stderr, "reading input failed: %s\n", strerror(errno));
+			return FALSE;
+		}
+	}
+	else
+	{
+		mapped = chunk_map(path, FALSE);
+		if (!mapped)
+		{
+			fprintf(stderr, "reading '%s' failed: %s\n", path, strerror(errno));
+			return FALSE;
+		}
+		chunk = *mapped;
+	}
+
+	if (!is_asn1(chunk))
+	{
+		pem = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PEM,
+								 BUILD_BLOB, chunk, BUILD_END);
+	}
+	if (pem)
+	{
+		enumerator = pem->create_cert_enumerator(pem);
+		while (enumerator->enumerate(enumerator, &cert))
+		{
+			array_insert_create(certs, ARRAY_TAIL, cert->get_ref(cert));
+		}
+		enumerator->destroy(enumerator);
+		pem->container.destroy(&pem->container);
+	}
+	else
+	{
+		cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
+								  BUILD_BLOB, chunk, BUILD_END);
+		if (!cert)
+		{
+			goto out;
+		}
+		array_insert_create(certs, ARRAY_TAIL, cert);
+	}
+	success = TRUE;
+
+out:
+	if (mapped)
+	{
+		chunk_unmap(mapped);
+	}
+	else
+	{
+		free(chunk.ptr);
+	}
+	return success;
+}
+
 /**
  * Handle PEM certificate bundles
  */
@@ -98,11 +209,13 @@ static int bundle()
 {
 	char *arg, *file = NULL, *outform = NULL;
 	pem_t *pem = NULL;
+	array_t *certs = NULL;
 	int res = 1, index = 0;
 	enum {
 		OP_NONE,
 		OP_LIST,
 		OP_EXPORT,
+		OP_CREATE,
 	} op = OP_NONE;

 	while (TRUE)
@@ -110,6 +223,7 @@ static int bundle()
 		switch (command_getopt(&arg))
 		{
 			case 'h':
+				array_destroy_offset(certs, offsetof(certificate_t, destroy));
 				return command_usage(NULL);
 			case 'i':
 				file = arg;
@@ -132,39 +246,57 @@ static int bundle()
 			case 'f':
 				outform = arg;
 				continue;
+			case 'C':
+				if (op != OP_NONE)
+				{
+					goto invalid;
+				}
+				op = OP_CREATE;
+				continue;
+			case 'c':
+				if (!add_certs(&certs, arg))
+				{
+					array_destroy_offset(certs, offsetof(certificate_t, destroy));
+					return command_usage("parsing certificate failed");
+				}
+				continue;
 			case EOF:
 				break;
 			default:
 			invalid:
+				array_destroy_offset(certs, offsetof(certificate_t, destroy));
 				return command_usage("invalid --bundle option");
 		}
 		break;
 	}

-	if (file)
+	if (op != OP_CREATE)
 	{
-		pem = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PEM,
-								 BUILD_FROM_FILE, file, BUILD_END);
-	}
-	else
-	{
-		chunk_t chunk;
-
-		set_file_mode(stdin, CERT_ASN1_DER);
-		if (!chunk_from_fd(0, &chunk))
+		if (file)
 		{
-			fprintf(stderr, "reading input failed: %s\n", strerror(errno));
-			return 1;
+			pem = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PEM,
+									 BUILD_FROM_FILE, file, BUILD_END);
 		}
-		pem = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PEM,
-								 BUILD_BLOB, chunk, BUILD_END);
-		free(chunk.ptr);
-	}
+		else
+		{
+			chunk_t chunk;

-	if (!pem)
-	{
-		fprintf(stderr, "reading input failed!\n");
-		goto end;
+			set_file_mode(stdin, CERT_ASN1_DER);
+			if (!chunk_from_fd(0, &chunk))
+			{
+				fprintf(stderr, "reading input failed: %s\n", strerror(errno));
+				goto end;
+			}
+			pem = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PEM,
+									 BUILD_BLOB, chunk, BUILD_END);
+			free(chunk.ptr);
+		}
+
+		if (!pem)
+		{
+			fprintf(stderr, "reading input failed!\n");
+			goto end;
+		}
 	}

 	switch (op)
@@ -175,8 +307,12 @@ static int bundle()
 		case OP_EXPORT:
 			res = export(pem, index, outform);
 			break;
+		case OP_CREATE:
+			res = create(certs);
+			break;
 		default:
 			pem->container.destroy(&pem->container);
+			array_destroy_offset(certs, offsetof(certificate_t, destroy));
 			return command_usage(NULL);
 	}

@@ -185,6 +321,7 @@ end:
 	{
 		pem->container.destroy(&pem->container);
 	}
+	array_destroy_offset(certs, offsetof(certificate_t, destroy));
 	return res;
 }

@@ -195,7 +332,7 @@ static void __attribute__ ((constructor))reg()
 {
 	command_register((command_t) {
 		bundle, 'b', "bundle", "PEM certificate bundle functions",
-		{"--export index|--list [--in file]",
+		{"--export index|--list [--in file]|--create [--cert -|file]+",
 		 "[--outform der|pem]"},
 		{
 			{"help",	'h', 0, "show usage information"},
@@ -203,6 +340,8 @@ static void __attribute__ ((constructor))reg()
 			{"list",	'l', 0, "list certificates"},
 			{"export",	'e', 1, "export the certificate with the given index"},
 			{"outform",	'f', 1, "encoding of exported certificate, default: der"},
+			{"create",	'C', 0, "create a bundle from multiple certificates"},
+			{"cert",	'c', 1, "certificate/bundle to place into a new bundle"},
 		}
 	});
 }
diff --git a/src/pki/man/pki---bundle.1.in b/src/pki/man/pki---bundle.1.in
index e766b2edda..85b591f11b 100644
--- a/src/pki/man/pki---bundle.1.in
+++ b/src/pki/man/pki---bundle.1.in
@@ -20,6 +20,12 @@ pki \-\-bundle \- Provides PEM certificate bundle functions
 .YS
 .
 .SY pki\ \-\-bundle
+.BR \-\-create
+.BI \-\-cert\~ -|file
+.OP \-\-debug level
+.YS
+.
+.SY pki\ \-\-bundle
 .BI \-\-options\~ file
 .YS
 .
@@ -60,6 +66,17 @@ Encoding of the exported credential. Either \fIder\fR (ASN.1 DER) or
 .TP
 .BI "\-i, \-\-in " file
 PEM input file. If not given the input is read from \fISTDIN\fR.
+.TP
+.BI "\-C, \-\-create"
+Create a new PEM-encoded certificate bundle from multiple certificates passed
+via \fI\-\-cert\fR. They are added in the order they appear on the command
+line.
+.TP
+.BI "\-c, \-\-cert " file
+PEM- or DER-encoded certificate to add to a new bundle. If \fI\-\fR is passed,
+the input is read from \fISTDIN\fR. Input may be a PEM-encoded certificate
+bundle itself, if so, all certificates are added to the new bundle in order.
+Can be used multiple times.
 .
 .SH "SEE ALSO"
 .