Add 'openssl x509 -new' functionality to the libcrypto CLI utility
authorjob <job@openbsd.org>
Fri, 26 Jan 2024 11:58:36 +0000 (11:58 +0000)
committerjob <job@openbsd.org>
Fri, 26 Jan 2024 11:58:36 +0000 (11:58 +0000)
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
usr.bin/openssl/openssl.1
usr.bin/openssl/x509.c

index 500fae0..8c0e75d 100755 (executable)
@@ -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 <inoguchi@openbsd.org>
 #
@@ -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
index b608b16..0e2ffbc 100644 (file)
@@ -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.
 .\"
 .\" 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
index 332399e..0d5cf5d 100644 (file)
@@ -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)