From: Daniel Kubec Date: Mon, 2 Mar 2026 16:56:52 +0000 (+0100) Subject: x509: add EXFLAG_DUPLICATE and cheap O(1) extension duplicate check X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=25c2f88caaaa53db5f116206207d1d760c9d2c53;p=thirdparty%2Fopenssl.git x509: add EXFLAG_DUPLICATE and cheap O(1) extension duplicate check In ossl_x509v3_cache_extensions(), introduce EXFLAG_DUPLICATE flag to signal duplicate X.509 extensions. Add O(1) duplicate detection using a bitset with minimal stack memory footprint, in compliance with RFC 5280 Section 4.2. Fixes #26325 Co-authored-by: Tomáš Mráz Co-authored-by: David von Oheimb Reviewed-by: Tomas Mraz Reviewed-by: Simo Sorce Reviewed-by: Paul Dale Reviewed-by: Neil Horman MergeDate: Tue Mar 17 13:43:13 2026 (Merged from https://github.com/openssl/openssl/pull/30233) --- diff --git a/CHANGES.md b/CHANGES.md index 24b68d6d06b..88b6a0ebdec 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -55,6 +55,12 @@ OpenSSL Releases *Nikolas Gauder* + * Add new verification error `X509_V_ERR_DUPLICATE_EXTENSION` with descriptive + message for certificates containing duplicate X.509 extensions, which are + explicitly prohibited by [RFC 5280]. + + *Daniel Kubec* + ### Changes between 3.6 and 4.0 [xx XXX xxxx] * Added `-expected-rpks` option to the `openssl s_client` diff --git a/crypto/x509/v3_purp.c b/crypto/x509/v3_purp.c index 7b548e32f5a..df1f0ae2d08 100644 --- a/crypto/x509/v3_purp.c +++ b/crypto/x509/v3_purp.c @@ -15,6 +15,8 @@ #include "crypto/x509.h" #include "internal/tsan_assist.h" #include "x509_local.h" +#include "crypto/objects/obj_dat.h" +#include "internal/hashfunc.h" static int check_ssl_ca(const X509 *x); static int check_purpose_ssl_client(const X509_PURPOSE *xp, const X509 *x, @@ -420,6 +422,90 @@ static int check_sig_alg_match(const EVP_PKEY *issuer_key, const X509 *subject) return X509_V_ERR_SIGNATURE_ALGORITHM_MISMATCH; } +static unsigned long oid_hash(const void *p) +{ + const ASN1_OBJECT *a = p; + + return (unsigned long)ossl_fnv1a_hash((uint8_t *)a->data, a->length); +} + +static int oid_cmp(const void *a, const void *b) +{ + return OBJ_cmp((const ASN1_OBJECT *)a, (const ASN1_OBJECT *)b); +} + +/* + * Scan all extensions of a certificate to collect extension-related flags. + * Detects duplicate extensions (RFC 5280 section 4.2), the presence of a + * freshest CRL extension and unsupported critical extensions. + * + * In the future, if needed, this scanning function could return the index + * of the offending extension on error, allowing the caller to identify which + * extension caused the problem and report it via ERR_raise_data(). + */ +static void scan_ext_flags(const X509 *x509, uint32_t *flags) +{ + OPENSSL_LHASH *h = NULL; + uint8_t ex_bitset[(NUM_NID + 7) / 8]; + + memset(ex_bitset, 0, sizeof(ex_bitset)); + /* A certificate MUST NOT include more than one instance of an extension. */ + for (int i = 0; i < X509_get_ext_count(x509); i++) { + const X509_EXTENSION *ex = X509_get_ext(x509, i); + const ASN1_OBJECT *a = X509_EXTENSION_get_object(ex); + int nid = OBJ_obj2nid(a); + + /* + * Known NIDs within the build-time bitset limit are checked for + * duplicates in constant time. Unknown OIDs and dynamically registered + * NIDs that exceed the limit fall back to duplicate detection via a + * hash table. + */ + if (nid > NID_undef && nid < NUM_NID) { + unsigned int ex_bit = nid; + + if ((ex_bitset[ex_bit >> 3] & (1u << (ex_bit & 7))) != 0) { + *flags |= EXFLAG_DUPLICATE; + break; + } + ex_bitset[ex_bit >> 3] |= (1u << (ex_bit & 7)); + } else { + /* + * Extensions with unknown NID (NID_undef) and dynamically + * registered NIDs are handled here by hashing the OID (data/length). + * A zero-length OID should not reach this point, but we check for + * it anyway and assign the EXFLAG_INVALID flag if it does. + */ + if (a->length < 1) { + *flags |= EXFLAG_INVALID; + break; + } + /* + * Hashing the OID should be manageable more cheaply as well, and + * without additional dynamic allocations. In the case of this + * corner case, it’s not a problem at all, but the other duplicate + * detections also require hashing, so for the sake of consistency + * it would make sense to use a cheaper construct here later as well. + */ + if (h == NULL && (h = OPENSSL_LH_new(oid_hash, oid_cmp)) == NULL) + break; + if (OPENSSL_LH_insert(h, (void *)a) != NULL) { + *flags |= EXFLAG_DUPLICATE; + break; + } + } + if (nid == NID_freshest_crl) + *flags |= EXFLAG_FRESHEST; + if (!X509_EXTENSION_get_critical(ex)) + continue; + if (!X509_supported_extension(ex)) { + *flags |= EXFLAG_CRITICAL; + break; + } + } + OPENSSL_LH_free(h); +} + #define V1_ROOT (EXFLAG_V1 | EXFLAG_SS) #define ku_reject(x, usage) \ (((x)->ex_flags & EXFLAG_KUSAGE) != 0 && ((x)->ex_kusage & (usage)) == 0) @@ -660,19 +746,7 @@ int ossl_x509v3_cache_extensions(const X509 *const_x) tmp_ex_flags |= EXFLAG_INVALID; #endif - for (i = 0; i < X509_get_ext_count(const_x); i++) { - const X509_EXTENSION *ex = X509_get_ext(const_x, i); - int nid = OBJ_obj2nid(X509_EXTENSION_get_object(ex)); - - if (nid == NID_freshest_crl) - tmp_ex_flags |= EXFLAG_FRESHEST; - if (!X509_EXTENSION_get_critical(ex)) - continue; - if (!X509_supported_extension(ex)) { - tmp_ex_flags |= EXFLAG_CRITICAL; - break; - } - } + scan_ext_flags(const_x, &tmp_ex_flags); /* Set x->siginf, ignoring errors due to unsupported algos */ (void)ossl_x509_init_sig_info(const_x, &tmp_siginf); diff --git a/crypto/x509/x509_txt.c b/crypto/x509/x509_txt.c index 1b14957bf03..3a77e52f32b 100644 --- a/crypto/x509/x509_txt.c +++ b/crypto/x509/x509_txt.c @@ -227,6 +227,8 @@ const char *X509_verify_cert_error_string(long n) return "Empty Authority Key Identifier"; case X509_V_ERR_AKID_ISSUER_SERIAL_NOT_PAIRED: return "Authority Key Identifier issuer and serial number must be paired"; + case X509_V_ERR_DUPLICATE_EXTENSION: + return "Certificate includes more than one instance of a particular extension"; /* * Entries must be kept consistent with include/openssl/x509_vfy.h.in diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c index 5c3d4619c98..c2c39a1e498 100644 --- a/crypto/x509/x509_vfy.c +++ b/crypto/x509/x509_vfy.c @@ -625,6 +625,9 @@ static int check_extensions(X509_STORE_CTX *ctx) for (i = 0; i < num; i++) { x = sk_X509_value(ctx->chain, i); + /* RFC 5280, 4.2: a given extension MUST NOT appear more than once */ + CB_FAIL_IF((x->ex_flags & EXFLAG_DUPLICATE) != 0, + ctx, x, i, X509_V_ERR_DUPLICATE_EXTENSION); CB_FAIL_IF((ctx->param->flags & X509_V_FLAG_IGNORE_CRITICAL) == 0 && (x->ex_flags & EXFLAG_CRITICAL) != 0, ctx, x, i, X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION); diff --git a/doc/man1/openssl-verification-options.pod b/doc/man1/openssl-verification-options.pod index 9acff1b4d4a..2fd0881fa23 100644 --- a/doc/man1/openssl-verification-options.pod +++ b/doc/man1/openssl-verification-options.pod @@ -670,6 +670,13 @@ it checks that any present EKU extension (that does not contain B) contains the respective EKU as detailed below. Moreover, it does these checks even for trust anchor certificates. +=head3 Duplicate Extensions + +According to RFC 5280 section 4.2, a certificate MUST NOT include more +than one instance of a particular extension. If a duplicate extension is +detected, the certificate is rejected. +This check applies to all certificates: end-entity certificates, intermediate CA certificates, and root CA (trust anchor) certificates. + =head3 Checks Implied by Specific Predefined Policies A specific description of each check is given below. The comments about diff --git a/doc/man3/X509_STORE_CTX_get_error.pod b/doc/man3/X509_STORE_CTX_get_error.pod index 63bbd823b69..dd45b9ef89a 100644 --- a/doc/man3/X509_STORE_CTX_get_error.pod +++ b/doc/man3/X509_STORE_CTX_get_error.pod @@ -518,6 +518,10 @@ Authority Key Identifier has no attributes. Authority Key Identifier issuer and serial number must be paired. +=item B + +Certificate includes more than one instance of a particular extension. + =back =head1 NOTES diff --git a/include/openssl/x509_vfy.h.in b/include/openssl/x509_vfy.h.in index 8672d28c236..e6d19ff14c0 100644 --- a/include/openssl/x509_vfy.h.in +++ b/include/openssl/x509_vfy.h.in @@ -337,6 +337,8 @@ void X509_STORE_CTX_set_depth(X509_STORE_CTX *ctx, int depth); #define X509_V_ERR_EMPTY_AUTHORITY_KEY_IDENTIFIER 102 #define X509_V_ERR_AKID_ISSUER_SERIAL_NOT_PAIRED 103 +#define X509_V_ERR_DUPLICATE_EXTENSION 104 + /* Certificate verify flags */ #ifndef OPENSSL_NO_DEPRECATED_1_1_0 #define X509_V_FLAG_CB_ISSUER_CHECK 0x0 /* Deprecated */ diff --git a/include/openssl/x509v3.h.in b/include/openssl/x509v3.h.in index 6eb86273cec..4a0365f380d 100644 --- a/include/openssl/x509v3.h.in +++ b/include/openssl/x509v3.h.in @@ -455,6 +455,9 @@ struct ISSUING_DIST_POINT_st { #define EXFLAG_SKID_CRITICAL 0x40000 #define EXFLAG_SAN_CRITICAL 0x80000 +/* A certificate MUST NOT include more than one instance of an extension. */ +#define EXFLAG_DUPLICATE 0x200000 + /* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 */ #define KU_DIGITAL_SIGNATURE X509v3_KU_DIGITAL_SIGNATURE #define KU_NON_REPUDIATION X509v3_KU_NON_REPUDIATION diff --git a/test/x509_internal_test.c b/test/x509_internal_test.c index 8fbd3304bb1..19a7c2469d2 100644 --- a/test/x509_internal_test.c +++ b/test/x509_internal_test.c @@ -688,6 +688,257 @@ err: return test; } +/* https://github.com/openssl/openssl/issues/26325 */ +static const char *kRootExtensionDuplicity[] = { + "-----BEGIN CERTIFICATE-----\n", + "MIIDhDCCAmygAwIBAgIDCxQYMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNVBAYTAlVO\n", + "MQ8wDQYDVQQIDAZNeSBTVDExFTATBgNVBAcMDE1ZIExvY2FsaXR5MTEUMBIGA1UE\n", + "CgwLTVkgQ29tcGFueTExETAPBgNVBAsMCE15IFVuaXQxMRowGAYDVQQDDBF3d3cu\n", + "bXljb21wYW55LmNvbTAeFw0xOTA2MTkwODU1NTlaFw0yOTA2MTkwODU1NTlaMHox\n", + "CzAJBgNVBAYTAlVOMQ8wDQYDVQQIDAZNeSBTVDExFTATBgNVBAcMDE1ZIExvY2Fs\n", + "aXR5MTEUMBIGA1UECgwLTXkgQ29tcGFueTExETAPBgNVBAsMCE15IFVuaXQxMRow\n", + "GAYDVQQDDBF3d3cubXljb21wYW55LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP\n", + "ADCCAQoCggEBALVNKQrEfNWp3s0FOW+9RAjXOMvhAprV/FsWo6M72Mq/EwaV4Ny+\n", + "Q2CZ2Bs09KmRw43RG4dHHkB5/ewE7HhohQcHVH+tcWrM0IdgQIzKva2vICFZkp6O\n", + "am71qSe8+qtLSkzlTYJv4oeTLmMA2SSwTTP74hB29MS6O8scaLcM+OqfaGzr6k/Z\n", + "GnMMjI/zf4rbrLGPJcGGZ4jIMkrYm1PnwAwg6ijXrU0kb8DBgVvpmrluYfQdBvy6\n", + "bSib3P9ckyCGqqszn50qQZqqa2n6Ol/CBwRsCuYuhazRsBcXiULQ1lv2JQG86ILb\n", + "h/SXXfB4A6p0ti3tmcTMIPN5AI3y/EvUUwkCAwEAAaMTMBEwDwYDVR0TAQH/BAUw\n", + "AwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAS6joG5vUo2kMLX0bcpjKzE3h40ZypVgJ\n", + "bSCLu/alVcIDzdLTK/SOp2NMvtGmn+BMRvfzW+Lk58sMZ2QC3x+RZKHV+pDsT+Lj\n", + "Zi1bhpvtzrN62PmYZXGTu0xPME3SlBLilUFIRgH5lrxzlBdRURMCbHJOblAfzVdw\n", + "EBCtDVdGcox/mzu1Jo/sJQb59a49ZQpvwp7m7kZE0q6dBgElYX4JaRYhbwsv/tP2\n", + "jEA+jQYNORgFvCOkITbaO4Avc7BXSCGkDHoH6GsANf0bdtaMQCbUMeaC2CYUzRoC\n", + "fTEJ9LvFu7syeEDpUbgPXgRqpUQLyxoVYxWjXZ3CG5jeRmJzAEAoyg==\n", + "-----END CERTIFICATE-----\n", + NULL +}; + +static const char *kCertExtensionDuplicity[] = { + "-----BEGIN CERTIFICATE-----\n", + "MIIENDCCAxygAwIBAgIVASygDp5oUEVMj9bx6z7bj3ce5ypQMA0GCSqGSIb3DQEB\n", + "CwUAMHoxCzAJBgNVBAYTAlVOMQ8wDQYDVQQIDAZNeSBTVDExFTATBgNVBAcMDE1Z\n", + "IExvY2FsaXR5MTEUMBIGA1UECgwLTXkgQ29tcGFueTExETAPBgNVBAsMCE15IFVu\n", + "aXQxMRowGAYDVQQDDBF3d3cubXljb21wYW55LmNvbTAiGA8yMDE5MDYxOTA4NTU1\n", + "OVoYDzIwMjkwNjE5MDg1NTU5WjB7MQswCQYDVQQGEwJVTjEPMA0GA1UECAwGTXkg\n", + "U1QxMRUwEwYDVQQHDAxNWSBMb2NhbGl0eTExFDASBgNVBAoMC015IENvbXBhbnkx\n", + "MREwDwYDVQQLDAhNeSBVbml0MTEbMBkGA1UEAwwSd3d3Lm15Y29tcGFueTEuY29t\n", + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtU0pCsR81anezQU5b71E\n", + "CNc4y+ECmtX8WxajozvYyr8TBpXg3L5DYJnYGzT0qZHDjdEbh0ceQHn97ATseGiF\n", + "BwdUf61xaszQh2BAjMq9ra8gIVmSno5qbvWpJ7z6q0tKTOVNgm/ih5MuYwDZJLBN\n", + "M/viEHb0xLo7yxxotwz46p9obOvqT9kacwyMj/N/itussY8lwYZniMgyStibU+fA\n", + "DCDqKNetTSRvwMGBW+mauW5h9B0G/LptKJvc/1yTIIaqqzOfnSpBmqprafo6X8IH\n", + "BGwK5i6FrNGwFxeJQtDWW/YlAbzogtuH9Jdd8HgDqnS2Le2ZxMwg83kAjfL8S9RT\n", + "CQIDAQABo4GrMIGoMFIGCCsGAQUFBwELBEYwRDBCBggrBgEFBQcwBYY2ZnRwOi8v\n", + "NjYuMjMzLjIuMjM1L2Z8M2YvTUI5JT94dV89WEdlYXxIMFgmcTRpRm1YIXs9dS89\n", + "MFIGCCsGAQUFBwELBEYwRDBCBggrBgEFBQcwBYY2ZnRwOi8vNjouMjMzLjIuMjM1\n", + "L2Z8M2YvTUI5JT94dV89WEdlYXxIMFgmcTRpRm1YIXs9dS89MA0GCSqGSIb3DQEB\n", + "CwUAA4IBAQCoLSlKFFlg2xSGf9PFrXayO9ODk4pUkzb/+u0fsf6Vekwo/0dFNxSM\n", + "1sPtfoyprGMd7DK8R0rELq7k4+TaypV1JFBj9G9///dCTdX8Fg1SMRamIY0cs8Cu\n", + "VJCPWpLD6RQzZm9WkUqcc1yhjW8eO7OABazKwFQBLRS97ocztbyNvPbsZ0xInSMV\n", + "7E3xOj4XeibJ2y+EHUbMRDPtwZuy+E1m/kYScLAqIweVaxrWQnCC1HcARxL6eHx9\n", + "8kzGS23XAT9jLvdxwNs23GXiAjzxifJmR7oujP+uALF+FfHdJb7vr6l8lVNzRnDH\n", + "utv/6BamvgrYfDmA6GO3UItEgYozDtaN\n", + "-----END CERTIFICATE-----\n", + NULL +}; + +/* + * The following test checks for a duplicate extension with a known build-time + * NID, which is detected in constant time. + * */ +static int tests_x509_check_ext_duplicity(void) +{ + X509 *root = NULL, *leaf = NULL; + X509_STORE_CTX *ctx = NULL; + X509_STORE *store = NULL; + X509_VERIFY_PARAM *param = NULL; + STACK_OF(X509) *x509s = NULL; + const time_t verify_time = 1753284700; /* July 23th, 2025 */ + int test; + + test = TEST_ptr(ctx = X509_STORE_CTX_new()) + && TEST_ptr(store = X509_STORE_new()) + && TEST_ptr(param = X509_VERIFY_PARAM_new()) + && TEST_ptr(x509s = sk_X509_new_null()) + && TEST_ptr((root = X509_from_strings(kRootExtensionDuplicity))) + && TEST_ptr((leaf = X509_from_strings(kCertExtensionDuplicity))) + && TEST_true(X509_STORE_CTX_init(ctx, store, leaf, NULL)); + + if (test != 1) + goto err; + if (!TEST_true(sk_X509_push(x509s, root))) + goto err; + root = NULL; + + X509_STORE_CTX_set0_trusted_stack(ctx, x509s); + X509_VERIFY_PARAM_set_depth(param, 16); + X509_VERIFY_PARAM_set_time(param, verify_time); + X509_STORE_CTX_set0_param(ctx, param); + param = NULL; + ERR_clear_error(); + + test = TEST_int_eq(X509_verify_cert(ctx), 0) + && TEST_int_eq(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_DUPLICATE_EXTENSION); + +err: + OSSL_STACK_OF_X509_free(x509s); + X509_VERIFY_PARAM_free(param); + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + X509_free(leaf); + X509_free(root); + + return test; +} + +/* + * This test checks for a duplicate extension with an undefined NID, where the + * duplicate is detected via OID. + */ +static int tests_x509_check_ext_duplicity_nid_undef(void) +{ + X509 *root = NULL, *leaf = NULL; + X509_STORE_CTX *ctx = NULL; + X509_STORE *store = NULL; + X509_VERIFY_PARAM *param = NULL; + STACK_OF(X509) *x509s = NULL; + ASN1_OBJECT *obj1 = NULL, *obj2 = NULL; + ASN1_OCTET_STRING *oct1 = NULL, *oct2 = NULL; + X509_EXTENSION *ext1 = NULL, *ext2 = NULL; + const unsigned char data[] = { 0x04, 0x03, 0x41, 0x42, 0x43 }; + const time_t verify_time = 1753284700; /* July 23th, 2025 */ + const char *unknown_oid = "1.2.3.4.5.6.7.8.9"; + int test; + + test = TEST_ptr(ctx = X509_STORE_CTX_new()) + && TEST_ptr(store = X509_STORE_new()) + && TEST_ptr(param = X509_VERIFY_PARAM_new()) + && TEST_ptr(x509s = sk_X509_new_null()) + && TEST_ptr((root = X509_from_strings(kRootMendelsonAKIDKeyNULL))) + && TEST_ptr((leaf = X509_from_strings(kLeafMendelsonAKIDKeyNULL))) + && TEST_true(X509_STORE_CTX_init(ctx, store, leaf, NULL)) + && TEST_ptr(obj1 = OBJ_txt2obj(unknown_oid, 1)) + && TEST_ptr(oct1 = ASN1_OCTET_STRING_new()) + && TEST_int_eq(ASN1_OCTET_STRING_set(oct1, data, sizeof(data)), 1) + && TEST_ptr(ext1 = X509_EXTENSION_create_by_OBJ(NULL, obj1, 0, oct1)) + && TEST_int_eq(X509_add_ext(leaf, ext1, -1), 1) + && TEST_ptr(obj2 = OBJ_txt2obj(unknown_oid, 1)) + && TEST_ptr(oct2 = ASN1_OCTET_STRING_new()) + && TEST_int_eq(ASN1_OCTET_STRING_set(oct2, data, sizeof(data)), 1) + && TEST_ptr(ext2 = X509_EXTENSION_create_by_OBJ(NULL, obj2, 0, oct2)) + && TEST_int_eq(X509_add_ext(leaf, ext2, -1), 1); + + if (test != 1) + goto err; + if (!TEST_true(sk_X509_push(x509s, root))) + goto err; + root = NULL; + + X509_STORE_CTX_set0_trusted_stack(ctx, x509s); + X509_VERIFY_PARAM_set_depth(param, 16); + X509_VERIFY_PARAM_set_time(param, verify_time); + X509_STORE_CTX_set0_param(ctx, param); + param = NULL; + ERR_clear_error(); + + test = TEST_int_eq(X509_verify_cert(ctx), 0) + && TEST_int_eq(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_DUPLICATE_EXTENSION); + +err: + ASN1_OBJECT_free(obj1); + ASN1_OCTET_STRING_free(oct1); + X509_EXTENSION_free(ext1); + ASN1_OBJECT_free(obj2); + ASN1_OCTET_STRING_free(oct2); + X509_EXTENSION_free(ext2); + OSSL_STACK_OF_X509_free(x509s); + X509_VERIFY_PARAM_free(param); + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + X509_free(leaf); + X509_free(root); + + return test; +} + +/* + * This test checks for a duplicate extension with a dynamically registered NID, + * where the duplicate is detected via OID. + */ +static int tests_x509_check_ext_duplicity_nid_dynamic(void) +{ + X509 *root = NULL, *leaf = NULL; + X509_STORE_CTX *ctx = NULL; + X509_STORE *store = NULL; + X509_VERIFY_PARAM *param = NULL; + STACK_OF(X509) *x509s = NULL; + ASN1_OBJECT *obj1 = NULL, *obj2 = NULL; + ASN1_OCTET_STRING *oct1 = NULL, *oct2 = NULL; + X509_EXTENSION *ext1 = NULL, *ext2 = NULL; + const unsigned char data[] = { 0x04, 0x03, 0x41, 0x42, 0x43 }; + const time_t verify_time = 1753284700; /* July 23th, 2025 */ + const char *oid = "1.2.3.4.5.6.7.8.9"; + const char *sn = "testOID"; + const char *ln = "testOID Long Name"; + int nid; + int test; + + test = TEST_ptr(ctx = X509_STORE_CTX_new()) + && TEST_ptr(store = X509_STORE_new()) + && TEST_ptr(param = X509_VERIFY_PARAM_new()) + && TEST_ptr(x509s = sk_X509_new_null()) + && TEST_ptr((root = X509_from_strings(kRootMendelsonAKIDKeyNULL))) + && TEST_ptr((leaf = X509_from_strings(kLeafMendelsonAKIDKeyNULL))) + && TEST_true(X509_STORE_CTX_init(ctx, store, leaf, NULL)) + && TEST_true((nid = OBJ_create(oid, sn, ln)) != NID_undef) + && TEST_ptr(obj1 = OBJ_nid2obj(nid)) + && TEST_ptr(oct1 = ASN1_OCTET_STRING_new()) + && TEST_int_eq(ASN1_OCTET_STRING_set(oct1, data, sizeof(data)), 1) + && TEST_ptr(ext1 = X509_EXTENSION_create_by_OBJ(NULL, obj1, 0, oct1)) + && TEST_int_eq(X509_add_ext(leaf, ext1, -1), 1) + && TEST_ptr(obj2 = OBJ_nid2obj(nid)) + && TEST_ptr(oct2 = ASN1_OCTET_STRING_new()) + && TEST_int_eq(ASN1_OCTET_STRING_set(oct2, data, sizeof(data)), 1) + && TEST_ptr(ext2 = X509_EXTENSION_create_by_OBJ(NULL, obj2, 0, oct2)) + && TEST_int_eq(X509_add_ext(leaf, ext2, -1), 1); + + if (test != 1) + goto err; + if (!TEST_true(sk_X509_push(x509s, root))) + goto err; + root = NULL; + + X509_STORE_CTX_set0_trusted_stack(ctx, x509s); + X509_VERIFY_PARAM_set_depth(param, 16); + X509_VERIFY_PARAM_set_time(param, verify_time); + X509_STORE_CTX_set0_param(ctx, param); + param = NULL; + ERR_clear_error(); + + test = TEST_int_eq(X509_verify_cert(ctx), 0) + && TEST_int_eq(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_DUPLICATE_EXTENSION); + +err: + ASN1_OBJECT_free(obj1); + ASN1_OCTET_STRING_free(oct1); + X509_EXTENSION_free(ext1); + ASN1_OBJECT_free(obj2); + ASN1_OCTET_STRING_free(oct2); + X509_EXTENSION_free(ext2); + OSSL_STACK_OF_X509_free(x509s); + X509_VERIFY_PARAM_free(param); + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + X509_free(leaf); + X509_free(root); + + return test; +} + int setup_tests(void) { ADD_TEST(test_standard_exts); @@ -697,5 +948,9 @@ int setup_tests(void) ADD_TEST(tests_X509_check_crypto); ADD_TEST(tests_x509_check_dpn); ADD_TEST(tests_x509_check_akid); + ADD_TEST(tests_x509_check_ext_duplicity); + ADD_TEST(tests_x509_check_ext_duplicity_nid_undef); + ADD_TEST(tests_x509_check_ext_duplicity_nid_dynamic); + return 1; }