Don't automatically sync the two CONF parameters in X509V3_EXT_nconf.
https://boringssl-review.googlesource.com/c/boringssl/+/56109 tried to
simplify the X509V3_CTX story by automatically handling the second half
of initialization, but it turns out not all callers specify both values.
Instead, align with OpenSSL 3.0's behavior. Now X509V3_set_ctx
implicitly zeros the other fields, so it is the only mandatory init
function. This does mean callers which call X509V3_set_nconf before
X509V3_set_ctx will break, but that's true in OpenSSL 3.0 too.
I've retained the allowance for ctx being NULL, because whether
functions tolerate that or not is still a bit inconsistent. Also added
some TODOs about how strange this behavior is, but it's probably not
worth spending much more time on this code.
Change-Id: Ia04cf11eb5158374ca186795b7e579575e80666f
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/56265
Reviewed-by: Adam Langley <agl@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 32b2483..93dccda 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -6120,6 +6120,7 @@
// Repeat the test with an explicit |X509V3_CTX|.
X509V3_CTX ctx;
X509V3_set_ctx(&ctx, nullptr, nullptr, nullptr, nullptr, 0);
+ X509V3_set_nconf(&ctx, conf.get());
ext.reset(X509V3_EXT_nconf(conf.get(), &ctx, t.name, t.value));
if (t.expected.empty()) {
EXPECT_FALSE(ext);
diff --git a/crypto/x509v3/v3_conf.c b/crypto/x509v3/v3_conf.c
index b784954..3261302 100644
--- a/crypto/x509v3/v3_conf.c
+++ b/crypto/x509v3/v3_conf.c
@@ -83,26 +83,22 @@
static unsigned char *generic_asn1(const char *value, const X509V3_CTX *ctx,
long *ext_len);
-static void setup_ctx(X509V3_CTX *out, const CONF *conf,
- const X509V3_CTX *ctx_in) {
- if (ctx_in == NULL) {
- X509V3_set_ctx(out, NULL, NULL, NULL, NULL, 0);
- } else {
- *out = *ctx_in;
- }
- X509V3_set_nconf(out, conf);
-}
-
-X509_EXTENSION *X509V3_EXT_nconf(const CONF *conf, const X509V3_CTX *ctx_in,
+X509_EXTENSION *X509V3_EXT_nconf(const CONF *conf, const X509V3_CTX *ctx,
const char *name, const char *value) {
- X509V3_CTX ctx;
- setup_ctx(&ctx, conf, ctx_in);
+ // If omitted, fill in an empty |X509V3_CTX|.
+ X509V3_CTX ctx_tmp;
+ if (ctx == NULL) {
+ X509V3_set_ctx(&ctx_tmp, NULL, NULL, NULL, NULL, 0);
+ X509V3_set_nconf(&ctx_tmp, conf);
+ ctx = &ctx_tmp;
+ }
+
int crit = v3_check_critical(&value);
int ext_type = v3_check_generic(&value);
if (ext_type != 0) {
- return v3_generic_extension(name, value, crit, ext_type, &ctx);
+ return v3_generic_extension(name, value, crit, ext_type, ctx);
}
- X509_EXTENSION *ret = do_ext_nconf(conf, &ctx, OBJ_sn2nid(name), crit, value);
+ X509_EXTENSION *ret = do_ext_nconf(conf, ctx, OBJ_sn2nid(name), crit, value);
if (!ret) {
OPENSSL_PUT_ERROR(X509V3, X509V3_R_ERROR_IN_EXTENSION);
ERR_add_error_data(4, "name=", name, ", value=", value);
@@ -110,17 +106,23 @@
return ret;
}
-X509_EXTENSION *X509V3_EXT_nconf_nid(const CONF *conf, const X509V3_CTX *ctx_in,
+X509_EXTENSION *X509V3_EXT_nconf_nid(const CONF *conf, const X509V3_CTX *ctx,
int ext_nid, const char *value) {
- X509V3_CTX ctx;
- setup_ctx(&ctx, conf, ctx_in);
+ // If omitted, fill in an empty |X509V3_CTX|.
+ X509V3_CTX ctx_tmp;
+ if (ctx == NULL) {
+ X509V3_set_ctx(&ctx_tmp, NULL, NULL, NULL, NULL, 0);
+ X509V3_set_nconf(&ctx_tmp, conf);
+ ctx = &ctx_tmp;
+ }
+
int crit = v3_check_critical(&value);
int ext_type = v3_check_generic(&value);
if (ext_type != 0) {
return v3_generic_extension(OBJ_nid2sn(ext_nid), value, crit, ext_type,
- &ctx);
+ ctx);
}
- return do_ext_nconf(conf, &ctx, ext_nid, crit, value);
+ return do_ext_nconf(conf, ctx, ext_nid, crit, value);
}
// CONF *conf: Config file
@@ -143,6 +145,9 @@
// Now get internal extension representation based on type
if (method->v2i) {
if (*value == '@') {
+ // TODO(davidben): This is the only place where |X509V3_EXT_nconf|'s
+ // |conf| parameter is used. All other codepaths use the copy inside
+ // |ctx|. Should this be switched and then the parameter ignored?
if (conf == NULL) {
OPENSSL_PUT_ERROR(X509V3, X509V3_R_NO_CONFIG_DATABASE);
return NULL;
@@ -168,6 +173,10 @@
return NULL;
}
} else if (method->r2i) {
+ // TODO(davidben): Should this check be removed? This matches OpenSSL, but
+ // r2i-based extensions do not necessarily require a config database. The
+ // two built-in extensions only use it some of the time, and already handle
+ // |X509V3_get_section| returning NULL.
if (!ctx->db) {
OPENSSL_PUT_ERROR(X509V3, X509V3_R_NO_CONFIG_DATABASE);
return NULL;
@@ -416,6 +425,7 @@
void X509V3_set_ctx(X509V3_CTX *ctx, const X509 *issuer, const X509 *subj,
const X509_REQ *req, const X509_CRL *crl, int flags) {
+ OPENSSL_memset(ctx, 0, sizeof(*ctx));
ctx->issuer_cert = issuer;
ctx->subject_cert = subj;
ctx->crl = crl;
diff --git a/include/openssl/x509v3.h b/include/openssl/x509v3.h
index 530444b..37e1a22 100644
--- a/include/openssl/x509v3.h
+++ b/include/openssl/x509v3.h
@@ -565,8 +565,8 @@
// v3_ext_ctx, aka |X509V3_CTX|, contains additional context information for
// constructing extensions. Some string formats reference additional values in
-// these objects. It must be initialized with both |X509V3_set_ctx| and
-// |X509V3_set_nconf| before use.
+// these objects. It must be initialized with |X509V3_set_ctx| or
+// |X509V3_set_ctx_test| before use.
struct v3_ext_ctx {
int flags;
const X509 *issuer_cert;
@@ -578,16 +578,12 @@
#define X509V3_CTX_TEST 0x1
-// X509V3_set_ctx partially initializes |ctx| with the specified objects. Some
-// string formats will reference fields in these objects. Each object may be
-// NULL to omit it, in which case those formats cannot be used. |flags| should
-// be zero, unless called via |X509V3_set_ctx_test|.
+// X509V3_set_ctx initializes |ctx| with the specified objects. Some string
+// formats will reference fields in these objects. Each object may be NULL to
+// omit it, in which case those formats cannot be used. |flags| should be zero,
+// unless called via |X509V3_set_ctx_test|.
//
// |issuer|, |subject|, |req|, and |crl|, if non-NULL, must outlive |ctx|.
-//
-// WARNING: This function only partially initializes |ctx|. Unless otherwise
-// documented, callers must also call |X509V3_set_nconf| or
-// |X509V3_set_ctx_nodb|.
OPENSSL_EXPORT void X509V3_set_ctx(X509V3_CTX *ctx, const X509 *issuer,
const X509 *subject, const X509_REQ *req,
const X509_CRL *crl, int flags);
@@ -597,21 +593,15 @@
// incomplete and should be discarded. This can be used to partially validate
// syntax.
//
-// WARNING: This function only partially initializes |ctx|. Unless otherwise
-// documented, callers must also call |X509V3_set_nconf| or
-// |X509V3_set_ctx_nodb|.
-//
// TODO(davidben): Can we remove this?
#define X509V3_set_ctx_test(ctx) \
X509V3_set_ctx(ctx, NULL, NULL, NULL, NULL, X509V3_CTX_TEST)
-// X509V3_set_nconf partially initializes |ctx| with |conf| as the config
-// database. Some string formats will reference sections in |conf|. |conf| may
-// be NULL, in which case these formats cannot be used. If non-NULL, |conf| must
-// outlive |ctx|.
-//
-// WARNING: This function only partially initializes |ctx|. Callers must also
-// call |X509V3_set_ctx| or |X509V3_set_ctx_test|.
+// X509V3_set_nconf sets |ctx| to use |conf| as the config database. |ctx| must
+// have previously been initialized by |X509V3_set_ctx| or
+// |X509V3_set_ctx_test|. Some string formats will reference sections in |conf|.
+// |conf| may be NULL, in which case these formats cannot be used. If non-NULL,
+// |conf| must outlive |ctx|.
OPENSSL_EXPORT void X509V3_set_nconf(X509V3_CTX *ctx, const CONF *conf);
// X509V3_set_ctx_nodb calls |X509V3_set_nconf| with no config database.
@@ -624,8 +614,11 @@
// in which case features which use it will be disabled.
//
// If non-NULL, |ctx| must be initialized with |X509V3_set_ctx| or
-// |X509V3_set_ctx_test|. This function implicitly calls |X509V3_set_nconf| with
-// |conf|, so it is safe to only call |X509V3_set_ctx|.
+// |X509V3_set_ctx_test|.
+//
+// Both |conf| and |ctx| provide a |CONF| object. When |ctx| is non-NULL, most
+// features use the |ctx| copy, configured with |X509V3_set_ctx|, but some use
+// |conf|. Callers should ensure the two match to avoid surprisingly behavior.
OPENSSL_EXPORT X509_EXTENSION *X509V3_EXT_nconf(const CONF *conf,
const X509V3_CTX *ctx,
const char *name,