]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
APPS/x509: add -multi option for outputting all certs found in input
authorJeremy Doupe <jeremy@doupe.com>
Thu, 10 Apr 2025 15:19:31 +0000 (10:19 -0500)
committerDr. David von Oheimb <dev@ddvo.net>
Wed, 14 May 2025 10:51:42 +0000 (12:51 +0200)
Reviewed-by: David von Oheimb <david.von.oheimb@siemens.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/27340)

apps/x509.c
doc/man1/openssl-x509.pod.in
test/recipes/25-test_x509.t

index fdae8f383a667eb53ff271ab16ed6b00a588277a..9bae7fa72215992df308855be212e5435b20f573 100644 (file)
@@ -47,7 +47,7 @@ typedef enum OPTION_choice {
     OPT_CASERIAL, OPT_SET_SERIAL, OPT_NEW, OPT_FORCE_PUBKEY, OPT_ISSU, OPT_SUBJ,
     OPT_ADDTRUST, OPT_ADDREJECT, OPT_SETALIAS, OPT_CERTOPT, OPT_DATEOPT, OPT_NAMEOPT,
     OPT_EMAIL, OPT_OCSP_URI, OPT_SERIAL, OPT_NEXT_SERIAL,
-    OPT_MODULUS, OPT_PUBKEY, OPT_X509TOREQ, OPT_TEXT, OPT_HASH,
+    OPT_MODULUS, OPT_MULTI, OPT_PUBKEY, OPT_X509TOREQ, OPT_TEXT, OPT_HASH,
     OPT_ISSUER_HASH, OPT_SUBJECT, OPT_ISSUER, OPT_FINGERPRINT, OPT_DATES,
     OPT_PURPOSE, OPT_STARTDATE, OPT_ENDDATE, OPT_CHECKEND, OPT_CHECKHOST,
     OPT_CHECKEMAIL, OPT_CHECKIP, OPT_NOOUT, OPT_TRUSTOUT, OPT_CLRTRUST,
@@ -122,6 +122,7 @@ const OPTIONS x509_options[] = {
     {"purpose", OPT_PURPOSE, '-', "Print out certificate purposes"},
     {"pubkey", OPT_PUBKEY, '-', "Print the public key in PEM format"},
     {"modulus", OPT_MODULUS, '-', "Print the RSA key modulus"},
+    {"multi", OPT_MULTI, '-', "Process multiple certificates"},
 
     OPT_SECTION("Certificate checking"),
     {"checkend", OPT_CHECKEND, 'M',
@@ -281,12 +282,13 @@ int x509_main(int argc, char **argv)
     X509_STORE *ctx = NULL;
     char *CAkeyfile = NULL, *CAserial = NULL, *pubkeyfile = NULL, *alias = NULL;
     char *checkhost = NULL, *checkemail = NULL, *checkip = NULL;
+    STACK_OF(X509) *certs = NULL;
     char *ext_names = NULL;
     char *extsect = NULL, *extfile = NULL, *passin = NULL, *passinarg = NULL;
     char *infile = NULL, *outfile = NULL, *privkeyfile = NULL, *CAfile = NULL;
     char *prog, *not_before = NULL, *not_after = NULL;
     int days = UNSET_DAYS; /* not explicitly set */
-    int x509toreq = 0, modulus = 0, print_pubkey = 0, pprint = 0;
+    int x509toreq = 0, modulus = 0, multi = 0, print_pubkey = 0, pprint = 0;
     int CAformat = FORMAT_UNDEF, CAkeyformat = FORMAT_UNDEF;
     unsigned long dateopt = ASN1_DTFLGS_RFC822;
     int fingerprint = 0, reqfile = 0, checkend = 0;
@@ -294,7 +296,7 @@ int x509_main(int argc, char **argv)
     int next_serial = 0, subject_hash = 0, issuer_hash = 0, ocspid = 0;
     int noout = 0, CA_createserial = 0, email = 0;
     int ocsp_uri = 0, trustout = 0, clrtrust = 0, clrreject = 0, aliasout = 0;
-    int ret = 1, i, j, num = 0, badsig = 0, clrext = 0, nocert = 0;
+    int ret = 1, i, j, k = 0, num = 0, badsig = 0, clrext = 0, nocert = 0;
     int text = 0, serial = 0, subject = 0, issuer = 0, startdate = 0, ext = 0;
     int enddate = 0;
     time_t checkoffset = 0;
@@ -499,6 +501,9 @@ int x509_main(int argc, char **argv)
         case OPT_MODULUS:
             modulus = ++num;
             break;
+        case OPT_MULTI:
+            multi = 1;
+            break;
         case OPT_PUBKEY:
             print_pubkey = ++num;
             break;
@@ -732,6 +737,11 @@ int x509_main(int argc, char **argv)
         }
     }
 
+    if (multi && (reqfile || newcert)) {
+        BIO_printf(bio_err, "Error: -multi cannot be used with -req or -new\n");
+        goto err;
+    }
+
     if (reqfile) {
         if (infile == NULL && isatty(fileno_stdin()))
             BIO_printf(bio_err,
@@ -788,11 +798,30 @@ int x509_main(int argc, char **argv)
     } else {
         if (infile == NULL && isatty(fileno_stdin()))
             BIO_printf(bio_err,
-                       "Warning: Reading certificate from stdin since no -in or -new option is given\n");
-        x = load_cert_pass(infile, informat, 1, passin, "certificate");
-        if (x == NULL)
-            goto end;
+                       "Warning: Reading certificate(s) from stdin since no -in or -new option is given\n");
+        if (multi) {
+            certs = sk_X509_new_null();
+            if (certs == NULL)
+                goto end;
+            if (!load_certs(infile, 1, &certs, passin, NULL))
+                goto end;
+            if (sk_X509_num(certs) <= 0)
+                goto end;
+        } else {
+            x = load_cert_pass(infile, informat, 1, passin, "certificate");
+            if (x == NULL)
+                goto end;
+        }
     }
+
+    out = bio_open_default(outfile, 'w', outformat);
+    if (out == NULL)
+        goto end;
+
+ cert_loop:
+    if (multi)
+        x = sk_X509_value(certs, k);
+
     if ((fsubj != NULL || req != NULL)
         && !X509_set_subject_name(x, fsubj != NULL ? fsubj :
                                   X509_REQ_get_subject_name(req)))
@@ -809,10 +838,6 @@ int x509_main(int argc, char **argv)
             goto end;
     }
 
-    out = bio_open_default(outfile, 'w', outformat);
-    if (out == NULL)
-        goto end;
-
     if (alias)
         X509_alias_set1(x, (unsigned char *)alias, -1);
 
@@ -1079,7 +1104,7 @@ int x509_main(int argc, char **argv)
             BIO_printf(out, "Certificate will expire\n");
         else
             BIO_printf(out, "Certificate will not expire\n");
-        goto end;
+        goto end_cert_loop;
     }
 
     if (!check_cert_attributes(out, x, checkhost, checkemail, checkip, 1))
@@ -1087,7 +1112,7 @@ int x509_main(int argc, char **argv)
 
     if (noout || nocert) {
         ret = 0;
-        goto end;
+        goto end_cert_loop;
     }
 
     if (outformat == FORMAT_ASN1) {
@@ -1106,6 +1131,10 @@ int x509_main(int argc, char **argv)
         goto err;
     }
 
+ end_cert_loop:
+    if (multi && ++k < sk_X509_num(certs))
+        goto cert_loop;
+
     ret = 0;
     goto end;
 
@@ -1113,6 +1142,10 @@ int x509_main(int argc, char **argv)
     ERR_print_errors(bio_err);
 
  end:
+    if (multi) {
+        sk_X509_pop_free(certs, X509_free);
+        x = NULL;
+    }
     NCONF_free(extconf);
     BIO_free_all(out);
     X509_STORE_free(ctx);
index b72f898f8bc9cc3a60e8902549e7416cc9887c6d..8b75754266ebf101c059ecb83c05507f4dceac9b 100644 (file)
@@ -48,6 +48,7 @@ B<openssl> B<x509>
 [B<-purpose>]
 [B<-pubkey>]
 [B<-modulus>]
+[B<-multi>]
 [B<-checkend> I<num>]
 [B<-checkhost> I<host>]
 [B<-checkemail> I<host>]
@@ -338,6 +339,11 @@ Prints the certificate's SubjectPublicKeyInfo block in PEM format.
 This option prints out the value of the modulus of the public key
 contained in the certificate.
 
+=item B<-multi>
+
+This option prints selected information about all certificates
+contained in the input.
+
 =back
 
 =head2 Certificate Checking Options
index 09b61708ff8a5aa8fe8d1cd048743971cfcbcf1b..efda91d15e92e12c6ab39f6f68e1ed48ee95652e 100644 (file)
@@ -12,11 +12,12 @@ use warnings;
 
 use File::Spec;
 use OpenSSL::Test::Utils;
-use OpenSSL::Test qw/:DEFAULT srctop_file/;
+use OpenSSL::Test qw/:DEFAULT srctop_file result_file/;
+use File::Compare qw/compare_text/;
 
 setup("test_x509");
 
-plan tests => 134;
+plan tests => 136;
 
 # Prevent MSys2 filename munging for arguments that look like file paths but
 # aren't
@@ -485,6 +486,19 @@ ok(!run(app(["openssl", "x509", "-noout", "-dates", "-dateopt", "invalid_format"
             "-in", srctop_file("test/certs", "ca-cert.pem")])),
    "Run with invalid -dateopt format");
 
+my $ca_cert = srctop_file(@certs, "ca-cert.pem");
+my $goodcn2_chain = srctop_file(@certs, "goodcn2-chain.pem");
+
+# -multi test with single cert
+ok(run(app(["openssl", "x509", "-multi", "-in", $ca_cert])),
+   "Run with -multi (single cert)");
+
+# -multi test with multiple certs
+my $outfile = result_file("multi.out");
+ok(run(app(["openssl", "x509", "-multi", "-in", $goodcn2_chain, "-out", $outfile]))
+   && compare_text($outfile, $goodcn2_chain) == 0,
+   "Run with -multi (multiple certs)");
+
 # Tests for signing certs (broken in 1.1.1o)
 my $a_key = "a-key.pem";
 my $a_cert = "a-cert.pem";