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"
.