From d4c9bc7e07bcf455e26a4d261eb43cf80a9b7fe1 Mon Sep 17 00:00:00 2001 From: job Date: Fri, 26 Jan 2024 11:58:36 +0000 Subject: [PATCH] Add 'openssl x509 -new' functionality to the libcrypto CLI utility The ability to generate a new certificate is useful for testing and experimentation with rechaining PKIs. While there, alias '-key' to '-signkey' for compatibility. with and OK tb@ --- regress/usr.bin/openssl/appstest.sh | 51 +++++++++++++++++++++++- usr.bin/openssl/openssl.1 | 23 ++++++++++- usr.bin/openssl/x509.c | 62 +++++++++++++++++++++++------ 3 files changed, 121 insertions(+), 15 deletions(-) diff --git a/regress/usr.bin/openssl/appstest.sh b/regress/usr.bin/openssl/appstest.sh index 500fae02512..8c0e75deb48 100755 --- a/regress/usr.bin/openssl/appstest.sh +++ b/regress/usr.bin/openssl/appstest.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# $OpenBSD: appstest.sh,v 1.60 2024/01/12 13:16:48 tb Exp $ +# $OpenBSD: appstest.sh,v 1.61 2024/01/26 11:58:36 job Exp $ # # Copyright (c) 2016 Kinichiro Inoguchi # @@ -867,6 +867,55 @@ __EOF__ diff $server_dir/testpubkey.pem $revoke_cert.pub check_exit_status $? + start_message "x509 ... test -new" + $openssl_bin genrsa -out $server_dir/ca-new.key 2048 + check_exit_status $? + $openssl_bin x509 -new -set_issuer '/CN=test-issuer' \ + -set_subject '/CN=test-subject' \ + -out $server_dir/new.pem -days 1 -key $server_dir/ca-new.key \ + -force_pubkey $revoke_cert.pub + check_exit_status $? + $openssl_bin x509 -in $server_dir/new.pem -pubkey -noout \ + > $server_dir/new.pem.pub + check_exit_status $? + + start_message "x509 ... check if -new cert has proper pubkey" + diff $server_dir/testpubkey.pem $server_dir/new.pem.pub + check_exit_status $? + + start_message "x509 ... check if -new cert has proper issuer & subject" + if [ "$($openssl_bin x509 -in $server_dir/new.pem -issuer -noout)" != \ + "issuer= /CN=test-issuer" ]; then + exit 1 + fi + if [ "$($openssl_bin x509 -in $server_dir/new.pem -subject -noout)" != \ + "subject= /CN=test-subject" ]; then + exit 1 + fi + check_exit_status 0 + + start_message "x509 ... test -new without -force_pubkey" + $openssl_bin x509 -new -set_subject '/CN=test-subject2' \ + -out $server_dir/new2.pem -days 1 -key $server_dir/ca-new.key + check_exit_status $? + $openssl_bin x509 -in $server_dir/new2.pem -pubkey -noout \ + > $server_dir/new2.pem.pub + check_exit_status $? + $openssl_bin rsa -in $server_dir/ca-new.key -pubout \ + -out $server_dir/ca-new.pubkey + check_exit_status $? + diff $server_dir/new2.pem.pub $server_dir/ca-new.pubkey + check_exit_status $? + if [ "$($openssl_bin x509 -in $server_dir/new2.pem -issuer -noout)" \ + != "issuer= /CN=test-subject2" ]; then + exit 1 + fi + if [ "$($openssl_bin x509 -in $server_dir/new2.pem -subject -noout)" \ + != "subject= /CN=test-subject2" ]; then + exit 1 + fi + check_exit_status 0 + start_message "ca ... issue cert for server csr#3" sv_ecdsa_cert=$server_dir/sv_ecdsa_cert.pem diff --git a/usr.bin/openssl/openssl.1 b/usr.bin/openssl/openssl.1 index b608b1634e4..0e2ffbcd000 100644 --- a/usr.bin/openssl/openssl.1 +++ b/usr.bin/openssl/openssl.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: openssl.1,v 1.154 2024/01/12 11:24:03 job Exp $ +.\" $OpenBSD: openssl.1,v 1.155 2024/01/26 11:58:37 job Exp $ .\" ==================================================================== .\" Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved. .\" @@ -110,7 +110,7 @@ .\" copied and put under another distribution licence .\" [including the GNU Public Licence.] .\" -.Dd $Mdocdate: January 12 2024 $ +.Dd $Mdocdate: January 26 2024 $ .Dt OPENSSL 1 .Os .Sh NAME @@ -6112,6 +6112,7 @@ version. .Op Fl modulus .Op Fl multivalue-rdn .Op Fl nameopt Ar option +.Op Fl new .Op Fl next_serial .Op Fl noout .Op Fl ocsp_uri @@ -6153,10 +6154,14 @@ The following are x509 input, output, and general purpose options: .It Fl in Ar file The input file to read from, or standard input if not specified. +This option cannot be used with +.Fl new . .It Fl inform Cm der | net | pem The input format. Normally, the command will expect an X.509 certificate, but this can change if other options such as +.Fl in +or .Fl req are present. .It Fl md5 | sha1 @@ -6710,8 +6715,22 @@ The format of the key file used in the and .Fl signkey options. +.It Fl new +Generate a new certificate using the subject given by +.Fl set_subject +and signed by +.Fl signkey . +If no public key is provided with +.Fl force_pubkey , +the resulting certificate is self-signed. +This option cannot be used with +.Fl in +or +.Fl req . .It Fl req Expect a certificate request on input instead of a certificate. +This option cannot be used with +.Fl new . .It Fl set_issuer Ar name The issuer name to use. .Ar name diff --git a/usr.bin/openssl/x509.c b/usr.bin/openssl/x509.c index 332399e7cce..0d5cf5d0339 100644 --- a/usr.bin/openssl/x509.c +++ b/usr.bin/openssl/x509.c @@ -1,4 +1,4 @@ -/* $OpenBSD: x509.c,v 1.36 2024/01/12 11:24:03 job Exp $ */ +/* $OpenBSD: x509.c,v 1.37 2024/01/26 11:58:37 job Exp $ */ /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * @@ -81,7 +81,8 @@ static int callb(int ok, X509_STORE_CTX *ctx); static int sign(X509 *x, EVP_PKEY *pkey, int days, int clrext, - const EVP_MD *digest, CONF *conf, char *section, X509_NAME *issuer); + const EVP_MD *digest, CONF *conf, char *section, X509_NAME *issuer, + char *force_pubkey); static int x509_certify(X509_STORE *ctx, char *CAfile, const EVP_MD *digest, X509 *x, X509 *xca, EVP_PKEY *pkey, STACK_OF(OPENSSL_STRING) *sigopts, char *serial, int create, int days, int clrext, CONF *conf, char *section, @@ -127,6 +128,7 @@ static struct { const EVP_MD *md_alg; int modulus; int multirdn; + int new; int next_serial; unsigned long nmflag; int noout; @@ -530,6 +532,12 @@ static const struct option x509_options[] = { .order = &cfg.num, }, #endif + { + .name = "key", + .argname = "file", + .type = OPTION_ARG_FUNC, + .opt.argfunc = x509_opt_signkey, + }, { .name = "keyform", .argname = "fmt", @@ -557,6 +565,12 @@ static const struct option x509_options[] = { .type = OPTION_ARG_FUNC, .opt.argfunc = x509_opt_nameopt, }, + { + .name = "new", + .desc = "Generate a new certificate", + .type = OPTION_FLAG, + .opt.flag = &cfg.new, + }, { .name = "next_serial", .desc = "Print the next serial number", @@ -758,7 +772,7 @@ x509_usage(void) " [-in file] [-inform der | net | pem] [-issuer]\n" " [-issuer_hash] [-issuer_hash_old] [-keyform der | pem]\n" " [-md5 | -sha1] [-modulus] [-multivalue-rdn]\n" - " [-nameopt option] [-next_serial] [-noout] [-ocsp_uri]\n" + " [-nameopt option] [-new] [-next_serial] [-noout] [-ocsp_uri]\n" " [-ocspid] [-out file] [-outform der | net | pem]\n" " [-passin arg] [-pubkey] [-purpose] [-req] [-serial]\n" " [-set_issuer name] [-set_serial n] [-set_subject name]\n" @@ -778,6 +792,7 @@ x509_main(int argc, char **argv) X509 *x = NULL, *xca = NULL; X509_NAME *iname = NULL, *sname = NULL; EVP_PKEY *Fpkey = NULL, *Upkey = NULL, *CApkey = NULL; + EVP_PKEY *pkey; int i; BIO *out = NULL; BIO *STDout = NULL; @@ -869,8 +884,28 @@ x509_main(int argc, char **argv) cfg.keyformat, 0, NULL, "Forced key")) == NULL) goto end; } + if (cfg.new) { + if (cfg.infile != NULL) { + BIO_printf(bio_err, "Can't combine -new and -in\n"); + goto end; + } + if (cfg.reqfile) { + BIO_printf(bio_err, "Can't combine -new and -req\n"); + goto end; + } + if (cfg.set_subject == NULL) { + BIO_printf(bio_err, "Must use -set_subject with -new\n"); + goto end; + } + if (cfg.keyfile == NULL) { + BIO_printf(bio_err, "Must use -signkey with -new\n"); + goto end; + } + if ((Upkey = load_key(bio_err, cfg.keyfile, cfg.keyformat, 0, + passin, "Private key")) == NULL) + goto end; + } if (cfg.reqfile) { - EVP_PKEY *pkey; BIO *in; if (!cfg.sign_flag && !cfg.CA_flag) { @@ -919,6 +954,8 @@ x509_main(int argc, char **argv) print_name(bio_err, "subject=", X509_REQ_get_subject_name(req), cfg.nmflag); + } + if (cfg.reqfile || cfg.new) { if ((x = X509_new()) == NULL) goto end; @@ -958,6 +995,8 @@ x509_main(int argc, char **argv) if ((pkey = Fpkey) == NULL) pkey = X509_REQ_get0_pubkey(req); + if (pkey == NULL) + pkey = Upkey; if (pkey == NULL) goto end; if (!X509_set_pubkey(x, pkey)) @@ -1263,10 +1302,7 @@ x509_main(int argc, char **argv) BIO_printf(STDout, "%02X%c", md[j], (j + 1 == (int)n) ? '\n' : ':'); } - - /* should be in the library */ } else if (cfg.sign_flag == i && cfg.x509req == 0) { - BIO_printf(bio_err, "Getting Private key\n"); if (Upkey == NULL) { Upkey = load_key(bio_err, cfg.keyfile, cfg.keyformat, 0, passin, @@ -1276,10 +1312,10 @@ x509_main(int argc, char **argv) } if (!sign(x, Upkey, cfg.days, cfg.clrext, cfg.digest, - extconf, cfg.extsect, iname)) + extconf, cfg.extsect, iname, + cfg.force_pubkey)) goto end; } else if (cfg.CA_flag == i) { - BIO_printf(bio_err, "Getting CA Private Key\n"); if (cfg.CAkeyfile != NULL) { CApkey = load_key(bio_err, cfg.CAkeyfile, cfg.CAkeyformat, 0, passin, @@ -1564,7 +1600,7 @@ callb(int ok, X509_STORE_CTX *ctx) /* self sign */ static int sign(X509 *x, EVP_PKEY *pkey, int days, int clrext, const EVP_MD *digest, - CONF *conf, char *section, X509_NAME *issuer) + CONF *conf, char *section, X509_NAME *issuer, char *force_pubkey) { EVP_PKEY *pktmp; @@ -1591,8 +1627,10 @@ sign(X509 *x, EVP_PKEY *pkey, int days, int clrext, const EVP_MD *digest, (long) 60 * 60 * 24 * days) == NULL) goto err; - if (!X509_set_pubkey(x, pkey)) - goto err; + if (force_pubkey == NULL) { + if (!X509_set_pubkey(x, pkey)) + goto err; + } if (clrext) { while (X509_get_ext_count(x) > 0) { if (X509_delete_ext(x, 0) == NULL) -- 2.20.1