Commit 16e5d81a61 for openssl.org
commit 16e5d81a610003ea0b5e1d40c116f2b21b3443fd
Author: Viktor Dukhovni <openssl-users@dukhovni.org>
Date: Wed Feb 25 18:14:28 2026 +1100
Replace built-in AKID/SKID with configs
Add tests for suppression of skid/akid via explicit "none"
values and per-keyword "nonss" qualifiers and update docs.
Signing of X509 certs and X509_REQ CSRs rejects empty AKID/SKID
extensions, document and test this behaviour.
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Neil Horman <nhorman@openssl.org>
MergeDate: Sat Feb 28 15:22:53 2026
(Merged from https://github.com/openssl/openssl/pull/29057)
diff --git a/CHANGES.md b/CHANGES.md
index e3acbdd31c..09c2d4207c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,6 +31,23 @@ OpenSSL Releases
### Changes between 3.6 and 4.0 [xx XXX xxxx]
+ * The `openssl-x509(1)`, `openssl-req(1)` and `openssl-ca(1)` command-line
+ utilities no longer have specialised built-in logic to add the SKID and AKID
+ extensions, they are handled through configuration files and command-line
+ options just like any other extension. See their documentation and also
+ `x509v3_config(5)` for additional details.
+
+ The x509 "mini-CA" now attempts to find extension settings in the default
+ configuration file even if neither the `-extfile` nor the `-extensions`
+ option is explicitly specified. Failure to open the default configuration
+ is silently ignored.
+
+ The settings in the stock OpenSSL 4.0 configuration file arrange for
+ addition of the requisite SKID and AKID extensions. Other configuration
+ files may need to be adjusted if desired.
+
+ *Viktor Dukhovni*
+
* New `-expected-rpks` option in the `openssl-s_client(1)` and `openssl-s_server(1)`
command line utilities. This makes it possible to specify one more public keys
expected from the remote peer that are then used to authenticate the connection.
diff --git a/apps/openssl-vms.cnf b/apps/openssl-vms.cnf
index 9c02c1c7ec..da0a530bb5 100644
--- a/apps/openssl-vms.cnf
+++ b/apps/openssl-vms.cnf
@@ -23,12 +23,22 @@ config_diagnostics = 1
# oid_file = $ENV::HOME/.oid
oid_section = new_oids
-# To use this configuration file with the "-extfile" option of the
-# "openssl x509" utility, name here the section containing the
-# X.509v3 extensions to use:
-# extensions =
-# (Alternatively, use a configuration file that has only
-# X.509v3 extensions in its main [= default] section.)
+# When present, The `extensions` setting of the default configuration file is
+# used by the `openssl-x509(1)` "mini-CA" as a list of extensions to add to
+# each newly signed certificate. See the descriptions of the `-extfile` and
+# `-extensions` options for details in the documentation.
+#
+# The below setting arranges for `openssl-x509(1)` to add
+# subjectKeyIdentifier and authorityKeyIdentifier extensions by default.
+#
+extensions = default_skid_akid
+
+[ default_skid_akid ]
+# Always a subjectKeyIdentifier
+subjectKeyIdentifier = hash
+# Only if the certificate is not self-signed, with the issuer+serial only as a
+# fallback if no issuer keyid is available.
+authorityKeyIdentifier = keyid:nonss,issuer:nonss
[ new_oids ]
# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
@@ -213,7 +223,7 @@ basicConstraints=CA:FALSE
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
+authorityKeyIdentifier=keyid:nonss,issuer:nonss
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
@@ -236,16 +246,11 @@ basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[ v3_ca ]
-
-
# Extensions for a typical CA
-
# PKIX recommendation.
-
subjectKeyIdentifier=hash
-
-authorityKeyIdentifier=keyid:always,issuer
+authorityKeyIdentifier=keyid:nonss,issuer:nonss
basicConstraints = critical,CA:true
@@ -286,7 +291,7 @@ basicConstraints=CA:FALSE
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
+authorityKeyIdentifier=keyid:always
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
diff --git a/apps/openssl.cnf b/apps/openssl.cnf
index 4db6a549b1..1de7883d23 100644
--- a/apps/openssl.cnf
+++ b/apps/openssl.cnf
@@ -23,12 +23,22 @@ config_diagnostics = 1
# oid_file = $ENV::HOME/.oid
oid_section = new_oids
-# To use this configuration file with the "-extfile" option of the
-# "openssl x509" utility, name here the section containing the
-# X.509v3 extensions to use:
-# extensions =
-# (Alternatively, use a configuration file that has only
-# X.509v3 extensions in its main [= default] section.)
+# When present, The `extensions` setting of the default configuration file is
+# used by the `openssl-x509(1)` "mini-CA" as a list of extensions to add to
+# each newly signed certificate. See the descriptions of the `-extfile` and
+# `-extensions` options for details in the documentation.
+#
+# The below setting arranges for `openssl-x509(1)` to add
+# subjectKeyIdentifier and authorityKeyIdentifier extensions by default.
+#
+extensions = default_skid_akid
+
+[ default_skid_akid ]
+# Always a subjectKeyIdentifier
+subjectKeyIdentifier = hash
+# Only if the certificate is not self-signed, with the issuer+serial only as a
+# fallback if no issuer keyid is available.
+authorityKeyIdentifier = keyid:nonss,issuer:nonss
[ new_oids ]
# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
@@ -213,7 +223,7 @@ basicConstraints=CA:FALSE
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
+authorityKeyIdentifier=keyid:nonss,issuer:nonss
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
@@ -236,16 +246,11 @@ basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[ v3_ca ]
-
-
# Extensions for a typical CA
-
# PKIX recommendation.
-
subjectKeyIdentifier=hash
-
-authorityKeyIdentifier=keyid:always,issuer
+authorityKeyIdentifier=keyid:nonss,issuer:nonss
basicConstraints = critical,CA:true
@@ -286,7 +291,7 @@ basicConstraints=CA:FALSE
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
+authorityKeyIdentifier=keyid:always
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
diff --git a/crypto/x509/v3_akid.c b/crypto/x509/v3_akid.c
index 7556d10533..76bd07a6de 100644
--- a/crypto/x509/v3_akid.c
+++ b/crypto/x509/v3_akid.c
@@ -85,6 +85,13 @@ err:
return NULL;
}
+enum qualifier {
+ NEVER,
+ NONSS,
+ MAYBE,
+ GOTTO,
+};
+
/*-
* Three explicit tags may be given, where 'keyid' and 'issuer' may be combined:
* 'none': do not add any authority key identifier.
@@ -98,7 +105,6 @@ static AUTHORITY_KEYID *v2i_AUTHORITY_KEYID(X509V3_EXT_METHOD *method,
X509V3_CTX *ctx,
STACK_OF(CONF_VALUE) *values)
{
- char keyid = 0, issuer = 0;
int i, n = sk_CONF_VALUE_num(values);
CONF_VALUE *cnf;
ASN1_OCTET_STRING *ikeyid = NULL;
@@ -109,8 +115,8 @@ static AUTHORITY_KEYID *v2i_AUTHORITY_KEYID(X509V3_EXT_METHOD *method,
const X509_EXTENSION *ext;
X509 *issuer_cert;
int same_issuer, ss;
- int nonss = 0, always = 0;
AUTHORITY_KEYID *akeyid = AUTHORITY_KEYID_new();
+ enum qualifier keyid = NEVER, issuer = NEVER;
if (akeyid == NULL)
goto err;
@@ -119,22 +125,24 @@ static AUTHORITY_KEYID *v2i_AUTHORITY_KEYID(X509V3_EXT_METHOD *method,
return akeyid;
for (i = 0; i < n; i++) {
+ enum qualifier q = MAYBE;
+
cnf = sk_CONF_VALUE_value(values, i);
if (cnf->value != NULL && *cnf->value != '\0') {
if (strcmp(cnf->value, "always") == 0) {
- always = 1;
+ q = GOTTO;
} else if (strcmp(cnf->value, "nonss") == 0) {
- nonss = 1;
+ q = NONSS;
} else {
ERR_raise_data(ERR_LIB_X509V3, X509V3_R_UNKNOWN_OPTION,
"name=%s option=%s", cnf->name, cnf->value);
goto err;
}
}
- if (strcmp(cnf->name, "keyid") == 0 && keyid == 0) {
- keyid = always ? 3 : (nonss ? 1 : 2);
+ if (strcmp(cnf->name, "keyid") == 0 && keyid == NEVER) {
+ keyid = q;
} else if (strcmp(cnf->name, "issuer") == 0 && issuer == 0) {
- issuer = always ? 3 : (nonss ? 1 : 2);
+ issuer = q;
} else if (strcmp(cnf->name, "none") == 0
|| strcmp(cnf->name, "keyid") == 0
|| strcmp(cnf->name, "issuer") == 0) {
@@ -167,7 +175,7 @@ static AUTHORITY_KEYID *v2i_AUTHORITY_KEYID(X509V3_EXT_METHOD *method,
ss = same_issuer;
ERR_pop_to_mark();
- if (keyid > 1 || (keyid == 1 && !ss)) {
+ if (keyid > NONSS || (keyid == NONSS && !ss)) {
/*
* The subject key identifier of the issuer cert is acceptable unless
* the issuer cert is same as subject cert, but the subject will not
@@ -201,7 +209,7 @@ static AUTHORITY_KEYID *v2i_AUTHORITY_KEYID(X509V3_EXT_METHOD *method,
ikeyid = ossl_x509_pubkey_hash(pubkey);
X509_PUBKEY_free(pubkey);
}
- if (keyid == 3 && ikeyid == NULL) {
+ if (keyid == GOTTO && ikeyid == NULL) {
ERR_raise(ERR_LIB_X509V3, X509V3_R_UNABLE_TO_GET_ISSUER_KEYID);
goto err;
}
@@ -214,16 +222,16 @@ static AUTHORITY_KEYID *v2i_AUTHORITY_KEYID(X509V3_EXT_METHOD *method,
* serial number, so can't create an isser+serial AKID.
*/
if (!same_issuer || ss) {
- if (issuer == 3 /* always */
+ if (issuer == GOTTO
|| (ikeyid == NULL
- && (issuer == 2 /* absent a keyid */
- || (issuer == 1 && !ss) /* unless self-signed */))) {
+ && (issuer == MAYBE
+ || (issuer == NONSS && !ss)))) {
isname = X509_NAME_dup(X509_get_issuer_name(issuer_cert));
serial = ASN1_INTEGER_dup(X509_get0_serialNumber(issuer_cert));
}
}
/* "always" fails unless both issuer and serial are available */
- if (issuer == 3 && (isname == NULL || serial == NULL)) {
+ if (issuer == GOTTO && (isname == NULL || serial == NULL)) {
ERR_raise(ERR_LIB_X509V3, X509V3_R_UNABLE_TO_GET_ISSUER_DETAILS);
goto err;
}
diff --git a/crypto/x509/x509_v3.c b/crypto/x509/x509_v3.c
index ac53530914..e343162582 100644
--- a/crypto/x509/x509_v3.c
+++ b/crypto/x509/x509_v3.c
@@ -122,7 +122,11 @@ STACK_OF(X509_EXTENSION) *X509v3_add_ext(STACK_OF(X509_EXTENSION) **x,
/*
* Empty OCTET STRINGs and empty SEQUENCEs encode to just two bytes of tag
- * (0x04 or 0x30) and length (0x00).
+ * (0x04 or 0x30) and length (0x00). We use this fact to suppress empty
+ * AKID and SKID extensions that may be briefly generated when processing
+ * the "= none" value or only ":nonss"-qualified AKIDs when the subject is
+ * self-signed. The resulting extension is empty, and must not be retained,
+ * but does serve to drop any previous value of the same extension.
*/
if (ex->value.length == 2
&& (ex->value.data[0] == 0x30 || ex->value.data[0] == 0x04)) {
diff --git a/doc/man1/openssl-ca.pod.in b/doc/man1/openssl-ca.pod.in
index aba61c82ff..0b23d6cf2e 100644
--- a/doc/man1/openssl-ca.pod.in
+++ b/doc/man1/openssl-ca.pod.in
@@ -55,8 +55,8 @@ B<openssl> B<ca>
[B<-preserveDN>]
[B<-noemailDN>]
[B<-batch>]
+[B<-extfile> I<file>]
[B<-extensions> I<section>]
-[B<-extfile> I<section>]
[B<-subj> I<arg>]
[B<-utf8>]
[B<-sigopt> I<nm>:I<v>]
@@ -302,9 +302,9 @@ extension section format.
=item B<-extfile> I<file>
-An additional configuration file to read certificate extensions from
-(using the default section unless the B<-extensions> option is also
-used).
+An additional configuration I<file> to read certificate extensions from
+(using its unnamed (C<default>) section unless the B<-extensions> option is
+also used).
=item B<-subj> I<arg>
@@ -838,6 +838,10 @@ The B<-engine> option was removed in OpenSSL 4.0.
The B<-msie-hack> option was removed in OpenSSL 4.0.
+As of OpenSSL 4.0, the B<ca> utility no longer has specialised built-in logic
+to add the SKID or AKID extensions, they are handled through configuration
+files and command-line options just like any other extension.
+
=head1 SEE ALSO
L<openssl(1)>,
diff --git a/doc/man1/openssl-req.pod.in b/doc/man1/openssl-req.pod.in
index 30099e772e..031fd8ffca 100644
--- a/doc/man1/openssl-req.pod.in
+++ b/doc/man1/openssl-req.pod.in
@@ -312,15 +312,15 @@ X.509 extensions to be added can be specified in the configuration file,
possibly using the B<-config> and B<-extensions> options,
and/or using the B<-addext> option.
-Unless B<-x509v1> is given, generated certificates bear X.509 version 3.
-Unless specified otherwise,
-key identifier extensions are included as described in L<x509v3_config(5)>.
+Unless B<-x509v1> is given, generated certificates bear X.509 version 3
+even if no extensions are added.
=item B<-x509v1>
Request generation of certificates with X.509 version 1.
This implies B<-x509>.
-If X.509 extensions are given, anyway X.509 version 3 is set.
+If X.509 extensions are added, X.509 version 3 is used regardless of this
+option.
=item B<-CA> I<filename>|I<uri>
@@ -387,23 +387,39 @@ values for certain extensions such as subjectAltName.
=item B<-extensions> I<section>,
B<-reqexts> I<section>
-Can be used to override the name of the configuration file section
-from which X.509 extensions are included
-in the certificate (when B<-x509> is in use) or certificate request.
+Can be used to override the name of the configuration file section from which
+X.509 extensions are included in certificates (when B<-x509> is in use) or
+certificate requests.
This allows several different sections to be used in the same configuration
file to specify requests for a variety of purposes.
+OpenSSL 4.0 removed built-in generation of the B<subjectKeyIdentifier> and
+B<authorityKeyIdentifier> extensions when generating certificates.
+Any desired extensions need to be listed either in the configuration file or
+via the B<-addext> option described below.
+
=item B<-addext> I<ext>
-Add a specific extension to the certificate (if B<-x509> is in use)
-or certificate request. The argument must have the form of
-a C<key=value> pair as it would appear in a config file.
+Add a specific extension to the certificate (if B<-x509> is in use) or
+certificate request.
+The argument must have the form of a C<key=value> pair as it would appear in a
+configuration file.
+
+Each extension can appear at most once in a certificate or certificate request.
+Extensions listed via the option on the command-line override any corresponding
+extensions in the configuration file.
-If an extension is added using this option that has the same OID as one
-defined in the extension section of the config file, it overrides that one.
+This option can be given multiple times, but any given extension can be
+specified through this option at most once.
-This option can be given multiple times.
-Doing so, the same key must not be given more than once.
+To drop any unwanted B<subjectKeyIdentifier> or B<authorityKeyIdentifier>
+extensions that may be added by default via the configuration file use either
+or both of the options below:
+
+ -addext subjectKeyIdentifier=none
+ -addext authorityKeyIdentifier=none
+
+These work as expected even if the extensions were not there to begin with.
=item B<-precert>
@@ -838,6 +854,10 @@ Since OpenSSL 3.3, the B<-verify> option will exit with 1 on failure.
The B<-engine> option was removed in OpenSSL 4.0.
+As of OpenSSL 4.0, the B<req> utility no longer has specialised built-in logic
+to add the SKID or AKID extensions, they are handled through configuration
+files and command-line options just like any other extension.
+
=head1 COPYRIGHT
Copyright 2000-2025 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man1/openssl-x509.pod.in b/doc/man1/openssl-x509.pod.in
index 43a5747d80..eff02af047 100644
--- a/doc/man1/openssl-x509.pod.in
+++ b/doc/man1/openssl-x509.pod.in
@@ -483,20 +483,45 @@ neither subject identifier nor authority key identifier extensions are included.
Configuration file containing certificate and request X.509 extensions to add.
+When both of the B<-extfile> I<filename> and B<-extensions> I<section> options
+are given, the extensions to add are taken from the named I<section> of the
+file named I<filename>.
+This file must exist, be readable, and all the listed extensions must be valid.
+
+If only the B<-extfile> I<filename> option is given, the section name to use is
+taken from the value of the variable named C<extensions> in the file's unnamed
+(C<default>) section.
+If no B<extensions> setting is found, the file's C<default> section itself is
+instead used as the list of extensions to add.
+As before, the file must be readable and all the extensions must be valid.
+
+Prior to OpenSSL 4.0, the B<-extensions> option required that the B<-extfile>
+option be also specified.
+As of OpenSSL 4.0 the default configuration file is used instead as described
+below.
+
+If only the B<-extensions> I<section> option is given, the named I<section> of
+the default configuration file, whether from the C<OPENSSL_CONF> environment
+variable, or the built-in default, is used as list of extensions to add.
+As before, the file must be readable and all the extensions must be valid.
+
+If neither of the options are given, an attempt is made to open the default
+configuration file (as detailed above).
+If the file does not exist or cannot be opened, no extensions are added.
+If the unnamed (C<default>) section of the file does not list an B<extensions>
+variable, no extensions are added.
+Otherwise, the section named by the B<extensions> variable is taken to be the
+list of extensions to add.
+The extensions listed there must all be valid.
+
=item B<-extensions> I<section>
The section in the extfile to add X.509 extensions from.
-If this option is not
-specified then the extensions should either be contained in the unnamed
-(default) section or the default section should contain a variable called
-"extensions" which contains the section to use.
+See the description of B<-extfile> above for details.
See the L<x509v3_config(5)> manual page for details of the
extension section format.
-Unless specified otherwise,
-key identifier extensions are included as described in L<x509v3_config(5)>.
-
=item B<-sigopt> I<nm>:I<v>
Pass options to the signature algorithm during sign operations.
@@ -848,6 +873,14 @@ and key identifier extensions are included by default.
The B<-engine> option was removed in OpenSSL 4.0.
+OpenSSL 4.0 added support for specifying extensions to add via the default
+configuration file.
+See B<-extfile> above for details.
+
+As of OpenSSL 4.0, the B<x509> utility no longer has specialised built-in logic
+to add the SKID or AKID extensions, they are handled through configuration
+files and command-line options just like any other extension.
+
=head1 COPYRIGHT
Copyright 2000-2025 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man3/X509_sign.pod b/doc/man3/X509_sign.pod
index 92bd52b484..6c7a9f8d87 100644
--- a/doc/man3/X509_sign.pod
+++ b/doc/man3/X509_sign.pod
@@ -52,6 +52,10 @@ is not always updated meaning a stale version is sometimes used. This is not
normally a problem because modifying the signed portion will invalidate the
signature and signing will always update the encoding.
+When signing X509 certificates and certificate requests, any B<empty>
+subjectKeyIdentifier (empty string) or authorityKeyIdentifier (empty sequence)
+extensions are flagged as an error and the signature operation fails.
+
=head1 RETURN VALUES
All functions return the size of the signature
diff --git a/doc/man3/X509v3_get_ext_by_NID.pod b/doc/man3/X509v3_get_ext_by_NID.pod
index 5bd7e6058a..d9e4257940 100644
--- a/doc/man3/X509v3_get_ext_by_NID.pod
+++ b/doc/man3/X509v3_get_ext_by_NID.pod
@@ -128,6 +128,14 @@ because these functions do not free the extension they delete.
They return an B<X509_EXTENSION> object which must be explicitly freed
using X509_EXTENSION_free().
+X509v3_add_ext(), X509v3_add_extensions() and X509_add_ext() ignore any
+B<empty> values of the C<subjectKeyIdentifier> (empty OCTET STRING) or the
+C<authorityKeyIdentifier> (empty SEQUENCE) extensions.
+These empty values are not added to the extension list.
+In the case of X509v3_add_extensions() any previous instance of the same
+extension is deleted, so the call is not necessarily without side-effects in
+that case.
+
=head1 RETURN VALUES
X509v3_get_ext_count() returns the extension count or 0 for failure.
diff --git a/doc/man5/x509v3_config.pod b/doc/man5/x509v3_config.pod
index 781891966e..4ca94f0539 100644
--- a/doc/man5/x509v3_config.pod
+++ b/doc/man5/x509v3_config.pod
@@ -175,7 +175,7 @@ Examples:
=head2 Subject Key Identifier
-The SKID extension specification has a value with three choices.
+The Subject Key Identifier (SKID) extension takes three possible forms:
=over 4
@@ -191,12 +191,13 @@ STRING subjectPublicKey (excluding the tag, length, and number of unused bits).
=item A hex string (possibly with C<:> separating bytes)
-The provided value is output directly.
+The specified value is used directly.
This choice is strongly discouraged.
=back
-By default the B<x509>, B<req>, and B<ca> apps behave as if B<hash> was given.
+See L<openssl-x509(1)>, L<openssl-req(1)>, and L<openssl-ca(1)> documentation
+for further details relevant to each of the command-line utilities.
Example:
@@ -204,35 +205,58 @@ Example:
=head2 Authority Key Identifier
-The AKID extension specification may have the value B<none>
-indicating that no AKID shall be included.
-Otherwise it may have the value B<keyid> or B<issuer>
-or both of them, separated by C<,>.
-Either or both can have the option B<always>,
-indicated by putting a colon C<:> between the value and this option.
-For self-signed certificates the AKID is suppressed unless B<always> is present.
-
-By default the B<x509>, B<req>, and B<ca> apps behave as if B<none> was given
-for self-signed certificates and B<keyid>C<,> B<issuer> otherwise.
-
-If B<keyid> is present, an attempt is made to
-copy the subject key identifier (SKID) from the issuer certificate except if
-the issuer certificate is the same as the current one and it is not self-signed.
-The hash of the public key related to the signing key is taken as fallback
-if the issuer certificate is the same as the current certificate.
-If B<always> is present but no value can be obtained, an error is returned.
-
-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.
-If this fails, an error is returned.
+The Authority Key Identifier (SKID) extension takes two different forms.
+
+The value B<none> indicates that no AKID shall be included.
+
+Otherwise, the value syntax is that of a comma-separated list of either or both
+of the B<keyid> or B<issuer> keywords.
+
+The B<keyid> keyword asks that the AKID include the issuer's subject key
+identifier.
+
+The B<issuer> keyword asks that the AKID include the serial number and issuer
+distinguished name (grandparent name of subject certificate) of the issuer
+certificate.
+By default these are added only as a fallback when no SKID is available in the
+issuer certificate.
+
+Either or both keywords can be suffixed with an optional qualifier, which can
+be either C<always> or C<nonss>.
+If the qualifier is present, it is separated from the keyword by a colon
+(C<:>).
+
+The C<always> qualifier makes that AKID element mandatory, an error is raised
+if that element cannot be included.
+The B<issuer> certificate's issuer name and serial number are no longer a
+fallback when the B<always> qualifier is used.
+The C<nonss> qualifier makes that AKID element further conditional on the
+certificate not being self-signed.
+
+When creating a self-signed certificate, be sure to specify a SKID extension if
+you want to have a mandatory (C<always> qualified) B<keyid> in the AKID
+extension.
+
+If an empty AKID would otherwise be generated, the extension is instead skipped
+entirely, just as with C<none>.
+
+See L<openssl-x509(1)>, L<openssl-req(1)>, and L<openssl-ca(1)> for further
+details relevant to that specific command-line utility.
Examples:
+ # Keyid preferred, otherwise issuer name & serial
authorityKeyIdentifier = keyid, issuer
+ # As above, but skip the extension entirely if self-signed
+ authorityKeyIdentifier = keyid:nonss, issuer:nonss
+
+ # Keyid when possible, issuer name & serial always
authorityKeyIdentifier = keyid, issuer:always
+ # Keyid when possible, issuer as fallback when not self-signed
+ authorityKeyIdentifier = keyid, issuer:nonss
+
=head2 Subject Alternative Name
This is a multi-valued extension that supports several types of name
@@ -606,6 +630,15 @@ invalid extensions if they are not used carefully.
L<openssl-req(1)>, L<openssl-ca(1)>, L<openssl-x509(1)>,
L<ASN1_generate_nconf(3)>
+=head1 HISTORY
+
+OpenSSL 4.0 updated the syntax of the B<subjectKeyIdentifier> (SKID) and
+B<authorityKeyIdentifier> (AKID) extensions, described above, to introduce the
+C<nonss> qualifier for the B<keyid> and B<issuer> keywords.
+The command-line utilities no longer have specialised built-in logic to add
+these extensions, they are handled through configuration files and command-line
+options just like any other extension.
+
=head1 COPYRIGHT
Copyright 2004-2025 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/test/ca-and-certs.cnf b/test/ca-and-certs.cnf
index 58ca0eda64..78016a4323 100644
--- a/test/ca-and-certs.cnf
+++ b/test/ca-and-certs.cnf
@@ -9,6 +9,8 @@ CN2 = Brother 2
distinguished_name = req_distinguished_name
encrypt_rsa_key = no
default_md = sha1
+req_extensions = empty
+x509_extensions = minimal
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
@@ -33,6 +35,21 @@ organizationName = Dodgy Brothers
[ empty ]
+[ minimal ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:nonss
+
+[ v3_skid ]
+subjectKeyIdentifier = hash
+
+[ v3_akid ]
+# With just the AKID, we can't produce keyids for self-signed certs.
+authorityKeyIdentifier = keyid:nonss, issuer
+
+[ v3_askid ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always
+
[ v3_ee ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
@@ -85,7 +102,7 @@ emailAddress = optional
[ v3_ca ]
subjectKeyIdentifier = hash
-authorityKeyIdentifier = keyid:always,issuer:always
+authorityKeyIdentifier = keyid:nonss,issuer:nonss
basicConstraints = critical,CA:true,pathlen:1
keyUsage = cRLSign, keyCertSign
issuerAltName = issuer:copy
diff --git a/test/recipes/25-test_req.t b/test/recipes/25-test_req.t
index 2230d39476..eb4088949a 100644
--- a/test/recipes/25-test_req.t
+++ b/test/recipes/25-test_req.t
@@ -542,8 +542,8 @@ 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",
- "-subj", "/CN=$cn", @_, "-out", $cert);
+ my @cmd = ("openssl", "req", "-config", srctop_file("test", "ca-and-certs.cnf"),
+ "-x509", "-subj", "/CN=$cn", @_, "-out", $cert);
push(@cmd, ("-key", $key)) if $ss;
push(@cmd, ("-CA", $ca_cert, "-CAkey", $ca_key)) unless $ss;
ok(run(app([@cmd])), "generate $cert");
@@ -573,15 +573,19 @@ my $SKID_AKID = "subjectKeyIdentifier,authorityKeyIdentifier";
my $cert = "self-signed_default_SKID_no_explicit_exts.pem";
generate_cert($cert);
has_version($cert, 3);
-has_SKID($cert, 1); # SKID added, though no explicit extensions given
+has_SKID($cert, 1);
has_AKID($cert, 0);
-my $cert = "self-signed_v3_CA_hash_SKID.pem";
+$cert = "self-signed_v3_CA_hash_SKID.pem";
generate_cert($cert, @v3_ca, "-addext", "subjectKeyIdentifier = hash");
has_SKID($cert, 1); # explicit hash SKID
$cert = "self-signed_v3_CA_no_SKID.pem";
-generate_cert($cert, @v3_ca, "-addext", "subjectKeyIdentifier = none");
+generate_cert($cert, @v3_ca,
+ # Add SKID
+ "-extensions", "v3_skid",
+ # And explicitly drop it
+ "-addext", "subjectKeyIdentifier = none");
cert_ext_has_n_different_lines($cert, 0, $SKID_AKID); # no SKID and no AKID
#TODO strict_verify($cert, 0);
@@ -605,12 +609,16 @@ has_AKID($ca_cert, 0); # no default AKID
strict_verify($ca_cert, 1);
$cert = "self-signed_v3_CA_no_AKID.pem";
-generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = none");
+generate_cert($cert, @v3_ca,
+ # Add AKID
+ "-extensions", "v3_akid",
+ # Explicitly drop it
+ "-addext", "authorityKeyIdentifier = none");
has_AKID($cert, 0); # forced no AKID
$cert = "self-signed_v3_CA_explicit_AKID.pem";
-generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = keyid");
-has_AKID($cert, 1);
+generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = keyid:nonss");
+has_AKID($cert, 0); # for self-signed cert, AKID suppressed and not forced
$cert = "self-signed_v3_CA_forced_AKID.pem";
generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = keyid:always");
@@ -618,23 +626,23 @@ cert_ext_has_n_different_lines($cert, 3, $SKID_AKID); # forced AKID, AKID == SKI
strict_verify($cert, 1);
$cert = "self-signed_v3_CA_issuer_AKID.pem";
-generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = issuer");
-has_AKID($cert, 1);
+generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = issuer:nonss");
+has_AKID($cert, 0); # suppressed AKID since not forced
$cert = "self-signed_v3_CA_forced_issuer_AKID.pem";
generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = issuer:always");
cert_contains($cert, "Authority Key Identifier: DirName:/CN=CA serial:", 1); # forced issuer AKID
$cert = "self-signed_v3_CA_nonforced_keyid_issuer_AKID.pem";
-generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = keyid, issuer");
-has_AKID($cert, 1);
+generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = keyid:nonss, issuer:nonss");
+has_AKID($cert, 0); # AKID not present because not forced and cert self-signed
$cert = "self-signed_v3_CA_keyid_forced_issuer_AKID.pem";
-generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = keyid, issuer:always");
-cert_contains($cert, "Authority Key Identifier: keyid(:[0-9A-Fa-f]{2})+ DirName:/CN=CA serial:", 1);
+generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = keyid:nonss, issuer:always");
+cert_contains($cert, "Authority Key Identifier: DirName:/CN=CA serial:", 1); # issuer AKID forced, with keyid not forced
$cert = "self-signed_v3_CA_forced_keyid_issuer_AKID.pem";
-generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = keyid:always, issuer");
+generate_cert($cert, @v3_ca, "-addext", "authorityKeyIdentifier = keyid:always, issuer:nonss");
has_AKID($cert, 1); # AKID with keyid forced
cert_contains($cert, "Authority Key Identifier: DirName:/CN=CA serial:", 0); # no issuer AKID
@@ -666,10 +674,14 @@ cert_ext_has_n_different_lines($cert, 4, $SKID_AKID); # SKID != AKID
strict_verify($cert, 1);
$cert = "self-issued_v3_CA_no_AKID.pem";
-generate_cert($cert, "-addext", "authorityKeyIdentifier = none",
- "-in", srctop_file(@certs, "x509-check.csr"));
+generate_cert($cert,
+ # Add SKID and AKID
+ "-extensions", "v3_askid",
+ # Explicitly drop the AKID
+ "-addext", "authorityKeyIdentifier = none",
+ "-in", srctop_file(@certs, "x509-check.csr"));
has_version($cert, 3);
-has_SKID($cert, 1); # SKID added, though no explicit extensions given
+has_SKID($cert, 1);
has_AKID($cert, 0);
strict_verify($cert, 1);
@@ -766,7 +778,7 @@ ok(run(app(["openssl", "x509", "-in", "testreq-cert.pem",
# Generate cert with explicit start and end dates
my %today = (strftime("%Y-%m-%d", gmtime) => 1);
-my $cert = "self-signed_explicit_date.pem";
+$cert = "self-signed_explicit_date.pem";
ok(run(app(["openssl", "req", "-x509", "-new", "-text",
"-config", srctop_file('test', 'test.cnf'),
"-key", srctop_file("test", "testrsa.pem"),
diff --git a/test/recipes/80-test_ca.t b/test/recipes/80-test_ca.t
index f33a5d51b3..363fc100b5 100644
--- a/test/recipes/80-test_ca.t
+++ b/test/recipes/80-test_ca.t
@@ -80,7 +80,7 @@ SKIP: {
}
my $v3_cert = "v3-test.crt";
-ok(run(app(["openssl", "ca", "-batch", "-config", $cnf, "-extensions", "empty",
+ok(run(app(["openssl", "ca", "-batch", "-config", $cnf, "-extensions", "minimal",
"-in", src_file("x509-check.csr"), "-out", $v3_cert])));
# although no explicit extensions given:
has_version($v3_cert, 3);
diff --git a/test/x509_test.c b/test/x509_test.c
index 7ef6094a1d..a3306dc8af 100644
--- a/test/x509_test.c
+++ b/test/x509_test.c
@@ -10,6 +10,7 @@
#define OPENSSL_SUPPRESS_DEPRECATED /* EVP_PKEY_get1/set1_RSA */
#include <openssl/x509.h>
+#include <openssl/x509v3.h>
#include <openssl/asn1.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
@@ -284,6 +285,122 @@ err:
return ret;
}
+static int test_drop_empty_cert_keyids(void)
+{
+ static const unsigned char commonName[] = "test";
+ X509 *x = NULL;
+ X509_NAME *subject = NULL;
+ X509_NAME_ENTRY *name_entry = NULL;
+ X509_EXTENSION *ext = NULL;
+ X509V3_CTX ctx;
+ int ret = 0;
+
+ if (!TEST_ptr(x = X509_new())
+ || !TEST_int_eq(X509_set_version(x, X509_VERSION_3), 1)
+ || !TEST_int_eq(ASN1_INTEGER_set(X509_get_serialNumber(x), 1), 1)
+ || !TEST_ptr(subject = X509_NAME_new()))
+ goto err;
+
+ name_entry = X509_NAME_ENTRY_create_by_NID(NULL, NID_commonName,
+ MBSTRING_ASC, commonName, -1);
+ if (!TEST_ptr(name_entry)
+ || !TEST_int_eq(X509_NAME_add_entry(subject, name_entry, -1, 0), 1)
+ || !TEST_int_eq(X509_set_subject_name(x, subject), 1)
+ || !TEST_int_eq(X509_set_issuer_name(x, subject), 1)
+ || !TEST_ptr(X509_gmtime_adj(X509_getm_notBefore(x), 0))
+ || !TEST_ptr(X509_gmtime_adj(X509_getm_notAfter(x), 24 * 3600))
+ || !TEST_int_eq(X509_set_pubkey(x, pubkey), 1))
+ goto err;
+
+ X509V3_set_ctx(&ctx, x, x, NULL, NULL, 0);
+ if (!TEST_ptr(ext = X509V3_EXT_conf(NULL, &ctx, "subjectKeyIdentifier",
+ "none"))
+ || !TEST_int_eq(X509_add_ext(x, ext, -1), 1)
+ || !TEST_ptr_null(X509_get0_extensions(x)))
+ goto err;
+
+ X509_EXTENSION_free(ext);
+ if (!TEST_ptr(ext = X509V3_EXT_conf(NULL, &ctx, "authorityKeyIdentifier",
+ "none"))
+ || !TEST_int_eq(X509_add_ext(x, ext, -1), 1)
+ || !TEST_ptr_null(X509_get0_extensions(x))
+ || !TEST_int_gt(X509_sign(x, privkey, signmd), 0))
+ goto err;
+
+ ret = 1;
+err:
+ X509_NAME_ENTRY_free(name_entry);
+ X509_NAME_free(subject);
+ X509_EXTENSION_free(ext);
+ X509_free(x);
+ return ret;
+}
+
+static int test_drop_empty_csr_keyids(void)
+{
+ static const unsigned char commonName[] = "test";
+ X509_REQ *x = NULL;
+ X509_NAME *subject = NULL;
+ X509_NAME_ENTRY *name_entry = NULL;
+ X509_EXTENSION *ext = NULL;
+ STACK_OF(X509_EXTENSION) *exts = NULL;
+ X509V3_CTX ctx;
+ int ret = 0;
+
+ if (!TEST_ptr(x = X509_REQ_new())
+ || !TEST_int_eq(X509_REQ_set_version(x, X509_REQ_VERSION_1), 1)
+ || !TEST_ptr(subject = X509_NAME_new()))
+ goto err;
+
+ name_entry = X509_NAME_ENTRY_create_by_NID(NULL, NID_commonName,
+ MBSTRING_ASC, commonName, -1);
+ if (!TEST_ptr(name_entry)
+ || !TEST_int_eq(X509_NAME_add_entry(subject, name_entry, -1, 0), 1)
+ || !TEST_int_eq(X509_REQ_set_subject_name(x, subject), 1)
+ || !TEST_int_eq(X509_REQ_set_pubkey(x, pubkey), 1))
+ goto err;
+
+ X509V3_set_ctx(&ctx, NULL, NULL, x, NULL, 0);
+ if (!TEST_ptr(ext = X509V3_EXT_conf(NULL, &ctx, "subjectKeyIdentifier",
+ "none"))
+ || !TEST_ptr(X509v3_add_ext(&exts, ext, -1))
+ || !TEST_int_eq(sk_X509_EXTENSION_num(exts), 0))
+ goto err;
+ X509_EXTENSION_free(ext);
+
+ if (!TEST_ptr(ext = X509V3_EXT_conf(NULL, &ctx, "authorityKeyIdentifier",
+ "none"))
+ || !TEST_ptr(X509v3_add_ext(&exts, ext, -1)))
+ goto err;
+
+ if (!TEST_int_eq(X509_REQ_add_extensions(x, exts), 1)
+ || !TEST_int_eq(sk_X509_EXTENSION_num(exts), 0))
+ goto err;
+ sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
+
+ if (!TEST_ptr(exts = X509_REQ_get_extensions(x))
+ || !TEST_int_eq(sk_X509_EXTENSION_num(exts), 0))
+ goto err;
+ sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
+ exts = NULL;
+
+ if (!TEST_int_gt(X509_REQ_sign(x, privkey, signmd), 0))
+ goto err;
+
+ if (!TEST_ptr(exts = X509_REQ_get_extensions(x))
+ || !TEST_int_eq(sk_X509_EXTENSION_num(exts), 0))
+ goto err;
+
+ ret = 1;
+err:
+ X509_NAME_ENTRY_free(name_entry);
+ X509_NAME_free(subject);
+ X509_EXTENSION_free(ext);
+ sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
+ X509_REQ_free(x);
+ return ret;
+}
+
OPT_TEST_DECLARE_USAGE("<pss-self-signed-cert.pem>\n")
int setup_tests(void)
@@ -321,6 +438,8 @@ int setup_tests(void)
ADD_TEST(test_x509_delete_last_extension);
ADD_TEST(test_x509_crl_delete_last_extension);
ADD_TEST(test_x509_revoked_delete_last_extension);
+ ADD_TEST(test_drop_empty_cert_keyids);
+ ADD_TEST(test_drop_empty_csr_keyids);
return 1;
}