[rt-commit] r226 - in /redwax-tool/trunk: ChangeLog redwax-tool.c redwax-tool.h redwax_openssl.c

rt-commit at redwax.eu rt-commit at redwax.eu
Wed Feb 25 22:27:24 CET 2026


Author: minfrin at redwax.eu
Date: Wed Feb 25 22:27:23 2026
New Revision: 226

Log:
Add --pems-out to output certificates as seperate files.

Modified:
    redwax-tool/trunk/ChangeLog
    redwax-tool/trunk/redwax-tool.c
    redwax-tool/trunk/redwax-tool.h
    redwax-tool/trunk/redwax_openssl.c

Modified: redwax-tool/trunk/ChangeLog
==============================================================================
--- redwax-tool/trunk/ChangeLog	(original)
+++ redwax-tool/trunk/ChangeLog	Wed Feb 25 22:27:23 2026
@@ -1,5 +1,8 @@
 
 Changes with v1.0.1
+
+ *) Add --pems-out to output certificates as seperate
+    files. [Graham Leggett]
 
  *) Implement the generation of the secret file when
     the secret file does not exist. [Graham Leggett]

Modified: redwax-tool/trunk/redwax-tool.c
==============================================================================
--- redwax-tool/trunk/redwax-tool.c	(original)
+++ redwax-tool/trunk/redwax-tool.c	Wed Feb 25 22:27:23 2026
@@ -107,6 +107,8 @@
         APR_HOOK_LINK(complete_nss_token_out)
         APR_HOOK_LINK(complete_der_out)
         APR_HOOK_LINK(process_der_out)
+        APR_HOOK_LINK(complete_pems_out)
+        APR_HOOK_LINK(process_pems_out)
         APR_HOOK_LINK(complete_pem_out)
         APR_HOOK_LINK(process_pem_out)
         APR_HOOK_LINK(complete_pkcs12_out)
@@ -200,6 +202,8 @@
         (redwax_tool_t * r, apr_hash_t *tokens), (r, tokens), DECLINED)
 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(rt, REDWAX, int, process_der_out,
         (redwax_tool_t * r, const char *arg, const char *secret), (r, arg, secret), DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(rt, REDWAX, int, process_pems_out,
+        (redwax_tool_t * r, const char *arg, const char *secret), (r, arg, secret), DECLINED)
 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(rt, REDWAX, int, process_pem_out,
         (redwax_tool_t * r, const char *arg, const char *secret), (r, arg, secret), DECLINED)
 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(rt, REDWAX, int, process_pkcs12_out,
@@ -302,31 +306,32 @@
 #define REDWAX_TOOL_NSS_OUT 299
 #define REDWAX_TOOL_NSS_SLOT_OUT 300
 #define REDWAX_TOOL_DER_OUT 301
-#define REDWAX_TOOL_PEM_OUT 302
-#define REDWAX_TOOL_PKCS12_OUT 303
-#define REDWAX_TOOL_PKCS11_OUT 304
-#define REDWAX_TOOL_PKCS11_MODULE_OUT 305
-#define REDWAX_TOOL_METADATA_OUT 306
-#define REDWAX_TOOL_METADATA_THRESHOLD 307
-#define REDWAX_TOOL_FORMAT_OUT 308
-#define REDWAX_TOOL_CALENDAR_OUT 309
-#define REDWAX_TOOL_CALENDAR_ALARM 310
-#define REDWAX_TOOL_REMINDER_OUT 311
-#define REDWAX_TOOL_JWKS_OUT 312
-#define REDWAX_TOOL_TEXT_OUT 313
-#define REDWAX_TOOL_NO_TEXT_OUT 314
-#define REDWAX_TOOL_SSH_PRIVATE_OUT 315
-#define REDWAX_TOOL_SSH_PUBLIC_OUT 316
-#define REDWAX_TOOL_SMIMEA_OUT 317
-#define REDWAX_TOOL_SSHFP_OUT 318
-#define REDWAX_TOOL_TLSA_OUT 319
-#define REDWAX_TOOL_USER_IN 320
-#define REDWAX_TOOL_USER_OUT 321
-#define REDWAX_TOOL_GROUP_IN 322
-#define REDWAX_TOOL_GROUP_OUT 323
-#define REDWAX_TOOL_ORDER_OUT 324
-#define REDWAX_TOOL_DNS_SERVER 325
-#define REDWAX_TOOL_DNS_TRUST_ANCHOR 326
+#define REDWAX_TOOL_PEMS_OUT 302
+#define REDWAX_TOOL_PEM_OUT 303
+#define REDWAX_TOOL_PKCS12_OUT 304
+#define REDWAX_TOOL_PKCS11_OUT 305
+#define REDWAX_TOOL_PKCS11_MODULE_OUT 306
+#define REDWAX_TOOL_METADATA_OUT 307
+#define REDWAX_TOOL_METADATA_THRESHOLD 308
+#define REDWAX_TOOL_FORMAT_OUT 309
+#define REDWAX_TOOL_CALENDAR_OUT 310
+#define REDWAX_TOOL_CALENDAR_ALARM 311
+#define REDWAX_TOOL_REMINDER_OUT 312
+#define REDWAX_TOOL_JWKS_OUT 313
+#define REDWAX_TOOL_TEXT_OUT 314
+#define REDWAX_TOOL_NO_TEXT_OUT 315
+#define REDWAX_TOOL_SSH_PRIVATE_OUT 316
+#define REDWAX_TOOL_SSH_PUBLIC_OUT 317
+#define REDWAX_TOOL_SMIMEA_OUT 318
+#define REDWAX_TOOL_SSHFP_OUT 319
+#define REDWAX_TOOL_TLSA_OUT 320
+#define REDWAX_TOOL_USER_IN 321
+#define REDWAX_TOOL_USER_OUT 322
+#define REDWAX_TOOL_GROUP_IN 323
+#define REDWAX_TOOL_GROUP_OUT 324
+#define REDWAX_TOOL_ORDER_OUT 325
+#define REDWAX_TOOL_DNS_SERVER 326
+#define REDWAX_TOOL_DNS_TRUST_ANCHOR 327
 
 #define REDWAX_EXIT_OK 0
 #define REDWAX_EXIT_INIT 1
@@ -558,7 +563,10 @@
             "\t\t\t\tcertificates, root certificates, crls, and keys will\n\t\t\t\tbe written to an NSS database. Must appear after the\n\t\t\t\t--nss-out option." },
     { "der-out", REDWAX_TOOL_DER_OUT, 1,
             "  --der-out=prefix\t\tWrite certificates, intermediate certificates,\n"
-            "\t\t\t\troot certificates, crls, and keys. Each one is\n\t\t\t\twritten to a file with a suffix indicating type and\n\t\t\t\tindex. Use '-' for stdout, output will be concatenated." },
+            "\t\t\t\troot certificates, crls, and keys. Each one is\n\t\t\t\twritten DER encoded to a file with a suffix indicating\n\t\t\t\ttype and index. Use '-' for stdout, output will be\n\t\t\t\tconcatenated." },
+    { "pems-out", REDWAX_TOOL_PEMS_OUT, 1,
+            "  --pems-out=prefix\t\tWrite certificates, intermediate certificates,\n"
+            "\t\t\t\troot certificates, crls, and keys. Each one is\n\t\t\t\twritten PEM encoded to a file with a suffix indicating\n\t\t\t\ttype and index. Use '-' for stdout, output will be\n\t\t\t\tconcatenated." },
     { "pem-out", REDWAX_TOOL_PEM_OUT, 1,
             "  --pem-out=file\t\tWrite certificates, intermediate certificates,\n"
             "\t\t\t\troot certificates, crls, and keys. Use '-'\n\t\t\t\tfor stdout." },
@@ -3070,6 +3078,12 @@
             }
             break;
         }
+        case REDWAX_TOOL_PEMS_OUT: {
+            if (redwax_file_out(r, optarg, &rt_run_process_pems_out, "pems-out")) {
+                return r->rc;
+            }
+            break;
+        }
         case REDWAX_TOOL_PEM_OUT: {
             if (redwax_file_out(r, optarg, &rt_run_process_pem_out, "pem-out")) {
                 return r->rc;
@@ -3332,6 +3346,10 @@
                 redwax_complete_file(r, optarg, state.isquoted);
                 break;
             }
+            case REDWAX_TOOL_PEMS_OUT: {
+                redwax_complete_file(r, optarg, state.isquoted);
+                break;
+            }
             case REDWAX_TOOL_PEM_OUT: {
                 redwax_complete_file(r, optarg, state.isquoted);
                 break;
@@ -3553,6 +3571,10 @@
             break;
         }
         case REDWAX_TOOL_DER_OUT: {
+            redwax_complete_file(r, "", state.isquoted);
+            break;
+        }
+        case REDWAX_TOOL_PEMS_OUT: {
             redwax_complete_file(r, "", state.isquoted);
             break;
         }

Modified: redwax-tool/trunk/redwax-tool.h
==============================================================================
--- redwax-tool/trunk/redwax-tool.h	(original)
+++ redwax-tool/trunk/redwax-tool.h	Wed Feb 25 22:27:23 2026
@@ -668,6 +668,14 @@
         (redwax_tool_t *r, const char *arg, const char *secret))
 
 /**
+ * Hook to write outgoing certificates / intermediates / keys to files.
+ *
+ * @param r The redwax-tool context.
+ */
+APR_DECLARE_EXTERNAL_HOOK(rt, REDWAX, apr_status_t, process_pems_out,
+        (redwax_tool_t *r, const char *arg, const char *secret))
+
+/**
  * Hook to write outgoing certificates / intermediates / keys.
  *
  * @param r The redwax-tool context.

Modified: redwax-tool/trunk/redwax_openssl.c
==============================================================================
--- redwax-tool/trunk/redwax_openssl.c	(original)
+++ redwax-tool/trunk/redwax_openssl.c	Wed Feb 25 22:27:23 2026
@@ -2772,6 +2772,301 @@
     return APR_SUCCESS;
 }
 
+static apr_status_t redwax_openssl_process_pems_out(redwax_tool_t *r,
+        const char *file, const char *secret)
+{
+
+    BIO *bio;
+    int i;
+
+    if (r->cert_out) {
+        for (i = 0; i < r->certs_out->nelts; i++)
+        {
+            const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t);
+
+            const unsigned char *der = cert->der;
+
+             X509 *x = d2i_X509(NULL, &der, cert->len);
+
+             if (x) {
+
+                 redwax_print_error(r, "pems-out: certificate: %s\n",
+                         redwax_openssl_name(r->pool, X509_get_subject_name(x)));
+
+                 if (!strcmp(file, "-")) {
+                     if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) {
+                         redwax_openssl_print_errors(r);
+                         return APR_ENOMEM;
+                     }
+                 }
+                 else if ((bio = BIO_new(BIO_s_file())) == NULL) {
+                     redwax_openssl_print_errors(r);
+                     return APR_ENOMEM;
+                 }
+                 else if (BIO_write_filename(bio,
+                     (char *)apr_psprintf(r->pool, "%s.cert.%d", file, i)) <= 0) {
+                     redwax_openssl_print_errors(r);
+                     BIO_free(bio);
+                     return APR_ENOENT;
+                 }
+
+                 if ((r->text && !X509_print_ex(bio, x, 0, 0)) ||
+                          !PEM_write_bio_X509(bio, x)) {
+                     redwax_openssl_print_errors(r);
+                     X509_free(x);
+                     BIO_free(bio);
+                     return APR_ENOENT;
+                 }
+
+                 BIO_flush(bio);
+                 X509_free(x);
+                 BIO_free(bio);
+
+             }
+
+        }
+    }
+
+    if (r->chain_out) {
+        for (i = 0; i < r->intermediates_out->nelts; i++)
+        {
+            const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t);
+
+            const unsigned char *der = cert->der;
+
+            X509 *x = d2i_X509(NULL, &der, cert->len);
+
+            if (x) {
+
+                redwax_print_error(r, "pems-out: intermediate: %s\n",
+                        redwax_openssl_name(r->pool, X509_get_subject_name(x)));
+
+                if (!strcmp(file, "-")) {
+                    if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) {
+                        redwax_openssl_print_errors(r);
+                        return APR_ENOMEM;
+                    }
+                }
+                else if ((bio = BIO_new(BIO_s_file())) == NULL) {
+                    redwax_openssl_print_errors(r);
+                    return APR_ENOMEM;
+                }
+                else if (BIO_write_filename(bio,
+                    (char *)apr_psprintf(r->pool, "%s.chain.%d", file, i)) <= 0) {
+                    redwax_openssl_print_errors(r);
+                    BIO_free(bio);
+                    return APR_ENOENT;
+                }
+
+                if ((r->text && !X509_print_ex(bio, x, 0, 0)) ||
+                         !PEM_write_bio_X509(bio, x)) {
+                    redwax_openssl_print_errors(r);
+                    X509_free(x);
+                    BIO_free(bio);
+                    return APR_ENOENT;
+                }
+
+                BIO_flush(bio);
+                X509_free(x);
+                BIO_free(bio);
+
+            }
+
+        }
+    }
+
+    if (r->root_out || r->trust_out) {
+        for (i = 0; i < r->trusted_out->nelts; i++)
+        {
+            const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t);
+
+            const unsigned char *der = cert->der;
+
+            X509 *x = d2i_X509_AUX(NULL, &der, cert->len);
+
+            if (x) {
+
+                redwax_print_error(r, "pems-out: trusted: %s\n",
+                        redwax_openssl_name(r->pool, X509_get_subject_name(x)));
+
+                if (!strcmp(file, "-")) {
+                    if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) {
+                        redwax_openssl_print_errors(r);
+                        return APR_ENOMEM;
+                    }
+                }
+                else if ((bio = BIO_new(BIO_s_file())) == NULL) {
+                    redwax_openssl_print_errors(r);
+                    return APR_ENOMEM;
+                }
+                else if (BIO_write_filename(bio,
+                    (char *)apr_psprintf(r->pool, "%s.ca.%d", file, i)) <= 0) {
+                    redwax_openssl_print_errors(r);
+                    BIO_free(bio);
+                    return APR_ENOENT;
+                }
+
+                if ((r->text && !X509_print_ex(bio, x, 0, 0)) ||
+                         !PEM_write_bio_X509(bio, x)) {
+                    redwax_openssl_print_errors(r);
+                    X509_free(x);
+                    BIO_free(bio);
+                    return APR_ENOENT;
+                }
+
+                BIO_flush(bio);
+                X509_free(x);
+                BIO_free(bio);
+
+            }
+
+        }
+    }
+
+    if (r->crl_out) {
+        for (i = 0; i < r->crls_out->nelts; i++)
+        {
+            const redwax_crl_t *crl = &APR_ARRAY_IDX(r->crls_out, i, const redwax_crl_t);
+
+            const unsigned char *der = crl->der;
+
+            X509_CRL *c = d2i_X509_CRL(NULL, &der, crl->len);
+
+            if (c) {
+
+                redwax_print_error(r, "pems-out: crl: %s\n",
+                        redwax_openssl_name(r->pool, X509_CRL_get_issuer(c)));
+
+                if (!strcmp(file, "-")) {
+                    if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) {
+                        redwax_openssl_print_errors(r);
+                        X509_CRL_free(c);
+                        return APR_ENOMEM;
+                    }
+                }
+                else if ((bio = BIO_new(BIO_s_file())) == NULL) {
+                    redwax_openssl_print_errors(r);
+                    X509_CRL_free(c);
+                    return APR_ENOMEM;
+                }
+                else if (BIO_write_filename(bio,
+                    (char *)apr_psprintf(r->pool, "%s.crl.%d", file, i)) <= 0) {
+                    redwax_openssl_print_errors(r);
+                    X509_CRL_free(c);
+                    BIO_free(bio);
+                    return APR_ENOENT;
+                }
+
+                if ((r->text && !X509_CRL_print_ex(bio, c, 0)) ||
+                         !PEM_write_bio_X509_CRL(bio, c)) {
+                    redwax_openssl_print_errors(r);
+                    X509_CRL_free(c);
+                    BIO_free(bio);
+                    return APR_ENOENT;
+                }
+
+                BIO_flush(bio);
+                X509_CRL_free(c);
+                BIO_free(bio);
+            }
+
+        }
+    }
+
+    if (r->key_out) {
+        for (i = 0; i < r->keys_out->nelts; i++)
+        {
+            const redwax_key_t *key = &APR_ARRAY_IDX(r->keys_out, i, const redwax_key_t);
+
+            BIO *kbio;
+            PKCS8_PRIV_KEY_INFO *p8inf;
+            EVP_PKEY *pkey;
+
+            if (!key->der) {
+                redwax_print_error(r, "pems-out: non-extractable private key, skipping\n");
+
+                continue;
+            }
+
+            if ((kbio = BIO_new_mem_buf(key->der, key->len)) == NULL) {
+                return APR_ENOMEM;
+            }
+
+            apr_pool_cleanup_register(r->pool, kbio, cleanup_bio,
+                    apr_pool_cleanup_null);
+
+            if (!(p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(kbio, NULL))) {
+
+                redwax_openssl_print_errors(r);
+                return APR_ENOENT;
+            }
+
+            if (!(pkey = EVP_PKCS82PKEY(p8inf))) {
+
+                redwax_openssl_print_errors(r);
+                PKCS8_PRIV_KEY_INFO_free(p8inf);
+                return APR_ENOENT;
+            }
+
+#if HAVE_EVP_PKEY_GET0_DESCRIPTION
+            redwax_print_error(r, "pems-out: private key: %s\n",
+                    EVP_PKEY_get0_description(pkey));
+#else
+            redwax_print_error(r, "pems-out: private key\n");
+#endif
+
+            if (!strcmp(file, "-")) {
+                if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) {
+                    redwax_openssl_print_errors(r);
+                    EVP_PKEY_free(pkey);
+                    PKCS8_PRIV_KEY_INFO_free(p8inf);
+                    return APR_ENOMEM;
+                }
+            }
+            else if ((bio = BIO_new(BIO_s_file())) == NULL) {
+                redwax_openssl_print_errors(r);
+                EVP_PKEY_free(pkey);
+                PKCS8_PRIV_KEY_INFO_free(p8inf);
+                return APR_ENOMEM;
+            }
+            else if (BIO_write_filename(bio,
+                (char *)apr_psprintf(r->pool, "%s.key.%d", file, i)) <= 0) {
+                redwax_openssl_print_errors(r);
+                EVP_PKEY_free(pkey);
+                PKCS8_PRIV_KEY_INFO_free(p8inf);
+                BIO_free(bio);
+                return APR_ENOENT;
+            }
+
+            if ((r->text && !EVP_PKEY_print_private(bio, pkey, 0, NULL))) {
+                redwax_openssl_print_errors(r);
+                PKCS8_PRIV_KEY_INFO_free(p8inf);
+                BIO_free(bio);
+                return APR_ENOENT;
+            }
+
+            if (r->param_out) {
+                PEM_write_bio_Parameters(bio, pkey);
+            }
+
+            if (!PEM_write_bio_PKCS8_PRIV_KEY_INFO(bio, p8inf)) {
+                redwax_openssl_print_errors(r);
+                PKCS8_PRIV_KEY_INFO_free(p8inf);
+                BIO_free(bio);
+                return APR_ENOENT;
+            }
+
+            BIO_flush(bio);
+            EVP_PKEY_free(pkey);
+            PKCS8_PRIV_KEY_INFO_free(p8inf);
+            BIO_free(bio);
+
+        }
+    }
+
+    return APR_SUCCESS;
+}
+
 static apr_status_t redwax_openssl_process_pem_out_all(redwax_tool_t *r,
         const char *file, const char *secret)
 {
@@ -6974,6 +7269,7 @@
     rt_hook_complete_filter(redwax_openssl_complete_filter_search, NULL, NULL, APR_HOOK_MIDDLE);
     rt_hook_process_filter(redwax_openssl_process_filter_search, NULL, NULL, APR_HOOK_MIDDLE);
     rt_hook_process_der_out(redwax_openssl_process_der_out, NULL, NULL, APR_HOOK_MIDDLE);
+    rt_hook_process_pems_out(redwax_openssl_process_pems_out, NULL, NULL, APR_HOOK_MIDDLE);
     rt_hook_process_pem_out(redwax_openssl_process_pem_out, NULL, NULL, APR_HOOK_MIDDLE);
     rt_hook_process_pkcs12_out(redwax_openssl_process_pkcs12_out, NULL, NULL, APR_HOOK_MIDDLE);
     rt_hook_process_ssh_public_out(redwax_openssl_process_ssh_public_out, NULL, NULL, APR_HOOK_MIDDLE);



More information about the rt-commit mailing list