Commit 1b25e3fca5 for openssl.org

commit 1b25e3fca5772fce2edd71a6adfc306dbe254c3e
Author: Greensi7 <adam.tabak04@gmail.com>
Date:   Thu Aug 6 15:33:27 2026 +0200

    Reject duplicate fields in X509v3 config

    Reject repeated fields in policyConstraints, basicConstraints
    and basicAttConstraints instead of silently accepting the last value.

    Assisted-by: ChatGPT:gpt-5.6

    Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
    Reviewed-by: Andrew Dinh <andrewd@openssl.org>
    MergeDate: Sun Aug  9 04:06:37 2026
    (Merged from https://github.com/openssl/openssl/pull/32181)

diff --git a/CHANGES.md b/CHANGES.md
index 139bacbbc4..1bfca49b15 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,6 +31,12 @@ OpenSSL Releases

 ### Changes between 4.0 and 4.1 [xx XXX xxxx]

+ * Repeated fields in the `basicConstraints`, `basicAttConstraints`,
+   and `policyConstraints` X.509v3 extension configurations are now rejected
+   instead of silently using the last value.
+
+   *Adam Tabak*
+
  * The `tsget` utility now uses `Net::Curl::Easy` (from the `Net-Curl` CPAN
    distribution) instead of the abandoned `WWW::Curl::Easy`.  Users who relied
    on `tsget` must install `Net::Curl::Easy` before upgrading.
diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index 8b6c912977..de4b47ebb7 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -1775,6 +1775,7 @@ X509V3_R_BN_DEC2BN_ERROR:100:bn dec2bn error
 X509V3_R_BN_TO_ASN1_INTEGER_ERROR:101:bn to asn1 integer error
 X509V3_R_DIRNAME_ERROR:149:dirname error
 X509V3_R_DISTPOINT_ALREADY_SET:160:distpoint already set
+X509V3_R_DUPLICATE_FIELD:174:duplicate field
 X509V3_R_DUPLICATE_ZONE_ID:133:duplicate zone id
 X509V3_R_EMPTY_KEY_USAGE:169:empty key usage
 X509V3_R_ERROR_CONVERTING_ZONE:131:error converting zone
diff --git a/crypto/x509/v3_battcons.c b/crypto/x509/v3_battcons.c
index 7bcca27537..fd2fd87f7b 100644
--- a/crypto/x509/v3_battcons.c
+++ b/crypto/x509/v3_battcons.c
@@ -61,7 +61,7 @@ static OSSL_BASIC_ATTR_CONSTRAINTS *v2i_OSSL_BASIC_ATTR_CONSTRAINTS(
 {
     OSSL_BASIC_ATTR_CONSTRAINTS *battcons = NULL;
     CONF_VALUE *val;
-    int i;
+    int i, authority_seen = 0;

     if ((battcons = OSSL_BASIC_ATTR_CONSTRAINTS_new()) == NULL) {
         ERR_raise(ERR_LIB_X509V3, ERR_R_ASN1_LIB);
@@ -70,11 +70,20 @@ static OSSL_BASIC_ATTR_CONSTRAINTS *v2i_OSSL_BASIC_ATTR_CONSTRAINTS(
     for (i = 0; i < sk_CONF_VALUE_num(values); i++) {
         val = sk_CONF_VALUE_value(values, i);
         if (strcmp(val->name, "authority") == 0) {
+            if (authority_seen) {
+                ERR_raise_data(ERR_LIB_X509V3, X509V3_R_DUPLICATE_FIELD,
+                               "field=%s", val->name);
+                goto err;
+            }
             if (!X509V3_get_value_bool(val, &battcons->authority))
                 goto err;
+            authority_seen = 1;
         } else if (strcmp(val->name, "pathlen") == 0) {
-            ASN1_INTEGER_free(battcons->pathlen);
-            battcons->pathlen = NULL;
+            if (battcons->pathlen != NULL) {
+                ERR_raise_data(ERR_LIB_X509V3, X509V3_R_DUPLICATE_FIELD,
+                               "field=%s", val->name);
+                goto err;
+            }
             if (!X509V3_get_value_int(val, &battcons->pathlen))
                 goto err;
         } else {
diff --git a/crypto/x509/v3_bcons.c b/crypto/x509/v3_bcons.c
index da438346b8..88ecc35793 100644
--- a/crypto/x509/v3_bcons.c
+++ b/crypto/x509/v3_bcons.c
@@ -60,7 +60,7 @@ static BASIC_CONSTRAINTS *v2i_BASIC_CONSTRAINTS(X509V3_EXT_METHOD *method,
 {
     BASIC_CONSTRAINTS *bcons = NULL;
     CONF_VALUE *val;
-    int i;
+    int i, ca_seen = 0;

     if ((bcons = BASIC_CONSTRAINTS_new()) == NULL) {
         ERR_raise(ERR_LIB_X509V3, ERR_R_ASN1_LIB);
@@ -69,11 +69,20 @@ static BASIC_CONSTRAINTS *v2i_BASIC_CONSTRAINTS(X509V3_EXT_METHOD *method,
     for (i = 0; i < sk_CONF_VALUE_num(values); i++) {
         val = sk_CONF_VALUE_value(values, i);
         if (strcmp(val->name, "CA") == 0) {
+            if (ca_seen) {
+                ERR_raise_data(ERR_LIB_X509V3, X509V3_R_DUPLICATE_FIELD,
+                               "field=%s", val->name);
+                goto err;
+            }
             if (!X509V3_get_value_bool(val, &bcons->ca))
                 goto err;
+            ca_seen = 1;
         } else if (strcmp(val->name, "pathlen") == 0) {
-            ASN1_INTEGER_free(bcons->pathlen);
-            bcons->pathlen = NULL;
+            if (bcons->pathlen != NULL) {
+                ERR_raise_data(ERR_LIB_X509V3, X509V3_R_DUPLICATE_FIELD,
+                               "field=%s", val->name);
+                goto err;
+            }
             if (!X509V3_get_value_int(val, &bcons->pathlen))
                 goto err;
         } else {
diff --git a/crypto/x509/v3_pcons.c b/crypto/x509/v3_pcons.c
index 376be44608..db5d3e9acb 100644
--- a/crypto/x509/v3_pcons.c
+++ b/crypto/x509/v3_pcons.c
@@ -67,13 +67,19 @@ static void *v2i_POLICY_CONSTRAINTS(const X509V3_EXT_METHOD *method,
     for (i = 0; i < sk_CONF_VALUE_num(values); i++) {
         val = sk_CONF_VALUE_value(values, i);
         if (strcmp(val->name, "requireExplicitPolicy") == 0) {
-            ASN1_INTEGER_free(pcons->requireExplicitPolicy);
-            pcons->requireExplicitPolicy = NULL;
+            if (pcons->requireExplicitPolicy != NULL) {
+                ERR_raise_data(ERR_LIB_X509V3, X509V3_R_DUPLICATE_FIELD,
+                               "field=%s", val->name);
+                goto err;
+            }
             if (!X509V3_get_value_int(val, &pcons->requireExplicitPolicy))
                 goto err;
         } else if (strcmp(val->name, "inhibitPolicyMapping") == 0) {
-            ASN1_INTEGER_free(pcons->inhibitPolicyMapping);
-            pcons->inhibitPolicyMapping = NULL;
+            if (pcons->inhibitPolicyMapping != NULL) {
+                ERR_raise_data(ERR_LIB_X509V3, X509V3_R_DUPLICATE_FIELD,
+                               "field=%s", val->name);
+                goto err;
+            }
             if (!X509V3_get_value_int(val, &pcons->inhibitPolicyMapping))
                 goto err;
         } else {
diff --git a/crypto/x509/v3err.c b/crypto/x509/v3err.c
index 96a27aaf48..9368da4906 100644
--- a/crypto/x509/v3err.c
+++ b/crypto/x509/v3err.c
@@ -1,6 +1,6 @@
 /*
  * Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 1995-2025 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2026 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
@@ -25,6 +25,7 @@ static const ERR_STRING_DATA X509V3_str_reasons[] = {
     { ERR_PACK(ERR_LIB_X509V3, 0, X509V3_R_DIRNAME_ERROR), "dirname error" },
     { ERR_PACK(ERR_LIB_X509V3, 0, X509V3_R_DISTPOINT_ALREADY_SET),
         "distpoint already set" },
+    { ERR_PACK(ERR_LIB_X509V3, 0, X509V3_R_DUPLICATE_FIELD), "duplicate field" },
     { ERR_PACK(ERR_LIB_X509V3, 0, X509V3_R_DUPLICATE_ZONE_ID),
         "duplicate zone id" },
     { ERR_PACK(ERR_LIB_X509V3, 0, X509V3_R_EMPTY_KEY_USAGE), "empty key usage" },
diff --git a/include/crypto/x509v3err.h b/include/crypto/x509v3err.h
index b3874fc472..279acb7e61 100644
--- a/include/crypto/x509v3err.h
+++ b/include/crypto/x509v3err.h
@@ -1,6 +1,6 @@
 /*
  * Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 2020-2021 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2020-2026 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
diff --git a/include/openssl/x509v3err.h b/include/openssl/x509v3err.h
index 19f938626b..75d9e7d3e4 100644
--- a/include/openssl/x509v3err.h
+++ b/include/openssl/x509v3err.h
@@ -1,6 +1,6 @@
 /*
  * Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 1995-2025 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2026 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
@@ -27,6 +27,7 @@
 #define X509V3_R_BN_TO_ASN1_INTEGER_ERROR 101
 #define X509V3_R_DIRNAME_ERROR 149
 #define X509V3_R_DISTPOINT_ALREADY_SET 160
+#define X509V3_R_DUPLICATE_FIELD 174
 #define X509V3_R_DUPLICATE_ZONE_ID 133
 #define X509V3_R_EMPTY_KEY_USAGE 169
 #define X509V3_R_ERROR_CONVERTING_ZONE 131