Index | Thread | Search

From:
Job Snijders <job@openbsd.org>
Subject:
usr.bin/openssl: x509 add -force_pubkey -utf8 -set_issuer -set_subject -multivalue-rdn
To:
tech@openbsd.org
Date:
Thu, 11 Jan 2024 20:40:24 +0000

Download raw body.

Thread
Dear all,

The below changeset adds support to the 'openssl x509' command line
utility for the following options: -force_pubkey, -multivalue-rdn,
-set_issuer, -set_subject, and -utf8.

This diff solves https://github.com/libressl/portable/issues/842

I proposed -set_issuer and -set_subject to OpenSSL as well:
https://github.com/openssl/openssl/pull/23257

The -set_issuer, -set_subject, and -force_pubkey features can be used to
'rechain' PKIs, for more information see https://labs.apnic.net/nro-ta/
and https://blog.apnic.net/2023/12/14/models-of-trust-for-the-rpki/ .

This brings us quite close, but there is a bit more work to be done
after this lands.

Kind regards,

Job

Index: regress/usr.bin/openssl/appstest.sh
===================================================================
RCS file: /cvs/src/regress/usr.bin/openssl/appstest.sh,v
diff -u -p -r1.58 appstest.sh
--- regress/usr.bin/openssl/appstest.sh	24 Jul 2023 05:54:12 -0000	1.58
+++ regress/usr.bin/openssl/appstest.sh	11 Jan 2024 20:28:26 -0000
@@ -834,12 +834,37 @@ __EOF__
 
 	start_message "x509 ... issue cert for server csr#2"
 
+	$openssl_bin genrsa -out $server_dir/testkey.pem 2>&1
+	check_exit_status $?
+	$openssl_bin rsa -in $server_dir/testkey.pem -pubout \
+		-out $server_dir/testpubkey.pem 2>&1
+	check_exit_status $?
+
 	revoke_cert=$server_dir/revoke_cert.pem
 	$openssl_bin x509 -req -in $revoke_csr -CA $ca_cert -CAform pem \
 		-CAkey $ca_key -CAkeyform pem \
 		-CAserial $ca_dir/serial -set_serial 10 \
 		-passin pass:$ca_pass -CAcreateserial -out $revoke_cert \
+		-set_issuer /CN=issuer -set_subject /CN=subject \
+		-force_pubkey $server_dir/testpubkey.pem
 		> $revoke_cert.log 2>&1
+	check_exit_status $?
+	
+	start_message "x509 ... check if csr#2 cert has proper issuer & subject"
+	if [ "$($openssl_bin x509 -in $revoke_cert -issuer -noout)" != \
+		"issuer= /CN=issuer" ]; then
+		exit 1
+	fi
+	if [ "$($openssl_bin x509 -in $revoke_cert -subject -noout)" != \
+		"subject= /CN=subject" ]; then
+		exit 1
+	fi
+	check_exit_status 0
+
+	start_message "x509 ... check if csr#2 cert pubkey was forced"
+	$openssl_bin x509 -in $revoke_cert -pubkey -noout > $revoke_cert.pub
+	check_exit_status $?
+	diff $server_dir/testpubkey.pem $revoke_cert.pub
 	check_exit_status $?
 
 	start_message "ca ... issue cert for server csr#3"
Index: usr.bin/openssl/openssl.1
===================================================================
RCS file: /cvs/src/usr.bin/openssl/openssl.1,v
diff -u -p -r1.153 openssl.1
--- usr.bin/openssl/openssl.1	29 Dec 2023 12:06:48 -0000	1.153
+++ usr.bin/openssl/openssl.1	11 Jan 2024 20:28:27 -0000
@@ -6100,6 +6100,7 @@ version.
 .Op Fl extensions Ar section
 .Op Fl extfile Ar file
 .Op Fl fingerprint
+.Op Fl force_pubkey Ar key
 .Op Fl hash
 .Op Fl in Ar file
 .Op Fl inform Cm der | net | pem
@@ -6109,6 +6110,7 @@ version.
 .Op Fl keyform Cm der | pem
 .Op Fl md5 | sha1
 .Op Fl modulus
+.Op Fl multivalue-rdn
 .Op Fl nameopt Ar option
 .Op Fl next_serial
 .Op Fl noout
@@ -6121,7 +6123,9 @@ version.
 .Op Fl purpose
 .Op Fl req
 .Op Fl serial
+.Op Fl set_issuer Ar name
 .Op Fl set_serial Ar n
+.Op Fl set_subject Ar name
 .Op Fl setalias Ar arg
 .Op Fl signkey Ar file
 .Op Fl sigopt Ar nm:v
@@ -6131,6 +6135,7 @@ version.
 .Op Fl subject_hash_old
 .Op Fl text
 .Op Fl trustout
+.Op Fl utf8
 .Op Fl x509toreq
 .Ek
 .El
@@ -6254,6 +6259,16 @@ using the older algorithm as used by
 versions before 1.0.0.
 .It Fl modulus
 Print the value of the modulus of the public key contained in the certificate.
+.It Fl multivalue-rdn
+This option causes the
+.Fl subj
+argument to be interpreted with full support for multivalued RDNs,
+for example
+.Qq "/DC=org/DC=OpenSSL/DC=users/UID=123456+CN=John Doe" .
+If
+.Fl multivalue-rdn
+is not used, the UID value is set to
+.Qq "123456+CN=John Doe" .
 .It Fl nameopt Ar option
 Customise how the subject or issuer names are displayed,
 either using a list of comma-separated options or by specifying
@@ -6686,12 +6701,25 @@ which contains the section to use.
 .It Fl extfile Ar file
 File containing certificate extensions to use.
 If not specified, no extensions are added to the certificate.
+.It Fl force_pubkey Ar key
+Set the public key of the certificate to the public key contained in
+.Ar key .
 .It Fl keyform Cm der | pem
-The format of the private key file used in the
+The format of the key file used in the
+.Fl force_pubkey
+and
 .Fl signkey
-option.
+options.
 .It Fl req
 Expect a certificate request on input instead of a certificate.
+.It Fl set_issuer Ar name
+The issuer name to use.
+.Ar name
+must be formatted as /type0=value0/type1=value1/type2=...;
+characters may be escaped by
+.Sq \e
+(backslash);
+no spaces are skipped.
 .It Fl set_serial Ar n
 The serial number to use.
 This option can be used with either the
@@ -6710,6 +6738,14 @@ options) is not used.
 The serial number can be decimal or hex (if preceded by
 .Sq 0x ) .
 Negative serial numbers can also be specified but their use is not recommended.
+.It Fl set_subject Ar name
+The subject name to use.
+.Ar name
+must be formatted as /type0=value0/type1=value1/type2=...;
+characters may be escaped by
+.Sq \e
+(backslash);
+no spaces are skipped.
 .It Fl signkey Ar file
 Self-sign
 .Ar file
@@ -6730,6 +6766,10 @@ option is supplied.
 If the input is a certificate request, a self-signed certificate
 is created using the supplied private key using the subject name in
 the request.
+.It Fl utf8
+Interpret field values read from a terminal or obtained from a configuration
+file as UTF-8 strings.
+By default, they are interpreted as ASCII.
 .It Fl x509toreq
 Convert a certificate into a certificate request.
 The
Index: usr.bin/openssl/x509.c
===================================================================
RCS file: /cvs/src/usr.bin/openssl/x509.c,v
diff -u -p -r1.35 x509.c
--- usr.bin/openssl/x509.c	21 Nov 2023 17:56:19 -0000	1.35
+++ usr.bin/openssl/x509.c	11 Jan 2024 20:28:27 -0000
@@ -81,11 +81,11 @@
 
 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);
+    const EVP_MD *digest, CONF *conf, char *section, X509_NAME *issuer);
 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,
-    ASN1_INTEGER *sno);
+    ASN1_INTEGER *sno, X509_NAME *issuer);
 static int purpose_print(BIO *bio, X509 *cert, const X509_PURPOSE *pt);
 
 static struct {
@@ -103,6 +103,7 @@ static struct {
 	unsigned long certflag;
 	int checkend;
 	int checkoffset;
+	unsigned long chtype;
 	int clrext;
 	int clrreject;
 	int clrtrust;
@@ -113,6 +114,7 @@ static struct {
 	char *extfile;
 	char *extsect;
 	int fingerprint;
+	char *force_pubkey;
 	char *infile;
 	int informat;
 	int issuer;
@@ -124,6 +126,7 @@ static struct {
 	int keyformat;
 	const EVP_MD *md_alg;
 	int modulus;
+	int multirdn;
 	int next_serial;
 	unsigned long nmflag;
 	int noout;
@@ -139,6 +142,8 @@ static struct {
 	STACK_OF(ASN1_OBJECT) *reject;
 	int reqfile;
 	int serial;
+	char *set_issuer;
+	char *set_subject;
 	int sign_flag;
 	STACK_OF(OPENSSL_STRING) *sigopts;
 	ASN1_INTEGER *sno;
@@ -312,6 +317,13 @@ x509_opt_sigopt(char *arg)
 	return (0);
 }
 
+static int
+x509_opt_utf8(void)
+{
+	cfg.chtype = MBSTRING_UTF8;
+	return (0);
+}
+
 static const struct option x509_options[] = {
 	{
 		.name = "C",
@@ -468,6 +480,13 @@ static const struct option x509_options[
 		.order = &cfg.num,
 	},
 	{
+		.name = "force_pubkey",
+		.argname = "key",
+		.desc = "Force the public key to be put in the certificate",
+		.type = OPTION_ARG,
+		.opt.arg = &cfg.force_pubkey,
+	},
+	{
 		.name = "hash",
 		.desc = "Synonym for -subject_hash",
 		.type = OPTION_ORDER,
@@ -526,6 +545,12 @@ static const struct option x509_options[
 		.order = &cfg.num,
 	},
 	{
+		.name = "multivalue-rdn",
+		.desc = "Enable support for multivalued RDNs",
+		.type = OPTION_FLAG,
+		.opt.flag = &cfg.multirdn,
+	},
+	{
 		.name = "nameopt",
 		.argname = "option",
 		.desc = "Various certificate name options",
@@ -609,6 +634,13 @@ static const struct option x509_options[
 		.order = &cfg.num,
 	},
 	{
+		.name = "set_issuer",
+		.argname = "name",
+		.desc = "Set the issuer name",
+		.type = OPTION_ARG,
+		.opt.arg = &cfg.set_issuer,
+	},
+	{
 		.name = "set_serial",
 		.argname = "n",
 		.desc = "Serial number to use",
@@ -616,6 +648,13 @@ static const struct option x509_options[
 		.opt.argfunc = x509_opt_set_serial,
 	},
 	{
+		.name = "set_subject",
+		.argname = "name",
+		.desc = "Set the subject name",
+		.type = OPTION_ARG,
+		.opt.arg = &cfg.set_subject,
+	},
+	{
 		.name = "setalias",
 		.argname = "arg",
 		.desc = "Set certificate alias",
@@ -680,6 +719,12 @@ static const struct option x509_options[
 		.opt.flag = &cfg.trustout,
 	},
 	{
+		.name = "utf8",
+		.desc = "Input characters are in UTF-8 (default ASCII)",
+		.type = OPTION_FUNC,
+		.opt.func = x509_opt_utf8,
+	},
+	{
 		.name = "x509toreq",
 		.desc = "Output a certification request object",
 		.type = OPTION_ORDER,
@@ -704,16 +749,17 @@ x509_usage(void)
 	    "    [-CAkeyform der | pem] [-CAserial file] [-certopt option]\n"
 	    "    [-checkend arg] [-clrext] [-clrreject] [-clrtrust] [-dates]\n"
 	    "    [-days arg] [-email] [-enddate] [-extensions section]\n"
-	    "    [-extfile file] [-fingerprint] [-hash] [-in file]\n"
-	    "    [-inform der | net | pem] [-issuer] [-issuer_hash]\n"
-	    "    [-issuer_hash_old] [-keyform der | pem] [-md5 | -sha1]\n"
-	    "    [-modulus] [-nameopt option] [-next_serial] [-noout]\n"
-	    "    [-ocsp_uri] [-ocspid] [-out file]\n"
-	    "    [-outform der | net | pem] [-passin arg] [-pubkey]\n"
-	    "    [-purpose] [-req] [-serial] [-set_serial n] [-setalias arg]\n"
-	    "    [-signkey file] [-sigopt nm:v] [-startdate] [-subject]\n"
-	    "    [-subject_hash] [-subject_hash_old] [-text] [-trustout]\n"
-	    "    [-x509toreq]\n");
+	    "    [-extfile file] [-fingerprint] [-force_pubkey key] [-hash]\n"
+	    "    [-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"
+	    "    [-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"
+	    "    [-setalias arg] [-signkey file] [-sigopt nm:v] [-startdate]\n"
+	    "    [-subject] [-subject_hash] [-subject_hash_old] [-text]\n"
+	    "    [-trustout] [-utf8] [-x509toreq]\n");
 	fprintf(stderr, "\n");
 	options_usage(x509_options);
 	fprintf(stderr, "\n");
@@ -725,7 +771,8 @@ x509_main(int argc, char **argv)
 	int ret = 1;
 	X509_REQ *req = NULL;
 	X509 *x = NULL, *xca = NULL;
-	EVP_PKEY *Upkey = NULL, *CApkey = NULL;
+	X509_NAME *iname = NULL, *sname = NULL;
+	EVP_PKEY *Fpkey = NULL, *Upkey = NULL, *CApkey = NULL;
 	int i;
 	BIO *out = NULL;
 	BIO *STDout = NULL;
@@ -741,6 +788,7 @@ x509_main(int argc, char **argv)
 	}
 
 	memset(&cfg, 0, sizeof(cfg));
+	cfg.chtype = MBSTRING_ASC;
 	cfg.days = DEF_DAYS;
 	cfg.informat = FORMAT_PEM;
 	cfg.outformat = FORMAT_PEM;
@@ -811,6 +859,11 @@ x509_main(int argc, char **argv)
 			goto end;
 		}
 	}
+	if (cfg.force_pubkey != NULL) {
+		if ((Fpkey = load_pubkey(bio_err, cfg.force_pubkey,
+		    cfg.keyformat, 0, NULL, "Forced key")) == NULL)
+			goto end;
+	}
 	if (cfg.reqfile) {
 		EVP_PKEY *pkey;
 		BIO *in;
@@ -875,9 +928,18 @@ x509_main(int argc, char **argv)
 		} else if (!X509_set_serialNumber(x, cfg.sno))
 			goto end;
 
-		if (!X509_set_issuer_name(x, X509_REQ_get_subject_name(req)))
+		if (cfg.set_issuer != NULL)
+			iname = parse_name(cfg.set_issuer, cfg.chtype,
+			    cfg.multirdn);
+
+		if (cfg.set_subject != NULL)
+			sname = parse_name(cfg.set_subject, cfg.chtype,
+			    cfg.multirdn);
+		else
+			sname = X509_NAME_dup(X509_REQ_get_subject_name(req));
+		if (sname == NULL)
 			goto end;
-		if (!X509_set_subject_name(x, X509_REQ_get_subject_name(req)))
+		if (!X509_set_subject_name(x, sname))
 			goto end;
 
 		if (X509_gmtime_adj(X509_get_notBefore(x), 0) == NULL)
@@ -886,7 +948,9 @@ x509_main(int argc, char **argv)
 		    NULL) == NULL)
 			goto end;
 
-		if ((pkey = X509_REQ_get0_pubkey(req)) == NULL)
+		if ((pkey = Fpkey) == NULL)
+			pkey = X509_REQ_get0_pubkey(req);
+		if (pkey == NULL)
 			goto end;
 		if (!X509_set_pubkey(x, pkey))
 			goto end;
@@ -1204,7 +1268,7 @@ x509_main(int argc, char **argv)
 				}
 				if (!sign(x, Upkey, cfg.days,
 				    cfg.clrext, cfg.digest,
-				    extconf, cfg.extsect))
+				    extconf, cfg.extsect, iname))
 					goto end;
 			} else if (cfg.CA_flag == i) {
 				BIO_printf(bio_err, "Getting CA Private Key\n");
@@ -1218,7 +1282,7 @@ x509_main(int argc, char **argv)
 				if (!x509_certify(ctx, cfg.CAfile, cfg.digest,
 				    x, xca, CApkey, cfg.sigopts, cfg.CAserial,
 				    cfg.CA_createserial, cfg.days, cfg.clrext,
-				    extconf, cfg.extsect, cfg.sno))
+				    extconf, cfg.extsect, cfg.sno, iname))
 					goto end;
 			} else if (cfg.x509req == i) {
 				EVP_PKEY *pk;
@@ -1302,10 +1366,13 @@ x509_main(int argc, char **argv)
 	NCONF_free(extconf);
 	BIO_free_all(out);
 	BIO_free_all(STDout);
+	X509_NAME_free(iname);
+	X509_NAME_free(sname);
 	X509_STORE_free(ctx);
 	X509_REQ_free(req);
 	X509_free(x);
 	X509_free(xca);
+	EVP_PKEY_free(Fpkey);
 	EVP_PKEY_free(Upkey);
 	EVP_PKEY_free(CApkey);
 	sk_OPENSSL_STRING_free(cfg.sigopts);
@@ -1366,7 +1433,7 @@ 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 *serialfile, int create, int days, int clrext, CONF *conf,
-    char *section, ASN1_INTEGER *sno)
+    char *section, ASN1_INTEGER *sno, X509_NAME *issuer)
 {
 	int ret = 0;
 	ASN1_INTEGER *bs = NULL;
@@ -1405,8 +1472,14 @@ x509_certify(X509_STORE *ctx, char *CAfi
 		    "CA certificate and CA private key do not match\n");
 		goto end;
 	}
-	if (!X509_set_issuer_name(x, X509_get_subject_name(xca)))
+
+	if (issuer == NULL)
+		issuer = X509_get_subject_name(xca);
+	if (issuer == NULL)
 		goto end;
+	if (!X509_set_issuer_name(x, issuer))
+		goto end;
+
 	if (!X509_set_serialNumber(x, bs))
 		goto end;
 
@@ -1483,7 +1556,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)
+    CONF *conf, char *section, X509_NAME *issuer)
 {
 	EVP_PKEY *pktmp;
 
@@ -1493,7 +1566,11 @@ sign(X509 *x, EVP_PKEY *pkey, int days, 
 	EVP_PKEY_copy_parameters(pktmp, pkey);
 	EVP_PKEY_save_parameters(pktmp, 1);
 
-	if (!X509_set_issuer_name(x, X509_get_subject_name(x)))
+	if (issuer == NULL)
+		issuer = X509_get_subject_name(x);
+	if (issuer == NULL)
+		goto err;
+	if (!X509_set_issuer_name(x, issuer))
 		goto err;
 	if (X509_gmtime_adj(X509_get_notBefore(x), 0) == NULL)
 		goto err;