From a245620e2eee001b809742326f5e25d551a1c89e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 21 Jun 2026 18:56:02 +0900 Subject: [PATCH] x509store: add X509::Store{,Context}#flags and #clear_flags OpenSSL::X509::Store{,Context}#flags= does not clear existing flags. Instead, it ORs new flags to the current set. This is contrary to normal convention and likely unintentional, but changing the behavior would not be acceptable for compatibility reasons. Add #flags to get the current flags to allow users to explicitly express the intention with "store.flags |= OpenSSL::X509::V_...". Also, add #clear_flags to remove existing flags. The underlying OpenSSL C APIs appear to have been added in a patch release of OpenSSL 0.9.8. Warn in #flags= if the argument is not a superset of the existing flags. --- ext/openssl/ossl_x509store.c | 129 +++++++++++++++++++++++++++++++-- test/openssl/test_x509store.rb | 18 +++++ 2 files changed, 139 insertions(+), 8 deletions(-) diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c index 9e43336c4..bed124dc3 100644 --- a/ext/openssl/ossl_x509store.c +++ b/ext/openssl/ossl_x509store.c @@ -224,6 +224,41 @@ ossl_x509store_initialize(int argc, VALUE *argv, VALUE self) return self; } +/* + * call-seq: + * store.flags -> Integer + * + * Gets the verification flags for the Store. + * + * See also the man page X509_VERIFY_PARAM_get_flags(3). + */ +static VALUE +ossl_x509store_get_flags(VALUE self) +{ + X509_STORE *store; + X509_VERIFY_PARAM *vpm; + + GetX509Store(self, store); + vpm = X509_STORE_get0_param(store); + return ULONG2NUM(X509_VERIFY_PARAM_get_flags(vpm)); +} + +static void +x509vpm_set_flags_i(X509_VERIFY_PARAM *vpm, VALUE flags) +{ + unsigned long curr = X509_VERIFY_PARAM_get_flags(vpm); + unsigned long f = NUM2ULONG(flags); + + if ((curr | f) != f) { + rb_warn("`obj.flags = new_flags` does not clear existing flags; " \ + "use `obj.clear_flags` first if you want to replace them, " \ + "or `obj.flags |= new_flags` to indicate that " \ + "appending flags is intentional"); + } + if (!X509_VERIFY_PARAM_set_flags(vpm, f)) + ossl_raise(eX509StoreError, "X509_VERIFY_PARAM_set_flags"); +} + /* * call-seq: * store.flags = flags @@ -231,6 +266,10 @@ ossl_x509store_initialize(int argc, VALUE *argv, VALUE self) * Sets the default flags used by certificate chain verification performed with * the Store. * + * *NOTE*: Despite the name, this method appends the specified flags to the + * existing flag set rather than replacing it. To clear existing flags, use + * #clear_flags before calling this method. + * * _flags_ consists of zero or more of the constants defined in OpenSSL::X509 * with name V_FLAG_* or'ed together. * @@ -243,14 +282,42 @@ static VALUE ossl_x509store_set_flags(VALUE self, VALUE flags) { X509_STORE *store; - long f = NUM2LONG(flags); GetX509Store(self, store); - X509_STORE_set_flags(store, f); - + x509vpm_set_flags_i(X509_STORE_get0_param(store), flags); return flags; } +static void +x509vpm_clear_flags_i(X509_VERIFY_PARAM *vpm, VALUE flags) +{ + unsigned long f = NIL_P(flags) ? ~0UL : NUM2ULONG(flags); + + if (!X509_VERIFY_PARAM_clear_flags(vpm, f)) + ossl_raise(eX509StoreError, "X509_VERIFY_PARAM_clear_flags"); +} + +/* + * call-seq: + * store.clear_flags(flags = nil) + * + * Clears verification flags _flags_ for the Store. If _flags_ is omitted, + * clears all existing flags. + * + * See also the man page X509_VERIFY_PARAM_clear_flags(3). + */ +static VALUE +ossl_x509store_clear_flags(int argc, VALUE *argv, VALUE self) +{ + X509_STORE *store; + VALUE flags; + + rb_scan_args(argc, argv, "01", &flags); + GetX509Store(self, store); + x509vpm_clear_flags_i(X509_STORE_get0_param(store), flags); + return Qnil; +} + /* * call-seq: * store.purpose = purpose @@ -768,6 +835,25 @@ ossl_x509stctx_get_curr_crl(VALUE self) return ossl_x509crl_new(crl); } +/* + * call-seq: + * stctx.flags -> Integer + * + * Gets the verification flags for the context. + * + * See also the man page X509_VERIFY_PARAM_get_flags(3). + */ +static VALUE +ossl_x509stctx_get_flags(VALUE self) +{ + X509_STORE_CTX *ctx; + X509_VERIFY_PARAM *vpm; + + GetX509StCtx(self, ctx); + vpm = X509_STORE_CTX_get0_param(ctx); + return ULONG2NUM(X509_VERIFY_PARAM_get_flags(vpm)); +} + /* * call-seq: * stctx.flags = flags @@ -775,20 +861,43 @@ ossl_x509stctx_get_curr_crl(VALUE self) * Sets the verification flags to the context. This overrides the default value * set by Store#flags=. * + * *NOTE*: Despite the name, this method appends the specified flags to the + * existing flag set rather than replacing it. To clear existing flags, use + * #clear_flags before calling this method. + * * See also the man page X509_VERIFY_PARAM_set_flags(3). */ static VALUE ossl_x509stctx_set_flags(VALUE self, VALUE flags) { - X509_STORE_CTX *store; - long f = NUM2LONG(flags); - - GetX509StCtx(self, store); - X509_STORE_CTX_set_flags(store, f); + X509_STORE_CTX *ctx; + GetX509StCtx(self, ctx); + x509vpm_set_flags_i(X509_STORE_CTX_get0_param(ctx), flags); return flags; } +/* + * call-seq: + * stctx.clear_flags(flags = nil) + * + * Clears verification flags _flags_ for the Store. If _flags_ is omitted, + * clears all existing flags. + * + * See also the man page X509_VERIFY_PARAM_clear_flags(3). + */ +static VALUE +ossl_x509stctx_clear_flags(int argc, VALUE *argv, VALUE self) +{ + X509_STORE_CTX *ctx; + VALUE flags; + + rb_scan_args(argc, argv, "01", &flags); + GetX509StCtx(self, ctx); + x509vpm_clear_flags_i(X509_STORE_CTX_get0_param(ctx), flags); + return Qnil; +} + /* * call-seq: * stctx.purpose = purpose @@ -944,7 +1053,9 @@ Init_ossl_x509store(void) rb_define_method(cX509Store, "initialize", ossl_x509store_initialize, -1); rb_undef_method(cX509Store, "initialize_copy"); rb_define_method(cX509Store, "verify_callback=", ossl_x509store_set_vfy_cb, 1); + rb_define_method(cX509Store, "flags", ossl_x509store_get_flags, 0); rb_define_method(cX509Store, "flags=", ossl_x509store_set_flags, 1); + rb_define_method(cX509Store, "clear_flags", ossl_x509store_clear_flags, -1); rb_define_method(cX509Store, "purpose=", ossl_x509store_set_purpose, 1); rb_define_method(cX509Store, "trust=", ossl_x509store_set_trust, 1); rb_define_method(cX509Store, "time=", ossl_x509store_set_time, 1); @@ -973,7 +1084,9 @@ Init_ossl_x509store(void) rb_define_method(cX509StoreContext, "error_depth", ossl_x509stctx_get_err_depth, 0); rb_define_method(cX509StoreContext, "current_cert", ossl_x509stctx_get_curr_cert, 0); rb_define_method(cX509StoreContext, "current_crl", ossl_x509stctx_get_curr_crl, 0); + rb_define_method(cX509StoreContext, "flags", ossl_x509stctx_get_flags, 0); rb_define_method(cX509StoreContext, "flags=", ossl_x509stctx_set_flags, 1); + rb_define_method(cX509StoreContext, "clear_flags", ossl_x509stctx_clear_flags, -1); rb_define_method(cX509StoreContext, "purpose=", ossl_x509stctx_set_purpose, 1); rb_define_method(cX509StoreContext, "trust=", ossl_x509stctx_set_trust, 1); rb_define_method(cX509StoreContext, "time=", ossl_x509stctx_set_time, 1); diff --git a/test/openssl/test_x509store.rb b/test/openssl/test_x509store.rb index c13beae36..6385a46d9 100644 --- a/test/openssl/test_x509store.rb +++ b/test/openssl/test_x509store.rb @@ -370,6 +370,24 @@ def test_dup assert_raise(NoMethodError) { ctx.dup } end + def test_flags_clear + store = OpenSSL::X509::Store.new + assert_equal(0, store.flags) + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK + assert_equal(OpenSSL::X509::V_FLAG_CRL_CHECK, store.flags) + assert_warning(/clear_flags/) { + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL + } + assert_equal( + OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL, + store.flags + ) + store.clear_flags(OpenSSL::X509::V_FLAG_CRL_CHECK_ALL) + assert_equal(OpenSSL::X509::V_FLAG_CRL_CHECK, store.flags) + store.clear_flags + assert_equal(0, store.flags) + end + def test_ctx_cleanup # Deprecated in Ruby 1.9.3 cert = OpenSSL::X509::Certificate.new