From 0293fcf84b774f6b132df4502b0b7e376322bf76 Mon Sep 17 00:00:00 2001 From: job Date: Fri, 12 Jan 2024 11:24:02 +0000 Subject: [PATCH] Add -force_pubkey -multivalue-rdn -set_issuer -set_subject -utf8 to x509 app 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/ OK tb@ --- regress/usr.bin/openssl/appstest.sh | 27 +++++- usr.bin/openssl/openssl.1 | 48 +++++++++- usr.bin/openssl/x509.c | 131 +++++++++++++++++++++++----- 3 files changed, 178 insertions(+), 28 deletions(-) diff --git a/regress/usr.bin/openssl/appstest.sh b/regress/usr.bin/openssl/appstest.sh index 3125a424ecd..ae93fd22dfa 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.58 2023/07/24 05:54:12 tb Exp $ +# $OpenBSD: appstest.sh,v 1.59 2024/01/12 11:24:02 job Exp $ # # Copyright (c) 2016 Kinichiro Inoguchi # @@ -834,13 +834,38 @@ __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" diff --git a/usr.bin/openssl/openssl.1 b/usr.bin/openssl/openssl.1 index 3c376f4b463..b608b1634e4 100644 --- a/usr.bin/openssl/openssl.1 +++ b/usr.bin/openssl/openssl.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: openssl.1,v 1.153 2023/12/29 12:06:48 tb Exp $ +.\" $OpenBSD: openssl.1,v 1.154 2024/01/12 11:24:03 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: December 29 2023 $ +.Dd $Mdocdate: January 12 2024 $ .Dt OPENSSL 1 .Os .Sh NAME @@ -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 diff --git a/usr.bin/openssl/x509.c b/usr.bin/openssl/x509.c index 7f60110c47d..332399e7cce 100644 --- a/usr.bin/openssl/x509.c +++ b/usr.bin/openssl/x509.c @@ -1,4 +1,4 @@ -/* $OpenBSD: x509.c,v 1.35 2023/11/21 17:56:19 tb Exp $ */ +/* $OpenBSD: x509.c,v 1.36 2024/01/12 11:24:03 job Exp $ */ /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * @@ -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", @@ -467,6 +479,13 @@ static const struct option x509_options[] = { .opt.order = &cfg.fingerprint, .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", @@ -525,6 +544,12 @@ static const struct option x509_options[] = { .opt.order = &cfg.modulus, .order = &cfg.num, }, + { + .name = "multivalue-rdn", + .desc = "Enable support for multivalued RDNs", + .type = OPTION_FLAG, + .opt.flag = &cfg.multirdn, + }, { .name = "nameopt", .argname = "option", @@ -608,6 +633,13 @@ static const struct option x509_options[] = { .opt.order = &cfg.serial, .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", @@ -615,6 +647,13 @@ static const struct option x509_options[] = { .type = OPTION_ARG_FUNC, .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", @@ -643,6 +682,11 @@ static const struct option x509_options[] = { .opt.order = &cfg.startdate, .order = &cfg.num, }, + { + .name = "subj", + .type = OPTION_ARG, + .opt.arg = &cfg.set_subject, + }, { .name = "subject", .desc = "Print subject name", @@ -679,6 +723,12 @@ static const struct option x509_options[] = { .type = OPTION_FLAG, .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", @@ -704,16 +754,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 +776,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 +793,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 +864,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 +933,21 @@ 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 (iname == NULL) + goto end; + } + + 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 +956,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 +1276,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 +1290,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 +1374,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 +1441,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 +1480,14 @@ x509_certify(X509_STORE *ctx, char *CAfile, const EVP_MD *digest, X509 *x, "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 +1564,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 +1574,11 @@ sign(X509 *x, EVP_PKEY *pkey, int days, int clrext, const EVP_MD *digest, 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; -- 2.20.1