From dca67c0aa17010f2315bc5bf1915ad4509ff8e52 Mon Sep 17 00:00:00 2001 From: Jeremy Doupe Date: Thu, 10 Apr 2025 10:19:31 -0500 Subject: [PATCH] APPS/x509: add -multi option for outputting all certs found in input Reviewed-by: David von Oheimb Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/27340) --- apps/x509.c | 59 ++++++++++++++++++++++++++++-------- doc/man1/openssl-x509.pod.in | 6 ++++ test/recipes/25-test_x509.t | 18 +++++++++-- 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/apps/x509.c b/apps/x509.c index fdae8f383a..9bae7fa722 100644 --- a/apps/x509.c +++ b/apps/x509.c @@ -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); diff --git a/doc/man1/openssl-x509.pod.in b/doc/man1/openssl-x509.pod.in index b72f898f8b..8b75754266 100644 --- a/doc/man1/openssl-x509.pod.in +++ b/doc/man1/openssl-x509.pod.in @@ -48,6 +48,7 @@ B B [B<-purpose>] [B<-pubkey>] [B<-modulus>] +[B<-multi>] [B<-checkend> I] [B<-checkhost> I] [B<-checkemail> I] @@ -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 diff --git a/test/recipes/25-test_x509.t b/test/recipes/25-test_x509.t index 09b61708ff..efda91d15e 100644 --- a/test/recipes/25-test_x509.t +++ b/test/recipes/25-test_x509.t @@ -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";