Commit 41e597a01d for openssl.org

commit 41e597a01d95540f52e8bc4d69f88c3d93a093ce
Author: Dr. David von Oheimb <David.von.Oheimb@siemens.com>
Date:   Thu Dec 24 11:25:47 2020 +0100

    Add X509V3_set_issuer_pkey, needed for AKID of self-issued not self-signed cert

    Also clean up some related auxiliary functions and documentation

    Reviewed-by: Tomas Mraz <tmraz@fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13658)

diff --git a/apps/req.c b/apps/req.c
index c57d338ec9..13d54770db 100644
--- a/apps/req.c
+++ b/apps/req.c
@@ -532,6 +532,7 @@ int req_main(int argc, char **argv)
     if (extensions != NULL) {
         /* Check syntax of file */
         X509V3_CTX ctx;
+
         X509V3_set_ctx_test(&ctx);
         X509V3_set_nconf(&ctx, req_conf);
         if (!X509V3_EXT_add_nconf(req_conf, &ctx, extensions, NULL)) {
@@ -544,6 +545,7 @@ int req_main(int argc, char **argv)
     if (addext_conf != NULL) {
         /* Check syntax of command line extensions */
         X509V3_CTX ctx;
+
         X509V3_set_ctx_test(&ctx);
         X509V3_set_nconf(&ctx, addext_conf);
         if (!X509V3_EXT_add_nconf(addext_conf, &ctx, "default", NULL)) {
@@ -591,6 +593,7 @@ int req_main(int argc, char **argv)
     if (req_exts != NULL) {
         /* Check syntax of file */
         X509V3_CTX ctx;
+
         X509V3_set_ctx_test(&ctx);
         X509V3_set_nconf(&ctx, req_conf);
         if (!X509V3_EXT_add_nconf(req_conf, &ctx, req_exts, NULL)) {
@@ -773,7 +776,7 @@ int req_main(int argc, char **argv)
     }
     if (newreq || gen_x509) {
         if (pkey == NULL /* can happen only if !newreq */) {
-            BIO_printf(bio_err, "Must provide the corresponding private key using -key\n");
+            BIO_printf(bio_err, "Must provide a signature key using -key\n");
             goto end;
         }

@@ -793,7 +796,8 @@ int req_main(int argc, char **argv)
             X509V3_CTX ext_ctx;
             X509_NAME *issuer = CAcert != NULL ? X509_get_subject_name(CAcert) :
                 X509_REQ_get_subject_name(req);
-            X509_NAME *n_subj = X509_REQ_get_subject_name(req);
+            X509_NAME *n_subj = fsubj != NULL ? fsubj :
+                X509_REQ_get_subject_name(req);

             if ((new_x509 = X509_new_ex(app_get0_libctx(),
                                         app_get0_propq())) == NULL)
@@ -823,6 +827,15 @@ int req_main(int argc, char **argv)
             /* Set up V3 context struct */
             X509V3_set_ctx(&ext_ctx, CAcert != NULL ? CAcert : new_x509,
                            new_x509, NULL, NULL, X509V3_CTX_REPLACE);
+            if (CAcert == NULL) { /* self-issued, possibly self-signed */
+                if (!X509V3_set_issuer_pkey(&ext_ctx, pkey)) /* prepare right AKID */
+                    goto end;
+                ERR_set_mark();
+                if (!X509_check_private_key(new_x509, pkey))
+                    BIO_printf(bio_err,
+                               "Warning: Signature key and public key of cert do not match\n");
+                ERR_pop_to_mark();
+            }
             X509V3_set_nconf(&ext_ctx, req_conf);

             /* Add extensions */
diff --git a/apps/x509.c b/apps/x509.c
index 34d654c8f2..5769f5f982 100644
--- a/apps/x509.c
+++ b/apps/x509.c
@@ -1079,7 +1079,13 @@ static int sign(X509 *x, EVP_PKEY *pkey, X509 *issuer,
         while (X509_get_ext_count(x) > 0)
             X509_delete_ext(x, 0);
     }
+
     X509V3_set_ctx(&ext_ctx, issuer, x, NULL, NULL, X509V3_CTX_REPLACE);
+    if (issuer == x
+        /* prepare the correct AKID of self-issued, possibly self-signed cert */
+            && !X509V3_set_issuer_pkey(&ext_ctx, pkey))
+        return 0;
+
     if (conf != NULL) {
         X509V3_set_nconf(&ext_ctx, conf);
         if (!X509V3_EXT_add_nconf(conf, &ext_ctx, section, x)) {
@@ -1149,7 +1155,7 @@ static int print_x509v3_exts(BIO *bio, X509 *x, const char *ext_names)

     exts = X509_get0_extensions(x);
     if ((num = sk_X509_EXTENSION_num(exts)) <= 0) {
-        BIO_printf(bio, "No extensions in certificate\n");
+        BIO_printf(bio_err, "No extensions in certificate\n");
         ret = 1;
         goto end;
     }
diff --git a/crypto/x509/v3_akey.c b/crypto/x509/v3_akey.c
index 2e90d495c5..d0d20c4455 100644
--- a/crypto/x509/v3_akey.c
+++ b/crypto/x509/v3_akey.c
@@ -13,6 +13,7 @@
 #include <openssl/asn1.h>
 #include <openssl/asn1t.h>
 #include <openssl/x509v3.h>
+#include "crypto/x509.h"
 #include "ext_dat.h"

 static STACK_OF(CONF_VALUE) *i2v_AUTHORITY_KEYID(X509V3_EXT_METHOD *method,
@@ -86,7 +87,7 @@ static AUTHORITY_KEYID *v2i_AUTHORITY_KEYID(X509V3_EXT_METHOD *method,
     GENERAL_NAME *gen = NULL;
     ASN1_INTEGER *serial = NULL;
     X509_EXTENSION *ext;
-    X509 *cert;
+    X509 *issuer_cert;
     AUTHORITY_KEYID *akeyid = AUTHORITY_KEYID_new();

     if (akeyid == NULL)
@@ -113,36 +114,49 @@ static AUTHORITY_KEYID *v2i_AUTHORITY_KEYID(X509V3_EXT_METHOD *method,
         }
     }

-    if (!ctx || !ctx->issuer_cert) {
-        if (ctx && (ctx->flags == CTX_TEST))
-            return akeyid;
+    if (ctx != NULL && (ctx->flags & CTX_TEST) != 0)
+        return akeyid;
+
+    if (ctx == NULL) {
+        ERR_raise(ERR_LIB_X509V3, ERR_R_PASSED_NULL_PARAMETER);
+        goto err;
+    }
+    if ((issuer_cert = ctx->issuer_cert) == NULL) {
         ERR_raise(ERR_LIB_X509V3, X509V3_R_NO_ISSUER_CERTIFICATE);
         goto err;
     }
-    cert = ctx->issuer_cert;

-    if (keyid) {
-        i = X509_get_ext_by_NID(cert, NID_subject_key_identifier, -1);
-        if ((i >= 0) && (ext = X509_get_ext(cert, i)))
+    if (keyid != 0) {
+        /* prefer any pre-existing subject key identifier of the issuer cert */
+        i = X509_get_ext_by_NID(issuer_cert, NID_subject_key_identifier, -1);
+        if (i >= 0 && (ext = X509_get_ext(issuer_cert, i)) != NULL)
             ikeyid = X509V3_EXT_d2i(ext);
+        if (ikeyid == NULL && ctx->issuer_pkey != NULL) { /* fallback */
+            /* generate AKID from scratch, emulating s2i_skey_id(..., "hash") */
+            X509_PUBKEY *pubkey = NULL;
+
+            if (X509_PUBKEY_set(&pubkey, ctx->issuer_pkey))
+                ikeyid = x509_pubkey_hash(pubkey);
+            X509_PUBKEY_free(pubkey);
+        }
         if ((keyid == 2 || issuer == 0)
             && (ikeyid == NULL
-                || ASN1_STRING_length(ikeyid) <= 2)  /* indicating "none" */ ) {
+                || ASN1_STRING_length(ikeyid) <= 2) /* indicating "none" */) {
             ERR_raise(ERR_LIB_X509V3, X509V3_R_UNABLE_TO_GET_ISSUER_KEYID);
             goto err;
         }
     }

-    if ((issuer && !ikeyid) || (issuer == 2)) {
-        isname = X509_NAME_dup(X509_get_issuer_name(cert));
-        serial = ASN1_INTEGER_dup(X509_get0_serialNumber(cert));
-        if (!isname || !serial) {
+    if (issuer == 2 || (issuer == 1 && ikeyid == NULL)) {
+        isname = X509_NAME_dup(X509_get_issuer_name(issuer_cert));
+        serial = ASN1_INTEGER_dup(X509_get0_serialNumber(issuer_cert));
+        if (isname == NULL || serial == NULL) {
             ERR_raise(ERR_LIB_X509V3, X509V3_R_UNABLE_TO_GET_ISSUER_DETAILS);
             goto err;
         }
     }

-    if (isname) {
+    if (isname != NULL) {
         if ((gens = sk_GENERAL_NAME_new_null()) == NULL
             || (gen = GENERAL_NAME_new()) == NULL
             || !sk_GENERAL_NAME_push(gens, gen)) {
diff --git a/crypto/x509/v3_alt.c b/crypto/x509/v3_alt.c
index 2344c554fa..d2e3ec138b 100644
--- a/crypto/x509/v3_alt.c
+++ b/crypto/x509/v3_alt.c
@@ -325,7 +325,7 @@ static int copy_issuer(X509V3_CTX *ctx, GENERAL_NAMES *gens)
     X509_EXTENSION *ext;
     int i, num;

-    if (ctx && (ctx->flags == CTX_TEST))
+    if (ctx != NULL && (ctx->flags & CTX_TEST) != 0)
         return 1;
     if (!ctx || !ctx->issuer_cert) {
         ERR_raise(ERR_LIB_X509V3, X509V3_R_NO_ISSUER_DETAILS);
@@ -410,12 +410,12 @@ static int copy_email(X509V3_CTX *ctx, GENERAL_NAMES *gens, int move_p)
     GENERAL_NAME *gen = NULL;
     int i = -1;

-    if (ctx != NULL && ctx->flags == CTX_TEST)
+    if (ctx != NULL && (ctx->flags & CTX_TEST) != 0)
         return 1;
     if (ctx == NULL
         || (ctx->subject_cert == NULL && ctx->subject_req == NULL)) {
         ERR_raise(ERR_LIB_X509V3, X509V3_R_NO_SUBJECT_DETAILS);
-        goto err;
+        return 0;
     }
     /* Find the subject name */
     if (ctx->subject_cert)
diff --git a/crypto/x509/v3_conf.c b/crypto/x509/v3_conf.c
index 1f424325a0..f8a2e3fe27 100644
--- a/crypto/x509/v3_conf.c
+++ b/crypto/x509/v3_conf.c
@@ -437,6 +437,10 @@ static X509V3_CONF_METHOD nconf_method = {

 void X509V3_set_nconf(X509V3_CTX *ctx, CONF *conf)
 {
+    if (ctx == NULL) {
+        ERR_raise(ERR_LIB_X509V3, ERR_R_PASSED_NULL_PARAMETER);
+        return;
+    }
     ctx->db_meth = &nconf_method;
     ctx->db = conf;
 }
@@ -444,11 +448,33 @@ void X509V3_set_nconf(X509V3_CTX *ctx, CONF *conf)
 void X509V3_set_ctx(X509V3_CTX *ctx, X509 *issuer, X509 *subj, X509_REQ *req,
                     X509_CRL *crl, int flags)
 {
+    if (ctx == NULL) {
+        ERR_raise(ERR_LIB_X509V3, ERR_R_PASSED_NULL_PARAMETER);
+        return;
+    }
+    ctx->flags = flags;
     ctx->issuer_cert = issuer;
     ctx->subject_cert = subj;
-    ctx->crl = crl;
     ctx->subject_req = req;
-    ctx->flags = flags;
+    ctx->crl = crl;
+    ctx->db_meth = NULL;
+    ctx->db = NULL;
+    ctx->issuer_pkey = NULL;
+}
+
+/* For API backward compatibility, this is separate from X509V3_set_ctx() */
+int X509V3_set_issuer_pkey(X509V3_CTX *ctx, EVP_PKEY *pkey)
+{
+    if (ctx == NULL) {
+        ERR_raise(ERR_LIB_X509V3, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (ctx->subject_cert == NULL && pkey != NULL) {
+        ERR_raise(ERR_LIB_X509V3, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    ctx->issuer_pkey = pkey;
+    return 1;
 }

 /* Old conf compatibility functions */
@@ -489,6 +515,10 @@ static X509V3_CONF_METHOD conf_lhash_method = {

 void X509V3_set_conf_lhash(X509V3_CTX *ctx, LHASH_OF(CONF_VALUE) *lhash)
 {
+    if (ctx == NULL) {
+        ERR_raise(ERR_LIB_X509V3, ERR_R_PASSED_NULL_PARAMETER);
+        return;
+    }
     ctx->db_meth = &conf_lhash_method;
     ctx->db = lhash;
 }
diff --git a/crypto/x509/v3_skey.c b/crypto/x509/v3_skey.c
index 6122596081..8d13dc248a 100644
--- a/crypto/x509/v3_skey.c
+++ b/crypto/x509/v3_skey.c
@@ -52,56 +52,49 @@ ASN1_OCTET_STRING *s2i_ASN1_OCTET_STRING(X509V3_EXT_METHOD *method,

 }

-static ASN1_OCTET_STRING *s2i_skey_id(X509V3_EXT_METHOD *method,
-                                      X509V3_CTX *ctx, char *str)
+ASN1_OCTET_STRING *x509_pubkey_hash(X509_PUBKEY *pubkey)
 {
     ASN1_OCTET_STRING *oct;
-    X509_PUBKEY *pubkey;
     const unsigned char *pk;
     int pklen;
     unsigned char pkey_dig[EVP_MAX_MD_SIZE];
     unsigned int diglen;

-    if (strcmp(str, "none") == 0)
-        return ASN1_OCTET_STRING_new(); /* dummy */
-
-    if (strcmp(str, "hash") != 0)
-        return s2i_ASN1_OCTET_STRING(method, ctx, str);
-
-    if ((oct = ASN1_OCTET_STRING_new()) == NULL) {
-        ERR_raise(ERR_LIB_X509V3, ERR_R_MALLOC_FAILURE);
+    if (pubkey == NULL) {
+        ERR_raise(ERR_LIB_X509V3, X509V3_R_NO_PUBLIC_KEY);
         return NULL;
     }
+    if ((oct = ASN1_OCTET_STRING_new()) == NULL)
+        return NULL;

-    if (ctx && (ctx->flags == CTX_TEST))
+    X509_PUBKEY_get0_param(NULL, &pk, &pklen, NULL, pubkey);
+    /* TODO(3.0) - explicitly fetch the digest */
+    if (EVP_Digest(pk, pklen, pkey_dig, &diglen, EVP_sha1(), NULL)
+            && ASN1_OCTET_STRING_set(oct, pkey_dig, diglen))
         return oct;

-    if (!ctx || (!ctx->subject_req && !ctx->subject_cert)) {
-        ERR_raise(ERR_LIB_X509V3, X509V3_R_NO_PUBLIC_KEY);
-        goto err;
-    }
-
-    pubkey = ctx->subject_req != NULL ?
-        ctx->subject_req->req_info.pubkey :
-        ctx->subject_cert->cert_info.key;
-    if (pubkey == NULL) {
-        ERR_raise(ERR_LIB_X509V3, X509V3_R_NO_PUBLIC_KEY);
-        goto err;
-    }
+    ASN1_OCTET_STRING_free(oct);
+    return NULL;
+}

-    X509_PUBKEY_get0_param(NULL, &pk, &pklen, NULL, pubkey);
+static ASN1_OCTET_STRING *s2i_skey_id(X509V3_EXT_METHOD *method,
+                                      X509V3_CTX *ctx, char *str)
+{
+    if (strcmp(str, "none") == 0)
+        return ASN1_OCTET_STRING_new(); /* dummy */

-    if (!EVP_Digest(pk, pklen, pkey_dig, &diglen, EVP_sha1(), NULL))
-        goto err;
+    if (strcmp(str, "hash") != 0)
+        return s2i_ASN1_OCTET_STRING(method, ctx /* not used */, str);

-    if (!ASN1_OCTET_STRING_set(oct, pkey_dig, diglen)) {
-        ERR_raise(ERR_LIB_X509V3, ERR_R_MALLOC_FAILURE);
-        goto err;
+    if (ctx != NULL && (ctx->flags & CTX_TEST) != 0)
+        return ASN1_OCTET_STRING_new();
+    if (ctx == NULL
+        || (ctx->subject_cert == NULL && ctx->subject_req == NULL)) {
+        ERR_raise(ERR_LIB_X509V3, X509V3_R_NO_SUBJECT_DETAILS);
+        return NULL;
     }

-    return oct;
-
- err:
-    ASN1_OCTET_STRING_free(oct);
-    return NULL;
+    return x509_pubkey_hash(ctx->subject_req != NULL ?
+                            ctx->subject_req->req_info.pubkey :
+                            ctx->subject_cert->cert_info.key);
 }
diff --git a/crypto/x509/v3_utf8.c b/crypto/x509/v3_utf8.c
index d37ac73246..465e0a39a3 100644
--- a/crypto/x509/v3_utf8.c
+++ b/crypto/x509/v3_utf8.c
@@ -12,7 +12,6 @@
 #include <openssl/asn1.h>
 #include <openssl/conf.h>
 #include <openssl/x509v3.h>
-#include <crypto/x509v3.h>
 #include "ext_dat.h"

 /*
diff --git a/doc/internal/man3/s2i_ASN1_UTF8STRING.pod b/doc/internal/man3/s2i_ASN1_UTF8STRING.pod
deleted file mode 100644
index b6d1375189..0000000000
--- a/doc/internal/man3/s2i_ASN1_UTF8STRING.pod
+++ /dev/null
@@ -1,46 +0,0 @@
-=pod
-
-=head1 NAME
-
-i2s_ASN1_UTF8STRING,
-s2i_ASN1_UTF8STRING
-- convert objects from/to ASN.1/string representation
-
-=head1 SYNOPSIS
-
- #include "crypto/x509v3.h"
-
- char *i2s_ASN1_UTF8STRING(X509V3_EXT_METHOD *method,
-                           ASN1_UTF8STRING *utf8);
- ASN1_UTF8STRING *s2i_ASN1_UTF8STRING(X509V3_EXT_METHOD *method,
-                                      X509V3_CTX *ctx, const char *str);
-
-=head1 DESCRIPTION
-
-These functions convert OpenSSL objects to and from their ASN.1/string
-representation. This function is used for B<X509v3> extensions.
-
-=head1 NOTES
-
-The letters B<i> and B<s> in i2s_ASN1_UTF8STRING() stand for
-"internal" (that is, an internal C structure) and string respectively.
-So B<i2s_ASN1_UTF8STRING>() converts from internal to string.
-
-=head1 RETURN VALUES
-
-B<s2i_ASN1_UTF8STRING>() return a valid
-B<ASN1_UTF8STRING> structure or NULL if an error occurs.
-
-B<i2s_ASN1_UTF8STRING>() returns the pointer to a UTF-8 string
-or NULL if an error occurs.
-
-=head1 COPYRIGHT
-
-Copyright 2020 The OpenSSL Project Authors. All Rights Reserved.
-
-Licensed under the Apache License 2.0 (the "License").  You may not use
-this file except in compliance with the License.  You can obtain a copy
-in the file LICENSE in the source distribution or at
-L<https://www.openssl.org/source/license.html>.
-
-=cut
diff --git a/doc/man3/ASN1_generate_nconf.pod b/doc/man3/ASN1_generate_nconf.pod
index a507e444c6..8b195d523f 100644
--- a/doc/man3/ASN1_generate_nconf.pod
+++ b/doc/man3/ASN1_generate_nconf.pod
@@ -2,7 +2,7 @@

 =head1 NAME

-ASN1_generate_nconf, ASN1_generate_v3 - ASN1 generation functions
+ASN1_generate_nconf, ASN1_generate_v3 - ASN1 string generation functions

 =head1 SYNOPSIS

@@ -16,10 +16,10 @@ ASN1_generate_nconf, ASN1_generate_v3 - ASN1 generation functions
 These functions generate the ASN1 encoding of a string
 in an B<ASN1_TYPE> structure.

-I<str> contains the string to encode I<nconf> or I<cnf> contains
+I<str> contains the string to encode. I<nconf> or I<cnf> contains
 the optional configuration information where additional strings
 will be read from. I<nconf> will typically come from a config
-file whereas I<cnf> is obtained from an B<X509V3_CTX> structure
+file whereas I<cnf> is obtained from an B<X509V3_CTX> structure,
 which will typically be used by X509 v3 certificate extension
 functions. I<cnf> or I<nconf> can be set to NULL if no additional
 configuration will be used.
diff --git a/doc/man3/X509V3_set_ctx.pod b/doc/man3/X509V3_set_ctx.pod
new file mode 100644
index 0000000000..136e3f1982
--- /dev/null
+++ b/doc/man3/X509V3_set_ctx.pod
@@ -0,0 +1,60 @@
+=pod
+
+=head1 NAME
+
+X509V3_set_ctx,
+X509V3_set_issuer_pkey - X.509v3 extension generation utility functions
+
+=head1 SYNOPSIS
+
+ #include <openssl/x509v3.h>
+
+ void X509V3_set_ctx(X509V3_CTX *ctx, X509 *issuer, X509 *subject,
+                     X509_REQ *req, X509_CRL *crl, int flags);
+ int X509V3_set_issuer_pkey(X509V3_CTX *ctx, EVP_PKEY *pkey);
+
+=head1 DESCRIPTION
+
+X509V3_set_ctx() fills in the basic fields of I<ctx> of type B<X509V3_CTX>,
+providing details potentially needed by functions producing X509 v3 certificate
+extensions, e.g., to look up values for filling in authority key identifiers.
+Any of I<subj>, I<req>, or I<crl> may be provided, pointing to a certificate,
+certification request, or certificate revocation list, respectively.
+If I<subj> or I<crl> is provided, I<issuer> should point to its issuer,
+for instance to help generating an authority key identifier extension.
+Note that if I<subj> is provided, I<issuer> may be the same as I<subj>,
+which means that I<subj> is self-issued (or even self-signed).
+I<flags> may be 0 or contain B<CTX_TEST>, which means that just the syntax of
+extension definitions is to be checked without actually producing an extension,
+or B<X509V3_CTX_REPLACE>, which means that each X.509v3 extension added as
+defined in some configuration section shall replace any already existing
+extension with the same OID.
+
+X509V3_set_issuer_pkey() explicitly sets the issuer private key of
+the certificate that has been provided in I<ctx>.
+This should be done for self-issued certificates (which may be self-signed
+or not) to provide fallback data for the authority key identifier extension.
+
+=head1 RETURN VALUES
+
+X509V3_set_ctx() and X509V3_set_issuer_pkey()
+return 1 on success and 0 on error.
+
+=head1 SEE ALSO
+
+L<X509_add_ext(3)>
+
+=head1 HISTORY
+
+X509V3_set_issuer_pkey() was added in OpenSSL 3.0.
+
+=head1 COPYRIGHT
+
+Copyright 2015-2020 The OpenSSL Project Authors. All Rights Reserved.
+
+Licensed under the Apache License 2.0 (the "License").  You may not use
+this file except in compliance with the License.  You can obtain a copy
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut
diff --git a/doc/man3/s2i_ASN1_IA5STRING.pod b/doc/man3/s2i_ASN1_IA5STRING.pod
index e59a85606b..5215b6a179 100644
--- a/doc/man3/s2i_ASN1_IA5STRING.pod
+++ b/doc/man3/s2i_ASN1_IA5STRING.pod
@@ -10,11 +10,13 @@ i2s_ASN1_OCTET_STRING,
 s2i_ASN1_OCTET_STRING,
 i2s_ASN1_ENUMERATED,
 i2s_ASN1_ENUMERATED_TABLE,
+i2s_ASN1_UTF8STRING,
+s2i_ASN1_UTF8STRING
 - convert objects from/to ASN.1/string representation

 =head1 SYNOPSIS

-=for openssl generic
+ #include <openssl/x509v3.h>

  char *i2s_ASN1_IA5STRING(X509V3_EXT_METHOD *method, ASN1_IA5STRING *ia5);
  ASN1_IA5STRING *s2i_ASN1_IA5STRING(X509V3_EXT_METHOD *method,
@@ -29,6 +31,11 @@ i2s_ASN1_ENUMERATED_TABLE,
  char *i2s_ASN1_ENUMERATED_TABLE(X509V3_EXT_METHOD *method,
                                 const ASN1_ENUMERATED *e);

+ char *i2s_ASN1_UTF8STRING(X509V3_EXT_METHOD *method,
+                           ASN1_UTF8STRING *utf8);
+ ASN1_UTF8STRING *s2i_ASN1_UTF8STRING(X509V3_EXT_METHOD *method,
+                                      X509V3_CTX *ctx, const char *str);
+
 =head1 DESCRIPTION

 These functions convert OpenSSL objects to and from their ASN.1/string
@@ -36,7 +43,7 @@ representation. This function is used for B<X509v3> extensions.

 =head1 NOTES

-The letters B<i> and B<s> in B<i2s_ASN1_IA5STRING>() stand for
+The letters B<i> and B<s> in B<i2s> and B<s2i> stand for
 "internal" (that is, an internal C structure) and string respectively.
 So B<i2s_ASN1_IA5STRING>() converts from internal to string.

@@ -70,6 +77,16 @@ string or NULL if an error occurs.
 B<s2i_ASN1_ENUMERATED>() returns the pointer to a B<ASN1_ENUMERATED>
 structure or NULL if an error occurs.

+B<s2i_ASN1_UTF8STRING>() return a valid
+B<ASN1_UTF8STRING> structure or NULL if an error occurs.
+
+B<i2s_ASN1_UTF8STRING>() returns the pointer to a UTF-8 string
+or NULL if an error occurs.
+
+=head1 HISTORY
+
+i2s_ASN1_UTF8STRING() and s2i_ASN1_UTF8STRING() were made public in OpenSSL 3.0.
+
 =head1 COPYRIGHT

 Copyright 2020 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man5/x509v3_config.pod b/doc/man5/x509v3_config.pod
index b2ee41b853..c15a1d0ce0 100644
--- a/doc/man5/x509v3_config.pod
+++ b/doc/man5/x509v3_config.pod
@@ -169,7 +169,7 @@ Examples:
 =head2 Subject Key Identifier

 The SKID extension specification has a value with three choices.
-If the value is the word B<none>, then no SKID extension will be included.
+If the value is the word B<none> then no SKID extension will be included.
 If the value is the word B<hash>, or by default for the B<x509>, B<req>, and
 B<ca> apps, the process specified in RFC 5280 section 4.2.1.2. (1) is followed:
 The keyIdentifier is composed of the 160-bit SHA-1 hash of the value of the BIT
@@ -193,14 +193,14 @@ indicated by putting a colon C<:> between the value and this option.
 By default the B<x509>, B<req>, and B<ca> apps behave as if
 "none" was given for self-signed certificates and "keyid, issuer" otherwise.

-If B<keyid> is present, an attempt is made to copy the subject key identifier
-(SKID) from the issuer certificate, which is the default behavior.
+If B<keyid> is present, an attempt is made to compute the hash of the public key
+corresponding to the signing key in case the certificate is self-signed,
+or else to copy the subject key identifier (SKID) from the issuer certificate.
 If this fails and the option B<always> is present, an error is returned.
-For self-issued certs the specification for the SKID must be given before.

-If B<issuer> is present and no B<keyid> has been added
-or it has the option B<always> specified, then
-the issuer DN and serial number are copied from the issuer certificate.
+If B<issuer> is present, and in addition it has the option B<always> specified
+or B<keyid> is not present,
+then the issuer DN and serial number are copied from the issuer certificate.

 Examples:

diff --git a/include/crypto/x509.h b/include/crypto/x509.h
index d88cd31902..1003f6f652 100644
--- a/include/crypto/x509.h
+++ b/include/crypto/x509.h
@@ -318,3 +318,5 @@ int X509_add_cert_new(STACK_OF(X509) **sk, X509 *cert, int flags);

 int X509_PUBKEY_get0_libctx(OSSL_LIB_CTX **plibctx, const char **ppropq,
                             const X509_PUBKEY *key);
+/* Calculate default key identifier according to RFC 5280 section 4.2.1.2 (1) */
+ASN1_OCTET_STRING *x509_pubkey_hash(X509_PUBKEY *pubkey);
diff --git a/include/crypto/x509v3.h b/include/crypto/x509v3.h
deleted file mode 100644
index 4ca85e9a2e..0000000000
--- a/include/crypto/x509v3.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2020 The OpenSSL Project Authors. All Rights Reserved.
- *
- * Licensed under the Apache License 2.0 (the "License").  You may not use
- * this file except in compliance with the License.  You can obtain a copy
- * in the file LICENSE in the source distribution or at
- * https://www.openssl.org/source/license.html
- */
-#ifndef OSSL_CRYPTO_X509V3_H
-# define OSSL_CRYPTO_X509V3_H
-
-#define EXT_UTF8STRING(nid) { nid, 0, ASN1_ITEM_ref(ASN1_UTF8STRING), \
-                        0,0,0,0, \
-                        (X509V3_EXT_I2S)i2s_ASN1_UTF8STRING, \
-                        (X509V3_EXT_S2I)s2i_ASN1_UTF8STRING, \
-                        0,0,0,0, \
-                        NULL}
-
-char *i2s_ASN1_UTF8STRING(X509V3_EXT_METHOD *method, ASN1_UTF8STRING *utf8);
-ASN1_UTF8STRING *s2i_ASN1_UTF8STRING(X509V3_EXT_METHOD *method,
-                                   X509V3_CTX *ctx, const char *str);
-
-#endif
diff --git a/include/openssl/x509v3.h.in b/include/openssl/x509v3.h.in
index dad8694ffa..3726f37999 100644
--- a/include/openssl/x509v3.h.in
+++ b/include/openssl/x509v3.h.in
@@ -98,6 +98,7 @@ struct v3_ext_ctx {
     X509_CRL *crl;
     X509V3_CONF_METHOD *db_meth;
     void *db;
+    EVP_PKEY *issuer_pkey;
 /* Maybe more here */
 };

@@ -380,6 +381,13 @@ struct ISSUING_DIST_POINT_st {
                         0,0,0,0, \
                         NULL}

+#define EXT_UTF8STRING(nid) { nid, 0, ASN1_ITEM_ref(ASN1_UTF8STRING), \
+                        0,0,0,0, \
+                        (X509V3_EXT_I2S)i2s_ASN1_UTF8STRING, \
+                        (X509V3_EXT_S2I)s2i_ASN1_UTF8STRING, \
+                        0,0,0,0, \
+                        NULL}
+
 # define EXT_END { -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

 /* X509_PURPOSE stuff */
@@ -525,6 +533,9 @@ STACK_OF(CONF_VALUE) *i2v_ASN1_BIT_STRING(X509V3_EXT_METHOD *method,
 char *i2s_ASN1_IA5STRING(X509V3_EXT_METHOD *method, ASN1_IA5STRING *ia5);
 ASN1_IA5STRING *s2i_ASN1_IA5STRING(X509V3_EXT_METHOD *method,
                                    X509V3_CTX *ctx, const char *str);
+char *i2s_ASN1_UTF8STRING(X509V3_EXT_METHOD *method, ASN1_UTF8STRING *utf8);
+ASN1_UTF8STRING *s2i_ASN1_UTF8STRING(X509V3_EXT_METHOD *method,
+                                   X509V3_CTX *ctx, const char *str);

 STACK_OF(CONF_VALUE) *i2v_GENERAL_NAME(X509V3_EXT_METHOD *method,
                                        GENERAL_NAME *gen,
@@ -645,6 +656,8 @@ void X509V3_string_free(X509V3_CTX *ctx, char *str);
 void X509V3_section_free(X509V3_CTX *ctx, STACK_OF(CONF_VALUE) *section);
 void X509V3_set_ctx(X509V3_CTX *ctx, X509 *issuer, X509 *subject,
                     X509_REQ *req, X509_CRL *crl, int flags);
+/* For API backward compatibility, this is separate from X509V3_set_ctx(): */
+int X509V3_set_issuer_pkey(X509V3_CTX *ctx, EVP_PKEY *pkey);

 int X509V3_add_value(const char *name, const char *value,
                      STACK_OF(CONF_VALUE) **extlist);
diff --git a/test/recipes/25-test_req.t b/test/recipes/25-test_req.t
index cb9f8888a5..7f699c065d 100644
--- a/test/recipes/25-test_req.t
+++ b/test/recipes/25-test_req.t
@@ -15,9 +15,9 @@ use OpenSSL::Test qw/:DEFAULT srctop_file/;

 setup("test_req");

-plan tests => 39;
+plan tests => 38;

-require_ok(srctop_file('test','recipes','tconversion.pl'));
+require_ok(srctop_file('test', 'recipes', 'tconversion.pl'));

 my @certs = qw(test certs);

@@ -259,7 +259,7 @@ sub generate_cert {
     my $cn = $is_ca ? "CA" : "EE";
     my $ca_key = srctop_file(@certs, "ca-key.pem");
     my $key = $is_ca ? $ca_key : srctop_file(@certs, "ee-key.pem");
-    my @cmd = ("openssl", "req", "-config", "\"\"","-x509",
+    my @cmd = ("openssl", "req", "-config", "\"\"", "-x509",
                "-key", $key, "-subj", "/CN=$cn", @_, "-out", $cert);
     push(@cmd, ("-CA", $ca_cert, "-CAkey", $ca_key)) unless $ss;
     ok(run(app([@cmd])), "generate $cert");
@@ -286,10 +286,10 @@ sub strict_verify {

 my @v3_ca = ("-addext", "basicConstraints = critical,CA:true",
              "-addext", "keyUsage = keyCertSign");
+my $SKID_AKID = "subjectKeyIdentifier,authorityKeyIdentifier";
 my $cert = "self-signed_v1_CA_no_KIDs.pem";
 generate_cert($cert);
-has_SKID($ca_cert, 0);
-has_AKID($ca_cert, 0);
+cert_ext_has_n_different_lines($cert, 0, $SKID_AKID); # no SKID and no AKID
 #TODO strict_verify($cert, 1); # self-signed v1 root cert should be accepted as CA

 $ca_cert = "self-signed_v3_CA_default_SKID.pem";
@@ -300,15 +300,13 @@ strict_verify($ca_cert, 1);

 $cert = "self-signed_v3_CA_no_SKID.pem";
 generate_cert($cert, @v3_ca, "-addext", "subjectKeyIdentifier = none");
-has_SKID($cert, 0);
-has_AKID($cert, 0);
+cert_ext_has_n_different_lines($cert, 0, $SKID_AKID); # no SKID and no AKID
 #TODO strict_verify($cert, 0);

 $cert = "self-signed_v3_CA_both_KIDs.pem";
 generate_cert($cert, @v3_ca, "-addext", "subjectKeyIdentifier = hash",
             "-addext", "authorityKeyIdentifier = keyid");
-has_SKID($cert, 1);
-has_AKID($cert, 1);
+cert_ext_has_n_different_lines($cert, 3, $SKID_AKID); # SKID == AKID
 strict_verify($cert, 1);

 $cert = "self-signed_v3_EE_wrong_keyUsage.pem";
@@ -317,8 +315,7 @@ generate_cert($cert, "-addext", "keyUsage = keyCertSign");

 $cert = "v3_EE_default_KIDs.pem";
 generate_cert($cert, "-addext", "keyUsage = dataEncipherment");
-has_SKID($cert, 1);
-has_AKID($cert, 1);
+cert_ext_has_n_different_lines($cert, 4, $SKID_AKID); # SKID != AKID
 strict_verify($cert, 1, $ca_cert);

 $cert = "v3_EE_no_AKID.pem";
@@ -326,3 +323,9 @@ generate_cert($cert, "-addext", "authorityKeyIdentifier = none");
 has_SKID($cert, 1);
 has_AKID($cert, 0);
 strict_verify($cert, 0, $ca_cert);
+
+$cert = "self-issued_v3_EE_default_KIDs.pem";
+generate_cert($cert, "-addext", "keyUsage = dataEncipherment",
+    "-in", srctop_file(@certs, "x509-check.csr"));
+cert_ext_has_n_different_lines($cert, 4, $SKID_AKID); # SKID != AKID
+strict_verify($cert, 1);
diff --git a/test/recipes/tconversion.pl b/test/recipes/tconversion.pl
index bf096994e3..6ae5cf17ea 100644
--- a/test/recipes/tconversion.pl
+++ b/test/recipes/tconversion.pl
@@ -111,8 +111,8 @@ sub cmp_text {
 sub file_contains {
     $_ = shift @_;
     my $pattern = shift @_;
-    open(DATA,$_) or return 0;
-    $_= join('',<DATA>);
+    open(DATA, $_) or return 0;
+    $_= join('', <DATA>);
     close(DATA);
     return m/$pattern/ ? 1 : 0;
 }
@@ -122,11 +122,37 @@ sub cert_contains {
     my $pattern = shift @_;
     my $expected = shift @_;
     my $name = shift @_;
-    my $out = "tmp.out";
+    my $out = "cert_contains.out";
     run(app(["openssl", "x509", "-noout", "-text", "-in", $cert, "-out", $out]));
     is(file_contains($out, $pattern), $expected, ($name ? "$name: " : "").
        "$cert should ".($expected ? "" : "not ")."contain $pattern");
     # not unlinking $out
 }

+sub uniq (@) {
+    my %seen = ();
+    grep { not $seen{$_}++ } @_;
+}
+
+sub file_n_different_lines {
+    my $filename = shift @_;
+    open(DATA, $filename) or return 0;
+    chomp(my @lines = <DATA>);
+    close(DATA);
+    return scalar(uniq @lines);
+}
+
+sub cert_ext_has_n_different_lines {
+    my $cert = shift @_;
+    my $expected = shift @_;
+    my $exts = shift @_;
+    my $name = shift @_;
+    my $out = "cert_n_different_exts.out";
+    run(app(["openssl", "x509", "-noout", "-ext", $exts,
+             "-in", $cert, "-out", $out]));
+    is(file_n_different_lines($out), $expected, ($name ? "$name: " : "").
+       "$cert '$exts' output should contain $expected different lines");
+    # not unlinking $out
+}
+
 1;
diff --git a/util/libcrypto.num b/util/libcrypto.num
index aa35b4185c..2f3255303d 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5100,6 +5100,9 @@ X509_STORE_load_file_ex                 ?	3_0_0	EXIST::FUNCTION:
 X509_STORE_load_store_ex                ?	3_0_0	EXIST::FUNCTION:
 X509_STORE_load_locations_ex            ?	3_0_0	EXIST::FUNCTION:
 X509_STORE_set_default_paths_ex         ?	3_0_0	EXIST::FUNCTION:
+X509V3_set_issuer_pkey                  ?	3_0_0	EXIST::FUNCTION:
+i2s_ASN1_UTF8STRING                     ?	3_0_0	EXIST::FUNCTION:
+s2i_ASN1_UTF8STRING                     ?	3_0_0	EXIST::FUNCTION:
 OSSL_STORE_open_ex                      ?	3_0_0	EXIST::FUNCTION:
 OSSL_DECODER_fetch                      ?	3_0_0	EXIST::FUNCTION:
 OSSL_DECODER_up_ref                     ?	3_0_0	EXIST::FUNCTION:
diff --git a/util/missingcrypto.txt b/util/missingcrypto.txt
index a4da3bc3fb..8b3ebab119 100644
--- a/util/missingcrypto.txt
+++ b/util/missingcrypto.txt
@@ -1294,7 +1294,6 @@ X509V3_get_value_int(3)
 X509V3_parse_list(3)
 X509V3_section_free(3)
 X509V3_set_conf_lhash(3)
-X509V3_set_ctx(3)
 X509V3_set_nconf(3)
 X509V3_string_free(3)
 X509_ALGORS_it(3)
diff --git a/util/missingmacro.txt b/util/missingmacro.txt
index a24cb8a685..4cad414f3a 100644
--- a/util/missingmacro.txt
+++ b/util/missingmacro.txt
@@ -174,3 +174,4 @@ X509V3_set_ctx_test(3)
 X509V3_set_ctx_nodb(3)
 EXT_BITSTRING(3)
 EXT_IA5STRING(3)
+EXT_UTF8STRING(3)