From 529586085e38487d45974817d4f3ff40f30e19f6 Mon Sep 17 00:00:00 2001 From: David von Oheimb Date: Tue, 19 Mar 2019 09:35:03 +1000 Subject: [PATCH] Add -new and -subj options to x509 app for direct cert generation Complete and improve error output of parse_name() in apps/apps.c Reviewed-by: Viktor Dukhovni Reviewed-by: Paul Dale (Merged from https://github.com/openssl/openssl/pull/8193) --- apps/apps.c | 17 +++++---- apps/x509.c | 72 ++++++++++++++++++++++++++++--------- doc/man1/x509.pod | 26 ++++++++++++++ test/recipes/25-test_x509.t | 21 ++++++++++- 4 files changed, 113 insertions(+), 23 deletions(-) diff --git a/apps/apps.c b/apps/apps.c index 06b543488f..8921c18cbc 100644 --- a/apps/apps.c +++ b/apps/apps.c @@ -1623,8 +1623,10 @@ X509_NAME *parse_name(const char *cp, long chtype, int canmulti) if (n == NULL) return NULL; work = OPENSSL_strdup(cp); - if (work == NULL) + if (work == NULL) { + BIO_printf(bio_err, "%s: Error copying name input\n", opt_getprog()); goto err; + } while (*cp) { char *bp = work; @@ -1639,7 +1641,7 @@ X509_NAME *parse_name(const char *cp, long chtype, int canmulti) *bp++ = *cp++; if (*cp == '\0') { BIO_printf(bio_err, - "%s: Hit end of string before finding the equals.\n", + "%s: Hit end of string before finding the '='\n", opt_getprog()); goto err; } @@ -1655,8 +1657,8 @@ X509_NAME *parse_name(const char *cp, long chtype, int canmulti) } if (*cp == '\\' && *++cp == '\0') { BIO_printf(bio_err, - "%s: escape character at end of string\n", - opt_getprog()); + "%s: escape character at end of string\n", + opt_getprog()); goto err; } } @@ -1670,7 +1672,7 @@ X509_NAME *parse_name(const char *cp, long chtype, int canmulti) nid = OBJ_txt2nid(typestr); if (nid == NID_undef) { BIO_printf(bio_err, "%s: Skipping unknown attribute \"%s\"\n", - opt_getprog(), typestr); + opt_getprog(), typestr); continue; } if (*valstr == '\0') { @@ -1681,8 +1683,11 @@ X509_NAME *parse_name(const char *cp, long chtype, int canmulti) } if (!X509_NAME_add_entry_by_NID(n, nid, chtype, valstr, strlen((char *)valstr), - -1, ismulti ? -1 : 0)) + -1, ismulti ? -1 : 0)) { + BIO_printf(bio_err, "%s: Error adding name attribute \"/%s=%s\"\n", + opt_getprog(), typestr ,valstr); goto err; + } } OPENSSL_free(work); diff --git a/apps/x509.c b/apps/x509.c index e4d5e079dd..3a5d561293 100644 --- a/apps/x509.c +++ b/apps/x509.c @@ -49,8 +49,8 @@ typedef enum OPTION_choice { OPT_ERR = -1, OPT_EOF = 0, OPT_HELP, OPT_INFORM, OPT_OUTFORM, OPT_KEYFORM, OPT_REQ, OPT_CAFORM, OPT_CAKEYFORM, OPT_SIGOPT, OPT_DAYS, OPT_PASSIN, OPT_EXTFILE, - OPT_EXTENSIONS, OPT_IN, OPT_OUT, OPT_SIGNKEY, OPT_CA, - OPT_CAKEY, OPT_CASERIAL, OPT_SET_SERIAL, OPT_FORCE_PUBKEY, + OPT_EXTENSIONS, OPT_IN, OPT_OUT, OPT_SIGNKEY, OPT_CA, OPT_CAKEY, + OPT_CASERIAL, OPT_SET_SERIAL, OPT_NEW, OPT_FORCE_PUBKEY, OPT_SUBJ, OPT_ADDTRUST, OPT_ADDREJECT, OPT_SETALIAS, OPT_CERTOPT, OPT_NAMEOPT, OPT_C, OPT_EMAIL, OPT_OCSP_URI, OPT_SERIAL, OPT_NEXT_SERIAL, OPT_MODULUS, OPT_PUBKEY, OPT_X509TOREQ, OPT_TEXT, OPT_HASH, @@ -132,7 +132,9 @@ const OPTIONS x509_options[] = { {"CAform", OPT_CAFORM, 'F', "CA format - default PEM"}, {"CAkeyform", OPT_CAKEYFORM, 'f', "CA key format - default PEM"}, {"sigopt", OPT_SIGOPT, 's', "Signature parameter in n:v form"}, + {"new", OPT_NEW, '-', "Generate a certificate from scratch"}, {"force_pubkey", OPT_FORCE_PUBKEY, '<', "Force the key to put inside certificate"}, + {"subj", OPT_SUBJ, 's', "Set or override certificate subject (and issuer)"}, {"next_serial", OPT_NEXT_SERIAL, '-', "Increment current certificate serial number"}, {"clrreject", OPT_CLRREJECT, '-', "Clears all the prohibited or rejected uses of the certificate"}, @@ -158,6 +160,11 @@ int x509_main(int argc, char **argv) BIO *out = NULL; CONF *extconf = NULL; EVP_PKEY *Upkey = NULL, *CApkey = NULL, *fkey = NULL; + int newcert = 0; + char *subj = NULL; + X509_NAME *fsubj = NULL; + const unsigned long chtype = MBSTRING_ASC; + const int multirdn = 0; STACK_OF(ASN1_OBJECT) *trust = NULL, *reject = NULL; STACK_OF(OPENSSL_STRING) *sigopts = NULL; X509 *x = NULL, *xca = NULL; @@ -281,9 +288,15 @@ int x509_main(int argc, char **argv) if ((sno = s2i_ASN1_INTEGER(NULL, opt_arg())) == NULL) goto opthelp; break; + case OPT_NEW: + newcert = 1; + break; case OPT_FORCE_PUBKEY: fkeyfile = opt_arg(); break; + case OPT_SUBJ: + subj = opt_arg(); + break; case OPT_ADDTRUST: if ((objtmp = OBJ_txt2obj(opt_arg(), 0)) == NULL) { BIO_printf(bio_err, @@ -477,18 +490,38 @@ int x509_main(int argc, char **argv) goto end; } + if (newcert && infile != NULL) { + BIO_printf(bio_err, "The -in option must not be used since -new is set\n"); + goto end; + } + if (newcert && fkeyfile == NULL) { + BIO_printf(bio_err, + "The -new option requires a public key to be set using -force_pubkey\n"); + goto end; + } if (fkeyfile != NULL) { fkey = load_pubkey(fkeyfile, keyformat, 0, NULL, e, "Forced key"); if (fkey == NULL) goto end; } - if ((CAkeyfile == NULL) && (CA_flag) && (CAformat == FORMAT_PEM)) { + if (newcert && subj == NULL) { + BIO_printf(bio_err, + "The -new option requires a subject to be set using -subj\n"); + goto end; + } + if (subj != NULL && (fsubj = parse_name(subj, chtype, multirdn)) == NULL) + goto end; + + if (CAkeyfile == NULL && CA_flag && CAformat == FORMAT_PEM) { CAkeyfile = CAfile; - } else if ((CA_flag) && (CAkeyfile == NULL)) { + } else if (CA_flag && CAkeyfile == NULL) { BIO_printf(bio_err, "need to specify a CAkey if using the CA command\n"); goto end; + } else if (!CA_flag && CAkeyfile != NULL) { + BIO_printf(bio_err, + "ignoring -CAkey option since no -CA option is given\n"); } if (extfile != NULL) { @@ -516,10 +549,6 @@ int x509_main(int argc, char **argv) EVP_PKEY *pkey; BIO *in; - if (!sign_flag && !CA_flag) { - BIO_printf(bio_err, "We need a private key to sign with\n"); - goto end; - } in = bio_open_default(infile, 'r', informat); if (in == NULL) goto end; @@ -537,21 +566,30 @@ int x509_main(int argc, char **argv) } i = X509_REQ_verify(req, pkey); if (i < 0) { - BIO_printf(bio_err, "Signature verification error\n"); + BIO_printf(bio_err, "Request self-signature verification error\n"); ERR_print_errors(bio_err); goto end; } if (i == 0) { BIO_printf(bio_err, - "Signature did not match the certificate request\n"); + "Request self-signature did not match the certificate request\n"); goto end; } else { - BIO_printf(bio_err, "Signature ok\n"); + BIO_printf(bio_err, "Request self-signature ok\n"); } print_name(bio_err, "subject=", X509_REQ_get_subject_name(req), get_nameopt()); + } + + if (reqfile || newcert) { + X509_NAME *n; + if (!sign_flag && CAkeyfile == NULL) { + BIO_printf(bio_err, + "We need a private key to sign with, use -signkey or -CAkey or -CA with private key\n"); + goto end; + } if ((x = X509_new()) == NULL) goto end; @@ -567,9 +605,8 @@ int x509_main(int argc, char **argv) goto end; } - if (!X509_set_issuer_name(x, X509_REQ_get_subject_name(req))) - goto end; - if (!X509_set_subject_name(x, X509_REQ_get_subject_name(req))) + n = req == NULL ? fsubj : X509_REQ_get_subject_name(req); + if (!X509_set_issuer_name(x, n) || !X509_set_subject_name(x, n)) goto end; if (!set_cert_times(x, NULL, NULL, days)) goto end; @@ -582,6 +619,8 @@ int x509_main(int argc, char **argv) goto end; if (fkey != NULL && !X509_set_pubkey(x, fkey)) goto end; + if (fsubj != NULL && !X509_set_subject_name(x, fsubj)) + goto end; } if (CA_flag) { @@ -654,7 +693,7 @@ int x509_main(int argc, char **argv) i2a_ASN1_INTEGER(out, ser); ASN1_INTEGER_free(ser); BIO_puts(out, "\n"); - } else if ((email == i) || (ocsp_uri == i)) { + } else if (email == i || ocsp_uri == i) { int j; STACK_OF(OPENSSL_STRING) *emlst; if (email == i) @@ -788,7 +827,7 @@ int x509_main(int argc, char **argv) } /* should be in the library */ - else if ((sign_flag == i) && (x509req == 0)) { + else if (sign_flag == i && x509req == 0) { BIO_printf(bio_err, "Getting Private key\n"); if (Upkey == NULL) { Upkey = load_key(keyfile, keyformat, 0, @@ -890,6 +929,7 @@ int x509_main(int argc, char **argv) NCONF_free(extconf); BIO_free_all(out); X509_STORE_free(ctx); + X509_NAME_free(fsubj); X509_REQ_free(req); X509_free(x); X509_free(xca); diff --git a/doc/man1/x509.pod b/doc/man1/x509.pod index 251e3f7a05..749d6cc001 100644 --- a/doc/man1/x509.pod +++ b/doc/man1/x509.pod @@ -52,7 +52,9 @@ B B [B<-CAkey filename>] [B<-CAcreateserial>] [B<-CAserial filename>] +[B<-new>] [B<-force_pubkey filename>] +[B<-subj arg>] [B<-text>] [B<-ext extensions>] [B<-certopt option>] @@ -454,15 +456,39 @@ specified then the extensions should either be contained in the unnamed L manual page for details of the extension section format. +=item B<-new> + +Generate a certificate from scratch, not using an input certificate +or certificate request. So the B<-in> option must not be used in this case. +Instead, the B<-subj> and <-force_pubkey> options need to be given. + =item B<-force_pubkey filename> When a certificate is created set its public key to the key in B instead of the key contained in the input or given with the B<-signkey> option. + This option is useful for creating self-issued certificates that are not self-signed, for instance when the key cannot be used for signing, such as DH. +It can also be used in conjunction with b<-new> and B<-subj> to directly +generate a certificate containing any desired public key. The format of the key file can be specified using the B<-keyform> option. +=item B<-subj arg> + +When a certificate is created set its subject name to the given value. +The arg must be formatted as I. +Keyword characters may be escaped by \ (backslash), and whitespace is retained. +Empty values are permitted, but the corresponding type will not be included +in the certificate. Giving a single I will lead to an empty sequence of RDNs +(a NULL subject DN). + +Unless the B<-CA> option is given the issuer is set to the same value. + +This option can be used in conjunction with the B<-force_pubkey> option +to create a certificate even without providing an input certificate +or certificate request. + =back =head2 Name Options diff --git a/test/recipes/25-test_x509.t b/test/recipes/25-test_x509.t index 0703e087a3..2ff49f6243 100644 --- a/test/recipes/25-test_x509.t +++ b/test/recipes/25-test_x509.t @@ -15,7 +15,7 @@ use OpenSSL::Test qw/:DEFAULT srctop_file/; setup("test_x509"); -plan tests => 9; +plan tests => 10; require_ok(srctop_file('test','recipes','tconversion.pl')); @@ -34,6 +34,25 @@ is(cmp_text($out, srctop_file("test/certs", "cyrillic.utf8")), 0, 'Comparing utf8 output'); unlink $out; +# producing and checking self-issued (but not self-signed) cert +my @path = qw(test certs); +my $subj = "/CN=CA"; # using same DN as in issuer of ee-cert.pem +my $pkey = srctop_file(@path, "ca-key.pem"); # issuer private key +my $pubkey = "ca-pubkey.pem"; # the corresponding issuer public key +# use any (different) key for signing our self-issued cert: +my $signkey = srctop_file(@path, "ee-ecdsa-key.pem"); +my $selfout = "self-issued.out"; +my $testcert = srctop_file(@path, "ee-cert.pem"); +ok(run(app(["openssl", "pkey", "-in", $pkey, "-pubout", "-out", $pubkey])) + && + run(app(["openssl", "x509", "-new", "-force_pubkey", $pubkey, + "-subj", $subj, "-signkey", $signkey, "-out", $selfout])) + && + run(app(["openssl", "verify", "-no_check_time", + "-trusted", $selfout, $testcert]))); +unlink $pubkey; +unlink $selfout; + subtest 'x509 -- x.509 v1 certificate' => sub { tconversion("x509", srctop_file("test","testx509.pem")); }; -- 2.25.1