Commit 6f67e7cbdd for strongswan.org

commit 6f67e7cbdd8ceac6673bf600eaeab8c04991ae57
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Mon Sep 14 12:20:29 2026 +0200

    swanctl: Add support to load certificates from a PEM-encoded bundle

    We try to parse a certificate file as bundle first, which covers single
    PEM-encoded files as well.  Only attempt to do this for directories
    that contain CAs and regular certificates.  The other dirs are generally
    for single item files (in particular the ones that enforce a flag like
    `x509ocsp` or `x509aa`).  To allow loading full chain bundles that
    include the intermediate CA certificates as well as the end entity
    certificate from both the `x509` and the `x509ca` directory, the flag is
    filtered when a parsed certificate does not contain a CA basic
    constraint (it would get rejected by the vici plugin otherwise).

    Note that the latter changes how PEM files with a single end entity
    certificate in the `x509ca` directory are treated.  Previously, the
    passed flag caused the vici plugin to reject the certificate.  Now it
    gets accepted because the flag is changed.  While technically in the
    wrong location, it shouldn't pose an issue to load such a certificate.

    Another change is the log message, for PEM-encoded certificates the
    subject DN is printed, while for DER-encoded files that's not the
    case as we don't parse them in swanctl.  For PKCS#12 files, the DN
    of extracted certificates was previously written to stderr, that is now
    also changed.

diff --git a/src/swanctl/commands/load_creds.c b/src/swanctl/commands/load_creds.c
index 3fc38456b7..1c0d5288be 100644
--- a/src/swanctl/commands/load_creds.c
+++ b/src/swanctl/commands/load_creds.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016-2017 Tobias Brunner
+ * Copyright (C) 2016-2026 Tobias Brunner
  * Copyright (C) 2015 Andreas Steffen
  * Copyright (C) 2014 Martin Willi
  *
@@ -26,9 +26,11 @@
 #include "swanctl.h"
 #include "load_creds.h"

+#include <asn1/asn1.h>
 #include <credentials/sets/mem_cred.h>
 #include <credentials/sets/callback_cred.h>
 #include <credentials/containers/pkcs12.h>
+#include <credentials/containers/pem.h>
 #include <collections/hashtable.h>

 #include <vici_cert_info.h>
@@ -54,8 +56,8 @@ typedef struct {
 /**
  * Load a single certificate over vici
  */
-static bool load_cert(load_ctx_t *ctx, char *dir, certificate_type_t type,
-					  x509_flag_t flag, chunk_t data)
+static bool load_cert(load_ctx_t *ctx, certificate_t *cert, char *dir,
+					  certificate_type_t type, x509_flag_t flag, chunk_t data)
 {
 	vici_req_t *req;
 	vici_res_t *res;
@@ -87,6 +89,11 @@ static bool load_cert(load_ctx_t *ctx, char *dir, certificate_type_t type,
 				dir, vici_find_str(res, "", "errmsg"));
 		ret = FALSE;
 	}
+	else if (cert)
+	{
+		printf("loaded certificate '%Y' from '%s'\n", cert->get_subject(cert),
+			   dir);
+	}
 	else
 	{
 		printf("loaded certificate from '%s'\n", dir);
@@ -95,6 +102,44 @@ static bool load_cert(load_ctx_t *ctx, char *dir, certificate_type_t type,
 	return ret;
 }

+/**
+ * Load certificates from a PEM-encoded bundle (handles single PEM-encoded
+ * files as well)
+ */
+static void load_bundle(load_ctx_t *ctx, char *dir, certificate_type_t type,
+						x509_flag_t dir_flag, pem_t *bundle)
+{
+	enumerator_t *enumerator;
+	certificate_t *cert;
+	x509_t *x509;
+	chunk_t encoding;
+
+	enumerator = bundle->create_cert_enumerator(bundle);
+	while (enumerator->enumerate(enumerator, &cert))
+	{
+		x509_flag_t cert_flag = dir_flag;
+
+		if (cert->get_type(cert) == type &&
+			cert->get_encoding(cert, CERT_ASN1_DER, &encoding))
+		{
+			/* don't load end entity certificates as CA certificates, the vici
+			 * plugin would reject them */
+			if (type == CERT_X509 && dir_flag == X509_CA)
+			{
+				x509 = (x509_t*)cert;
+				if (!(x509->get_flags(x509) & X509_CA))
+				{
+					cert_flag = X509_NONE;
+				}
+			}
+			load_cert(ctx, cert, dir, type, cert_flag, encoding);
+			free(encoding.ptr);
+		}
+	}
+	enumerator->destroy(enumerator);
+	bundle->container.destroy(&bundle->container);
+}
+
 /**
  * Load certificates from a directory
  */
@@ -122,7 +167,27 @@ static void load_certs(load_ctx_t *ctx, char *type_str, char *dir)
 				map = chunk_map(path, FALSE);
 				if (map)
 				{
-					load_cert(ctx, path, type, flag, *map);
+					pem_t *bundle = NULL;
+
+					/* only load PEM bundles (possibly containing full chains)
+					 * for regular X.509 certificates and CAs. for other types
+					 * and flags it makes not much sense and would even silently
+					 * skip files that contained at least one certificate */
+					if (!is_asn1(*map) && type == CERT_X509 &&
+						(flag == X509_NONE || flag == X509_CA))
+					{
+						bundle = lib->creds->create(lib->creds, CRED_CONTAINER,
+													CONTAINER_PEM, BUILD_BLOB,
+													*map, BUILD_END);
+						if (bundle)
+						{
+							load_bundle(ctx, path, type, flag, bundle);
+						}
+					}
+					if (!bundle)
+					{
+						load_cert(ctx, NULL, path, type, flag, *map);
+					}
 					chunk_unmap(map);
 				}
 				else
@@ -474,11 +539,7 @@ static bool load_pkcs12(load_ctx_t *ctx, char *path, pkcs12_t *p12)
 		loaded = FALSE;
 		if (cert->get_encoding(cert, CERT_ASN1_DER, &encoding))
 		{
-			loaded = load_cert(ctx, path, CERT_X509, X509_NONE, encoding);
-			if (loaded)
-			{
-				fprintf(stderr, "  %Y\n", cert->get_subject(cert));
-			}
+			loaded = load_cert(ctx, cert, path, CERT_X509, X509_NONE, encoding);
 			free(encoding.ptr);
 		}
 		else