diff --git a/crypto/x509/internal.h b/crypto/x509/internal.h index c66b29f4f0..950d0b0862 100644 --- a/crypto/x509/internal.h +++ b/crypto/x509/internal.h @@ -344,6 +344,8 @@ struct x509_store_ctx_st { X509_STORE_CTX_verify_cb verify_cb; // error callback X509_STORE_CTX_get_crl_fn get_crl; // retrieve CRL X509_STORE_CTX_check_crl_fn check_crl; // Check CRL validity + X509_STORE_CTX_verify_crit_oids_cb + verify_custom_crit_oids; // Check custom critical oids // The following is built up @@ -359,6 +361,9 @@ struct x509_store_ctx_st { int current_crl_score; // score of current CRL + // Stack of allowed custom critical extension oids. + STACK_OF(ASN1_OBJECT) *custom_crit_oids; + CRYPTO_EX_DATA ex_data; } /* X509_STORE_CTX */; diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc index f269ba4d53..72481e029e 100644 --- a/crypto/x509/x509_test.cc +++ b/crypto/x509/x509_test.cc @@ -62,6 +62,67 @@ j2kCAwG+LLpGNmNwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBw5lmgITTEvXIj+8ls -----END CERTIFICATE----- )"; +static const char kX509CustomExtensionsCA[] = R"( +-----BEGIN CERTIFICATE----- +MIIBxzCCAW2gAwIBAgIFAQAAAAAwCgYIKoZIzj0EAwIwJjEPMA0GA1UECgwGQW1h +em9uMRMwEQYDVQQpDAo0Mjk0OTY3Mjk2MCIYDzIwMjUwMzI5MjA0OTE5WhgPOTk5 +OTEyMzEyMzU5NTlaMCYxDzANBgNVBAoMBkFtYXpvbjETMBEGA1UEKQwKNDI5NDk2 +NzI5NjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL3eDQzFx4cherBJdIQsxMzZ +rCtzXBTB3f/rRMLrtjxpk2/6h3ZbE4t8MmDbwVepAKYQgT1bjPUFn+edG2U8kRej +gYMwgYAwEgYDVR0TAQH/BAgwBgEB/wIBADBLBgNVHSMERDBCgBTMNuas7QD5hDXY +KS7k0WDN6ckMFqEqpCgwJjEPMA0GA1UECgwGQW1hem9uMRMwEQYDVQQpDAo0Mjk0 +OTY3Mjk2MB0GA1UdDgQWBBTMNuas7QD5hDXYKS7k0WDN6ckMFjAKBggqhkjOPQQD +AgNIADBFAiB3MJLK86+JyyoBr2s1Ugjvc7gWAHSk9OgXfyfsVmBV9gIhAPIUiYo8 +Jx+IbRyNj2WfeCbn8v3fob0wkGsKf1TSVcZ8 +-----END CERTIFICATE----- +)"; + +static const char kX509CustomExtensionsCert[] = R"( +-----BEGIN CERTIFICATE----- +MIIB6zCCAZGgAwIBAgIFAQAAAAAwCgYIKoZIzj0EAwIwJjEPMA0GA1UECgwGQW1h +em9uMRMwEQYDVQQpDAo0Mjk0OTY3Mjk2MCIYDzIwMjUwMzI5MjA0OTE5WhgPOTk5 +OTEyMzEyMzU5NTlaMBExDzANBgNVBAoMBkFtYXpvbjBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABNbNswB+jmoICPKu567Odfq83s9P0N82kFYnyANgmztgHqoK7yIX +0meBn5N9Y4m3wAmvokYeK7dU1oRSM397unmjgbwwgbkwDAYDVR0TAQH/BAIwADAO +BgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwEwFQYHK4E7gcR0 +BQEB/wQHcHJlc2VudDBLBgNVHSMERDBCgBTMNuas7QD5hDXYKS7k0WDN6ckMFqEq +pCgwJjEPMA0GA1UECgwGQW1hem9uMRMwEQYDVQQpDAo0Mjk0OTY3Mjk2MB0GA1Ud +DgQWBBRiexFm2K7Ou2dx4+c0LjQOsuqHJjAKBggqhkjOPQQDAgNIADBFAiAxH63Q +eK26A9QPOkqi+5Hvrptpb9HRstSC6emJdaEB1QIhAKyhyLBPrG85QDoXrFcVZUA2 ++StWnDVDGtgWM6tPz4Uw +-----END CERTIFICATE----- +)"; + +static const char kX509MultipleCustomExtensionsCA[] = R"( +-----BEGIN CERTIFICATE----- +MIIByDCCAW2gAwIBAgIFAQAAAAAwCgYIKoZIzj0EAwIwJjEPMA0GA1UECgwGQW1h +em9uMRMwEQYDVQQpDAo0Mjk0OTY3Mjk2MCIYDzIwMjUwMzMwMjExMzA1WhgPOTk5 +OTEyMzEyMzU5NTlaMCYxDzANBgNVBAoMBkFtYXpvbjETMBEGA1UEKQwKNDI5NDk2 +NzI5NjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABMhC2xZcc7UouUMo1xPMiq8E +Z7DdWJq0I9nPunowEwidaif/YU6tjAPVFPmcRIhRYvZH6HWyNc0gztcfgAxa7tej +gYMwgYAwEgYDVR0TAQH/BAgwBgEB/wIBADBLBgNVHSMERDBCgBRMh4uFf12IUZ1m +v3WCstI0aqCBdKEqpCgwJjEPMA0GA1UECgwGQW1hem9uMRMwEQYDVQQpDAo0Mjk0 +OTY3Mjk2MB0GA1UdDgQWBBRMh4uFf12IUZ1mv3WCstI0aqCBdDAKBggqhkjOPQQD +AgNJADBGAiEAyZK6Elt1iqVV1Rys4G8HmIE7/hRW3rbQWiNPd4FnANACIQCgbgki +hQaJgNo+8hOTEOQZsRSaIbu+F2afe6ncp996RQ== +-----END CERTIFICATE----- +)"; + +static const char kX509MultipleCustomExtensionsCert[] = R"( +-----BEGIN CERTIFICATE----- +MIICAjCCAaigAwIBAgIFAQAAAAAwCgYIKoZIzj0EAwIwJjEPMA0GA1UECgwGQW1h +em9uMRMwEQYDVQQpDAo0Mjk0OTY3Mjk2MCIYDzIwMjUwMzMwMjExMzA1WhgPOTk5 +OTEyMzEyMzU5NTlaMBExDzANBgNVBAoMBkFtYXpvbjBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABPVuvcRmJ8fqyZferbqGWP8Kd1yHHX+4gcglS5WV9Zt7T957fhNY +QpimdCfV+KEJji8IwBc7vOk+1Db3ulQ0dZejgdMwgdAwDAYDVR0TAQH/BAIwADAO +BgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwEwFQYHK4E7gcR0 +BQEB/wQHcHJlc2VudDAVBgcrgTuBxHQGAQH/BAdwcmVzZW50MEsGA1UdIwREMEKA +FEyHi4V/XYhRnWa/dYKy0jRqoIF0oSqkKDAmMQ8wDQYDVQQKDAZBbWF6b24xEzAR +BgNVBCkMCjQyOTQ5NjcyOTYwHQYDVR0OBBYEFGt+Hy7qdE2lFnnjYPGqeVvJ4uPf +MAoGCCqGSM49BAMCA0gAMEUCIQC4aXyPOO6asCwoG1pGGmODmAEMA2tAXXNp67Oo +hDO90wIgETGPNCQIHlvUXAfDmZdUPh+PKkv6paVhWMTXrsh19LQ= +-----END CERTIFICATE----- +)"; std::string GetTestData(const char *path); @@ -8208,3 +8269,141 @@ TEST(X509Test, Trust) { {intermediate.normal.get()}, {}, /*flags=*/0, set_server_trust)); } + +// A brief validation against the |oids| expected to be done by the consumer. +// This example simulates the consumer checking that the certificate has the +// correct number of unknown extensions and there aren't any duplicates. +static int verify_crit_oids_callback(X509_STORE_CTX *ctx, X509 *x509, + STACK_OF(ASN1_OBJECT) *oids) { + if (oids == nullptr) { + return 0; // Fail if no OIDs provided + } + size_t known_oid_count = sk_ASN1_OBJECT_num(oids); + size_t unknown_ext_count = 0; + int last_pos = X509_get_ext_by_critical(x509, 1, -1); + while (last_pos >= 0) { + const X509_EXTENSION *ext = X509_get_ext(x509, last_pos); + if (!X509_supported_extension(ext)) { + unknown_ext_count++; + } + last_pos = X509_get_ext_by_critical(x509, 1, last_pos); + } + return known_oid_count == unknown_ext_count; +} + +// Helper function to set up the basic verification context +static void SetupVerificationContext( + X509_STORE_CTX *ctx, const std::vector &custom_oids = {}, + bool set_callback = false) { + X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); + X509_VERIFY_PARAM_set_time_posix(param, 1745884800); // Apr 28, 2025 + + for (const auto &oid : custom_oids) { + ASSERT_TRUE(X509_STORE_CTX_add_custom_crit_oid(ctx, oid)); + } + + if (set_callback) { + X509_STORE_CTX_set_verify_crit_oids(ctx, verify_crit_oids_callback); + } +} + +TEST(X509Test, X509CustomExtensions) { + bssl::UniquePtr cert(CertFromPEM(kX509CustomExtensionsCert)); + ASSERT_TRUE(cert); + bssl::UniquePtr ca(CertFromPEM(kX509CustomExtensionsCA)); + ASSERT_TRUE(ca); + + // Check that the cert has been marked as |EXFLAG_CRITICAL|. + EXPECT_TRUE(X509_get_extension_flags(cert.get()) & EXFLAG_CRITICAL); + + bssl::UniquePtr custom_oid(OBJ_txt2obj("1.3.187.25204.5", 1)); + ASSERT_TRUE(custom_oid); + + // A typical call to |X509_verify_cert| without any set up would fail due to + // the unknown critical extensions. + auto typical_setup = [&](X509_STORE_CTX *ctx) { + SetupVerificationContext(ctx, {}, false); + }; + EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, + Verify(cert.get(), {ca.get()}, {}, {}, + /*flags=*/0, typical_setup)); + + // Unknown critical certificate extensions aren't enabled without the + // callback. + auto set_custom_ext_with_no_callback = [&](X509_STORE_CTX *ctx) { + SetupVerificationContext(ctx, {custom_oid.get()}, false); + }; + EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, + Verify(cert.get(), {ca.get()}, {}, {}, + /*flags=*/0, set_custom_ext_with_no_callback)); + + // Unknown critical certificate extensions aren't enabled, when only the + // callback is enabled, but no custom oids are set. + auto set_no_custom_ext_with_callback = [&](X509_STORE_CTX *ctx) { + SetupVerificationContext(ctx, {}, true); + }; + EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, + Verify(cert.get(), {ca.get()}, {}, {}, + /*flags=*/0, set_no_custom_ext_with_callback)); + + // This correctly sets up |ctx| with a custom critical extension and the + // |verify_crit_oids| callback. + auto set_custom_ext_with_callback = [&](X509_STORE_CTX *ctx) { + SetupVerificationContext(ctx, {custom_oid.get()}, true); + }; + EXPECT_EQ(X509_V_OK, + Verify(cert.get(), {ca.get()}, {}, {}, /*flags=*/0, + set_custom_ext_with_callback)); + // Check that |EXFLAG_CRITICAL| has been removed after validation. + EXPECT_FALSE(X509_get_extension_flags(cert.get()) & EXFLAG_CRITICAL); +} + +TEST(X509Test, X509MultipleCustomExtensions) { + bssl::UniquePtr cert(CertFromPEM(kX509MultipleCustomExtensionsCert)); + ASSERT_TRUE(cert); + bssl::UniquePtr ca(CertFromPEM(kX509MultipleCustomExtensionsCA)); + ASSERT_TRUE(ca); + + // Check that the cert has been marked as |EXFLAG_CRITICAL|. + EXPECT_TRUE(X509_get_extension_flags(cert.get()) & EXFLAG_CRITICAL); + + bssl::UniquePtr custom_oid(OBJ_txt2obj("1.3.187.25204.5", 1)); + ASSERT_TRUE(custom_oid); + bssl::UniquePtr custom_oid2(OBJ_txt2obj("1.3.187.25204.6", 1)); + ASSERT_TRUE(custom_oid2); + + // The result should be |X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION| since only + // one custom critical extension was set. Both extensions are needed since the + // cert contains two unknown extensions. + auto set_single_custom_ext = [&](X509_STORE_CTX *ctx) { + SetupVerificationContext(ctx, {custom_oid.get()}, true); + }; + EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, + Verify(cert.get(), {ca.get()}, {}, {}, + /*flags=*/0, set_single_custom_ext)); + auto set_other_custom_ext = [&](X509_STORE_CTX *ctx) { + SetupVerificationContext(ctx, {custom_oid2.get()}, true); + }; + EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, + Verify(cert.get(), {ca.get()}, {}, {}, + /*flags=*/0, set_other_custom_ext)); + + // Verification should not pass if all custom critical extensions are set, but + // the |verify_crit_oids| callback is not configured. + auto only_custom_exts_set = [&](X509_STORE_CTX *ctx) { + SetupVerificationContext(ctx, {custom_oid.get(), custom_oid2.get()}, false); + }; + EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, + Verify(cert.get(), {ca.get()}, {}, {}, + /*flags=*/0, only_custom_exts_set)); + + // Verification should only pass if all custom critical extensions are set, and + // the |verify_crit_oids| callback is configured. + auto set_custom_exts_with_callback = [&](X509_STORE_CTX *ctx) { + SetupVerificationContext(ctx, {custom_oid.get(), custom_oid2.get()}, true); + }; + EXPECT_EQ(X509_V_OK, Verify(cert.get(), {ca.get()}, {}, {}, + /*flags=*/0, set_custom_exts_with_callback)); + // Check that |EXFLAG_CRITICAL| has been removed after validation. + EXPECT_FALSE(X509_get_extension_flags(cert.get()) & EXFLAG_CRITICAL); +} diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c index 8d1d91d301..fbff3fed46 100644 --- a/crypto/x509/x509_vfy.c +++ b/crypto/x509/x509_vfy.c @@ -122,6 +122,13 @@ static int cert_crl(X509_STORE_CTX *ctx, X509_CRL *crl, X509 *x); static int internal_verify(X509_STORE_CTX *ctx); static int null_callback(int ok, X509_STORE_CTX *e) { return ok; } +static int null_verify_custom_crit_oids_callback(X509_STORE_CTX *ctx, + X509 *x509, + STACK_OF(ASN1_OBJECT) *oids) { + // This returns 0 by default, so that the callback must be configured by the + // user when enabling the custom critical extensions feature. + return 0; +} // cert_self_signed checks if |x| is self-signed. If |x| is valid, it returns // one and sets |*out_is_self_signed| to the result. If |x| is invalid, it @@ -519,7 +526,7 @@ static X509 *find_issuer(X509_STORE_CTX *ctx, STACK_OF(X509) *sk, X509 *x) { issuer = sk_X509_value(sk, i); if (x509_check_issued_with_callback(ctx, x, issuer)) { candidate = issuer; - if (x509_check_cert_time(ctx, candidate, /*suppress_error*/1)) { + if (x509_check_cert_time(ctx, candidate, /*suppress_error*/ 1)) { break; } } @@ -561,6 +568,71 @@ static int get_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x) { return X509_STORE_CTX_get1_issuer(issuer, ctx, x); } +static int check_custom_critical_extensions(X509_STORE_CTX *ctx, X509 *x) { + if (ctx->custom_crit_oids == NULL) { + // Fail if custom critical extensions are enabled, but none were set. + return 0; + } + size_t known_oid_count = sk_ASN1_OBJECT_num(ctx->custom_crit_oids); + if (known_oid_count == 0) { + return 0; + } + + // Allocate |found_exts| to pass to the callback. + STACK_OF(ASN1_OBJECT) *found_exts = sk_ASN1_OBJECT_new_null(); + if (found_exts == NULL) { + return 0; + } + + // Iterate through all critical extensions of |x| and validate against the + // ones that aren't recognized by |X509_supported_extension|. + int last_pos = X509_get_ext_by_critical(x, 1, -1); + while (last_pos >= 0) { + const X509_EXTENSION *ext = X509_get_ext(x, last_pos); + if (!X509_supported_extension(ext)) { + int found = 0; + + // Iterate through all set |custom_crit_oids|. + for (size_t i = 0; i < known_oid_count; i++) { + const ASN1_OBJECT *known_ext = + sk_ASN1_OBJECT_value(ctx->custom_crit_oids, i); + if (OBJ_cmp(ext->object, known_ext) == 0) { + // |sk_ASN1_OBJECT_value| returns a direct pointer. + ASN1_OBJECT *dup_obj = OBJ_dup(known_ext); + if (dup_obj == NULL || !sk_ASN1_OBJECT_push(found_exts, dup_obj)) { + ASN1_OBJECT_free(dup_obj); + sk_ASN1_OBJECT_pop_free(found_exts, ASN1_OBJECT_free); + return 0; + } + found = 1; + break; + } + } + + if (!found) { + // If any critical extension isn't in our known list, return early. + sk_ASN1_OBJECT_pop_free(found_exts, ASN1_OBJECT_free); + return 0; + } + } + last_pos = X509_get_ext_by_critical(x, 1, last_pos); + } + + // If we get here, all unknown critical extensions in |x| were + // properly handled and we pass the ones that were found to the caller. + if (!ctx->verify_custom_crit_oids(ctx, x, found_exts)) { + sk_ASN1_OBJECT_pop_free(found_exts, ASN1_OBJECT_free); + return 0; + } + + // Remove the |EXFLAG_CRITICAL| flag from |x|, now that all unknown + // critical extensions have been handled. + x->ex_flags &= ~EXFLAG_CRITICAL; + + sk_ASN1_OBJECT_pop_free(found_exts, ASN1_OBJECT_free); + return 1; +} + // Check a certificate chains extensions for consistency with the supplied // purpose @@ -571,8 +643,14 @@ static int check_chain_extensions(X509_STORE_CTX *ctx) { // Check all untrusted certificates for (int i = 0; i < ctx->last_untrusted; i++) { X509 *x = sk_X509_value(ctx->chain, i); - if (!(ctx->param->flags & X509_V_FLAG_IGNORE_CRITICAL) && - (x->ex_flags & EXFLAG_CRITICAL)) { + if ( // OpenSSL's historic check for unknown critical extensions. + // |EXFLAG_CRITICAL| indicates an unsupported critical extension was + // found in |x| during the initial parsing of the certificate. + (!(ctx->param->flags & X509_V_FLAG_IGNORE_CRITICAL) && + (x->ex_flags & EXFLAG_CRITICAL)) && + // AWS-LC specific logic for enabling custom unknown critical + // extensions. + !check_custom_critical_extensions(ctx, x)) { ctx->error = X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION; ctx->error_depth = i; ctx->current_cert = x; @@ -1439,7 +1517,7 @@ static int internal_verify(X509_STORE_CTX *ctx) { } check_cert: - ok = x509_check_cert_time(ctx, xs, /*suppress_error*/0); + ok = x509_check_cert_time(ctx, xs, /*suppress_error*/ 0); if (!ok) { goto end; } @@ -1686,6 +1764,8 @@ int X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, X509 *x509, ctx->check_crl = check_crl; } + ctx->verify_custom_crit_oids = null_verify_custom_crit_oids_callback; + return 1; err: @@ -1714,6 +1794,7 @@ void X509_STORE_CTX_cleanup(X509_STORE_CTX *ctx) { CRYPTO_free_ex_data(&g_ex_data_class, ctx, &(ctx->ex_data)); X509_VERIFY_PARAM_free(ctx->param); sk_X509_pop_free(ctx->chain, X509_free); + sk_ASN1_OBJECT_pop_free(ctx->custom_crit_oids, ASN1_OBJECT_free); OPENSSL_memset(ctx, 0, sizeof(X509_STORE_CTX)); } @@ -1762,3 +1843,30 @@ void X509_STORE_CTX_set0_param(X509_STORE_CTX *ctx, X509_VERIFY_PARAM *param) { } ctx->param = param; } + +int X509_STORE_CTX_add_custom_crit_oid(X509_STORE_CTX *ctx, ASN1_OBJECT *oid) { + GUARD_PTR(ctx); + GUARD_PTR(oid); + + ASN1_OBJECT *oid_dup = OBJ_dup(oid); + if (oid_dup == NULL) { + return 0; + } + if (ctx->custom_crit_oids == NULL) { + ctx->custom_crit_oids = sk_ASN1_OBJECT_new_null(); + if (ctx->custom_crit_oids == NULL) { + return 0; + } + } + + if (!sk_ASN1_OBJECT_push(ctx->custom_crit_oids, oid_dup)) { + return 0; + } + return 1; +} + +void X509_STORE_CTX_set_verify_crit_oids( + X509_STORE_CTX *ctx, + X509_STORE_CTX_verify_crit_oids_cb verify_custom_crit_oids) { + ctx->verify_custom_crit_oids = verify_custom_crit_oids; +} diff --git a/include/openssl/x509.h b/include/openssl/x509.h index 16b2889068..430d261fb0 100644 --- a/include/openssl/x509.h +++ b/include/openssl/x509.h @@ -2903,6 +2903,38 @@ OPENSSL_EXPORT int X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose); // difference. OPENSSL_EXPORT int X509_STORE_CTX_set_trust(X509_STORE_CTX *ctx, int trust); +// X509_STORE_CTX_add_custom_crit_oid adds |oid| to the list of "known" critical +// extension OIDs in |ctx|. Typical OpenSSL/AWS-LC behavior returns an error if +// there are any unknown critical extensions present within the certificates +// being validated. This function lets users specify custom OIDs of any critical +// extensions that are within the certificates being validated, that they wish +// to allow. +// +// To properly consume this feature, the callback mechanism with +// |X509_STORE_CTX_set_verify_crit_oids| must be set. See its specific +// documentation for more details. +OPENSSL_EXPORT int X509_STORE_CTX_add_custom_crit_oid(X509_STORE_CTX *ctx, + ASN1_OBJECT *oid); + +// X509_STORE_CTX_verify_crit_oids is the callback signature for +// |X509_STORE_CTX_set_verify_crit_oids|. |ctx| is the context being used, +// |x509| represents the current certificate being validated, and |oids| +// is a stack of |ASN1_OBJECT|s representing unknown critical extension +// OIDs that were found in |x509| and match those previously registered via +// |X509_STORE_CTX_add_custom_crit_oid|. +typedef int (*X509_STORE_CTX_verify_crit_oids_cb)(X509_STORE_CTX *ctx, + X509 *x509, + STACK_OF(ASN1_OBJECT) *oids); + +// X509_STORE_CTX_set_verify_crit_oids sets the |verify_crit_oids| callback +// function for |ctx|. Consumers should be performing additional validation +// against the custom extension oids after or during the handshake with +// |X509_STORE_CTX_set_verify_crit_oids|. This callback forces users to validate +// their custom OIDs when processing unknown custom critical extensions. +OPENSSL_EXPORT void X509_STORE_CTX_set_verify_crit_oids( + X509_STORE_CTX *ctx, + X509_STORE_CTX_verify_crit_oids_cb verify_custom_crit_oids); + // Verification parameters //